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% 不再复位
This commit is contained in:
@@ -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) {
|
||||
|
||||
@@ -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 */
|
||||
|
||||
|
||||
@@ -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 ①)
|
||||
|
||||
### 背景
|
||||
|
||||
Reference in New Issue
Block a user