165 Commits
Author SHA1 Message Date
wangfq c879ed04e2 fix(vd960DBN): 0x8F 接入 DBN 私有指令处理器 + net_srv 裸 printf + devlog 行尾
按用户 2026-09-10 四条指示:

1) 0x8F 下行的"本地处理"明确为 CH32V208GBU6 私有指令 —— 只作用在自身读写配置上,
   一般不转发。实现: 不再留空壳计数, 直接复用 BLE 侧同一个分发器
   manage_dbn_ble_default() (dbn_ble_srv.c:729)。两侧帧布局完全一致
   (pkg[1]=Addr, pkg[2]=LEN, pkg[3]=CMD, pkg[4..]=Value), 零适配。
   响应仍走 g_buf_ble_response -> BLE 通知; 不回 4G —— Air780 侧 parser 只认 0x7F,
   回 0x8F 也没人接。
   增加 OTA 门控: g_flag_counter_ota.flag != 0 时拒绝私有指令 ——
   私有指令会写自身配置(碰 flash), 与 OTA 刷写并发是自找麻烦。
   魔数宏改用 dbn_ble_srv.h 的 MAGIC_BYTE_DBN_DEFAULT, 不再硬编码 0x8F。

2) 0x7D 帧: 删除/不理会。V1.11 曾提的 DBN<->Air780 握手/配置同步构想作废,
   代码里本就没实现, 正式协议也从未收录。devlog 待办项移除, 记入"已定案"。

3) net_srv.c:125 裸 printf() -> PRINT。该文件本已用 PRINT 53 处, 无需补 include。
   DEBUG=0 时随 PRINT 一起消失, 不再出现"往未初始化的 USART1 写字节"。

4) devlog.md 行尾统一 CRLF (修复 132 个孤立 LF: 顶部 V1.13 条目原为 LF)。

验证 (host 单测, 代码从 usart_biz.c 原样抽取, gcc -Wall -Wextra -Werror 零警告):
  15 组用例全部通过, 其中新增:
  - 0x8F -> manage_dbn_ble_default 被调用 1 次, 传入 len==全帧长, 内容逐字节一致, 未转发
  - OTA 期间 0x8F -> 处理器未被调用, 但仍计数 (可观测)
  原有 13 组 (魔数分流/最大帧/坏帧回找魔数/截断帧不丢帧/LEN 非法/0x9F 忽略...) 保持通过。

待板级验证: 需 MounRiver 工具链编译 + 实机 (本机无 riscv 工具链)。
2026-09-10 16:41:09 +08:00
wangfq d4d57f6487 feat(vd960DBN): UART1 ↔ Air780 4G 通道 (协议 §6.1 纯字节流透传)
需求(2026-09-10): 打通 vd960DBN 与 vd960Air(Air780) 两侧串口对接逻辑 ——
DBN 侧 UART1(PB6/PB7) 通道 + UART2↔UART1 双向透传。

协议依据 DLD960_IoT_MQTT协议 §6.1(V1.12 方案C):
  上行 Loop 0x7F(UART2) --原样转发--> UART1 --> Air780 解析转 JSON --> MQTT
  下行 平台 JSON --> Air780 --转 0x7F 帧--> DBN --0x7F(UART2)--> Loop
  DBN 只做字节流透传, 零业务转换; 魔数分流: 0x7F -> UART2, 0x8F -> DBN 本地

实现:
- usart_biz.c +242 行: UART1 通道整段
  * uart1_dma_init(): USART1 重映射 PB6(TX)/PB7(RX), 115200 8N1,
    RX 走 DMA1_Ch5 循环 512B (UART2 占 Ch6/Ch7, 不冲突)
  * uart1_feed_byte()/uart1_frame_ready()/uart1_resync(): 独立帧装配器
  * uart1_dispatch_frame(): 按魔数分流
  * uart1_dma_poll(): 主循环消费 DMA 缓冲
  * uart_srv() 内插入上行转发(484-490): 紧接 lup_process_frame 之后、
    InitPkgUart 消费之前, 且只转发 lup_verify_checksum()==0 的完整帧
    (位置必须在 BLE 分支改写 pkg[0]=0x8F 之前, 否则会给 Air780 发错魔数帧)
- cmcng.h: 声明 uart1_dma_init/poll + 5 个通道计数
- peripheral_main.c: 主循环加 uart1_dma_poll() (置于 MK_UART_SRV fault marker
  之外, 不改变既有 marker 语义)

三个关键决定:
1. 接收走 DMA 而非 RXNE 中断 —— 理由同 UART2 方案A(2026-08-17): PRINT 临界区
   关中断 ~7.4ms / BLE 栈回调 / SPI 擦除 45ms 等长阻塞窗口会屏蔽 RXNE 丢字节。
   "不丢帧"是 Air780 沿检测(loop_state 变化沿)判车的硬前提。
2. 帧装配器独立于 g_lup_parser —— 后者硬编码专供 UART2/Loop 通道, 两条流混进
   同一状态机必然互相残杀; 只复用纯函数 lup_verify_checksum()。
3. 坏帧回找魔数 resync —— 防"单帧损坏 → 后续帧全部失步、链路长时间瞎掉"。

与 printf 共用 USART1 (按用户 2026-09-10 指示: printf 串口不动, 4G 时禁用 debug):
- 开关 UART1_AIR780_EN, 默认 (DEBUG == 0)
- 4G 版本 -DDEBUG=0 编译即自动启用: #if(DEBUG) 为假 -> PRINT 空宏, 且
  USART_Printf_Init() 里 USART1 配置分支整段不编译(已核对 USART_Init/USART_Cmd
  均在 #if(DEBUG == DEBUG_UART1) 内)
- 调试版本通道为空实现; debug.h/debug.c 一行未改, printf 行为完全不变

验证:
- host 单测: 从 usart_biz.c 原样抽取装配器 + 逐字复刻 loop_uart_proto.c 的
  XOR/SUM 校验, gcc -Wall -Wextra -Werror 零警告, 13 组用例全部通过 ——
  含"坏帧后紧跟好帧仍送达"、"截断帧后只交付 1 帧完整好帧"、70B 最大帧、
  LEN 非法/超长、0x9F(Loop OTA 魔数)忽略、上行闸门只放行校验通过帧
- 待板级验证: 本机无 RISC-V 工具链, 固件编译与实机联调尚未进行

现场核对: 新增 g_uart1_fwd_to_air / _to_loop / _local_cnt / _badchk / _drop
计数, 判据为"g_uart1_fwd_to_air 与 Air780 侧实收帧数相等"

文档: devlog 新增 2026-09-10 条目(含接线说明 PB6/PB7 交叉 + 待办清单)
2026-09-10 16:31:54 +08:00
wangfq 3d9df52d1c feat(MQTT): 协议 V1.13 — initialize extra_info 增加 imei/iccid 可选字段
用户需求 2026-08-31:
- 协议 §5.1: extra_info 增加 imei(4G 模块 IMEI)/ iccid(流量卡 ICCID)可选字段
- 语义: 无 4G 模块省略/空串;4G 通道由 Air780 填真实值(§6.2)
- 固件 iot_send_initialize: extra_info 补 imei/iccid(空串占位, DBN 无 4G 信息;
  待"BLE→Air780 配置同步"实现后可回读填充)
- 同步: README 索引 V1.13 + devlog 置顶条目
2026-08-31 15:37:40 +08:00
wangfq 6e711e35ef release(vd960DBN): 固件版本 1.02.04 → 1.02.05 (2026-08-21)
涵盖 8-20 联网稳定性修复系列 + 8-21 MQTT 协议 V1.10 实现:
- SocketSend 0x11 退避 / SocketCreat 失败中止 (死循环闭环)
- OTA hex 提取兼容 json.dumps 空格
- OTA 刷写状态回 idle + ota_report 主动上报
- OTA 命令缓冲 union 合并 (RAM 90% 复位闭环)
- MQTT V1.10: dev_info_query/initialize 补 loop_ver/loop_hw_ver + loop_version_query 实时查询

cmcng.h 三段式一致: FIRMWARE_VER="1.02.05" + MAIN=1/SUB=2/SUBSUB=5
README 当前发布行 + 子项目版本表 → 1.02.05
CHANGELOG 新增 V1.02.05 条目 (配套矩阵: MQTT V1.07→V1.10)
devlog V4.8 条目补版本说明

产品手册/技术规格书留待打 tag 正式发布时同步
2026-08-21 08:55:01 +08:00
wangfq 181467b9e6 feat(vd960DBN): MQTT 协议 V1.10 固件实现 — 网络上报携带地感版本 (2026-08-21)
协议 V1.10 (3be9e50) 三处新增落地:
1. dev_info_query 响应 data 补 loop_ver/loop_hw_ver (0x4A 缓存值, 未完成/失败为空串)
2. initialize 上报同样携带 (尽力, 可为空)
3. 新增 §4.25 loop_version_query: 0x4A 异步实时查询, 响应在 iot_verq_poll() 回包

固件:
- loop_uart_proto.h/c: 新增 LoopVerCache + g_lup_ver_cache (0x4A 缓存);
  lup_cache_from_info() 写缓存, lup_refresh_version_cache() 后台刷新 (通道空闲才发)
- iot_mqtt_srv.c: dev_info_query/initialize 加字段; loop_version_query 分支
  (暂存 msg_id/deadline 300ms, busy 拒绝重复查询); iot_verq_poll() 异步回包
  (RESPONSE_READY -> code=0 + 三字段 + 同步刷缓存; TIMEOUT -> code=5 保缓存值);
  后台刷新: 上电 iot_mqtt_init + OTA done (iot_ota_report_result ok=1) 置 dirty

关键设计: 版本来源=0x4A 实时查询 (非 OTA 元数据 version=目标版本);
缓存值语义 dev_info_query/initialize 同步回包不等异步; 与 TCP 共用 g_lup_cmd
单命令通道 (后发覆盖, 先发者超时 — 低频命令, 待板上验证时序)

单测: tests/test_iot_loop_ver.c — gcc 隔离 (extract+embed 6 函数 + mock)
44 断言全过: 版本帧解析(正常+4 错误路径) / 缓存写入+格式化(空串/NULL 防护) /
verq_poll 成功回包(code=0+三字段+topic+缓存刷新+通道释放) / 超时回包(code=5+缓存值) /
后台刷新(发起/忙不消费/响应消费/残留超时清理)

验证: check_c_balance.py 3 文件 OK; 待板上验证 UART2 0x4A 与 loop_data 共用总线时序
2026-08-21 08:26:11 +08:00
wangfq 51298693da fix(vd960DBN): OTA 刷写成功误判未启动 — 状态回 idle + 补 ota_report 主动上报
现场: 远程平台发起 OTA, 设备物理刷写成功 (70块全ACK, flash DONE),
但平台 ota_status 轮询只见 state=ready → 误判'刷写未启动'。

根因:
1. ota_flash_done 刷写成功后台侧状态置 READY (与协议状态机
   'FLASHING──成功──▶IDLE(清槽/保留)' 不符), 平台无法区分
   '待刷 ready' 与 '已刷完' → 持续 ready 判未启动
2. 固件漏实现协议 §5.5 ota_report 主动上报 (done/failed 重发 3次×5s),
   平台收不到成功信号, 只能靠 ota_status 轮询兜底

修复 (协议 V1.09 + 固件):
- 协议: 刷写成功状态明确回 idle (镜像保留, last_result=0, 可重刷);
  ota_report 升级为刷写结果主依据; 平台判定指引 (idle+size>0+last_result=0
  =成功; 不得以轮询未见 flashing 或持续 ready 判未启动)
- ota_srv.c: ota_flash_done → OTA_STATE_IDLE (镜像保留) + 上报 done;
  ota_flash_fail → 补 ota_report failed (保留 event_report 告警);
  ota_cmd_begin 兼容 idle+size/crc32 一致 → 直接回 ready (免下载重刷)
- iot_mqtt_srv.c: 新增 ota_report 上报状态机 (立即首发 + 5s×3 重发,
  同 msg_id/ts), 共享 _iot_pub_payload (event_report 复用, RAM 零新增)
- 单测: test_flash_flow/test_flash_ready_timeout 断言 ota_report 上报,
  state 断言 READY→IDLE; 新增 test_begin_reflash; 10/10 全过 + 22 py 断言
2026-08-20 18:56:21 +08:00
wangfq 1badba3893 fix(vd960DBN): 联网 SocketSend 0x11 死循环 — 发送退避 + SocketCreat 失败中止
现象: loop_data 606B 发送 0x11(ERR_MEM) → 立即重试10次 → 3次断连 →
重连 SocketCreat 0x1D(ISCONN) 仍继续 Connect → timeout 死循环永久失联

根因:
1. WCHNET_NUM_TCP_SEG=2 (瘦身改小): 重传队列占用即无发送缓冲 → 0x11 瞬时
2. iot_mqtt_send 失败处理过激: 无退避立即重试 + 3次就强制断连
3. WCHNET_CreateTcpMqttSocket: SocketCreat 失败(mStopIfError只打印)仍继续
   Connect → 无效 socket 等超时死循环; SocketId_TCP 未置 0xFF 沿用旧 id

修复:
- iot_mqtt_send: 0x11 200ms 退避重试(≤10次, 等SEG释放), 不立即断连
- 强制重连: 补 SocketId_TCP=0xFF
- WCHNET_CreateTcpMqttSocket: SocketCreat 失败置 0xFF + return 不 Connect,
  上层指数退避

验证: 语法 0 错误; 待板级确认重发自动恢复 + 重连退避
2026-08-20 18:16:31 +08:00
wangfq c305ca651f fix(vd960DBN): OTA 下载失败 — hex 提取兼容 json.dumps 空格
现象: DBNMQTTool 第一片 ota_data code=1 crc/gap, received=0, 重试全败

根因: 工具 json.dumps 默认带空格 ("data": "hex"), 设备端 strstr 找
"data":" 无空格格式 → 提取失败 → hexbuf 空 → 解码失败 code=1
(simple_parse_json 容错空格所以其他命令正常, 只有手工 strstr 踩坑)

修复:
- ota_srv.c/h: 新增 ota_json_extract_hex() 兼容有无空格 (扫描 data 键跳过空白)
- iot_mqtt_srv.c: ota_data 改用该函数, 删 strstr 手工提取 + hp 残留
- 单测 +test_json_extract_hex: 无空格/带空格(事故场景)/512hex/找不到 4 场景

验证: 单测 11/11 PASS; 语法 0 错误; 待板级重测下载
2026-08-20 14:13:06 +08:00
wangfq 2484a03326 fix(vd960DBN): 联网复位事故 — OTA 命令缓冲合并 union (RAM 90% 栈余量不足)
现象: SUBACK 后收平台 report_config (PUBLISH len=197) → HardFault →
NVIC_SystemReset (RST_REASON 0x10000000 = SFT, 非 IWDG) → 死循环复位

根因: RAM 90.02% + 6 个 OTA 分支独立 static resp[256~400] + hexbuf[513]
共 ~2.5KB BSS → 栈余量被挤 → iot_handle_publish 深调用链栈溢出
(局部栈改 static 是伪优化: BSS↑=栈余量↓; 正解是 union 复用减总量)

修复:
- iot_mqtt_srv.c: 6 resp + hexbuf 合并函数级 static union _ota_io (~2KB 省)
- ota_srv.c: _chunk_buf/_fs_frame 合并 union (~260B 省) + 删无用变量

验证: 语法 0 错误; gcc 隔离单测 10/10; 待板级确认 RAM 回 ~88% 不再复位
2026-08-20 14:04:00 +08:00
wangfq 0b51d84a9b perf(vd960DBN): OTA RAM 优化 — union 复用块缓冲 + 命令分支数组改 static
MRS 编译 RAM 90.02% (44248/48KB) 逼近历史 .bss 挤栈红线, 减负:
- ota_srv.c: _chunk_buf[256] + _fs_frame[254] 合并为 union (下载/校验与刷写帧
  互斥复用, 省 ~260B static); ota_send_9f 组帧直接用 union 缓冲 (省 254B 栈)
- iot_mqtt_srv.c: 6 个 ota_* 分支 resp[256~400] + ota_data hexbuf[513] 局部栈
  数组改 static (栈峰值 → BSS, 防运行时栈溢出; RAM 总量不变但运行时安全)

验证: gcc 隔离单测 9/9 全过; 语法 0 新增错误
2026-08-20 13:41:33 +08:00
wangfq 49736c341c feat(vd960DBN): Loop MCU 远程 OTA 固件实现 (MQTT V1.08, ROADMAP P1.4 ①)
先存后刷: MQTT 分片下载 → W25Qxx OTA 暂存区 (0x010000 512KB) → 全镜像 CRC32
复核 → 本地 0x9F ISP 透传刷写 Loop MCU (AT32F421)

- 新增 ota_srv.c/h: CRC32(ISO-HDLC 逐位零表)/OtaMeta 双备份(节流 4KB flush)/
  会话状态机(begin/data/end/abort/flash)/本地刷写状态机(非阻塞 tick: A5→A6→A7
  停等 ACK 1s×3)/安全窗口(有车拒绝 force 跳过)/失败 event_report ota_error
- iot_mqtt_srv.c: 6 个 ota_* 命令分发 + 会话静默(event_report 积压/offlog·快照
  落盘暂停, 结束补发) + IOT_EVT_OTA_ERROR + iot_any_car
- usart_biz.c: uart_srv OTA 分支 ACK 分发 (本地刷写→ota_flash_feed_ack, BLE 透传不变)
- peripheral_main.c: ota_init + ota_poll 挂主循环
- offlog: OTA_START 0x60/OTA_RESULT 0x61 审计事件
- tests/test_ota_srv.c: gcc 隔离单测 9 组全过 (mock NOR/UART2/时钟, 嵌入真实实现)
  抓到 2 个真 bug: ①A7 序号未递减(bootloader 等不到末包) ②重发时 retry 清零(超时永不失败)
- 语法检查: ota_srv/iot_mqtt_srv/usart_biz/offlog 0 新增错误 (基线 interrupt 假阳性除外)
- devlog 置顶条目 + 待板上验证清单
2026-08-20 12:09:13 +08:00
wangfq d4ce0ba23a chore(vd960DBN): 固件版本 1.02.03 → 1.02.04 (SPI 存储适配)
内容: SPI Flash 识别去厂商代码 + 恢复 factory 配置写入 (板级验证通过)。
README/CHANGELOG/devlog 同步; 手册/规格书配套版本更新 (文档保持 V1.02, 同日补充不升版)
2026-08-19 15:23:35 +08:00
wangfq 8e58d5fdd1 fix(vd960DBN): 恢复 factory 配置写入 — 换新空片后配置永久丢失
8-13 止血(SPI 写触发复位)根因已 8-17 闭环(栈溢出+printf 重入, 非 SPI 问题),
offlog/snapshot 8-18 起持续 SPI 写入稳定 → 解除止血。
新空片(W25Q128)参数区无 magic + write_net_config 不写 magic → 每次上电
memory defaults, 保存配置永久丢失。恢复: mismatch 分支调 factory_dev_info()
写 magic+默认参数到 flash, 首次上电自动初始化。
2026-08-19 15:13:18 +08:00
wangfq f1d9ac4e48 fix(vd960DBN): SPI Flash 识别去厂商代码判断 — 兼容其他厂家同容量型号
- W25Qxx 宏 0XEF13~17 -> 0X13~17 (去掉厂商前缀, 只留设备 ID)
- SPI_Flash_ReadJEDEC_ID() 删除 id[0]!=0xEF||id[1]!=0x40 校验, 直接返回容量码 id[2]
  (JEDEC 容量码跨厂商标准化, offlog/snapshot 分区判断随之生效)
- storage_init() switch 改 Flash_Model & 0xFF 低字节匹配
- 换非华邦芯片 (如 0x1A 厂商) 时识别不再不一致
2026-08-19 14:45:30 +08:00
wangfq 832d2953b0 chore(vd960DBN): 固件版本 1.02.01 → 1.02.03 (2026-08-19)
OTA 0x9F 透传修复板级验证通过, 用户更新版本号三段式一致。
V1.02.03 内容: BLE→Loop OTA 修复 (0x9F 帧透传回 BLE) +
8-18 脱机日志快照流/hex 上报 + MQTT 查询命令补齐。
README/CHANGELOG/devlog 同步; 已知约束: OTA 模式无自动退出 (维持现状)
2026-08-19 11:20:03 +08:00
wangfq d67f955fd4 fix(vd960DBN): BLE→Loop OTA 失效修复 — 0x9F 帧透传回 BLE
DMA 改造(fed4335)后 UART2 RX 走 lup_feed_byte 只认 0x7F, Loop bootloader
回的 0x9F 响应帧(pre_ok/addr_ok/data ACK)全被吞; OTA 是停等协议
(0xA7 WITH_BACK 每块必回 ACK), 工具等不到 ACK 升级必然卡死。

- loop_uart_proto: 新增 lup_feed_byte_ota() 0x9F 帧状态机 (SUM 校验无 XOR,
  复用 g_lup_parser), 枚举追加 OTA 专用状态
- usart_biz: uart2_dma_poll 按 g_flag_counter_ota.flag 切换 0x9F/0x7F 解析器,
  模式切换 reset, OTA 溢出阈值放宽整缓冲, 补收帧 tick 归零;
  uart_srv OTA 分支 0x9F 帧透传回 BLE + 清 flag
- 单测 tests/test_lup_ota_parser.c 8 断言全过 (bootloader 真实帧向量)
- 遗留: g_flag_counter_ota.flag 无退出机制, 升级后需断电重启
2026-08-19 11:01:45 +08:00
wangfq 5a1893cd1c feat(vd960DBN)+fix(DBNMQTTool): log_query 改 hex 原始字节上报 (2026-08-18)
背景: MQTT 快照流实测 MQTTSerialize_publish failed — JSON 化快照记录 ~810B/条 超 800B 发送缓冲
方案(用户拍板): 对齐 BLE 通道, 原始字节 hex 上报

固件 (V4.3):
- offlog.c/h: 新增 offlog_evt_to_hex() (32B→64 hex)
- snapshot.c/h: 新增 snap_rec_to_hex() (64B→128 hex); 删 SNAP_MAX_QUERY_JSON, 恢复 count=2
- tcp_json_srv.c / iot_mqtt_srv.c: log_query 改 {"seq":N,"hex":"..."}; SEND_BUF 保持 800
- 2 条快照 hex 响应 406B < 800B

文档: TCP JSON V1.03 / MQTT V1.07 §4.17 records 改 hex + 解析表引用 BLE §6.4/§7

工具: parse_offlog_hex/parse_snap_hex/offlog_payload_desc + hex 展示; 验证: gcc 9 断言 + 工具解析全过 + offscreen UI
2026-08-18 14:04:26 +08:00
wangfq f2141976f0 feat(vd960DBN)+fix(DBNMQTTool): MQTT 查询命令补齐 + 工具命令集对齐 (2026-08-18)
固件 (V4.2):
- iot_mqtt_srv.c 补齐 ssc_net_query / iot_net_query / iot_topic_query (只读全局组包, 与 TCP JSON §4.5/4.7/4.9 对齐)
- 修复实测: MQTT 通道 ssc_net_query/iot_net_query 返回 code=4 unsupported

工具:
- 禁用固件未实现的 6 个按钮 (ssc/iot_net/iot_topic_set + pwd_set/factory_reset/device_reset) + tooltip 引导 TCP/BLE
- 设备刷新列表移除 loop_param_query (固件未实现)
- offscreen 验证按钮状态正确
2026-08-18 11:44:52 +08:00
wangfq f1c9358aad feat(vd960DBN): TCP/MQTT log_* 命令支持快照流 stream=snapshot (2026-08-18)
- snapshot.c/h: 新增 snap_rec_to_json() — SnapRec 64B → JSON (channels 对齐 0xC0, variation 3B 符号扩展, misc_type 全枚举)
- tcp_json_srv.c: handle_log_stat/query/clear 加 stream 解析 + snapshot 分支
- iot_mqtt_srv.c: log_stat/query/clear 加 stream 解析 + snapshot 分支
- 事件流 count=0 按上限处理 (与 BLE 对齐); 快照 QUERY count≤2, CLEAR ~45ms
- 新增 gcc 隔离单测 tests/test_snap_to_json.c 34 断言全过
- devlog V4.1
2026-08-18 09:01:22 +08:00
wangfq ae9f5eaf4c chore(vd960DBN): 固件版本 1.02.01 (三段式) + cmcng.h 注释修复 (2026-08-17)
用户更新:
- FIRMWARE_VER "1.02.01" (MAIN=1 SUB=2, 新增 FIRMWARE_VER_SUBSUB=1)
- 注释修复: GBK 乱码注释恢复中文 (文件 GBK→UTF-8 编码转换, 中文注释正常)
- 注意: BLE 上报仍为 MAIN/SUB 两字节 (1.02), SUBSUB 仅字符串上报使用
2026-08-17 17:19:40 +08:00
wangfq 263dc4e05d chore(vd960DBN): 固件版本 1.0 → 1.1 (2026-08-17)
cmcng.h FIRMWARE_VER "1.0"→"1.1" (MAIN=1 SUB=1):
- 修正字符串与数字不一致 (旧 "1.0" vs MAIN=1 SUB=1)
- 版本内容: 栈溢出修复 + RAM 瘦身 + UART2 RX DMA + 清理
- 注释用 ASCII (cmcng.h 为 GBK 编码)
2026-08-17 16:55:51 +08:00
wangfq 8c0f135629 chore(vd960DBN): 结案清理 — 移除临时诊断代码 (2026-08-17)
频繁复位问题闭环后清理:
- peripheral.c performPeriodicTask: 删 _dbg_cnt 500ms 调试心跳打印 (BLE 状态已由上层链路验证)
- peripheral_main.c: 删 g_boot_count(.noinit) + BOOT_CNT/END/SP 诊断打印块
- 保留 fault_diag 基础设施 + RST_REASON 一行 (诊断价值, 未来复用)
- 注意: peripheral.c 为 GBK+CRLF, 新注释用 ASCII
2026-08-17 16:42:59 +08:00
wangfq fed4335947 feat(vd960DBN): UART2 RX DMA 循环接收 — 根治打印关中断丢帧 (2026-08-17)
遗留项'UART2 偶发丢帧(1-3分钟一次 checksum fail)'落地:
- 根因: PRINT 临界区(关中断~7.4ms)屏蔽 USART2 RXNE 中断 → 丢字节
- 方案: DMA1_Ch6 循环模式硬件收字节(不依赖CPU中断), 主循环轮询消费
  - uart2_dma_init(): Ch6 循环模式 + 512B 环形缓冲(aligned(4)) + 关 RXNE
  - uart2_dma_poll(): 主循环读 DMA_GetCurrDataCounter 批量喂 lup_feed_byte
    (状态机移入主循环无竞争); 溢出保护(未消费>256B重置+丢帧计数)
  - uart_init() 末尾调 uart2_dma_init(); USART2_IRQHandler 清空留 IDLE 注释
- 资源: DMA1 全空闲(WCHNET 独立 ETH DMA/BLE 不用 DMA1) → Ch6 独占
- .bss +512B(缓冲), 栈 6.3KB→5.8KB 仍充裕
2026-08-17 15:59:16 +08:00
wangfq cb63c581be perf(vd960DBN): 二轮RAM瘦身 — RECE_BUF_LEN 1024 + ARP 16 + 中断路径static (2026-08-17)
.map 分析: BLE 栈固定占 RAM 低 16KB 不可裁; 用户 48KB 内 .bss 41.7KB。
- net_config.h: RECE_BUF_LEN MSS×2(1400)→1024 (MSS=700帧+头≈740B, 5数组省~2.6KB)
- net_config.h: WCHNET_NUM_ARP_TABLE 50→16 (停车场景IP少, 省~0.8KB)
- tcp_json_srv.c: tmp_buf[RECE_BUF_LEN]/frame[800] 局部→static (中断上下文栈数组防溢出)

栈 4.7KB→~6.3KB, 中断栈峰值 -1.8KB。devlog 记录 .map 分析结论。
2026-08-17 14:26:22 +08:00
wangfq 89ba602e4f fix(vd960DBN): 栈溢出修复闭环 — 打印恢复 + 字符串0终止保险 (2026-08-17)
RAM 瘦身后栈 1.9KB→~4.75KB (END 0x2000f834→0x2000ed3c 实测), printf 峰值安全:
- cfig_flash.c: 恢复 load_cfg 3 处调试 PRINT (magic mismatch×2 + Sub_Code)
- cfig_flash.c: 字符串 0 终止保险 (remote_addr/client_id/username/password/topic_pub/sub
  末字节强制 '\0', 防 Flash 字符串不足定长时 %s 越界打印)
- peripheral_main.c: 恢复 output_cfg_from_flash() (调试配置打印)
- 注释更新为修复闭环说明

设备实测: 无复位, MQTT 连 159.75.137.141:1883, BLE 广播正常
2026-08-17 14:18:52 +08:00
wangfq 421334ec48 perf(vd960DBN): RAM 瘦身 — .bss 省 4.8KB, 栈 1.9KB→~6.7KB (2026-08-17)
栈溢出根因确认后 .bss 瘦身四刀:
- net_config.h: ETH_MAX_PACKET_SIZE 1520→768 (MSS=700最大帧758B, 省3.76KB)
- iot_mqtt_srv.c: payload[1400]→800 (loop_data最大~604B, 省600B)
- iot_mqtt_srv.h: IOT_MQTT_SEND_BUF_LEN 1024→800 (_iot_send_buf/resp/buf 同步, 省224B)
- net_srv.c: MAX_MQTTBUF_LEN 1024→800 (512仍不够勿回改, 省224B)

⚠ ETH 768 为 MSS=700 最小安全值; 若现场有大UDP包截断改回1024。
待办: .map 确认 BLE 栈占用; RECE_BUF_LEN/ARP 表可再省; 局部大数组转static。
2026-08-17 14:10:52 +08:00
wangfq 26f2326c96 fix(vd960DBN): 栈溢出止血 — load_cfg 去调试打印, output_cfg 暂关 (2026-08-17)
实验D确认 load_cfg/output_cfg 为跑飞触发点。根因: .bss≈46KB
(BLE栈+ETH DMA 7.6KB+SPI_FLASH_BUF 4KB+网络缓冲) 挤占 RAM,
栈仅 ~1.9KB (BOOT_CNT@0x2000f84c 证明 .noinit 被推到 RAM 顶)。
load_cfg 的 printf 栈峰值触顶 → 覆盖 .noinit+返回地址 → PC 跑飞循环。

修复:
- cfig_flash.c: load_cfg 内 3 处调试 PRINT 注释 (magic mismatch×2 + Sub_Code×1)
- peripheral_main.c: load_cfg 恢复(配置加载保留); output_cfg 调用 #if 0 (纯调试打印)
- devlog: 实验D结论 + 修复记录

根治待办: 减 .bss 给栈腾 4KB+ (需 .map 确认大数组);
output_cfg 恢复调试打印前需减栈压力
2026-08-17 11:06:56 +08:00
wangfq 452ff68bae test(vd960DBN): 实验D — 栈空间诊断 + load_cfg/output_cfg 触发点验证 (2026-08-17)
新证据: BOOT_CNT@0x2000f854 = .noinit 被推到 RAM 顶, 栈顶(0x20010000)
之下仅 ~1.9KB → .bss≈46KB (BLE/WCHNET/SPI_FLASH_BUF 等大数组挤占)。
栈溢出 → 覆盖 .noinit(g_boot_count 恒1/g_fault_diag 消失) + 覆盖返回地址
→ PC 跑飞跳回 0 → 循环。快照延后无效因为问题不是快照 SPI 而是栈空间。

- BOOT_CNT 打印加 _end(堆起点, 反推.bss大小) + 当前 SP(栈深)
- load_cfg/output_cfg #if 0 (EXPD 标记): 稳定→触发点确认; 仍崩→更晚
- 待用户提供编译后 .map 确认 .bss 组成
2026-08-17 11:00:03 +08:00
wangfq 8cf01544f6 feat(vd960DBN): 快照区延后初始化(开机3s后) — 避开启动早期SPI重负载窗口 (2026-08-17)
诊断: 08-14恢复快照区后频繁重启。RSTSCKR无复位标志(非硬件复位) +
BOOT_CNT恒=1(.noinit被清, RAM全丢级) + 实验A(跳过offlog_boot)仍崩
→ 启动早期SPI重负载(offlog/snap init 擦+整扇区写回~80ms×2)让系统
进入临界状态, 后续SPI操作(load_cfg读参数区)成为压垮点。

方案:
- snapshot.c/h: 新增 snap_delayed_init(), 主循环每轮调用, 开机3s后
  首次进入执行 snap_init (非阻塞, mstick()>=3000 门控)
- peripheral_main.c: main() 移除 snap_init 调用; 主循环喂狗后加
  snap_delayed_init(); 撤销实验A(恢复offlog_boot)/实验B(恢复load_cfg)
- 3s内传感帧由 snap_enqueue/snap_flush 的 !_ready 门控自动丢弃不落盘
- 保留 BOOT_CNT 诊断打印(.noinit 计数器+地址)
- devlog 置顶 2026-08-17 条目
2026-08-17 10:31:39 +08:00
wangfq 5803255d89 test(vd960DBN): 实验B — 跳过 load_cfg/output_cfg, 跑飞触发点下移 (2026-08-17)
实验A(跳过 offlog_boot)仍循环复位 → offlog_boot 非触发点, 跑飞点下移。
新证据: BOOT_CNT 恒=1 (.noinit 计数器每次被清) → RAM 全丢级重启,
RSTSCKR 却无复位标志 → 疑似掉电-恢复(未触发POR) 而非软件跑飞。

- load_cfg_from_flash + output_cfg_from_flash #if 0, 加 EXPB 标记
- BOOT_CNT 打印加 &g_boot_count 地址, 确认 .noinit 段是否生效
  (地址 >= _ebss≈0x2000612c 且在 .noinit → 复位不清零; 恒1 → RAM被清)

预期: 不崩 → 触发点在参数区SPI读或%s打印; 仍崩 → 触发点更晚/更早
2026-08-17 10:15:21 +08:00
wangfq 0c717e4818 test(vd960DBN): 实验A — 跳过 offlog_boot SPI 写, 验证跑飞触发点 (2026-08-17)
日志诊断: RSTSCKR 全 0(非硬件复位) + snap ok 后 71B 乱码 + 无 ASCII 打印
→ PC 跑飞跳回 0x00000000 重启, 触发点锁定 offlog_boot(SPI 32B 页写)。
与 08-12 实验 99ef634 结论一致。

- offlog_boot() 调用 #if 0, 保留 RMVF 清除
- 新增 .noinit g_boot_count 计数器: 每次 main 重进 +1 并打印 BOOT_CNT
  (跑飞重启不清零, 区分真复位 vs 跑飞)
- 新增 EXPA: offlog_boot skipped 标记打印

预期: 不崩 → offlog_boot 写实锤; 仍崩 → 跑飞点下移 load_cfg/output_cfg
2026-08-17 10:04:52 +08:00
wangfq 8873b334d2 feat(vd960DBN): 恢复快照区功能, 移除 sim_snap_spi 实验桩, UART2 波特率注释修正
- peripheral_main.c: snap_init() 恢复 (去 #if 0), offlog_boot() 恢复
  (上电复位原因记入事件日志); 删除 sim_snap_spi 模拟写密度实验函数
  及主循环调用 (printf 重入修复后 SPI 写无罪已确诊, 实验收尾)
- loop_uart_proto.h/c: UART2 波特率注释 115200/19200 -> 192000
  (usart_biz.c 硬编码 192000 为实际值; g_storage_uart_baud=19200
  为独立配置项不驱动 UART2, BLE 显示 19200 不代表实际速率)
- devlog: 新增 2026-08-14 条目, UART2 RX DMA 方案列入待办
2026-08-14 09:46:23 +08:00
wangfq b493e10454 fix(vd960DBN): UART2 粘帧/checksum fail — 高频打印关中断丢字节 + flag 不清
现场: LUP 帧后 checksum fail 刷屏 (坏帧: 7F 03 39 EF... 多帧拼接)。
根因链条:
1. LUP Rx 打印 190B @256000 = 7.4ms 关中断 (PRINT 临界区)
   → UART2(19200) RX 溢出丢字节 → 帧解析错位 → 粘帧
2. g_pkg_uart_2.flag 清理缺失: uart_srv 0xC0 分支不 InitPkgUart,
   tcp_json_push_sensor not authed 提前 return 也不清
   → 坏帧反复处理 → checksum fail 刷屏

修复:
- LUP Rx 打印 #if 0 (高频打印关中断是丢字节根因, 调试时开)
- json_sensor_callback not authed 打印 #if 0 (每帧刷屏)
- uart_srv 所有分支消费后统一 InitPkgUart (0xC0 + 非0xC0无BLE)
- 顺带修 report disabled 双反斜杠
2026-08-13 17:43:24 +08:00
wangfq a36cc15cf2 test(vd960DBN): 二分卡死点 — 注释 json_sensor_callback enter PRINT + 删冗余打印
现场: LUP 帧后卡死 (5分钟后神秘复位 RST=0x10000000, marker丢失)。
卡死点在 lup_process_frame 内 json_sensor_callback 的 enter PRINT
(printf %d 处理), LUP Rx 打印(%s)成功但 enter(%d%d%d)卡死。
实验:
- json_sensor_callback enter PRINT #if 0 (坐实 printf 问题)
- uart_srv 187-190 重复逐字节打印 #if 0 (LUP Rx 已缓冲打印, 冗余)
验证: 不卡死→printf %d 问题实锤; 仍卡死→json_sensor_callback 后续代码
2026-08-13 17:22:31 +08:00
wangfq e7389b95fb fix(vd960DBN): fault_diag 无条件打印 marker — 卡死(非HardFault)也能定位
原只在 magic=0xFA57FA57 (HardFault) 时打印, 卡死/IWDG 复位时
marker 有值但 magic=0 → 轨迹丢失。改为:
- magic 有效 → 完整现场 (mcause/mepc/mtval/marker)
- marker!=0 且无 HardFault → 打印 no-hardfault + marker (卡死定位)
2026-08-13 16:35:30 +08:00
wangfq 4bdf8a7993 fix(vd960DBN): IWDG 无条件启用+喂狗 — 卡死兜底 (卡死定位闭环)
现场: printf 重入修复后乱码消失, 但 LUP 帧处理卡死(连 JSON:
sensor_cb enter 都没打印), 且 iot_enable=0 模式 IWDG 未初始化
(iot_mqtt_poll 才喂狗, tcp_json 分支不喂) → 卡死永久冻结无兜底。

修复:
- main 无条件 iot_watchdog_init() (IWDG 4s 超时)
- 主循环 while 开头无条件 iot_watchdog_kick() (原只在 iot_mqtt_poll)
- json_sensor_callback / iot_evt_sensor_cb 加 FAULT_MARKER(MK_EVT_CB_IN/OUT)

效果: 卡死 → 4s IWDG 复位 → 上电 FAULT_DIAG 打印 marker 定位卡死点
2026-08-13 16:15:26 +08:00
wangfq ee3ade6f00 fix(vd960DBN): PRINT 宏临界区保护 — printf 重入修复 (乱码+HardFault 根因)
现场: LUP 帧后乱码+复位。JSON: sensor 打印被二进制污染、
LUP Rx hex 残留重复 → printf 交叉输出的典型症状。
根因: BLE 协议栈回调(peripheral.c, BB 中断上下文)与主循环
都有 PRINT, 交叉调用 printf → 输出乱码 + newlib 堆/状态破坏
→ HardFault (RST=0x10000000 为 HardFault_Handler 内 NVIC_SystemReset)。

修复:
- debug.h PRINT 宏: 保存/恢复 MSTATUS 临界区 (__get_MSTATUS/
  __disable_irq/printf/__set_MSTATUS), 中断里调用也能正确恢复,
  不会嵌套误开中断
- loop_uart_proto: LUP Rx 逐字节打印改缓冲一次性输出 —
  逐字节 PRINT 关中断 ~350us/字节屏蔽 UART2 ISR 丢帧
2026-08-13 16:06:35 +08:00
wangfq 9f5102e9e8 feat(vd960DBN): fault_diag — .noinit RAM 现场 + 执行轨迹 marker 定位神秘复位
现象: LUP 帧后复位, RST=0x00000000 (无标志) 且 HardFault 打印缺失
(上次 0x10000000=SFT 确认 HardFault 路径, 这次连打印都没有)
方案: .noinit 段复位不清零, 跨复位留痕
- Link.ld: 新增 .noinit 段 (startup 只清 .bss 不碰)
- fault_diag.h: FaultDiag{magic,mcause,mepc,mtval,marker,boot_cnt} + FAULT_MARKER 轨迹宏
- HardFault_Handler: 纯 RAM 写 mcause/mepc/mtval (官方 __get_MCAUSE/MEPC/MTVAL,
  不依赖 UART/printf, 栈坏也能留痕) + 尽力打印
- main 开头 fault_diag_init(): 打印上次现场 + 递增 boot_cnt
- 轨迹 marker: 主循环 LOOP_TOP/UART_SRV/SIM_SPI/POLL_BLE + LUP 链
  LUP_FRAME/INGEST/EVT_FEED 入口出口, 复位后看 marker 停在哪一步
2026-08-13 15:44:07 +08:00
wangfq 1bbaac0046 fix(vd960DBN): HardFault_Handler 抓 RISC-V 异常现场 — 定位崩溃源头
诊断修正(2026-08-13): RST=0x10000000(bit28 SFT) = HardFault_Handler
内 NVIC_SystemReset, 不是硬件复位! 复位紧跟 LUP 传感器帧(30ms内),
0xC0 帧处理路径触发代码异常。模拟实验 SPI 写(cnt=64/128 擦除+头写)
全部成功, SPI 写本身不触发复位, 推翻 VDD 跌落/共线假设。

- HardFault_Handler 打印 mcause(0x342)/mepc(0x341)/mtval(0x343)
  mcause 5/7=野指针(mtval给地址) 2=非法指令 4/6=未对齐
  mepc 用 .map 文件反查函数
2026-08-13 15:31:34 +08:00
wangfq 87c8e01936 test(vd960DBN): SPI写密度模拟实验 + factory写止血 — VDD跌落诊断
- peripheral_main: sim_snap_spi 模拟快照落盘节奏 (20ms 64B页写 + 每64条切扇区擦除+头写)
- cfig_flash: magic不匹配时跳过 factory SPI_Flash_Write, 用内存默认值继续
  (SPI写触发硬件复位死循环止血: 参数区0x00写也崩, RST=0x00000000)
- 诊断修正: CH32V208GBU6 QFN28 无 NRST 引脚, 共线假设作废;
  SPI写/擦除→电流尖峰→VDD跌落→内部POR/PDR(2.4V)复位;
  BLE射频+SPI电流叠加是头号嫌疑
2026-08-13 11:27:08 +08:00
wangfq 99ef6348d7 test(vd960DBN): 跳过 offlog_boot 写 — 二分定位 SPI 触发点
f766530(跳过snap)仍崩 → 非快照SPI; 旧固件也有 offlog_boot 32B 写
→ 硬件一直有问题只是未暴露; 另一差异: 事件区 0x010000(旧) vs 0x090000(新)
本版: #if 0 offlog_boot 写 (保留 RMVF 清除)
不崩 → offlog_boot 写(或切扇区擦除)触发; 崩 → 更早 SPI 读操作/非SPI
2026-08-12 19:39:50 +08:00
wangfq f766530609 test(vd960DBN): 跳过 snap_init 实验 — SPI 序列还原旧固件
回答'为什么旧固件正常': 旧固件也有 offlog_boot 32B 写, 理论上也踩同样硬件问题
差异: 新固件启动早期 SPI 密度翻倍(snap_init 读+擦+写头) + 快照区首擦
实验: #if 0 snap_init → SPI 序列=旧固件
稳定 → 快照 SPI 触发; 崩 → offlog_boot 本身, 旧固件也该崩(硬件一直有问题)
2026-08-12 19:34:55 +08:00
wangfq 8bb576111b test(vd960DBN): SPI 分频 16→128 极慢版 — 验证频率无关性
降频4倍(9053831)后乱码字节不变+仍复位 → 非频率相关串扰
疑似 MOSI(PA7) 写数据与 NRST/调试线物理共线/短路 (乱码=写数据错位采样)
分频128(~0.5-1MHz): 若仍崩 → 100%物理层问题, 软件无解, 必须硬件修
硬件测量: 断电测 PA5/PA7 与 NRST 导通电阻(应无穷大); 查原理图 SPI 走线
2026-08-12 19:31:49 +08:00
wangfq 9053831f27 fix(vd960DBN): SPI 串扰复位软件缓解 — 降时钟+缓边沿, 恢复功能
实验闭环: Flash/SPI 全禁用后设备稳定 (BLE/WCHNET/TCP 全正常)
→ SPI 操作触发复位+乱码实锤; 崩点在 offlog_boot 写 32B (不擦除)
→ 排除电流, 判定 SPI 信号串扰 NRST(复位) + 调试线(乱码)

缓解: storage.c GPIO 50MHz→10MHz 缓边沿 + SPI 分频 4→16 降时钟
恢复: peripheral_main.c 两个隔离块改 #if 0 (Flash + UART2 正常)
硬件根治(待现场): NRST 加 100nF 电容 / SPI 走线远离 NRST / 串阻 22Ω
2026-08-12 19:27:47 +08:00
wangfq 22224416a3 test(vd960DBN): Flash/SPI 全禁用实验 — 定位 SPI 操作触发复位
UART2 隔离后(06fb009)依旧 80ms 复位循环+乱码 → 排除 Loop 数据路径
Hex 日志: 乱码紧跟 snap ok 后的 offlog_boot SPI 写, 复位标志全 0
= NRST 毛刺复位特征, 疑似 SPI 总线(PA4-7)串扰 NRST/调试线 或 电源瞬态
本版: #if 1 跳过 storage/offlog/snap/offlog_boot/load_cfg 全部 SPI 调用
判定: 稳定 → SPI 触发实锤, 硬件查电源/NRST/布线
      仍复位 → 与 SPI 无关, 纯硬件(电源/晶振/其他)
2026-08-12 19:21:57 +08:00
wangfq 06fb009773 test(vd960DBN): UART2 隔离实验 — 软件禁用 Loop 口定位复位源
现场: RST_REASON 全 0 瞬态复位, 总在 Loop 帧后 ~10ms, 指向 Loop 侧干扰
实验: uart_init 后立即禁用 USART2 + PA3 脱离 UART 功能(上拉输入)
     = 等效软件断开 Loop; 同时快照无帧入队, 同步验证 SPI 活动非诱因
判定: 不复位+无乱码 → Loop 数据路径干扰实锤
      还复位 → 物理引脚级干扰 或 CH32 自身电源问题
通过后删除此块
2026-08-12 19:15:37 +08:00
wangfq eadc67584f fix(vd960DBN): 复位原因位定义错位修正 — RST_REASON=0x08000000=PORRSTF
现场: RST_REASON 打印 0x08000000, 对照 WCH ch32v20x.h 发现
offlog.h 位定义整体错位 (IWDG=bit31实为bit29, SFT=bit24实为bit28,
POR=bit25实为bit27, LPWR=bit29实为bit31)
结论: 0x08000000=PORRSTF=上电/掉电复位 → 电源跌落方向 (SPI擦除电流脉冲?)
修正:
- offlog.h 位定义对齐 WCH 库, 新增 OFFLOG_RST_RMVF=bit24
- peripheral_main.c RMVF 清除改 bit24 (原写 bit27=PORRSTF 只读位,
  复位标志从未清除, 历史累积污染诊断)
- 协议文档 boot 事件位解析同步
测试回归全过
2026-08-12 18:58:16 +08:00
wangfq f5b52d5bde fix(vd960DBN): 启动诊断打印 + snap_flush 无条件挂载
现场: 懒擦后仍起不来 (200ms 乱码后死机), 需定位复位/卡死点
- main 开头打印 RST_REASON (RCC_RSTSCKR): 一锤定音区分
  IWDG死锁(bit31) / POR掉电(bit25) / NRST外部复位(bit26) / 软复位(bit24)
- 各 init 步骤加标记 (storage/offlog/snap ok), 定位卡死位置
- snap_flush 从 iot_enable 分支移到 Main_Circulation 无条件调用:
  原实现 TCP 模式(iot_enable=0)下快照永不落盘, RAM 暂存满丢新
2026-08-12 18:42:40 +08:00
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
wangfq 92887a3d92 feat(vd960DBN): 传感快照区落地 snapshot.c + BLE SNAP_STAT/QUERY/CLEAR
- snapshot.h/c: 64B 定长 SnapRec (头部16B + 4x12B 0xC0线圈数据原样)
  环形分区独立于事件日志, 快照区 = 总容量 - 固定区576KB - 事件区
- 线程模型: USART2 ISR 只打包+RAM暂存(8深满丢新), 主循环 snap_flush 落盘
  (iot_sensor_ingest 在中断上下文, SPI 45ms 擦除严禁进中断)
- dbn_ble_srv: SNAP_STAT(0x28)/SNAP_QUERY(0x29)/SNAP_CLEAR(0x2A)
  QUERY 上限 2 条 (64x2+2=130B, ODR 教训防御)
- iot_mqtt_srv: ingest 挂 snap_enqueue, publish_sensor 挂 snap_flush
  (放在 READY 检查前, 断网照常落盘)
- peripheral_main: offlog_init 后加 snap_init
- 清空审计: 写事件流 log_clear payload[0]=2
- 测试: test_snapshot.c 8 例全过, offlog/ble_offlog 回归全过
- 文档: DLD960_BLE协议 V1.02 + ROADMAP P1.2 状态 + devlog
2026-08-12 17:40:14 +08:00