/** ****************************************************************************** * @file snapshot.c * @author wfq * @version V1.0 * @date 2026-08-12 * @brief DBN 传感快照日志 — 环形实现 (与 offlog 同模式, 分区独立) * * 依赖: storage.c (SPI_Flash_*), cmcng.h (mstick), offlog.c (g_offlog_part + 审计) * 线程模型: enqueue 在中断 (USART2 ISR), flush/write_raw/clear 在主循环。 * 所有 SPI 操作只在主循环 (扇区擦除 ~45ms 阻塞可接受, 与 offlog 同底线)。 ****************************************************************************** */ #include "snapshot.h" #include "storage.h" #include "cmcng.h" #include /* 编译期断言: 记录必须是 64B (定长环形索引依赖) */ typedef char snap_rec_size_must_be_64[(sizeof(SnapRec) == 64) ? 1 : -1]; typedef char snap_head_size_must_be_32[(sizeof(SnapHead) == 32) ? 1 : -1]; /*=========================================================================== * 内部状态 (仅主循环访问: init/flush/clear/查询) *===========================================================================*/ static uint32_t _wr_off; /* 下一条记录写入的绝对地址 */ static uint16_t _wr_sector; /* 当前写扇区号 (区内 1..n) */ static uint32_t _wr_seq; /* 下一条记录的全局 seq */ static uint16_t _boot_seq; /* 当前 boot 序号 */ static uint32_t _count; /* 有效记录数 (0..max_records) */ static uint8_t _ready; /* init 完成标志 */ /* 中断安全 RAM 暂存 (单生产者=中断 enqueue / 单消费者=主循环 flush, 无锁) 生产者只写 g_snap_wr / g_snap_count, 消费者只读 g_snap_rd; count 满则丢新 */ static SnapRec g_snap_pending[SNAP_RAM_DEPTH]; static volatile uint8_t g_snap_rd; /* 消费者 (主循环) 读指针 */ static volatile uint8_t g_snap_wr; /* 生产者 (中断) 写指针 */ static volatile uint8_t g_snap_count; /* 暂存条数 (容量 = SNAP_RAM_DEPTH) */ static volatile uint32_t g_snap_drop; /* 满时丢弃计数 (调试用) */ /*=========================================================================== * 运行时分区表 (ROADMAP 2026-08-12: JEDEC ID + 事件区大小 → 快照区 = 剩余) *===========================================================================*/ SnapPart g_snap_part = { OFFLOG_CHIP_W25Q32, /* 默认 */ 0x110000UL, /* W25Q32: 0x090000 + 512KB 事件区 */ 0x2F0000UL, /* 3008KB = 4MB - 576KB - 512KB */ 751, /* 3008KB/4096 - 1 头扇区 */ 48064, /* 751 * 64 */ }; /* 芯片总容量 (W25Q32 4MB → W25Q256 32MB) */ static uint32_t snap_chip_total(uint8_t chip) { switch (chip) { case OFFLOG_CHIP_W25Q32: return 0x00400000UL; case OFFLOG_CHIP_W25Q64: return 0x00800000UL; case OFFLOG_CHIP_W25Q128: return 0x01000000UL; case OFFLOG_CHIP_W25Q256: return 0x02000000UL; default: return 0x00400000UL; /* 未知→默认 4MB */ } } /* 读 JEDEC ID → 填分区表。事件区大小与 offlog 同一事实来源 (g_offlog_part), 快照区 = 总容量 - 固定区 - 事件区。未知芯片按 W25Q32 兜底 */ static void snap_part_detect(void) { uint8_t dev = SPI_Flash_ReadJEDEC_ID(); uint8_t chip = OFFLOG_CHIP_W25Q32; uint32_t evt_size; uint32_t total; switch (dev) { case OFFLOG_CHIP_W25Q32: chip = dev; break; case OFFLOG_CHIP_W25Q64: chip = dev; break; case OFFLOG_CHIP_W25Q128: chip = dev; break; case OFFLOG_CHIP_W25Q256: chip = dev; break; default: chip = OFFLOG_CHIP_W25Q32; break; } evt_size = g_offlog_part.area_size; /* 与事件区严格一致 */ total = snap_chip_total(chip); g_snap_part.chip = chip; g_snap_part.area_base = SNAP_FIXED_SIZE + evt_size; g_snap_part.area_size = total - SNAP_FIXED_SIZE - evt_size; g_snap_part.data_sectors = (g_snap_part.area_size / 4096UL) - 1; g_snap_part.max_records = g_snap_part.data_sectors * SNAP_REC_PER_SECTOR; } /*=========================================================================== * 基础读写 *===========================================================================*/ static void snap_head_write(const SnapHead *h) { uint8_t buf[32]; memset(buf, 0xFF, sizeof(buf)); memcpy(buf, h, sizeof(SnapHead)); SPI_Flash_Write(buf, g_snap_part.area_base, sizeof(buf)); } static void snap_head_read(SnapHead *h) { uint8_t buf[32]; SPI_Flash_Read(buf, g_snap_part.area_base, sizeof(buf)); memcpy(h, buf, sizeof(SnapHead)); } static int snap_rec_read_at(uint32_t abs_off, SnapRec *out) { SPI_Flash_Read((uint8_t *)out, abs_off, SNAP_REC_SIZE); return (out->magic == SNAP_MAGIC) ? 0 : -1; } /*=========================================================================== * 上电扫描恢复 (与 offlog 同模式: 从头记录写位置向后扫, magic + seq 连续) *===========================================================================*/ static void snap_scan_recover(const SnapHead *h) { uint32_t pos = h->wr_off; uint32_t scanned = 0; uint32_t expect_seq = h->wr_seq; SnapRec r; /* 防御: wr_off 必须在数据区范围内且 64B 对齐 */ if (pos < SNAP_DATA_BASE || pos >= g_snap_part.area_base + g_snap_part.area_size) { pos = SNAP_DATA_BASE; } if ((pos - SNAP_DATA_BASE) % SNAP_REC_SIZE != 0) { pos = SNAP_DATA_BASE; } while (scanned < SNAP_MAX_RECORDS) { if (snap_rec_read_at(pos, &r) != 0) { break; /* 找到真实写位置 */ } if (expect_seq != 0 && r.seq != expect_seq) { break; /* seq 不连续 (损坏/残留) → 视为写位置 */ } pos += SNAP_REC_SIZE; if (pos >= SNAP_DATA_BASE + SNAP_DATA_SIZE) { pos = SNAP_DATA_BASE; /* 回绕数据区 */ } scanned++; expect_seq++; } _wr_off = pos; _wr_sector = (uint16_t)((pos - SNAP_DATA_BASE) / 4096) + 1; /* 区内 1..n */ _wr_seq = expect_seq; _count = h->count + scanned; if (_count > SNAP_MAX_RECORDS) _count = SNAP_MAX_RECORDS; _boot_seq = (uint16_t)(h->boot_seq + 1); if (_boot_seq == 0) _boot_seq = 1; } /*=========================================================================== * 初始化 *===========================================================================*/ void snap_init(void) { SnapHead h; snap_part_detect(); /* JEDEC ID + 事件区大小 → 快照区容量 */ memset(&h, 0xFF, sizeof(h)); snap_head_read(&h); if (!(h.magic[0] == SNAP_HEAD_MAGIC0 && h.magic[1] == SNAP_HEAD_MAGIC1 && h.magic[2] == SNAP_HEAD_MAGIC2 && h.magic[3] == SNAP_HEAD_MAGIC3)) { /* 全新区: 擦全部数据扇区, 写头 */ uint32_t i; for (i = 0; i < SNAP_DATA_SECTOR_CNT; i++) { SPI_Flash_Erase_Sector(SNAP_DATA_BASE / 4096 + i); } memset(&h, 0xFF, sizeof(h)); h.magic[0] = SNAP_HEAD_MAGIC0; h.magic[1] = SNAP_HEAD_MAGIC1; h.magic[2] = SNAP_HEAD_MAGIC2; h.magic[3] = SNAP_HEAD_MAGIC3; h.boot_seq = 0; h.wr_sector = 1; h.wr_off = SNAP_DATA_BASE; h.wr_seq = 0; h.count = 0; snap_head_write(&h); _wr_off = SNAP_DATA_BASE; _wr_sector = 1; _wr_seq = 1; _count = 0; _boot_seq = 1; } else { snap_scan_recover(&h); } /* 回写头: boot_seq 递增 */ h.magic[0] = SNAP_HEAD_MAGIC0; h.magic[1] = SNAP_HEAD_MAGIC1; h.magic[2] = SNAP_HEAD_MAGIC2; h.magic[3] = SNAP_HEAD_MAGIC3; h.boot_seq = _boot_seq; h.wr_sector = _wr_sector; h.wr_off = _wr_off; h.wr_seq = _wr_seq; h.count = _count; snap_head_write(&h); g_snap_rd = 0; g_snap_wr = 0; g_snap_count = 0; g_snap_drop = 0; _ready = 1; } /*=========================================================================== * 落盘 (主循环上下文) *===========================================================================*/ static void snap_flush_head(void) { SnapHead h; h.magic[0] = SNAP_HEAD_MAGIC0; h.magic[1] = SNAP_HEAD_MAGIC1; h.magic[2] = SNAP_HEAD_MAGIC2; h.magic[3] = SNAP_HEAD_MAGIC3; h.boot_seq = _boot_seq; h.wr_sector = _wr_sector; h.wr_off = _wr_off; h.wr_seq = _wr_seq; h.count = _count; snap_head_write(&h); } /* 目标扇区是否已有数据 (环形覆盖判定): 读首条记录 magic */ static int snap_sector_has_data(uint32_t abs_sec_off) { SnapRec r; SPI_Flash_Read((uint8_t *)&r, abs_sec_off, SNAP_REC_SIZE); return (r.magic == SNAP_MAGIC) ? 1 : 0; } /* 写一条记录 (仅主循环; rec->seq 已由 snap_flush 分配) */ static void snap_write_raw(const SnapRec *rec) { /* 环形回绕: 写指针越过数据区末尾 → 回到区首 */ if (_wr_off >= SNAP_DATA_BASE + SNAP_DATA_SIZE) { _wr_off = SNAP_DATA_BASE; } /* 切扇区判定: 写指针所在扇区与 _wr_sector 不一致 → 擦除目标扇区 */ { uint32_t sec_index = (_wr_off - SNAP_DATA_BASE) / 4096; /* 0..n-2 */ if ((uint32_t)(_wr_sector - 1) != sec_index) { uint32_t target_abs = (SNAP_DATA_BASE / 4096 + sec_index) * 4096; if (snap_sector_has_data(target_abs)) { uint32_t erased = (_count > SNAP_REC_PER_SECTOR) ? SNAP_REC_PER_SECTOR : _count; _count -= erased; } SPI_Flash_Erase_Sector(target_abs / 4096); _wr_sector = (uint16_t)(sec_index + 1); /* 区内 1..n */ snap_flush_head(); /* 扇区切换 → 更新头 (掉电恢复锚点) */ } } SPI_Flash_Write_NoCheck((uint8_t *)rec, _wr_off, SNAP_REC_SIZE); _wr_off += SNAP_REC_SIZE; _wr_seq++; if (_count < SNAP_MAX_RECORDS) _count++; } /*=========================================================================== * 打包 (与 0xC0 线上线圈单元 12B 格式一致) *===========================================================================*/ static void snap_pack_coil(const LUP_CoilSensor *cs, uint8_t *o) { o[0] = (uint8_t)((cs->freq_level << 6) | (cs->direction << 5) | (cs->freq_type << 4) | (cs->sensitivity & 0x0F)); o[1] = (uint8_t)((cs->condition << 4) | (cs->loop_state << 3) | (cs->car_state << 2) | (cs->misc_type & 0x03)); o[2] = (uint8_t)(cs->freq & 0xFF); o[3] = (uint8_t)((cs->freq >> 8) & 0xFF); o[4] = (uint8_t)((cs->freq >> 16) & 0xFF); o[5] = (uint8_t)(cs->variation & 0xFF); o[6] = (uint8_t)((cs->variation >> 8) & 0xFF); o[7] = (uint8_t)((cs->variation >> 16) & 0xFF); o[8] = (uint8_t)(cs->misc.passtime_ms & 0xFF); o[9] = (uint8_t)((cs->misc.passtime_ms >> 8) & 0xFF); o[10] = (uint8_t)((cs->misc.passtime_ms >> 16) & 0xFF); o[11] = (uint8_t)((cs->misc.passtime_ms >> 24) & 0xFF); } /* 打包传感帧 → SnapRec (seq 留待 flush 时分配; ts_ms/boot_seq 用采集时刻) */ static void snap_pack(const LUP_SensorReport *sr, SnapRec *rec) { uint8_t i, n; memset(rec, 0, sizeof(*rec)); rec->magic = SNAP_MAGIC; n = (sr->coil_count > SNAP_COILS_MAX) ? SNAP_COILS_MAX : sr->coil_count; rec->len = n * SNAP_COIL_BYTES; rec->ts_ms = mstick(); rec->boot_seq = _boot_seq; for (i = 0; i < n; i++) { snap_pack_coil(&sr->coils[i], rec->coils + i * SNAP_COIL_BYTES); } } /*=========================================================================== * 中断安全入队 (USART2 ISR 上下文; 无 SPI, 无阻塞; 满丢新不覆盖) * - 生产者只写 g_snap_wr / g_snap_pending / g_snap_count * - 消费者只读 g_snap_rd; count 为 volatile 字节, 单核单指令读写天然原子 * - 满判定: count >= DEPTH (容量 = SNAP_RAM_DEPTH) *===========================================================================*/ void snap_enqueue(const LUP_SensorReport *sr) { SnapRec rec; if (!_ready || sr == NULL) return; snap_pack(sr, &rec); if (g_snap_count >= SNAP_RAM_DEPTH) { g_snap_drop++; /* 暂存满 → 丢新帧 (快照环形, 可接受) */ return; } g_snap_pending[g_snap_wr] = rec; g_snap_wr = (uint8_t)((g_snap_wr + 1) & (SNAP_RAM_DEPTH - 1)); g_snap_count++; } /*=========================================================================== * 主循环落盘 (iot_mqtt_publish_sensor 每轮调用; 断网/未使能照常落) * - seq 在主循环分配 (中断不碰 _wr_seq, 避免共享状态) *===========================================================================*/ void snap_flush(void) { if (!_ready) return; while (g_snap_count > 0) { SnapRec *rec = &g_snap_pending[g_snap_rd]; rec->seq = _wr_seq; /* 主循环独占分配 */ snap_write_raw(rec); g_snap_rd = (uint8_t)((g_snap_rd + 1) & (SNAP_RAM_DEPTH - 1)); g_snap_count--; } if (g_snap_drop != 0) { g_snap_drop = 0; /* 丢弃计数清零 (调试观察点) */ } } /*=========================================================================== * 查询 *===========================================================================*/ uint8_t snap_enabled(void) { return _ready; } uint32_t snap_count(void) { return _count; } uint32_t snap_seq_last(void) { return (_wr_seq > 0) ? (_wr_seq - 1) : 0; } uint32_t snap_boot_seq(void) { return _boot_seq; } int snap_read_idx(uint32_t idx, SnapRec *out) { uint32_t cnt = _count; if (!_ready || idx >= cnt) return -1; /* 逻辑首 = 写位置向前 cnt 条 (环形) */ uint32_t phys = (_wr_off - SNAP_DATA_BASE); /* 0..DATA_SIZE */ uint32_t start = (phys + SNAP_DATA_SIZE - cnt * SNAP_REC_SIZE) % SNAP_DATA_SIZE; uint32_t read_off = SNAP_DATA_BASE + (start + idx * SNAP_REC_SIZE) % SNAP_DATA_SIZE; return snap_rec_read_at(read_off, out); } /*=========================================================================== * 清空 (审计: 事件流记一条 LOG_CLEAR, payload[0]=2=快照流) *===========================================================================*/ void snap_clear(void) { uint32_t i; if (!_ready) return; for (i = 0; i < SNAP_DATA_SECTOR_CNT; i++) { SPI_Flash_Erase_Sector(SNAP_DATA_BASE / 4096 + i); } _wr_off = SNAP_DATA_BASE; _wr_sector = 1; _count = 0; /* _wr_seq / _boot_seq 不重置: 序号单调递增, 保证快照全局唯一 */ { uint8_t p[1] = {2}; /* 2 = 快照流 (审计区分) */ offlog_evt(OFFLOG_EVT_LOG_CLEAR, p, 1); } snap_flush_head(); }