From 1780e5320bf81c03ec974af7e55aa2ce6c15a5f4 Mon Sep 17 00:00:00 2001 From: Wanderingss <1624155937@qq.com> Date: Sat, 3 Oct 2026 09:05:57 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=9E=E6=96=BDA2/A3/A4=E5=B9=B6=E9=80=9A?= =?UTF-8?q?=E8=BF=87=E7=9C=9F=E6=9C=BA=E9=AA=8C=E8=AF=81=EF=BC=9Anet=5Ftas?= =?UTF-8?q?k=E5=A4=B1=E8=B4=A5=E9=87=8D=E8=AF=95=E8=87=AA=E6=A3=80?= =?UTF-8?q?=E3=80=81ch395f=E6=9C=89=E9=99=90=E8=B6=85=E6=97=B6=E6=95=85?= =?UTF-8?q?=E9=9A=9C=E9=94=81=E5=AD=98=E3=80=81remo=5Fsend=E7=9F=AD?= =?UTF-8?q?=E5=86=99=E9=98=BB=E6=96=AD=EF=BC=9B@test=207/7=EF=BC=8CA1?= =?UTF-8?q?=E5=90=AB=E9=85=8D=E7=BD=AE=E6=8C=81=E4=B9=85=E5=8C=96=E9=97=AD?= =?UTF-8?q?=E7=8E=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- App/task/net_task.c | 32 ++++++++++++-- Drivers/BSP/CH395F/ch395f.c | 58 ++++++++++++++++++++++---- Drivers/BSP/CH395F/ch395f.h | 9 ++++ Drivers/BSP/UI/ui_net_sink.c | 55 ++++++++++++++++++++---- docs/内存管理/RAM使用分析与优化方案.md | 4 +- docs/审查记录/下一步任务规划.md | 2 + docs/待处理内容.md | 1 + docs/测试说明/实机测试实施指南.md | 37 +++++++++++----- 8 files changed, 165 insertions(+), 33 deletions(-) diff --git a/App/task/net_task.c b/App/task/net_task.c index 0330ec0d..ba7564b0 100644 --- a/App/task/net_task.c +++ b/App/task/net_task.c @@ -16,6 +16,8 @@ * v1.6 2026-09-15 王建锋 P1-6:入口加 CH395F 硬件层测试模式自挂起守卫 * (基础线程无条件创建,该模式下不运行); * freertos.c USER CODE 区重复创建已删除 + * v1.7 2026-10-02 王建锋 P1-8:初始组网失败/重建失败 → 自检记录 + 30s 周期重试 + * (原实现:初始失败 = netTask 无限驻留;重建失败 = 静默丢配置) */ #include @@ -42,6 +44,9 @@ * 函数说明:只置事件位;真正的重建在主循环内完成(CH395F SPI 归 netTask 独占, * UI 不得直接操作) */ +/* P1-8(2026-10-02):重建失败延迟重试的截止 tick;0 = 无待重试 */ +static uint32_t s_reinit_retry_at; + static void net_reconfig_request_cb(void) { if (sys_evtHandle != NULL) { (void)osEventFlagsSet(sys_evtHandle, SYS_EVT_NET_RECONFIG); @@ -203,9 +208,18 @@ void net_task_func(void const *arg) { } } - if (net_stack_bring_up() != 0) { - for (;;) { - osDelay(1000); + /* P1-8(2026-10-02):初始组网失败不自驻留——自检台账留痕 + 30s 周期重试 + * (1s 分片、每片喂心跳):线缆/芯片恢复即可当场完成组网;原 for(;;)osDelay + * 会令看门狗 NET 位持续陈旧,并阻塞全部下游就绪屏障 */ + for (;;) { + if (net_stack_bring_up() == 0) { + break; + } + DBG_ERROR("netTask: init FAIL, retry in 30s"); + (void)syslog_post(SYSLOG_SELFTEST, "net init fail"); + for (uint32_t i = 0U; i < 30U; i++) { + osDelay(1000U); + task_wd_beat(TASK_WD_NET); } } @@ -233,9 +247,19 @@ void net_task_func(void const *arg) { DBG_INFO("netTask: apply new net config (re-init)"); if (net_stack_bring_up() == 0) { (void)osEventFlagsSet(sys_evtHandle, SYS_EVT_NET_READY); + s_reinit_retry_at = 0U; } else { - DBG_ERROR("netTask: re-init FAIL"); + /* P1-8(2026-10-02):重建失败 → 自检留痕 + 30s 延迟重试(重事件位), + * 新配置不再静默丢失(原实现仅一条 DBG_ERROR 后丢弃) */ + DBG_ERROR("netTask: re-init FAIL, retry in 30s"); + (void)syslog_post(SYSLOG_SELFTEST, "net reinit fail"); + s_reinit_retry_at = (uint32_t)HAL_GetTick() + 30000U; } + } else if ((sys_evtHandle != NULL) && (s_reinit_retry_at != 0U) + && ((int32_t)((uint32_t)HAL_GetTick() - s_reinit_retry_at) >= 0)) { + /* P1-8:30s 倒计时到 → 重新触发重建(符号差处理 tick 回绕) */ + s_reinit_retry_at = 0U; + (void)osEventFlagsSet(sys_evtHandle, SYS_EVT_NET_RECONFIG); } net_poll(); diff --git a/Drivers/BSP/CH395F/ch395f.c b/Drivers/BSP/CH395F/ch395f.c index f5304b46..7335b6b3 100644 --- a/Drivers/BSP/CH395F/ch395f.c +++ b/Drivers/BSP/CH395F/ch395f.c @@ -8,6 +8,8 @@ * 修改记录: * 2026-03-14 王建锋 创建初始版本,按代码规范重构 * 2026-07-18 王建锋 改进write_send_buf/read_recv_buf使用SPI2 DMA批量传输 + * 2026-10-02 王建锋 E-34:3 处字节级 SPI 调用由 HAL_MAX_DELAY 改有限超时(100ms) + * + 返回值检查 + 故障锁存(沿程只记一条日志);原 SPI 异常时调用方永久挂死 */ /* 头文件包含区 - 先包含系统头文件,再包含自定义头文件 */ @@ -30,6 +32,7 @@ #define CH395F_RESET_DELAY_MS 20U /* 复位后等待延时(ms) */ #define CH395F_INIT_TIMEOUT_MS 500U /* 初始化超时时间(ms) */ #define CH395F_SPI_BEGIN_DELAY_MS 1U /* SPI事务开始延时(ms) */ +#define CH395F_SPI_BYTE_TIMEOUT_MS 100U /* E-34:字节级SPI有限超时(原 HAL_MAX_DELAY) */ /* DMA缓冲大小使用ch395f.h中公开的CH395F_SPI_DMA_BUF_SIZE */ #define CH395F_DMA_BUF_SIZE CH395F_SPI_DMA_BUF_SIZE @@ -37,6 +40,7 @@ * 静态变量定义区 - DMA传输相关 */ static volatile uint8_t s_spi2_dma_done = 0U; /* DMA传输完成标志 */ +static uint8_t s_spi_byte_fault = 0U; /* E-34:字节级SPI故障锁存(超时/出错置位,MCU复位清除) */ __attribute__((aligned(4))) static uint8_t s_spi2_dma_tx_buf[CH395F_DMA_BUF_SIZE]; /* DMA发送缓冲区 */ @@ -49,6 +53,7 @@ static uint8_t s_spi2_dma_rx_buf[CH395F_DMA_BUF_SIZE]; /* DMA接收缓冲区 */ static void ch395f_spi_begin(void); static void ch395f_spi_end(void); static void ch395f_spi_dma_transfer(uint16_t len); +static void ch395f_spi_byte_fault_set(void); /* * 私有函数定义区 @@ -76,6 +81,21 @@ static void ch395f_spi_end(void) { HAL_GPIO_WritePin(CH395_SCS_GPIO_Port, CH395_SCS_Pin, GPIO_PIN_SET); } +/* + * 函数功能:锁存一次字节级SPI故障(E-34:仅首条故障记日志) + * 入口参数:无 + * 返回值:无 + * 限定条件:无 + * 函数说明:任一字节级SPI调用超时/失败时调用;置位 s_spi_byte_fault 并记一条日志, + * 后续故障静默(防串口刷屏);上层经 ch395f_spi_byte_fault() 查询 + */ +static void ch395f_spi_byte_fault_set(void) { + if (s_spi_byte_fault == 0U) { + s_spi_byte_fault = 1U; + DBG_ERROR("SPI byte op fail, channel latched"); + } +} + /* * 函数功能:DMA全双工传输并等待完成 * 入口参数:len - 传输长度(字节) uint16_t 1 - CH395F_DMA_BUF_SIZE @@ -175,10 +195,13 @@ void HAL_SPI_ErrorCallback(SPI_HandleTypeDef *hspi) { * 函数说明:SPI模式下命令和数据字节之间无需间隔 */ uint8_t ch395f_write_cmd(uint8_t cmd) { - uint8_t rx_data; + uint8_t rx_data = 0xFFU; - /* 通过SPI发送命令并接收回显 */ - HAL_SPI_TransmitReceive(&hspi2, &cmd, &rx_data, 1, HAL_MAX_DELAY); + /* 通过SPI发送命令并接收回显(E-34:有限超时+返回值检查;失败返回0xFF占位并锁存故障) */ + if (HAL_SPI_TransmitReceive(&hspi2, &cmd, &rx_data, 1, + CH395F_SPI_BYTE_TIMEOUT_MS) != HAL_OK) { + ch395f_spi_byte_fault_set(); + } return rx_data; } @@ -189,10 +212,13 @@ uint8_t ch395f_write_cmd(uint8_t cmd) { * 限定条件:必须在ch395f_spi_begin()和ch395f_spi_end()之间调用 */ uint8_t ch395f_write_data(uint8_t data) { - uint8_t rx_data; + uint8_t rx_data = 0xFFU; - /* 通过SPI发送数据并接收回显 */ - HAL_SPI_TransmitReceive(&hspi2, &data, &rx_data, 1, HAL_MAX_DELAY); + /* 通过SPI发送数据并接收回显(E-34:有限超时+返回值检查) */ + if (HAL_SPI_TransmitReceive(&hspi2, &data, &rx_data, 1, + CH395F_SPI_BYTE_TIMEOUT_MS) != HAL_OK) { + ch395f_spi_byte_fault_set(); + } return rx_data; } @@ -204,13 +230,27 @@ uint8_t ch395f_write_data(uint8_t data) { */ uint8_t ch395f_read_data(void) { uint8_t tx_dummy = 0xFFU; - uint8_t rx_data; + uint8_t rx_data = 0xFFU; - /* 发送0xFF作为时钟源,读取芯片返回数据 */ - HAL_SPI_TransmitReceive(&hspi2, &tx_dummy, &rx_data, 1, HAL_MAX_DELAY); + /* 发送0xFF作为时钟源,读取芯片返回数据(E-34:有限超时+返回值检查) */ + if (HAL_SPI_TransmitReceive(&hspi2, &tx_dummy, &rx_data, 1, + CH395F_SPI_BYTE_TIMEOUT_MS) != HAL_OK) { + ch395f_spi_byte_fault_set(); + } return rx_data; } +/* + * 函数功能:查询字节级SPI故障锁存(E-34) + * 返回值:0 - 正常 1 - 曾发生字节级SPI超时/错误 + * 限定条件:无 + * 函数说明:锁存式(不自动清除,MCU 复位前一直有效);上层据此判定 + * "SPI 通道异常"(区别于"无芯片"/"PHY 未链接");仅首条故障记日志 + */ +uint8_t ch395f_spi_byte_fault(void) { + return s_spi_byte_fault; +} + /* * 函数功能:检测CH395芯片是否存在且通信正常 * 返回值:CH395F_STATUS_OK - 芯片检测成功 ch395f_status_t diff --git a/Drivers/BSP/CH395F/ch395f.h b/Drivers/BSP/CH395F/ch395f.h index 25e25c50..909f3ed2 100644 --- a/Drivers/BSP/CH395F/ch395f.h +++ b/Drivers/BSP/CH395F/ch395f.h @@ -301,6 +301,15 @@ uint8_t ch395f_write_data(uint8_t data); */ uint8_t ch395f_read_data(void); +/* + * 函数功能:查询字节级SPI故障锁存(E-34) + * 返回值:0 - 正常 1 - 曾发生字节级SPI超时/错误 + * 限定条件:无 + * 函数说明:锁存式(MCU 复位前一直有效);上层据此判定"SPI 通道异常" + * (区别于"无芯片"/"PHY 未链接");仅首条故障记日志 + */ +uint8_t ch395f_spi_byte_fault(void); + /* * 函数声明区 - 芯片检测与版本 */ diff --git a/Drivers/BSP/UI/ui_net_sink.c b/Drivers/BSP/UI/ui_net_sink.c index e817cbe0..b9c56b5b 100644 --- a/Drivers/BSP/UI/ui_net_sink.c +++ b/Drivers/BSP/UI/ui_net_sink.c @@ -12,6 +12,9 @@ * 2026-09-10 王建锋 改为双端共用:底层改用 net_* API + 平台后端;接收改为逐字节 * 状态机(失步自动重同步);断开/网络重建后经 net_get_sock 判活 * 并重建监听(修复 MCU 单连接模型断线后无法重连的隐患) + * 2026-10-02 王建锋 E-04:remo_send 校验 net_send 返回值(原丢弃)——短写/失败 + * 告警,连续 3 次失败阻断推送,新连接/网络重建解除阻断 + * (MCU 单连接模型不得 net_close 数据 fd,会连监听一起毁掉) */ #include "ui_net_sink.h" @@ -23,6 +26,10 @@ #include "test_config.h" /* TEST_LATENCY_PROBE(链路时延探针编译开关) */ #include "net_latency_test.h" /* M3 探针:prod_cycle_mcu(LCDMEM 处理净成本) */ +/* E-04(2026-10-02):发送短写/失败告警(MCU=控制台日志,模拟器=printf 桩) */ +#define DBG_TAG "[UI_NET_SINK]" +#include "dbg_log.h" + /* RemoDispBus 标签与命令(与 ui_net_sink.h 的 UI_REMO_* 常量对齐) */ #define TAG_CLIENT UI_REMO_TAG_CLIENT #define TAG_DEVICE UI_REMO_TAG_DEVICE @@ -47,6 +54,8 @@ static int g_listen = -1; /* 监听 Socket fd(未启动或已停止时 static int g_client = -1; /* 当前客户端 Socket fd(无连接时为 -1) */ static uint16_t g_port; /* 监听端口(网络重建 / 断线后重监听用) */ static uint32_t g_gen; /* 建立监听时的网络代次号(变化 = 网络已重建) */ +static uint8_t s_send_fail_run; /* 连续发送失败计数(E-04:0 = 正常) */ +static uint8_t s_sink_blocked; /* 推送阻断标志(1 = 停发帧,等客户端重连解除) */ /* * 函数功能:计算 RemoDispBus 帧 payload 的 CRC 校验值 @@ -95,16 +104,25 @@ static void put_u32(uint8_t *p, uint32_t v) { /* * 函数功能:构造并发送一帧 RemoDispBus 响应(设备→客户端) * 入口参数:cmd - 命令码;data - payload 指针(len 非 0 时须非空);len - payload 长度 - * 返回值:无 + * 返回值:0 - 整帧发出;-1 - 发送失败/短写/推送已阻断 * 限定条件:g_tx 空间足够容纳 4+len+1 字节;g_client >= 0(已有连接) * 函数说明:帧格式 [TAG_DEVICE][CMD][LEN_H][LEN_LO][DATA][CRC];CRC 为 payload 异或; * 无连接时直接返回 + * E-04(2026-10-02):校验 net_send 返回值(原丢弃)——短写 = 客户端收到坏帧 + * (CRC 不符 → 画面卡死黑屏,而设备侧日志全正常);连续 3 次失败 → + * 停发:客户端察觉无帧自行重连,芯片侧 DISCONNECT/TIMEOUT 自动重开 + * 监听重建连接;MCU 单连接模型严禁 net_close 数据 fd(会连监听一起毁) */ -static void remo_send(uint8_t cmd, const uint8_t *data, uint16_t len) { +static int remo_send(uint8_t cmd, const uint8_t *data, uint16_t len) { uint8_t *p; + int total; + int ret; if (g_client < 0) { - return; + return -1; + } + if (s_sink_blocked) { + return -1; /* 推送已阻断:等客户端重连解除 */ } p = g_tx; @@ -116,7 +134,24 @@ static void remo_send(uint8_t cmd, const uint8_t *data, uint16_t len) { memcpy(p + 4, data, len); } p[4 + len] = remo_crc(data, len); - net_send(g_client, p, (int)(4 + len + 1), 0); + total = (int)(4 + len + 1); + ret = net_send(g_client, p, total, 0); + if (ret == total) { + s_send_fail_run = 0U; + return 0; + } + + /* 短写/发送失败:告警 + 累计连续失败数 */ + if (s_send_fail_run < 255U) { + s_send_fail_run++; + } + DBG_ERROR("remo send short: ret=%d/%d run=%u", ret, total, + (unsigned)s_send_fail_run); + if (s_send_fail_run >= 3U) { + s_sink_blocked = 1U; + DBG_ERROR("remo sink blocked, await client reconnect"); + } + return -1; } /* @@ -135,7 +170,7 @@ static void remo_send_init(void) { put_u16(init, UI_W); put_u16(init + 2, UI_H); put_u32(init + 4, UI_MEMSIZE); - remo_send(CMD_INIT, init, 8); + (void)remo_send(CMD_INIT, init, 8); } /* @@ -196,7 +231,7 @@ static void remo_handle(const uint8_t *f, int n) { switch (cmd) { case CMD_KEEPLIVE: /* 心跳保活:原样回显一个空 KEEPLIVE 帧,维持连接活性 */ - remo_send(CMD_KEEPLIVE, NULL, 0); + (void)remo_send(CMD_KEEPLIVE, NULL, 0); break; case CMD_INIT: /* 查询屏幕参数:回送 [宽u16][高u16][帧字节数u32](大端,共 6 字节) */ @@ -208,7 +243,7 @@ static void remo_handle(const uint8_t *f, int n) { ui_net_sink_inject_key(data[0]); } /* 回空帧作“已接收”确认(LEN=0,CRC=0) */ - remo_send(CMD_KEY, NULL, 0); + (void)remo_send(CMD_KEY, NULL, 0); break; case CMD_LCDMEM: { /* 读取显存:数据段为 4 字节大端起始偏移,不足 4 字节判非法丢弃 */ @@ -236,7 +271,7 @@ static void remo_handle(const uint8_t *f, int n) { if (ui_net_sink_get_lcdmem(g_lcdmem_data + 4, UI_MEMSIZE) == 0) { /* 应答数据段 = [偏移u32(回填请求原值)] + [1bpp 位图 3200B] */ memcpy(g_lcdmem_data, data, 4); - remo_send(CMD_LCDMEM, g_lcdmem_data, (uint16_t)(4 + UI_MEMSIZE)); + (void)remo_send(CMD_LCDMEM, g_lcdmem_data, (uint16_t)(4 + UI_MEMSIZE)); } #if defined(TEST_LATENCY_PROBE) lat_probe_add_seg("prod_cycle_mcu", lat_mark_us(&tm)); @@ -433,6 +468,8 @@ void ui_net_sink_poll(void) { } g_client = -1; rm_parser_reset(); + s_send_fail_run = 0U; /* 网络重建:失败累计清零、解除推送阻断(E-04) */ + s_sink_blocked = 0U; (void)ui_net_sink_start(g_port); return; } @@ -449,6 +486,8 @@ void ui_net_sink_poll(void) { if (c >= 0) { g_client = c; rm_parser_reset(); + s_send_fail_run = 0U; /* 新连接:失败累计清零、解除推送阻断(E-04) */ + s_sink_blocked = 0U; } return; } diff --git a/docs/内存管理/RAM使用分析与优化方案.md b/docs/内存管理/RAM使用分析与优化方案.md index 3a5b6b20..c162a4bb 100644 --- a/docs/内存管理/RAM使用分析与优化方案.md +++ b/docs/内存管理/RAM使用分析与优化方案.md @@ -36,10 +36,10 @@ | 区域 | 用量 | 容量 | 占比 | |---|---|---|---| -| ER_IROM1 | 264,284 B (258.09 KB) | 1,048,576 B (1024 KB) | 25.2% | +| ER_IROM1 | 264,936 B (258.73 KB) | 1,048,576 B (1024 KB) | 25.3% | | RW_IRAM1 | 84,952 B (82.96 KB) | 131,072 B (128 KB) | 64.8% | | RW_CCM | 49,184 B (48.03 KB) | 65,280 B (64 KB) | 75.3% | -| **Flash (ROM 合计)** | **265,012 B (258.8 KB)** | 1,048,576 B (1024 KB) | **25.3%** | +| **Flash (ROM 合计)** | **265,668 B (259.4 KB)** | 1,048,576 B (1024 KB) | **25.3%** | 各区的容量与常量来源: diff --git a/docs/审查记录/下一步任务规划.md b/docs/审查记录/下一步任务规划.md index d13af8e4..0a8edede 100644 --- a/docs/审查记录/下一步任务规划.md +++ b/docs/审查记录/下一步任务规划.md @@ -49,6 +49,8 @@ ### 阶段 A:立即执行(改动小、风险高、当周可完) +> **状态(2026-10-03)**:A1~A4 全部**已实施并通过真机验证**——详见 `测试说明/实机测试实施指南.md` §3.2(A1 判定 PASS 含配置持久化;A2/A3/A4 正常路径零回归;芯片级失败分支/短写/拔线负向场景记为待办,待专项台架窗口)。A5 待查手册。 + > 目标:清掉"会静默坏事"的产品级风险 + 还清验证债。全部是一档性质,无架构变更。 | 序 | 任务 | 为什么现在 | 入口 | 完成判据 | diff --git a/docs/待处理内容.md b/docs/待处理内容.md index 74be456b..52f42e52 100644 --- a/docs/待处理内容.md +++ b/docs/待处理内容.md @@ -29,6 +29,7 @@ | # | 事项 | 说明 | 出处 | |---|---|---|---| | N-4 | **pending 机制二期:ACCEPT + SEND 阻塞路径 pending 化**——E-06(accept 轮询计时失真)、E-20(lftpd 控制通道 errno 分类/501)、E-21(timeSync errno 分级)剩余分量共用该机制扩展;届时 L3/L5 基线再重锁、探针族扩充(close/reconfig/EOF/disconnect-during-pending)、看门狗接 selftest 留痕、模拟器差距清单 +1 | 一期(RECV)已落地并入主线:`a45c0cb`(+文档闭环 `7ff2958`);架构现状见 `Net层阻塞模型架构设计.md`,二期设计基线与门禁见 `下一步任务规划.md` §3.1 | 同左 + `错误码传播审计.md` 第二档 #2 | +| N-16 | **A2/A4 专项场景台架验证待办**(正常路径已实测零回归,2026-10-03):① PHY 拔线负向测试——断链需断 **DTU 侧**线缆或关**受管交换机端口**(拔交换机侧线缆链路仍 UP,CH395 对交换机拓扑),断链后复位验证 `PHY: DISCONNECT` + 自检台账 `comms fault` 降级放行;② A2 芯片级 `net_init` 失败分支(需 SPI 故障注入,验证 `net init fail` 自检 + 30s 重试恢复);③ A4 短写压测(需远显客户端 + UI 推流压力,验证 `remo send short` 告警/阻断/重连恢复) | 2026-10-03(台架;台架拓扑 = CH395↔交换机,拔线操作受限) | `实机测试实施指南.md` §3.2 真机判定表 | ## 可靠性与自恢复 diff --git a/docs/测试说明/实机测试实施指南.md b/docs/测试说明/实机测试实施指南.md index 184ed335..51bb3cd3 100644 --- a/docs/测试说明/实机测试实施指南.md +++ b/docs/测试说明/实机测试实施指南.md @@ -42,7 +42,7 @@ | A1 | 工作区干净 | `git status --short` | 无输出(或有明确待提交项) | | A2 | 产品构建 0/0 | `& "MDK-ARM\build.bat"` | 末行 `BUILD_OK: 0 errors, 0 warnings` | | A3 | 构建产物为**产品态** | 检查 `test/board/test_config.h` L30-33 | 四个 `TEST_SUITE_*` **全部注释** | -| A4 | `.map` 非测试态 | 看 `MDK-ARM/STM32F407-Demo/STM32F407-Demo.map` 的 `ER_IROM1` | 产品态基线随已接受的修复增长:262,100(初版)→ 262,836 B(10-02 round 1~6)→ **264,284 B(当前,含 round 7~9)**;明显阶跃 ≈3.7KB 及以上(10-02 实测产品 262,836 → 套件 266,540)= 停在套件态 → 重跑 A2 | +| A4 | `.map` 非测试态 | 看 `MDK-ARM/STM32F407-Demo/STM32F407-Demo.map` 的 `ER_IROM1` | 产品态基线随已接受的修复增长:262,100(初版)→ 262,836 B(10-02 round 1~6)→ 264,284 B(round 7~9)→ **264,936 B(当前,含 A2/A3/A4 补码)**;明显阶跃 ≈3.7KB 及以上(10-02 实测产品 262,836 → 套件 266,540)= 停在套件态 → 重跑 A2 | | A5 | 全量静态门禁 | `pre-commit run --all-files` | **16/16 Passed** | | A6 | ST-LINK 探针可见 | `& "MDK-ARM\reset.bat" list`(CLI 经 `DTU_STLINK_CLI` / PATH 解析) | 列出探针 SN `0B250D12A218303030303032` | | A7 | 串口端口正确 | 检查 `.opencode/serial_config.json` 的 `portName` | 端口单一配置源 = 此文件(当前 COM6,2026-09-21 台架重插后由 COM7 变更);变更后先杀旧 daemon 再 `open` | @@ -89,15 +89,24 @@ | 序 | 事项 | 代码状态(2026-10-02 核对) | 上板判据 | 命令/入口 | |---|---|---|---|---| -| **A1** | **P0-1 `cfg_file` 堆分配改造** | **已落地**(分配收进 `cfg_store_alloc/free` 平台后端;模拟器用宿主堆,**照不出** `Heap_Size=0x200`(512B)约束——真机是最后一道门) | 串口日志出现 `/config.ini` **`load: OK`**;Forbidden `FAIL`/`[ERR]` 零命中;**保存配置后复位,值持久** | `& '.venv\Scripts\python.exe' test/host/run_boot_test.py` | -| **A2** | **P1-8 网络重建失败恢复** | **部分落地**:首启链路超时已有 `comms fault` 自检(`net_task.c` L131);重建(reconfig)失败路径仅一条 `DBG_ERROR`,**无自检记录、无周期重试**;首启 `net_init` 失败 = netTask 无限驻留(L206-210) | 补码后:失败路径写 syslog 自检 + 30s 周期重试;**不自驻留死循环**;恢复后各 TCP 服务据代次号重建监听;`@test` PASS | 补码 → `@build` → `@test`;UI 网口设置页改 IP;`test/host/e02_probe.py` | -| **A3** | **E-34 CH395F 字节命令超时** | **未落地**(`ch395f.c` L181/195/210 仍 `HAL_MAX_DELAY` → SPI 异常时 netTask 永久挂死) | 补码后:有限超时(100~500ms)+ 返回值检查;构建 0/0;**正常通信零回归**(FTP 传输正常) | 补码 → `@build` + `@test` | -| **A4** | **E-04 `remo_send` 返回值** | **未落地**(`ui_net_sink.c` L119 仍丢弃 `net_send` 返回值;短写 → 客户端 CRC 失败 → 画面卡死黑屏而设备侧日志全正常) | 补码后:改返回 `int` 校验 `ret == len`;短写 `DBG_ERROR` + 连续失败强制重连;**UI 推流压力下无黑屏** | 补码 → `@build` + UI 远显推流压力回归 | +| **A1** | **P0-1 `cfg_file` 堆分配改造** | **已落地**(分配收进 `cfg_store_alloc/free` 平台后端;模拟器用宿主堆,**照不出** `Heap_Size=0x200`(512B)约束——真机是最后一道门) | 串口日志出现 `[CFG]` **`load: OK (…B, heap free …B)`**(实测行格式);Forbidden `FAIL`/`[ERR]` 零命中;**保存配置后复位,值持久** | `& '.venv\Scripts\python.exe' test/host/run_boot_test.py` | +| **A2** | **P1-8 网络重建失败恢复** | **已落地**(2026-10-02 补码):首启组网失败 = 自检 `net init fail` + 30s 周期重试(1s 分片喂心跳,**不自驻留**);重建(reconfig)失败 = 自检 `net reinit fail` + 30s 延迟重触发事件位(tick 回绕安全),成功即清零;PHY 链路超时仍走既有 `comms fault`(降级放行,语义不变) | 失败路径写 syslog 自检 + 30s 周期重试;**不自驻留死循环**;恢复后各 TCP 服务据代次号重建监听;`@test` PASS | `@build` → `@test`;UI 网口设置页改 IP(触发重建路径);`test/host/e02_probe.py` | +| **A3** | **E-34 CH395F 字节命令超时** | **已落地**(2026-10-02 补码):3 处字节级 SPI 调用 = 有限超时 100ms + 返回值检查;失败锁存 `ch395f_spi_byte_fault()`(仅首条故障记日志,防刷屏)——SPI 异常不再挂死 netTask | 构建 0/0;**正常通信零回归**(FTP 传输正常、启动无 `SPI byte op fail` 行) | `@build` + `@test` | +| **A4** | **E-04 `remo_send` 返回值** | **已落地**(2026-10-02 补码):`remo_send` 改返回 `int` 校验 `ret == total`;短写/失败 → `DBG_ERROR` + 连续失败计数;连续 3 次 → **阻断推送**(客户端察觉无帧自行重连,芯片侧 DISCONNECT/TIMEOUT 自动重开监听;MCU 单连接模型**不** `net_close` 数据 fd),新连接/网络重建时解除 | 短写可观测(串口 `remo send short` 告警)+ 连续失败阻断 → 重连恢复;**UI 推流压力下无黑屏** | `@build` + UI 远显推流压力回归 | > **A5(P1-7 MAC 写入顺序)**:**先查手册**(CH395F §9.2.1 INIT 是否锁存 MAC), > 再决定改或文档化"无需调整"——**属离线可做部分**,不在本文强绑。 -### 3.3 自检项清单(S7 判定对象,18 处) +**真机判定(2026-10-03,本机 MCU,新固件全闭环 `@test` PASS 7/7)**: + +| 项 | 判定 | 证据 | +|---|---|---| +| A1 | ✅ **PASS(含持久化)** | `[CFG] load: OK (523B, heap free 8840B)`(经 FreeRTOS 堆分配——512B C 堆约束的验收点);Forbidden 零命中;FTP 改配置 IP→.101 后复位即加载生效(`[NET_CFG] load: OK ip=192.168.1.101` + 芯片 `IP_INF` .101 + 21 端口探测可达),原档恢复后复测回 .100 | +| A2 | ⚠️ **正常路径零回归**;失败分支待专项窗口 | 开机 `net init: OK` / `PHY: 100M FULL` / `netTask: started`,**无 `init FAIL` / `retry in 30s` 误触发**;芯片级 `net_init` 失败分支需故障注入、UI 重建路径需 UI 操作,台架无自然触发(PHY 链路超时走既有 `comms fault` 降级路径,未改动);拔线负向测试因台架线缆拓扑记待办(N-16) | +| A3 | ⚠️ **正常路径零回归**;故障分支待专项窗口 | 开机全部芯片 SPI 命令正常(MAC/IP_INF 回读正确),**`SPI byte op fail` 零出现**;SPI 异常分支(故障锁存)只能经故障注入验证 | +| A4 | ⚠️ **无误报基线**;推流压测待专项窗口 | 开机 UI 远显服务正常启动(port 7003 监听),无客户端连接时**零 `remo send short` 误报**;短写判定/阻断重连路径需远显客户端 + 推流压力验证 | + +### 3.3 自检项清单(S7 判定对象,20 处) > 来源:全库 `syslog_post(SYSLOG_SELFTEST, ...)` 调用点。**这是 S7 的判定覆盖面基准**。 @@ -106,7 +115,7 @@ | `App/app/app_main.c` | `watchdog reset` / `clock chip fault` / `NAND fault` / `ADC fault` | | `App/cfg/cfg_file.c` | `cfg heap alloc fail`(×3) / `cfg template write fail` / `cfg load oversized` / `cfg load read fail` / `cfg save read fail` / `cfg save overflow` / `cfg save write fail` / `cfg poll oversized` | | `App/task/default_task.c` | `filesystem fault`(×2) / `storage fault`(×2) | -| `App/task/net_task.c` | `comms fault` | +| `App/task/net_task.c` | `comms fault` / `net init fail` / `net reinit fail` | **联动**:`syslog_post` 的两级自愈(L1 文件级重试 / L2 错误去重 `s_fault_reported[]`) 决定**持续故障只留第一条**——故 S7 判定"有记录即 FAIL"是**充分**的(不会被去重掩盖)。 @@ -167,12 +176,13 @@ $cli = if ($env:DTU_STLINK_CLI) { $env:DTU_STLINK_CLI } else { (Get-Command ST-L ## 5. 堆低水位告警实测(P1-B,S11) -**现状证据**:上一轮实测 `free=8,784 / min_ever=4,848 B`(初始 `configTOTAL_HEAP_SIZE` 48KB), -**min_ever 已低于 5KB**,余量不到 10%——新增任务/缓冲可能触发分配失败。 +**现状证据**:2026-10-03 上板复测 `free=8,688 / min_ever=3,712~4,728 B` +(两次开机,运行间波动 ≈1KB;初始 `configTOTAL_HEAP_SIZE` 48KB)—— +**min_ever 下限已跌破 4KB**,余量不到 8%,新增任务/缓冲可能触发分配失败。 现有代码仅有周期性 `[STACK]` 报告(每周期打印 `heap free/min_ever`),**无阈值告警、不写自检记录**。 **待补代码**(约 15 行 + 1 个编译期常量,在 `defaultTask` 低频循环(LED 闪烁节拍)内): -检查 `xPortGetMinimumEverFreeHeapSize()`,低于阈值(建议 **4096 B**)时 +检查 `xPortGetMinimumEverFreeHeapSize()`,低于阈值(建议 **3072 B**,低于 10-03 实测下限 3,712 B,防运行间波动误报)时 `DBG_ERROR("[HEAP] low water: %u B")` + 写 syslog selftest。 **判据**:台架长跑(复用 §4.2 V5 窗口)下,若发生低水位 → 串口出现告警行 + `selftest.log` 有记录; @@ -375,6 +385,12 @@ cppcheck --enable=warning --error-exitcode=1 test/ 2>&1 | tee test_cppcheck_base - 现场改寄存器(如 IWDG PR/RLR 验证手段)须在**一次调用**内完成整段键序列 (解锁→参数→键锁)——分多次调用会超过看门狗超时、中途被复位;寄存器随复位丢失, 只能用于验证,不是修复。 +8. **开机良性 `MISS start` 行**(2026-10-03 上板):看门狗抽样在**开机屏障等待期**偶见 + UI 位单抽样缺席,每 episode 记一条裸 UART `MISS start snap=0x… req=0x… t=…` + (`task_watchdog.c` 的停摆起点标记,round-7 取证件)。实测每次开机 2 条 + (t≈0.6s / t≈2.8s,snap 仅缺 UI 位),NET_READY 解除后立即恢复——**未达 800ms 停喂 + 阈值、不触发复位**,属分片心跳与 100ms 抽样边界的固有交错,非回归; + 处置:见到该行不必恐慌,**连续**出现 `task stall` 行/复位才是真停摆。 --- @@ -402,6 +418,7 @@ cppcheck --enable=warning --error-exitcode=1 test/ 2>&1 | tee test_cppcheck_base | 日期 | 变更 | |---|---| | 2026-10-02 | **更名**:`实机测试规划.md` → `实机测试实施指南.md`(与文档性质对齐——常设执行判据,非规划;引用面改造后仅剩 `docs/README.md` 索引 1 处,同步更新) | +| 2026-10-03 | **A2/A3/A4 实施 + 真机验证**:A2 = `net_task.c` 初始组网失败/重建失败 → 自检留痕 + 30s 周期重试(分片喂心跳,不自驻留);A3 = `ch395f.c` 3 处字节级 SPI 调用 `HAL_MAX_DELAY` → 100ms 有限超时 + 返回值检查 + 故障锁存 `ch395f_spi_byte_fault()`(首条记日志);A4 = `ui_net_sink.c` `remo_send` 校验 `net_send` 返回值,短写告警、连续 3 次失败阻断推送、新连接/网络重建解除(单连接模型不 `net_close` 数据 fd)。`@build` 0/0 + L0/L0.5 全绿 + 模拟器构建通过;上板 `@test` PASS 7/7:A1 判定 PASS(含 IP 改→复位→持久→可达闭环,原档已恢复)、A2/A3/A4 正常路径零回归;自检清单 18→20 处(`net init fail` / `net reinit fail`);上板新数据:min_ever 下限 3,712 B(S11 阈值建议改 3,072 B)、开机良性 `MISS start` 行新增陷阱 §9.8;拔线负向/短写压测记 `待处理内容.md` N-16 | | 2026-10-02 | 初版:无 MCU 窗口建立。汇总 P0-A 环境预检 / P0-B `@test`+S7 / P1-A IWDG V2/V4/V5 / P1-B S11 / P2-A S9 / P2-B P1-8+N-2+N-3 / P3 S5+S4+待核实项,共 **7 批**。每项含现状证据 / 目标 / 判据(量化)/ 执行步骤(精确命令)/ 风险回滚。18 处自检调用点清单 + 模拟器差距清单 §3 决策规则交叉引用 | | 2026-10-02 | **台架窗口执行交接**:新增 **§0.5**(P0-A 通过;S7 已落地并提交 `4a4cd29`;P0-B 唯一阻断 = 0 字节 STOR → IWDG 误复位(round 3~6 后仍 5/5 复现);round 1~6 修复集摘要;**round 7 纯取证埋点设计 + 判据表**;P0-B 出口闭环(含 FTL flush 诱导式清台账);设备/台账/本地证据快照;**7 条实测陷阱**);§2.1 标注 S7 已落地、命令形态已确认 | | 2026-10-02 | **文档改造**(同 `本地工具路径环境变量方案.md` 思路):删除一次性台架窗口状态(原 §0.5——round 1~9 取证全过程、快照、未提交警告:结论已沉淀 `看门狗设计说明.md`/`STM32F4-Base存储架构说明.md` §5.4.1/`控制台单写者日志设计说明.md` §12,修复随 `930215f` 提交)与规划框架(预估耗时/阶段措辞),重组为执行判据说明——§1 事项总览 / §2 环境预检 / §3 `@test` 闭环+自检判定+待上板项 / §4 IWDG 注入长跑 / §5 堆告警 / §6 GD5F 错误通道 / §7 网络基线 / §8 工程卫生 / §9 实测陷阱 / §10 引用闭环;A1~A4 代码状态按当日代码核对改写(A1 已落地 / A2 部分 / A3、A4 未补码);A4 基线更新 264,284 B、A7 端口更正 COM6;原 §0.5.x 的 8 处代码注释、`待处理内容.md` 3 行、`看门狗设计说明.md` 变更记录、`docs/README.md` 同步重指向模块文档 |