Files
vd_960/vd960DBN/BLE/OnlyUpdateApp_Peripheral/APP/snapshot.c
T
wangfq 3d4814bffe fix(vd960DBN): init/clear 懒擦修复不断重启 — 34s 全量擦除超 IWDG 4s
现场: 烧录快照固件后 CH32V208 不断重启 + 串口乱码
根因: snap_init 首擦 751 扇区 ~34s (头在擦完才写→复位后仍全新→死循环);
      snap_clear 34s / offlog_clear 2.8s 同类; 均超 IWDG 4s
修复(懒擦): init/clear 只擦写指针起点扇区 ~45ms, 其余由环形写切扇区
      逻辑自动擦; clear 为逻辑清除 (count=0 旧数据不可读)
附带: usart_biz.c 非 0x7F 帧 %s 打印改 hex (Loop 数据当字符串=乱码源)
      snapshot.h 线程模型注释修正 (lup_process_frame 实际在主循环 uart_srv)
测试: test_snapshot 9例(新增 lazy_erase) + offlog/ble_offlog 回归全过
2026-08-12 18:31:56 +08:00

408 lines
16 KiB
C

/**
******************************************************************************
* @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 <string.h>
/* 编译期断言: 记录必须是 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)) {
/* 全新区: 懒擦 — 只擦当前写扇区 (数据扇区1) ~45ms, 其余扇区由环形写
切扇区时自动擦。原擦全部 751 扇区 ~34s → 主循环阻塞超 IWDG 4s → 复位 */
SPI_Flash_Erase_Sector(SNAP_DATA_BASE / 4096);
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);
}
}
/*===========================================================================
* 入队 (主循环上下文: uart_srv → lup_process_frame → iot_sensor_ingest)
* USART2 ISR 只逐字节喂 lup_feed_byte, 完整帧由主循环 uart_srv 处理 →
* 本函数不在中断里, 但保留 RAM 暂存 + flush 两段式: 批量落盘省 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)
{
if (!_ready) return;
/* 逻辑清除 + 只擦写指针起点扇区 (~45ms): count=0 使旧数据不可读,
其余扇区由环形写覆盖时自动擦。原擦全部 751 扇区 ~34s > IWDG 4s → 复位 */
SPI_Flash_Erase_Sector(SNAP_DATA_BASE / 4096);
_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();
}