diff --git a/vd960DBN/BLE/OnlyUpdateApp_Peripheral/APP/iot_mqtt_srv.c b/vd960DBN/BLE/OnlyUpdateApp_Peripheral/APP/iot_mqtt_srv.c index faf5ed4..1ee9e9b 100644 --- a/vd960DBN/BLE/OnlyUpdateApp_Peripheral/APP/iot_mqtt_srv.c +++ b/vd960DBN/BLE/OnlyUpdateApp_Peripheral/APP/iot_mqtt_srv.c @@ -449,6 +449,12 @@ static int iot_mqtt_publish(const char *topic, const char *payload, /* 处理一条收到的 MQTT PUBLISH 消息 */ static void iot_handle_publish(const char *topic, uint8_t *payload, int payload_len) { static char json[IOT_MQTT_RECV_BUF_LEN]; // static: 避免 1KB 栈开销 + /* OTA 命令 IO 缓冲 (V1.08): 各分支响应组包 + ota_data hex 提取共用 + (RAM 90% 环境: 6 个独立 static 数组 ~2.5KB 挤栈, 合并 union 省 ~2KB) */ + static union { + char resp[512]; /* ota_* 命令响应 JSON */ + char hexbuf[512 + 1]; /* ota_data hex 字符串 (512 hex + NUL) */ + } _ota_io; int copy_len = payload_len < (int)sizeof(json) - 1 ? payload_len : (int)sizeof(json) - 1; memcpy(json, payload, copy_len); json[copy_len] = '\0'; @@ -710,7 +716,6 @@ static void iot_handle_publish(const char *topic, uint8_t *payload, int payload_ } else if (strcmp(cmd_str, "ota_begin") == 0) { /* V1.08: 开启 OTA 会话 / 断点续传定位 (Loop MCU 远程 OTA, 先存后刷) */ - static char resp[400]; /* static: RAM 90% 下防栈峰值 (历史 .bss 挤栈事故) */ uint32_t size = 0, crc32 = 0; char version[16] = {0}; uint8_t force = 0; @@ -730,7 +735,7 @@ static void iot_handle_publish(const char *topic, uint8_t *payload, int payload_ rc = ota_cmd_begin(size, crc32, version, force); if (rc == 0) { const OtaMeta *m = ota_meta(); - snprintf(resp, sizeof(resp), + snprintf(_ota_io.resp, sizeof(_ota_io.resp), "{\"msg_id\":%lu,\"cmd\":\"ota_begin\",\"ts\":%lu,\"code\":0,\"msg\":\"success\"," "\"data\":{\"target\":\"loop\",\"slot\":\"%s\",\"offset\":%lu,\"received\":%lu," "\"size\":%lu,\"crc32\":%lu,\"state\":\"%s\"}}", @@ -740,18 +745,16 @@ static void iot_handle_publish(const char *topic, uint8_t *payload, int payload_ (unsigned long)m->size, (unsigned long)m->crc32, ota_state_str((uint8_t)m->state)); } else { - snprintf(resp, sizeof(resp), + snprintf(_ota_io.resp, sizeof(_ota_io.resp), "{\"msg_id\":%lu,\"cmd\":\"ota_begin\",\"ts\":%lu,\"code\":1,\"msg\":\"param error\"," "\"data\":{\"target\":\"loop\",\"err_code\":%d}}", msg_id, dev_time_now(), (rc == 3) ? 2 : 4); } - iot_mqtt_publish(resp_topic, resp, strlen(resp), 1); + iot_mqtt_publish(resp_topic, _ota_io.resp, strlen(_ota_io.resp), 1); PRINT("IOT: ota_begin size=%lu rc=%d\n", (unsigned long)size, rc); } else if (strcmp(cmd_str, "ota_data") == 0) { /* V1.08: 分片下发 (256B/片, 单片 CRC32; 重复片幂等, 乱序拒收指示续传) */ - static char resp[400]; - static char hexbuf[512 + 1]; /* 512 hex 最大, static 防栈峰值 */ const char *hp; uint32_t offset = 0, crc32 = 0; int rc; @@ -763,37 +766,36 @@ static void iot_handle_publish(const char *topic, uint8_t *payload, int payload_ if (strlen(tmp) > 0) crc32 = (uint32_t)strtoul(tmp, NULL, 10); /* hex 值提取: data 对象内字段是 "data":"...", 顶层 data 对象是 "data":{ — 前者唯一 */ hp = strstr(json, "\"data\":\""); - memset(hexbuf, 0, sizeof(hexbuf)); + memset(_ota_io.hexbuf, 0, sizeof(_ota_io.hexbuf)); if (hp) { hp += 8; /* 跳过 "data":" */ - strncpy(hexbuf, hp, sizeof(hexbuf) - 1); - { char *q = strchr(hexbuf, '"'); if (q) *q = '\0'; } + strncpy(_ota_io.hexbuf, hp, sizeof(_ota_io.hexbuf) - 1); + { char *q = strchr(_ota_io.hexbuf, '"'); if (q) *q = '\0'; } } - rc = ota_cmd_data(offset, crc32, hexbuf); + rc = ota_cmd_data(offset, crc32, _ota_io.hexbuf); if (rc == 0) { const OtaMeta *m = ota_meta(); - snprintf(resp, sizeof(resp), + snprintf(_ota_io.resp, sizeof(_ota_io.resp), "{\"msg_id\":%lu,\"cmd\":\"ota_data\",\"ts\":%lu,\"code\":0,\"msg\":\"success\"," "\"data\":{\"offset\":%lu,\"received\":%lu}}", msg_id, dev_time_now(), (unsigned long)offset, (unsigned long)m->received); } else if (rc == 1) { const OtaMeta *m = ota_meta(); - snprintf(resp, sizeof(resp), + snprintf(_ota_io.resp, sizeof(_ota_io.resp), "{\"msg_id\":%lu,\"cmd\":\"ota_data\",\"ts\":%lu,\"code\":1,\"msg\":\"crc/gap error\"," "\"data\":{\"err_code\":1,\"offset\":%lu,\"received\":%lu}}", msg_id, dev_time_now(), (unsigned long)m->received, (unsigned long)m->received); } else { - snprintf(resp, sizeof(resp), + snprintf(_ota_io.resp, sizeof(_ota_io.resp), "{\"msg_id\":%lu,\"cmd\":\"ota_data\",\"ts\":%lu,\"code\":3,\"msg\":\"busy\"," "\"data\":{\"err_code\":2}}", msg_id, dev_time_now()); } - iot_mqtt_publish(resp_topic, resp, strlen(resp), 1); + iot_mqtt_publish(resp_topic, _ota_io.resp, strlen(_ota_io.resp), 1); PRINT("IOT: ota_data offset=%lu rc=%d\n", (unsigned long)offset, rc); } else if (strcmp(cmd_str, "ota_end") == 0) { /* V1.08: 结束下载, 全镜像 CRC32 复核 */ - static char resp[300]; uint32_t crc32 = 0; int rc; memset(tmp, 0, sizeof(tmp)); @@ -801,43 +803,41 @@ static void iot_handle_publish(const char *topic, uint8_t *payload, int payload_ if (strlen(tmp) > 0) crc32 = (uint32_t)strtoul(tmp, NULL, 10); rc = ota_cmd_end(crc32); if (rc == 0) { - snprintf(resp, sizeof(resp), + snprintf(_ota_io.resp, sizeof(_ota_io.resp), "{\"msg_id\":%lu,\"cmd\":\"ota_end\",\"ts\":%lu,\"code\":0,\"msg\":\"success\"," "\"data\":{\"crc_ok\":true}}", msg_id, dev_time_now()); } else if (rc == 5) { - snprintf(resp, sizeof(resp), + snprintf(_ota_io.resp, sizeof(_ota_io.resp), "{\"msg_id\":%lu,\"cmd\":\"ota_end\",\"ts\":%lu,\"code\":5,\"msg\":\"crc mismatch\"," "\"data\":{\"err_code\":5,\"crc_ok\":false}}", msg_id, dev_time_now()); } else if (rc == 3) { - snprintf(resp, sizeof(resp), + snprintf(_ota_io.resp, sizeof(_ota_io.resp), "{\"msg_id\":%lu,\"cmd\":\"ota_end\",\"ts\":%lu,\"code\":3,\"msg\":\"busy\"," "\"data\":{\"err_code\":2}}", msg_id, dev_time_now()); } else { const OtaMeta *m = ota_meta(); - snprintf(resp, sizeof(resp), + snprintf(_ota_io.resp, sizeof(_ota_io.resp), "{\"msg_id\":%lu,\"cmd\":\"ota_end\",\"ts\":%lu,\"code\":1,\"msg\":\"incomplete\"," "\"data\":{\"offset\":%lu,\"received\":%lu}}", msg_id, dev_time_now(), (unsigned long)m->received, (unsigned long)m->received); } - iot_mqtt_publish(resp_topic, resp, strlen(resp), 1); + iot_mqtt_publish(resp_topic, _ota_io.resp, strlen(_ota_io.resp), 1); PRINT("IOT: ota_end rc=%d\n", rc); } else if (strcmp(cmd_str, "ota_abort") == 0) { /* V1.08: 中止会话, 释放暂存 */ - static char resp[256]; int rc = ota_cmd_abort(); - snprintf(resp, sizeof(resp), + snprintf(_ota_io.resp, sizeof(_ota_io.resp), "{\"msg_id\":%lu,\"cmd\":\"ota_abort\",\"ts\":%lu,\"code\":%d,\"msg\":\"%s\"}", msg_id, dev_time_now(), (rc == 0) ? 0 : 1, (rc == 0) ? "success" : "internal error"); - iot_mqtt_publish(resp_topic, resp, strlen(resp), 1); + iot_mqtt_publish(resp_topic, _ota_io.resp, strlen(_ota_io.resp), 1); PRINT("IOT: ota_abort rc=%d\n", rc); } else if (strcmp(cmd_str, "ota_flash") == 0) { /* V1.08: 触发本地 ISP 刷写 (同步安全检查 → 异步执行) */ - static char resp[300]; char slot_buf[8] = {0}; uint8_t slot = 0, force = 0; int rc; @@ -850,27 +850,26 @@ static void iot_handle_publish(const char *topic, uint8_t *payload, int payload_ if (strlen(tmp) > 0) force = (strcmp(tmp, "true") == 0); rc = ota_cmd_flash(slot, force); if (rc == 0) { - snprintf(resp, sizeof(resp), + snprintf(_ota_io.resp, sizeof(_ota_io.resp), "{\"msg_id\":%lu,\"cmd\":\"ota_flash\",\"ts\":%lu,\"code\":0,\"msg\":\"success, flashing async\"}", msg_id, dev_time_now()); } else if (rc == 3) { - snprintf(resp, sizeof(resp), + snprintf(_ota_io.resp, sizeof(_ota_io.resp), "{\"msg_id\":%lu,\"cmd\":\"ota_flash\",\"ts\":%lu,\"code\":3,\"msg\":\"busy\"," "\"data\":{\"err_code\":3}}", msg_id, dev_time_now()); } else { - snprintf(resp, sizeof(resp), + snprintf(_ota_io.resp, sizeof(_ota_io.resp), "{\"msg_id\":%lu,\"cmd\":\"ota_flash\",\"ts\":%lu,\"code\":1,\"msg\":\"param error\"}", msg_id, dev_time_now()); } - iot_mqtt_publish(resp_topic, resp, strlen(resp), 1); + iot_mqtt_publish(resp_topic, _ota_io.resp, strlen(_ota_io.resp), 1); PRINT("IOT: ota_flash slot=%d force=%d rc=%d\n", slot, force, rc); } else if (strcmp(cmd_str, "ota_status") == 0) { /* V1.08: 查询 OTA 状态 (含刷写进度) */ - static char resp[400]; const OtaMeta *m = ota_meta(); - snprintf(resp, sizeof(resp), + snprintf(_ota_io.resp, sizeof(_ota_io.resp), "{\"msg_id\":%lu,\"cmd\":\"ota_status\",\"ts\":%lu,\"code\":0,\"msg\":\"success\"," "\"data\":{\"target\":\"loop\",\"state\":\"%s\",\"slot\":\"%s\",\"size\":%lu," "\"received\":%lu,\"crc32\":%lu,\"version\":\"%s\"," @@ -881,7 +880,7 @@ static void iot_handle_publish(const char *topic, uint8_t *payload, int payload_ m->version, (unsigned long)ota_flash_sent(), (unsigned long)ota_flash_total(), (unsigned long)m->last_result, (unsigned long)m->last_result); - iot_mqtt_publish(resp_topic, resp, strlen(resp), 1); + iot_mqtt_publish(resp_topic, _ota_io.resp, strlen(_ota_io.resp), 1); PRINT("IOT: ota_status state=%s\n", ota_state_str((uint8_t)m->state)); } else if (strcmp(cmd_str, "ssc_net_query") == 0) { diff --git a/vd960DBN/BLE/OnlyUpdateApp_Peripheral/APP/ota_srv.c b/vd960DBN/BLE/OnlyUpdateApp_Peripheral/APP/ota_srv.c index ad1ff6b..83b1448 100644 --- a/vd960DBN/BLE/OnlyUpdateApp_Peripheral/APP/ota_srv.c +++ b/vd960DBN/BLE/OnlyUpdateApp_Peripheral/APP/ota_srv.c @@ -42,7 +42,6 @@ static uint8_t _fs_retry; /* 当前步重试计数 */ static uint32_t _fs_start_ms; /* 当前步起始时刻 (超时判断) */ static uint16_t _fs_sub; /* 当前块序号 (首=总块数, 末=1) */ static uint32_t _fs_sent; /* 已送 Loop 字节数 */ -static uint32_t _fs_block_crc; /* 当前块 CRC (调试/审计) */ uint8_t g_ota_flash_active = 0; /* 本地刷写进行中 → uart_srv 把 ACK 交给 ota_srv */ diff --git a/vd960DBN/docs/devlog.md b/vd960DBN/docs/devlog.md index e1341ad..4fe0b9c 100644 --- a/vd960DBN/docs/devlog.md +++ b/vd960DBN/docs/devlog.md @@ -4,6 +4,33 @@ > > 项目定位: DLD960 通信板 — BLE 配网、TCP JSON 协议服务、Loop MCU 串口桥接 +## 2026-08-20 — 联网复位事故修复:OTA 命令缓冲合并 union(RAM 90% 栈余量不足) + +### 现象(板级) + +联网后 SUBACK → 平台下发 MQTT PUBLISH(len=197,即 `report_config` 时钟同步)→ **HardFault → NVIC_SystemReset**(`RST_REASON: 0x10000000` = SFT 软件复位位,非 IWDG 0x20000000;CH32V20x 默认 HardFault_Handler 即软件复位)→ 死循环复位。 + +### 根因 + +MRS 编译 RAM **90.02%**(44248/48KB)逼近历史 .bss 挤栈红线。本次 OTA 实现 6 个命令分支各声明独立 `static char resp[256~400]` + `static char hexbuf[513]`(合计 ~2.5KB BSS)→ 栈余量被挤 → `iot_handle_publish` 深调用链(MQTT 解析 → simple_json → snprintf,report_config 分支本身还有局部 `data[512]`)**栈溢出 → HardFault**。复现稳定(每次收 report_config 必崩)。 + +### 修复 + +| 项 | 改动 | 省 | +|----|------|-----| +| iot_mqtt_srv.c | 6 个 resp + hexbuf 合并为函数级 `static union _ota_io { resp[512]; hexbuf[513]; }`(互斥复用) | **~2KB BSS** | +| ota_srv.c | `_chunk_buf[256]` + `_fs_frame[254]` 合并 union(下载/校验与刷写帧互斥) | ~260B | +| ota_srv.c | 删无用 `_fs_block_crc` | 4B | + +**教训**:RAM 90% 环境下"局部栈改 static"是**伪优化**——BSS 增加 = 栈余量减少,深调用链照样溢出。正确方向是**合并复用缓冲减少总占用**(union),不是搬家。 + +### 验证 + +- 语法 0 错误(基线 interrupt 假阳性除外);gcc 隔离单测 10/10 PASS +- **待板级确认**:拉最新代码重编,RAM 应回 ~88%,联网收 report_config 不再复位 + +--- + ## 2026-08-20 — Loop MCU 远程 OTA 固件实现(MQTT V1.08,ROADMAP P1.4 ①) ### 背景