诊断: 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 条目
69 KiB
vd960DBN 开发日志
MCU: CH32V208 (RISC-V, WCH) | 通信: BLE + ETH (WCHNET) + UART2→Loop MCU | TCP JSON 端口: 5960
项目定位: DLD960 通信板 — BLE 配网、TCP JSON 协议服务、Loop MCU 串口桥接
2026-08-17 — 快照区延后初始化(开机 3s 后)— 启动早期 SPI 重负载疑似触发跑飞
背景
08-14 恢复快照区功能(8873b33)后,现场频繁"复位"(100~180ms 一轮完整启动日志)。 Hex 日志诊断:
| 观察 | 推断 |
|---|---|
| RST_REASON 首次 0x08000000(POR),之后恒 0x00000002(无任何复位标志位) | 非硬件复位(POR/IWDG/SFT/PIN 都会置位) |
| 无 FAULT_DIAG 输出 | 非 HardFault(fault_diag 仅在 HardFault 后打印) |
每轮都到 INIT: snap ok,之后无任何 ASCII 直接 71B 乱码+重启 |
跑飞点在 snap_init 之后(offlog_boot/load_cfg/output_cfg 区间) |
| 实验A(跳过 offlog_boot,commit 0c717e4):仍崩 | offlog_boot 非触发点 |
| BOOT_CNT 恒=1(.noinit 计数器每次从 0 开始) | RAM 全丢级重启(掉电-恢复但未触发 POR) |
结论
启动早期 SPI 重负载(offlog_init 擦+整扇区写回 ~80ms + snap_init 擦+整扇区写回 ~80ms) 让电源/系统进入临界状态,随后的 SPI 操作(load_cfg 读参数区)成为压垮点。 08-13"稳定 6 分钟"实为实验状态(snap_init/offlog_boot 均 #if 0),非完整固件。
方案
快照区延后到开机 3s 后初始化:避开启动早期 SPI 重负载窗口;3s 内传感帧 由 snap_enqueue/snap_flush 的 !_ready 门控自然丢弃(视为无效快照数据)。
| 文件 | 变更 |
|---|---|
snapshot.c |
新增 snap_delayed_init():主循环每轮调用,mstick()>=3000 且未初始化时执行 snap_init(非阻塞) |
snapshot.h |
声明 snap_delayed_init() |
peripheral_main.c |
main() 移除 snap_init 调用;主循环喂狗后加 snap_delayed_init();撤销实验A(恢复 offlog_boot)/实验B(恢复 load_cfg/output_cfg);保留 BOOT_CNT 诊断打印 |
验证
- 板级:开机 3s 内无
INIT: snap ok(快照区未初始化),3s 后出现 - 板级:不再循环重启,BOOT_CNT 不再恒 1
- 板级:断网期间快照正常落盘、BLE 0x28/0x29/0x2A 查询正常
- 若仍循环:示波器抓 VDD(SPI 擦写瞬间跌落)+ 实验B 二分 load_cfg/output_cfg
遗留
- BOOT_CNT 打印保留(确认 .noinit 段生效 + 区分复位 vs 跑飞重启)
- 快照区延后 3s 期间(0~3s)传感数据不落盘(按需求接受)
- 电源裕量问题若确认:W25Q32 VCC 加 100nF+10uF 电容、SPI 走线远离电源
2026-08-14 — 快照区功能恢复启用 + UART2 波特率澄清
背景
printf 重入复位问题已确诊修复(2026-08-13,SPI 写无罪)。实验遗留的隔离代码收尾:快照区恢复启用、模拟写密度的 sim_snap_spi 实验桩移除。
变更
| 文件 | 变更 |
|---|---|
peripheral_main.c |
snap_init() 恢复(去 #if 0);offlog_boot() 恢复(上电复位原因记入事件日志);删除 sim_snap_spi 实验函数 + 主循环调用 |
loop_uart_proto.h |
注释 UART2 波特率 115200 → 192000 |
loop_uart_proto.c |
注释 UART2(19200) → UART2(192000) |
snapshot.c/h |
无需改动(2026-08-12 已实现:环形 64B SnapRec、懒擦、掉电恢复、BLE 0x28/0x29/0x2A) |
UART2 波特率澄清
- UART2(Loop MCU 口)实际波特率 = 192000,
usart_biz.c硬编码;协议文档写 115200 已过时,以代码为准 g_storage_uart_baud = 19200(peripheral_main.c)是独立配置项(BLE 可读可改 + flash 存储),不驱动 UART2 波特率——BLE 小程序看到 19200 不代表 UART2 实际速率- 后续规划:UART2 RX 改 DMA/双缓冲,解决偶发丢帧(1-3 分钟一次 checksum fail)
验证
tests/test_snapshot.c9 用例 ALL PASS(gcc 隔离单测)check_c_balance.py3 文件 OK- 板级待办:上电
INIT: snap ok、BLE 0x28/0x29/0x2A 查询、断网期间快照落盘
遗留
- UART2 RX DMA 方案落地(防丢帧)
- 板级验证快照 BLE 查询命令
2026-08-13 — 复位/卡死全链路诊断闭环:printf 重入 + 回调卡死 + UART2 丢字节
现场症状(多轮)
| 阶段 | 现象 | 关键数据 |
|---|---|---|
| ① 快照区落地后 | SPI 写触发复位 + 串口乱码 | RST=0x00000000 |
| ② factory 写死循环 | 参数区 magic 不匹配 → 写→复位循环 | RST=0x08000000 → 0x00000000 |
| ③ 加 HardFault 诊断后 | LUP 帧后复位 | RST=0x10000000 (SFT) |
| ④ PRINT 临界区修复后 | 乱码消失,但 LUP 帧后卡死 | 无复位(IWDG 未兜底) |
| ⑤ 注释 enter PRINT 后 | 不卡死,偶发 checksum fail 刷屏 | 坏帧 7F 03 39 EF... 多帧拼接 |
| ⑥ 关高频打印+清 flag 后 | 系统稳定(6+ 分钟无复位) | SIM 心跳持续 |
根因链(三层,全部是软件问题)
1. printf 重入: BLE 协议栈回调(peripheral.c, BB 中断上下文)与主循环都有 PRINT
→ newlib printf 非中断安全交叉调用 → 输出乱码 + 堆/状态破坏 → HardFault
2. json_sensor_callback enter PRINT: printf 处理 %d %d %d 死循环
(LUP Rx %s 打印成功, enter %d 打印卡死) → 主循环永久卡死
3. UART2 丢字节: LUP Rx 打印 190B @256000 = 7.4ms 关中断 (PRINT 临界区)
→ UART2(192000) RX 溢出丢字节 → 帧解析错位 → 粘帧 → checksum fail
+ g_pkg_uart_2.flag 清理缺失 → 坏帧反复处理 → fail 刷屏
重要认知修正
- SPI 写完全无罪:模拟实验跑 17000+ 条写入(含 45ms 擦除 + 100ms 头写),0 硬件复位。推翻此前"SPI 写触发 VDD 跌落复位"判断
- CH32V208GBU6 QFN28 无 NRST 引脚(数据手册第 1400 行 QFN28 列 = "-")→ "PA7/NRST 共线"假设作废
- 5 分钟"神秘复位"真相:
tcp_json的TCP_JSON_IDLE_TIMEOUT_MS=300000(5min idle → restart)。主循环卡死时表现为 SFT 复位,恢复后只重启 TCP 服务 - IWDG 卡死未兜底:iot_enable=0 时
iot_mqtt_poll不跑 → 无人喂狗且未初始化 → 已改为 main 无条件 init + 主循环无条件 kick(待确认 LSI 频率偏差导致的超时漂移)
修复清单
| 文件 | 修复 |
|---|---|
SRC/Debug/debug.h |
PRINT 宏加临界区:保存/恢复 MSTATUS(防 printf 重入,中断里调用也能正确恢复) |
loop_uart_proto.c |
LUP Rx 逐字节打印→缓冲一次性输出→#if 0 关闭(7.4ms 关中断丢字节根因) |
tcp_json_srv.c |
json_sensor_callback enter PRINT #if 0(printf %d 卡死规避);not authed 打印 #if 0;修 \\n 双反斜杠 |
usart_biz.c |
uart_srv 所有分支消费后统一 InitPkgUart(0xC0 + 非0xC0无BLE 原来不清 flag);重复逐字节打印 #if 0 |
ch32v20x_it.c |
HardFault_Handler 纯 RAM 写 mcause/mepc/mtval(官方 __get_MCAUSE/MEPC/MTVAL)+ fault_diag 无条件打印 marker |
Link.ld + fault_diag.h |
.noinit 段(复位不清零)跨复位留痕:HardFault 现场 + 执行轨迹 marker |
peripheral_main.c + iot_mqtt_srv.h |
IWDG 无条件初始化 + 主循环无条件喂狗 |
cfig_flash.c |
factory 写止血:magic 不匹配时内存默认值(原 SPI 写→复位死循环) |
遗留待办
- UART2 偶发丢帧(1-3 分钟一次 checksum fail):RX 改 DMA/双缓冲(波特率已确认 192000,非 19200)
- json_sensor_callback enter PRINT 的 printf %d 卡死根因(newlib lock / UART1 TC 轮询)——已注释规避,恢复前需查明
- IWDG 卡死时未在 4s 复位:LSI 频率偏差 or 配置问题
g_lup_sensor_cb单回调竞争:iot_evt_sensor_cb 与 json_sensor_callback 后注册覆盖先注册,事件沿检测可能被跳过(需多回调或合并)- 恢复快照区功能(2026-08-14):
snap_init已恢复、sim_snap_spi已移除,待板级验证 BLE 0x28/0x29/0x2A 查询
2026-08-12 — 复位原因位定义错位修正 + POR 复位定位(电源方向)
诊断进展(现场 RST_REASON 打印)
新固件启动首行输出 RST_REASON: 0x08000000。
对照 WCH 官方库 ch32v20x.h(RCC_RSTSCKR 定义)发现 offlog.h 复位原因位定义整体错位:
| 实际位 | 含义 | offlog 原定义 | 状态 |
|---|---|---|---|
| bit31 | LPWR | bit29 | ❌ |
| bit30 | WWDG | bit30 | ✓ |
| bit29 | IWDG | bit31 | ❌ |
| bit28 | SFT 软件复位 | bit24 | ❌ |
| bit27 | POR 上电/掉电复位 | bit25 | ❌ |
| bit26 | PIN NRST | bit26 | ✓ |
| bit24 | RMVF 清标志 | (main 写 bit27) | ❌ |
两个结论
0x08000000= PORRSTF(上电/掉电复位) → 设备被电源跌落复位,不是看门狗、不是软件死锁。方向:SPI 擦除电流脉冲(W25Q32 ~20mA 级)或供电余量不足 → 3.3V 跌落触发 PDR。待现场量测 3.3V 在 SPI 擦除瞬间的跌落。- RMVF 清除写错位(bit27 实为 PORRSTF 只读位)→ 复位标志从未清除、历史累积 → 修正为 bit24。
修正
- offlog.h:复位原因位定义对齐 WCH 库(IWDG=bit29 / SFT=bit28 / POR=bit27 / LPWR=bit31),新增 OFFLOG_RST_RMVF=bit24
- peripheral_main.c:RMVF 清除
|= OFFLOG_RST_RMVF - 协议文档 boot 事件位解析同步更新
下一步(板上)
修正后重新上电,RST_REASON 将只显示最近一次复位原因:
- 若仍显示 PORRSTF(bit27)→ 电源问题实锤:查 3.3V 供电余量、W25Q32 VCC 电容、SPI 擦除瞬间跌落
- 若显示 IWDGRSTF(bit29)→ 主循环死锁,继续查软件
- 若显示 PINRSTF(bit26)→ NRST 干扰/复位电路
2026-08-12 — 设备不断重启修复:init/clear 同步全量擦除阻塞超 IWDG
现场症状
烧录快照固件后 CH32V208 不断重启,启动打印(CH32V20x_BLE_LIB / SystemCoreClock / W25Q32 OK!)后出现乱码:>>8>潈8>�0>8�...。
根因链
| # | 环节 | 问题 |
|---|---|---|
| 1 | snap_init() 全新区 |
首次初始化同步擦全部 751 个数据扇区 ≈ 34s(SPI 擦除 45ms/扇区) |
| 2 | IWDG 看门狗 4s(iot_mqtt_srv.c:978) | 34s 主循环阻塞 → 看门狗超时 → 复位 |
| 3 | 复位后快照区仍无 "SNAP" magic | 头在擦完数据扇区后才写 → 下次启动又全新区 → 再擦 34s → 再复位 = 死循环 |
| 4 | offlog_init() 首擦 5.7s / offlog_clear() 2.8s |
同类隐患(事件区全新或清空时) |
| 5 | snap_clear() 擦 751 扇区 34s |
IWDG 启用后执行 BLE SNAP_CLEAR 命令 → 必复位 |
| 6 | usart_biz.c:195 非 0x7F 帧 %s 打印 |
Loop MCU 数据被当字符串 → 串口乱码刷屏 |
附带修正认知:
lup_process_frame实际由主循环uart_srv()调用(usart_biz.c:166),USART2 ISR 只逐字节喂lup_feed_byte()。快照 enqueue/flush 全程主循环上下文——此前 snapshot.h 注释误标"ISR 上下文"。
修复:懒擦(init/clear 只擦当前写扇区,45ms)
snap_init/offlog_init全新区:只擦数据扇区 1(写指针起点),其余扇区由环形写切扇区逻辑自动擦(write_raw 已有 has_data 判定)snap_clear/offlog_clear:逻辑清除(count=0 → 旧数据立即不可读)+ 只擦写指针起点扇区;其余扇区写覆盖时逐个擦usart_biz.c:非 0x7F 帧%s打印改 hex(%02X),杜绝二进制当字符串
启动阻塞 34s → 45ms;SNAP_CLEAR 阻塞 34s → 45ms。环形覆盖语义完全不变。
测试
test_snapshot 9 例全过(新增 test_lazy_erase:预埋扇区 2 旧数据,init 后保持,写满 64 条切扇区时正确覆盖);test_offlog 8 例、test_ble_offlog 7 例回归全过。
待现场确认
- 乱码另一可能源:Loop MCU ↔ CH32 UART2 波特率/接线(192000 应为标准 0x7F 帧;若 Loop 发送端配置不同,帧头识别失败 → 非 0x7F 路径)。hex 打印修复后可通过
Rcv_len:xx,dat: 7F 00 ...判断 Loop 帧是否正常到达。
2026-08-12 — 传感快照区落地(snapshot.c,BLE 新增 0x28/0x29/0x2A)
背景
ROADMAP P1.2 分区规划(参数区→OTA→事件日志区→传感快照区)中,事件区已落地(offlog),快照区此前为预留。本次实现快照流:0xC0 传感帧按上报节奏落盘,断网期间波形照常记录,可离线回放。
关键设计决策
- 64B 定长记录 SnapRec:头部 16B(magic=0xA6/len/flags/seq/ts_ms/boot_seq)+ 4×12B 线圈数据(与 0xC0 线上格式逐字节一致,可直接对照抓包)→ W25Q32 下 48064 条。
- 线程安全(踩坑预警):
iot_sensor_ingest()在 USART2 ISR 上下文(loop_uart_proto.h:209明确标注)被调用!SPI 擦除 ~45ms 绝不能在中断里做 → 中断只打包+入 RAM 暂存(8 深,满丢新),主循环iot_mqtt_publish_sensor()每轮snap_flush()落盘。单生产者(中断)单消费者(主循环)count 计数环形,volatile 字节天然原子。 - 分区表同一事实来源:快照区 = 总容量 − 固定区 576KB − 事件区(
g_offlog_part.area_size),杜绝两模块对事件区大小理解不一致。 - 审计:快照清除写事件流
log_clear(payload[0]=2=快照流)。 - BLE 指令对齐 OFFLOG 模式:SNAP_STAT(0x28)/SNAP_QUERY(0x29)/SNAP_CLEAR(0x2A);QUERY 上限 2 条(64×2+2=130B ≤ 单包缓冲,ODR 教训防御)。
测试
test_snapshot.c 8 例全过(中断安全/打包格式/环形回绕/掉电恢复/扇区切换/暂存满丢新/清空审计/分区表);test_offlog 8 例、test_ble_offlog 7 例回归全过。
⚠ 测试踩坑:不要在同文件 include offlog.c + snapshot.c —— 两个 .c 的 static 变量(如
_wr_off)在同一翻译单元符号冲突,快照 clear 审计会污染事件写指针。测试改为 mockg_offlog_part+offlog_evt。
待板上验证
- 快照区 0x110000 起、W25Q32 48064 条 capacity 打印
- 中断风暴下暂存满丢新行为(观察点:
g_snap_drop) - 掉电恢复跨扇区扫描
2026-08-12 — BLE 分包粒度动态化修复(Too large noti 丢包)
背景
CMD_DBN_OFFLOG_QUERY 拉取 4 条日志时响应 dat=130B,set_response_buf 按 写死的 MAX_BLE_DAT_RESPONSE_LEN=96 分包:第一包 = 帧头4 + dat96 + ckb2 = 102B。而小程序把 MTU 协商到 96 后,ATT 通知 payload 上限 = 96-3 = 93B,peripheralChar4Notify 判定 102 > 93 → "Too large noti" 直接 return,第一包永久丢失,小程序重组永远不完整(只收到第二包 34B)。
现场日志证据:
BLE: offlog_query start_seq=1 req=4 fetched=4
Too large noti, len:102, peripheralMTU:96
根因链
| # | 环节 | 问题 |
|---|---|---|
| 1 | BLE_BUFF_MAX_LEN=100 (config.h) |
MAX_BLE_DAT_RESPONSE_LEN = 100-4 = 96 分包块大小写死 |
| 2 | 分包粒度不随协商 MTU 变化 | MTU 协商到 96 后,每包 96B dat 必然超 93B 上限 |
| 3 | BLE_Notify_Buf.buf[100] |
set_response_to_notify 写 102B → 越界 2B(UB,可能踩坏相邻 g_flag_notify_temp/g_buf_ble_response) |
| 4 | peripheralChar4Notify 超限 return |
丢包无重传,小程序重组永久不完整 |
2026-08-10 日志中"响应超 96B 自动分包(既有机制)"的假设不成立:分包机制存在但粒度没跟随协商 MTU。
修复:ble_notify_chunk_max() 动态分包
/* 整包 = 帧头4(magic/header/len/cmd) + dat + ckb2
须满足 整包 <= peripheralMTU - 3 (ATT opcode+handle)
且 整包 <= MAX_BLE_Notify_Buf_LEN (本地缓冲防越界)
MTU=23 -> 14, MTU=96 -> 87, MTU>=103 -> 94 */
static uint16_t ble_notify_chunk_max(void)
{
uint16_t _mtu = peripheral_get_mtu();
if (_mtu < 23) _mtu = ATT_MTU_SIZE; /* 未协商兜底 */
uint16_t _limit = _mtu - 9; /* 整包上限-帧头4-ckb2 */
if (_limit > (MAX_BLE_Notify_Buf_LEN - 6))
_limit = MAX_BLE_Notify_Buf_LEN - 6;
return _limit;
}
改动文件(GBK+CRLF,全部 Python 二进制替换):
| 文件 | 改动 |
|---|---|
peripheral.c |
新增 peripheral_get_mtu() getter(peripheralMTU 是 static) |
dbn_ble_srv.c |
新增 ble_notify_chunk_max();set_response_buf / set_response_to_notify / set_response_iot_net / set_response_iot_topic 四处 MAX_BLE_DAT_RESPONSE_LEN 全部改用动态 chunk(组包包数与实际切包必须同一粒度,否则 pkg_amount 错乱) |
验证
- 隔离 C 测试(真实函数体 + mock
peripheral_get_mtu):MTU=23/96/185/517 四组,每组验证 chunk 上限、单包 ≤ MTU-3、单包 ≤ 100B 缓冲、帧结构一致、seq 递增、重组 130B 完整且逐字节一致,全部 PASS - MTU=96 时:2 包(93B + 49B)✓;MTU=185 时:2 包(100B + 42B)✓;MTU=23 未协商时降级 10 包(每包 20B,pkg_amount=10 ≤ header 高4位上限15)
- 顺带消除
g_notify_buftemp.buf[100]越界 2B 隐患 - 待板级验证:MRS 真编译 + 真机小程序 QUERY 拉 4 条日志(本地无 RISC-V 工具链)
小程序端(可选优化,非必须)
- Android 可在连接后
wx.setBLEMTU({mtu: 185}),分包粒度升到 94B/包,减少包数;iOS 系统自动协商,固件已自适应,不调也能正常拉取
2026-08-12 — 续:第二分包丢失 — performPeriodicTask 主动拉包
现场复测
小程序发 QUERY 后,第一包 93B 正常收到(value: ArrayBuffer(93),header 0x21 = amount=2/seq=1,含 2 条完整记录 + 第 3 条 21B 残片),第二包 49B 永远收不到。设备侧无 Too large noti(MTU 检查已过),两包间隔 50ms。
根因:分包续传依赖"下一次收包事件"
poll_dbn_ble 尾部 set_response_to_notify 虽在主循环每轮执行,但分包第二包的填充/发送链路脆弱:
- 第一包由
performPeriodicTask(50ms TMOS)发出并clear_ble_notify_buf - 第二包的填充依赖主循环下一轮
poll_dbn_ble执行set_response_to_notify—— 与 TMOS 周期事件交错,任何主循环阻塞(WCHNET/uart/网络轮询)都会让时序错位 peripheralChar4Notify发送失败(simpleProfile_Notify != SUCCESS)时静默丢弃,无任何日志
修复
static void performPeriodicTask(void)
{
if(g_flag_notify_temp){
g_flag_notify_temp = 0;
peripheralChar4Notify(g_notify_buftemp.buf, g_notify_buftemp.len);
clear_ble_notify_buf(&g_notify_buftemp);
}
/* 分包续传: notify 空闲且响应队列还有分片时, 50ms 周期主动拉下一包,
不依赖 poll_dbn_ble 收包事件 */
if(g_notify_buftemp.flag == 0)
{
g_flag_notify_temp = set_response_to_notify(&g_buf_ble_response, &g_notify_buftemp);
}
}
peripheral.c:performPeriodicTask发完当前包后主动set_response_to_notify拉下一分片;peripheralChar4Notify增加发送结果打印(BLE notify OK/FAIL, len, MTU),定位发送失败不再静默- 时序:cycle1 填充 pkt1 → cycle2 发 pkt1 + 填充 pkt2 → cycle3 发 pkt2,两包间隔 50ms,与收包事件解耦
验证
- 隔离 C 测试:无任何收包事件、仅靠
performPeriodicTask驱动,MTU=96 → 3 周期发出 93B+49B 两包,重组 130B 逐字节一致;MTU=185 → 100B+42B,全 PASS - 板上烧录后设备日志应出现:
BLE notify OK, len:93→BLE notify OK, len:49;若出现FAIL则问题在 GATT 层(连接/通知使能) - 待真机确认:若固件打印两包都 OK 但小程序仍只收 1 包 → 检查小程序
onBLECharacteristicValueChange订阅连续性/连接参数(连接间隔大 + 从机延迟会拉长第二包到达时间)
2026-08-12 — 事件日志区动态分区(ROADMAP 落地:JEDEC ID 选档)
背景
ROADMAP P1.2 更新:分区顺序调整为 参数区 → OTA 镜像暂存区 → 事件日志区 → 传感快照区;事件日志区容量随存储芯片动态(W25Q32=512KB / Q64=1MB / Q128=2MB / Q256=4MB),上电读 JEDEC ID 选档。移除 W25Q80 支持。
代码改动
| 文件 | 改动 |
|---|---|
offlog.h |
新增 OfflogPart 运行时分区表 + g_offlog_part;OFFLOG_AREA_BASE 改 0x090000(参数64KB+OTA512KB);OFFLOG_AREA_SIZE/DATA_SECTOR_CNT/MAX_RECORDS/DATA_SIZE 宏改为展开运行时值(主体零改动);新增 OFFLOG_CHIP_W25Q32/64/128/256 JEDEC ID 宏 |
offlog.c |
定义 g_offlog_part���默认 W25Q32 512KB/16256条);新增 offlog_part_detect()(读 JEDEC ID → 填表,未知按 W25Q32 兜底);offlog_init 开头调用 |
storage.c |
新增 SPI_Flash_ReadJEDEC_ID()(0x9F 读 3 字节,校验 EF 40,返回 device id) |
storage.h |
声明 |
注:offlog.h/c 是 UTF-8(2026-08-04 新建),storage.c 是 GBK——改造按各自编码精确替换,未破坏。
容量对照
| 芯片 | JEDEC | 事件区 | 数据扇区 | max_records | capacity LE |
|---|---|---|---|---|---|
| W25Q32(默认) | 0x16 | 512KB | 127 | 16256 | 80 3F 00 00 |
| W25Q64 | 0x17 | 1MB | 255 | 32640 | 80 7F 00 00 |
| W25Q128 | 0x18 | 2MB | 511 | 65408 | 80 FF 00 00 |
| W25Q256 | 0x19 | 4MB | 1023 | 130944 | 80 FF 01 00 |
兼容性
- 旧固件事件区在 0x010000(256KB),新固件在 0x090000——旧日志数据不会误读(新地址头扇区无 magic → 视为全新区自动重建),属预期行为
offlog_init在storage_init之后调用(peripheral_main.c:305-306),SPI 已就绪
文档同步
DLD960_BLE协议.md/DLD960_IoT_MQTT协议.md/DLD960_TCP_JSON协议.md:capacity 字段 8064 → 动态(含四芯片对照)DLD960_BLE日志查询交互.md:STAT 示例 capacity 更新ROADMAP.md:P1.2 分区规划(前序提交)
验证
tests/test_offlog.c:mock JEDEC=0x16,8 例全过(环形回绕自动适配 16256)tests/test_ble_offlog.c:mockg_offlog_part,capacity 断言 16256,7 例全过- 地址范围模拟:四芯片事件区 0x090000 起均不超芯片容量
2026-08-12 — 续2:第二分包丢失 — 根因确认(响应缓冲越界踩踏 g_notify_buftemp)
破案过程(三份现场日志 + .map 对照)
| 证据 | 结论 |
|---|---|
CLEAR busy resp: amt=2 seq=2 off=130 ret=00008122 |
ret 对 .map = set_response_to_notify 内部(0x7FEC+0x136)→ 第二包填充成功(正常清空),队列清空不是问题 |
BLE ntf flag 1->0 (len=0 temp=0) 无 BLE send |
第二包(49B)从通知缓冲物理消失,非发送路径 |
CLR_NTF: buf=20007bdc flag=1 len=93 ret=0000d604 |
第一包发送后正常清空(ret=Peripheral_ProcessEvent 内联 performPeriodicTask) |
.map RAM 布局 |
g_buf_ble_response=0x20007B58 +0x6B(107B),g_notify_buftemp=0x20007BDC → 107B = 7 + dat[100] = MAX_BLE_DAT_BUF_LEN 编译时是 100(旧宏) |
根因链(ODR 违规)
用户 MRS 工程 dbn_ble_srv.h 宏未同步: MAX_BLE_TMP_BUF_LEN/MAX_BLE_DAT_BUF_LEN=100 (repo 为 132)
→ Buf_DBN_BLE 实际 107B (dat[100]), 链接器把 g_notify_buftemp 排在 0x20007BDC
→ QUERY 响应组包 130B: tmp_ble_buf[100..129] 越界 + set_response_buf 写 dat[0..129] 越界 30B
→ 越界写踩中 g_notify_buftemp.flag/len (物理地址重叠)
→ 第二分包填充后 flag/len 被清零 → 下一 TMOS 周期不发送 → 永久丢失
同一时间戳
BLE resp flag 1->0 (pull=0)是越界后的连锁反应:notify flag 被清 0 → poll_dbn_ble 走 idle 分支 →g_flag_notify_temp=0→ 发送周期被跳过。
修复(防御性根治,兼容新旧宏)
- QUERY 组包动态限制记录数:
_max_rec = (MAX_BLE_TMP_BUF_LEN - 2) / sizeof(OfflogEvt)→ 宏=100 时最多 3 条(98B),宏=132 时最多 4 条(130B),永不越界 set_response_buf截断防御:dat_len > MAX_BLE_DAT_BUF_LEN时截断- 验证:旧宏 100 下 3 条 = 98B → 分包 [93, 17] 全合规;新宏 132 下 4 条 = 130B → [93, 49] 全合规
⚠ 必做:确认 MRS 工程头文件
.map 铁证:用户编译的固件里 MAX_BLE_DAT_BUF_LEN=100(旧宏),repo 已是 132。 即使加防御,也请确认 MRS 工程 APP/include/dbn_ble_srv.h 的 MAX_BLE_TMP_BUF_LEN/MAX_BLE_DAT_BUF_LEN 均为 132(与 repo 一致),否则结构体大小不一致(ODR 违规)的隐患仍在。
2026-08-10 — BLE 读取脱机日志接口 (OFFLOG_STAT/QUERY/CLEAR)
背景
offlog 日志已落地 MQTT V1.06 / TCP JSON V1.02 分发(2026-08-05),但 BLE 侧(小程序/APP 现场离线取证)无读取通道。现场常见场景:设备无网/断网,需蓝牙直连拉日志判断"重复上线"是平台问题还是设备复位。
方案:3 条 BLE 命令,复用 offlog 底层 API
| 命令码 | 名称 | 语义(对齐 MQTT log_*) |
|---|---|---|
0x25 |
CMD_DBN_OFFLOG_STAT |
日志统计:status + boot_seq(2) + count(4) + capacity(4) + seq_first(4) + seq_last(4),全 LE,19B |
0x26 |
CMD_DBN_OFFLOG_QUERY |
分页拉取:请求 start_seq(LE32) + count(1);响应 status(1) + count(1) + N×32B OfflogEvt 原始结构,N≤4(对齐 OFFLOG_MAX_QUERY_RECORDS) |
0x27 |
CMD_DBN_OFFLOG_CLEAR |
清空(审计留痕),阻塞 ~2.8s,主循环上下文可接受 |
- QUERY 定位复用 MQTT 同款公式
idx = start_seq - seq_first→offlog_read_idx(),不新增 offlog API - 记录直接传 32B 二进制 OfflogEvt(BLE 是二进制协议,无需 JSON 化;文档给出结构偏移 + 事件类型表)
- 响应超 96B 自动分包(
set_response_buf既有机制):QUERY 最大 130B → 2 包
缓冲扩容(RAM +64B)
| 宏 | 旧值 | 新值 | 原因 |
|---|---|---|---|
MAX_BLE_TMP_BUF_LEN |
BLE_BUFF_MAX_LEN(100) |
132 | QUERY 组包 1+4×32=129B |
Buf_DBN_BLE.dat |
MAX_BLE_BUF_LEN(100) |
MAX_BLE_DAT_BUF_LEN(132) |
set_response_buf 拷贝 129B |
新增 MAX_BLE_DAT_BUF_LEN=132;clear_buf_dbn_ble_all 的 memset 同步改用新宏。tmp_ble_buf/Buf_DBN_BLE 仅 dbn_ble_srv.c/h 内使用,影响面局部。
关键决策与坑
- GBK+CRLF 文件二进制编辑:dbn_ble_srv.c/h 是 GBK 编码(file 报 ISO-8859),
patch工具会静默转码成 UTF-8 致中文注释乱码。全部用 Python rb/wb 精确替换,每处 count==1 校验;改后 file 复核仍 ISO-8859。新增注释用 ASCII 规避编码问题。 - case 内变量名冲突:
manage_dbn_ble_default顶部已有uint8_t i = 0, k = 0,case 内再声明i会 redefinition。全部改用_i/_j。 - QUERY 短帧防御:
len < 11(magic+header+len+cmd+5data+2ckb)返回 status=0x02,防 pkg[4..8] 越界读。 - LOG_CLEAR 阻塞告警:BLE 连接期间 2.8s 擦除阻塞,若 MQTT 在线会导致 WCHNET 短时失服务(60s keepalive 可吸收),文档已标注。
- 编码验证:check_c_balance.py 自身 docstring 有转义 bug(
\\x在普通字符串触发 unicodeescape),已改 raw string 修复。
验证
- 新增
tests/test_ble_offlog.c:隔离测试框架嵌入 dbn_ble_srv.c 提取的 3 case 真实文本(extract_offlog_cases.py提取,生成物 gitignore),mock offlog_* + set_response_buf,7 例全过:- STAT 正常(19B 字段逐字节验证)/ disabled
- QUERY 正常(4×32B 记录 + seq/type/payload 偏移验证)/ 越界空 / 短帧 / disabled
- CLEAR 正常
- offlog 回归 8 例 ALL PASS(既有 7 例 + export_json 未破坏)
- 新增
docs/DLD960_BLE协议.mdV1.00(帧格式 + 分包 + 3 命令 + OfflogEvt 32B 结构 + 事件类型表) - README 协议文档表新增 BLE 协议行
- 本地无 RISC-V 工具链,MRS 真编译待板上联调(语法级 check_c_balance 通过)
2026-08-05 — offlog 协议导出命令分发落地 (MQTT V1.06 + TCP JSON V1.02)
背景
协议文档先行(MQTT V1.06 / TCP JSON V1.02,commit 08893d5)新增 log_stat / log_query / log_clear 三命令规范,固件命令分发未接。本次补齐 P1.3:offlog 底层 API 已就绪(count/read_idx/clear),差协议层接线。
offlog 导出 API(两侧共用,放 offlog 模块避免两协议层重复)
| API | 作用 |
|---|---|
offlog_enabled() |
日志功能是否启用(Flash 初始化成功) |
offlog_seq_last() |
最新一条记录全局序号(_wr_seq - 1,0=空) |
offlog_type_str(type) |
事件类型数字 → 协议字符串名(10 类 + unknown) |
offlog_evt_to_json() |
单条记录 → JSON 对象(data 按事件类型组装,无参数事件 data=null) |
OFFLOG_MAX_QUERY_RECORDS |
分页上限 4(MQTT ≤500B 发布限制) |
payload 大端解析(offlog_payload_u32)与写入侧 offlog_boot/offlog_coil 完全一致;coil sub 映射 1=car_enter 2=car_leave 3=loop_cut 4=loop_restore。
命令分发
- MQTT(
iot_handle_publish三分支):log_stat(enabled/boot_seq/count/capacity/seq_first/seq_last)、log_query(按全局序号分页,idx = start_seq - seq_first映射 offlog_read_idx,越界返回空 records,响应用static resp[IOT_MQTT_SEND_BUF_LEN]防大栈)、log_clear - TCP JSON(三 handler + cmd 表 3 条目,全部需鉴权):同上,
data_json[TCP_JSON_DATA_BUF_LEN]组装后json_send_ok
关键决策与坑
log_clear阻塞 ~2.8s(63 扇区擦除 × ~45ms):SPI 擦除只能在主循环上下文(中断写 SPI 会炸栈),三命令 handler 均在主循环 poll 链上调用,可接受;MQTT 侧 60s keepalive、TCP 有状态重传均能吸收。注释已标注。seq_first = seq_last - count + 1(count=0 时置 0):环形覆盖后 count < capacity 是正确语义,分页定位按全局序号不按时间(未同步段时间不可靠)。- CRLF 二进制编辑:四个 C 源文件 UTF-8+CRLF,用 Python rb/wb 精确替换,每处校验 count==1;gcc 全文件括号/引号平衡检查通过。
验证
test_offlog.c新增测试8test_export_json:enabled/seq_last/type_str/evt_to_json(boot rst 大端、coil sub/ch/value、evt_retry msg_id+retry、无参数 data:null、seq_first 公式)- gcc 隔离单测 8 例全过(含既有 7 例回归)
- CRLF/UTF-8 编码零漂移(file 复核)
2026-07-15 — 🔴 loop_data 陈旧快照: 事件与缓存不同源 (帧竞争)
现象 (现场测试: 长车过双线圈)
车离开线圈1后, event_report(car_leave ch1 val=97) 正确上报并 ACK; 但随后 loop_data 连续 34 秒 100+ 条 ch1/ch2 iscar:true, 直到 msg_id 141 才恢复正常。
根因: 两条帧消费路径的数据源分裂
0xC0 帧有两条竞争消费路径 (谁先看到 g_pkg_uart_2.flag 谁消费):
- uart_srv → lup_process_frame → lup 回调: 只喂事件检测
iot_evt_feed, 不更新_cached_sr - iot_mqtt_publish_sensor Step1 直读: 事件 + 缓存都更新
主循环里 uart_srv 先于 publish_sensor 执行, Step1 只能吃到"帧恰好在两调用之间完成"的窄窗口 — 实测命中率 ~1/56 ≈ 2%。结果:
- 事件路径 (回调) 每帧必达 → car_leave 正确 ✅
_cached_sr冻结在车压线圈时的旧帧 (freq=61518/diff=517/relay_count, msg 28~140 逐字节一致) ❌- 陈旧数据自激: 冻结的 diff=517 > 阈值 → fast_mode 锁死 → 以 300ms 最高频率刷屏错误快照
- 34s 后 Step1 偶然赢一次 → 缓存刷新 → car_edge(陈旧true→新false) 立即档发出 msg 141 恢复
证据: 同期真实 LUP 帧 eval 字节 car_state=0、variation=±个位数、misc_type 0→3 轮转, 与冻结快照完全对不上。
修复
- 新增
iot_sensor_ingest(): 事件沿检测 + 刷新_cached_sr, 单一数据源; lup 回调与 Step1 直读两路全部汇入 → 帧竞争从此无关紧要 - Step1 直读路径补
lup_verify_checksum(此前只有 uart_srv 路径过校验, 直读裸解析)
教训 (通用)
同一物理量的多个消费者必须同源。事件说"车走了"、快照说"车还在", 这种自相矛盾一定是数据源分裂 — 排查时先问: 这两个字段是从同一帧解析的吗?
板上验证
长车复测同场景: car_leave 事件后下一条 loop_data 的 iscar 必须已翻 false (两者同帧同源); 车走后 diff 回落 → 300ms 快速档应在数秒内退回空闲档, 不再刷屏。
2026-07-15 — loop_data 上报调度升级三档: car_state 沿立即上报
背景
原双速率 (空闲 interval / 变化态 300ms) 下, 进/出车沿最坏也要等 300ms 门控。车辆进出是最关键时刻 (道闸联动/计数), 王工要求: car_state 翻转沿立即上报, 不等间隔。
实现 (iot_mqtt_publish_sensor Step2/3)
三档调度:
| 档位 | 触发 | 间隔 |
|---|---|---|
| 立即档 | 任一通道 car_state 翻转沿 | 0 (直通门控) |
| 快速档 | 任一通道 |variation| > 阈值(10) | 300ms |
| 空闲档 | 平稳 | 配置 interval (默认 60s, 最小 1s) |
- car_edge 优先级最高, 命中即定档; variation 记录但不 break (后续通道可能有沿)
- 门控:
if (!car_edge && 未到间隔) return;— 沿直通, 其余照旧 - 快照仍在门控通过后刷新 → 同一个沿只触发一次立即发布, 下一轮回归常规档
防洪泛分析
立即档天然有界: Loop MCU 事件帧最快 150ms 一帧, car_state 是 Loop 侧防抖后的状态 (进入确认 3 次 + 离开检测), 不存在毛刺沿; 且发布后快照即同步, 同沿不重复。最坏情况 = 帧率上限 150ms/次, 可接受。
验证
tests/test_report_cadence.c 8 组全过: 空闲60s到点 / 进车沿距上次50ms立即发 / 同沿不重复 / 快速档仍受300ms门控 / 出车沿20ms立即 / 回落空闲 / 双通道同帧沿单包覆盖。
2026-07-15 — 设备时钟同步 (方案B): 平台经 report_config 下发 Unix ts
背景
原上行 ts = mstick()/1000 (上电秒数), 非 Unix 时间戳 (现场事故报告实锤)。设备无 RTC/SNTP。王工定方案B: initialize 上线 → 平台经 report_config 下发真 Unix ts → 设备校准。
实现
net_srv.c新增时钟模块:dev_time_sync(unix_ts)(合法性门槛 ≥1600000000 挡掉上电秒数/0) +dev_time_now()(已校准→真 Unix; 未校准→退回上电秒数)。基准用base_unix + (mstick()-base_tick)/1000, 无符号相减天然处理 49.7 天回绕。manage_mqtt_recv_message解析 msg_id 后, 从任意下行命令信封ts校准 (report_config 为主同步点)。- 全部上行
ts由mstick()/1000改dev_time_now(): net_srv.c 14 处命令响应 + initialize; iot_mqtt_srv.c loop_data + event_report(首发时刻, 重发不刷新) + 遗留响应。heartbeat 的uptime字段保留mstick()/1000(那本就是运行时长, 非时间戳)。
关键点
- 校准前 (含首个 initialize) ts=上电秒数, 平台按量级识别未校准。
- 设备重启无掉电保持 → 每次 initialize 后平台都须重下发 report_config 带 ts。
- 门槛 1600000000 (2020-09) 挡掉上电秒数(几百)/0/异常, 防污染基准。
验证
tests/test_dev_time_sync.c 6 组全过: 未同步退回上电秒数 / 非法ts被拒 / 校准瞬间对齐 / 校准后随时钟递增 / 门槛边界 / mstick 49.7天回绕无符号相减正确。
⚠️ 平台侧须配合: 收到 initialize 后, 下发 report_config 时信封 ts 填当前 Unix 时间 (协议 V1.05 §2.3)。
2026-07-15 — 🔴 现场事故: MQTT protocol error 重连风暴 (根因/修复)
现象
设备 DC045A49718F (DLD960GA) 上电后每 ~1s 被 mosquitto 以 disconnected due to protocol error 踢下线, 15 分钟 80+ 次重连。平台抓包: 设备发出 520B 垃圾包 = 前 512B 全 0x00 + 尾部 8B ASCII "report_c"。
根因 (静态分析 + 报文长度量化坐实, 与平台"环形缓冲/event_report队列"推测不同)
mqtt_publish 的 mqttBuf 只有 512B, 但 loop_data(4通道)≈604B → 溢出。
链路:
loop_data4 通道 JSON = 576B payload / 604B 整包 MQTT >MAX_MQTTBUF_LEN=512MQTTSerialize_publish检测缓冲不足 → 返回MQTTPACKET_BUFFER_TOO_SHORT(-2), 且一字节不写 buf(仍是 clear_mqtt_buf 的全零)mqtt_publish里len是 uint32_t → -2 变 4294967294WCHNET_SocketSend(mqttBuf, &len)把清零的 512B + 越界相邻全局temp_guide("report_config"→"report_c") 当一包发出 = 520B 垃圾- broker 见非法报文类型 0x00 → RST → TCP Timeout(0x40) → 全量重连 → 风暴
⚠️ 与 event_report 无关: 事故日志中设备从未发过 event_report;
mqtt_publish是扁平缓冲非环形。此为先前就存在的隐患, 4 通道 loop_data 必触发。report_config 响应仅 216B 装得下, 故看着正常。
修复 (net_srv.c)
MAX_MQTTBUF_LEN512 → 1024: 容纳 604B loop_data + 余量 (TCP 自动分段, MQTT 不关心段边界)mqtt_publish的len改 int + 守卫:if (len <= 0) return;序列化失败绝不发残缓冲 —— 这是根本防线, 即便未来任何 payload 溢出也不再吐垃圾包- keepalive
MQTT_KEEPALIVE_INTERVAL9 → 60 (报告建议; 9s 过激易误断)
协议违规修复 (event_report, 报告实锤)
- 断线重连后重发用了新 msg_id → 平台 (sn,msg_id) 去重失效重复入库。修复
iot_evt_process重连沿: 有未决包时保持原 msg_id/原 ts 立即重发 (V1.04 §5.3-2), 不再作废换号。
待决 (需老大拍板)
- ts 字段是上电秒数 (mstick()/1000) 而非 Unix 时间戳: MQTT 模式设备无 RTC/SNTP 时间源。选项: (a) 平台以服务端收包时间为准忽略设备 ts (最省, 推荐); (b) 平台下发时间同步命令; (c) 设备加 SNTP。暂未改, 待定方案。
验证
tests/test_mqtt_publish_overflow.c: 复现旧版 512 缓冲发 520B 垃圾包(首字节0x00) + 验证守卫拦截 + 1024 缓冲 641B 正常 + report_config 回归。全过。tests/test_event_report.cT8 改为断言重连保持同 msg_id/ts。8 组全过。
⚠️ 板上验证: 编译查 .map 确认 RAM 余量(mqttBuf +512B); 烧录后观察不再有 TCP Timeout 风暴, loop_data 正常周期上报, 压线圈看 event_report 断线重连去重。
2026-07-15 — event_report 实现: 平台必答 + 设备重发 (协议 V1.04)
实现 (iot_mqtt_srv.c 事件模块 + net_srv.c ACK 路由)
| 组件 | 说明 |
|---|---|
沿检测 iot_evt_feed |
每帧比对 car_state/loop_state 快照。双路汇聚: Step1 消费路径直接喂 + lup_set_sensor_callback(iot_evt_sensor_cb) 覆盖 uart_srv 消费路径(两路都存在帧竞争, 单独任一路都会漏帧漏沿) |
| 事件队列 | 16 深环形, 溢出丢最旧; 事件仅在 ACK(code=0) 后出队 |
发送状态机 iot_evt_process |
每轮主循环调用(挂在 iot_mqtt_publish_sensor 内, 置于 READY/enable 检查之前)。5s 超时重发, 同 msg_id/原始 ts, 最多 3 次; 耗尽→挂起, 新事件或重连沿解除并以新 msg_id 合并重报 |
ACK 入口 iot_evt_handle_ack |
net_srv.c manage_mqtt_recv_message 收到 cmd=event_report 的回显帧时调用, 必须 return 不回 unsupported(否则与平台互打乒乓) |
| msg_id | 事件独立 uint32 计数器(g_iot_msg_id 是 uint8 且与 MQTT packet id 混用, 255 回绕会破坏平台去重窗口) |
关键决策
- 帧消费提前到 READY/enable 之前: 断网/未使能期间事件照样检测入队, 重连后补报。loop_data 周期上报仍受 READY+enable 门控。
- event_report 不受 report_config.enable 门控(王工 2026-07-15 拍板): 事件为关键不可再生数据, 上电即检测上报, 与 loop_data 周期上报的开关解耦。
report_config.enable只管 loop_data。 - 线圈断开期间屏蔽 car 沿: 断开时 car_state 不可信, 仅前后两帧 loop 均正常才判进出车; loop_restore 的 value = DBN 本地计时(mstick 差)/50。
- car_leave 的 value 直接取离开帧 misc(时间量) = 通过时间(50ms 单位)。
- 首帧只建快照: 上电线圈上已有车不算进入沿。
已知边界
- 队列溢出且恰有未决包时: 作废未决包重发新 msg_id → 平台 (sn,msg_id) 去重失效, 可能重复入库一包。触发条件: broker 不应答的 20s 窗口内涌入 >16 条事件, 现场概率极低, 平台可按事件内容+ts 二次去重兜底。
- BSS 增量 ~700B(队列128B + 发送缓冲 512B + 状态), 编译后查 .map 确认 RAM 余量。
验证
gcc 隔离单测 (/tmp/test_event_report.c) 8 组全过: 首帧快照/进出车沿/5s×3 重发同 id 同 ts/挂起与新事件解除/错 msg_id 及 code≠0 不出队/断开恢复时长+断开期屏蔽 car 沿/单包 6 条上限(实测 317B<500B)/重连沿补报。
⚠️ 平台端注意: 收到 event_report 先落库后应答, 按 (dev_serial,msg_id) 10 分钟窗口去重, 重复包直接答 code=0。
2026-07-15 — MQTT 快速上报增加 car_state 翻转沿触发
背景
原 fast_mode 仅由 |variation| >= 10 触发。灵敏度设高档时小车信号弱,variation 可能不过阈值,但 Loop MCU 已判定 car_state 翻转——进/出车事件仍按空闲间隔(≥1s)慢发,后台看到的过车时刻误差大。故增加:任一通道 car_state 翻转沿也触发 300ms 快速上报。
实现要点(两个坑,首版都踩了)
if (fast_mode = 0)单等号 → 赋值恒假,沿检测死代码。修正为直接判断(循环内走到该行 fast_mode 必为 0,无需再判)。- 快照刷新必须在间隔门控之后。若在门控前无条件刷新
_last_car_state,沿被门控吞掉(如距上次发布 <300ms)时快照已同步,下一轮检测不到翻转 → 事件退化为空闲间隔上报。修正:return(未到间隔)时不刷快照,沿保持"待发"持续顶住 fast_mode,直到真正发布才同步。
/* Step 2 循环内: variation 阈值 与 car_state 沿, 任一命中即 fast */
if (av >= IOT_MQTT_VARIATION_THRESHOLD) { fast_mode = 1; break; }
if (_last_car_state[i] != coils[i].car_state) { fast_mode = 1; break; }
/* Step 3 门控通过后才刷新快照 */
for (i = 0; i < coil_count; i++) _last_car_state[i] = coils[i].car_state;
验证
本地 gcc 隔离单测(buggy vs fixed 对照):进车沿后 buggy 延迟 950ms(退化空闲间隔),fixed 250ms(≤300ms 快速档);出车沿、负向 variation 回归均通过。
⚠️ 上电首帧若已有车(_last_car_state 初值 0 vs car_state=1),会触发一次 fast_mode——属良性,首帧本就该尽快发。
2026-07-14 — Loop 上报 variation 解析升级 2B→3B 有符号 (协议 V1.05)
背景
Loop MCU 上报的 SENS_MULTI_LOOP_DYNAMIC (0xC0/0x0C) 中变化量 variation 由 2B 无符号扩展为 3B 有符号补码(详见 Loop 侧 devlog 与协议 V1.05)。DBN 作为接收/转发端,解析逻辑须同步升级,否则每通道单元 12B 会被按旧的 11B 步长错位解析,4 通道数据全错。
改动 (4 处)
1. loop_uart_proto.h — 结构体字段类型
// uint16_t variation; -> int32_t variation; // 3B有符号, = Origin-CAPVD
2. loop_uart_proto.c — 解析函数 lup_parse_sensor_report
- 每通道单元步长
i*11→i*12 - 通道数计算
data_len/11→data_len/12 - 频率仍 3B 无符号;变化量 3B 解码 + bit23 符号扩展:
int32_t v = c[5] | ((int32_t)c[6] << 8) | ((int32_t)c[7] << 16);
if (v & 0x800000) v |= (int32_t)0xFF000000; // 符号扩展, 漏了负值会变~1600万大正数
cs->variation = v;
- 杂项偏移
c[7..10]→c[8..11](整体右移 1B)
3. iot_mqtt_srv.c — fast_mode 阈值判断(隐藏坑)
// variation 变有符号后, 车进入(正)/反向漂移(负) 都应触发加速上报
int32_t av = (v >= 0) ? v : -v;
if (av >= IOT_MQTT_VARIATION_THRESHOLD) fast_mode = 1; // 原 variation>=10 会漏掉负向
4. JSON 输出(iot_mqtt_srv.c / tcp_json_srv.c)
"diff":%d/"variation":%d格式符不变——CH32V208 上int32_t == int,%d天然正确输出负号,无需改动。
缓冲核查
整帧 52B→56B,DBN 侧 g_pkg_uart_2.pkg[BUFF_STACK_SIZE=512]、LUP_MAX_PKG_LEN=70 均远大于 56B,接收无截断;转发 MSS=576 不分片。
⚠️ 必须与 Loop 固件同版本发布(步长死绑定)。
2026-06-26 — TCP JSON 协议框架搭建
1. TCP JSON Server 基础实现
- WCHNET TCP listen socket 创建(端口 5960,PROTO_TYPE_TCP)
- 参考 WCH 官方 EVT/EXAM/ETH/TCPServer 例程
g_net_state.flag状态机:ETH_LibInit→1, WCHNET_CreateUdpSocket→2, WCHNET_CreateTcpSocket→3- SSC 禁用时 (
NET_SSC_ENABLE=0) 手动设flag=2跳过 UDP 初始化
2. 鉴权 + 15 条命令
pwd_verify鉴权,3次错误 → 60s 锁定- 命令表驱动分发:
dev_info_query,ssc_net_set/query,iot_net_set/query,iot_topic_set/query,pwd_set,factory_reset,device_reset,loop_param_set/query,loop_version_query,loop_factory_init,loop_sens_read/write - Deferred 响应模式: Loop MCU 命令异步 →
TcpJsonPending挂起 →json_check_pending()轮询
3. simple_json 解析器修复
- 6 个 bug 修复: NULL 解引用崩溃(plain values)、缺少 null terminator、buffer unsafe clear、数组支持缺失
- 字符串值提取时不带引号(调用方无需手动 strip quotes)
4. WCHNET TCP 踩坑
| 问题 | 修复 |
|---|---|
| listen socket 与数据 socket 混用 → 收不到数据 | CONNECT 发到 N, RECV/DISCONNECT 发到 N+1 |
WCHNET_SocketSend 在 listen socket 静默失败 |
改用 g_json_socket_listen + 1 |
| 接收缓冲区与帧缓冲区重叠 → 数据损坏 | 分离 WCHNET 内部 buf + 帧累加 buf |
#if NET_SSC_ENABLE 误包共享函数 |
移出 mStopIfError/GetMacAddr/get_ipstr_to_array |
2026-06-30 — Loop MCU 串口协议 (0x7F) + 传感器上报
1. USART2 Loop MCU 通信
- 波特率 192000(文档写 115200,实际硬件 192000)
- 0x7F 协议帧解析器:
lup_feed_byte()逐字节状态机 + LEN-based 帧边界 - 校验字节站位:
total_len = 5 + LEN(CMD 已在 LEN 中,勿重复计) - 命令:
lup_cmd_send()发送 →lup_cmd_check_timeout()轮询超时
2. 0xC0 传感器数据上报
- Loop MCU 主动推送 0xC0 帧 →
lup_process_frame()校验 → 回调json_sensor_callback - TCP JSON 输出格式:
{"sens_type":"multi_coil","coils":[...]} g_report_active开关控制上报启停- 0xC0 帧通过回调直接驱动网络上报,不经
uart_srv阻塞
3. 0xC0 帧时间量字段
misc_type=0→passtime_ms(通过时间 / 车间距,根据car_state区分)misc_type=1→cut_amount(线圈断开次数)misc_type=2→flow_amount(车流量)misc_type=3→relay_count(继电器动作次数)
4. 协议文档
docs/vd960_loop_protocol_v1.0x.md— 0x7F 帧格式、校验算法、命令参考- 波特率修正: 115200 → 192000
2026-07-01 — passtime_ms5 字段改名
vd960Loop 侧时间戳从 5ms 改 50ms 后,DBN 侧同步:
passtime_ms5→passtime_ms(字段名去 5 后缀)loop_uart_proto.h/c结构体 + 解析 +tcp_json_srv.cJSON 字段同步
2026-07-02~03 — TCP Server 超时自动重启机制
1. 三项超时触发条件
| 条件 | 时间 | 处理 |
|---|---|---|
| 无任何连接 | 5min | do_restart → 计数重启 |
| 无数据交互 | 5min | do_restart → 计数重启 |
| Auth 密码错 3 次 | 立即 | tcp_json_restart() 直接重启 |
2. 重启计数保护
- 最多连续重启 3 次,超过进入 10 分钟冷却期
CONNECT成功时restart_count清零(正常连接重置计数,不算异常)- Auth timeout 不消耗计数器(正常运维行为)
3. WCHNET 限制 - listen socket 不可关闭重建
根因: WCHNET_SocketClose(listen) 后端口 5960 仍标记"已占用",SocketCreat 返回 ERR_ISCONN(0x1D)。
最终方案: tcp_json_restart() 只关数据 socket(N+1) 断开客户端,listen socket 保持不动,仅重置应用层状态。WCHNET 自动处理下一个 CONNECT。
4. Auth timeout 死循环修复
原 Auth timeout 手工 WCHNET_SocketClose 后未设 g_json_socket_listen = 0xFF,下次 poll 条件仍满足 → 无限打印。改用统一的 do_restart 路径后修复。
5. Auth 3次失败 → 直接重启
去掉 60s Auth timeout 倒计时。改为连接后 3 次鉴权失败(密码错/格式错)即 tcp_json_restart()。连接后无交互由 5min 空闲检查覆盖。
2026-07-06 — MQTT IoT 基础打通
背景
在 CH32V208(RISC-V, 栈仅 2KB)上实现 MQTT IoT 协议栈,对标 DBN101GA 参考项目。WCHNET TCP MSS=576, Publish 须 ≤500B。
1. MQTT 重构对标 DBN101GA
iot_mqtt_srv.c对标 DBN101GA 参考实现重写- MQTT socket 复用
SocketId_TCP(而非独立 socket) iot_connect_broker加SourPort参数- 出厂默认 topic 使用真实设备序列号
2. 栈溢出系列修复
| 问题 | 修复 |
|---|---|
| MQTT buffer 在栈上 → 溢出 | 改为 static 全局分配 |
iot_mqtt_publish_sensor: data_json[2KB] + payload[2KB] = 4KB 远超 2KB 栈 |
减小缓冲区 + static 分配 |
| 缓冲区 2048 → 1024 | 单次交互不超 1KB |
3. MQTT CONNECT 延迟发送
根因: MQTT_connect() 在中断上下文调用 WCHNET_SocketSend → 崩溃。
修复: CONNECT 包延到 poll 轮询内发送,避免中断内调用网络 API。
4. MQTTPacket 初始化修复
MQTTPacket_connectData_initializer 是复合字面量,不能用于赋值。改用临时变量中转。
5. IoT socket 重复创建 + broker IP 解析修复
- IoT socket 重复创建 → 连接失败,改为复用已有 socket
- broker IP 字符串解析修正
2026-07-07 — MQTT 协议 V1.01 + 命令分发
1. MQTT 主题压缩:多主题 → 双主题
协议从 V1.00 的多 topic(dld960/{sn}/loop_data, /event_report, /heartbeat, /cmd, /cfg…)压缩为 V1.01 双主题:
| 方向 | V1.00 | V1.01 |
|---|---|---|
| 设备→平台 | 5+ topics | dld960/{sn}/dev |
| 平台→设备 | 5+ topics | dld960/{sn}/srv |
2. MQTT 命令分发
manage_mqtt_recv_message() 实现 V1.01 协议命令分发,支持 cmd / Method 双格式:pwd_verify, dev_info_query, loop_param_set/query, loop_version_query, loop_factory_init, device_reset, factory_reset 等。
3. report_config 完整 7 参数
MQTT/TCP report_config 支持完整 7 参数配置:period, loop_data, event_report, heartbeat, passtime, cut_amount, flow_amount。与 TCP JSON 协议统一。
4. MQTT 网络配置
ssc_net_set / iot_net_set / iot_topic_set 三条命令实现,支持通过 MQTT 远程配置设备网络参数和主题。
2026-07-08 — MQTT 稳定性修复系列
1. g_iot_socket 未初始化 → 硬故障重启
g_iot_socket 声明为全局变量但未初始化,值为 0xFF → WCHNET_SocketSend(0xFF) 直接触发硬件故障重启。
修复: 初始化为 INVALID_SOCKET。
2. loop_data JSON 超 MSS 导致 SocketSend 溢出
WCHNET TCP MSS=576,原 loop_data JSON 四通道全字段 ~800B → WCHNET_SocketSend 溢出崩溃重启。
修复: 精简 JSON 字段至 ~400B(单包达标),再分批发送(2通道/包 × 2包)。
3. iot_mqtt_send() hex dump 循环 → 栈溢出
调试用的 hex dump 循环(65次 PRINT)在仅 2KB 栈的 CH32V208 上直接栈溢出重启。
修复: 移除 hex dump 循环。
4. MQTT 主动上报无数据 + PINGREQ 缺失
poll_mqtt() 未正确触发 MQTT_Yield() 和 MQTT_Live(),导致主动上报无数据、PINGREQ 缺失断连。
修复: 改用 net_srv.c 统一的 mqtt_publish() + 修正 poll_mqtt() 调度逻辑。
5. 传感数据 topic 修正
topic 修正为 V1.01 双主题协议 dld960/{sn}/dev(此前遗留旧 topic 格式)。
6. 恢复 loop_data 完整字段
精简 JSON 后缺失部分字段(freq, passtime_ms 等),恢复完整字段并在分批框架内发送。
2026-07-10 — 双主题发布统一 + initialize 对齐 + packet_id 断连修复
1. 双主题发布统一 (dld960/{sn}/dev)
此前 heartbeat 和传感器上报仍使用旧 topic 路径(dev/status、g_iot_topic.topic_pub),与 V1.01 双主题模型不一致。
修复: 所有 MQTT 发布统一为 dld960/{sn}/dev:
iot_mqtt_srv.cheartbeat:iot_make_topic(..., "dev", "status", ...)→snprintf(..., "dld960/%s/dev", ...)iot_mqtt_srv.ciot_mqtt_publish_sensor:g_iot_topic.topic_pub→dld960/{sn}/devnet_srv.cdev_initialize_pub:g_iot_topic.topic_pub→dld960/{sn}/dev
2. initialize 消息格式对齐 V1.03
dev_initialize_pub() 原先缺少顶层 model/hard_ver/soft_ver 字段,且在 extra_info 中有已废弃的 version 字段。
修复: 对齐协议文档 §5.1:
- 补充
model(PRODUCT_MODEL)、hard_ver(HARDWARE_VER)、soft_ver(FIRMWARE_VER) 到data顶层 - 移除
extra_info.version - 缓冲区扩容 256→512 字节
3. mqtt_publish() packet_id=0 导致 broker 断连 🔴
现象: 设备收到查询指令后正确发布响应 JSON,10ms 后 broker 主动断开 TCP 连接(SockInt stat=0x10)。
根因: net_srv.c 的 mqtt_publish() 调用 MQTTSerialize_publish() 时 packet_id 硬编码为 0。当 req_qos=1(命令响应使用 QoS 1)时,MQTT 规范要求 packet_id ≠ 0,0 属于协议违规,broker 检测后直接踢掉连接。
修复: 新增 static uint16_t s_mqtt_pkt_id 计数器,QoS>0 时自增作为 packet_id,QoS=0 保持 0。
static uint16_t s_mqtt_pkt_id = 0;
uint16_t pkt_id = (req_qos > 0) ? ++s_mqtt_pkt_id : 0;
4. DBNMQTTool 同步更新
- 协议版本注释 V1.01→V1.03
- 协议 Topic 树 + 模拟页增加
initialize支持 device_manager.mark_online()扩展model/hard_ver/soft_ver参数,设备列表正确显示型号- 增加
dld960/+/dev/#通配订阅兼容旧固件多级 topic
2026-07-23 — loop_data JSON 构建优化: 消灭 data_json 中间缓冲 + coil_count 硬限
背景 / 问题
现场 DC045A49718F 出现 MQTT 上报异常: 平台收到 _raw 消息, hex 解码后发现
loop_data JSON 中间嵌入了完整的 MQTT PUBLISH 二进制帧 (event_report 的封包)。
根因: iot_mqtt_publish_sensor() 的 static char data_json[1024] 和
static char payload[1400] 合计 2424B BSS, 叠加同文件其他大缓冲 (~9KB total),
在 CH32V208 48KB RAM 中可能导致链接器将 data_json/payload 与 mqttBuf[1024]
分配到重叠地址。
污染路径:
iot_evt_process()→mqtt_publish()→MQTTSerialize_publish(mqttBuf,...)写入 MQTT 二进制到 mqttBuf- 因 BSS 重叠,
data_json也被写入相同内容 snprintf(data_json, ...)覆写前 ~427B 为通道 JSON, 尾部残留 MQTT 二进制snprintf(payload, ..., "%s", data_json)→%s将残留二进制也拷进 payloadmqtt_publish(topic, payload, 0)→ 发出污染后的 payload
修复
| 改动 | 说明 |
|---|
2026-07-23 — MQTT 栈重构 + 硬件看门狗
问题背景
vd960DBN 现场出现三类 MQTT 上报异常:
- _raw 异常: loop_data JSON 中间嵌入 MQTT PUBLISH 二进制帧
- 频繁 initialize: 每 4 秒重连, 旧 MQTT 栈与 IoT 栈抢 socket
- SocketSend hard fault: MQTT CONNECT 发送后设备重启
根因链
| 问题 | 根因 | 修复 commit |
|---|---|---|
_raw |
① data_json[1024] 与 mqttBuf[1024] BSS 重叠 → 消灭 data_json |
e11a80c |
_raw 仍出现 |
② mqtt_publish() 用全局 mqttBuf, event_report 二进制污染 loop_data |
086dbc6 |
→ 改用 iot_mqtt_publish(), buf[1024] 与 mqttBuf 物理隔离 |
||
| 频繁 initialize | ③ WCHNET_HandleSockInt 调旧 mqtt_connect() + IoT 栈也发 CONNECT → 双 CONNECT |
3d017cc |
→ socket 事件全委托给 iot_mqtt_handle_sock_int, 旧栈切断 |
||
| SocketSend 重启 | ④ iot_mqtt_send() 里 uint16_t slen 传 uint32_t* → 栈破坏 hard fault |
a577537 |
| SocketSend 还重启 | ⑤ WCHNET_ModifyRecvBuf(_iot_wchnet_buf) 覆盖原生 SocketRecvBuf → DMA 非法 |
9521a5e |
⑥ tmp[1152] 栈变量在中断上下文撑爆 2KB 栈 → 改 static |
||
| SocketSend 再重启 | ⑦ WCHNET SocketSend 在中断外调用 hard fault → 改回中断内发 CONNECT | 32f0edb |
_raw 再次出现 |
⑧ WCHNET_SocketSend 部分发送 520/607B → 残留拼到下一帧 |
9e83063 |
→ iot_mqtt_send 改为 while 循环重试 |
||
| 命令处理缺失 | ⑨ report_config/event_report ACK 无人处理 → code=4 |
67976ec |
| msg_id 跳号 | ⑩ 心跳 ++g_iot_msg_id + iot_mqtt_publish 双递增 |
a9c36e4 |
三层保活
┌─ MQTT 层: PINGREQ/PINGRESP (60s) → broker 不回 → TCP 超时
├─ TCP 层: KeepAlive → WCHNET SINT_STAT_DISCONNECT/TIMEOUT
└─ 硬件层: IWDG (LSI≈40kHz, Prescaler=256, Reload=625 → ~4s)
主循环 iot_mqtt_poll() 每轮喂狗
heartbeat 是单向设备上报, 平台不回复; MQTT PINGREQ/PINGRESP 才是双向保活机制。
最终架构
WCHNET_HandleSockInt (net_srv.c)
└─ iot_enable? → SocketRecvBuf(原生) + KeepLive + iot_mqtt_handle_sock_int()
├─ CONNECT → iot_mqtt_send_connect() [中断内] → MQTT_CONNECTING
├─ RECV → _iot_recv_buf → iot_process_recv()
│ ├─ CONNACK → iot_mqtt_send_subscribe() → SUBACK → READY
│ ├─ PUBLISH → iot_handle_publish(report_config/event_report ACK/...)
│ └─ PINGRESP → (MQTT 库自动处理)
└─ DISCONNECT → g_iot_state=DISCONNECTED → 重连退避
Main_Circulation:
iot_mqtt_publish_sensor() ← 0xC0→loop_data + event_report
iot_mqtt_poll() ← IWDG喂狗 + 状态机 + PINGREQ(60s)
发送缓冲隔离 (v3)
event_report → mqtt_publish() → mqttBuf[1024] (net_srv.o BSS)
loop_data → iot_mqtt_publish() → buf[1024] (iot_mqtt_srv.o BSS)
heartbeat → iot_mqtt_publish() → buf[1024]
initialize → iot_mqtt_publish() → buf[1024]
msg_id 统一
所有发布点用 g_iot_msg_id + 1 预读, iot_mqtt_publish 内部 ++g_iot_msg_id (MQTT pktid for QoS>0, 对QoS=0 也递增以保持一致)。
2026-07-23 — 保活精简 + SocketSend 故障恢复
保活机制梳理
heartbeat JSON (60s 单向设备上报) 与 MQTT PINGREQ/PINGRESP 功能重叠:
- heartbeat 是设备→平台单向, 平台不回复, 不能做存活检测
- MQTT PINGREQ/PINGRESP 才是双向保活, broker 不回 → TCP 超时
决策: 删除 heartbeat JSON, MQTT PINGREQ 独立保活;
loop_data 按 g_report_cfg.interval 上报即可。
三层保活链路
| 层级 | 机制 | 超时 | 触发 |
|---|---|---|---|
| MQTT | PINGREQ/PINGRESP (60s) | broker无响应→TCP超时 | SINT_STAT_TIM_OUT |
| TCP | KeepAlive | WCHNET 检测 | SINT_STAT_DISCONNECT |
| 硬件 | IWDG (4s, LSI) | 主循环卡死 | 硬件复位 |
SocketSend 故障快速恢复
问题: WCHNET_SocketSend 突然返回 ret=0x11 sent=0,
TCP 超时要等 ~2 分钟才触发 SINT_STAT_TIM_OUT,
期间所有 loop_data/event_report 全丢。
修复 v1 — 连续失败检测 (7c0de6c):
_iot_send_fail_cnt追踪连续失败次数- 3 次连续失败 → 主动
WCHNET_SocketClose+DISCONNECTED→ 重连 - 任一次成功清零, 新连接也清零
修复 v2 — 重连时重建 socket (98d9b99):
iot_connect_broker()原只设g_iot_socket = SocketId_TCP但 close 后 socket 已销毁 → TCP connect 一直超时- 改为调
WCHNET_CreateTcpMqttSocket()真正 create + connect - 断连后加 3s 延迟再重连, 给 WCHNET 清理时间
BSS 变动
| 变量 | 改动 | ΔBSS |
|---|---|---|
data_json[1024] |
删除 (v1) | -1024B |
iot_send_heartbeat::payload[512] |
删除 (函数移除) | -512B |
_iot_send_fail_cnt |
新增 | +4B |
| 净省 | -1532B |
---|
| 消灭 data_json[1024] | 直接在 payload[1400] 构建完整 JSON, 省 1024B BSS |
| 消除 %s 拷贝 | 不再从中间缓冲格式化到 payload, 杜绝尾部二进制残留路径 |
| coil_count 硬限 | if (coil_n > 4) coil_n = 4 — 防 0xC0 坏帧致 snprintf 循环溢出 |
| JSON 构建顺序调整 | 先写包装头 {"msg_id":...,"data":{"channels":[, 再追加通道, 最后 ]}} |
BSS 节省: 1024 bytes
协议兼容: JSON 输出格式不变 ({"msg_id":N,"cmd":"loop_data","ts":T,"data":{"channels":[...]}})
2026-08-04 — 脱机事件日志系统 (W25Q32 环形) — 解决"重复上线"取证
背景
设备挂平台测试出现重复上线(现场未断电)。平台每次收到 initialize 都视为上线,但无本地日志无法区分两种可能:
| 可能 | 特征 | 诊断 |
|---|---|---|
| 设备真复位 | 有 BOOT 事件 + boot_seq 递增 | 设备侧问题(电源/看门狗/软件) |
| MQTT 断连重连 | 无 BOOT 事件,只有 IOT_* 事件,boot_seq 不变 | 网络问题(链路/ broker) |
注:
iot_handle_suback()每次 MQTT 连上 READY 都会发iot_send_initialize()——断连重连就会触发平台"重复上线",这是最大嫌疑点。
方案 (P1.2 落地: 事件流, 快照流后置)
分区 (W25Q32 4MB):参数区 64KB(0x000000) | 事件日志区 256KB(0x010000) | OTA 区 512KB(0x050000) | 快照区 ~3.25MB(0x0D0000, 预留)
环形实现:头扇区(扇区0, 32B 元数据) + 63 数据扇区 × 128 条 × 32B = 8064 条;顺序写、满扇擦下一扇区(天然磨损均衡);头在扇区切换时刷新,上电从头部写位置向后扫描恢复(掉电不丢)
记录格式 (32B 定长):magic type len flags | seq(4) ts_ms(4) unix_ts(4) boot_seq(2) rsvd(2) | payload[12]
事件类型 (V1):
| type | 事件 | 说明 |
|---|---|---|
| 0x01 | BOOT | 复位原因寄存器 RCC_RSTSCKR 全量入日志 (IWDG/POR/SFT 区分) |
| 0x10/0x11 | IOT_CONNECT / IOT_READY | READY 即发 initialize——重复上线直接证据 |
| 0x12 | IOT_DISCONN | 细分原因: 1=断开 2=超时 3=CONNACK拒绝 4=连接超时 |
| 0x13 | IOT_RECONN | 退避时长 ms |
| 0x30/0x31 | EVT_RETRY / EVT_GIVEUP | event_report ACK 超时重发/耗尽 (网络质量) |
| 0x40 | COIL | 进/出/断/恢复 (sub+ch+value) |
| 0x50 | TIME_ANCHOR | 时钟同步锚点 (boot_seq↔unix 回算绝对时间) |
| 0x70 | LOG_CLEAR | 清日志审计 |
插桩点(全部主循环上下文,socket 中断内不写 SPI):
| 文件 | 位置 | 事件 |
|---|---|---|
| peripheral_main.c | main() storage_init 后 | offlog_init + 复位原因读+清标志 |
| iot_mqtt_srv.c | iot_mqtt_poll() 状态沿检测 | CONNECT/READY/DISCONN |
| iot_mqtt_srv.c | 重连退避 / TCP超时 / CONNACK拒绝 | RECONN / DISCONN(4) / DISCONN(3) |
| iot_mqtt_srv.c | iot_evt_process | EVT_RETRY / EVT_GIVEUP |
| iot_mqtt_srv.c | iot_evt_enqueue | COIL |
| net_srv.c | dev_time_sync 成功 | TIME_ANCHOR |
三个关键坑 (单测抓出来的)
- 中断上下文不能写 SPI:
iot_mqtt_handle_sock_int是 WCHNET 中断,SPI 擦除 ~45ms 阻塞会炸;MQTT 事件改在iot_mqtt_poll()状态沿检测(毫秒级滞后,可接受) sizeof(OfflogEvt)=36而非 32:payload[14] 后结构体对齐补 2B padding → 每扇区实际 113 条,8064 条撑爆 63 扇区提前回绕、写指针错乱。修复:字段重排(uint8×4→uint32×3→uint16×2→payload[12])+ 编译期断言typedef char size_must_be_32[...]- 扇区级覆盖粒度 vs 记录级 count:回绕擦整扇区会丢 128 条,但 count 仍封顶 8064 → read_idx 反推逻辑首错位。修复:擦扇区前检查目标扇区是否有数据(读首条 magic),有则
count -= min(count,128)
单测 (gcc 隔离, tests/test_offlog.c)
| 用例 | 覆盖 |
|---|---|
| test_fresh_init | 全新初始化 + BOOT 事件字段 |
| test_ring_wrap | 8069 条环形回绕: count=7941, 逻辑首 seq=129 |
| test_power_loss_recovery | 掉电重启: boot_seq 递增, seq 跨 boot 连续 |
| test_sector_switch | 128 条扇区切换 + 头 wr_off/wr_sector |
| test_clear | 清空 + LOG_CLEAR 审计 (seq 不重置) |
| test_power_loss_mid_sector | 跨扇区掉电恢复 |
待办
- P1.3 导出:
log_query/log_stat/log_clear命令接入 MQTT/TCP/BLE - 快照流 (0xC0 帧原样落盘) 后置
- 复位原因寄存器布局待板上验证 (RCC_RSTSCKR 按 STM32F1 兼容写)
- MRS 工程编译确认 offlog.c 被自动收集
2026-08-04 — tcp_json_srv 缓冲宏化: data_json[2048] → TCP_JSON_DATA_BUF_LEN(1024)
三处 char data_json[2048](sensor_report 推送×2、loop_param_query 响应×1)改用头文件宏 TCP_JSON_DATA_BUF_LEN(默认 1024)。
容量核验:format_sensor_json 4 通道最大 ~660B、format_loop_param_json ~920B,均 < 1024 且 format 函数有 snprintf 溢出保护(written >= remaining 返回 -1)。最终发送帧受 TCP_JSON_MAX_FRAME=800 截断,data_json 只做中间组装,1024 足够。
附带收益:栈上缓冲 2048→1024,每处省 1KB 栈(CH32V208 栈紧张,中断路径 832/1322 在回调/主循环调用)。
2026-08-04 — offlog: TIME_ANCHOR 严格使用平台下发 unix_ts
offlog_time_anchor() 原为丢弃传参、内部重取 dev_time_now()(毫秒级误差)。改为 offlog_evt_ts() 直接写入平台下发值,锚点事件与平台 ts 严格一致。新增单测 test_time_anchor_exact(传 1800000000 验证写入值),7 例 ALL PASS。
修订记录
| 版本 | 时间 | 说明 |
|---|---|---|
| V4.0 | 2026-08-10 | BLE 脱机日志接口: OFFLOG_STAT/QUERY/CLEAR (0x25/0x26/0x27), 32B OfflogEvt 二进制直传, 缓冲扩容 132B, 隔离测试 7 例 + 协议文档 V1.00 |
| V3.9 | 2026-08-05 | offlog 协议导出命令分发: log_stat/log_query/log_clear 落地 MQTT V1.06 + TCP JSON V1.02 (seq_first/seq_last 计算、idx 映射、OFFLOG_MAX_QUERY_RECORDS=4、log_clear 阻塞~2.8s) |
| V3.8 | 2026-08-04 | offlog: TIME_ANCHOR 严格用平台下发 unix_ts (offlog_evt_ts), 单测 7 例 |
| V3.7 | 2026-08-04 | tcp_json_srv 缓冲宏化: data_json[2048]→TCP_JSON_DATA_BUF_LEN(1024), 省栈1KB×3 |
| V3.6 | 2026-08-04 | 脱机事件日志系统: W25Q32 256KB 环形(8064条) + BOOT/网络/事件/线圈/时钟锚点 8 类事件 + 掉电恢复 + gcc 单测 6 例 |
| V3.5 | 2026-07-23 | 保活精简(去heartbeat+IWDG) + SocketSend故障恢复(3次失败重连+重建socket) |
| V3.4 | 2026-07-23 | MQTT 栈重构: 发送缓冲隔离/命令处理/msg_id统一/看门狗, 共10项修复 |
| V3.3 | 2026-07-23 | loop_data: 消灭 data_json[1024] BSS 缓冲 + coil_count 硬限, 防 mqttBuf 重叠致 _raw 异常 |
| V3.2 | 2026-07-10 | 双主题发布统一 + initialize 数据格式对齐 V1.03 + mqtt_publish packet_id=0 断连修复 |
| V3.1 | 2026-07-09 | V1.03: 订阅后发 initialize 上线消息; iot_mqtt_srv 订阅改双主题 |
| V3.0 | 2026-07-08 | MQTT 稳定性修复: socket初始化/buffer溢出/分批发送/hex dump/PINGREQ |
| V2.9 | 2026-07-07 | MQTT V1.01 双主题协议 + 命令分发 + report_config 7参数 |
| V2.8 | 2026-07-07 | MQTT 网络配置: ssc_net_set / iot_net_set / iot_topic_set |
| V2.7 | 2026-07-06 | MQTT IoT 基础打通: 对标DBN101GA + 栈溢出修复 + CONNECT延迟发送 |
| V2.6 | 2026-07-06 | IoT 模式配置 bug 修复 |
| V2.5 | 2026-07-03 | tcp_json_restart 只关数据socket, listen保持不动 (WCHNET限制) |
| V2.4 | 2026-07-03 | 去掉 Auth 60s timeout, 改为3次失败即重启 |
| V2.3 | 2026-07-03 | Auth timeout 不计入重启限额, 修复冷却期阻断连接 |
| V2.2 | 2026-07-03 | Auth timeout 死循环打印修复 → goto do_restart |
| V2.1 | 2026-07-03 | TCP Server 自动重启机制 (3条件 + 3次限额 + 10min冷却) |
| V2.0 | 2026-07-01 | passtime_ms5 → passtime_ms 字段改名 |
| V1.5 | 2026-06-30 | 0xC0 传感器上报 + 时间量字段完善 + 协议文档 |
| V1.4 | 2026-06-30 | USART2 Loop MCU 0x7F 协议实现 |
| V1.3 | 2026-06-30 | simple_json 6 bug 修复 |
| V1.2 | 2026-06-30 | WCHNET buffer 分离 + SocketSend listen→data 修正 |
| V1.1 | 2026-06-30 | NET_SSC_ENABLE 隔离 + #if guard 共享函数修复 |
| V1.0 | 2026-06-26 | TCP JSON 协议框架 (鉴权 + 15条命令) |