From 3d4814bffe7f0eaeacf1b93e932f64682e43e047 Mon Sep 17 00:00:00 2001 From: wangfq Date: Wed, 12 Aug 2026 18:31:56 +0800 Subject: [PATCH] =?UTF-8?q?fix(vd960DBN):=20init/clear=20=E6=87=92?= =?UTF-8?q?=E6=93=A6=E4=BF=AE=E5=A4=8D=E4=B8=8D=E6=96=AD=E9=87=8D=E5=90=AF?= =?UTF-8?q?=20=E2=80=94=2034s=20=E5=85=A8=E9=87=8F=E6=93=A6=E9=99=A4?= =?UTF-8?q?=E8=B6=85=20IWDG=204s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 现场: 烧录快照固件后 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 回归全过 --- docs/DLD960_BLE协议.md | 2 +- .../APP/dbn_ble_srv.c | 4 +- .../APP/include/snapshot.h | 16 ++++---- .../BLE/OnlyUpdateApp_Peripheral/APP/offlog.c | 15 +++----- .../OnlyUpdateApp_Peripheral/APP/snapshot.c | 19 +++++----- .../OnlyUpdateApp_Peripheral/APP/usart_biz.c | 8 +++- vd960DBN/docs/devlog.md | 37 +++++++++++++++++++ vd960DBN/tests/test_snapshot.c | 36 ++++++++++++++++++ 8 files changed, 105 insertions(+), 32 deletions(-) diff --git a/docs/DLD960_BLE协议.md b/docs/DLD960_BLE协议.md index 99c5bc4..2f23c2a 100644 --- a/docs/DLD960_BLE协议.md +++ b/docs/DLD960_BLE协议.md @@ -168,7 +168,7 @@ Magic | Header | Data | CheckByte **请求 data:** 无。**响应 data(1B):** `status`:`0x00`=OK。 > ⚠ **高风险操作**:清除动作本身写入**事件流** `log_clear` 审计(payload[0]=2=快照流,不可清除)。 -> ⚠ **阻塞时长随芯片**:W25Q32 快照 751 扇区 SPI 擦除 ≈ 34s,期间主循环阻塞。请勿高频调用。 +> 阻塞 ~45ms:逻辑清除(count=0 旧数据立即不可读)+ 只擦写指针起点扇区,其余扇区由环形写覆盖时自动擦(原擦全部 751 扇区 ~34s,会触发 IWDG 4s 复位——2026-08-12 修复)。 成功后 `SNAP_STAT` 的 `count` 归 0,`seq_last` 继续递增(序号不复位)。 diff --git a/vd960DBN/BLE/OnlyUpdateApp_Peripheral/APP/dbn_ble_srv.c b/vd960DBN/BLE/OnlyUpdateApp_Peripheral/APP/dbn_ble_srv.c index ac41aeb..a08672e 100644 --- a/vd960DBN/BLE/OnlyUpdateApp_Peripheral/APP/dbn_ble_srv.c +++ b/vd960DBN/BLE/OnlyUpdateApp_Peripheral/APP/dbn_ble_srv.c @@ -1006,7 +1006,7 @@ void manage_dbn_ble_default(uint8_t *pkg, uint8_t len) (unsigned long)_start_seq, (unsigned long)_req_count, (unsigned)_j); } break; case CMD_DBN_OFFLOG_CLEAR: { - /* clear offlog (audit trail). blocking ~2.8s (63 sectors erase), main-loop ctx ok */ + /* clear offlog (audit trail). blocking ~45ms (logical clear + current sector), main-loop ctx ok */ uint8_t _i = 0; offlog_clear(); tmp_ble_buf[_i++] = 0x00; /* ok */ @@ -1097,7 +1097,7 @@ void manage_dbn_ble_default(uint8_t *pkg, uint8_t len) (unsigned long)_start_seq, (unsigned long)_req_count, (unsigned)_j); } break; case CMD_DBN_SNAP_CLEAR: { - /* clear snapshot (audit trail). blocking (sectors erase), main-loop ctx ok */ + /* clear snapshot (audit trail). blocking ~45ms (logical clear + current sector) */ uint8_t _i = 0; snap_clear(); tmp_ble_buf[_i++] = 0x00; /* ok */ diff --git a/vd960DBN/BLE/OnlyUpdateApp_Peripheral/APP/include/snapshot.h b/vd960DBN/BLE/OnlyUpdateApp_Peripheral/APP/include/snapshot.h index ded143f..8ddd57c 100644 --- a/vd960DBN/BLE/OnlyUpdateApp_Peripheral/APP/include/snapshot.h +++ b/vd960DBN/BLE/OnlyUpdateApp_Peripheral/APP/include/snapshot.h @@ -22,13 +22,13 @@ * - 顺序写, 写满擦下一扇区 (环形覆盖), 天然磨损均衡 * - 上电从头部记录的写位置向后扫描, 找第一条无效记录 = 真实写位置 (掉电恢复) * - * 线程模型 (关键): - * - 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, 留一格判满 + * 线程模型 (关键, 2026-08-12 修正): + * - USART2 ISR 只逐字节喂 lup_feed_byte(), 完整帧由主循环 uart_srv() 调 + * lup_process_frame → iot_sensor_ingest → snap_enqueue() — 全程主循环 + * - snap_enqueue(): 打包 + RAM 环形暂存 (无 SPI, 无阻塞); 满时丢新不覆盖 + * - snap_flush(): iot_mqtt_publish_sensor 每轮调用, RAM 暂存 → SPI 落盘 + * (扇区切换擦除 ~45ms 阻塞发生在主循环, 与 offlog 同底线) + * - 单生产者单消费者无锁环形: 生产者只写 tail/count, 消费者只读 head * * 记录格式 SnapRec (64B 定长, 无 padding): * magic(1) len(1) flags(1) rsvd(1) | seq(4) ts_ms(4) boot_seq(2) rsvd2(2) | coils(48) @@ -124,7 +124,7 @@ typedef struct { void snap_init(void); /* 上电初始化 (扫描恢复) */ uint8_t snap_enabled(void); /* Flash 初始化成功 */ -void snap_enqueue(const LUP_SensorReport *sr); /* 中断安全: 打包→RAM 暂存 (满丢新) */ +void snap_enqueue(const LUP_SensorReport *sr); /* 主循环入队: 打包→RAM 暂存 (满丢新) */ void snap_flush(void); /* 主循环: RAM 暂存→SPI 落盘 */ uint32_t snap_count(void); /* 有效记录数 */ diff --git a/vd960DBN/BLE/OnlyUpdateApp_Peripheral/APP/offlog.c b/vd960DBN/BLE/OnlyUpdateApp_Peripheral/APP/offlog.c index fe14973..73f35f9 100644 --- a/vd960DBN/BLE/OnlyUpdateApp_Peripheral/APP/offlog.c +++ b/vd960DBN/BLE/OnlyUpdateApp_Peripheral/APP/offlog.c @@ -142,11 +142,9 @@ void offlog_init(void) if (!(h.magic[0] == OFFLOG_HEAD_MAGIC0 && h.magic[1] == OFFLOG_HEAD_MAGIC1 && h.magic[2] == OFFLOG_HEAD_MAGIC2 && h.magic[3] == OFFLOG_HEAD_MAGIC3)) { - /* 全新区: 擦全部数据扇区, 写头 */ - uint32_t i; - for (i = 0; i < OFFLOG_DATA_SECTOR_CNT; i++) { - SPI_Flash_Erase_Sector(OFFLOG_DATA_BASE / 4096 + i); - } + /* 全新区: 懒擦 — 只擦当前写扇区 ~45ms (原擦全部 127 扇区 ~5.7s, + 主循环阻塞超 IWDG 4s 风险), 其余由环形写切扇区时自动擦 */ + SPI_Flash_Erase_Sector(OFFLOG_DATA_BASE / 4096); memset(&h, 0xFF, sizeof(h)); h.magic[0] = OFFLOG_HEAD_MAGIC0; h.magic[1] = OFFLOG_HEAD_MAGIC1; @@ -478,12 +476,11 @@ int offlog_evt_to_json(const OfflogEvt *e, char *buf, int buf_len) *===========================================================================*/ void offlog_clear(void) { - uint32_t i; if (!_ready) return; - for (i = 0; i < OFFLOG_DATA_SECTOR_CNT; i++) { - SPI_Flash_Erase_Sector(OFFLOG_DATA_BASE / 4096 + i); - } + /* 逻辑清除 + 只擦写指针起点扇区 (~45ms, 原 2.8s 接近 IWDG 4s 风险): + count=0 使旧数据不可读, 其余扇区由环形写覆盖时自动擦 */ + SPI_Flash_Erase_Sector(OFFLOG_DATA_BASE / 4096); _wr_off = OFFLOG_DATA_BASE; _wr_sector = 1; _count = 0; diff --git a/vd960DBN/BLE/OnlyUpdateApp_Peripheral/APP/snapshot.c b/vd960DBN/BLE/OnlyUpdateApp_Peripheral/APP/snapshot.c index 9c0da31..4d408b3 100644 --- a/vd960DBN/BLE/OnlyUpdateApp_Peripheral/APP/snapshot.c +++ b/vd960DBN/BLE/OnlyUpdateApp_Peripheral/APP/snapshot.c @@ -167,11 +167,9 @@ void snap_init(void) 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); - } + /* 全新区: 懒擦 — 只擦当前写扇区 (数据扇区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; @@ -306,7 +304,9 @@ static void snap_pack(const LUP_SensorReport *sr, SnapRec *rec) } /*=========================================================================== - * 中断安全入队 (USART2 ISR 上下文; 无 SPI, 无阻塞; 满丢新不覆盖) + * 入队 (主循环上下文: 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) @@ -389,12 +389,11 @@ int snap_read_idx(uint32_t idx, SnapRec *out) *===========================================================================*/ 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); - } + /* 逻辑清除 + 只擦写指针起点扇区 (~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; diff --git a/vd960DBN/BLE/OnlyUpdateApp_Peripheral/APP/usart_biz.c b/vd960DBN/BLE/OnlyUpdateApp_Peripheral/APP/usart_biz.c index e3c4030..8becf5b 100644 --- a/vd960DBN/BLE/OnlyUpdateApp_Peripheral/APP/usart_biz.c +++ b/vd960DBN/BLE/OnlyUpdateApp_Peripheral/APP/usart_biz.c @@ -191,8 +191,12 @@ void uart_srv(void) } else { - // 非 0x7F 魔法字节 - PRINT("Rcv_len:%d,dat: %s\n", g_pkg_uart_2.offset, g_pkg_uart_2.pkg); + // 非 0x7F 魔法字节 — hex 打印 (原 %s 会把二进制当字符串 → 乱码) + PRINT("Rcv_len:%d,dat:", g_pkg_uart_2.offset); + for(i = 0; i < g_pkg_uart_2.offset; i++){ + PRINT(" %02X", g_pkg_uart_2.pkg[i]); + } + PRINT("\n"); } } else{ diff --git a/vd960DBN/docs/devlog.md b/vd960DBN/docs/devlog.md index 177f65d..b7fe757 100644 --- a/vd960DBN/docs/devlog.md +++ b/vd960DBN/docs/devlog.md @@ -6,6 +6,43 @@ --- +## 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) ### 背景 diff --git a/vd960DBN/tests/test_snapshot.c b/vd960DBN/tests/test_snapshot.c index dfe71c7..d7325f0 100644 --- a/vd960DBN/tests/test_snapshot.c +++ b/vd960DBN/tests/test_snapshot.c @@ -333,6 +333,41 @@ static void test_part_table(void) printf("PASS test_part_table\n"); } +/* 测试9: 懒擦 — init 只擦当前写扇区, 其余扇区由环形写切扇区时覆盖 + (修复: 原 init 擦全部 751 扇区 ~34s → 主循环阻塞超 IWDG 4s → 不断复位) */ +static void test_lazy_erase(void) +{ + LUP_SensorReport sr; + SnapRec r; + uint32_t i; + + reset_flash(); + /* 在数据扇区 2 预埋旧数据 (模拟 Flash 残留) */ + flash[SNAP_DATA_BASE + 4096] = 0xAA; + flash[SNAP_DATA_BASE + 4096 + 1] = 0x55; + + snap_init(); + /* 懒擦: 只擦扇区 1, 扇区 2 保持旧数据 */ + CHECK(flash[SNAP_DATA_BASE] == 0xFF); /* 扇区1 已擦 */ + CHECK(flash[SNAP_DATA_BASE + 4096] == 0xAA); /* 扇区2 未动 */ + CHECK(flash[SNAP_DATA_BASE + 4096 + 1] == 0x55); + + make_report(&sr, 0x600, 600); + for (i = 0; i < 64; i++) { /* 写满扇区 1 */ + snap_enqueue(&sr); + snap_flush(); + } + CHECK(flash[SNAP_DATA_BASE + 4096] == 0xAA); /* 仍在扇区 1 内, 扇区2 未动 */ + + snap_enqueue(&sr); /* 第 65 条切扇区 */ + snap_flush(); + CHECK(flash[SNAP_DATA_BASE + 4096] != 0xAA); /* 扇区2 已擦并写入新数据 */ + CHECK(snap_read_idx(64, &r) == 0); + CHECK(r.seq == 65); + CHECK(snap_count() == 65); + printf("PASS test_lazy_erase\n"); +} + int main(void) { test_fresh_init_and_flush(); @@ -343,6 +378,7 @@ int main(void) test_ram_overflow_drop(); test_clear_audit(); test_part_table(); + test_lazy_erase(); if (failures) { printf("\n%d FAILURE(S)\n", failures);