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:
wangfq
2026-08-20 14:04:00 +08:00
parent 0b51d84a9b
commit 2484a03326
3 changed files with 57 additions and 32 deletions
@@ -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 */