/** ****************************************************************************** * @file snapshot.h * @author wfq * @version V1.0 * @date 2026-08-12 * @brief DBN 传感快照日志 (W25Qxx 外部 SPI NOR, 环形, 与事件日志区分区独立) * * 用途: 0xC0 传感帧 (4 线圈) 按上报节奏落盘, 断网期间也有现场波形可查; * 事件流管"发生了什么", 快照流管"波形长什么样"。 * * 分区 (ROADMAP 2026-08-12 新规划): * | 参数区 0x000000 64KB (固定) * | OTA 区 0x010000 512KB (固定) * | 事件区 0x090000 动态 (offlog 持有, g_offlog_part.area_size) * | 快照区 事件区后 剩余容量 (总容量 - 固定区 - 事件区) * 上电读 JEDEC ID (0x9F) 决定事件区/快照区容量 (g_snap_part 运行时分区表) * * 环形实现 (与 offlog 同模式): * - 扇区 0 (区内) = 头扇区, 存元数据 (boot_seq/写位置/条数), 扇区切换时更新 * - 数据扇区每扇区 64 条 x 64B 定长记录 (4096/64) * - 顺序写, 写满擦下一扇区 (环形覆盖), 天然磨损均衡 * - 上电从头部记录的写位置向后扫描, 找第一条无效记录 = 真实写位置 (掉电恢复) * * 线程模型 (关键): * - snap_enqueue(): 中断上下文 (USART2 ISR → lup_process_frame → iot_sensor_ingest), * 只做打包 + RAM 环形暂存 (memcpy, 无阻塞, 无 SPI 操作); 满时丢新不覆盖 * - snap_flush(): 主循环上下文 (iot_mqtt_publish_sensor), RAM 暂存 → SPI 落盘 * (扇区切换擦除 ~45ms 阻塞仅发生在主循环, 与 offlog 同底线) * - 单生产者 (中断) 单消费者 (主循环) 无锁环形: 生产者只写 tail, * 消费者只读 head, 留一格判满 * * 记录格式 SnapRec (64B 定长, 无 padding): * magic(1) len(1) flags(1) rsvd(1) | seq(4) ts_ms(4) boot_seq(2) rsvd2(2) | coils(48) * coils = 4 x 12B, 与 0xC0 线上线圈单元完全一致: * [0] 配置1 freq_level(2)|direction(1)|freq_type(1)|sensitivity(4) * [1] 条件 condition(4)|loop_state(1)|car_state(1)|misc_type(2) * [2..4] 频率 3B LE * [5..7] 变化量 3B LE (有符号补码) * [8..11] 杂项 4B LE ****************************************************************************** */ #ifndef _SNAPSHOT_H__ #define _SNAPSHOT_H__ #include #include "offlog.h" /* OFFLOG_CHIP_xxx + g_offlog_part (事件区大小) */ #include "loop_uart_proto.h" /* LUP_SensorReport / LUP_CoilSensor */ /*=========================================================================== * 分区与容量 *===========================================================================*/ /* 固定分区 (与 offlog 一致): 参数区 64KB + OTA 512KB */ #define SNAP_PARAM_SIZE 0x10000UL /* 参数区 64KB (固定) */ #define SNAP_OTA_SIZE 0x80000UL /* OTA 镜像暂存 512KB (固定) */ #define SNAP_FIXED_SIZE (SNAP_PARAM_SIZE + SNAP_OTA_SIZE) /* 0x090000 */ #define SNAP_HEAD_SECTOR 0 /* 区内扇区号: 头扇区 */ #define SNAP_REC_PER_SECTOR 64 /* 4096 / 64 */ /* 运行时分区表 (snap_init 由 JEDEC ID + g_offlog_part 填充; 默认 W25Q32) */ typedef struct { uint8_t chip; /* OFFLOG_CHIP_xxx */ uint32_t area_base; /* 快照区起始 (事件区后) */ uint32_t area_size; /* 快照区大小 (剩余容量) */ uint32_t data_sectors; /* 数据扇区数 (不含头扇区) */ uint32_t max_records; /* 最大记录数 = data_sectors * 64 */ } SnapPart; extern SnapPart g_snap_part; /* 兼容宏: 展开为运行时值 */ #define SNAP_DATA_BASE (g_snap_part.area_base + 4096UL) #define SNAP_AREA_SIZE (g_snap_part.area_size) #define SNAP_DATA_SECTOR_CNT (g_snap_part.data_sectors) #define SNAP_DATA_SIZE ((g_snap_part.data_sectors) * 4096UL) #define SNAP_MAX_RECORDS (g_snap_part.max_records) /* 头扇区记录 (32B, 扇区切换时更新) */ typedef struct { uint8_t magic[4]; /* "SNAP" */ uint32_t boot_seq; /* 上次上电序号 (本 boot 由 init 时 +1) */ uint32_t wr_sector; /* 上次写扇区号 (区内 1..n) */ uint32_t wr_off; /* 上次写偏移 (绝对地址) */ uint32_t wr_seq; /* 下一条记录全局 seq (掉电恢复锚点) */ uint32_t count; /* 有效记录数 (0..max_records) */ uint32_t rsvd[2]; } SnapHead; /* 28B, 按 32B 对齐使用 */ #define SNAP_HEAD_MAGIC0 'S' #define SNAP_HEAD_MAGIC1 'N' #define SNAP_HEAD_MAGIC2 'A' #define SNAP_HEAD_MAGIC3 'P' /* 快照记录 (64B 定长, 字段重排保证自然对齐无 padding: 4×uint8 → uint32×2 → uint16×2 → coils[48] = 16+48 = 64) */ typedef struct { uint8_t magic; /* 0xA6 (区别于事件 0xA5) */ uint8_t len; /* 线圈数据有效长度 = coil_count*12 (0..48) */ uint8_t flags; /* bit0: 分包帧 (预留) */ uint8_t rsvd; uint32_t seq; /* 全局序号 (跨 boot 递增, flush 时分配) */ uint32_t ts_ms; /* boot 内相对时间 mstick() (采集时刻) */ uint16_t boot_seq; /* 启动序号 */ uint16_t rsvd2; uint8_t coils[48]; /* 4 × 12B 线圈数据 (0xC0 线上格式原样) */ } SnapRec; /* 64B, 无 padding */ #define SNAP_MAGIC 0xA6 #define SNAP_REC_SIZE sizeof(SnapRec) #define SNAP_COIL_BYTES 12 #define SNAP_COILS_MAX 4 /* RAM 暂存深度 (中断 enqueue → 主循环 flush 之间的缓冲) Loop 帧最快 150ms, publish_sensor 每轮 flush, 8 深 ≈ 1.2s 余量; CH32V208 SRAM 紧张: 64B × 8 = 512B, 远小于 SPI_FLASH_BUF 的 4KB */ #define SNAP_RAM_DEPTH 8 #define SNAP_MAX_QUERY_RECORDS 2 /* 快照 QUERY 分页上限: (132-2)/64 */ /*=========================================================================== * API *===========================================================================*/ void snap_init(void); /* 上电初始化 (扫描恢复) */ uint8_t snap_enabled(void); /* Flash 初始化成功 */ void snap_enqueue(const LUP_SensorReport *sr); /* 中断安全: 打包→RAM 暂存 (满丢新) */ void snap_flush(void); /* 主循环: RAM 暂存→SPI 落盘 */ uint32_t snap_count(void); /* 有效记录数 */ uint32_t snap_seq_last(void); /* 最新一条记录全局序号 (0=空) */ uint32_t snap_boot_seq(void); /* 当前 boot 序号 */ int snap_read_idx(uint32_t idx, SnapRec *out); /* 按逻辑序号读, 0=成功 -1=越界 */ void snap_clear(void); /* 清空快照区 (审计写入事件流) */ #endif /* _SNAPSHOT_H__ */