From 506eccc68f5f69f401e9f916f0a809821d8cb941 Mon Sep 17 00:00:00 2001 From: Wanderingss <1624155937@qq.com> Date: Thu, 15 Oct 2026 07:39:38 +0800 Subject: [PATCH] =?UTF-8?q?docs:=20=E9=94=99=E8=AF=AF=E7=A0=81=E4=BC=A0?= =?UTF-8?q?=E6=92=AD=E5=AE=A1=E8=AE=A1=E6=94=B6=E5=8F=A3=E5=B9=B6=E5=88=A0?= =?UTF-8?q?=E9=99=A4=E8=AF=A5=E6=96=87=E6=A1=A3=EF=BC=88E-xx=20=E8=BD=AC?= =?UTF-8?q?=E5=8E=86=E5=8F=B2=E7=BC=96=E5=8F=B7=E6=97=8F=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 本次会话先后销项与关闭: - E-61 SD2506 清 INTAF:原实现只读取 CTR1,而按手册 ARST=0 时读操作 不清标志,属空操作;改为读-改-写 CTR1,并按手册订正 CTR1 位宏 (INTAF/INTDF/BLF 位置,ARST 归 CTR3) - E-16 cfg_file:新增 CFG_FILE_MAX_NAME_LEN(段名/键名 ≤15),注册时 拒绝超长键名;s_gen_document 两处 snprintf 改检查式;补 TC-CFG-024 用例(主机单测 149→151 PASSED) - E-23 TC704:PHASE7_MULTI_COUNT 8→5(并发上限 = 8 − 3 个常驻监听), accept 失败分支加网络代次检查(网络重建即重建监听) - E-26(UDP)按“保留 API + 文档定案”关闭;E-30 评估为不必要后关闭; N-4(ACCEPT 决定不实施)关闭 审计文档剩余 5 项 P2 + 2 项监督,经用户裁定“都不重要”后整体删除: - 删除 docs/问题记录/错误码传播审计.md(634 行) - 九类缺陷模式表迁入 docs/编号规范.md §4.2.1(PAT-01~09) - E-xx 转为纯历史编号族:编号规范 §4.1 属主改“无(原属主已删除)”, §4.3 保持冻结;全仓 26 处引用按“保留编号、去悬空文档名”处理 - 顺带修正 N-4 收口遗留:排期详述 C1 才是 E-25/E-27/E-28 的承载处 (审计 §4 曾误写为登记簿);README 两处 C1 旧名改回“net 层跨上下文收口” 一并提交此前遗留的体系层评审报告 2026-10-14 复核 WIP(六维评分复核、 未闭环 Top 5→3、三区水位实测刷新)。 校验:Keil 构建 0 错误 0 警告(Code=212344);check_doc_links --sections-strict 与 --all(142 文档)通过;内存双台账刷新后均 PASS。 --- App/cfg/cfg_file.c | 24 +- App/cfg/cfg_file.h | 12 +- App/sync/time_sync.c | 2 +- App/util/README.md | 2 +- Drivers/BSP/NET/README.md | 2 +- Drivers/BSP/SD2506/sd2506.c | 24 +- Drivers/BSP/SD2506/sd2506.h | 18 +- Drivers/BSP/TPAFE5160/tpafe5160.h | 2 +- docs/README.md | 5 +- docs/UI设计/RemoDispBus协议与接收设计说明.md | 2 +- docs/产品需求/产品需求设计文档.md | 1 - docs/代码规范/pre-commit使用说明.md | 2 +- docs/任务规划/README.md | 36 +- docs/任务规划/基线与待确认.md | 47 +- docs/任务规划/排期详述.md | 7 +- docs/任务规划/测试与架构专项.md | 4 +- docs/任务规划/登记簿.md | 13 +- docs/内存管理/RAM使用分析与优化方案.md | 4 +- docs/内存管理/RAM预算台账.md | 8 +- docs/内存管理/内存内容分配策略.md | 8 +- docs/内存管理/存储栈RAM优化调研.md | 2 +- docs/子系统设计/看门狗设计说明.md | 15 +- docs/测试说明/ADC输入层单元测试说明.md | 2 +- docs/测试说明/CH395F_Test_Guide.md | 18 +- docs/测试说明/实机测试实施指南.md | 14 +- docs/测试说明/时间同步单元测试说明.md | 2 +- docs/测试说明/模拟器差距清单.md | 6 +- docs/测试说明/链路延迟测试说明.md | 3 +- docs/编号规范.md | 23 +- docs/网络/BSD_Socket_API_使用指南.md | 3 + docs/网络/BSD_Socket_API_函数调用关系.md | 1 - docs/网络/Net层阻塞模型架构设计.md | 10 +- docs/网络/网络自愈架构设计.md | 9 +- docs/问题记录/CH395F_Trap_Records.md | 2 +- docs/问题记录/错误码传播审计.md | 634 ------------------- docs/问题记录/项目审查与优化建议报告.md | 627 ++++++++---------- test/board/net_test_task.c | 32 +- test/host/README.md | 2 +- test/host/cfg_file/cfg_file_test.c | 26 + test/host/e02_probe.py | 2 +- 40 files changed, 514 insertions(+), 1142 deletions(-) delete mode 100644 docs/问题记录/错误码传播审计.md diff --git a/App/cfg/cfg_file.c b/App/cfg/cfg_file.c index 2b88c51a..91de18e6 100644 --- a/App/cfg/cfg_file.c +++ b/App/cfg/cfg_file.c @@ -12,7 +12,7 @@ * 移除对 C 库 malloc 的全部依赖(MicroLIB C 堆仅 512B, * 原 4 处 KB 级 malloc 真机必然失败);增编译期堆峰值预算 * 校验 + 失败上报自检记录 - * v1.2 2026-09-21 王建锋 E-01(错误码传播审计 P0):超长文件守卫根治——poll 判定 + * v1.2 2026-09-21 王建锋 E-01(历史审计 P0,属主文档已删除):超长文件守卫根治——poll 判定 * 前置到 cfg_store_stat 真实 st.size(旧检查位于截断后的 * len 上恒假=死码,超长文件被截断当有效解析 → 界外静默 * 回工厂默认 → 基线粘住 → 用户配置销毁链);save 输出上限 @@ -21,6 +21,10 @@ * v1.3 2026-10-04 王建锋 P2-1:受管键行超 CFG_LINE_TEST_MAX 时拒绝保存—— * 原实现 Pass1 静默跳过超长行,Pass2 遂在段头补写新 KV 且 * 原文超长行原样保留 → 同一键出现两行(旧值残留、静默截断) + * v1.4 2026-10-15 王建锋 E-16(历史审计 P2,属主文档已删除):生成文档边界余量收口—— + * s_gen_document 两处 `off += snprintf(...)` 改检查式(截断即 + * 返回 -1,不再跳过缓冲末尾);注册拒超长键名(≤ 15 字符), + * 使每行 112 字节余量成为可编译期验证的前提 * 函数说明:引擎状态非重入——调用方须收敛同一任务(defaultTask:poll/save,见 P1-3; * 启动期 load 先于任务竞争);FS 卷并发由 FatFS REENTRANT 卷锁保证 * (见《配置文件设计说明》D15 A+ 方案) @@ -63,7 +67,7 @@ static const char s_doc_header[] = #define CFG_OUT_MAX 5120u /* 保存输出缓冲上限(经 cfg_store_alloc 取,见下方堆峰值预算) */ #define CFG_LINE_TEST_MAX 127u /* 受管键行匹配的行长上限(超长必非受管键行) */ #define CFG_SNAP_MAX 64u /* 变更检测段快照缓冲(挂回调段 data_size 须 ≤ 此值) */ -#define CFG_NAME_MAX 16u /* 段名缓冲(15 字符 + NUL) */ +#define CFG_NAME_MAX (CFG_FILE_MAX_NAME_LEN + 1u) /* 段名/键名缓冲(15 字符 + NUL) */ #define CFG_HEAP_BUDGET 10240u /* 暂态堆峰值预算:读缓冲(1B NUL) + 输出缓冲之和上限 */ /* 编译期钉死暂态堆峰值:放大 CFG_FILE_MAX_LEN / CFG_OUT_MAX 会先在此编译失败,强制 @@ -457,6 +461,7 @@ static int s_decode(const cfg_key_def_t *p_key, uint8_t *p_dst, const char *p_va */ static int s_gen_document(char *p_buf, size_t cap, size_t *p_len) { size_t off; + int n; int i; int k; size_t hlen; @@ -480,7 +485,11 @@ static int s_gen_document(char *p_buf, size_t cap, size_t *p_len) { if (cap < off + 112u) { return -1; } - off += (size_t)snprintf(p_buf + off, cap - off, "\r\n[%s]\r\n", p_sec->name); + n = snprintf(p_buf + off, cap - off, "\r\n[%s]\r\n", p_sec->name); + if (n < 0 || (size_t)n >= cap - off) { + return -1; + } + off += (size_t)n; for (k = 0; k < (int)p_sec->key_count; k++) { const cfg_key_def_t *p_key = &p_sec->p_keys[k]; @@ -492,7 +501,11 @@ static int s_gen_document(char *p_buf, size_t cap, size_t *p_len) { if (cap < off + 112u) { return -1; } - off += (size_t)snprintf(p_buf + off, cap - off, "%s=%s\r\n", p_key->name, v); + n = snprintf(p_buf + off, cap - off, "%s=%s\r\n", p_key->name, v); + if (n < 0 || (size_t)n >= cap - off) { + return -1; + } + off += (size_t)n; } } *p_len = off; @@ -1004,7 +1017,8 @@ int cfg_file_register(const cfg_section_reg_t *p_reg) { for (k = 0; k < (int)p_reg->key_count; k++) { const cfg_key_def_t *p_key = &p_reg->p_keys[k]; - if (p_key->name == NULL || p_key->len == 0u) { + if (p_key->name == NULL || p_key->len == 0u || + strlen(p_key->name) >= CFG_NAME_MAX) { return -1; } if (p_key->type == CFG_VAL_UINT && diff --git a/App/cfg/cfg_file.h b/App/cfg/cfg_file.h index 26480f51..ee2013a2 100644 --- a/App/cfg/cfg_file.h +++ b/App/cfg/cfg_file.h @@ -9,10 +9,12 @@ * 修改记录: * v1.0 2026-09-14 王建锋 创建初始版本(P3:段键表引擎 + net_cfg 集成) * v1.1 2026-09-15 王建锋 属主任务由 netTask 改为 defaultTask(配置引擎单一属主,P1-3) - * v1.2 2026-09-21 王建锋 E-01(错误码传播审计 P0):超长守卫契约同步——poll 沉降 + * v1.2 2026-09-21 王建锋 E-01(历史审计 P0,属主文档已删除):超长守卫契约同步——poll 沉降 * 后先按真实 st.size 判超长(旧实现查截断后读长恒假)、 * save 输出上限收紧至 CFG_FILE_MAX_LEN、写盘唯一闸口拒写 * 超长;超长处置补自检留痕 + * v1.3 2026-10-15 王建锋 E-16(历史审计 P2,属主文档已删除):新增公开常量 + * CFG_FILE_MAX_NAME_LEN(段名/键名上限 15 字符) */ #ifndef CFG_FILE_H @@ -33,6 +35,10 @@ extern "C" { /* 引擎可注册段数上限 */ #define CFG_FILE_MAX_SECTIONS 8u +/* 段名 / 键名最大字符数(不含结尾 NUL);超长的注册描述符被 cfg_file_register 拒绝。 + * 该上限是生成文档每行余量(112 字节)可编译期验证的前提,勿放宽 */ +#define CFG_FILE_MAX_NAME_LEN 15u + /* 单段键数上限 */ #define CFG_FILE_MAX_KEYS 16u @@ -55,7 +61,7 @@ typedef enum { /* 键定义(offset/len 均相对段 RAM 副本起始) */ typedef struct { - const char *name; /* 键名(小写) */ + const char *name; /* 键名(小写,≤ CFG_FILE_MAX_NAME_LEN 字符) */ cfg_val_type_t type; /* 值类型 */ size_t offset; /* 字段偏移 */ size_t len; /* 字段长度 */ @@ -63,7 +69,7 @@ typedef struct { /* 段注册描述符 */ typedef struct { - const char *name; /* 段名(≤ 15 字符) */ + const char *name; /* 段名(≤ CFG_FILE_MAX_NAME_LEN 字符) */ void *p_data; /* 段 RAM 副本(模块自有静态区,引擎原地更新) */ const void *p_defaults; /* 工厂默认值(与 p_data 同布局;load 先复位再解析) */ size_t data_size; /* RAM 副本字节数 */ diff --git a/App/sync/time_sync.c b/App/sync/time_sync.c index 36c41f5f..d9f9b57b 100644 --- a/App/sync/time_sync.c +++ b/App/sync/time_sync.c @@ -36,7 +36,7 @@ * E-02 头阻塞修复 + L3 pending 化(timeSync 侧;lftpd 侧见 lftpd_inet.c): * 旧实现用单次 30s 阻塞 net_recv(默认 NET_RECV_TIMEOUT_MS)等时间包——等待期间 * netTask(全部 socket 操作的唯一执行者)被独占,其余任务的网络操作(远端显示 / - * FTP)整体停摆(docs/问题记录/错误码传播审计.md E-02)。 + * FTP)整体停摆(历史审计 E-02,属主文档已删除)。 * 2026-09-22 先落地分片有界读(5ms 片),同日 L3 pending 化后改为"挂起请求": * 每次等待 = 一次挂起登记(netTask 零占用,完成按主循环节拍检查),调用方 * 单次超时 = 剩余总预算(总等待 ≤ TIME_SYNC_TOTAL_BUDGET_MS,语义与旧 30s diff --git a/App/util/README.md b/App/util/README.md index 69a164b8..39661683 100644 --- a/App/util/README.md +++ b/App/util/README.md @@ -13,7 +13,7 @@ > 两模块**零调用者**且未编入任何构建——CRC 从未被链接使用:名义上的关联点 `net_cfg_legacy.c` > (旧档 `/cfg/netcfg.bin` 迁移,**该文件亦已于 2026-10-10 删除**)自带静态 `s_crc16_modbus()`, > 从不调用本模块;环形缓冲由 `App/log/console_log.c` 内联实现(`s_con_ring`)。 -> 保留只会造成"有统一实现"的误导,故整体移除(详见 `docs/任务规划/排期详述.md` E-15(C5))。 +> 保留只会造成"有统一实现"的误导,故整体移除。 ## 设计要点 diff --git a/Drivers/BSP/NET/README.md b/Drivers/BSP/NET/README.md index 35c87e94..7a4fe5ef 100644 --- a/Drivers/BSP/NET/README.md +++ b/Drivers/BSP/NET/README.md @@ -21,7 +21,7 @@ - **recv 挂起(pending)已落地**:`net_recv` 阻塞不占 netTask——登记挂起请求 + 每轮 tick 唤醒 - **send 挂起已落地**:`net_send` 快路径有界自旋(`NET_SEND_SPIN_MS` = 2ms),超预算登记 挂起 send,由 `net_send_pending_tick` 按主循环节拍分块推进;netTask 最坏停滞由 1s 压到 2ms -- **ACCEPT 尚未挂起化**(调用方轮询问题,不是队头阻塞)→ `docs/任务规划/登记簿.md` N-4 +- **ACCEPT 决定不挂起化(2026-10-15)**(调用方轮询问题,不是队头阻塞;pending 化收益仅 RPC 开销与抖动) - 架构细节:`docs/网络/Net层阻塞模型架构设计.md` ### 周期对账自愈(TRAP-NET-17 修复;对账与监听重建合一) diff --git a/Drivers/BSP/SD2506/sd2506.c b/Drivers/BSP/SD2506/sd2506.c index deeece95..122778ac 100644 --- a/Drivers/BSP/SD2506/sd2506.c +++ b/Drivers/BSP/SD2506/sd2506.c @@ -496,22 +496,32 @@ int sd2506_set_alarm(const sd2506_time_t *p_time, uint8_t mask) { * 函数功能:清除闹钟中断标志 * 入口参数:无 * 返回值:0-成功,-2=I2C 错误 int - * 限定条件:sd2506_init() 已调用成功;ARST=1 时自动清除 INTAF - * 函数说明:读取 CTR1 即可清除 INTAF 标志 + * 限定条件:sd2506_init() 已调用成功 + * 函数说明:ARST 位于 CTR3(11H) bit7,本驱动从未写 CTR3 → ARST=0, + * 故读 CTR1 不会自动清 INTAF(手册 L519/L523),须手动写 CTR1 将 INTAF 置 0; + * 写 0FH 须在写允许窗口内且保持 WRTC2/WRTC3=1(手册 L521), + * 故写入值取写允许态 0xFF 清 INTAF 位后的 0xDF。 * return: 0=OK, -2=I2C error - * note: reading CTR1 clears INTAF */ int sd2506_clear_alarm(void) { int ret = 0; - uint8_t val = 0; + int ret_off = 0; - /* 读取 CTR1, ARST=1 时自动清除 INTAF */ - ret = sd2506_read_reg(SD2506_REG_CTR1, &val); + /* 写 0FH 须在写允许窗口内(手册 L519),且 WRTC2/WRTC3 必须保持 1(L521) */ + ret = sd2506_write_enable(); if (ret != SD2506_OK) { return ret; } - return SD2506_OK; + ret = sd2506_write_reg(SD2506_REG_CTR1, + (uint8_t)(SD2506_CTR1_WRITE_ON & (uint8_t)~SD2506_CTR1_INTAF)); + + ret_off = sd2506_write_disable(); + if (ret == SD2506_OK) { + ret = ret_off; + } + + return ret; } /* diff --git a/Drivers/BSP/SD2506/sd2506.h b/Drivers/BSP/SD2506/sd2506.h index 3807fa36..a64e8f58 100644 --- a/Drivers/BSP/SD2506/sd2506.h +++ b/Drivers/BSP/SD2506/sd2506.h @@ -85,11 +85,14 @@ extern "C" { /* ======================== 控制位定义 ======================== */ -/* 控制寄存器 1 (0FH) */ -#define SD2506_CTR1_ARST (1U << 5) /* 自动复位使能 */ -#define SD2506_CTR1_INTAF (1U << 3) /* 报警中断标志 */ -#define SD2506_CTR1_INTDF (1U << 2) /* 倒计时中断标志 */ -#define SD2506_CTR1_BLF (1U << 0) /* 电池欠压标志 */ +/* 控制寄存器 1 (0FH):bit7=WRTC3 bit6=OSF bit5=INTAF bit4=INTDF bit3=BLF + * bit2=WRTC2 bit1=PMF bit0=RTCF(手册寄存器总表) */ +#define SD2506_CTR1_INTAF (1U << 5) /* 报警中断标志 */ +#define SD2506_CTR1_INTDF (1U << 4) /* 倒计时中断标志 */ +#define SD2506_CTR1_BLF (1U << 3) /* 电池欠压标志 */ + +/* 控制寄存器 3 (11H) */ +#define SD2506_CTR3_ARST (1U << 7) /* 自动复位使能(ARST=1 时读 CTR1 自动清 INTAF/INTDF/BLF) */ /* 控制寄存器 2 (10H) */ #define SD2506_CTR2_INTAE (1U << 5) /* 报警中断允许 */ @@ -267,11 +270,12 @@ int sd2506_write_sram(uint8_t addr, const uint8_t *p_buf, uint8_t len); int sd2506_set_alarm(const sd2506_time_t *p_time, uint8_t mask); /* - * 函数功能:清除报警中断标志 + * 函数功能:清除报警中断标志 INTAF * 入口参数:无 * 返回值:0 - 成功,-2 - I2C 通信错误 * 限定条件:sd2506_init() 已调用 - * 函数说明:读取 CTR1 自动清除 INTAF (ARST=1 时) + * 函数说明:ARST(CTR3 bit7) 未启用,读 CTR1 不清 INTAF;本函数经写允许窗口 + * 手动写 CTR1 将 INTAF 置 0 */ int sd2506_clear_alarm(void); diff --git a/Drivers/BSP/TPAFE5160/tpafe5160.h b/Drivers/BSP/TPAFE5160/tpafe5160.h index 9210c5fa..3cb54a11 100644 --- a/Drivers/BSP/TPAFE5160/tpafe5160.h +++ b/Drivers/BSP/TPAFE5160/tpafe5160.h @@ -11,7 +11,7 @@ * 2026-07-17 王建锋 创建初始版本,参考 AD7606 并行驱动及 TPAFE5160 手册 * 2026-07-17 王建锋 增加 EXTI 中断读取模式 * 2026-09-20 王建锋 V0.1 引脚适配(引脚值随 main.h 宏,驱动逻辑不变) - * 2026-10-10 王建锋 消费者接入前置修复(错误码传播审计 E-44 / E-45 / E-58): + * 2026-10-10 王建锋 消费者接入前置修复(历史审计 E-44 / E-45 / E-58,属主文档已删除): * FRSTDATA 数据有效性校验 + 帧序号取代就绪电平标志 + * 原子快照 API;移除会撕裂的 get_buf/data_ready/clear_ready */ diff --git a/docs/README.md b/docs/README.md index b7a0f937..58411c5c 100644 --- a/docs/README.md +++ b/docs/README.md @@ -189,7 +189,6 @@ docs/ | [`CH395F_Trap_Records.md`](问题记录/CH395F_Trap_Records.md) | CH395F 以太网控制器陷阱台账 | | [`GD5F2GQ5UE_Trap_Records.md`](问题记录/GD5F2GQ5UE_Trap_Records.md) | GD5F2GQ5UE SPI NAND 陷阱台账 | | [`SD2506_Trap_Records.md`](问题记录/SD2506_Trap_Records.md) | SD2506 RTC 陷阱台账 | -| [`错误码传播审计.md`](问题记录/错误码传播审计.md) | 错误码传播缺陷审计(9 类模式 × 子系统) | --- @@ -258,13 +257,13 @@ docs/ | 问题 | 位置 | 说明 | |---|---|---| -| 指向未实现工具 | `UI_界面制作流程.md`、`UI_自动化测试流程.md` → `tools/ui_mock/` | 该目录**始终不存在**;golden 回归实际落在 `simulator/tests/`(四脚本)。引用待改指,见 `docs/问题记录/项目审查与优化建议报告.md` §6.1 | +| ~~指向未实现工具~~ | ~~`UI_界面制作流程.md`、`UI_自动化测试流程.md` → `tools/ui_mock/`~~ | ✅ 已改指(2026-10-14 核实):两份 UI 文档正文已无该路径(仅存变更记录里的历史提及);golden 回归落在 `simulator/tests/`(四脚本) | | ~~产物缺失~~ | ~~`内存管理/RAM使用分析与优化方案.md` → `scripts/ram_baseline.tsv`~~ | ✅ 已建(2026-10-12,N-27 前半):`ram_baseline.py` + `ram_baseline.tsv` + [`RAM预算台账.md`](内存管理/RAM预算台账.md) + `mem-budget` 钩子 | | ~~目录名含空格~~ | ~~`UI 设计/`~~ | ✅ 已修复(2026-09-25,D-6):改名为 `UI设计/`,全仓库 67 处引用同步;命名约定见 [`文档规范.md`](文档规范.md) §1 | | ~~缺一致性门禁~~ | — | ✅ 已落地(2026-09-25/26,D-7 三档齐):`scripts/check_doc_links.py`(存在性)+ `scripts/gen_task_table.py`(数值)+ `scripts/check_mem_numbers.py`(体积),接入 pre-commit `doc-links` / `task-table` / `mem-numbers` 钩子 | | ~~`docs/文档规范.md` 未创建~~ | — | ✅ 已创建(2026-09-25):[`文档规范.md`](文档规范.md) | -> 未闭环的文档类问题见 [`问题记录/项目审查与优化建议报告.md`](问题记录/项目审查与优化建议报告.md) §3.1(体量与 D-1)与 §3.4(复核后仅剩三条:D-1、语义断链、元信息漂移)。 +> 未闭环的文档类问题见 [`问题记录/项目审查与优化建议报告.md`](问题记录/项目审查与优化建议报告.md) §3.1(体量)与 §3.3/§3.4(**仅剩 D-1 仓库膨胀**,登记簿 N-29 已决策暂不处理)。 --- diff --git a/docs/UI设计/RemoDispBus协议与接收设计说明.md b/docs/UI设计/RemoDispBus协议与接收设计说明.md index ce0f01fe..2108f282 100644 --- a/docs/UI设计/RemoDispBus协议与接收设计说明.md +++ b/docs/UI设计/RemoDispBus协议与接收设计说明.md @@ -252,7 +252,7 @@ if (n > 0) { > "把推流搬出 uiTask(独立任务 + 帧 seqlock)"已完整评估并**决定不做**:实测刷新最坏周期 > (`uip` ≈61 ms)是 **LVGL 帧周期地板**、与网络无关,搬任务不会让刷新变快;成本却含 > **必改 `.ioc`** 的重生成与全套回归,收益只剩极端并发下的观感隔离。若日后它成为实际问题, -> 正确方向是先治 netTask 排队公平性(登记簿 N-4 二期),不是把 UI 搬走。 +> 正确方向是先治 netTask 排队公平性(见 `../网络/Net层阻塞模型架构设计.md`),不是把 UI 搬走。 > 观测手段:`[PROF]` 的 `uis`(往返最坏)与 `uisn`(此刻仍在推流段内多久)—— > **`uisn` 持续非 0 就是这一格真发生的铁证**;字段语义与判读见 > [`../子系统设计/看门狗设计说明.md`](../子系统设计/看门狗设计说明.md) §11.4。 diff --git a/docs/产品需求/产品需求设计文档.md b/docs/产品需求/产品需求设计文档.md index d63abc67..7bf9540f 100644 --- a/docs/产品需求/产品需求设计文档.md +++ b/docs/产品需求/产品需求设计文档.md @@ -865,7 +865,6 @@ cfg-file-unit-test、test-suite-switch、gitlint。 | 硬件网表 | [`docs/硬件平台/V0.1_网表详细分析报告.md`](../../docs/硬件平台/V0.1_网表详细分析报告.md) | | 待办登记簿 | [`docs/任务规划/README.md`](../任务规划/README.md) | | 体系层评审报告 | [`docs/问题记录/项目审查与优化建议报告.md`](../../docs/问题记录/项目审查与优化建议报告.md) | -| 错误码审计 | [`docs/问题记录/错误码传播审计.md`](../../docs/问题记录/错误码传播审计.md) | | 实机测试指南 | [`docs/测试说明/实机测试实施指南.md`](../../docs/测试说明/实机测试实施指南.md) | | 测试标准流程 | [`docs/测试说明/测试开发标准流程.md`](../../docs/测试说明/测试开发标准流程.md) | | RAM 预算与优化 | [`docs/内存管理/RAM使用分析与优化方案.md`](../../docs/内存管理/RAM使用分析与优化方案.md) | diff --git a/docs/代码规范/pre-commit使用说明.md b/docs/代码规范/pre-commit使用说明.md index d67747ac..c643604f 100644 --- a/docs/代码规范/pre-commit使用说明.md +++ b/docs/代码规范/pre-commit使用说明.md @@ -382,7 +382,7 @@ cppcheck 是开源 C/C++ 静态分析工具,**不运行代码**,通过语法 | **检查逻辑** | 解析 `.map` 执行区占比 → 超阈值即 FAIL 并附 **Top-10 RW+ZI 对象表**(定位增量元凶);同时校验 `docs/内存管理/RAM预算台账.md` 受管区与 `.map` 一致(漂移 → 提示 `--write`) | | **软提醒**(不拦) | tsv 末行落后于 `.map`(忘跑 `--update`);`.map` 早于最新源码(忘 `@build`) | | **配套三件套** | `scripts/ram_baseline.py`(门禁/趋势/受管区生成)+ `scripts/ram_baseline.tsv`(每次 `@build` 后 `--update` 追加的趋势基线,`--trend` 高亮 RW+ZI 增长 >1 KB 的对象)+ `docs/内存管理/RAM预算台账.md`(子系统配额 + CCM 台账 + 超配额流程) | -| **为什么需要** | `docs/问题记录/项目审查与优化建议报告.md` §5.5 第 1 条"超阈值即失败 + Top-10 附表"此前**无任何机械检查**——`mem-numbers` 只防"文档数字漂移"不防"占用超阈值";B4/B5 要新建任务与缓冲,红线必须先机械化(登记簿 N-27 前半 / W1-0) | +| **为什么需要** | 2026-09 体系评审提出的"超阈值即失败 + Top-10 附表"(现沉淀在 `docs/问题记录/项目审查与优化建议报告.md` §5.5 闭环说明)此前**无任何机械检查**——`mem-numbers` 只防"文档数字漂移"不防"占用超阈值";B4/B5 要新建任务与缓冲,红线必须先机械化(登记簿 N-27 前半 / W1-0) | | **运行器** | `scripts/ram_baseline.py --gate`(纯标准库,复用 `ram_report.py` 的 `.map` 解析口径;退出码 0=通过/跳过 / 1=超阈值或漂移 / 2=解析失败) | ### 2.20 iec104-unit-test — IEC 60870-5-104 协议栈主机侧单测(2026-10-12 加,B4) diff --git a/docs/任务规划/README.md b/docs/任务规划/README.md index 54feb904..6eda2190 100644 --- a/docs/任务规划/README.md +++ b/docs/任务规划/README.md @@ -22,8 +22,8 @@ > 已闭环项的**结论与证据沉淀进对应模块文档/问题记录**,本文件夹不再重复。 > **消化即销项**:事项修复/关闭后,直接删除对应条目,并同步清理其它文档对本编号的引用。 > -> **不在本文件夹范围**:`../问题记录/错误码传播审计.md`(E-xx,审计报告,非待办池)、 -> `../问题记录/项目审查与优化建议报告.md`(体系层评审报告)、`../测试说明/实机测试实施指南.md`(执行流程)。 +> **不在本文件夹范围**:`../问题记录/项目审查与优化建议报告.md`(体系层评审报告)、 +> `../测试说明/实机测试实施指南.md`(执行流程)。 > > **修订**:2026-10-04 四档合并;2026-10-11 统一排序 + 审查报告并轨; > **2026-10-12 按用户决定 RS-485(B2)整簇暂缓,波次重排为 W1~W7,清除已销项残留**; @@ -43,7 +43,7 @@ | `P0-x`~`P3-x` | 审计缺陷/改进项(2026-09-15/09-21 审计) | P0=功能 bug、P1=疑似 bug/健壮性、P2=改进、P3=卫生 | | `N-x` / `S-x` / `T-x` | 台架/审计观察登记项 / 存储·日志管线 / 测试与工具链 | **存量冻结沿用、不再新增**(新条目见上一行);`N-x` 含硬件改版项(需下一版硬件,固件侧只记口径,如 N-18) | | `S1~S14` / `A-1~A-7` | 2026-10-02 复审的测试充分性与架构专项 | 编号**不回收** | -| `E-xx` | 错误码传播审计发现 | 独立维护于 `错误码传播审计.md`,**不并入本文件夹**,只交叉引用 | +| `E-xx` | 错误码传播审计发现(**历史族**) | 属主文档已于 2026-10-15 删除;编号冻结(`docs/编号规范.md` §4.1 / §4.3),存量引用仅作历史指称,新发现一律 `DEF` | | `B`/`C`/`D` 前缀 | §2 排序条目编号:B=功能主线、C=结构债、D=验证窗口 | 编号不回收,见 §2 | | 报告 `N-x`/`R-x`/`F-x`/`S-x`/`U-x`/`D-x` | `项目审查与优化建议报告.md` **自有编号** | ⚠ **与本登记簿同名不同义**(报告 N-16 = 日志逐条开关,登记簿 N-16 = A2/A4 负向台架)→ **引用一律带文档名**。其未闭环项 2026-10-11 并入本登记簿(新编 `N-19`~`N-29` + U 簇,出处列保留报告编号) | @@ -64,19 +64,21 @@ | 类别 | 数量 | 条目 | |---|---|---| | P1 收尾 | 1 | P1-10 测试固件构建实测(`测试与架构专项.md` §1) | -| 网络 | 5 | N-2(49s 日志静默,**低风险观察项**)· N-3(L5 基线重验)· N-4(ACCEPT pending 化)· N-16(A2/A4 负向台架)· N-17(探针窗口心跳缺口) | +| 网络 | 4 | N-2(49s 日志静默,**低风险观察项**)· N-3(L5 基线重验)· N-16(A2/A4 负向台架)· N-17(探针窗口心跳缺口) | | 存储 / 日志 | 3 | **S-3(USART1 TX-only 根治)** · S-4(dhara 重试自旋)· S-5(可选全序列卷锁) | | 数据正确性 | 1 | **N-21(dhara `is_free`/`copy`,全簿最高风险)** | -| 结构 / 健壮性 | 9 | N-20(Error_Handler 死循环)· N-22(写回 flush)· N-23(注册校验残余白名单)· N-24(日志逐条开关)· N-25(性能/语义簇)· N-26(NAND DMA 断言)· N-27(内存优化簇,三件套 ✅ 前半落地)· N-28(Flash 死代码簇)· **N-33(栈余量收口,W1)** | -| 仓库 | 1 | N-29(`.git` 149 MB,**已决策暂不处理、登记观察**) | +| 结构 / 健壮性 | 7 | N-22(写回 flush)· N-23(注册校验残余白名单)· N-24(日志逐条开关)· N-25(性能/语义簇)· N-27(内存优化簇,三件套 ✅ 前半落地)· N-28(Flash 死代码簇)· **N-33(栈余量收口,W1)** | +| 仓库 | 1 | N-29(`.git` **235 MB**,**已决策暂不处理、登记观察**) | | 硬件 | 1 | N-18(V0.2 改版项) | | 测试 / 基础设施 | 6 | T-7(CI runner + 推送闭环 + 第二机交叉验证)· S12(断言宏)· S1-c(可选单测)· A-4(采集链路测试约束,RS485 侧随 B2 暂缓)· A-5 残余(CI 体积门禁盲区)· D7(串口助手 A6/A7 待 CH340) | | UI | 1 簇 | U 簇(27 页标准化,**本轮不排期**) | | **暂缓** | 1 簇 | **B2 RS-485 整簇(2026-10-12 用户决定,`暂缓-RS485.md`)** | -> **网络侧另有 E-xx 未闭环项**(E-12/15/16/23/24/25/26/27/28/30/47/48/49/50/58/59/61,共 17 项) -> 与 1 项回归监督(E-62),详见 `../问题记录/错误码传播审计.md` §3——其中 **E-49/E-50 随 RS-485 暂缓**。 -> 本文件夹不重复登记。UI 标准化簇与覆盖率缺口(lftpd/dhara 移植层无主机测试)为整簇排期项,见 §2 W6 / N-21。 +> **网络侧 E-xx 存量项**(历史编号族,属主文档已于 2026-10-15 删除,编号冻结见 +> `docs/编号规范.md` §4.3):E-25 / E-27 / E-28 挂 C1(net 层跨上下文收口,见 `排期详述.md` §1); +> E-47 待 CH395F 驱动侧定案;E-12 为 `FF_FS_LOCK` / 单写者纪律记录项;E-31 后续与 E-62 +> 为回归监督项(纪律已固化于 `AGENTS.md`,无需改动)。 +> UI 标准化簇与覆盖率缺口(lftpd/dhara 移植层无主机测试)为整簇排期项,见 §2 W6 / N-21。 ### 1.1 结构性判断(排序依据) @@ -100,11 +102,11 @@ |---|---|---| | **W1 采集+保护收尾** | **里程碑制 + 并行道**:M1 台架闭环 ✅ 达成(B6 ✅→N-33 ✅)∥ M2 功能面(代码道 B7 ✅→{B8∥B9})→ M3 送检(B10,Fixed Date)——任务卡/判据/写范围见 `W1-采集保护收尾.md` | **用户决定(2026-10-12):先把 ADC 采集、保护等相关功能完成**。B4/B5 代码已落地但未台架闭环、功能面不完整(定值不落盘 / 无频率保护 / 无动作记录 / 栈两处 FAIL)——本波收口到"可用 + 可验收";真约束只有**台架**与**送检窗口**两个稀缺资源,其余任务按写范围切分并行 | | **W2 数据正确性** | **N-21** dhara `is_free`/`copy`(报告 N-12/13) | 低概率、后果重(数据丢失);修复须配主机侧移植层测试,一并补报告"覆盖率"缺口 | -| **W3 预算与内存** | **N-27 后半**:R-9 池 40→16 KB → R-2 后半(54 KB→CCM)→ R-3~R-6 逐项评估 | 依赖 W1 三件套产出的台账数据;**B5 堆扩容后 CCM 只剩 ≈8 KB**(56 KB 堆),R-2 后半更被堵死(解锁链见 `docs/内存管理/RAM预算台账.md` §3) | -| **W4 结构债** | **N-20 → C6(S-3)→ C3(N-2+N-24)→ C1 ACCEPT pending 化 → C5(含 N-23 残余)→ N-22/N-25/N-26/N-28**(详表 `排期详述.md` §1) | 无紧急,按性价比排:C6 灭 IWDG 误复位机制根源;C1 体量大、验证重,放后(N-23 钩子已落地 2026-10-12,残余并入 C5) | +| **W3 预算与内存** | **N-27 后半**:R-9 池 40→16 KB → R-2 后半(54 KB→CCM)→ R-3/R-4/R-6 逐项评估(**R-5 已判定不可行**,见 `登记簿.md`) | 依赖 W1 三件套产出的台账数据;**B5 堆扩容后 CCM 只剩 7,904 B**(56 KB 堆),R-2 后半更被堵死(解锁链见 `docs/内存管理/RAM预算台账.md` §3) | +| **W4 结构债** | **C6(S-3)→ C3(N-2+N-24)→ C1 net 层跨上下文收口 → C5(含 N-23 残余)→ N-22/N-25/N-28**(详表 `排期详述.md` §1) | 无紧急,按性价比排:C6 灭 IWDG 误复位机制根源;C1 消化 E-25/E-27/E-28(原 ACCEPT+SEND pending 化已收口),体量大、验证重,放后(N-23 钩子已落地 2026-10-12,残余并入 C5) | | **W5 验证常态** | P1-10 收尾 · T-7 CI · D2 N-3 · D5 N-16 · D6 N-17 · D7 串口助手 · S12 · S1-c · A-5(详表 `排期详述.md` §2) | 窗口性,逢机顺带,不单独排期 | | **W6 UI 扩展** | U4 残余(golden + 字符集差集挂门禁)→ U1 令牌层 → U2/U3 → **27 页**落地 | 产品驱动、体量大,需单独立项(顺序按 `docs/问题记录/项目审查与优化建议报告.md` §6.5);**本轮不排期(2026-10-11 已确认)** | -| **W7 仓库决策** | **N-29** `.git` 149 MB + 图片 36.3 MB | **已决策(2026-10-11):暂不处理、登记观察**——推送/协作前重评(`git filter-repo` 历史重写影响未推送提交与协作者) | +| **W7 仓库决策** | **N-29** `.git` **235 MB** + 图片 36.3 MB | **已决策(2026-10-11):暂不处理、登记观察**——推送/协作前重评(`git filter-repo` 历史重写影响未推送提交与协作者) | | **暂缓** | **B2 RS-485 整簇**(`暂缓-RS485.md`) | **用户决定(2026-10-12):当前不处理 RS-485** | | **硬件改版** | N-18 8 路电气量全接 TPAFE(V0.2) | 下一版硬件;固件侧已用语义映射隔离,改版只动映射表 | @@ -116,9 +118,9 @@ W1 收尾 台架道: B6✅→N-33✅(M1 ✅ 2026-10-12) ∥ 代码道: B7✅ ↓ W2 数据 N-21 dhara is_free/copy(配主机侧移植层测试) ↓ -W3 预算 R-9 池瘦身 → R-2 后半(54KB→CCM)→ R-3~R-6 评估 +W3 预算 R-9 池瘦身 → R-2 后半(54KB→CCM)→ R-3/R-4/R-6 评估 ↓ -W4 结构 N-20 → C6(S-3) → C3(N-2/N-24) → C1 ACCEPT pending 化 → C5(含 N-23 残余) → N-22/25/26/28 +W4 结构 C6(S-3) → C3(N-2/N-24) → C1 net 层跨上下文收口 → C5(含 N-23 残余) → N-22/25/28 ↓ W5 验证 P1-10 · T-7 CI · D2/D5/D6/D7 · S12 · S1-c · A-5 ↓ @@ -139,11 +141,11 @@ W7 仓库 N-29 .git 149MB(已决策暂不处理) 硬件:N-18 V0.2 **不再新增字母+数字编号**(规则见 `docs/任务规划/README.md` §0 表首行);存量编号 (P-x / N-x / S-x / T-x / S1x / A-x / B·C·D 前缀)**冻结沿用**,而**已随销项消失的号永久作废**、 不得作为新条目候选——"不回收"在只写规则、不留痕迹的情况下不可执行(撞号实测见 - `docs/任务规划/README.md` §0 注);`错误码传播审计.md` 的 E-xx 独立维护。 + `docs/任务规划/README.md` §0 注);E-xx(历史编号族)冻结于 `docs/编号规范.md` §4.3。 - **引用审查报告编号一律带文档名**(报告 N-16 ≠ 登记簿 N-16、报告 S-3 ≠ 登记簿 S-3); 报告并轨项用本登记簿新编号(N-19~N-29 + U 簇),出处列保留报告编号。 - **§2 是全项目唯一的推进顺序权威**:`产品需求设计文档.md` 等文档中的路线图只留指针,顺序变更只改 §2.0(详表文件跟着挪,不改顺序)。 - **暂缓块(`暂缓-RS485.md`)不是销项**:条目仍在簿,只是不占波次;重启或关闭时更新该文件与 §1/§2.0。 - 新增发现按既有惯例分流:能立即修的进 §2 波次(W4 C5);需排期的进 `登记簿.md` / `测试与架构专项.md` 对应编号空间;跨版本观察的进 `登记簿.md`。 -- 上游引用关系:`问题记录/项目审查与优化建议报告.md`(体系层评审)、`问题记录/错误码传播审计.md`(E-xx)、 - `测试说明/实机测试实施指南.md`(执行流程)——三者均不在本文件夹合并范围。 +- 上游引用关系:`问题记录/项目审查与优化建议报告.md`(体系层评审)、 + `测试说明/实机测试实施指南.md`(执行流程)——两者均不在本文件夹合并范围。 diff --git a/docs/任务规划/基线与待确认.md b/docs/任务规划/基线与待确认.md index 56e82745..39fed330 100644 --- a/docs/任务规划/基线与待确认.md +++ b/docs/任务规划/基线与待确认.md @@ -11,46 +11,23 @@ ## 2. 设计基线(供后续接入参考) -### 2.1 ACCEPT pending 化设计基线(recv/send 挂起已落地) +### 2.1 net 层阻塞模型 pending 化(状态) -> **来源**:recv/send 架构现状见 `docs/网络/Net层阻塞模型架构设计.md`(演进史查 git 历史); -> 本节仅保留 **ACCEPT pending 化仍要用的设计基线**。 +> **来源**:`docs/网络/Net层阻塞模型架构设计.md`(现状架构;演进史查 git 历史)。 -**已验证的机制骨架**(ACCEPT 沿用,勿重新发明):独立静态 pending 表 + `net_pending_tick` -主循环按节拍结束挂起(recv 五路:数据/到点/断连/同 fd 串行/重建取消必通知)+ 登记守卫(每 fd 至多一个、 -不覆盖、拒绝 netTask 自等待)+ 挂起超时留痕探针(tripwire,只留痕不伪造返回值)。 - -**扩展点**: - -| 项 | 目标 | 对应缺陷 | 状态 | -|---|---|---|---| -| **SEND pending** | 发送续传改为"登记 → send_ready 完成",消除 1s/分片 stall 对队列的冻结 | E-02 残余(SEND 位置) | ✅ **2026-10-14 已落地**(`net_send_pending_tick` + 有界自旋 `NET_SEND_SPIN_MS`;架构见 `docs/网络/Net层阻塞模型架构设计.md` §4.5/§5.5/§7) | -| **ACCEPT pending** | accept 等待改为"登记 → CONNECT 事件完成",退役 100ms/200ms 轮询循环 | E-06(accept 轮询计时失真) | ⬜ 待做 | - -**ACCEPT 门禁**(在已落地机制的门禁基础上加强): - -1. CH395F NET 层全套件(含此前未跑的 Phase 7 / Phase 10,视 RAM 预算); -2. 延迟探针 L3/L5 基线重锁(recv 挂起落地时已重锁 M3,ACCEPT 落地须再锁); -3. `e02_probe.py` **探针族扩充**:close / reconfig / EOF / disconnect-during-pending 四专项; -4. `ftp_regression.py`(D20)+ E-06 假 425 场景**独立重跑**(recv 挂起那轮是借 M3 压力档覆盖的); -5. UI 远端推流压力 + timeSync 三场景; -6. 产品 `@test` 闭环 + ST-LINK 全片回读; -7. `ram_report.py` / `stack_report.py` 前后对比(铁律); -8. 看门狗接 selftest 留痕 + 模拟器差距清单 +1(`net_socket_sim.c` 无法建模 pending)。 - -**已知代价(延续,需文档化)**:pending 完成判定随主循环节拍运行,被长操作占用时超时 -顺延(~1ms 级);**SEND 已 pending 化(2026-10-14)**,该顺延面已收窄——发送停滞不再顺延整层; -剩余 ACCEPT 化不改变此结论。 - -**改动量估算**:SEND 实际落地约 300 行(`net_pending.c` +287 行、`net_socket.c` 10 处集成点、 -`net_config.h` 新增 `NET_SEND_SPIN_MS`);公开 API 不变。风险评级**中**(执行器核心 + D 系列前科) -——**大的是验证,不是代码**。SEND 已完成构建门禁(0 错误 0 警告)与静态核对,**台架复测待做** -(netTask 栈峰值须重测)。剩余 ACCEPT 预计再 ~100-200 行。 +- **recv 挂起 / send 挂起已落地**:`net_recv` 阻塞不占 netTask(登记 + 每轮 tick 唤醒); + `net_send` 快路径有界自旋(`NET_SEND_SPIN_MS` = 2 ms),超预算登记挂起、由 `net_send_pending_tick` + 按主循环节拍分块推进(2026-10-14 落地)。 +- **ACCEPT pending 化决定不实施(2026-10-15)**:ACCEPT 是**调用方轮询**问题、执行者零占用 + (`net_accept_locked` 单次 `net_poll` 即返回,0 停滞)——pending 化只换 RPC 开销与延迟抖动, + 不解队头阻塞;判据见 `docs/网络/Net层阻塞模型架构设计.md` §10。 +- 原 ACCEPT 设计基线(扩展点 / 门禁清单 / 改动量估算)随该决策作废,不再保留。 ### 2.2 错误码通道四要素(CH395F 范式) -> **来源**:`../问题记录/错误码传播审计.md`。GD5F 错误通道统一(E-10 / E-42 / E-53 / E-54 / E-56 / E-57) -> 即按此骨架落地(2026-10-03,套件回归 16/16 PASS)。**后续任何驱动 / 子系统建立错误通道时应沿用。** +> **来源**:错误码通道四要素方法论(2026-09 错误码传播审计确立,属主文档已于 2026-10-15 删除)。 +> GD5F 错误通道统一即按此骨架落地 +> (2026-10-03,套件回归 16/16 PASS,见 `../问题记录/GD5F2GQ5UE_Trap_Records.md`)。**后续任何驱动 / 子系统建立错误通道时应沿用。** ``` ① 分类:区分「超时 / 真故障 / 参数错」——不得混为一个返回码 diff --git a/docs/任务规划/排期详述.md b/docs/任务规划/排期详述.md index a6f87226..2c93d63f 100644 --- a/docs/任务规划/排期详述.md +++ b/docs/任务规划/排期详述.md @@ -9,12 +9,11 @@ | 序 | 任务 | 说明 | 出处 | |---|---|---|---| -| **N-20** | **`Error_Handler` 统一降级** | 一处初始化失败 = 整机挂死(只剩 IWDG 兜底)→ 记自检 + 禁用该子系统 + 其余照常。详见 `登记簿.md` | 报告 N-03c | | **C6** | **USART1 TX-only 结构根治(S-3)** | 灭 ORE 僵尸机制根源(2026-10-02 台架 IWDG 误复位的机制根源):改 `.ioc` 令 USART1 不使能 RX / PA10 不复用为 `USART1_RX` → CubeMX 重生成 → 跑 `check_cubemx_survival.py`。结构性改动,需单开一轮 + 复跑台架闭环 | S-3 | | **C3** | **存储侧日志管线(N-2 + N-24)** | 丢弃可观测性**代码已具备**(`console_log.c` `s_con_drop`+`[ERR] CONSOLE dropped`、`sys_log_mcu.c` 队列满 `DBG_ERROR`、TX 僵尸通道自愈);V5 30 min 日志**未复现 49s 静默**(运行期 >10s 静默全为 ~28s、均在 `e02_probe` 窗口内=N-17)。剩余:与 S8(非阻塞日志)合并评估 + 日志逐条 `f_open/f_write/f_close` 改造评估(N-24) | N-2 / N-24 | -| **C1** | **ACCEPT + SEND 阻塞路径 pending 化** | 沿用已落地的 recv 挂起机制扩展;同期消化 E-25(net_select 语义)、E-24/E-27/E-28(跨上下文收口);基线重锁 + 探针族扩充 + 看门狗接 selftest。**设计基线见 `基线与待确认.md` §2.1** | N-4 | -| **C5** | 一档剩余 | E-15(ringbuf 定去留——**已确认零调用者,倾向删除**)、E-61(SD2506 注释订正)、E-16(cfg_file 段名长度)、E-26(UDP 定案,倾向移除)、E-23、E-30;**N-23 残余**(`KNOWN_GAPS` 白名单清理 + `app_cfg.h` 未接线开关,见 `登记簿.md`)。**E-49(RS485 DE 收尾)已移入 `暂缓-RS485.md`** | 一档/二档 | -| **N-22/25/26/28** | 评估簇 | 写回 flush / 性能与语义一致性 / NAND DMA 断言 / Flash 死代码——逐项见 `登记簿.md` 对应行,随窗口评估 | 报告各条 | +| **C1** | **net 层跨上下文收口** | 消化 E-25(`net_select` 语义)、E-27/E-28(裸指针收口为状态查询 API)。原 ACCEPT + SEND pending 化已收口:SEND 2026-10-14 落地、ACCEPT 决定不实施(2026-10-15) | E-25 / E-27 / E-28 | +| **C5** | 一档剩余 | **N-23 残余**(`KNOWN_GAPS` 白名单清理 + `app_cfg.h` 未接线开关,见 `登记簿.md`)。**RS-485 DE 收尾已移入 `暂缓-RS485.md`** | 一档/二档 | +| **N-22/25/28** | 评估簇 | 写回 flush / 性能与语义一致性 / Flash 死代码——逐项见 `登记簿.md` 对应行,随窗口评估 | 报告各条 | ## 2. W5 验证常态详述 diff --git a/docs/任务规划/测试与架构专项.md b/docs/任务规划/测试与架构专项.md index 389df5cc..39bb535d 100644 --- a/docs/任务规划/测试与架构专项.md +++ b/docs/任务规划/测试与架构专项.md @@ -75,11 +75,11 @@ 1. **协议解析必须与硬件 I/O 分离**——`App/protocol/` 的解析函数应接受 `(const uint8_t *buf, size_t len)` 并返回解析结果,使其成为 L0.5 的**天然适格对象** (参考 `cfg_file` 的"核心 + 后端"模式); -2. **采集缓冲必须双缓冲 + 序号**(上游 E-49 / E-50)——否则消费者读到撕裂数据, +2. **采集缓冲必须双缓冲 + 序号**——否则消费者读到撕裂数据, 且**撕裂在单测里测不出来**(时序相关),必须在设计上消除; 3. **DE 收尾必须有超时路径覆盖**(RS485 半双工特有陷阱)。 -**完成判据**:B2 设计文档体现上述三条;协议解析函数有对应 L0.5 单测。(B1 侧:ADC 无协议解析面,其"同一时刻性"由 E-58 原子快照保证、已闭环。) +**完成判据**:B2 设计文档体现上述三条;协议解析函数有对应 L0.5 单测。(B1 侧:ADC 无协议解析面,其"同一时刻性"由原子快照保证、已闭环。) ### 2.4 A-5 残余:CI 体积门禁盲区 ⬜ diff --git a/docs/任务规划/登记簿.md b/docs/任务规划/登记簿.md index 82171039..65c6a4d2 100644 --- a/docs/任务规划/登记簿.md +++ b/docs/任务规划/登记簿.md @@ -11,9 +11,8 @@ | # | 事项 | 发现 | 证据与现状 | 出处 | |---|---|---|---|---| -| N-2 | **49s 全系统日志静默(defaultTask/NAND syslog 排空停滞)**——串口日志 49s 无行,期间 net 栈照常处理外部连接 → 日志管线停滞,队列满后各任务日志行被丢;系统功能不受损 | 2026-09-22(L3 台架 F2,gen 181) | **A/B 归因:既有存储侧现象**。**2026-10-03 降级为低风险观察项**:① 丢弃可观测性**代码已具备**(`console_log.c` `s_con_drop` + `[ERR] CONSOLE dropped`;`sys_log_mcu.c` 队列满 `DBG_ERROR`;UART TX 僵尸通道自愈);② V5 30 min **未复现 49s**(>10s 静默全为 ~28s、均在 `e02_probe` 窗口内=N-17)。**残余待办**:与 S8(非阻塞日志)合并评估(C3) | `错误码传播审计.md` E-02 阶段二 | +| N-2 | **49s 全系统日志静默(defaultTask/NAND syslog 排空停滞)**——串口日志 49s 无行,期间 net 栈照常处理外部连接 → 日志管线停滞,队列满后各任务日志行被丢;系统功能不受损 | 2026-09-22(L3 台架 F2,gen 181) | **A/B 归因:既有存储侧现象**。**2026-10-03 降级为低风险观察项**:① 丢弃可观测性**代码已具备**(`console_log.c` `s_con_drop` + `[ERR] CONSOLE dropped`;`sys_log_mcu.c` 队列满 `DBG_ERROR`;UART TX 僵尸通道自愈);② V5 30 min **未复现 49s**(>10s 静默全为 ~28s、均在 `e02_probe` 窗口内=N-17)。**残余待办**:与 S8(非阻塞日志)合并评估(C3) | `../网络/Net层阻塞模型架构设计.md`(原审计队头阻塞阶段二) | | N-3 | **L5 队列往返基线(1223/988µs)的 L3 后重验**——L5 测 DONTWAIT 队列全程;L3 对该路径逐位不变 → 机制上无影响,但基线数值属分片时代,严格重锁需跑一轮 Phase 13(需 PC peer) | 2026-09-22(L3 收尾留档) | 低风险:10ms/100ms 阈值余量 >8×;下次 Phase 13 窗口顺带重锁即可 | `链路延迟测试说明.md` §6.4 L5 注¹ | -| N-4 | **ACCEPT 阻塞路径 pending 化**(recv/send 挂起机制已落地,扩展到 accept)——E-06、E-20、E-21 剩余分量共用该机制扩展;届时延迟探针 L3/L5 基线再重锁、探针族扩充、看门狗接 selftest 留痕、模拟器差距清单 +1 | — | **recv 挂起已落地**;**send 挂起已于 2026-10-14 落地**(构建门禁过、台架复测待做),**ACCEPT 仍待做**(收益性质不同,优先级低——见 `Net层阻塞模型架构设计.md` §10 判据)。架构现状见 `Net层阻塞模型架构设计.md`,ACCEPT 设计基线见 `基线与待确认.md` §2.1 | `错误码传播审计.md` 第二档 #2 | | N-16 | **A2/A4 专项场景台架验证待办**(正常路径已实测零回归):① PHY 拔线负向测试——断链需断 **DTU 侧**线缆或关**受管交换机端口**;② A2 芯片级 `net_init` 失败分支(需 SPI 故障注入);③ A4 短写压测(需远显客户端 + UI 推流压力) | 2026-10-03(台架拓扑 = CH395↔交换机,拔线操作受限) | 待专项台架窗口 | `实机测试实施指南.md` §10 | | N-17 | **`time_sync` 30s 挂起客户端掉线 + 监听重建期间,网络任务心跳缺口达 800ms~1s(声明 stall 但未复位)。**待办**:定位监听重建为何阻塞 NET/FTP/UI;评估缩短重建重试(5×2s)或让掉线路径不阻塞其它任务 | 2026-10-03(V5 台架) | V5 长跑 44 次 `task stall: mask=0x00000001`,**100% 与 `e02_probe` 的 30s 挂起客户端触发 `time_sync: no TIME packet in 30s, drop conn` → 监听重建(约 4s)重合**;隔离实验(仅 FTP,120s)**0 次 stall** → 探针场景专属。缺口(~1s)< IWDG 最坏超时(1701ms),余量仅 ~0.7s。 | `实机测试实施指南.md` §4;`../子系统设计/看门狗设计说明.md` | | N-30 | **relisten 沉降段统一到驱动 `sock_reset` 原语受阻**——"去重复实现"的收口已实测回退:原语版 `ftp_abort_probe` RST **0/10**、FIN **0/5**(中止后首笔复连仍被截断到 ≤2048B),另新增 `sock_reset_sync timeout (600ms)` 与 `close: sock_reset not done` 两条 `[ERR]`;同场 net 层自建序列 **10/10 + 5/5** | 2026-10-14(同一块板 A/B,判据 `test/host/ftp_abort_probe.py`;快复连 50/50、背靠背 30/30 两版都过 → 失效面精确落在"中止后首笔复连") | **现状 = `net_listener.c` 保留自建排渣序列**(排空 + CLOSE + 排事件 + 等 CLOSED,30 ms 重试轮持续清)。重开前置三条语义:① 隔离窗内**持续**排空/关闭(残渣在 CLOSE 之后才落进 RX,一次排空接不住)② **复用让位**(复位机不得拆上层刚在同一芯片 Socket 上重建的监听)③ `net_sock_reset_sync` 改"已在运行 = 加入等待"(`begin` 返 -1 ≠ 失败) | `../网络/网络自愈架构设计.md` §8 延后项 1、§5.2 E16;`../问题记录/CH395F_Trap_Records.md` TRAP-NET-18 | @@ -42,14 +41,12 @@ | # | 事项 | 影响 / 现状 | 处置方向 | 波次 | 出处 | |---|---|---|---|---|---| -| N-20 | `Error_Handler` 仍 `__disable_irq(); while(1){}`(`Src/main.c:274-282`) | 一处初始化失败 = 整机挂死(只剩 IWDG 兜底),无自检日志 | 统一降级策略:记自检 + 禁用该子系统 + 其余照常 | W4 | 报告 N-03c | | N-21 | **dhara 两处**:`dhara_nand_is_free()` 只查页首 64 B;`dhara_nand_copy()` 不处理 `DHARA_NO_PAGE` 哨兵 | 映射恢复误判 → **数据丢失风险**(低概率、后果重);哨兵页号被当真实页 → 未定义行为;至今只能靠人工读代码发现 | 整页(或首/中/尾)扫描 + 返回 `DHARA_E_UNSUPPORTED`;**必须配主机侧移植层单测**(一并补报告"覆盖率"缺口) | **W2** | 报告 N-12/N-13 + §7 | | N-22 | 写回缓存无空闲/掉电强制 flush(整页写与换页已 flush) | 写回窗口内掉电丢最近日志/配置写 | defaultTask 空闲 N ms flush | W4 | 报告 N-14 | -| N-23 残余 | **钩子已落地**(2026-10-12 `registration-check` 挂 pre-commit,存量缺口走 `KNOWN_GAPS` 白名单只 WARN);**残余** = 白名单清理:`rs485_task.c`(随 B2 暂缓一并处置:删除或注册)、`lftpd_log.c`;`app_cfg.h` 未接线开关(C5) | 白名单不清 = "写了不编译"盲区仍在 | 逐项清白名单(`--strict` 供审计复跑);开关随 C5 处置 | W4(C5) | 报告 N-06 残余 + 报告 S-3 | +| N-23 残余 | **钩子已落地**(2026-10-12 `registration-check` 挂 pre-commit,存量缺口走 `KNOWN_GAPS` 白名单只 WARN);**残余** = 白名单只剩 `rs485_task.c` 一条**刻意**缺口(E-59 门禁下随 B2 暂缓,见 `暂缓-RS485.md`);`app_cfg.h` 未接线开关(C5)。⚠ `lftpd_log.c` 已随 N-19 删除并入 `lftpd_auth.c`(2026-10-11),不再是缺口 | 白名单不清 = "写了不编译"盲区仍在 | 随 B2 一并出白名单(`--strict` 供审计复跑);开关随 C5 处置 | W4(C5) | 报告 N-06 残余 + 报告 S-3 | | N-24 | 日志每条 `f_open+f_write+f_close`(`sys_log_mcu.c:123-140`) | 每条日志 2–4 次 NAND 页读写;UI 翻记录逐条 open/seek/close | 与 C3 日志管线合并评估(现仅 `FIL` static 化省 528 B 栈) | W4 | 报告 N-16(**≠ 本登记簿 N-16**) | -| N-25 | 性能/资源建议簇:PASV 每次新建监听;`NET_RECV_TIMEOUT_MS 30000`;PHY 强制 100M 无自协商回退;`NET_MAX_SOCKETS 8` 与单连接语义不符;`HAL_Delay(1)` 阻塞 DE(**DE 项随 B2 暂缓,见 `暂缓-RS485.md`**) | 次级性能/语义一致性 | 逐项见 `../问题记录/项目审查与优化建议报告.md` §4.2/§4.5/§4.7,随窗口评估 | W4 | 报告 N-09/P2-8/PHY/P3-x/rs485 | -| N-26 | NAND 直 DMA 调用方缓冲(已加运行时守卫,阈值 1024) | CCM 缓冲被 DMA = 静默传错(与 ADC DMA 不可达 CCM 同类坑) | 补编译期/启动期地址断言 + **新铁律:堆缓冲不可被 DMA 直接寻址** | W4 | 报告 N-11 | -| N-27 | **内存优化簇**:LVGL 池 + UI 两份缓冲 54,146 B 仍在 SRAM1,CCM 只剩 ≈8,160 B(2026-10-12 B5 堆扩容 48→56 KB 后) | R-2 后半段(迁 CCM)被堵死 → 后续功能预算看不清 | **三件套 ✅ 2026-10-12 落地**(`ram_baseline.py` 四模式 + `ram_baseline.tsv` 趋势 + `docs/内存管理/RAM预算台账.md` + `mem-budget` 钩子 92% 门禁)→ **W3** 再做 R-9(`LV_MEM_SIZE` 40→16 KB,池水位门禁数据已具备)→ R-2 后半;R-3/R-4/R-5/R-6 逐项评估(R-6 前置已满足) | **W3** | 报告 R-2 剩余/R-3~R-6/R-9/§5.5 | +| N-25 | 性能/资源建议簇:PASV 每次新建监听;PHY 强制 100M 无自协商回退;`NET_MAX_SOCKETS 8` 与单连接语义不符;`HAL_Delay(1)` 阻塞 DE(**DE 项随 B2 暂缓,见 `暂缓-RS485.md`**)。⚠ 原列的 `NET_RECV_TIMEOUT_MS 30000` 已随 2026-10-13「recv 语义主流化」删除(挂起 recv 模型),不再是待办 | 次级性能/语义一致性 | 逐项见 `../问题记录/项目审查与优化建议报告.md` §4.2/§4.7,随窗口评估 | W4 | 报告 N-09/PHY/P3-x/rs485 | +| N-27 | **内存优化簇**:LVGL 池 + UI 两份缓冲 54,146 B 仍在 SRAM1,CCM 只剩 **7,904 B**(`RW_CCM` 上限 65,280 − 57,376;2026-10-12 B5 堆扩容 48→56 KB 后) | R-2 后半段(迁 CCM)被堵死 → 后续功能预算看不清 | **三件套 ✅ 2026-10-12 落地**(`ram_baseline.py` 四模式 + `ram_baseline.tsv` 趋势 + `docs/内存管理/RAM预算台账.md` + `mem-budget` 钩子 92% 门禁)→ **W3** 再做 R-9(`LV_MEM_SIZE` 40→16 KB,池水位门禁数据已具备)→ R-2 后半;R-3/R-4/R-6 逐项评估(R-6 前置已满足)。⚠ **R-5 已判定不可行、从本簇剔除**:CMSIS-RTOS2 `freertos_os2.h` 有 `#if (configMAX_PRIORITIES != 56) #error`,改小直接编译失败(为 480 B 改第三方包装层不划算) | **W3** | 报告 R-2 剩余/R-3/R-4/R-6/R-9/§5.5 | | N-28 | Flash 死代码评估簇:剩余 ≈10 KB(`box_shadow` 3,246 / `draw_sw_img` 2,960 / `bin_decoder` 1,972 / `image_decoder` 952 / `triangle` 930)+ 双精度 `dadd` 等 ≈1 KB;uvprojx 仍挂 201 个空 LVGL TU(F-1/F-2/F-3 已闭环,省的是编译时长) | 编译时长 + 文档噪音 | 逐项实测再关;⚠ `mask`/`mask_rect`/`grad` 是文字与矩形绘制必经路径,**不可盲关** | W4 | 报告 F-4/F-5/F-6 | -| N-29 | **仓库膨胀**:`.git` 149 MB + 图片 36.3 MB | clone/备份慢,且持续恶化 | 降采样 / 拆 submodule / LFS 三选一 **+ 一次 `git filter-repo` 历史重写**。**已决策(2026-10-11):暂不处理、登记观察**——推送/协作前重评(届时未推送提交更少、重写代价最低) | **W7** | 报告 D-1 | +| N-29 | **仓库膨胀**:`.git` **235.3 MB**(2026-10-14 实测;本行建档时记 149 MB)+ 手册图片 36.3 MB(受控文件合计仅 79.3 MB,`.git` 已是它的 3 倍) | clone/备份慢,且持续恶化 | 降采样 / 拆 submodule / LFS 三选一 **+ 一次 `git filter-repo` 历史重写**(三项都不解决历史膨胀)。**已决策(2026-10-11):暂不处理、登记观察**——推送/协作前重评(届时未推送提交更少、重写代价最低) | **W7** | 报告 D-1 | | U 簇 | UI 标准化:无令牌层/组件库/页注册表(手写 `case` 仍在),**27 页**未落地;golden 未挂 pre-commit、字符集差集检查仍是约定 | 新增一页成本仍是 8 处手工改动 | 按 `../问题记录/项目审查与优化建议报告.md` §6.5:U4 残余(两脚本入门禁,可逢窗口顺带)→ U1 令牌层 → U2/U3 → 页面落地;U4 golden 可直接当 U1 验收工具。**已确认(2026-10-11):本轮不排期**,等功能主线出结果后按产品需求启动 | W6 | 报告 U1~U5 | diff --git a/docs/内存管理/RAM使用分析与优化方案.md b/docs/内存管理/RAM使用分析与优化方案.md index d78e6489..f664d493 100644 --- a/docs/内存管理/RAM使用分析与优化方案.md +++ b/docs/内存管理/RAM使用分析与优化方案.md @@ -36,10 +36,10 @@ | 区域 | 用量 | 容量 | 占比 | |---|---|---|---| -| ER_IROM1 | 231,036 B (225.62 KB) | 1,048,576 B (1024 KB) | 22.0% | +| ER_IROM1 | 231,084 B (225.67 KB) | 1,048,576 B (1024 KB) | 22.0% | | RW_IRAM1 | 98,144 B (95.84 KB) | 131,072 B (128 KB) | 74.9% | | RW_CCM | 57,376 B (56.03 KB) | 65,280 B (64 KB) | 87.9% | -| **Flash (ROM 合计)** | **232,148 B (226.7 KB)** | 1,048,576 B (1024 KB) | **22.1%** | +| **Flash (ROM 合计)** | **232,196 B (226.8 KB)** | 1,048,576 B (1024 KB) | **22.1%** | 各区的容量与常量来源: diff --git a/docs/内存管理/RAM预算台账.md b/docs/内存管理/RAM预算台账.md index ec19eddc..aa95726e 100644 --- a/docs/内存管理/RAM预算台账.md +++ b/docs/内存管理/RAM预算台账.md @@ -7,7 +7,7 @@ > > **数字不手抄**:下方受管区由 `scripts/ram_baseline.py --write` 从 `.map` 生成, > pre-commit `mem-budget` 钩子校验一致性(同 `mem-numbers` 的"事实源唯一"思路)。 -> 配额是**规划值**(`docs/问题记录/项目审查与优化建议报告.md` §5.5),改动 = 先登记本文件 §6 变更记录,再改 `ram_baseline.py` 的 `QUOTAS`。 +> 配额是**规划值**,**本文件 §2 是配额与阈值的唯一权威**(2026-09 体系评审提出、2026-10-12 落地后报告侧条目已销项),改动 = 先登记本文件 §6 变更记录,再改 `ram_baseline.py` 的 `QUOTAS`。 > 区域放置与 DMA 可达性规则 → `内存内容分配策略.md`(本文件不重复)。 ## 1. 区域预算与门禁(受管区) @@ -15,7 +15,7 @@ | 区域 | 用量 | 容量 | 占比 | 门禁(≤92%) | |---|---|---|---|---| -| ER_IROM1 (Flash) | 231,036 B | 1,048,576 B | 22.0% | 参考 | +| ER_IROM1 (Flash) | 231,084 B | 1,048,576 B | 22.0% | 参考 | | RW_CCM | 57,376 B | 65,280 B | 87.9% | PASS | | UNINIT_CCM | 48 B | 256 B | 18.8% | 参考 | | RW_IRAM1 | 98,144 B | 131,072 B | 74.9% | PASS | @@ -33,7 +33,7 @@ - **门禁**:pre-commit `mem-budget` 钩子(`scripts/ram_baseline.py --gate`)—— RW_IRAM1 / RW_CCM 任一 >92% 即拦提交,并打印 Top-10 RW+ZI 对象定位增量; 无 `.map` 时自适应跳过(CI 盲区登记在 `docs/任务规划/测试与架构专项.md` A-5 残余)。 -- **阈值依据**:92% = `docs/问题记录/项目审查与优化建议报告.md` §5.5 第 1 条——留 ~8% 给突发缓冲与栈抬升, +- **阈值依据**:92% 由 2026-09 体系评审提出、2026-10-12 落地为门禁(提出时的报告条目已随销项删除,闭环说明见 `docs/问题记录/项目审查与优化建议报告.md` §5.5)——留 ~8% 给突发缓冲与栈抬升, 触线即触发排期,而不是静默逼近溢出。 - **软提醒**(不拦提交):tsv 末行落后于 `.map`(忘跑 `--update`)、`.map` 早于最新源码(忘 `@build`)。 - **子系统归集为参考值**:按对象名前缀归集(口径见 §2),合计与区域总量可差几十字节 @@ -41,7 +41,7 @@ ## 2. 子系统配额口径 -配额值(`ram_baseline.py` 的 `QUOTAS`,与 `docs/问题记录/项目审查与优化建议报告.md` §5.5 第 3 条一致; +配额值(`ram_baseline.py` 的 `QUOTAS`,**本文件即唯一权威**; RTOS 堆 2026-10-12 B5 扩容 48→56 KB 见 §6):**网络 12 KB / 存储 12 KB / UI 16 KB / RTOS 堆 56 KB / 应用 8 KB**。归集口径(按 `.map` 对象名,首匹配): diff --git a/docs/内存管理/内存内容分配策略.md b/docs/内存管理/内存内容分配策略.md index c57bb571..28f75297 100644 --- a/docs/内存管理/内存内容分配策略.md +++ b/docs/内存管理/内存内容分配策略.md @@ -39,11 +39,13 @@ ## 4. 内容分配规则 - **R1(CCM 驻留)**:CCM 只放"**CPU-only 且损坏即致命、无自愈**"的内存。当前唯一驻留者 = **FreeRTOS 堆**(`heap_4.o`,56KB——2026-10-12 B5 由 48KB 扩容,`.ioc` L1 参数 `configTOTAL_HEAP_SIZE=57344`;全部任务栈/队列/定时器)。scatter 的 RW_CCM 节**不得**再列其他对象;任何新增必须经 §5 式裁决并走 §7 完整验证阶梯。 -- **R2(DMA 缓冲)**:只允许文件作用域静态缓冲,必须落 SRAM。两条防线缺一不可: +- **R2(DMA 缓冲)**:只允许文件作用域静态缓冲,必须落 SRAM。**三层防线缺一不可**: + - **窗口防线(编译期)**:DMA 白名单窗口 `GD5F_DMA_RAM_BASE/END`(0x20000000~0x20020000,须与 scatter `RW_IRAM1` 一致)与 CCM 窗口**永不得重叠**,另锁死阈值契约 `132 ≤ GD5F_DMA_THRESHOLD < GD5F_PAGE_SIZE`——两条都用项目惯用的**负数组 typedef 断言**(AC5 不支持 C11 `_Static_assert`),有人改窗口常量或阈值当场编译失败(`gd5f2gq5ue.h`)。 - **尺寸防线**:GD5F DMA 阈值 `GD5F_DMA_THRESHOLD=1024`(>1024B 才走 DMA;阈值以下一律 CPU 轮询)。取值须 ≥ 存储路径上可能出现在任务栈的最大缓冲(dhara meta 132B、`dhara_nand_is_free` 64B)、且 < 页大小(2048,保住页级热路径的 DMA)。 - - **地址防线(2026-09-21 加固)**:`gd5f_dma_buf_ok()` 运行时校验**整段**缓冲是否落在 SRAM 区间(0x20000000~0x2001FFFF),越界者拒走 DMA、回落 CPU 轮询,并打印 `[ERR][NAND] RX/TX DMA buffer not DMA-reachable (p=… size=…) fallback poll`。 + - **地址防线(运行时 + 启动期)**:`gd5f_dma_buf_ok()` 运行时校验**整段**缓冲是否落在 SRAM 区间(0x20000000~0x2001FFFF),越界者拒走 DMA、回落 CPU 轮询,并打印 `[ERR][NAND] RX/TX DMA buffer not DMA-reachable (p=… size=…) fallback poll`;`nand_ftl.c` 在 init 期对三块静态页缓冲**预先过同一守卫**,把"scatter 被改动导致静态缓冲落进 CCM"变成开机即报,而不是静默传错。 阈值只挡得住"当前已知的小栈缓冲",挡不住"将来新加的 >1024B 非 SRAM 缓冲";地址守卫把 R2/R3 从**约定**升级为**运行时强制**。守卫负路径已由 TC-GD5F-704/705 在台架实证(读/写两方向各一,探针缓冲取自 CCM 堆,数据仍正确落位)。 + ⚠ **缓冲自身地址无法编译期断言**:`.bss` 地址链接期才确定,AC5 的常量表达式取不到(`__builtin_*` 系在 MicroLIB 下亦不可用,实证见 `Src/main.c` `Error_Handler` 注释)→ 静态缓冲落位靠 **scatter + 启动期自检**双层保证,别再试图"加个 `_Static_assert`"。 - **R3(堆/栈隔离)**:禁止以 `pvPortMalloc` 所得内存或任务栈为 DMA 源/目的(09-21 事故类:DMA 静默不落地 = 静默数据损坏,比报错更难查)。该约束在 GD5F 驱动内已由 `gd5f_dma_buf_ok()` 运行时强制(违反即回落轮询 + `[ERR]` 日志),并有 TC-GD5F-704/705 覆盖读、写两个方向。 - **R4(UI/LVGL 留 SRAM)**:LVGL 池 + 帧缓冲为**自愈型**内容(每帧由 widget 树全量重渲染,脏帧下一帧即消失),不需要保护区;且留 SRAM 保留 DMA 门(将来帧推送若要 DMA 加速必须 SRAM 可达);其越界风险已由 32B 保护带 + canary + 溢出钩子兜住(见 §5.3)。 - **R5(新内容放置判定表)**: @@ -138,7 +140,9 @@ LVGL 集合明细(均在 SRAM):LVGL 池 40,960B(`lv_mem_core_builtin.o` |---|---| | CCM 布局(仅 heap_4.o) | `MDK-ARM/STM32F407-Demo/STM32F407-Demo.sct` | | DMA 阈值 1024B | `Drivers/BSP/GD5F2GQ5UE/gd5f2gq5ue.h`(`GD5F_DMA_THRESHOLD`,公开供测试对齐上下界) | +| DMA 窗口/阈值**编译期**断言 | `Drivers/BSP/GD5F2GQ5UE/gd5f2gq5ue.h`(`gd5f_dma_window_no_ccm_check_t` 窗口不重叠 + `gd5f_dma_threshold_contract_check_t` 阈值契约,负数组 typedef) | | DMA 可达性运行时守卫 | `Drivers/BSP/GD5F2GQ5UE/gd5f2gq5ue.c`(`gd5f_dma_buf_ok`,`gd5f_read_from_cache`/`gd5f_program_load` 两处接入) | +| 静态缓冲落位**启动期**自检 | `Drivers/BSP/GD5F2GQ5UE/nand_ftl.c`(init 期对三块 2KB 页缓冲过 `gd5f_dma_buf_ok`) | | 守卫负路径用例(读/写) | `test/board/gd5f_test_task.c`(TC-GD5F-704/705) | | 错误级日志纳入自动闭环 | `.dsh/skills/serial-boot-test/scripts/verify_log.py` + `.dsh/skills/serial-boot-test/scripts/run_boot_test.py`(默认 Forbidden 含 `[ERR]`,`[FAIL]` 仍拦截断言失败) | | LVGL 越界保护带 32B | `Drivers/BSP/UI/ui_port.c`(`UI_DRAW_BUF_GUARD`) | diff --git a/docs/内存管理/存储栈RAM优化调研.md b/docs/内存管理/存储栈RAM优化调研.md index ba471be9..392a6e5d 100644 --- a/docs/内存管理/存储栈RAM优化调研.md +++ b/docs/内存管理/存储栈RAM优化调研.md @@ -3,7 +3,7 @@ > 版本 v1.1 日期 2026-09-21 作者 AI 辅助调研 > 数据来源:`data/ram_report_20260921.md`(.map 静态占用)+ 逐行代码核验(`nand_ftl.c` / `gd5f2gq5ue.c` / `Lib/dhara` / `Lib/FatFs/ffconf.h` / `lftpd*`) > 定位:`RAM使用分析与优化方案.md` 的存储专项补充;优化项编号 P0/P1/P2,与 `docs/内存管理/RAM使用分析与优化方案.md` §6 的 S 序号在"规划"一节对齐 -> v1.1 清理(2026-09-21 同日):移除已落地的 P0-1 BBT 页缓冲收缩(−1,776B,commit `be1a30a`)与编程回读校验整体移除的实施记录(详见 GD5F TRAP-GD5F-05 / 错误码传播审计),以及过期逐模块基线表;仅保留未实施项与回归要求 +> v1.1 清理(2026-09-21 同日):移除已落地的 P0-1 BBT 页缓冲收缩(−1,776B,commit `be1a30a`)与编程回读校验整体移除的实施记录(详见 GD5F TRAP-GD5F-05),以及过期逐模块基线表;仅保留未实施项与回归要求 ## 1. 现状约束(不变,是下述全部方案的依据) diff --git a/docs/子系统设计/看门狗设计说明.md b/docs/子系统设计/看门狗设计说明.md index e44f28db..1a94e5b7 100644 --- a/docs/子系统设计/看门狗设计说明.md +++ b/docs/子系统设计/看门狗设计说明.md @@ -6,7 +6,7 @@ > **日期**:2026-09-29 创建;**2026-10-10 按当前代码重写正文**(旧正文含大量立项前的 > 描述,已清理;实施史与逐次决策见 §10 与文末变更记录,未删)。 > **相关文档**: -> [`../任务规划/README.md`](../任务规划/README.md)(登记簿未办项 N-17 / S-3 / N-4,见 `../任务规划/登记簿.md`)、 +> [`../任务规划/README.md`](../任务规划/README.md)(登记簿未办项 N-17 / S-3,见 `../任务规划/登记簿.md`)、 > [`运行与自检记录设计说明.md`](运行与自检记录设计说明.md)(复位记录与 `selftest.log`)、 > [`../测试说明/看门狗单元测试说明.md`](../测试说明/看门狗单元测试说明.md)(L0.5 判定状态机单测)、 > [`../测试说明/实机测试实施指南.md`](../测试说明/实机测试实施指南.md)(故障注入台架流程)、 @@ -243,6 +243,16 @@ static HAL_StatusTypeDef iwdg_deferred_start(const IWDG_HandleTypeDef *a_h) { (v 4 个 Fault(Hard/Mem/Bus/Usage)的采集与使能(`fault_handler_enable()` 使能后三者,否则被升级成 HardFault 看不出根因)见 §10.2 与 `AGENTS.md`「平台约束」。 +**`Error_Handler()` 统一降级(2026-10-12)**:CubeMX 弱定义的这个入口旧形态是 `__disable_irq(); while(1){}`——**单处外设初始化失败即整机挂死**,只剩 IWDG 兜底且不留任何自检日志。现改为打 1 条自检 + `NVIC_SystemReset()`,与 4 个 Fault 处理同款自恢复(IWDG 退回"最后兜底"而非唯一出路;复位归因 `SFTRSTF` → 开机日志 `software (NVIC_SystemReset)`,§4.5)。三条实现约束: + +| 约束 | 原因 | +|---|---| +| **不得 `__disable_irq()`** | 日志必须能走串口,关中断 = 现场彻底消失 | +| **`__builtin_return_address(0)` 不可用** | AC5 前端接受但编成库函数调用,MicroLIB 无该符号 → `L6218E`(2026-10-12 台架实证,勿再尝试);ARMCC `__asm` 只有整函数形态且取不到 C 符号(§10.4)。→ **失败点靠"最后一条成功日志"与 `main()` 的 `MX_*_Init` 顺序之间定位**(GPIO→DMA→USART1→SPI2→SPI1→I2C1→UART4→UART5→USART2→USART3→IWDG→ADC1→TIM3→`app_main_init`) | +| **日志路径必须内核前安全** | 调用点全在 `osKernelStart` 之前:控制台写者未注册 → `con_append` 走 `dbg_console_write_raw`(DWT/计数有界轮询,`huart1` 未就绪即返回),`sys_clock_get_str` 未初始化打占位串——全程无内核 API(§4.7 与 `AGENTS.md`「平台约束」同源) | + +成本 = 仅这 1 条日志(≈90 B Flash,见 `AGENTS.md` 日志成本)。重生成存活:函数体在 `USER CODE Error_Handler_Debug` 区(L2,`KeepUserCode` 逐字保留)+ `check_cubemx_survival.py` 审计项兜底(L3)。 + ### 4.5 复位原因锁存与优先级 `RCC_CSR` 的复位标志只由 POR 清除、读后不自动清 → 必须读并锁存后 `SET_BIT(RCC->CSR, RCC_CSR_RMVF)`,否则下次软复位会读回上次残留。 @@ -464,7 +474,6 @@ snapshot == required → miss_streak=0、证据门闩复位、喂狗 |---|---|---| | **N-17** | `time_sync` 30 s 挂起客户端掉线 + 监听重建期间 NET/FTP/UI 心跳缺口 800 ms~1 s(声明 stall 但未复位),**余量仅 ~0.7 s** | 待定位监听重建为何阻塞其它任务;必要时缩短重试 | | **S-3** | USART1 TX-only 结构根治(灭 ORE 僵尸机制) | 是 R3 节奏饥饿的机制根源 | -| **N-4 / C1** | 网络 pending 二期时"看门狗接 `selftest` 留痕" | 二期一并做 | | O1 | 是否需要复位计数与"连续 N 次看门狗复位后进入安全模式" | 当前不做;若现场出现复位风暴再评估 | | O2 | 细粒度复位原因是否上 `run.log`/UI | 倾向不上(避免中文字符集重生成),只保留串口 + `selftest.log` | @@ -617,7 +626,7 @@ enter/exit 段还能读出"此刻仍在段内已多久"——**永久阻塞的 |---|---| | **提高 uiTask 优先级** | 会**掩盖**"心跳依赖他任务"与"忙等"两个真实缺陷;而且它根本不解决"UI 在等 netTask"。缺陷已修完后,提优先级才成为可讨论项 | | 给 `net_*` 加"有界等待"版 | 回执通道是"每任务一个 32 位通知值",超时后**迟到的回执会被下一次调用的零等待拾取当成新结果**(`net_wait_result` 注释里 L7 挂死/整段错位的同款根因)。要做对必须引入请求序号,代价高于搬家 | -| 把推流搬出 uiTask(独立 `uiSinkTask`) | 实测 `uis`=1 µs 而 `uip` 仍 60.9 ms = **LVGL 帧周期地板** → 搬走推流**不会让刷新变快**;收益只剩极端并发下的观感隔离,成本含**必改 `.ioc`** 的重生成与全套回归。若日后成问题,正确方向是先治 netTask 排队公平性(登记簿 N-4 二期),不是搬 UI | +| 把推流搬出 uiTask(独立 `uiSinkTask`) | 实测 `uis`=1 µs 而 `uip` 仍 60.9 ms = **LVGL 帧周期地板** → 搬走推流**不会让刷新变快**;收益只剩极端并发下的观感隔离,成本含**必改 `.ioc`** 的重生成与全套回归。若日后成问题,正确方向是先治 netTask 排队公平性(见 `../网络/Net层阻塞模型架构设计.md`),不是搬 UI | | 把推流塞进 netTask | 零往返零新增 RAM,但共享源码要走 backend 端口(双端同改)、netTask 这个唯一执行者更胖、一个 UI 侧 bug 直接变成整机复位 | --- diff --git a/docs/测试说明/ADC输入层单元测试说明.md b/docs/测试说明/ADC输入层单元测试说明.md index 51b63404..df400a64 100644 --- a/docs/测试说明/ADC输入层单元测试说明.md +++ b/docs/测试说明/ADC输入层单元测试说明.md @@ -13,7 +13,7 @@ > [`相量层单元测试说明.md`](相量层单元测试说明.md)(同批落地的 L2 姊妹篇)、 > [`../硬件平台/ADC通道映射说明.md`](../硬件平台/ADC通道映射说明.md)(**语义映射权威口径**)、 > [`../算法/继电保护功能复现规划.md`](../算法/继电保护功能复现规划.md)(L1-3 / L1-4 设计依据)、 -> [`../问题记录/错误码传播审计.md`](../问题记录/错误码传播审计.md)(E-44 / E-58 快照语义) +> 历史审计 E-44 / E-58(快照语义约定;属主文档已于 2026-10-15 删除,编号仅作指称) --- diff --git a/docs/测试说明/CH395F_Test_Guide.md b/docs/测试说明/CH395F_Test_Guide.md index 3160d8ab..00bd9c6c 100644 --- a/docs/测试说明/CH395F_Test_Guide.md +++ b/docs/测试说明/CH395F_Test_Guide.md @@ -42,7 +42,7 @@ | 6 | 36 | 4 | 2KB | 40 | 2 | 1KB | | 7 | 42 | 4 | 2KB | 46 | 2 | 1KB | -UDP 路径 `net_socket()` 中对 Socket 4~7 的兜底重设使用同一布局。该分配已通过 **Phase 7 TC704(8 Socket 并发回显,40/40 PASS)** 验证。 +UDP 路径 `net_socket()` 中对 Socket 4~7 的兜底重设使用同一布局。该分配已通过 **Phase 7 TC704(5 Socket 并发回显)** 验证。 ### 大文件传输(20KB+)策略 @@ -853,31 +853,31 @@ sequenceDiagram > **实现说明**:TC-NET-703 与 TC-NET-701/702 的 PC 环境互斥(前者要求 PC 服务关闭、后两者要求开启),故固件中 TC-NET-703 由子开关 `PHASE7_TEST_TIMEOUT` 单独启用——默认 `ENABLE_PHASE7_TESTS` 仅跑 701/702;单独开启 `PHASE7_TEST_TIMEOUT` 并关闭 PC 服务后重编,方可执行 703。 -### TC-NET-704: 8-Socket 并发 TCP Client(多 Socket 模式验证) +### TC-NET-704: 5-Socket 并发 TCP Client(多 Socket 模式验证) | 字段 | 值 | |------|-----| | **ID** | TC-NET-704 | | **优先级** | P0 | | **类型** | 功能测试 / 并发测试(NET 层) | -| **标题** | 同时打开 8 个 Socket 连接 PC Server,各自 64B 收发 | -| **前置条件** | 1. PC: `python ./test/host/ch395f_socket_test.py tcp_server --port 8081 --max-conn 8`(并发回显,`--max-conn` 为并发计数)
2. `net_init()` 已置 `FUN_PARA=0x08`,8 个 Socket 缓冲按「CH395F 缓冲架构」分配(每 Socket 接收 2KB + 发送 1KB,MSS=1024)——**缓冲显式分配是本用例的前提**(Socket 可用性不依赖 FUN_PARA)
3. `ENABLE_PHASE7_TESTS` 已定义 | +| **标题** | 同时打开 5 个 Socket 连接 PC Server,各自 64B 收发 | +| **前置条件** | 1. PC: `python ./test/host/ch395f_socket_test.py tcp_server --port 8081 --max-conn 5`(并发回显,`--max-conn` 为并发计数)
2. `net_init()` 已置 `FUN_PARA=0x08`,Socket 缓冲按「CH395F 缓冲架构」分配(每 Socket 接收 2KB + 发送 1KB,MSS=1024)——**缓冲显式分配是本用例的前提**(Socket 可用性不依赖 FUN_PARA)
3. `ENABLE_PHASE7_TESTS` 已定义
4. `PHASE7_MULTI_COUNT=5`(并发上限 = `NET_MAX_SOCKETS`(8) − 3 常驻监听;2026-10-15 由 8 降至此) | **测试步骤与判定标准**: | 步 | 操作 | 成功标准 | 失败标准 | |----|------|----------|----------| -| 1 | 循环 i=0..7:`net_socket()` 创建 8 个 TCP socket(本地端口 51000+i),`net_connect(192.168.1.2:8081)` | 8 个 `net_connect` 均返回 `0` | 任一返回 `-1` | -| 2 | netTask `net_poll()` 推进下,`net_select` 等待全部 8 路可写 | 8 路均在 10s 内 ESTABLISHED | 任一超时未建立 | -| 3 | 每路 `net_send(sockfd, 64B)` → `net_recv(sockfd, 64B)` 校验 | 8 路收发各 64B、字节级比对 + 累加和 `TEST_CHECK` 通过 | 任一收发失败 / 数据不符 / `TEST_CHECK` 失败 | +| 1 | 循环 i=0..4:`net_socket()` 创建 5 个 TCP socket(本地端口 51000+i),`net_connect(192.168.1.2:8081)` | 5 个 `net_connect` 均返回 `0` | 任一返回 `-1` | +| 2 | netTask `net_poll()` 推进下,`net_select` 等待全部 5 路可写 | 5 路均在 10s 内 ESTABLISHED | 任一超时未建立 | +| 3 | 每路 `net_send(sockfd, 64B)` → `net_recv(sockfd, 64B)` 校验 | 5 路收发各 64B、字节级比对 + 累加和 `TEST_CHECK` 通过 | 任一收发失败 / 数据不符 / `TEST_CHECK` 失败 | -**通过标准**:8/8 路全部通过,0 失败。任一失败 → 标记 FAIL +**通过标准**:5/5 路全部通过,0 失败。任一失败 → 标记 FAIL **覆盖原则**:DP-01(多 Socket 并发主路径)、缓冲架构(8×6 块分配,MSS=1024) > **注**:PC 端 `--max-conn` 须为**并发**计数(连接关闭即减),否则 Phase 7 全量(701+702+704 共 12 连接)会按累计计数超限而拒掉 4~7。该用例已随 Phase 7 实测通过(并发回显落在 sock2/4/5/6/7,见 TRAP-NET-15 末)。 > -> **2026-10-11 复跑(N-31,`FUN_PARA=0x08`)**:**Phase 7 34/34 全通过**,5 路并发 ESTABLISHED + 64 B 回显字节级比对 OK,芯片 Socket 实测落在 **sock2/4/5/6/7**(0/1/3 被 FTP/对时/远显三个监听占用)→ 证明 Socket 可用性不依赖 FUN_PARA。两条**前置排雷**(否则会得到全量假 FAIL):① PC 适配器落在 **Public** 防火墙配置文件会静默丢入站 8081,症状是 TC701/702/704 全部 `CONNECT TIMEOUT`;② 残留的旧 `tcp_server` 进程占着 8081,新服务器绑不上端口不报错。另:`PHASE7_MULTI_COUNT` 须 ≤5(E-23 未修,`NET_MAX_SOCKETS=8` 减去三个常驻监听)。证据链见 `问题记录/CH395F_Trap_Records.md` TRAP-NET-15。 +> **2026-10-11 复跑(N-31,`FUN_PARA=0x08`)**:**Phase 7 34/34 全通过**,5 路并发 ESTABLISHED + 64 B 回显字节级比对 OK,芯片 Socket 实测落在 **sock2/4/5/6/7**(0/1/3 被 FTP/对时/远显三个监听占用)→ 证明 Socket 可用性不依赖 FUN_PARA。两条**前置排雷**(否则会得到全量假 FAIL):① PC 适配器落在 **Public** 防火墙配置文件会静默丢入站 8081,症状是 TC701/702/704 全部 `CONNECT TIMEOUT`;② 残留的旧 `tcp_server` 进程占着 8081,新服务器绑不上端口不报错。另:`PHASE7_MULTI_COUNT=5`(2026-10-15 由 8 降至此;`NET_MAX_SOCKETS=8` 减去三个常驻监听)。证据链见 `问题记录/CH395F_Trap_Records.md` TRAP-NET-15。 --- diff --git a/docs/测试说明/实机测试实施指南.md b/docs/测试说明/实机测试实施指南.md index 20d91808..63ba82e6 100644 --- a/docs/测试说明/实机测试实施指南.md +++ b/docs/测试说明/实机测试实施指南.md @@ -5,7 +5,7 @@ > **已完成事项**(S7、A1~A4)的实施细节与证据**不在本文**,见对应模块文档(§10 状态与去向)。 > **前置铁律**:本文所有步骤**均在接上 MCU / 串口 / ST-LINK 之后**执行; > 无实机时**只读本文,不执行任何烧录**。 -> **上游**:`错误码传播审计.md`(E-xx)· `docs/任务规划/README.md`(统一登记簿:P-x / N·S·T-x / +> **上游**:`docs/任务规划/README.md`(统一登记簿:P-x / N·S·T-x / > S1x·A-x / 阶段排序 / 诊断基线)· > `子系统设计/看门狗设计说明.md`(§7 验证记录)。 > **约定**:本文事项完成后**先销上游登记**,再在文末变更记录留一行; @@ -186,11 +186,11 @@ $cli = if ($env:DTU_STLINK_CLI) { $env:DTU_STLINK_CLI } else { (Get-Command ST-L ## 6. 步骤 5:GD5F 错误通道统一(P2-A,S9 = 上游 C2) · ✅ 已闭环 -> **2026-10-03 台架结论**:四要素(分类/透传/分级/留痕)+ 返回类型统一 `int32_t` 已落地(E-10/E-42/E-53/E-54/E-56/E-57 全部销项,见 `错误码传播审计.md`)。 +> **2026-10-03 台架结论**:四要素(分类/透传/分级/留痕)+ 返回类型统一 `int32_t` 已落地(E-10/E-42/E-53/E-54/E-56/E-57 全部销项;历史编号,属主文档已删除)。 > 套件回归:`test/run_all_suites.py --suites gd5f` → **`=== GD5F2GQ5UE Tests: 16/16 PASSED (failed=0) ===`**(只读模式,4 个破坏性用例按设计跳过);编排器 `[GD5F] -> PASS`。 > **顺带修复(编排器缺陷,非本次代码引入)**:`run_all_suites.py` 原对所有套件统一 `--forbidden "HardFault;assert;[ERR]"`,而 GD5F 的 `TC-GD5F-704`(CCM 非 DMA 可达缓冲)会**预期**打出 `[ERR][NAND] RX DMA buffer not DMA-reachable ... fallback poll`(用例 PASS)→ GD5F 套件**永远被判 FAIL**(自 `2055de0` 起)。按 `verify_log.py` 的说明改为**按套件覆盖 `--forbidden`**(GD5F = `HardFault;assert`),修复后 PASS。 -> **来源**:`错误码传播审计.md` 第二档 #1(E-10/E-42/E-53/E-54/E-56/E-57); +> **来源**:第二档 #1(E-10/E-42/E-53/E-54/E-56/E-57,历史编号); > `docs/任务规划/基线与待确认.md` §2.2 四要素骨架(设计基线)。 **现状证据**:`ch395f.c`(1330 行) 与 `gd5f2gq5ue.c`(1496 行) 是同代驱动,但**只有 CH395F** @@ -419,8 +419,8 @@ CH395F / STORAGE / GD5F,未含 RTC;怀疑 RTC 构建下 `testTask` 不被创 | **S7** 自检结果判定 | ✅ 已落地并投用 | `测试说明/自动化测试流程.md`;提交 `4a4cd29` | | **A1** P0-1 `cfg_file` 堆分配改造 | ✅ 已闭环(含真机持久化) | `子系统设计/配置文件设计说明.md`(实施 v1.11 + 2026-10-03 真机验证记录 + 2026-10-04 独立复跑记录) | | **A2** P1-8 网络重建失败恢复 | ✅ 已闭环(正常路径零回归;故障分支见 N-16) | `网络/Net层阻塞模型架构设计.md` | -| **A3** E-34 CH395F 字节命令超时 | ✅ 已闭环(正常路径零回归;故障分支待专项窗口) | `问题记录/错误码传播审计.md` E-34 | -| **A4** E-04 `remo_send` 返回值 | ✅ 已闭环(正常路径零回归;推流压测待专项窗口) | `问题记录/错误码传播审计.md` E-04 | +| **A3** E-34 CH395F 字节命令超时 | ✅ 已闭环(正常路径零回归;故障分支待专项窗口) | —(历史编号,属主文档已于 2026-10-15 删除) | +| **A4** E-04 `remo_send` 返回值 | ✅ 已闭环(正常路径零回归;推流压测待专项窗口) | —(历史编号,属主文档已于 2026-10-15 删除) | | 2026-10-02 台架取证链(IWDG PR/RLR 错配 / dhara resume 回滚 / ORE 僵尸通道) | ✅ 已修 | `子系统设计/看门狗设计说明.md` 2026-10-02 变更记录 · `架构设计/STM32F4-Base存储架构说明.md` §5.4.1 · `子系统设计/控制台单写者日志设计说明.md` §12;提交 `930215f` | > **真机判定汇总**(2026-10-03,本机 MCU,新固件全闭环 `@test` PASS 7/7):A1 ✅ PASS(含配置持久化); @@ -435,8 +435,8 @@ CH395F / STORAGE / GD5F,未含 RTC;怀疑 RTC 构建下 `testTask` 不被创 | S7 自检判定 | —(原复审项,已闭环 2026-10-02) | `测试说明/自动化测试流程.md`(参数已落地,`4a4cd29`) | | V2/V4/V5 | `子系统设计/看门狗设计说明.md` §7 验证记录 | `子系统设计/看门狗设计说明.md` 变更记录(2026-10-03) | | S11 堆告警 | —(原复审项,已闭环 2026-10-03) | `内存管理/RAM使用分析与优化方案.md`(基线更新) | -| S9 GD5F | `问题记录/错误码传播审计.md` 第二档 #1 | `问题记录/GD5F2GQ5UE_Trap_Records.md`、`测试说明/GD5F2GQ5UE_Test_Guide.md` | -| N-2 日志静默 | `docs/任务规划/登记簿.md` N-2 | `问题记录/错误码传播审计.md` E-02 | +| S9 GD5F | —(第二档 #1:历史编号 E-10/E-42/E-53/E-54/E-56/E-57,属主文档已删除) | `问题记录/GD5F2GQ5UE_Trap_Records.md`、`测试说明/GD5F2GQ5UE_Test_Guide.md` | +| N-2 日志静默 | `docs/任务规划/登记簿.md` N-2 | `网络/Net层阻塞模型架构设计.md`(E-02 头阻塞,历史编号) | | P1-8 网络重建 | —(原台账项,已闭环 2026-10-02) | `网络/Net层阻塞模型架构设计.md` | | S5/S4 | —(原复审项,已闭环 2026-10-03) | `测试说明/测试开发标准流程.md` | | 模拟器差距 | `测试说明/模拟器差距清单.md` §3 决策规则 | 本文为"需回真机"项的执行面 | diff --git a/docs/测试说明/时间同步单元测试说明.md b/docs/测试说明/时间同步单元测试说明.md index c5fd3180..46b030df 100644 --- a/docs/测试说明/时间同步单元测试说明.md +++ b/docs/测试说明/时间同步单元测试说明.md @@ -13,7 +13,7 @@ > [`卷锁单元测试说明.md`](卷锁单元测试说明.md)(同批落地的姊妹篇)、 > [`../子系统设计/看门狗设计说明.md`](../子系统设计/看门狗设计说明.md)(`required_mask` / 抽样窗 / 长耗时白名单)、 > [`../任务规划/README.md`](../任务规划/README.md)(**N-17** 登记条目)、 -> [`../问题记录/错误码传播审计.md`](../问题记录/错误码传播审计.md)(**E-02** 头阻塞) +> 历史审计 **E-02**(netTask 头阻塞;属主文档已于 2026-10-15 删除,现状见 [`../网络/Net层阻塞模型架构设计.md`](../网络/Net层阻塞模型架构设计.md)) --- diff --git a/docs/测试说明/模拟器差距清单.md b/docs/测试说明/模拟器差距清单.md index 1168f41c..d3295ce3 100644 --- a/docs/测试说明/模拟器差距清单.md +++ b/docs/测试说明/模拟器差距清单.md @@ -3,7 +3,7 @@ > **用途**:把散落在各子系统文档里的"模拟器无法建模 X"汇成**单一事实源**,回答一个关键问题—— > **模拟器回归通过 ≠ 真机通过,那么差在哪?差多少?哪些必须真机补测?** > **性质**:**登记册**(living document)。新增模拟器后端差异时必须在此登记一行,不得只写在别处。 -> **上游依据**:`docs/任务规划/基线与待确认.md` §2.1(N-4 门禁)。 +> **上游依据**:`docs/任务规划/基线与待确认.md` §2.1(net 层 pending 化状态)。 > **相关**:`UI设计/LVGL模拟器部署与使用说明.md`(部署与使用)、`simulator/README.md`(目录与架构)、 > `测试说明/测试开发标准流程.md`(L3 门禁在测试分层中的位置)。 @@ -40,7 +40,7 @@ | # | 差距 | 模拟器行为 | 真机行为 | 影响 | 回归补测 | |---|---|---|---|---|---| -| N-1 | **无 pending 机制** | `net_accept`/`net_send` 直接失败或阻塞于宿主 socket;无"挂起队列"概念 | 异步 TCP 状态机,`accept`/`send` 可处于 pending,由 `net_poll` 推进 | **E-06**(accept 轮询计时失真)、**E-20**(lftpd 控制通道 errno 分类)、**E-21**(timeSync errno 分级)等在模拟器上无法复现 | `@test` + FTP 套件;N-4(ACCEPT pending 化)专门建模 | +| N-1 | **无 pending 机制** | `net_accept`/`net_send` 直接失败或阻塞于宿主 socket;无"挂起队列"概念 | 异步 TCP 状态机,`accept`/`send` 可处于 pending,由 `net_poll` 推进 | **E-06**(accept 轮询计时失真)、**E-20**(lftpd 控制通道 errno 分类)、**E-21**(timeSync errno 分级)等在模拟器上无法复现 | `@test` + FTP 套件;ACCEPT 决定不 pending 化(2026-10-15),不再专门建模 | | N-2 | **代次号恒为 0** | `net_get_generation()` 恒返 0(无"网络重建"概念) | 每次 `net_stack_bring_up` / 重配置递增,各 TCP 服务据此重建监听 | "网络重建后服务是否重建监听" 的逻辑**在模拟器上恒走同一分支** | `@test` 网络重配置用例(UI 改 IP 免重启生效) | | N-3 | ~~`net_recv_to` 缺失~~(**已消除**:sim 以 `GetTickCount64` + `Sleep(1)` 轮询兑现预算,含 -2 返回码与 `timeout_ms=0` 拒 INVAL);`net_process_messages` 仍缺失 | `net_recv_to` 已实现(包装层模拟);消息泵未实现(MCU 专属:CH395F 消息队列驱动) | 由 `netMsgQueue` 串行化 SPI 事务、支持毫秒级超时接收 | 依赖"消息泵"的上层逻辑在模拟器上**不可编译/不可运行**;带超时接收的**外部语义**已可双端共测,但预算兑现精度为 Sleep(1) 粒度 | 板端 CH395F / NET 套件 | | N-4 | **Socket 失效语义不同** | 以 `in_use` 判活(`net_socket_sim.c` 设计要点 4) | 监听 Socket 随客户端一同失效,由 net 层 `auto_relisten` + 对账形态③④自动重建(无手动 `net_relisten`) | "客户端断线 → 监听失效 → 重连" 的黏滞状态在模拟器上不出现 | `check_remo_sink.py` 覆盖重连;真机 `@test` | @@ -101,7 +101,7 @@ - **何时登记**:新增/修改 `simulator/sim/` 下任何后端、或发现"某逻辑模拟器走了不同分支"时,**必须**在 §2 加一行。 - **格式**:沿用本表列(差距 / 模拟器行为 / 真机行为 / 影响 / 回归补测)。 - **联动**:若该差距影响 L3 门禁的可信度,须同步在 `UI设计/UI_自动化测试流程.md` 引用本清单。 -- **收敛**:当某差距被 N-4 之类的工作**消除**时,不删行——改标 ~~删除线~~ + 注明消除日期与提交,保留"曾经差在哪"的历史。 +- **收敛**:当某差距被后续工作**消除**时,不删行——改标 ~~删除线~~ + 注明消除日期与提交,保留"曾经差在哪"的历史。 --- diff --git a/docs/测试说明/链路延迟测试说明.md b/docs/测试说明/链路延迟测试说明.md index 6c679fce..5214d57f 100644 --- a/docs/测试说明/链路延迟测试说明.md +++ b/docs/测试说明/链路延迟测试说明.md @@ -625,7 +625,7 @@ sock4 auto-relisten ok / relisten retry ok ← 监听器随后自行恢复 维护注意:队列中长 SEND/CLOSE 消息处理期间,pending 完成顺延至下轮 tick(~1ms 级;长 SEND stall 期间最多顺延相应时长)——已知代价,勿"优化"回执行器内等待。 -### 8.4 相关既有缺陷(会掩盖/放大延迟,见 `docs/问题记录/错误码传播审计.md`) +### 8.4 相关既有缺陷(会掩盖/放大延迟;编号为历史审计编号,属主文档已删除,仅作指称) | 编号 | 位置 | 与延迟的关系 | |---|---|---| @@ -766,7 +766,6 @@ sock4 auto-relisten ok / relisten retry ok ← 监听器随后自行恢复 | UI 设计架构(离屏渲染 + 推流) | `docs/UI设计/UI_设计架构.md` | | BSD Socket API 与双端后端 | `docs/网络/BSD_Socket_API_使用指南.md` | | CH395F 陷阱史(TRAP-NET-09/-16 等) | `docs/问题记录/CH395F_Trap_Records.md` | -| 错误码传播审计(E-02 / E-04 / E-33 / E-34) | `docs/问题记录/错误码传播审计.md` | | CH395F 测试套件(吞吐 Phase 8 等) | `docs/测试说明/CH395F_Test_Guide.md` | | RAM 预算(`ui_net_sink` 6.4KB 常驻) | `docs/内存管理/RAM使用分析与优化方案.md` | | 串口助手(抓日志排查) | `docs/测试说明/串口助手使用指南.md` | diff --git a/docs/编号规范.md b/docs/编号规范.md index 20c3ce89..550bd312 100644 --- a/docs/编号规范.md +++ b/docs/编号规范.md @@ -53,7 +53,7 @@ | **P-2** | **编号不携带语义**:严重度、状态、日期、负责人、波次一律是表格列 | 降级只改列,编号永不变、引用永不断(`P1-x` 的历史教训) | | **P-3** | **一个前缀只有一个属主登记处**;前缀一经分配**永不复用给别类**,号段用尽即扩位(`N-99`→`N-100`),绝不换字母 | 保证"看到前缀就知道去哪张表查" | | **P-4** | **形态唯一**:`<前缀>-<序号>`;前缀 1~3 位大写字母;序号位数按类别固定并左补零;子项只允许一级 `-<小写字母>`。**禁止**裸形(`S3`)、空格形(`Trap 07`)、中文形(`陷阱 1`)、圈号(`①②③④`) | 连字符与补零不得承载语义;字面唯一才能被机器判定 | -| **P-5** | **只增不复用**;每个类别有「下一个可用号」游标(`[计划] scripts/id_registry.json`),新增先取游标 | 并行任务与多文档同时新增时防撞号(`docs/问题记录/项目审查与优化建议报告.md` 变更记录自述"并轨时未做全局唯一性检查"就是没有游标) | +| **P-5** | **只增不复用**;每个类别有「下一个可用号」游标(`[计划] scripts/id_registry.json`),新增先取游标 | 并行任务与多文档同时新增时防撞号(《项目审查与优化建议报告》与登记簿并轨时 `N-16` 撞过号,就是没有游标——见 `docs/任务规划/README.md` §0) | | **P-6** | **引用必须机器可判定**:跨文档引用用行内代码,首次出现紧邻属主文档名;文档内编号**必须**带文档名 | 与 `§N.M` 同一条路:能校验的约定才算存在 | | **P-7** | **编号不语义,引用必语义**:编号本体保持无语义并冻结(同 P-2);但**正文引用必须带语义括注**——`TRAP-NET-18(异常释放后排空 RX 残渣)`、`docs/网络/网络自愈架构设计.md` §4.6(应用主动拆会话保监听);**禁止裸 ID 单独承担信息**。语义放括注,事实变了只改括注、编号不变 | 读者不该为看懂一句话去跳三篇文档。可读性由**引用方式**解决,不由**编号命名**解决——命名带语义会重蹈 `P1-x` 覆辙(§1.2)。落地见 §5.6 | @@ -92,7 +92,7 @@ | `FR-[A-Z0-9]+-\d{2}` | 功能需求 | `docs/产品需求/产品需求设计文档.md` §5 | 各域独立递增;**新增域写进该文 §1.2 术语与缩写** | | `NFR-[A-Z0-9]+-\d{2}` | 非功能需求(**新建**,现 §7 全是散文无号) | `docs/产品需求/产品需求设计文档.md` §7 | 从 `NFR-<域>-01` 起 | | `N-\d{2}` | **登记簿条目**(技术债 / 长期观察 / 硬件改版项;含义由"网络项"重定义) | `docs/任务规划/登记簿.md` | **曾用**最大 `N-33`(`N-30`~`N-33` 均已销项、登记簿行已删,**现存至 `N-29`**)→ 游标 `N-34`(号不回收);原 `S-x`/`T-x` 并入本族(§7.1) | -| `E-\d{2}` | 错误码/返回值传播审计发现 | `docs/问题记录/错误码传播审计.md` | **冻结**:`E-63` 为最大号,**不再新增**;新发现一律 `DEF` | +| `E-\d{2}` | 错误码/返回值传播审计发现 | 无(原属主 `docs/问题记录/错误码传播审计.md` 已于 2026-10-15 删除) | **冻结**:`E-63` 为最大号,**不再新增**;新发现一律 `DEF`;现存引用仅作历史指称(§4.3) | | `E\d+` | **自愈实验编号**(N34 台架实验,**无连字符**) | `docs/网络/网络自愈架构设计.md` §5.2 | **已封闭**:`E0`~`E15`,**不再新增**;原始实验记录已归档(该文档 §12)。⚠ 与上一行靠**连字符**区分:带连字符 = 审计发现 | | `TC-[A-Z]{1,4}-\d{3,4}` | 测试用例 | 各测试文档 | 3 位起;分阶段套件序号 = 阶段号×100 + 项号(阶段 ≥10 自然进位 4 位,`TC-NET-1001` 合规);后缀只允许 `-<小写字母>` | | `TM-[A-Z]{1,4}-\d{2}` | 测试阶段 | 各测试文档 | 模块码取 §6 统一表(`TM-PHY-01` → `TM-NET-01`) | @@ -118,12 +118,29 @@ | `RUN--` | 台架轮次 | `round 1~9` | `docs/测试说明/实机测试实施指南.md` | 日期制 | | `MS-` | 里程碑 | `M1~M3` | 波次文件 | `MS-01` | | `RC-` | net 周期对账类型 | `①②③④⑤`(唯一被代码逻辑引用的圈号链) | `docs/网络/网络自愈架构设计.md` | `RC-01`~`RC-05` | -| `PAT-` | 缺陷模式(九类) | `docs/问题记录/错误码传播审计.md` §1.1 的 `P1~P9` | 同文 | `PAT-01`~`PAT-09` | +| `PAT-` | 缺陷模式(九类) | 原 `docs/问题记录/错误码传播审计.md` §1.1 的 `P1~P9`(该文档已删除,定义迁至本文档 §4.2.1) | 本文档 §4.2.1 | `PAT-01`~`PAT-09` | | `ST-` | 专项内部实施阶段(**文档内编号**) | 保护规划 §7.1 的 `P0~P5`、方案阶段 `P0~P3` | 各专项文档 | 各文档自 `ST-0` | | `UL-` | UI 标准化四层(**文档内编号**) | 报告 UI 四层 `L0~L3` | `docs/UI设计/UI_设计架构.md` | `UL-0` | | `LAT-` | 链路延迟分段(**文档内编号**) | `docs/测试说明/链路延迟测试说明.md` 的 `L1~L11` | 同文 | `LAT-01` | | `CS-` | CubeMX 定制存活三层 | `AGENTS.md` 的 `L1 写进 .ioc → L2 USER CODE → L3 审计` | `docs/子系统设计/看门狗设计说明.md` §10 | `CS-1`~`CS-3` | +#### 4.2.1 缺陷模式定义(`PAT-01`~`PAT-09`) + +2026-10-15 由已删除的 `docs/问题记录/错误码传播审计.md` §1.1 迁入(原编号 `P1`~`P9`,与发现编号 `E-xx` 区分)。 +新发现归入 `DEF-` 时,"缺陷模式"列仍按本表取值: + +| 模式 | 名字 | 一句话 | 典型症状 | +|---|---|---|---| +| **PAT-01** | 值拷贝队列陷阱 | 消息体按值拷进队列,netTask 修改的是**副本**,调用方看不到 | 回传字段恒为初始值 | +| **PAT-02** | 共享全局错误变量竞态 | 全局 `errno` 被多任务交错覆写 | 报出别人的错误码 | +| **PAT-03** | 返回值与错误码混同 | "0 = 成功"其实是"命令已发出";成功路径不清 errno | 失败被当成功 | +| **PAT-04** | 返回值未检查 | `HAL_*` / 库函数返回被丢弃 | 失败静默继续 | +| **PAT-05** | 事件标志语义 | 状态位被当作命令位消费掉 | 等待者永不唤醒 | +| **PAT-06** | 通知值宽度与符号 | 32 位通知值装不下 / 符号位被当成数据 | 大长度变负数 | +| **PAT-07** | ISR 与跨上下文共享状态 | ISR 写、任务读,无同步 | 读到半包 / 撕裂 | +| **PAT-08** | 外设 / 库特定约定 | 该外设 / 该移植版特有的规则 | 照抄上游示例即错 | +| **PAT-09** | 超时与错误混同 | 超时用同一个码表达,调用方无法区分 | 无法复位 / 无法重试 | + ### 4.3 冻结前缀(历史快照,正文不回改) | 冻结对象 | 处置 | diff --git a/docs/网络/BSD_Socket_API_使用指南.md b/docs/网络/BSD_Socket_API_使用指南.md index 3a9b435e..7ee9270d 100644 --- a/docs/网络/BSD_Socket_API_使用指南.md +++ b/docs/网络/BSD_Socket_API_使用指南.md @@ -440,6 +440,9 @@ void tcp_client(void) ## 6. UDP 编程 +> **本项目不使用 UDP(2026-10-15 定案)**:UDP API 保留为 BSD socket 完整性,仅**单任务独占期**可用 +> (`net_sendto` / `net_recvfrom` 直碰 SPI、不经队列,见 §2 三例外);本平台无 UDP 消费者。 + ### 6.1 UDP Client ```c diff --git a/docs/网络/BSD_Socket_API_函数调用关系.md b/docs/网络/BSD_Socket_API_函数调用关系.md index f8001a68..403629e5 100644 --- a/docs/网络/BSD_Socket_API_函数调用关系.md +++ b/docs/网络/BSD_Socket_API_函数调用关系.md @@ -449,7 +449,6 @@ sequenceDiagram - `docs/网络/网络自愈架构设计.md` —— **监听生命周期权威设计**(边沿→电平、单一所有者重建 = `net_reconcile_tick` 形态③④、`net_listener.c` / `net_events.c` 模块化边界 §11) - `docs/问题记录/CH395F_Trap_Records.md` —— TRAP-NET-01~23 机制细节(本文引用处) - `docs/网络/Net层阻塞模型架构设计.md` —— 阻塞等待模型的现状架构设计(pending 请求模型 / 五路完成语义) -- `docs/问题记录/错误码传播审计.md` —— E-02 头阻塞与错误码回传全审计 - `docs/测试说明/链路延迟测试说明.md` —— 芯片 / 队列 / 挂起 recv 等分段时延基线与自愈路径的台架证据 - `docs/子系统设计/网络配置说明.md` §4 —— 代次号与网络重建时序 - `docs/测试说明/CH395F_Test_Guide.md` —— 单连接模型测试规则(TC-NET-6xx) diff --git a/docs/网络/Net层阻塞模型架构设计.md b/docs/网络/Net层阻塞模型架构设计.md index 64fb8560..3fcaf50a 100644 --- a/docs/网络/Net层阻塞模型架构设计.md +++ b/docs/网络/Net层阻塞模型架构设计.md @@ -10,7 +10,6 @@ > `docs/网络/BSD_Socket_API_函数调用关系.md`(调用链与执行域)、 > `docs/网络/网络自愈架构设计.md`(周期对账与监听生命周期——与本模型的分工见 §2.4;模块化边界见该文档 §11)、 > `docs/测试说明/链路延迟测试说明.md`(延迟基线与故障对照)、 -> `docs/问题记录/错误码传播审计.md`(E-02 队头阻塞审计)、 > `docs/任务规划/基线与待确认.md` §2.1(ACCEPT 挂起化设计基线) ## 目录 @@ -53,7 +52,7 @@ netTask 是**全部 socket 操作的唯一执行者**——CH395F 的 SPI 总线 | B 有界分片轮询(`net_recv_to` 分片) | ≤ `timeout_ms` | 分片间隙可用 | ⚠️ 止血:仍周期性占用,调用方须自管总预算 | | C **挂起登记(本模型)** | 微秒级(登记一次) | 照常服务整个队列 | ✅ 结构性消灭 | -三条路线的量化对照见 `docs/问题记录/错误码传播审计.md` E-02。本模型选 C;路线 B 的入口 +三条路线的量化对照见历史审计 E-02(头阻塞;属主文档已于 2026-10-15 删除,编号仅作指称)。本模型选 C;路线 B 的入口 `net_recv_to` **仍在 API 面上**,定位是**调用方显式预算**(§6.2、§8),与挂起登记互补。 > **send 是同一问题的第二个面**:`net_send` 写入 CH395F 发送 FIFO 后要等 `send_ready` @@ -636,9 +635,9 @@ net_pending_capture_owner() ← 捕获执行者 | 项 | 现状 | 说明 | |---|---|---| | **SEND 路径**(执行者占用问题)✅ **已纳入本模型** | `net_send_locked` 快路径有界自旋 ≤ `NET_SEND_SPIN_MS`(2 ms);超预算登记挂起 send,由 `net_send_pending_tick` 按主循环节拍分块推进(`NET_SEND_CHUNK_MAX` = 1 KB/块),调用方在自己上下文睡 | 停滞期 netTask **不再冻结**:挂起 send 每轮至多写一块,`net_pending_tick` 与消息队列照常推进。健康链路块间等待 med 219 µs ≪ 2 ms,快路径行为不变。`NET_SEND_TIMEOUT_MS` 是**无进展**超时(每次成功写块刷新,见 §5.5) | -| **ACCEPT 路径未纳入**(调用方轮询问题) | `net_accept_locked` 单次 `net_poll` 后**立即**返回 `ESTABLISHED` 或 `WOULDBLOCK`——**不占用 netTask**(0 停滞) | 代价不在执行者,而在**调用方轮询**:没有"阻塞到有连接"的入口,各服务(`ui_net_sink` / `iec104_serv` 100 ms / `time_sync` / `lftpd`)各自手写轮询循环,每轮一次完整 RPC 往返(入队 + 通知 + 唤醒),并带来一个轮询周期级的 accept 延迟抖动。pending 化的收益是**消除轮询 churn 与抖动**,不是解队头阻塞 | +| **ACCEPT 路径决定不纳入**(调用方轮询问题;2026-10-15 决策) | `net_accept_locked` 单次 `net_poll` 后**立即**返回 `ESTABLISHED` 或 `WOULDBLOCK`——**不占用 netTask**(0 停滞) | 代价不在执行者,而在**调用方轮询**:没有"阻塞到有连接"的入口,各服务(`ui_net_sink` / `iec104_serv` 100 ms / `time_sync` / `lftpd`)各自手写轮询循环,每轮一次完整 RPC 往返(入队 + 通知 + 唤醒),并带来一个轮询周期级的 accept 延迟抖动。pending 化的收益是**消除轮询 churn 与抖动**,不是解队头阻塞 | | **超时精度** | 随主循环节拍(~1ms),被长操作占用时顺延 | 文档化、不修;发送停滞不再顺延整层(send 已在模型内),顺延面只剩单次长操作本身 | -| **挂起超时留痕探针** | 编译期选项 `NET_PENDING_TRIPWIRE` 默认关(排查挂死时开);未接自检 selftest | 待办见 `docs/任务规划/登记簿.md` N-4 | +| **挂起超时留痕探针** | 编译期选项 `NET_PENDING_TRIPWIRE` 默认关(排查挂死时开);未接自检 selftest | 可选改进,随窗口(无排期) | | **模拟器不可建模** | `net_socket_sim.c` 单线程 Winsock 无法复现**挂起模型内部机制**(外部语义已由包装层按 blocking 属性/预算模拟) | 挂起表 / tick / 通知路径在模拟器上零覆盖,只能硬件验证(§7.4) | | **单 fd 单挂起** | 每 fd 至多一个挂起 recv,第二个被 `BUSY` 拒绝 | 由登记守卫保证;同 fd 并发 recv 本就无意义(per-fd 串行语义) | @@ -648,8 +647,7 @@ net_pending_capture_owner() ← 捕获执行者 > (§5.5、§7.1 send 组); > - ACCEPT 是**调用方轮询**问题:执行者本就零占用,"accept 慢"来自调用方轮询周期, > 不是 netTask 卡住——pending 化换的是 RPC 开销与延迟抖动,**不解决任何队头阻塞**。 -> 故 ACCEPT 优先级低于 SEND,**尚未落地**(登记项见 `docs/任务规划/登记簿.md` N-4, -> 设计基线见 `docs/任务规划/基线与待确认.md` §2.1)。 +> 故 ACCEPT 优先级低于 SEND,**决定不挂起化(2026-10-15)**——保留调用方轮询。 --- diff --git a/docs/网络/网络自愈架构设计.md b/docs/网络/网络自愈架构设计.md index 502b2857..61317c14 100644 --- a/docs/网络/网络自愈架构设计.md +++ b/docs/网络/网络自愈架构设计.md @@ -29,9 +29,10 @@ > ⚠ **字母 `E` 有两套编号,靠连字符区分**: > - `E0`~`E19`(**无**连字符)= 本文的**自愈实验**编号,逐条见 §5.2; -> - `E-02`(**带**连字符)= `docs/问题记录/错误码传播审计.md` 的**审计发现**编号—— +> - `E-02`(**带**连字符)= **历史审计发现**编号(属主文档已于 2026-10-15 删除, +> 编号冻结于 `docs/编号规范.md` §4.3)—— > §5.2 末行引用它是为了记录该发现的**收口台架**,**不是**实验编号 -> (证据:`test/host/e02_probe.py` 文档串自述"错误码传播审计.md E-02 的 timeSync 触发形态"); +> (证据:`test/host/e02_probe.py` 文档串自述"历史审计 E-02 的 timeSync 触发形态"); > - 该审计编号族冻结于 `E-63`,不再新增。 ## 1. 为什么是这个架构 @@ -585,8 +586,8 @@ DISCONNECT 会被 `(void)` 丢掉。但 RECV_OK 是条件位(下轮补回) > ⚠ **这些编号是冻结标识**:`net_socket.c` / `net_events.c` / `net_listener.c` 注释、 > `test/host/e02_probe.py`、`test/host/e6_soak.py`、`docs/问题记录/CH395F_Trap_Records.md` > 都直接引用它们,**重排或改名会断链**。 -> ⚠ 末行 `E-02` **不是实验编号**——它是 `docs/问题记录/错误码传播审计.md` 的**审计发现**编号 -> (`E-` 族,冻结于 `E-63`),列在此处只为记录该发现的**收口台架**。 +> ⚠ 末行 `E-02` **不是实验编号**——它是**历史审计发现**编号 +> (`E-` 族,属主文档已于 2026-10-15 删除,冻结于 `E-63`),列在此处只为记录该发现的**收口台架**。 > 区分规则:**带连字符 = 审计发现,无连字符 = 自愈实验**。 | 编号 | 验证了什么(方法) | 结论(现状口径) | 落点 | diff --git a/docs/问题记录/CH395F_Trap_Records.md b/docs/问题记录/CH395F_Trap_Records.md index 119e683d..0ce7e7e9 100644 --- a/docs/问题记录/CH395F_Trap_Records.md +++ b/docs/问题记录/CH395F_Trap_Records.md @@ -285,7 +285,7 @@ CH395F 以太网驱动与 NET 层的实证陷阱:现象、定案根因、修 - **能真正掐断链路的手段**:`Disable-NetAdapter`(链路真 down)。⚠ 该 cmdlet **只有 `-Name`/`-InterfaceDescription`**,没有 `-InterfaceIndex`;网卡名是中文,用 `(Get-NetAdapter -InterfaceIndex N).Name` 取值再传入;禁用/恢复必须写在 `try/finally` 里。 - **仍未覆盖的场景**:"链路仍在、对端静默"(NAT 会话过期、中间节点黑洞)没有安全注入手段(静态 ARP 黑洞被交换机泛洪兜底、删 IP 连带拆掉本机连接)——E11 用禁用网卡的近似注入完成定案。 - **主机侧回显服务器收不到入站连接 → 全量 `CONNECT TIMEOUT`**:本机对 `C:\programdata\anaconda3\python.exe` 有 Public 入站 Block 规则(`C:\python314\python.exe` 才是 Allow),而 `.venv\Scripts\python.exe` 的基座解释器正是 Anaconda python。Windows 防火墙**块规则优先于放行规则** → 中途加 Allow 规则**完全无效**,只有 `Set-NetFirewallProfile -Name Public -Enabled False` 才通。症状极具误导性:芯片 ~1.6s `CONNECT TIMEOUT`、`net_connect` 仍返 0、`[CONN] openOK` 照常打印,看着像芯片故障,实为主机防火墙。固定跑法:`try { 关 Public → 复跑 } finally { 开 Public }`。 -- **Phase 7 两条既有失败与改动无关,别追**:① TC704 `socket create #5` FAIL = E-23(`NET_MAX_SOCKETS=8` 减 3 常驻监听 → 并发上限 5,而 `PHASE7_MULTI_COUNT` 仍是 8);② TC703 要求 PC 服务器**离线**,与 701/702/704 环境互斥,同轮连跑必有一侧失真。 +- **Phase 7 两条既有失败与改动无关,别追**:① TC704 `socket create #5` FAIL = 并发上限 5(`NET_MAX_SOCKETS=8` 减 3 常驻监听)而 `PHASE7_MULTI_COUNT` 曾是 8(该配置已于 2026-10-15 降为 5);② TC703 要求 PC 服务器**离线**,与 701/702/704 环境互斥,同轮连跑必有一侧失真。 - **对照组必须从干净态起测**:上一腿若**没死透**就重启下一腿,对照组失真。每个样本前 `stlink_flash.py reset` + 等 12s 干净启动,并取多样本再比均值。 - **`[PROF]`/`[PROF2]` 字段不是计数器,别拿来判"任务卡死"**:`netp` = `net_poll` 段最长耗时(µs)、`netn` = 采样瞬间在段内时长(`App/util/task_prof.c`)→ 数值恒定只说明"没刷新最大值"。真正的停摆信号是 `[ERR][WD] task stall`;其 `miss=` 打印的是**累计**数(连续数封顶在容忍值 8),容忍 = 8 轮×100ms = 连续 800ms 有任务缺席即停喂、IWDG 2.5s 溢出(`App/util/task_watchdog.c`)。缺席位图解码:`req=0x77` 为 DEFAULT/NET/FTP/UI/TIMESYNC/ADC,`mask=0x41` → 缺席 NET+FTP+UI+TIMESYNC = "netTask 卡住、所有做 `net_*` 的任务阻塞在队列+通知后面"的指纹。 - **静默黑洞用临时探针抓状态,测完即删**:B10 的孤儿态在日志里毫无痕迹,靠对账跳过分支加一条 `TEMP orphan …` 才直接看到;同一轮还顺手用探针**否掉了自己的第一假设**。假设写盘前先测,比推理便宜。 diff --git a/docs/问题记录/错误码传播审计.md b/docs/问题记录/错误码传播审计.md deleted file mode 100644 index 043165c4..00000000 --- a/docs/问题记录/错误码传播审计.md +++ /dev/null @@ -1,634 +0,0 @@ -# 错误码传播审计报告(全项目) - -> **模块名称**:全项目错误码 / 返回值 / 状态传播审计 -> **文档目的**:以 2026-09-21 网络错误码事故(errno 被并发覆写 → 远端显示误判断连)为起点, -> 普查全项目的"错误信息传递"链路:哪些机制会把**成功**报给调用方而实际已失败、哪些通道会让 -> 错误码**丢失 / 串扰 / 永远到不了**,并按 P0/P1/P2 排出修复优先级。 -> **本文档只读审计,不修改任何代码。** -> -> **首版**:2026-09-21(发现 59 项) -> **本版**:2026-09-25 重写 —— ① 已闭环项从正文移除,只在 §2 留一行索引;② 全部未闭环项 -> **逐项回到当前代码复核**;③ 新增本轮复核发现。 -> **代码基线**:`v2-on-ui` @ `9a8255f`(2026-09-24),复核日期 2026-09-25 -> **配套文档**:`docs/任务规划/README.md`(N-2/N-4 登记项)、`docs/网络/Net层阻塞模型架构设计.md`(E-02 架构现状)、 -> `docs/问题记录/*_Trap_Records.md`(模块陷阱史)、`AGENTS.md`(已固化铁律) - ---- - -## 0. 结论速览 - -**当前未闭环 16 项:P1×1、P2×15。无 P0。** - -> **2026-10-10 追记(E-59 门禁的 TPAFE 前置项全部闭环)**:**E-44 / E-45 / E-58** 已修复并经真机验证 -> (`tpafe5160` 驱动:FRSTDATA 数据有效性校验 + 帧序号取代就绪电平标志 + 原子快照 API; -> 旧的 `get_buf`/`data_ready`/`clear_ready` **删除**,全仓库零调用者)。未闭环 17 → **16**(P1 2→1), -> 已闭环索引 41 → **44**(§2.2)。门禁本身已固化进 `AGENTS.md`;**E-59 仍未闭环**(消费者仍是空桩, -> 且 RS485 侧 E-50 / E-49 未修、两任务栈预算未做)。 - -> **2026-10-04 订正(计数同步)**:2026-10-03 的 **E-34 / E-04**(第一档)与 -> **GD5F 错误通道统一(E-10 / E-42 / E-53 / E-56 / E-57,第二档 #1)** 已修复并台架验证, -> 加上 **E-60**(2026-09-21 订正),未闭环数由 25 降为 **17**(P1 5→2、P2 20→15); -> 已闭环索引同步为 **41** 项(§2)。本次只订正计数与索引,审计结论未变。 - -> **2026-10-02 追记(L0.5 单测新发现,已修复)**:**E-63(P0)** —— -> `App/cfg/cfg_file.c` 外科手术保存的 3 处 `s_encode(...) == 0` 判定恒假 -> (该函数成功返回**文本长度**而非 0)→ **受管值改写路径沦为死码**: -> 对已存在的 `/config.ini` 保存时受管键**从不被改写**(静默不落盘), -> 而 `net_cfg_save()` 仍返回 0 并打 `save: OK`。由本日新增的 `cfg_file` -> 主机侧单测(S1-b)首跑抓出,已修复并通过 `@build` 0/0。**已闭环**, -> 明细见 §2.1。 - -首版 59 项中 **44 项已闭环**(索引见 §2),其中 **4 项 P0 全部修复并在位**;**16 项未闭环** -(见 §3),另 **1 项回归监督**(E-62,无需改动、仅防后人误改)。剩余问题分三类: - -| 类别 | 数量 | 性质 | 处置 | -|---|---|---|---| -| **A. 待处理的实质缺陷** | 4 | 有明确后果,需改代码 | §3.1,第一 / 二档 | -| **B. 契约缺口(随消费者接入触发)** | 7 | 当前调用者"恰好不会踩",但契约没写死 | §3.2,接消费者前必修 | -| **C. 一致性 / 可维护性** | 5 | 无功能影响或影响极小 | §3.3,第三档 | - -**最值得先动的两件事**(2026-10-10 更新——原列的 **E-58 已闭环**,**E-34 / E-04 已闭环**,见 §2.2): - -1. **E-59(P1,元发现)**——`rs485_task` 仍为空桩(`adc_task` 已实现,B1 2026-10-10)。TPAFE 侧契约缺陷 - (**E-44 / E-45 / E-58**)已于 2026-10-10 **全部修复**,RS485 侧 **E-50 / E-49 仍未修**; - 门禁已固化进 `AGENTS.md`,接消费者前仍欠"栈预算 + RS485 双缓冲/序号"。 -2. **E-50(P2)**——RS485 单缓冲无 ISR/任务同步 → 读出跨两帧拼接的数据;与 E-59 同族, - 属"接 RS485 消费者前必修"(§5 第三档前置条件)。 - -> **2026-09-25 变更**:**E-03 已修复**(错误码改按任务隔离,见 §3.1)。 -> 它是本次审计中唯一"并发条件下会说谎"的通道,且 N-4(SEND/ACCEPT pending 化)不覆盖它,故独立小修先行。 -> -> **2026-10-03 变更**:**E-34 / E-04 已修复并通过真机验证**(CH395F 字节命令有限超时 + `remo_send` 返回值校验;见 §5 第一档)。 -> **2026-10-03 变更②**:**GD5F 错误通道统一(E-10 / E-42 / E-53 / E-54 / E-56 / E-57)已修复并通过台架套件回归**(`=== GD5F2GQ5UE Tests: 16/16 PASSED (failed=0) ===`,只读模式;见 §5 第二档 #1)。 -> 真机全闭环 `@test` PASS 7/7;执行判据见 `docs/测试说明/实机测试实施指南.md` §3 / §10。 - ---- - -## 1. 审计方法与口径 - -### 1.1 九类缺陷模式 - -所有发现按这九类归类,编号 P1~P9(与发现编号 E-xx 区分): - -| 模式 | 名字 | 一句话 | 典型症状 | -|---|---|---|---| -| **P1** | 值拷贝队列陷阱 | 消息体按值拷进队列,netTask 修改的是**副本**,调用方看不到 | 回传字段恒为初始值 | -| **P2** | 共享全局错误变量竞态 | 全局 `errno` 被多任务交错覆写 | 报出别人的错误码 | -| **P3** | 返回值与错误码混同 | "0 = 成功"其实是"命令已发出";成功路径不清 errno | 失败被当成功 | -| **P4** | 返回值未检查 | `HAL_*` / 库函数返回被丢弃 | 失败静默继续 | -| **P5** | 事件标志语义 | 状态位被当作命令位消费掉 | 等待者永不唤醒 | -| **P6** | 通知值宽度与符号 | 32 位通知值装不下 / 符号位被当成数据 | 大长度变负数 | -| **P7** | ISR 与跨上下文共享状态 | ISR 写、任务读,无同步 | 读到半包 / 撕裂 | -| **P8** | 外设 / 库特定约定 | 该外设 / 该移植版特有的规则 | 照抄上游示例即错 | -| **P9** | 超时与错误混同 | 超时用同一个码表达,调用方无法区分 | 无法复位 / 无法重试 | - -### 1.2 严重度定义 - -| 级别 | 含义 | 判据 | -|---|---|---| -| **P0** | 生产可触发的**静默数据丢失** | 无需特殊工况,正常运行即出错,且无任何告警 | -| **P1** | 故障条件下的潜在陷阱 | 触发需特定时序 / 工况 / 参数,一旦触达后果重(死锁、数据损坏、整机不可用) | -| **P2** | 健壮性 / 一致性 / 可维护性 | 当前路径无实际后果,或仅影响排障与后续维护 | - -### 1.3 静态代码 ≠ 可触发缺陷 - -本项目的硬口径:**只有"生产路径 + 可复现"才算 P0/P1**。多条首版 P0/P1 在本轮复核中被降级, -原因就是"静态上确实存在,但当前没有任何调用者能走到": - -- 共享全局 errno 竞态 → 唯一的错误消费者(`ui_net_sink`)**不读 errno、直接看返回值**, - 只会在 UI 任务与 PC 轮询叠加时偶发(P1 保留) -- `cfg_file` 的 `snprintf` 返回值全丢弃 → 但文件解析器**根本不接受转义序列**, - 溢出所需的字符永远进不来(P2) -- `nand_ftl` 的 `dhara_nand_is_free` 把垃圾 `0xFF` 当已用页 → 偏差方向是**保守**的, - 最多浪费一个页(P2 降级) - -也就是说:**降级不是"不重要",而是"触发不了",触发条件一出现就必须立刻重估**(见 §3.2)。 - -### 1.4 本轮复核范围 - -- 复核对象:首版 59 项中**未标注已闭环**的全部条目 -- 方法:逐项回到当前源码查实际代码(`grep` + 精读调用链),不看注释、不信文档既有结论 -- 已闭环项(§2):仅确认提交存在与机制在位,不重复逐行审 -- 未覆盖:`Lib/FatFs` 移植层内部、`Lib/dhara` 内核、lftpd 字符串 / IO 辅助模块、 - `Middlewares/` 内部(同首版边界) -- 模拟器保真度差异见 §6 - ---- - -## 2. 已闭环总览(44 项) - -只列索引与闭环依据,明细不再展开(首版明细可从 git 历史取回)。 - -### 2.1 P0 四项(全部已修复并台架验证) - -| 编号 | 位置 | 危害 | 闭环 | -|---|---|---|---| -| **E-01** | `App/cfg/cfg_file.c` | `cfg_file_poll` 的"超长=损坏"检查是**死代码**(读长被钉死在阈值)→ >4096B 配置被截断当有效解析 → 界外内容静默回工厂默认 → 基线粘住错误状态 → 下次开机 load 守卫判"损坏" → **用户配置文件整体销毁** | `d030cb0` + 关联 `70f1cc8`(`f_rename` 不覆盖既有目标) | -| **E-31** | `nand_ftl.c` / `journal.c` | 扫描读失败被**覆盖为 `TOO_BAD`** → 非空白卷在 I/O 故障下被误判"全新"→ 直接 `clear` → 映射表整体丢失 | `da1a6e8` | -| **E-02** | `net_socket.c` / `lftpd` / `time_sync` | 一条空闲 FTP 控制连接把 netTask 独占 30s → **全体消费者队列停摆**(实测冻结远端显示 17.7s) | `f8d6e90`+`4a1605b`(分片)→ `a45c0cb`(挂起 recv 化,结构性消灭) | -| **E-63** | `App/cfg/cfg_file.c` | 外科手术保存 3 处 `s_encode(...) == 0` 判定恒假(函数成功返回**文本长度**)→ 受管值改写为**死码**:对已存在 `/config.ini` 保存时受管键从不改写(**静默不落盘**,`net_cfg_save` 仍报 OK) | **2026-10-02**(L0.5 `cfg_file` 单测首跑抓出)——判据由 `== 0` 改 `>= 0`;见 [`../测试说明/配置文件单元测试说明.md`](../测试说明/配置文件单元测试说明.md) §5 | - -### 2.2 P1 二十一项 - -E-05(net 建链 API 队列化)、E-06 / E-20 / E-21 的**主要分量**(随 E-02 pending 化落地)、 -E-09(UI 页面局部副本 + 显式提交)、E-17(RECONFIG 原子消费)、 -E-32(SD2506 充电器寄存器回读校验)、E-33(CH395F DMA 错误通道)、 -E-36 / E-37(RS485 契约与 init 返回码)、E-38(SD2506 负温度解码)、 -E-39 / E-51(SD2506 写允许窗口 / `set_alarm` 读-改-写)、E-40(FTL erase/prog 错误分类)、 -E-43(GD5F erase 容量边界)、**E-03(错误码按任务隔离,2026-09-25)**、 -**E-34(CH395F 字节命令有限超时,2026-10-03)**、**E-04(`remo_send` 返回值校验,2026-10-03)**、 -**E-44(TPAFE `read_all` 无数据有效性校验:FRSTDATA 宏有定义、代码零引用,芯片死亡时返回 OK + 常量垃圾; -现 `read_channels` 要求"一帧内 FRSTDATA 必须跳变"、`init()` 首帧自检返 `-3`,2026-10-10)**、 -**E-45(TPAFE `s_ready` 三写者、重触发即清掉未消费帧;改帧序号 `frame_seq()` 只增不清,2026-10-10)**、 -**E-58(TPAFE `get_buf` 取指针 + 任务侧拷贝被 ISR 抢占 → 两帧混合;删 `get_buf`/`data_ready`/`clear_ready`, -改临界区原子快照 `snapshot()`,2026-10-10)**。 - -> 注:**E-44 / E-45 在 2026-09-25 重写时只留在首版 P1 速览表、未展开正文条目**(正文只余 E-59 的引用), -> 本次闭环直接进索引;实现口径与陷阱见 `AGENTS.md`「ADC 取数只用 `tpafe5160_snapshot()`」条与 -> `Drivers/BSP/TPAFE5160/README.md`。 - -> 注:E-05 已闭环(建链 API 并入队列),但**同族的 `net_getsockname` 残留通道** -> 在 §3.1-E-24 重新入册。 - -### 2.3 P2 十九项 - -E-07(`f_close` 返回值检查 ×5)、E-08(preboot 分支并轨)、E-11(`cfg_file` 读路径返回值)、 -E-14(net init/重建失败补自检留痕)、E-18(ftpTask 等待超时)、E-19(死事件位清理)、 -E-22 的**主要分量**、E-29(`ui_net_sink` `FD_CLR` 合法性)、E-35(测试常量对齐)、 -E-46(`0xECH` 非法字面量)、E-52(开机诊断检查 `get_*` 返回值)、 -E-53(GD5F 轮询路径 HAL 返回值)、E-54(ECC 使能回读)、E-55(`dhara_nand_read` 返回值)、 -E-57(`int` / `int32_t` 混用)、**E-10 / E-42 / E-56(GD5F init / BBT / raw 契约,2026-10-03)**、 -**E-60(SD2506 `SD2506_CHARGE_5K` 宏位订正,2026-09-21)**。 - ---- - -## 3. 未闭环发现(16 项) - -每项字段统一为:**位置 → 机制 → 后果 → 当前状态 → 建议**。 -"当前状态"是本轮(2026-09-25)回到代码实测的结论;**2026-10-03 已修复的条目 -(E-04 / E-10 / E-42 / E-53 / E-56)与 2026-09-25 的 E-03 保留为留档**(标 ✅),不计入未闭环数。 - -### 3.1 A 类:待处理的实质缺陷(未闭环 4 项) - -> **本类已闭环留档**:E-03(2026-09-25)、E-04 / E-10 / E-42 / E-53 / E-56(2026-10-03)—— -> 见下方各条目的 ✅ 标记与 §2 索引;此处保留详细条目仅作背景留档。 - ---- - -#### ~~E-03 `s_net_errno` 跨任务覆写竞态~~ — **已修复(2026-09-25)** | 模式 P2 - -- **位置**:`Drivers/BSP/NET/net_socket.c`(定义处 + 全部写入点 + `net_get_errno`) -- **机制**:netTask 在 `net_process_messages` 结束处把精确错误码写进全局 `s_net_errno`, - 随后调用方包装器在**自己的上下文**里再写一次。两条写路径分属不同任务,无任何互斥。 -- **后果**:netTask 刚为 A 任务置好码,B 任务的通知处理抢先覆盖 → A 读到 B 的码。 - 任何基于 `net_get_errno()` 做分支的逻辑都可能走错路径(错判"可重试"为"连接已死",或反过来)。 -- **修复(2026-09-25)——取"按任务隔离"路线**: - 1. 删除全局 `s_net_errno`,改为 `s_net_errno_slots[8]`(每槽 `{owner 任务句柄, err}`) - 2. `net_errno_slot()`:无锁快路径命中自己的槽(**只读不写**,并发读者看不到半成品槽位); - 首次占用在 `taskENTER_CRITICAL` 内复查后写入;槽位用尽**不新增共享槽**(那会把竞态带回来), - 统一回退 [0] 槽 → 语义退化为共享,仅影响排障可读性 - 3. `net_set_errno(err)` 取代全部 107 处 `s_net_errno = X` 赋值; - 新增 `NET_ERR_RETURN_1(err)` 宏合并"记码 + `return -1`",共替代 82 处 - 4. `net_get_errno()` 改读**本任务**槽位,对外语义不变(成功不清零,向后兼容) - 5. 被修路径的**真实收益**:三个既有消费者(`ui_net_sink.c:476`、`net_test_task.c:231/382`) - 都是"同任务 `net_recv` 后立即 `net_get_errno()`"——这正是竞态窗口内的模式, - 修复后该判定不再可能读到别的任务的码 -- **验证**:构建 0 错误 0 警告;cppcheck(warning/performance/portability)无告警; - `git diff --check` 无空白问题;编码无 `U+FFFD` 且 BOM 状态与原文一致 -- **代码代价**:BSS +64 B(8×(4+4)) -- **登记**:`docs/任务规划/README.md` N-5(✅ 已销项,2026-09-26 清理) -- **残留**:`net_recv_to` 继续用返回码 `-2` 表达超时(不依赖 errno),保持既有正确做法 - ---- - -#### E-04 `remo_send` 丢弃 `net_send` 返回值 — **P1** | 模式 P3 - -- **位置**:`Drivers/BSP/UI/ui_net_sink.c:101-120`(`:119` 调用点) -- **机制**:`net_send(g_client, p, (int)(4 + len + 1), 0)` 的返回值直接丢弃。 - `net_send` 在弱网 / 对端停滞时的合法返回是**短写**(已发字节数 < 请求),不是 -1。 -- **后果**:应答帧静默截断 → 客户端 CRC 校验失败 → 断开重连 → 用户观感是 - "画面卡住然后黑屏 / 一直卡"。这是**最难定位的一类故障**:设备侧一切日志正常。 -- **当前状态**:✅ **已修复并通过真机验证(2026-10-03)**——`remo_send` 改返回 `int` + 校验 `ret == total` + 短写告警 / 连续失败阻断推送;真机无误报基线。以下为**修复前**复核记录(保留作背景):`ui_net_sink.c` 全文件无 `net_send` 返回值检查, - 且 `remo_send` 的整帧(3204B)**远超过**单次可发送上限 - `NET_SEND_CHUNK_MAX`(= 芯片发送缓冲 1KB,`net_socket.c:1394`), - 所以"单次调用的返回值即全量"这一假设**在整帧路径上必然不成立**; - 当前能工作只是因为 `net_send_locked` 内部做了分块循环 + 内部重试。 - 一旦对端读取变慢,内部分块重试会耗尽预算并**合法地短写返回**,此时上层无任何感知。 -- **建议**:`remo_send` 改为返回 `int`,校验 `ret == len`;短写 → `DBG_ERROR` + 计入统计; - 连续失败 → `net_close(g_client)` 强制重连补帧(与 `lftpd_inet_write` 的处理方式对齐)。 -- **关联**:`docs/测试说明/链路延迟测试说明.md:632` 已把 E-04 列为"会掩盖延迟"的既有缺陷。 - ---- - -#### E-24 `net_getsockname` 直读 `s_local_ip`,绕过 netTask 串行化 — **P2** | 模式 P7 - -- **位置**:`Drivers/BSP/NET/net_socket.c:3637-3641`(读 `s_local_ip`); - 写点 `:201`(netTask 在 `net_init` 内 `memcpy`) -- **机制**:`s_local_ip` 是 4 字节静态数组,netTask 写、任意任务读,无同步。 -- **后果**:`net_getsockname` 可能拼出**新旧混合**的 IP。 -- **当前状态(复核)**:**仍然存在,但本轮确认影响面比首版预估的更窄——仍应记录。** - 写入只发生在 `net_init` 内部**一次**(`:201`),此后 IP 不再变化 - (DHCP → `0.0.0.0` 也在同一次写入内)。所以"撕裂"需要恰好在 `net_init` 执行期间调用 - `net_getsockname`,而所有调用方(`lftpd.c:407` PASV、`lftpd.c:673` 连接日志、 - `lftpd_inet.c:64`)都在 **NET_READY 屏障之后**才可能执行 → 当前不可达。 -- **建议**:维持 P2。`net_getsockname` 改为经队列请求(或至少把 4 字节打包成单次 32 位读 - + netTask 侧序贯穿),使契约与其余 socket API 一致;不作为独立修复项排期, - 随 N-4 顺带处理。 - ---- - -#### E-10 GD5F init 内多处 HAL 返回值未检查 — **P2** | 模式 P4 - -- **位置**:`Drivers/BSP/GD5F2GQ5UE/gd5f2gq5ue.c:606` 起(init 步骤 3~6 的 setup 序列)、 - 各直接调用的 `HAL_SPI_Transmit` / `gd5f_set_feature` / `gd5f_wait_busy` -- **机制**:扫坏块、使能 ECC、解除块保护、定位保留池等步骤大量调用点忽略返回值。 -- **后果**:init 返回 `GD5F_OK` 但实际配置未生效(例如 ECC 未真正打开), - 存储栈在**无 ECC 保护**下运行 → 数据完整性底线静默降级。 -- **当前状态**:✅ **已修复(2026-10-03)**——init 全链路检查返回值(见 §5 第二档 #1)。修复前复核:**当时仍然存在**。`gd5f_wait_busy` 自身有 `int` 返回值 - (`gd5f2gq5ue.c:147`,超时返 `GD5F_BUSY_TIMEOUT`),但 init 与 BBT rebuild 里 - 至少 5 处(`:548` / `:552` / `:643` / `:647` / `:1000`)直接丢弃返回值; - `gd5f_bbt_scan()` 内部的 `gd5f_page_read` / `gd5f_read_from_cache`(`:505-506`)同样未检查。 -- **建议**:init 全链路失败即返回错误码(`App/task/default_task.c:64` 已有 - `int ret = gd5f2gq5ue_init()` 的检查位,只是慢路径没把错误传上来);init 重试 1~2 次; - 让 `default_task` 能对"扫坏块失败 / ECC 未使能"做出差异化反应(而非静默继续)。 -- **关联**:与 E-42 / E-53 / E-54 同一族,建议合并为一次"GD5F 错误通道统一"改造(§5 第二档 #1)。 - ---- - -#### E-42 `gd5f_bbt_scan` 丢弃读返回值 — **P2** | 模式 P4 - -- **位置**:`Drivers/BSP/GD5F2GQ5UE/gd5f2gq5ue.c:505-506` -- **机制**:`gd5f_page_read(page)` 与 `gd5f_read_from_cache(...)` 的返回值均未接收; - `bbt_scan` 对外恒返回 `GD5F_OK`(`:518`)。 -- **后果**:SPI 读失败时局部 8 字节 `spare[8]` 保持**上一轮的陈旧内容**, - 按 `spare[0] != 0xFF` 判定 → 把好块判成坏块(保守,可接受)或反之。 - 更严重的是**整片扫描全失败时也返回成功**,上层以为坏块表可用。 -- **当前状态**:✅ **已修复(2026-10-03)**——`bbt_scan` 检查读返回值 + 每轮 `spare` 清零 + 读失败中止,`bbt_rebuild` 扫描失败**放弃持久化**(见 §5 第二档 #1)。修复前复核:**当时仍然存在**。调用点 `:548`(BBT rebuild)与 `:639`(init) - 都直接忽略返回,rebuild 路径随后照常 `gd5f_bbt_save()` → 可能把**错表**持久化覆盖正确副本。 -- **建议**:扫描中任一读失败即中止并返回错误;`bbt_scan` 增加 `spare[8]` 每轮清零; - `gd5f2gq5ue_bbt_rebuild` 在扫描失败时必须**放弃持久化**(保留既有好副本)。 - 第二项(失败时不动持久区)是比"检查返回值"更关键的语义修正。 - ---- - -#### E-53 GD5F 轮询路径 HAL 返回值系统性未检查 — **P2** | 模式 P4 - -- **位置**:`Drivers/BSP/GD5F2GQ5UE/gd5f2gq5ue.c:155-168`(`gd5f_wait_busy` 内 3 次 HAL 调用)、 - `:239` / `:403` / `:476` / `:545` / `:552` / `:643` / `:647` / `:1000`(`wait_busy` 调用点) -- **机制**:`gd5f_wait_busy` 里 `HAL_SPI_Transmit` / `HAL_SPI_Receive` 的返回值全部丢弃, - 失败时 `status` 保持初值 `0` → `(status & GD5F_STATUS_OIP) == 0` 立即可满足 → - **"操作完成"被伪造出来**。 -- **后果**:SPI 故障 → 状态字节读到垃圾 → 误判操作完成 → 后续命令在未就绪的芯片上执行, - 最终表现为"静默报成功但数据没落盘"。 -- **当前状态**:✅ **已修复(2026-10-03)**——`gd5f_wait_busy` 内检查三次 HAL 调用,失败即返回 `GD5F_ERROR`(与 `GD5F_BUSY_TIMEOUT` 区分;见 §5 第二档 #1)。修复前复核:**当时仍然存在**。`wait_busy` 的返回值形式上是"看起来被检查了" - (很多调用点确实写了 `ret = gd5f_wait_busy(...)`),但**函数内部不区分"超时"与"HAL 失败"**, - 且 HAL 失败造成的伪完成会**优先于**超时返回 `GD5F_OK`。 -- **建议**:`gd5f_wait_busy` 内检查三次 HAL 调用,失败即返回独立错误码(`GD5F_ERROR`); - 调用点区分 `GD5F_BUSY_TIMEOUT`(可重试)与 `GD5F_ERROR`(真故障,须上报自检)。 -- **关联**:与 E-10 / E-42 / E-54 合并改造(§5 第二档 #1)。 - ---- - -#### E-56 公开 `gd5f2gq5ue_read` 对不可纠正页仍返回 0 — **P2** | 模式 P3 - -- **位置**:`Drivers/BSP/GD5F2GQ5UE/gd5f2gq5ue.c:703-737`(read 主体)、 - `:239`(`gd5f_read_from_cache` 内) -- **机制**:`gd5f_read_from_cache` 的返回值语义是"**SPI 传输完成**",不是"**数据正确**"。 - ECC 不可纠正页(ECCS 非零)在 SPI 层是正常完成的,因此函数返回 `0`(成功)。 -- **后果**:raw API 使用者会拿到**带有未纠正位翻转**的数据并被告知成功。 -- **当前状态**:✅ **已修复(2026-10-03)**——按建议 ② 在 `gd5f2gq5ue.h` 的 `read` 声明处**显式文档化 raw 契约**("0 = SPI 传输完成,正确性由 ECC 层保证";见 §5 第二档 #1)。修复前复核:**当时仍然存在,缺口范围本轮已精确确认。** - 生产主路径是 **dhara glue 层**(`nand_ftl.c`),它自己有 ECC 检查保护,所以是安全的; - 缺口只存在于**直接调用公开 raw API 的场景**。真正的消费者目前只有 `test/` 测试套件与诊断路径。 -- **建议**:两条路选一条——① `gd5f2gq5ue_read` 增加 ECCS 回读并在不可纠正时返回错误码; - ② 在 `gd5f2gq5ue.h` 的 `read` 声明处**显式文档化**"0 = SPI 传输完成, - 数据正确性由 ECC 层保证"。因涉及公开 API 语义,倾向 ② + 在 - `docs/问题记录/GD5F2GQ5UE_Trap_Records.md` 补一条契约说明。 - ---- - -#### E-49 RS485 TX 超时立即拉低 DE — **P2** | 模式 P9 - -- **位置**:`Drivers/BSP/RS485/rs485.c:70-93` -- **机制**:发送超时路径直接拉低 DE 引脚,此时**末尾字节可能仍在移位寄存器中**。 -- **后果**:对端收到被截断的最后一帧(半字节)。 -- **当前状态(复核)**:**仍然存在,但仅在超时路径生效**。正常路径靠 TC 标志正常收尾, - 所以影响局限于"发送本身已经失败"的场景——属于"失败之后再补一刀"型缺陷。 -- **建议**:超时路径改为"等 TC 或短延时后再拉低 DE"(给移位器一个明确的排空窗口), - 代价是超时路径多等 ≤1 字符时间。 - ---- - -#### E-50 RS485 单缓冲无 ISR/任务同步 → 包撕裂 — **P2** | 模式 P7 - -- **位置**:`Drivers/BSP/RS485/rs485.c:37-39`(`rx_buf` / `rx_size`) -- **机制**:ISR 写 `rx_size`、任务侧读并 `memcpy`,无同步;115200bps 下 - 256B 帧仅约 22ms 窗口,任务一旦晚于窗口完成拷贝,新帧就会覆盖旧帧。 -- **后果**:读出**跨两帧**拼接的数据(长度字段与内容不匹配)。 -- **当前状态(复核)**:**仍然存在,休眠缺陷**。当前 `rs485_task.c:37-43` 是空桩, - 无消费者。与 E-36 / E-44 / E-45 / E-58 同一族。 -- **建议**:双缓冲 + 序号(或"任务处理窗口内关 IDLE 中断"的简化约定)。 - 属"接消费者前必修"(§5 第三档前置条件)。 - ---- - -#### E-59 消费者任务均为空桩,驱动层多项 P1 全部休眠 — **P1(元发现)** | 模式 —— - -- **位置**:`App/task/adc_task.c:37-43`、`App/task/rs485_task.c:37-43` -- **机制**:两个任务的函数体都是 `osDelay` 空循环,谁都不调驱动层的 `get_buf` / `on_rx`。 -- **后果**:**首版列出的多项驱动层 P1(E-36 / E-44 / E-45 / E-58)当前全部不可触发。** - 这意味着"审计通过"是假的——一旦接入真实消费者,这些缺陷会同时上线。 -- **当前状态(复核 2026-10-10)**:**仍然存在**(消费者仍未接入),但**闸门已经半开**: - ① 前置清单里 TPAFE 三项 **E-44 / E-45 / E-58 已修复**(原子快照 API + 帧序号取代 - 就绪标志 + FRSTDATA 数据有效性校验,索引见 §2.2);RS485 侧 **E-36 / E-37 早已闭环**, - 但 **E-50(单缓冲无同步)与 E-49(超时路径拉 DE)仍未修** → RS485 消费者仍被挡住。 - ② 门禁本身已固化进 `AGENTS.md`(与"改栈前跑 `stack_report.py`"同级)。 - ③ 栈预算**仍未做**:两任务尚未纳入 `Src/freertos.c` 栈表。 -- **建议**:接 **ADC** 消费者现在只欠"栈预算 + 产品定义(采样率 / 数据出口)"; - 接 **RS485** 消费者前还必须先修 E-50 / E-49。 - ---- - -### 3.2 B 类:契约缺口(随消费者接入触发,未闭环 7 项) - -这一类的共同特征:**静态缺陷真实存在,但当前没有调用者能走到**。它们现在不产生故障, -但契约没写死,属于"埋着的雷"。**触发条件一旦出现(新消费者、参数变更、库配置变更),立即升 P1。** - ---- - -#### E-16 `cfg_file` 生成文档的边界余量绑死 `snprintf` 返回语义 — **P2** | 模式 P4 - -- **位置**:`App/cfg/cfg_file.c:469` / `:481`(`s_gen_document`)、 - 同族丢弃点 `:252` / `:256` / `:261`(`s_encode`)、`:642` / `:663` / `:683`(行生成) -- **机制**:`off += (size_t)snprintf(...)` —— 直接丢弃返回值。`snprintf` 返回**本应写入的长度**, - 截断时该值 **> 剩余容量**,`off` 会**跳过缓冲末尾**(越界),而不是停在边界; - 现有的 `if (cap < off + 112u) return -1;` 前置守卫在 `off` 已经越界后也救不回来。 -- **后果**:极端情况下写越界 / 输出被静默截断。 -- **当前状态(复核)**:**仍然存在,但本轮确认当前不可达。** - 触发本缺陷需要一个**超过 104 字符**的段名或键名,而配置文件的**解析器根本不接受转义序列** - ——含引号 / 反斜杠的键名无法通过解析进入内存,长度上限也受解析缓冲约束。 - 换句话说:**防线是"上游解析器"而不是"本函数的守卫",而这条防线没有写进文档。** -- **建议**:① 把 `snprintf` 返回值改为检查式 - (`int n = snprintf(...); if (n < 0 || (size_t)n >= cap - off) return -1;`); - ② 更根本的做法是在 `cfg_file.h` 中**显式定义段名 / 键名的最大长度**,并让注册 API 拒绝超长名字 - ——这样余量才是**编译期可验证**的,而不是依赖"解析器恰好不接受转义"。②优先级高于①。 - ---- - -#### E-25 `net_select` LISTENING 恒可读 + 参数错误不置 errno — **P2** | 模式 P3 - -- **位置**:`Drivers/BSP/NET/net_socket.c`(`net_select` 实现) -- **机制**:① 监听态 fd 在 `select` 语义下永远"可读"(有连接待 accept 时可读, - 无连接时按 BSD 也应可读),调用方若用 `select` 做"有数据可读"判断会误判; - ② 参数错误时直接返回而**不设置 errno**,调用方拿到 `-1` 却查不到原因。 -- **后果**:无法区分"监听器上有新连接"与"有数据";参数错误无从定位。 -- **当前状态(复核)**:**仍然存在,休眠缺陷**。当前无 `net_select` 调用者 - (各服务都是 `accept` 轮询 / pending 模式)。 -- **建议**:随 N-4 或首个 `select` 消费者落地时一并订正; - 同时在头文件把"LISTENING 恒可读"作为**契约**写出来。 - ---- - -#### E-26 UDP 路径未并入队列 — **P2** | 模式 P8 - -- **位置**:`Drivers/BSP/NET/net_socket.c:2647`(`net_sendto`)、`:2776`(`net_recvfrom`) -- **机制**:UDP 的 `sendto` / `recvfrom` 走**直调芯片**路径,不在 `netMsgQueue` 上, - 与 netTask 串行语义冲突(SPI 事务可能与其他 socket 操作交错)。 -- **后果**:UDP 与 TCP 并发时 SPI 事务交错 → 芯片状态机混乱。 -- **当前状态(复核)**:**仍然存在,休眠缺陷**。当前无任何 UDP 用户 - (`net_init` 的 UDP 分支无调用者)。 -- **建议**:三选一并在文档中定案——① 并入队列(新增 `NET_MSG_SENDTO` / `NET_MSG_RECVFROM`); - ② 直接移除 UDP API(无用户,最干净);③ 保留但在头文件明确"仅单任务独占期可用"。 - 倾向 ② 或 ③,避免为无用户的路径增加队列类型与 RAM。 - ---- - -#### E-27 / E-28 socket 表裸指针跨上下文读 — **P2** | 模式 P2 / P7 - -- **位置**:`Drivers/BSP/NET/net_socket.c`(`net_get_sock()` 返回裸指针)、 - `App/sync/time_sync.c` / `Drivers/BSP/NET/lftpd/lftpd.c`(代次号读取) -- **机制**:`net_get_sock()` 把 socket 控制块**裸指针**交给调用方,调用方随后读 - `p_sock->state`;netTask 可能在同一时刻改写该结构。 -- **后果**:读到中间态的 `state`(例如"刚 `CLOSED` 但字段未清")。 -- **当前状态(复核)**:**仍然存在,休眠缺陷**(本轮从 E-05 族拆分并重新入册)。 - 当前唯一代表性读点是 `lftpd_inet_accept_timeout` - (`Drivers/BSP/NET/lftpd/lftpd_inet.c:100-104`)读 `p_sock->state != LISTENING`; - 该路径受 `timeout_ms` 约束、且 netTask 对 `state` 的写是**单次 32 位写**(无撕裂), - 实际风险低。已用 pending 模型重写的路径(`net_recv_to`)不再有此类读。 -- **建议**:长期方向是"状态查询 API 化"(`net_get_state(fd)` 走队列或返回快照值), - 与 E-24 / E-26 同属"消除跨上下文裸指针"的一组,可合并处理。 - ---- - -#### E-31 后续:卷自愈的误判边界 — **P2** | 模式 P3 - -- **位置**:`App/task/default_task.c:311`(`storage_bring_up`)及卷健康探针 -- **机制**:E-31 主体已修(错误分类 + 有界重试),但**自愈触发集**仍需一条纪律: - 只有"真卷损坏"才允许走重格式化,**名字 / 参数类错误码必须排除在外**。 -- **后果**:若把 `FR_INVALID_NAME` 之类的**纯 RAM 判定错误**归入损坏触发集, - 会触发**不可逆的整卷重格式化**。 -- **当前状态(复核)**:**机制在位,纪律已固化为 `AGENTS.md` 铁律** - ("自愈类逻辑严禁把名字/参数类错误码归入卷损坏触发集")。 - 本项保留为**回归监督项**:后续任何自愈逻辑改动都要回头核对这条纪律。 -- **建议**:不需要改动。在 `storage_bring_up` 的错误分类 switch 旁补一条注释 - 指向该铁律,降低后人误改概率。 - ---- - -#### E-47 / E-48 驱动侧边界项 — **P2** | 模式 P8 / P9 - -| 编号 | 位置 | 机制与后果 | 当前状态(复核) | 建议 | -|---|---|---|---|---| -| **E-47** | `Drivers/BSP/CH395F/ch395f.c:606-622` | 轮询超时与"芯片忙"共用 `0x10` 码;且 `CMD_STATUS` 读即清 → 下条命令可能先读到上条的完成态 | **仍然存在**,现边界已统一按"可重试"处理,实际影响有限 | 引入独立超时码或轮次序,使调用方能区分"忙"与"超时" | -| **E-48** | `Drivers/BSP/RS485/rs485.c:173` | `ErrorCallback` 的修复者返回值被丢弃 | **仍然存在,实为良性**:ORE 路径 HAL 会先复位 `RxState=READY`;非阻塞错误路径原本就存活自愈 | 非 `HAL_OK` 时补一条 `WARN` 便于排障即可 | - ---- - -### 3.3 C 类:一致性 / 可维护性(未闭环 5 项) - -无功能影响或影响极小,排在第三档批量处理。 -**本类已闭环留档**:E-57(2026-10-03)、E-60(2026-09-21);**监督项**:E-62(无需改动)。 - -| 编号 | 位置 | 问题 | 当前状态(复核) | 建议 | -|---|---|---|---|---| -| **E-12** | `Lib/FatFs/ffconf.h:39` | `FF_FS_LOCK 0` —— 无文件级互斥,同一文件可被两任务同时打开 | **仍然存在**。当前 `defaultTask` 是 syslog 与配置引擎的**唯一写者**(单写者收敛策略),故无实际冲突 | 开 `FF_FS_LOCK`(代价:每文件 ~几十字节 RAM);或固化"单写者纪律"。**当前选择后者,本项仅为记录** | -| **E-15** | `App/util/ringbuf.c` | ringbuf 实现存在但**全项目零调用者**(本轮 `grep` 确认除自身外无引用) | **仍然存在** | 修复后启用,或直接删除。**保留一个零调用者的工具模块是负债**,建议本轮就定去留 | -| **E-23** | `test/` | 测试套件缺代次检查(accept 失败分支);TC704 并发度偏高 | **仍然存在** | 加代次断言;TC704 降并发或在测试构建抑制服务任务 | -| **E-30** | `simulator/sim/net_socket_sim.c` | sim 后端 errno 语义与 MCU 不对齐(成功即清 errno,MCU 不清) | **仍然存在**(语义差异在 §6 表中仍成立) | 对齐语义;并在缺陷记录中标明"sim 复现盲区" | -| **E-57** | `Drivers/BSP/GD5F2GQ5UE/gd5f2gq5ue.h:119/140/189/222-253` | 公开 API 返回类型 `int` / `int32_t` 混用(码值均为小负数,无功能影响) | ✅ **已修复(2026-10-03)**:公开 API 全部统一为 `int32_t` | 见 §5 第二档 #1 | -| **E-60** | `Drivers/BSP/SD2506/sd2506.c/h` | `SD2506_CHARGE_5K` 宏位与 `docs/芯片手册/SD2506API-G.md` §4.8 不符的风险 | **已订正**(2026-09-21,`41e111b`):宏位已改为 `bit1`,18H 回读校验在位 | 无需改动,仅作为"手册总表 vs 专用表不一致"的案例保留 | -| **E-61** | `Drivers/BSP/SD2506/sd2506.c:482` | `sd2506_clear_int` 注释称"ARST=1 时自动清除 INTAF",实际 ARST 在 **CTR3**(11H)而代码从未写过 CTR3 | **仍然存在(纯注释错误)** | 修注释:明确"ARST 未启用,INTAF 靠读 CTR1 清除" | -| **E-62** | `Lib/FatFs/ffconf.h` | `FF_USE_LFN=0` → 严格 8.3 短名;自愈逻辑若把 `FR_INVALID_NAME` 归入损坏触发集 → 不可逆重格式化(`.fschk` 无限重格式化事故根因) | **铁律已固化**(`AGENTS.md`);`ff.c` `create_short` 全部拒绝点已加 DEBUG 打印 | 无需改动,回归监督项(与 §3.2-E-31 后续同族) | - ---- - -## 4. 模式 × 子系统矩阵(仅未闭环项) - -> 单元格内容 = 发现编号;`✓` = 本轮检查无问题;`—` = 不适用 - -| 模式 | net 层 | 事件标志 | 配置引擎 | syslog | CH395F | RS485 | SD2506 | GD5F/FTL | TPAFE | lftpd | timeSync | ui_sink | test | sim | -|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---| -| P1 值拷贝 | ✓ | — | ✓ | ✓ | — | — | — | — | — | — | — | — | — | — | -| P2 共享全局 | ✓ | ✓ | ✓ | ✓ | — | — | — | — | — | **E-27/E-28** | **E-27/E-28** | ✓ | ✓ | E-30 | -| P3 混同 | **E-25** | — | ✓ | — | E-47 | ✓ | ✓ | **E-31后续** | — | ✓ | ✓ | ✓ | ✓ | E-30 | -| P4 未检查 | ✓ | ✓ | **E-16** | ✓ | ✓ | E-48 | ✓ | ✓ | — | ✓ | ✓ | ✓ | ✓ | — | -| P5 事件语义 | ✓ | ✓ | ✓ | — | — | — | — | — | — | ✓ | ✓ | ✓ | ✓ | — | -| P6 符号/宽度 | — | — | — | — | — | — | — | — | — | — | — | — | — | — | -| P7 跨上下文 | **E-24** | — | ✓ | ✓ | — | **E-50** | — | — | **E-58** | E-27/E-28 | ✓ | ✓ | — | — | -| P8 外设/库 | **E-26** | — | ✓ | ✓ | E-47 | — | E-61 | ✓ | — | — | — | — | — | — | -| P9 超时/错误 | ✓ | — | — | — | E-47 | **E-49** | — | ✓ | ✓ | ✓ | ✓ | — | — | — | - -**读法提示**:三处"空白带"(事件标志、syslog、配置引擎的大部分行)不是没审, -而是首版查出的问题已闭环。**GD5F 列的项已于 2026-10-03 全部闭环**(矩阵该列已清空), -当前未闭环项集中在 **RS485 驱动(E-49 / E-50)、net 层全局状态 -(E-24 / E-25 / E-26 / E-27 / E-28)、配置引擎(E-16)与测试基础设施(E-23 / E-30)。** - ---- - -## 5. 修复计划 - -### 第一档:立即(改动小、价值高、风险低,无架构变更) - -| # | 项 | 内容 | 级别 | -|---|---|---|---| -| 1 | ~~**E-34**~~ | **已修复并通过真机验证(2026-10-03)**:CH395F 3 处字节级 SPI 调用 `HAL_MAX_DELAY` → 有限超时 100ms + 返回值检查 + 故障锁存 `ch395f_spi_byte_fault()`(仅首条故障记日志,防刷屏)——SPI 异常不再挂死 netTask;真机正常路径零回归 | ✅ | -| 2 | ~~**E-04**~~ | **已修复并通过真机验证(2026-10-03)**:`remo_send` 改返回 `int` 校验 `ret == total`;短写 → `DBG_ERROR` + 连续失败计数;连续 3 次 → 阻断推送(客户端无帧自行重连,芯片侧 DISCONNECT/TIMEOUT 自动重开监听;MCU 单连接模型不 `net_close` 数据 fd),新连接/网络重建解除;真机无误报基线 | ✅ | -| 3 | ~~**E-03 / N-5**~~ | **已修复(2026-09-25)**:错误码改按任务隔离,删除全局 `s_net_errno`(见 §3.1) | ✅ | -| 4 | **E-49** | RS485 TX 超时路径:等 TC / 短延时后再拉低 DE | P2 | -| 5 | **E-15** | `ringbuf` 定去留:修复后启用,或删除(零调用者模块不值得维护) | P2 | -| 6 | **E-61** | SD2506 `clear_int` 注释订正(ARST 在 CTR3,代码从未启用) | P2 | - -### 第二档:下一迭代(结构性改造) - -| # | 项 | 内容 | -|---|---|---| -| 1 | ~~**GD5F 错误通道统一**(E-10 / E-42 / E-53 / E-54 / E-56 / E-57)~~ | **已修复并通过台架套件回归(2026-10-03)**:`gd5f_wait_busy` 内检查三次 HAL 调用并区分"超时"(`GD5F_BUSY_TIMEOUT`,可重试)与"真故障"(`GD5F_ERROR`);`gd5f_bbt_scan` 检查读返回值 + 每轮 `spare` 清零 + 读失败中止,`bbt_rebuild` 扫描失败时**放弃持久化**(保留好副本);init 全链路检查返回值;init 后**回读 B0h 校验 ECC 使能(bit4)**;`gd5f2gq5ue_read` 的 raw 契约文档化(E-56);返回类型统一 `int32_t`(E-57);`nand_ftl.c` 2 处未检查的 `gd5f_read_from_cache` 补检查。套件回归 `=== GD5F2GQ5UE Tests: 16/16 PASSED (failed=0) ===` ✅ | -| 2 | **N-4:ACCEPT + SEND 阻塞路径 pending 化** | 沿用 recv 挂起机制扩展;同期消化 **E-25**(`net_select` 语义)、**E-24 / E-27 / E-28**(跨上下文裸指针收口为状态查询 API)。基线重锁 + 探针族扩充 + 看门狗接 selftest 留痕 | -| 3 | **E-16** | 在 `cfg_file.h` 显式定义段名 / 键名最大长度,注册 API 拒绝超长名字(把余量变成编译期可验证);`snprintf` 返回值改检查式 | -| 4 | **E-26** | UDP 路径定案:并入队列 / 移除 / 文档化"单任务独占"。倾向 **移除**(无用户) | -| 5 | **E-30** | sim 后端 errno 语义对齐 + 缺陷记录标注 sim 复现盲区 | -| 6 | **E-23** | 测试套件加代次断言;TC704 降并发或测试构建抑制服务任务 | - -### 第三档:长期 / 前置门禁 - -- **E-59 门禁**(**已固化进 `AGENTS.md`,2026-10-10**):`adc_task` / `rs485_task` 接入消费者 - **之前**必须先完成驱动契约修复 + 栈预算核算(两任务尚未纳入 `Src/freertos.c` 栈表), - 与"改栈前跑 `stack_report.py`"同级。清单现状:**E-36 / E-37 ✅(早前)**、 - **E-44 / E-45 / E-58 ✅(2026-10-10)**、**E-50 / E-49 ❌(RS485 未修)** - → **ADC 侧只欠"栈预算 + 产品定义",RS485 侧仍被挡住** -- **E-50**:RS485 双缓冲 + 序号(随 E-59 门禁,**接 RS485 消费者前必修**) -- **E-12**:`FF_FS_LOCK` 或固化"单写者纪律"(`AGENTS.md`) -- **E-31 后续 / E-62**:卷自愈触发集的回归监督(纪律已固化,需防后人误改) -- **E-47 / E-48**:驱动健壮性长尾(独立超时码 / WARN 留痕) - ---- - -## 6. 模拟器保真度差异 - -核心结论:**09-21 类 errno 缺陷在模拟器结构性不可复现**(单线程 + 成功即清 errno 双重掩盖), -该类修复**只能硬件闭环验证**。 - -| # | 项目 | MCU 后端 | 模拟器后端 | 影响 | -|---|---|---|---|---| -| 1 | accept fd 模型 | 返回监听 fd 自身(单连接) | 返回独立新 fd,监听 fd 常驻 | 依赖"fd 生命周期"的写法移植即泄漏 | -| 2 | EOF 后行为 | `auto_relisten`:fd 回 LISTEN、保持 `in_use` | 无:fd 仍 `in_use` 但 Winsock 已关 | "EOF 后 fd 仍活"的写法在 sim 失真 | -| 3 | recv 阻塞模式 | flags=0 阻塞 / pending | 恒非阻塞,`(void)flags` | **生产阻塞路径在 sim 零覆盖** | -| 4 | send 停滞契约 | 分块阻塞;1s/chunk 停滞 → 返回已发字节 + TIMEDOUT | 单次非阻塞;部分发送 → 部分值 + **NET_OK(静默截断无告警)** | 同一条件双端 errno 不同 | -| 5 | 零长度 send | -1 / INVAL | 0 / OK | 零长用例 sim 必假 FAIL | -| 6 | 零长度 recv | -1 / INVAL | 0 = 对端关闭 | 边界契约不一致 | -| 7 | `net_connect` 语义 | 异步:0 = issued,net_poll 推进状态机 | 进行中 = -1 / WOULDBLOCK;state 永不推进 | TC701 假 FAIL / TC703 负向用例**假 PASS** | -| 8 | 错误码粒度 | 12 种精确码(附录 A) | 仅 BADF / INVAL / WOULDBLOCK + 通用 NET_ERR | 依赖精确码的分类逻辑在 sim 失真 | -| 9 | 成功后 errno | **不清除**(stale 滞留) | **清零** | **本类缺陷在 sim 结构性不可复现** | -| 10 | `net_listen` 前置 | 须 BOUND / CREATED | 仅查 `in_use` | sim 更宽松 | -| 11 | `getsockname` 数据源 | `s_local_ip` 缓存 | Winsock `getsockname` | PASV 响应 IP 双端不同 | -| 12 | 代次号 | reconfig 递增 | 恒 0 | 重建 / 重监听路径 sim 零覆盖 | -| 13 | 事件回调 | net_poll 内 `fire_event` | 只存储不触发 | 回调驱动逻辑 sim 全死 | -| 14 | 并发模型 | 队列 + netTask 串行 SPI | 单主线程直调 Winsock | 跨任务竞态 sim 不可复现 | -| 15 | UDP bind | `open_socket` + SPI | 无对应步骤 | 与 E-26 相关,当前无用户 | -| 16 | `net_init` | 芯片全量初始化 | 仅 `WSAStartup` | 设计对齐 | - ---- - -## 7. 审计边界与未覆盖项 - -- **未逐行审计**:`Lib/FatFs` 移植层内部(已知陷阱已在 `AGENTS.md` 固化)、 - `Lib/dhara` 内核(仅审 glue 映射层与错误分类)、lftpd 的字符串 / IO 辅助模块(边界抽查)、 - `Middlewares/` CMSIS / HAL 内部(错误路径抽查) -- **模拟器**:16 项差异见 §6;09-21 类 errno 缺陷**只能硬件验证** -- **休眠缺陷的性质**:RS485 / TPAFE 侧 **E-50 / E-58** 因消费者为空桩而暂不可触发, - 接入消费者前必须先修(E-59 门禁) -- **基线**:本报告基于 2026-09-24 `v2-on-ui` @ `9a8255f`;后续代码变更需增量复核 -- **文档一致性提醒**:本轮复核发现首版存在若干"**文档内闭环比代码先行**"的情况 - (例如 E-26 / E-27 / E-28 在首版矩阵中已被标为 `✓`,但代码实际未改)。 - **建议后续复核一律以当前源码为准,不以既有矩阵为准。** - ---- - -## 附录 A:NET_ERR 错误码表(`Drivers/BSP/NET/net_types.h:98-109`) - -| 码 | 值 | 含义 | 通知值语义 | -|---|---|---|---| -| `NET_ERR_OK` | 0 | 成功 | 成功:通知值 = 字节数 / fd(≥0) | -| `NET_ERR` | -1 | 通用错误 | 失败:通知值 = 负的精确错误码 | -| `NET_ERR_BADF` | -2 | 无效 fd(含代次失效) | | -| `NET_ERR_INVAL` | -3 | 参数无效 | | -| `NET_ERR_NOMEM` | -4 | fd 池 / 缓冲耗尽 | | -| `NET_ERR_NOTCONN` | -5 | 未连接 | | -| `NET_ERR_ISCONN` | -6 | 已连接 | | -| `NET_ERR_WOULDBLOCK` | -7 | 无数据 / 不可写(**正常轮询态,非错误**) | | -| `NET_ERR_CONNRESET` | -8 | 对端复位 | | -| `NET_ERR_TIMEDOUT` | -9 | 超时(recv / send chunk / listen 重开) | | -| `NET_ERR_NOSPACE` | -10 | 芯片缓冲满 | | -| `NET_ERR_BUSY` | -11 | 芯片忙(含 listen 端口占用) | | - -超时参数(`net_config.h`):`NET_SEND_TIMEOUT_MS=1000`、`NET_RECV_TIMEOUT_MS=30000`。 - -## 附录 B:事件位表(`App/app/sys_events.h`) - -| 位 | 名称 | 语义 | 置位者 | 消费者 | -|---|---|---|---|---| -| bit0 | `NET_READY` | 状态位 | netTask(组网成功) | ftp / ui / timeSync(`WaitAny\|NoClear` ✓) | -| bit1 | `STORAGE_READY` | 状态位 | defaultTask(挂载 + 配置加载完成) | 同上 | -| bit2 | `FTP_READY` | 状态位(已改为可达) | ftpTask(`lftpd_start` 之后) | 预留给依赖 FTP 的功能 | -| bit3 | `TIMESYNC_READY` | **未置位**(仍无置位者) | 无 | 无(timeSync 实际等 NET / STORAGE) | -| bit4 | `NET_RECONFIG` | 命令位(可消费) | defaultTask(UI 保存网口配置) | netTask(已改原子消费 ✓) | -| bit5 | `CFG_SAVE` | 命令位(可消费) | uiTask | defaultTask(`WaitAny` 500ms + pending 锁存 ✓) | - -> **bit3 说明**:`TIMESYNC_READY` 仍未置位,属 E-19 的残留项。因当前无消费者, -> 影响为零;建议在 N-4 一并处置(置位或删除,不要长期留一个"假位")。 - -## 附录 C:队列清单与回传机制 - -| 队列 | 定义处 | 深度 | 条目形态 | 回传机制 | 风险 | -|---|---|---|---|---|---| -| `netMsgQueue` | `Src/freertos.c:166` | 8 | `net_msg_t` **按值** | 任务通知值(精确错误码 / 字节数)+ 指针字段回写 | 头阻塞已由 挂起 recv 化解;errno 竞态已由 E-03 修复(按任务隔离) | -| syslog 队列 | `App/log/sys_log_mcu.c:218-223`(惰性创建) | 64 | 输入型(id + 静态串指针) | 无(fire-and-forget) | 结构免疫 P1;preboot 窗口已并轨 | -| `cfgSaveQueue` | `Src/freertos.c` | 8 | 输入型(路径指针) | `s_save_pending` 锁存 + `s_save_result` 状态机 | 协议无损 ✓ | - ---- - -*报告完。2026-09-25 重写(**重写时点**:已闭环 33 项移入 §2 索引,未闭环 26 项全部回到 -`v2-on-ui` 当前代码逐项复核,新增 **E-24 / E-26 / E-27 / E-28 / E-61 / E-62** 及 -**E-59 门禁条**)。审计阶段未修改任何代码、未执行构建。* - -*2026-09-25 同日后续:依 §5 第一档 #3 完成 **E-03 修复**(错误码改按任务隔离,删除全局 -`s_net_errno` → `s_net_errno_slots[8]` + `net_set_errno()` + `NET_ERR_RETURN_1()` 宏, -共替代 82 处赋值-返回对;`net_get_errno()` 对外语义不变)。构建 0 错误 0 警告、 -cppcheck 无告警、`git diff --check` 干净、编码无 `U+FFFD` 且 BOM 状态一致;BSS +64 B。 -未闭环数量 26 → **25**(P1 6 → 5)。**台架硬件闭环未执行**——本机 COM6 未枚举 -(ST-Link 虚拟串口不在,`run_boot_test.py` 报 `open failed`)→ E-03 待补台架回归 -(建议复用既有 FTP/UI 推流路径 + `net_test_task` Phase 7 的 errno 判定分支)。* - -*2026-10-04 订正:同步 2026-10-03 的 **E-34 / E-04 / GD5F 族(E-10 / E-42 / E-53 / E-56 / E-57)** -与 **E-60** 闭环结果——**§0 未闭环 25 → 17**(P1 5→2、P2 20→15),**§2 已闭环 33 → 41** -(P0 3→4、P1 15→18、P2 15→19),§3 各类计数(A 10→5、C 8→5)与 §4 矩阵(清空 GD5F 列) -同步更新。本次订正**仅改计数与索引,未改动任何审计结论**。* - -*2026-10-10 闭环(E-59 门禁的 TPAFE 前置三项,`Drivers/BSP/TPAFE5160/`):**E-44**—— -`read_channels()` 要求"一帧读下来 FRSTDATA 必须发生跳变"(板上 R24 4.7k 上拉,芯片死亡则钉死为高), -`tpafe5160_init()` 增**首帧自检**,不满足即返回 `-3` + `[ERR]`;**E-45**——`s_ready` 电平标志改 -`s_frame_seq` 帧序号(只增不清),`start_conv_irq` 不再清标志;**E-58**——删 `get_buf` / `data_ready` / -`clear_ready`(删除前确认全仓库**零调用者**),新增临界区原子快照 `tpafe5160_snapshot()` 与 -`tpafe5160_frstdata_err()`。`@build` 0 错误 0 警告;真机 `@test` PASS + 烧录回读 `No difference found`; -开机日志 `[ADC] Init OK` 现只在自检帧通过后才打印 → 证明 FRSTDATA 在本板确实跳变、判据不是空转。 -**§0 未闭环 17 → 16**(P1 2→1)、**§2 已闭环 41 → 44**(P1 18→21)、§3.1 A 类 5→4、§4 矩阵去掉 E-58。 -门禁本身已写入 `AGENTS.md`(与"改栈前跑 `stack_report.py`"同级)。* diff --git a/docs/问题记录/项目审查与优化建议报告.md b/docs/问题记录/项目审查与优化建议报告.md index 4b114413..53a87165 100644 --- a/docs/问题记录/项目审查与优化建议报告.md +++ b/docs/问题记录/项目审查与优化建议报告.md @@ -2,83 +2,73 @@ | 项目 | 内容 | |------|------| -| 审查日期 | 2026-09-18 | -| 代码基线 | `HEAD = 2d0ec35`(工作树干净,98+ 提交) | -| 固件基线 | `MDK-ARM/STM32F407-Demo/STM32F407-Demo.map`,构建于 2026-09-16 12:33,0 Error / 0 Warning | -| **现状复核** | **2026-10-11**,复核基线 `HEAD = 54d6d2d`(分支领先 `origin/v2-on-ui`,未推送);固件基线换为 2026-10-11 00:38 链接的 `.map`。⚠ 复核期间分支又前进(ADC L2 相量层 / L1-6 有效性校验 / `adcTask` 抬栈),其数字影响已在 §5.1"基线口径说明"逐项量化,**不改变任何结论** | -| **销项状态** | 本报告采用**修复即销项**:已修复项的正文与表格行已删除(**874 → 733 行**,114 KB → 84 KB),去处清单见文末**变更记录**;未闭环清单见 **§0.2 / §0.4** | -| 审查范围 | 仓库全量(2,013 个受版本控制文件);重点为自研代码 24,353 行(108 文件)与构建产物内存画像 | +| 审查日期 | 2026-09-18(初版) | +| **最近复核** | **2026-10-14**,基线 `HEAD = 803f771`(分支 `v2-on-ui`,工作树干净);固件基线 = 2026-10-14 22:55 链接的 `MDK-ARM/STM32F407-Demo/STM32F407-Demo.map`(0 Error / 0 Warning)。⚠ 2026-10-15 06:41 该 `.map` 被**含未提交改动**的一次重链接覆盖(Code +48 B),本报告数字**不追那次构建**——复跑 `ram_report.py` 会看到小差异,属正常 | +| **销项状态** | 本报告采用**修复即销项**:已修复项的正文与表格行已删除,去处见文末**已销项条目的记录去处**;未闭环清单见 **§0.2 / §0.4** | +| 审查范围 | 仓库全量(2,466 个受版本控制文件 / 79.3 MB);重点为自研代码 50,874 行(191 文件)与构建产物内存画像 | | 审查方式 | 静态审查 + 链接图(.map)量化分析 + 构建脚本/门禁核对;**未做上机动态验证**(除特别标注外) | | 目标 | 结构合理性、文档分布合理性、模块设计合理性、改进路线图、内存优化、UI 标准化 | -> **与既有文档的关系**:本报告不重复 `docs/任务规划/README.md`(逐条缺陷台账)与 `docs/内存管理/RAM使用分析与优化方案.md`(内存专项方案),而是做**体系层评审 + 量化补强 + 新增发现**。新增发现统一编号 `N-xx`,已在台账中的条目按 `P1-x/P2-x/P3-x` 引用。 +> **与既有文档的关系**:本报告不重复 [`../任务规划/README.md`](../任务规划/README.md)(逐条缺陷台账 + **推进顺序唯一权威**)与 `../内存管理/RAM使用分析与优化方案.md`(内存专项方案),而是做**体系层评审 + 量化补强 + 新增发现**。 > -> **销项约定(2026-10-11 起)**:本报告采用与登记簿相同的**「修复即销项」**——问题修复后其分析正文与表格行**从本报告删除**,完整记录沉淀到对应模块文档(去处清单见文末**变更记录**)。因此本报告**只呈现未闭环问题**,不再随时间膨胀。 +> **编号口径**:本报告自有编号 `N-xx / D-x / S-x / R-x / F-x / U1~U5`,⚠ **与登记簿同名不同义**(报告 N-16 = 日志逐条开关,登记簿 N-16 = A2/A4 负向台架;报告 S-3 = 注册反向校验,登记簿 S-3 = USART1 TX-only 根治)→ **引用一律带文档名**。未闭环项已于 2026-10-11 并轨进登记簿(新编 `N-19`~`N-29` + U 簇,出处列保留本报告编号);**本报告编号本身不回改、不重排**(`../编号规范.md`)。 +> +> **销项约定**:问题修复后其分析正文与表格行**从本报告删除**,完整记录沉淀到对应模块文档。因此本报告**只呈现未闭环问题**,不随时间膨胀。 --- ## 0. 执行摘要 -### 0.1 六维评分(2026-09-18 评级 + 2026-10-11 复核) +### 0.1 六维评分(2026-10-14 复核) -| 维度 | 评分 | 一句话结论 | **2026-10-11 复核** | -|------|------|-----------|---------------------| -| 项目结构 | **B+** | 六层分层真实可执行、命名一致、门禁到位;主要问题是 **BSP 层承载业务模块**、**双构建清单漂移**、**5 个源文件两边都不编译** | **A−**:反向依赖 4→3 处、`net_cfg_legacy` 删除、CI + 22 钩子成型、`App/algo/` 分层补齐;❌ 但"存在但未注册"的**反向校验仍缺**(`check_registration.py` 未建),`rs485_task.c` / `lftpd_log.c` 仍两边不编 | -| 文档分布 | **B-** | 工程文档密度与深度高于同级嵌入式项目;主要问题是 **文档占仓库 60% 体积(其中芯片手册图片 40.5 MB)**、**README 任务表 4× 过期**、**约 18 条断链**、**文档承诺了不存在的工具/台账** | **A−**:工程文档 **28,895 行**(翻倍)、`docs/README.md` 索引 + `docs/文档规范.md` + 三档一致性门禁全部落地,`check_doc_links --all` = **129 文档 0 失效**;⚠ 新风险:AGENTS.md 自身数字开始落后实测("18 钩子"实为 22、"清单 23 项"实为 29) | -| 模块设计 | **B** | 配置引擎、网络串行化、就绪屏障、FatFs 互斥等设计成熟;主要问题是 **失败路径普遍"死循环/静默降级"**、**无看门狗**、**FTP 无鉴权**、**cfg_file 依赖注定失败的 malloc** | **B+**:看门狗(IWDG + 心跳位图 + 4 个 Fault 现场 + `.noinit`)、ADC 全链(TIM3 事件驱动 + DMA 乒乓)、卷锁、墙钟均已落地;❌ **FTP 仍无鉴权**、RS485 仍无协议栈(E-50/E-49 未修)、`Error_Handler` 仍 `while(1)` | -| 内存使用 | **C** | SRAM1 已用 96.6%(余 4,488 B),**64 KB CCM 完全空转**,FreeRTOS 堆被 6 条任务栈吃掉 2/3,**栈/堆水位零观测** | **B**:堆 49,184 B 迁 CCM → SRAM1 降到 **69.8%(余 39,584 B)**、CCM 用 75.1%;栈/堆三面观测常开(`[STACK]` + `stack_report.py` + `configCHECK_FOR_STACK_OVERFLOW=2`);🟡 LVGL 池 40,960 B 未迁也未下调;⚠ 新硬约束:**从堆分配的缓冲不可被 DMA 直接寻址** | -| Flash 使用 | **B+** | 仅用 23.4%;但 **≈50 KB(20% 镜像)是可证明的死代码**(LVGL 配置漂移所致) | **A−**:2026-10-11 落地 F-1/F-2/F-3(两份 `lv_conf.h` 对齐、只留 I1 绘制核、零控件、无 flex/grid)→ **省 64,576 B**,占用降到 **20.1%(211,024 B)**;剩余 F-4/F-5/F-6 ≈10 KB 待逐项实测 | -| UI 标准化 | **B-** | 已有"声明源→生成器→表驱动"的正确骨架;缺**设计令牌层**、**页面原型库**、**注册自动化**、**golden 回归**,42 个菜单叶子仅 7 个有内容页 | **B**:**golden 回归已落地**(`simulator/tests/` 4 脚本:菜单 8 状态全像素 + 内容页逐字节黄金参考 + 协议 e2e);❌ 令牌层 / 组件库 / 页注册表未动,现口径为 **33 叶子 / 6 个有内容页** | +| 维度 | 评分 | 一句话结论 | 仍开放的主要缺口 | +|------|------|-----------|-----------------| +| 项目结构 | **A−** | 六层分层真实可执行、命名一致、门禁到位;反向依赖控制在 4 处 | `NETCFG`/`UI` 语义层错位(S-1)、4 处裸 `extern`(S-2)、`rs485_task.c` 仍在注册白名单里等 B2 | +| 文档分布 | **A−** | 工程文档 **88 篇 / 31,416 行**,`docs/README.md` 索引 + `docs/文档规范.md` + 三档一致性门禁全部落地,`check_doc_links --all` = 0 失效 | **D-1**:`docs/` 占仓库 **71.8%**、手册图片 **36.3 MB**、`.git` 已 **235 MB**(已决策暂不处理,登记观察) | +| 模块设计 | **A−** | 看门狗(IWDG + 心跳位图 + 4 个 Fault 现场 + `.noinit`)、就绪屏障、卷锁、FTP 鉴权、`Error_Handler` 自恢复、ADC 全链均已落地 | **N-12/N-13** dhara 移植两处缺陷(数据丢失风险)、**N-16** 日志逐条开关、驱动层无统一错误码约定(S-5)、RS485 无协议栈(随 B2 暂缓) | +| 内存使用 | **B** | 堆迁 CCM + 栈/堆三面观测常开 + `mem-budget` 92% 门禁;SRAM1 **74.9%** | **LVGL 池 + UI 两份缓冲 54,146 B 仍在 SRAM1**,而 CCM 只剩 **7,904 B** → R-2 后半被堵死,须先 R-9(§5.3) | +| Flash 使用 | **A−** | F-1/F-2/F-3 落地省 64,576 B → 占用 **22.0%(231,036 B)** | F-4/F-5/F-6 ≈10 KB + 编译时长(§5.4) | +| UI 标准化 | **B** | golden 回归已建(`simulator/tests/` 四脚本,含池水位门禁) | 无令牌层 / 无组件库 / 无页注册表(手写 `case` 仍在)、**27 页**未落地、golden 与字符集差集未挂门禁(§6) | -### 0.2 未闭环问题(Top 5,2026-10-11) - -> 已修复/已消解的条目按「修复即销项」从本报告删除,完整记录见对应模块文档(去处清单见文末**变更记录**)。 +### 0.2 未闭环问题(Top 3,2026-10-14) | # | 编号 | 问题 | 影响 | 现状 | |---|------|------|------|------| -| 1 | **N-02** | `lftpd.c cmd_pass` 对任意口令无条件回 `230`(现 `:375-379`;`cmd_user :553-557` 同) | 局域网内任意主机可读写 `/config.ini`(含 IP/MAC/网关)→ 可远程断网;单连接 CH395F 上还可挤掉 UI/对时服务 | ❌ 一行未改;⚠ 且**不在 `../任务规划/README.md` 登记簿内** → 建议补登 | -| 2 | **N-04 / R-2 剩余** | LVGL 池 `work_mem_int` 40,960 B + UI 帧/绘制 `ui_port.o` 6,420 + UI 发送 `ui_net_sink.o` 6,766 = **54,146 B 仍在 SRAM1** | 堆迁 CCM 后 **CCM 只剩 16,304 B**,这 54 KB 装不下 → R-2 后半段被堵死 | 🟡 必须先做 R-9(`LV_MEM_SIZE` 40 KB→16 KB)或 R-4(帧副本 4→2)腾出空间 | -| 3 | **N-12 / N-13** | `dhara_nand_is_free()` 只查页首 64 B;`dhara_nand_copy()` 不处理 `DHARA_NO_PAGE` 哨兵 | 映射恢复误判 → **数据丢失风险**(低概率、后果重);哨兵页号被当真实页 → 未定义行为 | ❌ 两处原样;⚠ 也不在登记簿 | -| 4 | **N-06 残余** | `rs485_task.c`、`lftpd_log.c` 既不在 uvprojx 也不在 simulator | "写了不编译"= 死代码 + 误导 | ❌ 未处理;❌ `check_registration.py`(S-3)仍未建 | -| 5 | **N-16** | 日志每条记录一次 `f_open + f_write + f_close`(`sys_log_mcu.c:123-140`) | 每条日志 2–4 次 NAND 页读写;UI 翻记录逐条 open/seek/close | ❌ 未办(仅 `FIL` static 化省 528 B 栈) | +| 1 | **N-12 / N-13**(登记簿 **N-21**,W2) | `dhara_nand_is_free()` 只查页首 64 B(`nand_ftl.c:152-179`);`dhara_nand_copy()` 不处理 `DHARA_NO_PAGE` 哨兵(`:204-216`) | 映射恢复误判 → **数据丢失风险**(低概率、后果重);哨兵页号被当真实页 → 未定义行为 | ❌ 两处原样;至今只能靠人工读代码发现(主机侧无 dhara 移植层测试) | +| 2 | **R-2 剩余 / R-9**(登记簿 **N-27**,W3) | LVGL 池 `work_mem_int` 40,960 B + `ui_port.o` 6,420 + `ui_net_sink.o` 6,766 = **54,146 B 仍在 SRAM1** | 堆 56 KB 占掉 CCM 后 **CCM 只剩 7,904 B**,这 54 KB 装不下 → R-2 后半段堵死 | 🟡 必须先 R-9(`LV_MEM_SIZE` 40→16 KB)+ 缩堆腾 CCM,解锁链 → `../内存管理/RAM预算台账.md` §3 | +| 3 | **N-16**(登记簿 **N-24**,W4) | 日志每条记录一次 `f_open + f_write + f_close`(`sys_log_mcu.c:124-141`) | 每条日志 2–4 次 NAND 页读写;UI 翻记录逐条 open/seek/close | ❌ 未办(仅 `FIL` static 化省 528 B 栈) | -⚠ 另有两条**不在上表但需排期**:`Error_Handler` 仍 `__disable_irq(); while(1){}`(N-03c,§4.1)、`.git` 已膨胀到 **149 MB**(D-1,§3.1)。 +⚠ 另有两条**不在上表但需知晓**:`.git` 已膨胀到 **235 MB**(D-1,§3.1,2026-10-11 决策"暂不处理、登记观察");`rs485_task.c` 仍不在两端构建清单里——**这是 E-59 门禁下的刻意缺口**(随 B2 暂缓),已登记在 `check_registration.py` 的 `KNOWN_GAPS`,不是漏注册。 -### 0.3 三条主线建议 +### 0.3 两条主线建议 -1. **把内存从"卡死"变"富裕"**:启用 CCM 承载 LVGL 池与 UI 帧缓冲(≈54 KB),同时补齐栈/堆水位自检,作为后续功能的预算来源(§5.3)。 -2. **把失败路径从"死循环"变"可恢复"**:IWDG + 任务心跳位图 + 统一的"降级但活着"策略(§4.1)。 -3. **把 UI 从"每页手写"变"声明 + 原型库"**:令牌层 → 原型库 → 生成器补全注册 → golden 回归,使新增一页的成本从 8 处手工改动降到 1 处声明(§6)。 +> **推进顺序的唯一权威是 [`../任务规划/README.md`](../任务规划/README.md) §2**;本节只是体系评审视角的两条结构性建议,不另立顺序。 + +1. **把内存从"卡死"变"富裕"**:先按 R-9 下调 LVGL 池、再缩堆腾出 CCM,把 LVGL 池与 UI 帧缓冲(≈54 KB)搬进 CCM,作为后续功能的预算来源(§5.3)。 +2. **把 UI 从"每页手写"变"声明 + 原型库"**:令牌层 → 原型库 → 生成器补全注册 → golden 入门禁,使新增一页的成本从 8 处手工改动降到 1 处声明(§6)。 --- -## 0.4 未闭环清单(2026-10-11 复核) +## 0.4 未闭环清单(2026-10-14 复核) -> 只列**未办 / 半办**项;已修项已按「修复即销项」删除(去处见变更记录)。图例:❌ 未办 / 🟡 半办。 -> **统一排序与推进**:本表各项已于 2026-10-11 并入 [`../任务规划/README.md`](../任务规划/README.md) §2 与 [`../任务规划/登记簿.md`](../任务规划/登记簿.md) -> (新编号 N-19~N-29 + U 簇,出处列保留本报告编号);**顺序权威在 `docs/任务规划/README.md` §2,本表为评审快照**。 -> ⚠ 本报告编号与登记簿同名不同义(报告 N-16 = 日志逐条开关,登记簿 N-16 = A2/A4 负向台架)→ 引用带文档名。 +> 只列**未办 / 半办**项;已修项按「修复即销项」删除(去处见文末)。图例:❌ 未办 / 🟡 半办。 +> **顺序权威在 [`../任务规划/README.md`](../任务规划/README.md) §2,本表为评审快照**;登记簿编号在括号内。 | 类别 | 编号 | 未闭环内容 | 下一步 | |------|------|-----------|--------| -| **安全** | **N-02** ❌ | FTP 无条件 `230`,任意主机可读写 `/config.ini` | 补登登记簿 + `[ftp] user/pwd_hash` 定长比较 + 失败计数锁定 + 生产默认只读 | -| 安全 | N-03c ❌ | `Error_Handler` 仍 `__disable_irq(); while(1){}`(`Src/main.c:274-282`) | 统一降级策略:记自检 + 禁用该子系统 + 其余照常 | -| **数据正确性** | **N-12 / N-13** ❌ | `is_free` 只查页首 64 B;`copy` 不处理 `DHARA_NO_PAGE` | 补登登记簿 + 整页(或首/中/尾)扫描 + 返回 `DHARA_E_UNSUPPORTED` | -| 数据正确性 | N-14 🟡 | 写回缓存无空闲/掉电强制 flush(整页写与换页已 flush) | 空闲 N ms 由 defaultTask flush | -| **内存 RAM** | **R-2 剩余** 🟡 | LVGL 池 + UI 两份缓冲 54,146 B 仍在 SRAM1;CCM 只剩 16,304 B | 先做 R-9 或 R-4 腾 CCM,再迁 | -| 内存 RAM | R-3 / R-4 / R-5 / R-6 / R-9 ❌ | 页缓冲未合并 / UI 帧 4 副本 / `configMAX_PRIORITIES` 仍 56 / 栈仍动态 / `LV_MEM_SIZE` 仍 40 KB | R-9 最省力(池水位门禁数据已具备);R-6 前置条件已满足 | -| 内存 RAM | N-11 🟡 | NAND 仍直 DMA 调用方缓冲(已加运行时守卫、阈值 1024) | 补编译期/启动期地址断言 + **新铁律:堆缓冲不可被 DMA 直接寻址** | -| 内存 RAM | §5.5 ✅ | 三件套已建(2026-10-12):`scripts/ram_baseline.tsv` + `docs/内存管理/RAM预算台账.md` + `mem-budget` 钩子(92% + Top-10 附表) | 已闭环;R-9/R-2 排期解锁见登记簿 N-27(W3) | -| **Flash** | **F-4 / F-5 / F-6** ❌ | 剩余可评估 ≈10 KB(`box_shadow` 3,246 / `draw_sw_img` 2,960 / `bin_decoder` 1,972 / `image_decoder` 952 / `triangle` 930)+ 双精度 `dadd` 等 ≈1 KB;uvprojx 仍挂 201 个 LVGL `.c`(F-2 后均为空 TU,省的是编译时长) | 逐项实测再关;⚠ `mask`/`mask_rect`/`grad` 是文字与矩形绘制的必经路径,**不可盲关** | -| **性能/资源** | N-09 / N-16 / P2-8 / PHY / P3-x / rs485 ❌ | PASV 每次新建监听;日志每条 open/write/close;`NET_RECV_TIMEOUT_MS 30000`;PHY 强制 100M 无自协商回退;`NET_MAX_SOCKETS 8` 与单连接语义不符;`HAL_Delay(1)` 阻塞 DE | 见 §4.2 / §4.5 / §4.7 各行建议 | -| **结构** | S-1 / S-2 / S-3 / S-4 / N-06 残余 ❌ | `NETCFG`/`UI` 语义层错位;4 处裸 `extern`;**无注册反向校验**;`app_cfg.h` 开关未接线未删;`rs485_task.c`/`lftpd_log.c` 两边不编 | `check_registration.py` 是其中唯一"1 小时工作量"的一条 | -| **文档/仓库** | D-1 ❌ | 图片 36.3 MB 仍在仓;**`.git` 已 149 MB** | 降采样 / 拆 submodule / LFS 三选一 **+ 一次 `git filter-repo` 历史重写** | -| 文档/仓库 | 语义断链 ❌ | 两份 UI 文档仍指 `tools/ui_mock/`(不存在,实为 `simulator/tests/`) | 改指 + 挂进 `ui-verify` | -| 文档/仓库 | 元信息漂移 ⚠ | AGENTS.md 写"18 钩子"(实 22)、"清单 23 项"(实 29) | 纳入生成/校验(同任务表做法) | -| **UI** | U1 / U2 / U3 / U5 ❌ | 无令牌层、无组件库、无页注册表(手写 `case` 仍在);**27 页**未落地 | 按 §6.5 顺序,U4 的 golden 已可当 U1 的验收工具 | -| UI | U4 残余 🟡 | golden 已建但未挂 pre-commit;字符集差集检查仍是约定 | 两个脚本入门禁 | -| **测试** | P1-10 🟡 / 覆盖率 ❌ | CH395F 套件收尾两步;无覆盖率概念;主机侧对 `lftpd`/`dhara` 移植层无测试 | N-12/N-13 这类缺陷至今只能靠人工读代码发现 | -| **台账** | ⚠ | N-02、N-12/N-13 **漏登**;报告 N-18 与登记簿 N-18 **同号不同义** | 补登 + 引用一律带文档名 | +| **数据正确性** | **N-12 / N-13** ❌(N-21,W2) | `is_free` 只查页首 64 B;`copy` 不处理 `DHARA_NO_PAGE` | 整页(或首/中/尾)扫描 + 返回 `DHARA_E_UNSUPPORTED` + **必须配主机侧移植层单测** | +| **内存 RAM** | **R-2 剩余** 🟡(N-27,W3) | LVGL 池 + UI 两份缓冲 54,146 B 仍在 SRAM1;CCM 只剩 7,904 B | 先 R-9 + 缩堆腾 CCM,再迁(解锁链 → `../内存管理/RAM预算台账.md` §3) | +| 内存 RAM | R-3 / R-4 / R-6 / R-9 ❌(N-27,W3) | 页缓冲未合并 / UI 帧 4 副本 / 栈仍动态 / `LV_MEM_SIZE` 仍 40 KB | R-9 最省力(池水位门禁数据已具备);R-6 前置条件已满足。**R-5 已判定不可行**(§5.3) | +| 内存 RAM | N-14 🟡(N-22,W4) | 写回缓存无空闲/掉电强制 flush(整页写与换页已 flush) | 空闲 N ms 由 defaultTask flush | +| **Flash** | **F-4 / F-5 / F-6** ❌(N-28,W4) | 剩余可评估 ≈10 KB(`box_shadow` 3,246 / `draw_sw_img` 2,960 / `bin_decoder` 1,972 / `image_decoder` 952 / `triangle` 930)+ 双精度 `dadd` 等 ≈1 KB;uvprojx 仍挂 201 个 LVGL `.c`(F-2 后均为空 TU,省的是编译时长) | 逐项实测再关;⚠ `mask`/`mask_rect`/`grad` 是文字与矩形绘制的必经路径,**不可盲关** | +| **性能/资源** | N-09 / PHY / P3-x / rs485 DE ❌(N-25,W4) | PASV 每次新建监听;PHY 强制 100M 无自协商回退;`NET_MAX_SOCKETS 8` 与单连接语义不符;`HAL_Delay(1)` 阻塞 DE(DE 项随 B2 暂缓) | 见 §4.2 / §4.7 各行建议 | +| **结构** | S-1 / S-2 / S-4 ❌ | `NETCFG`/`UI` 语义层错位;4 处裸 `extern`;`app_cfg.h` 开关未接线未删 | 随登记簿 C5 处置;`rs485_task.c` 随 B2 一并出白名单 | +| **存储/日志** | N-15 🟡 / P2-10 ❌ | `nand_ftl` 三块 2 KB 页缓冲未合并(BBT 缓冲已收紧);容量轮转 `f_unlink` 与 UI 读并发未纳入卷锁 | R-3 评估;卷锁覆盖面 → 登记簿 S-5 | +| **驱动规范** | S-5 ❌ | 无统一"错误码 + 超时 + 重试 + 自检上报"约定(`GD5F_OK` / `0` / `bool` 混用) | 定义 `bsp_err_t` + `bsp_retry()` 写入 `docs/代码规范/` | +| **仓库** | D-1 ❌(N-29,W7) | 图片 36.3 MB 仍在仓;**`.git` 已 235 MB** | 降采样 / 拆 submodule / LFS 三选一 **+ 一次 `git filter-repo` 历史重写**;**已决策暂不处理、登记观察** | +| **UI** | U1 / U2 / U3 / U5 ❌(U 簇,W6) | 无令牌层、无组件库、无页注册表(手写 `case` 仍在);**27 页**未落地 | 按 §6.5 顺序,U4 的 golden 已可当 U1 的验收工具;**本轮不排期** | +| UI | U4 残余 🟡(U 簇,W6) | golden 已建但未挂 pre-commit;字符集差集检查仍是约定 | 两个脚本入门禁 | +| **测试** | P1-10 🟡 / 覆盖率 ❌ | CH395F 套件收尾两步(构建实测链接 + `@test` 闭环);无覆盖率概念;主机侧对 `dhara` 移植层无测试 | N-12/N-13 这类缺陷至今只能靠人工读代码发现 | --- @@ -88,22 +78,23 @@ | 数据 | 来源 | 可信度 | |------|------|--------| -| 内存/Flash 占用 | `.map` 的 component 表(214 个含码对象,列序 `Code incData RO RW ZI Debug Name`) | 高(构建产物,权威) | -| 总量 | map 尾部 `Total RO / Total RW / Total ROM` + `Program Size` | 高 | +| 内存/Flash 占用 | `.map` 的 component 表(237 个含码对象,列序 `Code incData RO RW ZI Debug Name`) | 高(构建产物,权威) | +| 总量 | map 尾部 `Total RO / Total RW / Total ROM` + `Execution Region` 表 | 高 | | 依赖方向 | 逐文件 `#include` 扫描 + map 的 `refers to` 交叉表 | 高 | -| 构建注册 | `MDK-ARM/STM32F407-Demo.uvprojx` + `simulator/CMakeLists.txt` + git 文件清单三方对齐 | 高 | -| 文档链接 | 全量 md 链接解析(剔除设备路径 `PB12`/通配/占位后 706 → 94 未解析 → 人工分诊) | 中高(人工分诊) | +| 构建注册 | `MDK-ARM/STM32F407-Demo.uvprojx` + `simulator/CMakeLists.txt` + git 文件清单三方对齐(已机械化:`scripts/check_registration.py`) | 高 | +| 文档链接 | `scripts/check_doc_links.py --all`(含 `--sections-strict`) | 高(门禁) | ### 1.2 复现命令(本报告所有数字可重跑) ```powershell -# 1) 构建体量 -Select-String -Path 'MDK-ARM\STM32F407-Demo\STM32F407-Demo.map' -Pattern 'Total RO Size|Total RW Size|Total ROM Size' +# 1) 构建体量(三区水位 + Top-10 RW+ZI) +& '.venv\Scripts\python.exe' scripts/ram_report.py +& '.venv\Scripts\python.exe' scripts/ram_baseline.py --trend # 2) MicroLIB C 堆真实大小(应为 0x200 = 512 B) Select-String -Path 'MDK-ARM\STM32F407-Demo\STM32F407-Demo.map' -Pattern '__heap_base|__heap_limit|HEAP' -# 3) 对象级 RW+ZI 排行(本报告脚本:obj_sizes2.py,注意 map 组件表是 6 个数值列) -# 4) 三方文件注册对齐(uvprojx / CMakeLists / git) -# 5) 仓库体量:git ls-files -z | 逐个统计字节(必须用 -z,否则中文名会被控制台代码页破坏) +# 3) 三方文件注册对齐(uvprojx / CMakeLists / git) +& '.venv\Scripts\python.exe' scripts/check_registration.py --strict +# 4) 仓库体量:git ls-files -z | 逐个统计字节(必须用 -z,否则中文名会被控制台代码页破坏) ``` > ⚠️ **踩坑记录**(供后续审查复用): @@ -116,132 +107,123 @@ Select-String -Path 'MDK-ARM\STM32F407-Demo\STM32F407-Demo.map' -Pattern '__heap | 术语 | 定义 | |------|------| | 自研代码 | `App/`、`Drivers/BSP/`(不含 `Lib/`、`Middlewares/`、HAL/CMSIS)、`test/`;不含 CubeMX 生成的 `Src/`、`Inc/` | -| Flash 占用 | `Code + RO-data`(基线 245,384 B → **2026-10-11 复核 273,860 B**);`ROM` 再加 `RW-data` 初始化表(246,016 → **274,060 B**) | -| RAM 占用 | `RW-data + ZI-data`(基线 126,584 B,全部压在 SRAM1)。**2026-10-11 复核:140,720 B,已跨两个区域** = SRAM1 `RW_IRAM1` 91,488 / 131,072(**69.8%**)+ CCM `RW_CCM` 49,184 / 65,280(**75.3%**)+ `UNINIT_CCM` 48 B(`.noinit` 崩溃现场保留区)。⚠ 口径变化:原报告"CCM 不在 scatter 内计 0"已不成立 | +| Flash 占用 | `Code + RO-data` = `ER_IROM1` **231,036 B(22.0% of 1 MB)**;`ROM` 再加 `RW-data` 初始化表 = 231,324 B | +| RAM 占用 | `RW-data + ZI-data`,**跨两个区域** = SRAM1 `RW_IRAM1` **98,144 / 131,072(74.9%)** + CCM `RW_CCM` **57,376 / 65,280(87.9%)** + `UNINIT_CCM` 48 B(`.noinit` 崩溃现场保留区)。⚠ 原报告"CCM 不在 scatter 内计 0"的口径已不成立 | | "死代码" | 该对象在镜像中有码,但其功能被工程配置(如 `LV_COLOR_DEPTH 1`)或调用点扫描(零调用者)证明不可达 | +> **内存数字的权威源**:区域水位与子系统配额由 `../内存管理/RAM预算台账.md`(受管区,`ram_baseline.py --write` 生成)持有,趋势在 `scripts/ram_baseline.tsv`。本报告只在支撑结论处引用,不与之竞争事实源。 + --- ## 2. 项目结构合理性 -### 2.1 规模与分布(实测) +### 2.1 规模与分布(2026-10-14 实测) | 分类 | 文件 | 行数 | 说明 | |------|------|------|------| -| `App/`(自研应用) | 38 | 8,516 | 配置引擎、墙钟、日志、任务薄壳;**新增 `App/algo/`(L1 采集 + L2 相量,6 文件 1,090 行)** | -| `Drivers/BSP/`(自研驱动/子系统) | 67 | 19,104 | 其中 **UI 子系统 6,036 行**(含 `ui_font_cn_12.c` 1,211)、NET 6,302、CH395F 2,144、GD5F 1,978、SD2506 835、TPAFE 680、NETCFG 578、RS485 310 | -| `test/`(自研测试) | 41 | 10,855 | **`test/host/` 主机侧单测 25 文件 / 4,505 行**(2026-09-18 时不存在)+ `test/board/` 板级套件 16 文件 / 6,350 行 | -| **自研合计** | **146** | **38,475** | 2026-09-18 基线为 **108 / 24,353** → **+38 文件 / +14,122 行** | -| CubeMX `Src/`+`Inc/` | 26 | 5,459 | 受 USER CODE 标记保护 | -| `simulator/` | 10 | 2,462 | PC SDL2 宿主 | -| 第三方 `Lib/` | 958 | 529,916 | LVGL 9.3(423 个 `.c` / 421,546 行)、FreeRTOS、FatFs、dhara、lftpd | +| `App/`(自研应用) | 64 | 13,841 | 配置引擎、墙钟、日志、任务编排;**`App/algo/`(L1~L3 + 测频 + 定值/SOE)14 文件 / 2,855 行** | +| `Drivers/BSP/`(自研驱动/子系统) | 76 | 21,845 | NET 8,264、UI 6,073(含 `ui_font_cn_12.c`)、CH395F 2,641、GD5F 2,035、SD2506 835、NETCFG 766、TPAFE 680、RS485 310 | +| `test/`(自研测试) | 51 | 15,188 | **`test/host/` 主机侧单测 33 文件 / 7,634 行** + `test/board/` 板级套件 18 文件 / 7,554 行 | +| **自研合计** | **191** | **50,874** | 2026-09-18 基线为 108 / 24,353 | +| CubeMX `Src/`+`Inc/` | 26 | 5,528 | 受 USER CODE 标记保护 | +| `simulator/`(自研部分,不含 SDL2 SDK) | 10 | 2,665 | PC SDL2 宿主 | +| 第三方 `Lib/` | 958 | 529,983 | LVGL 9.3、FreeRTOS、FatFs、dhara、lftpd | -**评价**:自研体量与第三方体量比约 **1:14**(原 1:20,自研涨了一倍半),仍属典型的"薄自研 + 厚中间件"。这决定了**结构审查的重点不在自研代码内部,而在"自研如何约束第三方"**——§5.4 曾量化这层约束(`lv_conf.h`)失守的代价:**64,576 B 死代码**(2026-10-11 已修)。 +**评价**:自研体量与第三方体量比约 **1:10.4**(基线 1:20),仍属"薄自研 + 厚中间件"。这决定了**结构审查的重点不在自研代码内部,而在"自研如何约束第三方"**——§5.4 量化的 `lv_conf.h` 约束失守代价(64,576 B 死代码)已修,但**两份 `lv_conf.h` 必须同一特性子集**这条硬约束如今靠 `lv-conf-sync` 钩子守住,属"约束被机械化"的正例。 ### 2.2 分层与依赖方向 -实测自研层间的**全部反向依赖**(BSP → App)——2026-10-11 复核为 **3 处**(原 4 处,`net_cfg_legacy.c` 已随旧档迁移路径整体删除,见 §2.4): +实测自研层间的**全部反向依赖**(BSP → App)——2026-10-14 复核为 **4 处**: | 位置 | 反向 include | 判定 | |------|-------------|------| | `Drivers/BSP/NETCFG/net_cfg.c:31` | `cfg_file.h` | 可接受但需说明:NETCFG 实为"业务配置模块",放在 BSP 层是历史命名 | +| `Drivers/BSP/NETCFG/ftp_cfg.c:17` | `cfg_file.h` | 同上(FTP 鉴权配置随 `net_cfg` 落在同一目录) | | `Drivers/BSP/UI/ui_page_runlog.c:19` | `sys_log.h` | UI 读日志属正常消费者,但 `sys_log` 在 App 层 → 层次倒置 | | `Drivers/BSP/UI/ui_page_selftest.c:19` | `sys_log.h` | 同上 | | `Lib/FatFs/ffsystem.c:19` | `sys_clock.h`(App/util) | 中间件→应用;已在文件内注释声明为"④中间件层"有意为之 | -**评价:良好**。在 38k 行规模上把反向依赖控制在 3 处,说明分层不是墙上的图。但存在**语义层错位**: +**评价:良好**。在 50k 行规模上把反向依赖控制在 4 处,说明分层不是墙上的图。但存在**语义层错位**: -- `Drivers/BSP/NETCFG/`、`Drivers/BSP/UI/` 名字是"板级支持包",内容是**业务**(网络配置语义、菜单/页面/权限/内容页框架)。后果:`sys_log`(App)与 `net_cfg`(BSP)互相引用,层次图自相矛盾。 -- **建议(S-1)**:把 `NETCFG`、`UI` 在概念上重新定性为 `App/` 或新增 `Service/` 层,`Drivers/BSP/` 只留下与引脚/芯片强绑定的 `ch395f`、`gd5f2gq5ue`、`sd2506`、`tpafe5160`、`rs485`、`dbg_*`。迁移成本最低的做法:**先改文档与分层图(零风险),目录迁移留到有 CI 兜底之后**(避免与 §2.3 的注册问题叠加)。 +- `Drivers/BSP/NETCFG/`、`Drivers/BSP/UI/` 名字是"板级支持包",内容是**业务**(网络/FTP 配置语义、菜单/页面/权限/内容页框架)。后果:`sys_log`(App)与 `net_cfg`(BSP)互相引用,层次图自相矛盾。 +- **建议(S-1)**:把 `NETCFG`、`UI` 在概念上重新定性为 `App/` 或新增 `Service/` 层,`Drivers/BSP/` 只留下与引脚/芯片强绑定的 `ch395f`、`gd5f2gq5ue`、`sd2506`、`tpafe5160`、`rs485`、`dbg_*`。迁移成本最低的做法:**先改文档与分层图(零风险),目录迁移留到有 CI 兜底之后**。 -依赖方向裸引用(绕过头文件契约的 `extern`)实测 4 处——**2026-10-11 复核:4 处全部仍在**(仅行号漂移),S-2 未办: +依赖方向裸引用(绕过头文件契约的 `extern`)实测 4 处——**2026-10-14 复核:4 处全部仍在**,S-2 未办: | 位置 | 裸 extern | |------|-----------| -| `Drivers/BSP/CH395F/ch395f.c:178` | `extern volatile uint8_t g_spi1_dma_done;`(实为 GD5F 在 `gd5f2gq5ue.c:50` 定义的变量,跨模块反向且**未进任何头文件**) | -| `Drivers/BSP/GD5F2GQ5UE/gd5f2gq5ue.c:47` | `extern SPI_HandleTypeDef hspi1;`(`Inc/spi.h:35` 已导出,局部重复) | -| `Drivers/BSP/NET/net_socket.c:56` | `extern osMessageQueueId_t netMsgQueueHandle;` | -| `Drivers/BSP/SD2506/sd2506.c:20` | `extern I2C_HandleTypeDef hi2c1;`(`Inc/i2c.h:35` 已导出,局部重复) | +| `Drivers/BSP/CH395F/ch395f.c:177` | `extern volatile uint8_t g_spi1_dma_done;`(实为 GD5F 在 `gd5f2gq5ue.c:50` 定义的变量,跨模块反向且**未进任何头文件**) | +| `Drivers/BSP/GD5F2GQ5UE/gd5f2gq5ue.c:47` | `extern SPI_HandleTypeDef hspi1;`(`Inc/spi.h` 已导出,局部重复) | +| `Drivers/BSP/NET/net_socket.c:77` | `extern osMessageQueueId_t netMsgQueueHandle;` | +| `Drivers/BSP/SD2506/sd2506.c:20` | `extern I2C_HandleTypeDef hi2c1;`(`Inc/i2c.h` 已导出,局部重复) | **建议(S-2)**:CubeMX 句柄类(`hspi1`/`hi2c1`)统一由 `main.h` 导出并 include,删除局部 extern;`g_spi1_dma_done` 应由 GD5F 模块自己定义并在头文件 `extern` 声明(当前跨模块裸 extern 是真正的耦合隐患);`netMsgQueueHandle` 改为 net_socket 内部持有 + 提供投递 API。 -### 2.3 双构建清单漂移(结构性风险最高的一条) +### 2.3 双构建清单漂移 把 **git 文件清单 × uvprojx × simulator/CMakeLists** 三方对齐后: | 类别 | 文件 | 后果 | |------|------|------| -| **两边都不注册** | 基线 5 个:`App/task/adc_task.c`、`App/util/crc.c`、`App/util/ringbuf.c`、`Drivers/BSP/NET/lftpd/lftpd_log.c`、`App/task/rs485_task.c` | **2026-10-11 复核:剩 2 个**——`crc.c`/`ringbuf.c` 已整体删除(`fac9d1c4`,2026-10-02,全库无调用者);`adc_task.c` 已挂 uvprojx(**仅 MCU**,模拟器不编,符合"任务层不入模拟器"的既有做法);❌ **`rs485_task.c`(等 E-50/E-49 修复)与 `lftpd_log.c`(与 `lftpd_log.h` 的 DBG 宏语义重复)仍两边不编** | +| **两边都不注册** | 基线 5 个 → 现剩 **1 个**:`App/task/rs485_task.c`(E-59 门禁下的**刻意**缺口,随 B2 暂缓,已登记 `KNOWN_GAPS`) | `crc.c`/`ringbuf.c` 已整体删除(全库无调用者)、`lftpd_log.c` 已删除并入 `lftpd_auth.c`、`adc_task.c` 已挂 uvprojx(仅 MCU,符合"任务层不入模拟器")——**"写了不编译"的盲区已消** | | **仅 simulator 注册** | `cfg_file_store_sim.c`、`net_socket_sim.c`、`sim_port.c`、`sim_syslog.c`、`ui_net_sink_backend_sim.c` | 正常(后端适配层模式) | -| **uvprojx** | 306 个 `.c`(含 201 个 LVGL)(基线 290) | LVGL 全量编译 → 编译时长;死代码问题被"全编"放大(见 §5.4) | +| **uvprojx** | 323 个 `.c`(含 201 个 LVGL) | LVGL 全量编译 → 编译时长;F-2 落地后未用控件类 TU 均为空目标文件(见 §5.4 F-5) | -**评价**:AGENTS.md 已把"新文件必须注册两处"写成铁律并有 `new-source-file` skill,但**缺少反向校验**("存在但未注册")。这是纯粹的机械检查缺口——**2026-10-11 复核:该缺口未补**(`scripts/` 现有 11 个脚本,无 `check_registration.py`)。 - -**建议(S-3,1 小时工作量)**:新增 `[计划] scripts/check_registration.py`,规则: -1. `App/**.c`、`Drivers/BSP/**.c`(排除 `*_sim.c`、`*_mcu.c` 之外的模式白名单)必须同时出现在 uvprojx 与 CMakeLists; -2. 例外必须写进 `[计划] scripts/registration_allowlist.txt` 并注明原因(如"待 RS485 协议启用"); -3. 挂入 pre-commit 的 `local` hooks(与 `no-mojibake` 同列)。 -这样 §2.3 的 5 个文件会立刻变成"要么注册、要么显式豁免"。 +**评价**:AGENTS.md 的"新文件必须注册两处"铁律如今**两端都有机械保障**——正向由 `new-source-file` skill 约束,反向由 `scripts/check_registration.py`(pre-commit `registration-check` 钩子,2026-10-12)校验"存在但未注册",并反向查"注册了但文件不存在"的幽灵注册。**残余只有一条刻意缺口**:`rs485_task.c` 在 `KNOWN_GAPS` 里等 B2 立项,随该簇一并处置即可(清完后把 `--strict` 设为默认)。 ### 2.4 目录组织与根目录卫生 | 观察 | 证据 | 判定 | |------|------|------| -| 根目录散置工作文件 | `ui_menu_tree.txt`、`ui_cn_chars.txt` | 菜单树/字符集是**生成器输入**,放根目录合理且已在 `docs/` 有权威说明(原点名的根目录 `待处理内容.md` 已并入 `docs/任务规划/README.md` 统一登记簿) | -| `Lib/` 命名 | `Lib/` 下混放 `lvgl/`、`lv_conf.h`、`FatFs/`、`dhara/` | `lv_conf.h` 是**工程配置**不是库文件;建议移到 `Config/lv_conf.h` 并同步 include 路径(低风险、提升可读性) | -| 功能开关名存实亡 | `App/app/app_cfg.h` 的 `APP_NET_ENABLE/APP_ADC_ENABLE/APP_RS485_ENABLE` **全库零引用** | 与台账 **P2-12** 同源。开关不生效比没有开关更糟——评审者会误信"ADC 可裁剪"。**2026-10-11 复核:🟡 已订正表述但未接线**——文件头 v1.1(2026-10-04)已如实标注"预留占位、不门控任何代码",`APP_CCM_ENABLE`/`APP_UI_NET_SINK` 已删除,三个 `APP_*_ENABLE` 仍在且仍零引用 | +| 根目录散置工作文件 | `ui_menu_tree.txt`、`ui_cn_chars.txt` | 菜单树/字符集是**生成器输入**,放根目录合理且已在 `docs/` 有权威说明 | +| `Lib/` 命名 | `Lib/` 下混放 `lvgl/`、`lv_conf.h`、`FatFs/`、`dhara/` | `lv_conf.h` 是**工程配置**不是库文件;建议移到 `Config/lv_conf.h` 并同步 include 路径(低风险;⚠ 须一并处理 `simulator/lv_conf.h`,两份必须同一特性子集) | +| 功能开关名存实亡 | `App/app/app_cfg.h` 的 `APP_NET_ENABLE/APP_ADC_ENABLE/APP_RS485_ENABLE` **全库零引用** | 开关不生效比没有开关更糟——评审者会误信"ADC 可裁剪"。文件头 v1.1 已如实标注"预留占位、不门控任何代码",但**仍未接线也未删**(S-4) | ### 2.5 结构结论 -**总体判断:结构合理,无需重构**。要做的是四类**低成本纠偏**:S-1 层次定性、S-2 消裸 extern、S-3 注册反向校验、S-4 让 `app_cfg.h` 开关真正生效或删除(并把 `APP_CCM_ENABLE`/`APP_UI_NET_SINK` 纳入同一处)。 +**总体判断:结构合理,无需重构**。要做的是三类**低成本纠偏**:S-1 层次定性、S-2 消裸 extern、S-4 让 `app_cfg.h` 开关真正生效或删除。 --- ## 3. 文档分布合理性 -### 3.1 体量画像(实测,工作树;2026-10-11 复核,括号内为 2026-09-18 基线) +### 3.1 体量画像(2026-10-14 实测,工作树) | 对象 | 文件数 | 体积 | 占仓库 | |------|--------|------|--------| -| 仓库受控文件总计 | 2,363(2,013) | **77.7 MB**(78.3 MB) | 100% | -| `docs/` 合计 | 849(652) | **44.4 MB**(47.19 MB) | **57.1%**(60.3%) | -| └ `docs/芯片手册/` 图片 | 703 张(564 张) | **36.3 MB**(40.5 MB) | **46.7%**(51.7%) | -| └ `docs/images/引用关系图.json` | 1 | 1,072 KB(1,052 KB) | 1.3% | -| └ `docs/UI设计/mockups` | 15 | 1,110 KB(1,110 KB) | 1.4% | -| `.git` 目录 | 1,027 | **149.3 MB**(46.6 MB pack) | — | -| 工程文档(Markdown,自研) | 77 篇 / **28,895 行**(40 篇 / 14,124 行) | — | — | -| 外部资料(Markdown,datasheet 转录) | 14 篇 / 58,244 行(16 篇 / 59,142 行) | — | — | +| 仓库受控文件总计 | 2,466 | **79.3 MB** | 100% | +| `docs/` 合计 | — | **56.9 MB** | **71.8%** | +| └ `docs/芯片手册/` 图片 | 703 张 | **36.3 MB** | **45.8%** | +| `.git` 目录 | — | **235.3 MB** | — | +| 工程文档(Markdown,自研) | 88 篇 / **31,416 行** | — | — | +| 外部资料(Markdown,datasheet 转录) | 14 篇 / 58,244 行 | — | — | -**关键失衡(复核后仍在,但已缓解)**:工程文档行数从 14,124 涨到 **28,895**(翻倍),外部资料行数基本不变 → 比例从 1:4.2 改善到 **1:2.0**;但**图片体量(36.3 MB)仍是全部 Markdown 的 4 倍**,`git clone` 的净耗时依旧几乎全花在扫描手册图片上。⚠ 新增事实:`.git` 已膨胀到 **149 MB**(受控文件才 77.7 MB),说明历史里存过多版图片/产物——**D-1 的优先级因此上升**(拆仓或 LFS 之外,还需 `git filter-repo` 类历史清理才能见效)。 +**关键失衡**:工程文档与外部资料的行数比已从 1:4.2 改善到 **1:1.9**,但**全部 Markdown 只有 5.4 MB,图片体量(36.3 MB)是它的 6.7 倍、占仓库近一半**,`git clone` 的净耗时几乎全花在扫描手册图片上。⚠ `.git` 已达 **235 MB**(受控文件才 79.3 MB),说明历史里存过多版图片/产物——**D-1 单靠拆仓或 LFS 不足以解决 clone 耗时,还需一次历史重写**。 ### 3.2 做得好的(应保留并显式制度化) 1. **文档密度与可执行性**:每篇都带"步骤 + 判据"(如 UI 制作流程有 P0→P4 Gate);测试说明按模块拆分并给出预期串口标记,可直接接 `@test`。 -2. **陷阱台账**:`docs/问题记录/*_Trap_Records.md` + `docs/任务规划/README.md`(P1-1..P3-13,带 ✅ 状态与日期)——这是本工程最有价值的资产,多数嵌入式项目没有。 +2. **陷阱台账**:`docs/问题记录/*_Trap_Records.md` + `docs/任务规划/`(带状态与日期)——这是本工程最有价值的资产,多数嵌入式项目没有。 3. **图与脚本同提交**:`tools/arch_diagram/`、`tools/refgraph/` 生成器 + 产物入库,图可再生而非手绘。 4. **UI 设计资产链完整**:原始参考图 → 说明 md → 代码,可追溯。 +5. **文档一致性已机械化**:`doc-links`(含 `--sections-strict`)、`task-table`、`mem-numbers`、`lv-conf-sync`、`cubemx-survival` 等只读钩子守住"文档说的 == 代码做的"。 ### 3.3 问题清单 #### D-1(P1)外部资料与工程文档同仓同树 -**影响**:clone 慢、备份慢、code review 时 diff 噪音大;`docs/芯片手册/` 的 564 张图属于**只读输入资料**,与代码演进无关。 +**影响**:clone 慢、备份慢、code review 时 diff 噪音大;`docs/芯片手册/` 的 703 张图属于**只读输入资料**,与代码演进无关。 **建议**(三选一,按代价递增): -1. **最省**:把 564 张 JPG 降采样到长边 1,200 px(40.5 MB → 约 8 MB),一次性提交并在 `[计划] docs/文档规范.md` 写入"入库图片上限 200 KB/张"; +1. **最省**:把手册 JPG 降采样到长边 1,200 px(约 36 MB → 8 MB),一次性提交并在 `../文档规范.md` 写入"入库图片上限 200 KB/张"; 2. **推荐**:`docs/芯片手册/` 拆为独立仓库 `dtu-datasheets`,用 `git submodule` 挂回原路径(现有全部链接不需改写); 3. **最正**:Git LFS(需要托管端支持)。 -> **2026-10-11 复核:未办,且问题变大**。图片增至 **703 张 / 36.3 MB**(受控文件总量 77.7 MB,图片仍占 46.7%);⚠ 新事实:`.git` 目录已达 **149.3 MB**(1,027 个对象文件),远大于工作树受控内容 → 历史里存了多版图片/日志/产物。**只做 1/2/3 中任一项都不足以解决 clone 耗时,还需一次历史重写(`git filter-repo`)**;重写属破坏性操作(所有克隆需重取),须单独排期并先备份裸仓。 +> ⚠ 三项都不解决历史膨胀:`.git` 235 MB 需一次 `git filter-repo` 历史重写(破坏性操作,所有克隆需重取)。**2026-10-11 已决策:暂不处理、登记观察**(登记簿 N-29),推送/协作前重评——届时未推送提交更少、重写代价最低。 ### 3.4 文档结论 -**内容质量高、分布形态差**。按 D-1..D-7 处理后可从 B- 提升到 A-:工程量约 1 人日(不含拆仓),收益是 clone 时间与"文档可信度"。 - -> **2026-10-11 复核**:**内容质量侧已到位**(评级 A−,见 §0.1;D-2..D-7 已销项),**分布形态侧未动**——未闭环的文档类问题只剩三条: -> - **D-1 ❌**:`docs/` 仍占仓库 **57.1%**、图片仍 **36.3 MB**,且 `.git` 已膨胀到 **149 MB** → 已从"P1 优化"变成影响日常 clone/备份的实际痛点;单靠拆仓或 LFS 不够,**还需一次历史重写**。 -> - **语义断链 ❌**:两份 UI 文档仍指向 `tools/ui_mock/`(该目录从未存在,实现落在 `simulator/tests/`)——路径检查抓不到,属语义层。 -> - **元信息漂移 ⚠**:门禁与文档越多,自身数字也会过期——AGENTS.md 现写"18 个钩子"(实测 `.pre-commit-config.yaml` **22** 个)、"清单 23 项"(实测 `check_cubemx_survival.py` **29** 项)。建议把这类计数也纳入生成/校验,否则报告与规范各说一套。 +**内容质量高(A−)、分布形态差**。D-1 处理后可到 A:工程量约 1 人日(不含拆仓与历史重写),收益是 clone 时间与"文档可信度"。 --- @@ -251,237 +233,195 @@ Select-String -Path 'MDK-ARM\STM32F407-Demo\STM32F407-Demo.map' -Pattern '__heap **设计良好**: - `app_main_init` 顺序有因(墙钟优先,使后续日志带时间戳); -- **Event Flags 就绪屏障**(`NET_READY/STORAGE_READY/FTP_READY/TIMESYNC_READY/CFG_SAVE/NET_RECONFIG`)取代魔法延时,是本项目最漂亮的设计之一; -- 配置引擎属主收敛到 defaultTask 单任务(P1-3 已修),消除了跨任务竞态。 +- **Event Flags 就绪屏障**(`NET_READY/STORAGE_READY/FTP_READY/TIMESYNC_READY`)取代魔法延时,是本项目最漂亮的设计之一; +- 配置引擎属主收敛到 defaultTask 单任务,消除了跨任务竞态; +- 初始化失败路径已收口:`Error_Handler` 打 1 条自检日志 + `NVIC_SystemReset()`(不再 `__disable_irq(); while(1)`)。 **问题**: -| 编号 | 问题 | 证据 | 影响/建议 | -|------|------|------|-----------| -| **N-03c** | 多处初始化失败即挂死(`Error_Handler()` at `main.c:221`;net/ui 初始化失败路径) | | 建议统一降级策略:**外设失败 → 记自检日志 + 该子系统禁用 + 其余照常**(LED/屏上给出降级标识)。**❌ 2026-10-11 复核仍在**:`Src/main.c:274-282` 仍 `__disable_irq(); while(1){}`(IWDG 独立于中断,最终仍会被硬复位,但无降级、无定位信息) | -| P2-12 | `App/app/app_cfg.h` 三个 `APP_*_ENABLE` 零引用 | | 开关要么生效要么删 → **🟡 部分处理**:已如实标注"预留占位、不门控任何代码"(v1.1),未接线也未删 | +| 编号 | 问题 | 影响/建议 | +|------|------|-----------| +| S-4 | `App/app/app_cfg.h` 三个 `APP_*_ENABLE` 零引用(文件头已标注"预留占位") | 开关要么生效要么删;产品定义(登记簿 B0)落定前至少别让评审者误信"可裁剪" | ### 4.2 网络子系统(CH395F + BSD 抽象层) **设计良好**: -- 所有 `net_*` 经 `netMsgQueue`(深 8,`Src/freertos.c:154`)串行到 netTask 执行 → 单线程语义清晰; +- 所有 `net_*` 经 `netMsgQueue` 串行到 netTask 执行 → 单线程语义清晰; - 全部 CH395F 中断集中在 `net_poll()` 内 `GET_GLOB_INT_STATUS` 排空(AGENTS 铁律,已落实); -- 网络代次号机制使"改 IP 免重启"能重建各监听。 +- 网络代次号机制使"改 IP 免重启"能重建各监听; +- **挂起 recv/send 模型**(`net_pending.c/h`)取代"强加 30 s 默认预算",recv 语义已主流化。 **问题**: | 编号 | 问题 | 证据 | 建议 | |------|------|------|------| -| **N-02** | **FTP 无鉴权**:`cmd_pass` 无条件 `send_simple_response(socket, 230, STATUS_230)` | `lftpd.c:367-371`(`cmd_user :539` 亦不校验) | 最小可用方案:`config.ini` 增 `[ftp] user/pwd_hash`,`cmd_pass` 用**定长比较 + 失败计数锁定**;生产固件默认关闭 FTP 写权限(`FF_FS_READONLY` 式的只读模式)。**❌ 2026-10-11 复核:一行未改**(现 `lftpd.c:375-379` / `:553-557`,全文件无 password/auth/attempt/read_only);⚠ 且**未登记进 `docs/任务规划/登记簿.md`** → 建议补登为 P0/P1 | -| N-09 | `cmd_pasv` 每次新建监听 `lftpd_inet_listen(0)`(随机端口) | `lftpd.c:373` | 在**单连接 socket** 上与 UI `:7003`、对时 `:8888` 争用同一物理 socket;建议限定监听资源池 + 显式"FTP 会话独占"策略,并在文档写明"一次只允许一个 TCP 服务活动"。**❌ 复核仍在**(现 `lftpd.c:392`,3 次重试 + 200 ms 间隔) | -| P2-8 | `net_recv` 阻塞超时 30 s | 台账 | 缩短并让上层可取消。**❌ 复核仍在**:`net_config.h:39` `NET_RECV_TIMEOUT_MS 30000` | -| — | PHY 强制 100M 全双工,忽略自协商 | AGENTS/architecture | 与某些交换机会联不通;至少加"链路 up 失败 3 次后回退自协商"。**❌ 复核仍在**:`net_task.c:110` 无条件 `ch395f_set_phy(CH395F_PHY_100M_FULL)`,仅 3 s 链路等待 + DISCONN 时上报 `comms fault` 自检 | -| P3-x | `NET_MAX_SOCKETS 8`(`net_config.h:24-25`)是**软件表**,与硬件单连接语义不符 | | 文档已说明,但 API 层看不到限制 → 建议在 `net_socket.c` 断言"同时 LISTEN 数 ≤ N"。**❌ 复核仍在**(现 `net_config.h:27`,值仍 8) | +| N-09 | `cmd_pasv` 每次新建监听 `lftpd_inet_listen(0)`(随机端口) | `lftpd.c:472` | 在**单连接 socket** 上与 UI `:7003`、对时 `:8888` 争用同一物理 socket;建议限定监听资源池 + 显式"FTP 会话独占"策略,并在文档写明"一次只允许一个 TCP 服务活动" | +| — | PHY 强制 100M 全双工,忽略自协商 | `App/task/net_task.c:110` 无条件 `ch395f_set_phy(CH395F_PHY_100M_FULL)` | 与某些交换机会联不通;至少加"链路 up 失败 3 次后回退自协商" | +| P3-x | `NET_MAX_SOCKETS 8`(`net_config.h:33`)是**软件表**,与硬件单连接语义不符 | | 文档已说明,但 API 层看不到限制 → 建议在 `net_socket.c` 断言"同时 LISTEN 数 ≤ N" | ### 4.3 存储子系统(GD5F2GQ5UE + dhara + FatFs) **设计良好**: - 分层正确:`gd5f2gq5ue`(芯片)→ `nand_ftl`(dhara map/journal + 页缓存)→ FatFs R0.14 → 应用; - `Lib/FatFs/ffsystem.c` 用真实 `osMutexNew` 实现 `FF_FS_REENTRANT=1` 的卷锁(多数移植是空实现)——**这是本工程移植质量的一个亮点**;`get_fattime()` 接系统墙钟,使 FAT 时间戳有意义; -- BBT 保留块池(`gd5f2gq5ue.c:28-40`)把坏块表存储区从 dhara 可见空间中排除,避免被 FTL 擦写。 +- BBT 保留块池把坏块表存储区从 dhara 可见空间中排除,避免被 FTL 擦写; +- **DMA 可达性三层防线**(原 N-11 的处置,2026-10-11):编译期窗口断言(`gd5f2gq5ue.h:96-107` 负数组 typedef,DMA 白名单窗口与 CCM 永不得重叠 + 阈值契约)→ 启动期自检(`nand_ftl.c:241-243` 三块静态页缓冲逐一过守卫)→ 运行时强制(`gd5f_dma_buf_ok()` 不可达即回落轮询 + `[ERR]`);规则本身(**R2 DMA 缓冲只允许 SRAM 静态**、**R3 堆/栈禁为 DMA 源/目的**)已写进 `../内存管理/内存内容分配策略.md` §4,并有 TC-GD5F-704/705 负路径用例实证。**缓冲自身地址无法编译期断言**(`.bss` 地址链接期才定,AC5 取不到)——这条边界在头文件注释里写明,别再试图"加个 `_Static_assert`"。 **问题**: | 编号 | 问题 | 证据 | 影响/建议 | |------|------|------|-----------| -| **N-11** | NAND 驱动**直接对调用方缓冲做 DMA**:`HAL_SPI_Receive_DMA(&hspi1, p_buf, size)`(`:200`)、`HAL_SPI_Transmit_DMA(&hspi1, (uint8_t*)p_buf, size)`(`:258`),阈值仅 32 B | `gd5f2gq5ue.c:185-217,246-270` | **这是 CCM 迁移的硬边界**:所有走 NAND 的缓冲区(含 `FIL`、FatFs window、任务栈上的临时缓冲)**必须留在 SRAM1**。必须在 `docs/内存管理/` 写成铁律,否则一旦把某任务栈搬进 CCM 就会静默 DMA 失败。**2026-10-11 复核:🟡 模式未变但已加固**——阈值改为 `GD5F_DMA_THRESHOLD 1024`(`gd5f2gq5ue.h:78`),并新增**运行时 DMA 可达性守卫**(`:110-136`,不可达自动回落轮询)。⚠ 复核新发现:堆已迁 CCM(`ucHeap @0x10000020`)→ **"从 FreeRTOS 堆分配的缓冲不可被任何 DMA 直接寻址"成为全局铁律**,比原结论更硬 | -| **N-12** | `dhara_nand_is_free()` 只检查页首 64 B | `nand_ftl.c:137-154` | dhara 依赖该判定做映射恢复;页首 64 B 全 FF 但后半页有数据时会误判为空闲 → 数据丢失风险(低概率但后果重)。建议整页扫描或至少首/中/尾三处采样。**❌ 复核仍在**(现 `nand_ftl.c:152-179`,仍 `uint8_t buf[64]`) | -| **N-13** | `dhara_nand_copy()` 未处理 `DHARA_NO_PAGE` 语义(dst 为哨兵值时仍执行"读+编程") | `nand_ftl.c:174-186` | dhara map 在回收/擦除路径会用 `DHARA_NO_PAGE` 试探;当前实现会把它当作真实页号 → 未定义行为。建议:`if (dst == DHARA_NO_PAGE) { dhara_set_error(err, DHARA_E_UNSUPPORTED); return -1; }`(返回不支持是安全语义)。**❌ 复核仍在**(现 `:204-216`,read→prog 直传) | -| N-14 | 写回缓存 `s_cache_buf/s_cache_dirty` 无掉电保护策略:`CTRL_SYNC` 已实现(`:343-353`)但**没有周期性或掉电前的强制 flush** | `nand_ftl.c:291-353` | 掉电窗口 = 最后一次未跨页的写。建议:空闲 N ms 由 defaultTask flush + 掉电检测(若硬件支持)或至少"日志落盘后立即 fsync"。**🟡 复核:部分改善**——整页写立即 flush(`:377-381`)、换页时 flush(`:365-368`)、`CTRL_SYNC` 走 flush + `dhara_map_sync`(`:400-406`);仍无空闲定时 flush | -| P2-3/P2-4 | 部分返回值丢失 | 台账 | 逐条修 | -| N-15 | 页缓冲重复:`nand_ftl` 3×2,048(`:s_page_buf/s_cache_buf/s_copy_buf`)+ `gd5f` 的 `s_bbt_page_buf[2048]`(仅 init 期使用) | 对象 ZI:nand_ftl 6,216 + gd5f 2,596 | BBT 页缓冲可与 `s_copy_buf` 复用或 init 后归还(§5.3 R-3,省 2–4 KB)。**🟡 复核:已省一半**——`s_bbt_page_buf` 由 2,048 B 收紧为 **272 B**(2026-09-21);`nand_ftl` 三块 2 KB 未合并(对象 ZI 仍 6,216) | -| — | `disk_ioctl GET_BLOCK_SIZE = 1` | `nand_ftl.c` | FatFs 的擦除块提示失效(FAT32 对齐优化拿不到信息);建议返回 64 页(=1 物理块)。**❌ 复核仍在** | +| **N-12** | `dhara_nand_is_free()` 只检查页首 64 B | `nand_ftl.c:152-179`(仍 `uint8_t buf[64]`) | dhara 依赖该判定做映射恢复;页首 64 B 全 FF 但后半页有数据时会误判为空闲 → 数据丢失风险(低概率但后果重)。建议整页扫描或至少首/中/尾三处采样 | +| **N-13** | `dhara_nand_copy()` 未处理 `DHARA_NO_PAGE` 语义(dst 为哨兵值时仍执行"读+编程") | `nand_ftl.c:204-216`(read→prog 直传) | dhara map 在回收/擦除路径会用 `DHARA_NO_PAGE` 试探;当前实现会把它当作真实页号 → 未定义行为。建议:`if (dst == DHARA_NO_PAGE) { dhara_set_error(err, DHARA_E_UNSUPPORTED); return -1; }` | +| N-14 | 写回缓存 `s_cache_buf/s_cache_dirty` 无掉电保护策略:`CTRL_SYNC` 已实现但**没有空闲时强制 flush** | `nand_ftl.c:418` | 掉电窗口 = 最后一次未跨页的写。已改善:整页写立即 flush、换页时 flush、`CTRL_SYNC` 走 flush + `dhara_map_sync`;仍无空闲定时 flush → 建议 defaultTask 空闲 N ms flush | +| N-15 | 页缓冲重复:`nand_ftl` 3×2,048(`s_page_buf/s_cache_buf/s_copy_buf`) | 对象 ZI:nand_ftl 6,216 | 与 `s_copy_buf` 复用可省 2 KB(§5.3 R-3)。🟡 `gd5f` 的 `s_bbt_page_buf` 已由 2,048 B 收紧为 272 B | +| — | `disk_ioctl GET_BLOCK_SIZE = 1` | `nand_ftl.c:442-444` | FatFs 的擦除块提示失效(FAT32 对齐优化拿不到信息);建议返回 64 页(=1 物理块) | -### 4.4 配置引擎(`App/cfg/cfg_file.c`,1,190 行) +### 4.4 配置引擎(`App/cfg/cfg_file.c`,1,343 行) -**设计良好**:描述符 + 段注册表、外科式(surgical)保存、模板生成、2 s 轮询(`cfg_file_poll`)响应 `SYS_EVT_CFG_SAVE`,属主单任务化。这是全项目设计最完整的一个模块。(原"首启旧档迁移"已于 2026-10-10 删除——该一次性路径把整条配置写链挂进启动期调用图,defaultTask 静态最坏链 +600 B。) +**设计良好**:描述符 + 段注册表、外科式(surgical)保存、模板生成、2 s 轮询(`cfg_file_poll`)响应 `SYS_EVT_CFG_SAVE`,属主单任务化。这是全项目设计最完整的一个模块。销毁用户文件的路径(超长/损坏)已补自检留痕(`cfg load oversized` / `cfg load read fail` → `SYSLOG_SELFTEST`),现场无调试器时可追溯。 **问题**: | 编号 | 问题 | 建议 | |------|------|------| -| P2-1 | 超长键行处理 | 台账 | -| — | 失败即静默回落工厂默认(`:955-988`:缺文件→模板→return -1;超长→模板→return -1;读失败→"factory defaults kept") | 语义本身合理(不阻塞组网),但**用户改了 IP 重启就丢且无告警**——建议回落时必写一条自检记录并在 UI 装置信息页给出"当前为出厂配置"标识 | +| — | 读失败仍静默回落工厂默认(`:1071-1102`),语义合理(不阻塞组网),但**用户改了 IP 重启就丢,串口之外无提示** | 装置信息页给出"当前为出厂配置"标识(自检记录已有,UI 侧未接) | ### 4.5 日志子系统(`App/log/sys_log_mcu.c`) -**设计良好**:定长 64 B 记录(`SYSLOG_REC_SIZE`)、单写者(defaultTask)、RAM 队列 + `syslog_drain`、容量满时删除重建、`/log` 目录惰性创建。 - -> **2026-10-11 复核:日志侧新增了两个基线没有的东西,值得记一笔。** -> ① **控制台单写者日志**(`App/log/console_log.c`,对象 ZI 4,120 B)——把"多任务直接 `printf` 到 USART1"收敛为单写者队列,配套 `DBG_*` 宏;代价已量化进 AGENTS.md(**每条日志约 90 B Flash + 216 B 栈**,深调用链模块另以 `#define DBG_LINE_BUF_MAX 128U` 收紧)。⚠ 这条代价模型正是 defaultTask 静态最坏链从 5,312 → 2,776 B 的主要杠杆,**属本报告"内存"章节应当吸收的新事实**。 -> ② **自检记录可查**:`selftest.log` + 开机 `selftest: no fault record` 标记 + Fault 现场 `.noinit` 跨复位 → 现场故障从"不可知"变成"可回读"。 +**设计良好**:定长 64 B 记录(`SYSLOG_REC_SIZE`)、单写者(defaultTask)、RAM 队列 + `syslog_drain`、容量满时删除重建、`/log` 目录惰性创建(`mkdir` 失败保持未就绪 + 幂等重试 + 上报);`/log/selftest.log` + 开机自检锚点 + Fault 现场 `.noinit` 跨复位 → 现场故障从"不可知"变成"可回读"。控制台侧另有**单写者队列**(`App/log/console_log.c`,对象 ZI 4,120 B)把"多任务直接 `printf` 到 USART1"收敛为一条流;代价已量化进 AGENTS.md(**每条日志约 90 B Flash + 216 B 栈**,深调用链模块另以 `#define DBG_LINE_BUF_MAX 128U` 收紧)。 **问题**: | 编号 | 问题 | 证据 | 建议 | |------|------|------|------| -| **N-16** | **每条记录一次 `f_open + f_write + f_close`**(`:89-95`),读侧同样每条 `f_open/f_lseek/f_close`(`:118-126`) | 每条日志触发 FatFs 目录项读写 → 2–4 次 NAND 页读写;UI 翻记录时逐条 open/seek/close | 写侧:常驻 `FIL`(仅 defaultTask 持有)+ 批量 flush;读侧:打开一次 + 缓存目录偏移(UI 已有 `s_cache_valid/s_last_rec` 雏形,可升级为页缓存)。**❌ 2026-10-11 复核仍在**:`sys_log_mcu.c:123-140` 每条仍 open/write/close(失败重建重试一次),`wdev.log` 同模式(`:481-484`);🟡 唯一改善 = `FIL` 已 static 化(`:70`,省 528 B 栈),**无常驻打开句柄、无批量 flush** | -| P2-9 | `mkdir` 失败后不再重试(`s_fs_ready` 逻辑,`:158-165`) | 台账 | 失败时保持未就绪 + 定期重试 + 上报自检 | -| P2-10 | 容量轮转 `f_unlink`(`:64`)与 UI 读并发 | 台账 | 轮转只在写者、读者持版本号 | +| **N-16** | **每条记录一次 `f_open + f_write + f_close`**,读侧同样每条 `f_open/f_lseek/f_close` | `sys_log_mcu.c:124-141`(失败重建重试一次)、`wdev.log` 同模式 `:486-489` | 写侧:常驻 `FIL`(仅 defaultTask 持有)+ 批量 flush;读侧:打开一次 + 缓存目录偏移(UI 已有 `s_cache_valid/s_last_rec` 雏形,可升级为页缓存)。🟡 唯一改善 = `FIL` 已 static 化(省 528 B 栈) | +| P2-10 | 容量轮转 `f_unlink` 与 UI 读并发 | | 轮转只在写者、读者持版本号;卷锁覆盖面评估 → 登记簿 S-5 | ### 4.6 UI 子系统 -见 §6 专题(同时是"标准化"章节)。此处仅记设计层结论:三层拆分(数据 `ui_menu_tab` / 视图 `ui_menu_view` / 控制器 `ui_menu`)+ 内容页描述符框架**方向正确**,但**框架未收敛到"新页面零框架改动"**,且 4 份 3,200 B 帧副本占 12.8 KB RAM(§5)。 - -> **2026-10-11 复核:两条结论均未变化。** 框架仍要求"改 `ui_menu.c` 的手写 `case` + 加实例文件 + 双端注册"才能新增一页(§6.2 的 8 步一字未减);帧副本仍 4 份(`ui_port.o` ZI 6,420 + `ui_net_sink.o` 6,766 = 13,186 B,R-4 未办)。✅ 唯一实质改善在**验证侧**:golden 回归已建(§6.1),UI 改动从此有逐像素/逐字节基线可依赖。 +见 §6 专题(同时是"标准化"章节)。此处仅记设计层结论:三层拆分(数据 `ui_menu_tab` / 视图 `ui_menu_view` / 控制器 `ui_menu`)+ 内容页描述符框架**方向正确**,但**框架未收敛到"新页面零框架改动"**(`menu_content_page_for()` 仍是手写 `case`,`ui_menu.c:118-128`),且 4 份 3,200 B 帧副本占 12.8 KB RAM(§5)。✅ 验证侧已到位:golden 回归已建(§6.1),UI 改动从此有逐像素/逐字节基线可依赖。 ### 4.7 外设驱动 -| 模块 | 评价 | 问题 | -|------|------|------| -| `sd2506`(RTC) | I2C 超时齐备,降级策略明确(RTC 故障→默认时基 + 自检上报),**良好** | P2-5(负温度解析)、P2-6(WRTC 解锁) | -| `tpafe5160`(并行 ADC) | BUSY 超时、EXTI 读取路径齐备;取数已收口为 `tpafe5160_snapshot()` 原子快照(帧序号只增不清 + FRSTDATA 自检) | ⚠ **V0.1 硬件缺陷(登记簿 N-18,V0.2 改版项;注意与本报告 N-18 同号不同义)**:8 路电气量被分给两片转换器 → 主板侧**禁止**跨转换器做相位/功率/功率因数运算;并行总线无批量采样框架,16 位端口读依赖 `gpio.c` 初值 | -| `rs485` | 结构简单 | DE 时序用 `HAL_Delay(1)` **阻塞 1 ms**(在调用任务里);`crc.c`/`ringbuf.c` 未注册 → 无帧校验、无接收环。**❌ 复核:`HAL_Delay(1)` 仍在(`rs485.c:91`)**;`crc.c`/`ringbuf.c` 已删除(2026-10-02)→ 帧校验/接收环**从零重做**;`rs485_task.c` 仍未注册,消费者被 E-50(包撕裂)/E-49(TX 超时拉 DE)挡住 | -| `ch395f` | 事务包裹 `begin/end`、SPI 分频 8 为 PCB 约束(已文档化,勿动) | 静态 DMA 缓冲 2×2,052 B(obj ZI 4,105),必须留 SRAM1 | +| 模块 | 评价 | 未闭环问题 | +|------|------|-----------| +| `sd2506`(RTC) | I2C 超时齐备,降级策略明确(RTC 故障→默认时基 + 自检上报),WRTC 解锁顺序按手册、负温度符号扩展已处理 | — | +| `tpafe5160`(并行 ADC) | BUSY 超时、EXTI 读取路径齐备;取数已收口为 `tpafe5160_snapshot()` 原子快照(帧序号只增不清 + FRSTDATA 自检) | ⚠ **V0.1 硬件缺陷(登记簿 N-18,V0.2 改版项)**:8 路电气量被分给两片转换器 → 主板侧**禁止**跨转换器做相位/功率/功率因数运算;并行总线无批量采样框架,16 位端口读依赖 `gpio.c` 初值 | +| `rs485` | 结构简单 | DE 时序用 `HAL_Delay(1)` **阻塞 1 ms**(在调用任务里,`rs485.c`);无帧校验、无接收环(`crc.c`/`ringbuf.c` 已删除 → **需从零重做**);`rs485_task.c` 仍未注册,消费者被 E-50(包撕裂)/E-49(TX 超时拉 DE)挡住(随 B2 暂缓) | +| `ch395f` | 事务包裹 `begin/end`、SPI 分频 8 为 PCB 约束(已文档化,勿动) | 静态 DMA 缓冲 2×2,052 B(obj ZI 4,162),必须留 SRAM1 | -**驱动共性缺口**:没有统一的"错误码 + 超时 + 重试 + 自检上报"约定,各模块自定返回码(`GD5F_OK` / `0` / `bool` 混用)。**建议(S-5)**:定义 `bsp_err_t` 统一错误码 + `bsp_retry()` 辅助 + 所有失败必须走 `syslog(SYS_SELFTEST,...)`,作为驱动编写规范写入 `docs/代码规范/`。 +**驱动共性缺口**:没有统一的"错误码 + 超时 + 重试 + 自检上报"约定,各模块自定返回码(`GD5F_OK` / `0` / `bool` 混用)。**建议(S-5)**:定义 `bsp_err_t` 统一错误码 + `bsp_retry()` 辅助 + 所有失败必须走自检记录,作为驱动编写规范写入 `docs/代码规范/`。 ### 4.8 测试体系 -**设计良好**:`test/board/test_config.h` 的 `TEST_SUITE_*` 互斥(`#error` 保护)、CH395F 硬件层/NET 层 Phase 互斥、`@test` 一键闭环(烧录→wait-idle→标记校验)、模拟器 `--cfgtest/--logtest`。 +**设计良好**:`test/board/test_config.h` 的 `TEST_SUITE_*` 互斥(`#error` 保护)、CH395F 硬件层/NET 层 Phase 互斥、`@test` 一键闭环(烧录→wait-idle→标记校验)、模拟器 `--cfgtest/--logtest`、主机侧单测 15 个钩子(含 `sha256` / `ftp_auth` / `vol_lock` / 采集算法族)。 -> ⚠ 仍缺:**覆盖率概念**(原建议未办)、**注册检查**(`check_registration.py`)、**主机侧对 `lftpd`/`dhara` 移植层的测试**(N-12/N-13 这类缺陷至今只能靠人工读代码发现)。 +> ⚠ 仍缺:**覆盖率概念**、**主机侧对 `dhara` 移植层的测试**(N-12/N-13 这类缺陷至今只能靠人工读代码发现)。 **问题**: | 编号 | 问题 | 建议 | |------|------|------| -| P1-10 | CH395F 硬件测试套件 RAM 溢出(`L6406E`):HW 套 +8.7 KB、NET 层固件 +28 KB(`s_rx_buf` 32 KB) | 测试缓冲改为**分 Phase 复用同一块静态缓冲**(union 或分阶段 `memset`);`docs/任务规划/测试与架构专项.md` 的这条待办应升级为正式任务。**🟡 2026-10-11 复核:已升级为登记簿唯一 P 类收尾项**(RAM 侧因堆迁 CCM 大幅缓解,剩"CH395F 套件构建实测链接确认 + `@test` 闭环"两步) | +| P1-10 | CH395F 硬件测试套件静态 RAM 预算(`L6406E` 历史:HW 套 +8.7 KB、NET 层固件 +28 KB) | 基线已重建、测试缓冲已瘦身、RAM 侧因堆迁 CCM 大幅缓解;**剩"套件构建实测链接确认 + `@test` 闭环"两步**(登记簿唯一 P 类收尾项,W5) | --- ## 5. 内存优化(量化) -### 5.1 实测基线(map) +### 5.1 实测基线(2026-10-14 `.map`) -| 指标 | 2026-09-16(审查基线) | **2026-10-11 复核** | 上限 | 复核占用 | -|------|----------------------|--------------------|------|---------| -| `Code + RO-data` | 245,384 B(239.6 KB) | **273,860 B**(267.4 KB) | 1 MB Flash | **26.1%** | -| `ROM`(含 RW 初始化表) | 246,016 B | **274,060 B** | 1 MB | 26.1% | -| `RW-data` | 632 B | 824 B | — | — | -| `ZI-data` | 125,952 B | **139,896 B** | — | — | -| **SRAM1(0x20000000,128 KB)** | **126,584 B / 96.6%(余 4,488 B)** | **91,488 B** | 131,072 B | **69.8%(余 39,584 B)** | -| **CCM(0x10000000,64 KB)** | **0 B / 0%** | **49,232 B**(`RW_CCM` 49,184 + `UNINIT_CCM` 48) | 65,536 B | **75.1%(余 16,304 B)** | -| `Total RW (RW+ZI)` | 126,584 B | **140,720 B** | — | — | +| 指标 | 数值 | 上限 | 占用 | +|------|------|------|------| +| `Code + RO-data`(`ER_IROM1`) | 231,036 B | 1 MB Flash | **22.0%** | +| `ROM`(`Code + RO + RW` 初始化表) | 232,148 B | 1 MB | 22.1% | +| **SRAM1(0x20000000,128 KB)** `RW_IRAM1` | **98,144 B** | 131,072 B | **74.9%(余 32,928 B)** | +| **CCM(0x10000000,64 KB)** `RW_CCM` | **57,376 B** | 65,280 B | **87.9%(余 7,904 B)** | +| `UNINIT_CCM`(`.noinit` 崩溃现场) | 48 B | 256 B | 18.8% | -> **2026-10-11 二次实测(LVGL 死代码裁剪后,F-1/F-2/F-3 已落地)**:`Code=194,276 / RO=15,872 / RW=876 / ZI=139,892` → `ER_IROM1` **210,148 B(20.0%)**、`ROM 合计` **211,024 B(20.1%)**;`RW_IRAM1` 91,536 B(69.8%)与 `RW_CCM` 49,184 B(75.3%)**不变**(本次只动 Flash)。§5.4 的口径以此为准。 +> **权威源**:区域水位与子系统配额 → `../内存管理/RAM预算台账.md`(受管区);趋势 → `scripts/ram_baseline.tsv`(`ram_baseline.py --trend` 高亮 RW+ZI 增长 >1 KB 的对象)。本节只列支撑结论所需的数。 -**复核结论:R-2(启用 CCM)已部分落地**——`.sct` 现为手工 scatter,除 `ER_IROM1` + `RW_IRAM1 0x20000000 0x00020000` 外新增 `RW_CCM 0x10000000 0x0000FF00`(**只收 `heap_4.o (+RW +ZI)`**)与 `UNINIT_CCM 0x1000FF00 UNINIT 0x00000100`(`*(.noinit)`,崩溃现场跨复位保留区)。→ **FreeRTOS 堆 `ucHeap` 49,152 B 已迁 CCM**(.map `ucHeap 0x10000020`),任务栈从此不占 SRAM1,SRAM1 因此从 96.6% 降到 **69.8%**。⚠ **未迁的部分仍在 SRAM1**:LVGL 池 `work_mem_int` 40,960 B(`0x20008d00`)、UI 帧/绘制缓冲 `ui_port.o` 6,420 B、UI 网络发送 `ui_net_sink.o` 6,766 B——即 R-2 的"−54,122 B"只兑现了 49,184 B(堆),**LVGL 池与 UI 缓冲这 54 KB 里还剩 54,146 B 在 SRAM1**(见 §5.3 R-2 状态)。 - -> **基线口径说明**:上表取 **2026-10-11 00:38 链接的 `.map`(HEAD `54d6d2d`,干净树)**。复核期间工作区另有**未提交**改动(`adcTask` 栈 512→**640 words**、新增 `App/algo/adc_health.c/h` + 主机侧套件),其 01:12 重链接结果为 `Code=258,520 / RO=16,208 / RW=880 / ZI=139,896` → `Code+RO` **274,728 B(26.2%)**、`RW_IRAM1` **91,544 B(69.8%)**、`RW_CCM` 49,184 B(75.3%)。差异仅 +868 B Code / +56 B SRAM1,**不影响本节任何结论**;栈迁 CCM 后任务栈大小变化只影响堆内占用,不改 `ZI` 总量。 - -**RW+ZI 主要对象(2026-10-11 复核,`scripts/ram_report.py` 排行;括号内为 2026-09-16 基线)** +**RW+ZI 主要对象(`ram_baseline.tsv` 末行 Top-10)** | 对象 / 符号 | 字节 | 所在区域 | 归属 | DMA 可达性 | |-------------|------|---------|------|-----------| -| `ucHeap`(`heap_4.o`) | 49,184(49,184) | **CCM**(`0x10000020`) | FreeRTOS 堆 | **DMA 不可达**——凡从堆里分配且被 DMA 直接寻址的缓冲都会失效(N-11 由此升级为**结构性约束**,见下) | -| `work_mem_int`(`lv_mem_core_builtin.o`,= `LV_MEM_SIZE 40*1024`) | 40,960(40,960) | **SRAM1**(`0x20008d00`) | LVGL 池 | 纯 CPU,**可迁 CCM(R-2 未完成项)** | -| `ui_net_sink.o`(`g_tx` + `g_lcdmem_data` + `g_rx`) | 6,766(6,762) | SRAM1 | UI 网络发送 | 可迁 CCM(CH395F 侧 memcpy 到自有 SRAM1 缓冲) | -| `ui_port.o`(`g_ui_fbuf` + `g_ui_draw_buf`) | 6,420(6,412) | SRAM1 | UI 帧/绘制缓冲 | 可迁 CCM | -| `nand_ftl.o`(3×2,048 页缓冲 + 控制块) | 6,216(6,216) | SRAM1 | 存储 | **必须 SRAM1**(N-11) | -| `lftpd.o` | 4,768 | SRAM1 | FTP | 任意 | +| `ucHeap`(`heap_4.o`) | 57,376 | **CCM**(`0x10000020`) | FreeRTOS 堆(`configTOTAL_HEAP_SIZE` 56 KB + 元数据) | **DMA 不可达**——规则 R3「堆/栈禁为 DMA 源/目的」已写进 `../内存管理/内存内容分配策略.md` §4,并由 `gd5f_dma_buf_ok()` 运行时强制 | +| `work_mem_int`(`lv_mem_core_builtin.o`,= `LV_MEM_SIZE 40*1024`) | 40,960 | **SRAM1** | LVGL 池 | 纯 CPU,**可迁 CCM(R-2 未完成项)** | +| `lftpd.o` | 7,884 | SRAM1 | FTP(含鉴权) | 任意 | +| `ui_net_sink.o`(`g_tx` + `g_lcdmem_data` + `g_rx`) | 6,766 | SRAM1 | UI 网络发送 | 可迁 CCM(CH395F 侧 memcpy 到自有 SRAM1 缓冲) | +| `ui_port.o`(`g_ui_fbuf` + `g_ui_draw_buf`) | 6,420 | SRAM1 | UI 帧/绘制缓冲 | 可迁 CCM | +| `nand_ftl.o`(3×2,048 页缓冲 + 控制块) | 6,216 | SRAM1 | 存储 | **必须 SRAM1**(规则 R2/R3) | +| `ch395f.o`(`s_spi2_dma_tx/rx[2052]`) | 4,162 | SRAM1 | 网络 | **必须 SRAM1** | | `console_log.o`(行环形缓冲) | 4,120 | SRAM1 | 日志 | 任意(TX 走 DMA → 实际必须 SRAM1) | -| `ch395f.o`(`s_spi2_dma_tx/rx[2052]`) | 4,106(4,105) | SRAM1 | 网络 | **必须 SRAM1** | -| `default_task.o` | 2,728 | SRAM1 | 任务薄壳 | — | -| `gd5f2gq5ue.o`(`s_bbt_page_buf[2048]` + BBT 表) | ≈2,596(2,596) | SRAM1 | 存储 | **必须 SRAM1** | +| `default_task.o` | 2,736 | SRAM1 | 任务薄壳 | — | | `sys_log_mcu.o` | 1,924 | SRAM1 | 日志 | 任意 | -| `cmsis_os2.o` | 1,724(1,724) | SRAM1 | RTOS 胶水 | 任意 | -| `sample_ring.o`(L2 输入 2 周波环缓冲,**新增**) | 1,448 | SRAM1 | 算法 | 任意 | -| `startup_...o`:`STACK 0x400`(`HEAP 0x200` 已被链接器移除) | 1,024(1,536) | SRAM1 | MSP + MicroLIB 堆 | 任意 | -| `tasks.o`(含 `pxReadyTasksLists[56]` = 1,120) | 1,280(1,280) | SRAM1 | RTOS | 任意(`configMAX_PRIORITIES` 仍 56 → **R-5 未办**) | -**两个静态池(49,184 + 40,960 = 90,144 B)仍是 RAM 问题的核心**,但**已分居两区**:堆在 CCM(占 CCM 的 75.3%)、LVGL 池在 SRAM1(占 SRAM1 的 45%)。→ 下一步收益点在**把 LVGL 池 + UI 两份缓冲(54,146 B)也搬进 CCM**,或按 R-9 先实测下调 `LV_MEM_SIZE`。⚠ 堆进 CCM 带来一条新铁律:**从 FreeRTOS 堆分配的缓冲不可被 DMA 直接寻址**(DMA2 不可达 `0x10000000`)——本仓已实测到同款约束(ADC1 DMA 目的缓冲必须在 `RW_IRAM1`),应写入 `docs/内存管理/`。 +**两个静态池(57,376 + 40,960 = 98,336 B)仍是 RAM 问题的核心**,且**分居两区**:堆在 CCM(占 CCM 的 87.9%)、LVGL 池在 SRAM1(占 SRAM1 的 42%)。→ 下一步收益点在**把 LVGL 池 + UI 两份缓冲(54,146 B)也搬进 CCM**,但 CCM 只剩 7,904 B → 必须先按 R-9 缩池并缩堆(解锁链 → `../内存管理/RAM预算台账.md` §3)。⚠ 迁移前必须过 DMA 可达性清单:区域放置与"堆/栈禁为 DMA 源/目的"的规则族(R1~R4)以 `../内存管理/内存内容分配策略.md` §4 为准,本仓另有两条实证同族约束(ADC1 DMA 目的缓冲必须在 `RW_IRAM1`;CCM 上做 DMA 静默不落地)。 -**Flash 侧(Code+RO)**:基线为 214 个含码对象;**2026-10-11 复核为 182 个含码对象**(按 `.map` 组件表对象名归类,`App/`+`Src/`+`Drivers/BSP/` 合并为"应用/驱动自研",算法层单列) +**Flash 侧(Code+RO,按 `.map` 组件表对象名归类)** -| 子系统 | Code+RO(基线) | 占镜像 | **2026-10-11** | **占镜像** | -|--------|----------------|--------|----------------|-----------| -| LVGL(含核心/绘制/控件/字体/缓存/图像) | ≈150,600 | **61.5%** | **152,185** | **55.4%** | -| 应用/驱动自研(`App/` + `Src/` + `Drivers/BSP/`,不含 UI 页面) | 10,468(仅 App+Src) | 4.3% | **27,076** | 9.9% | -| 网络(NET + NETCFG + lftpd) | 18,245 | 7.4% | **21,629** | 7.9% | -| 手写 UI 页面/框架(`ui_*.o`,现 6,036 行) | 17,520 | 7.2% | **18,535** | 6.8% | -| 存储(FatFs + dhara + NAND) | 8,222 | 3.4% | **15,133** | 5.5% | -| HAL/CMSIS | 11,866 | 4.8% | **14,391** | 5.2% | -| FreeRTOS + CMSIS-RTOS | 8,011 | 3.3% | **12,258** | 4.5% | -| 算法层 L1/L2(`adc_input`/`phasor`/`sample_ring`,**新增**) | — | — | **1,589** | 0.6% | -| 其他(startup/CMSIS 胶水等) | — | — | 5,842 | 2.1% | -| **合计(含库成员与填充)** | **245,384** | 100% | **274,728** | 100% | +| 子系统 | Code+RO | 占镜像 | +|--------|---------|--------| +| LVGL(核心/绘制/字体/缓存/图像) | 87,706 | **38.0%** | +| 网络(NET + CH395F + NETCFG + lftpd) | 27,561 | 11.9% | +| 存储(FatFs + dhara + NAND + 卷锁) | 19,265 | 8.3% | +| 手写 UI 页面/框架(`ui_*.o`) | 18,507 | 8.0% | +| FreeRTOS + CMSIS-RTOS | 13,562 | 5.9% | +| HAL/CMSIS | 13,532 | 5.9% | +| 算法层 L1~L3(`adc_input`/`phasor`/`sample_ring`/`prot_*`) | 5,454 | 2.4% | +| 其余(`App/` 应用与驱动自研、startup、C 库、填充) | ≈45,449 | ≈19.7% | +| **合计** | **231,036** | 100% | -**复核要点**:LVGL 的**绝对量几乎没动**(150,600 → 152,185),占比从 61.5% 降到 55.4% 纯粹是因为**自研侧涨了 ≈30 KB**(看门狗 + Fault 采集 + ADC 全链 + 相量层 + 日志/观测)。→ **§5.4 的裁剪结论依旧成立且更划算**:省下的 54.6 KB 现在相当于镜像的 **19.9%**。 +**要点**:LVGL 的绝对量自 F-1/F-2/F-3 裁剪后**未再增长**(87,706 B),但仍是镜像最大单一子系统 → §5.4 的剩余裁剪(≈10 KB)与 F-5 的编译时长收益依旧成立。 -### 5.2 结构性诊断(四条 + 未闭环部分) +### 5.2 结构性诊断(三条) -1. **64 KB CCM 空转**,而 96.6% 的负载压在 128 KB SRAM1 上。 - → **未闭环**:LVGL 池 **40,960 B** + UI 两份缓冲 **13,186 B** 仍在 SRAM1;堆迁入后 **CCM 只剩 16,304 B,装不下这 54 KB** → R-2 的剩余部分**必须先做 R-9(下调 `LV_MEM_SIZE`)或 R-4(帧副本去重)**才有余地。 -2. **任务栈全部动态取自 FreeRTOS 堆**:`osThreadAttr_t` 只设 `stack_size` 不设 `stack_mem`(`Src/freertos.c:64-105`)→ 栈 + idle/timer 栈 + TCB + IPC 全部从堆里扣,**留给动态分配的实际余量很紧**。 - → **未闭环**:7 条任务(`defaultTask/netTask/ftpTask/testTask/uiTask/timeSyncTask/adcTask`)栈合计 **10,176 words = 40,704 B**,仍全部动态取自堆。实测 `load: OK (523B, heap free 8840B)` → 空闲约 **8.8 KB / 49,152 B**,历史最低水位(`xPortGetMinimumEverFreeHeapSize`)**6,640 B**(2026-10-11 L2 相量 + L1-6 上链后由 7,152 B 降下来);同日 `adcTask` 栈由 512 抬到 **640 words**(静态最坏链 1,080 B),继续蚕食堆余量。**R-6(栈静态化)是把"池内碎片"变"可核算静态区"的正解,且其前置条件(观测常开)已满足。** -3. **零观测**:`xPortGetFreeHeapSize`/`xPortGetMinimumEverFreeHeapSize`/`uxTaskGetStackHighWaterMark` 全库零调用,无法回答"还能加多少"。 - → 已解决(三面观测:`[STACK]` 运行时水位 + `scripts/stack_report.py` 静态最坏链 + `ram_report.py`/`check_mem_numbers.py` 门禁),不再列为待办。 -4. **MicroLIB 512 B C 堆是隐形地雷**:任何新代码写一个 `malloc` 都会中招。 - → **未闭环**:`Heap_Size EQU 0x200` 未变、`.map` 仍 `Removing startup_stm32f407xx.o(HEAP), (512 bytes)`;铁律已进 AGENTS.md(只用 `pvPortMalloc/vPortFree` 或收进平台后端),但**没有机械化检查**——`check_coding_standard.py` 未含 `malloc(` 正则 → §5.5 第 5 条未落地。 +1. **CCM 已被堆占掉 87.9%(只剩 7,904 B)**,而 LVGL 池 + UI 两份缓冲 **54,146 B** 仍在 SRAM1。 + → R-2 的剩余部分**必须先做 R-9(下调 `LV_MEM_SIZE`)+ 缩堆**才有余地;顺序不可颠倒(`../内存管理/RAM预算台账.md` §3)。 +2. **任务栈全部动态取自 FreeRTOS 堆**:`osThreadAttr_t` 只设 `stack_size` 不设 `stack_mem`(`Src/freertos.c`)→ 8 条产品任务栈合计 **9,792 words = 39,168 B**(另 testTask 2,048 words 仅测试构建)连同 idle/timer 栈、TCB、IPC 全部从堆里扣,**留给动态分配的实际余量很紧**(堆 56 KB 是 2026-10-12 B5 扩容后的值)。 + → **R-6(栈静态化)是把"池内碎片"变"可核算静态区"的正解,前置条件(观测常开 + `stack_report.py` 每任务最坏链)已满足。** 实时水位以串口 `[STACK]` / 自检打印为准,不在本文固化快照。 +3. **MicroLIB 512 B C 堆是隐形地雷**:任何新代码写一个 `malloc` 都会中招。 + → `Heap_Size EQU 0x200` 未变、`.map` 仍 `Removing startup_stm32f407xx.o(HEAP), (512 bytes)`;铁律已进 AGENTS.md(只用 `pvPortMalloc/vPortFree` 或收进平台后端),但**没有机械化检查**——`check_coding_standard.py` 未含 `malloc(` 正则 → §5.5 第 5 条未落地。 ### 5.3 RAM 优化路线(收益/风险/优先级) -| 编号 | 措施 | 收益 | 风险 | 优先级 | **2026-10-11 状态** | -|------|------|------|------|--------|---------------------| -| **R-2** | **启用 CCM**:`.sct` 增 `RW_IRAM2 0x10000000 0x00010000 { *(CCM_DATA) *(.bss.CCM_DATA) }`;对 LVGL 池用 `LV_MEM_ATTR __attribute__((section("CCM_DATA")))`,UI 四份帧缓冲同样加段属性;用 `APP_CCM_ENABLE` 宏包裹(克隆片兜底 = 全部回 SRAM1) | **−54,122 B(52.9 KiB)** = 40,960 + 6,400 + 6,762 | 中:必须确保这些缓冲**不被任何 DMA 直接寻址**。已核实 UI 发送路径 memcpy 进 CH395F 自有 SRAM1 DMA 缓冲(`ch395f.c:1132-1190`),LVGL 为纯 CPU;**NAND 路径(N-11)绝对不可迁入** | **P1,本季度** | 🟡 **只做一半**:`.sct` 加了 `RW_CCM`,但**只收 `heap_4.o`**(49,184 B)→ SRAM1 −49,184 B。LVGL 池 + UI 两份缓冲(54,146 B)**未迁**;`APP_CCM_ENABLE` 宏**未采用**(该宏已从 `app_cfg.h` 删除)。⚠ 复核新增前置条件:**CCM 只剩 16,304 B,这 54 KB 装不下** → 必须先做 R-9 或 R-4 | -| **R-3** | 存储页缓冲合并:`gd5f.s_bbt_page_buf` 与 `nand_ftl.s_copy_buf` 复用(BBT 仅在 init 期使用);`nand_ftl` 三块 2 KB 中 `s_page_buf`(dhara scratch) 与 `s_copy_buf` 不可并发 | **−2 ~ −4 KB** | 中:需严格审查 dhara 调用序列(copy 期间不会用 map scratch) | P2 | ❌ 未办(合并未做);🟡 但 `s_bbt_page_buf` 已由 2,048 B 收紧为 **272 B**(2026-09-21,等效省 1,776 B) | -| **R-4** | UI 帧副本去重:同一 3,200 B 帧当前存 **4 份**(`g_ui_draw_buf` → `g_ui_fbuf` → `g_lcdmem_data` → `g_tx`)。让 `ui_flush_cb` 直接把 `color_p` 交给 sink、sink 在发送时才拼帧头(就地写 8 B 头 + 复用同一 buffer) | **−6.4 ~ −9.6 KB** | 中:需要 sink 与 LVGL 刷新生命周期对齐(同任务,可控) | P1 | ❌ 未办(`ui_port.o` 6,420 + `ui_net_sink.o` 6,766 = 13,186 B,副本数未变) | -| **R-5** | `configMAX_PRIORITIES` 56 → 32(实际最高用到 `osPriorityHigh` 级别) | **−480 B** | 低 | P2 | ❌ 未办(`FreeRTOSConfig.h:69` 仍 56;`pxReadyTasksLists[56]` = 1,120 B 仍在) | -| **R-6** | 任务栈改**静态分配**(`stack_mem` 指向各自 `.bss` 数组),从堆里释放 32,768 B | 堆可用 +32 KB(**总量不变**,但把"池内碎片"变成"可核算静态区") | 低-中:静态后溢出检测更难发现;先测水位再定尺寸 | P2 | ❌ 未办(仍全部 `stack_size` 动态)。⚠ 前置条件已满足:观测已三面常开,`stack_report.py` 已给出每任务最坏链 → 可直接按实测值定尺寸 | -| **R-8** | 观测常开:新增 `memStatsTask`(最低优先级,1 Hz,512 w)打印 `xPortGetFreeHeapSize` / `xPortGetMinimumEverFreeHeapSize` / 各任务高水位 / `lv_mem_monitor`,并支持串口命令按需打印 | 0(可核算) | 低 | **P1**(是 R-2/R-6 的前置) | 🟡 **目标达成、形态不同**:未建 `memStatsTask`,改为 ① 开机/自检打印 `[STACK]` 水位 + 堆空闲;② `scripts/stack_report.py`(静态最坏链)+ `ram_report.py`;③ `check_mem_numbers.py` 门禁 + `docs/内存管理/data/` 快照。**已足够支撑 R-2/R-6 决策** | -| R-9 | `LV_MEM_SIZE` 40 KB → 依 R-8 实测峰值下调(既有方案预估 16 KB) | −16 ~ −24 KB | 低(有 R-8 兜底) | P2 | ❌ 未办(`lv_conf.h:44` 仍 `(40 * 1024)`)。⚠ 复核:`simulator/tests/check_content_pages.py` 已带**池水位门禁** → 实测峰值数据已具备,本条只差改数值 + 真机复跑(⚠ 两端 `lv_conf.h` 要同步改) | +| 编号 | 措施 | 收益 | 风险 | 优先级 | 状态 | +|------|------|------|------|--------|------| +| **R-2** | **启用 CCM 承载 LVGL 池与 UI 缓冲**:对 `work_mem_int` 与 UI 帧/绘制缓冲加段属性(`.sct` 已有 `RW_CCM`) | **−54,146 B** | 中:必须确保这些缓冲**不被任何 DMA 直接寻址**。已核实 UI 发送路径 memcpy 进 CH395F 自有 SRAM1 DMA 缓冲,LVGL 为纯 CPU;**NAND 路径(规则 R2/R3)绝对不可迁入** | **P1(W3)** | 🟡 **只做一半**:`RW_CCM` 只收 `heap_4.o`(57,376 B)→ SRAM1 已减 49 KB+。⚠ 前置条件:**CCM 只剩 7,904 B,这 54 KB 装不下** → 必须先 R-9 + 缩堆 | +| **R-3** | 存储页缓冲合并:`nand_ftl` 三块 2 KB 中 `s_page_buf`(dhara scratch) 与 `s_copy_buf` 不可并发,可复用 | **−2 KB** | 中:需严格审查 dhara 调用序列(copy 期间不会用 map scratch) | P2 | ❌ 未办(`s_bbt_page_buf` 已单独收紧为 272 B) | +| **R-4** | UI 帧副本去重:同一 3,200 B 帧当前存 **4 份**(`g_ui_draw_buf` → `g_ui_fbuf` → `g_lcdmem_data` → `g_tx`)。让 `ui_flush_cb` 直接把 `color_p` 交给 sink、sink 发送时才拼帧头 | **−6.4 ~ −9.6 KB** | 中:需要 sink 与 LVGL 刷新生命周期对齐(同任务,可控) | P1 | ❌ 未办(`ui_port.o` 6,420 + `ui_net_sink.o` 6,766 = 13,186 B,副本数未变) | +| **R-6** | 任务栈改**静态分配**(`stack_mem` 指向各自 `.bss` 数组),从堆里释放 ≈39 KB | 堆可用 +39 KB(**总量不变**,但把"池内碎片"变成"可核算静态区") | 低-中:静态后溢出检测更难发现;先测水位再定尺寸 | P2 | ❌ 未办。⚠ 前置已满足:观测三面常开 + `stack_report.py` 已给出每任务最坏链 → 可直接按实测值定尺寸 | +| R-9 | `LV_MEM_SIZE` 40 KB → 依实测峰值下调(既有方案预估 16 KB) | −16 ~ −24 KB(SRAM1) | 低(有池水位门禁兜底) | P2(**R-2 后半的前置**) | ❌ 未办(两份 `lv_conf.h` 仍 `(40 * 1024)`)。⚠ `simulator/tests/check_content_pages.py` 已带**池水位门禁** → 实测峰值数据已具备,本条只差改数值 + 真机复跑(**两端 `lv_conf.h` 必须同步改**,`lv-conf-sync` 钩子会拦) | -**组合结果预估(注意措施之间存在重叠,不可简单相加)** +> ⚠️ **R-2 与 R-4 不可叠加计算**:R-4 省的是"副本份数",R-2 已把这四份缓冲整体搬到 CCM,因此 **R-4 在 R-2 之后省的是 CCM 而非 SRAM1**(CCM 侧 13,186 B → ≈6.8 KB,为后续 UI 扩展腾空间)。若只做 R-4 不做 R-2,则直接为 SRAM1 省 6.4 KB——两条路线对 RAM 总量等价,**但顺序决定收益落在哪个区域**。 +> +> **R-5 已判定不可行(原建议 `configMAX_PRIORITIES` 56→32 省 480 B)**:CMSIS-RTOS2 包装层 `freertos_os2.h:293` 有 `#if (configMAX_PRIORITIES != 56) #error`——改小直接编译失败。为 480 B 去改第三方包装层不划算,**本条关闭**(登记簿 N-27 的评估清单已据此剔除 R-5)。 -| 组合 | SRAM1 变化 | 说明 | -|------|-----------|------| -| 基线 | 126,584 B / **96.6%** | 余 4,488 B | -| **+ 实际执行(2026-10-11 实测)** | **91,488 B / 69.8%** | 余 **39,584 B**。实际做的 ≠ 预估路径:**R-2 只搬堆(−49,184 B → CCM 49,232 B / 75.1%)** + R-7(−8,192 B)+ BBT 缓冲收紧(−1,776 B);抵消项 = adcTask 栈、`sample_ring` 1,448 B、`console_log` 4,120 B、Fault/看门狗/日志新增 | -| + R-2(CCM 启用,原预估全量) | **72,462 B / 55.3%** | −54,122 B;CCM 用 54,122 B(仍余 11 KB)。⚠ 复核:该预估**已不可直接执行**——堆先占了 CCM 49,184 B,CCM 只剩 16,304 B,装不下 LVGL 池 + UI 缓冲这 54 KB | -| + R-3(存储页缓冲合并) | **≈66.3 KB / 50.6%** | −2,048 ~ −4,096 B(BBT 收紧已等效省 1,776 B) | -| + R-5(优先级 56→32) | **≈65.8 KB / 50.2%** | −480 B | - -> ⚠️ **R-2 与 R-4 不可叠加计算**:R-4(UI 帧副本 4→2)省的是"副本份数",R-2 已把这四份缓冲整体搬到 CCM,因此 **R-4 在 R-2 之后省的是 CCM 而非 SRAM1**(CCM 侧 13,162 B → 6,762 B,为后续 UI 扩展腾出 6.4 KB CCM 空间)。若只做 R-4 不做 R-2,则直接为 SRAM1 省 6.4 KB——两条路线对 RAM 总量等价,**但顺序决定收益落在哪个区域**:先做 R-2 时,R-4 应重新定性为"CCM 优化"。 - -最终形态(原预估):**SRAM1 约 50%、CCM 约 20% 使用率**,`L6406E`(P1-10 测试镜像溢出)随之消解,并为 ADC 数据路径与 RS485 协议栈预留预算空间。 - -> **2026-10-11 修正(未闭环部分)**:原预估的**执行顺序已不可按原样走**——实际走到 **SRAM1 69.8% / CCM 75.3%**,因为选了"先搬堆"而非"先搬 LVGL 池 + UI 缓冲",**CCM 已被堆占掉 75%(只剩 16,304 B)**。要拿到剩余收益,顺序必须改为 **R-9(下调 `LV_MEM_SIZE`,最省事的杠杆)或 R-4(帧副本去重)→ 再做 R-2 后半段**。另两条仍开放:**`L6406E` 未完全消解**(登记簿留 P1-10 收尾:CH395F 套件构建实测链接确认 + `@test` 闭环);RS485 协议栈前置件 `crc.c`/`ringbuf.c` 已删除,**需从零重做**而非"注册"。 - -> 与 `docs/内存管理/RAM使用分析与优化方案.md` 的关系:本表**沿用其阶段划分**(阶段 0 测量 / 阶段 1.1 池收缩 / 阶段 1.2 CCM / 阶段 2 堆收缩),新增的是 ①R-4 帧副本去重(原文档未识别 4 份副本)②R-3 存储侧缓冲合并 ③N-11 的 **DMA 可达性硬边界**(原文档未提出,属实施风险关键点)④观测前置条件(已落地,见 §5.5)。 +与 `../内存管理/RAM使用分析与优化方案.md` 的关系:本表**沿用其阶段划分**(阶段 0 测量 / 阶段 1.1 池收缩 / 阶段 1.2 CCM / 阶段 2 堆收缩),本报告新增的是 ①R-4 帧副本去重(原文档未识别 4 份副本)②R-3 存储侧缓冲合并 ③**DMA 可达性硬边界**(原报告 N-11 提出,现已沉淀为 `../内存管理/内存内容分配策略.md` §4 规则 R2/R3,属实施风险关键点)。 ### 5.4 Flash 优化(未闭环:F-4 / F-5 / F-6) -> **F-1 / F-2 / F-3 已落地并销项(2026-10-11)**:`Lib/lv_conf.h` 与 `simulator/lv_conf.h` 对齐为同一特性子集(只留 `LV_DRAW_SW_SUPPORT_I1`、零控件、`LV_USE_THEME_SIMPLE 0`、`LV_USE_FLEX/GRID 0`)→ **实测省 64,576 B**:`ER_IROM1` 274,724 → **210,148 B**,Flash 26.3% → **20.1%**。比本报告原估(54,630 B)多省约 10 KB——锚点消失后,`blend_to_i1` 内部按**源**格式的分支与 `transform` 的格式分支也一并退出。完整记录(对象级对照、上游 9.3.0 漏守卫的配套补丁、ui-verify 证据)见 `../内存管理/RAM使用分析与优化方案.md` §2.6.4 与 `../UI设计/UI_设计架构.md` §1.4 第 10 条。 +> **F-1 / F-2 / F-3 已落地并销项(2026-10-11)**:两份 `lv_conf.h` 对齐为同一特性子集 → **实测省 64,576 B**,Flash 26.3% → 20.1%(现 22.0%,增量来自 B4/B5 功能代码)。完整记录见 `../内存管理/RAM使用分析与优化方案.md` §2.6.4 与 `../UI设计/UI_设计架构.md` §1.4 第 10 条。 -| 编号 | 措施 | 可省 Flash | 依据 | **2026-10-11 状态** | -|------|------|-----------|------|---------------------| -| **F-4** | 关闭未用的绘制子模块与 `LV_USE_SNAPSHOT`(MCU 端)/`LV_USE_OBJ_PROPERTY`/`LV_USE_FLOAT` 等 | 可评估部分 **≈10 KB** | 新镜像对象实测:`mask` 3,862 + `box_shadow` 3,246 + `draw_sw_img` 2,960 + `bin_decoder` 1,972 + `image_decoder` 952 + `triangle` 930 + `transform` 598 + `grad` 420 + `mask_rect` 372 = **15,312 B**;工程无图片、无阴影、无变换 | ❌ 未办(数字已按 F-1 后的新镜像重测,`transform` 已从 4,340 降到 598)。⚠ **不可盲目关**:`mask`/`mask_rect`/`grad` 是文字与矩形绘制的必经路径;真正可评估的是 `box_shadow` + `draw_sw_img` + `bin_decoder` + `image_decoder` + `triangle` ≈ **10.1 KB** | -| **F-5** | 从 uvprojx 中**移除未使用的 LVGL 控件源文件**(201 个 → 实际需要的子集) | 编译时长 −40% 左右 | 与 F-2 配合;`lv_conf` 关开关后文件仍参与编译,删文件才省编译时间 | ❌ 未办(uvprojx 现挂 **306 个 `.c`,其中 LVGL 仍 201 个**)。⚠ F-2 落地后这些 TU 现在编出来是**空目标文件**(文件级 `#if` 全灭),删文件的收益更纯粹、风险更低 | +| 编号 | 措施 | 可省 Flash | 依据 | 状态 | +|------|------|-----------|------|------| +| **F-4** | 关闭未用的绘制子模块与 `LV_USE_SNAPSHOT`(MCU 端)/`LV_USE_OBJ_PROPERTY`/`LV_USE_FLOAT` 等 | 可评估部分 **≈10 KB** | 对象实测:`box_shadow` 3,246 + `draw_sw_img` 2,960 + `bin_decoder` 1,972 + `image_decoder` 952 + `triangle` 930 ≈ **10.1 KB**;工程无图片、无阴影、无变换 | ❌ 未办。⚠ **不可盲目关**:`mask` 3,862 / `mask_rect` / `grad` 是文字与矩形绘制的必经路径 | +| **F-5** | 从 uvprojx 中**移除未使用的 LVGL 控件源文件**(201 个 → 实际需要的子集) | 编译时长 −40% 左右 | 与 F-2 配合;`lv_conf` 关开关后文件仍参与编译,删文件才省编译时间 | ❌ 未办(uvprojx 现挂 **323 个 `.c`,其中 LVGL 仍 201 个**)。⚠ F-2 落地后这些 TU 现在编出来是**空目标文件**,删文件的收益更纯粹、风险更低 | | **F-6** | 双精度软浮点:`dadd`/`dmul`/`ddiv`/`depilogue`/`dfixul`(`mf_w.l`),来自 `printf` 的 `%f`/double 提升 | −1 KB + 运行期性能 | 日志与 UI 全用定点;把 `%f` 改整数刻度 | ❌ 未办(新镜像仍链入 `dadd.o`) | -**剩余可回收 ≈ 10–11 KB Flash**(F-4 可评估部分 + F-6);F-5 省的是编译时长不是 Flash。原"≈50 KB 死代码"的主体(64,576 B)已在 2026-10-11 消除。 +**剩余可回收 ≈ 10–11 KB Flash**(F-4 可评估部分 + F-6);F-5 省的是编译时长不是 Flash。 -### 5.5 内存治理长效机制(+ 2026-10-11 复核) +### 5.5 内存治理长效机制 + +> **已闭环(2026-10-12,登记簿 N-27 前半 / W1-0)**:① 超阈值即失败的 **`mem-budget` 钩子**(`ram_baseline.py --gate`,**92% 红线** + Top-10 RW+ZI 附表,无 `.map` 自适应跳过)② 趋势基线 **`scripts/ram_baseline.tsv`**(`--update` / `--trend`)③ **`../内存管理/RAM预算台账.md`**(子系统配额 + CCM 分配台账 + 超配额流程)。 +> **阈值与配额的权威值现在只在那份台账里**(本报告不再重复数值);`mem-numbers` 钩子继续防"文档数字漂移"。 +> +> **已闭环(2026-10-11,登记簿 N-26)**:**DMA 可达性清单与规则族**——`../内存管理/内存内容分配策略.md` §3「全板 DMA 使用方清点」+ §4 规则 R1~R4(含 **R3 堆/栈禁为 DMA 源/目的**),落地为编译期窗口断言 + 启动期自检 + 运行时守卫三层(见 §4.3)。原"独立清单文档 + `_Static_assert`"的建议形态**已被这份更好的实现取代**。 + +仍开放一条: -1. **`scripts/ram_report.py` 升级为门禁**:超阈值(如 SRAM1 > 92%)即失败,并在 PR 描述里自动附 Top-10 对象表。 - → **🟡 部分实现**:`scripts/check_mem_numbers.py` 已挂 pre-commit(以 `.map` 为**唯一事实源**,校验 `docs/内存管理/RAM使用分析与优化方案.md` 的 `MEM_SNAPSHOT:BEGIN/END` 受管区 + 散句陈旧数字扫描)——**防的是"文档数字漂移",不是"占用超阈值"**。⚠ 无 92% 阈值失败逻辑;且 CI 上无 `.map` 时该钩子自适应跳过(登记簿 **A-5 残余**)。 - → ✅ **2026-10-12 闭环**:新增 `mem-budget` 钩子(`scripts/ram_baseline.py --gate`)——RW_IRAM1/RW_CCM >92% 即 FAIL + Top-10 RW+ZI 附表;无 `.map` 自适应跳过(A-5 残余口径不变)。 -2. **建立 `[计划] scripts/ram_baseline.tsv`**(`docs/内存管理/RAM使用分析与优化方案.md:172` 已承诺但未创建):每次构建记录 Code/RO/RW/ZI/Top 对象,形成趋势。 - → **❌ 未建**;🟡 等效物已存在:`docs/内存管理/data/` 下 5 份 `ram_report`/`stack_report` 快照留档(人工触发,非 tsv 趋势表)。 - → ✅ **2026-10-12 闭环**:`scripts/ram_baseline.tsv` 已建(`--update` 每次 @build 后追加;`--trend` 高亮 RW+ZI 增长 >1 KB 对象)。 -3. **RAM 预算台账**(同上,文档已承诺 `[计划] docs/内存管理/RAM预算台账.md`):按子系统配额(网络 12 KB / 存储 12 KB / UI 16 KB / RTOS 48 KB / 应用 8 KB),超配额必须先申请。 - → **❌ 未建**。⚠ 复核:这条的价值在上升——堆已进 CCM 且只剩 16,304 B CCM 余量,"谁还能用 CCM"必须有台账,否则 R-2 后半段无法排期。 - → ✅ **2026-10-12 闭环**:`docs/内存管理/RAM预算台账.md` 已建——子系统配额(网络 12/存储 12/UI 16/RTOS 堆 48/应用 8 KB,现状超配 = W3 债务如实展示)+ **CCM 分配台账**(谁能用 CCM、R-2 后半解锁链)+ 超配额流程 + 受管区自动生成。 -4. **DMA 可达性清单**(新增,源于 N-11):文档化"哪些缓冲必须 SRAM1",并在 CCM 迁移时对该清单加 `_Static_assert`/地址断言(`assert((addr & 0xFFF00000) == 0x20000000)`)。 - → **🟡 半落地**:独立清单文档未建,但**铁律已进 AGENTS.md**("DMA 目的缓冲必须在 SRAM,DMA2 不可达 CCM 0x10000000"、"改 ADC 流后回 NVIC 核对");代码侧另有**运行时**守卫(`gd5f2gq5ue.c:110-136` 不可达回落轮询)。⚠ 仍缺**编译期/启动期断言**,且需补一条新铁律:**从 FreeRTOS 堆分配的缓冲不可被 DMA 直接寻址**(堆已在 CCM)。 5. **禁止新增 C 库 `malloc`**:写进 `docs/代码规范/`,并在 `check_coding_standard.py` 加正则检查(`App/`、`Drivers/` 内出现 `malloc(` 即告警)。 - → **🟡 前半已做、后半未做**:AGENTS.md 已有"自研代码禁用 C 库堆分配"铁律(含模拟器照不出来的原因);❌ `check_coding_standard.py` **未含 `malloc(` 正则** → 目前只靠人评审,属机械检查缺口。 + → **🟡 前半已做、后半未做**:AGENTS.md 已有铁律(含模拟器照不出来的原因);❌ `check_coding_standard.py` **未含 `malloc(` 正则** → 目前只靠人评审,属机械检查缺口。 --- @@ -491,17 +431,15 @@ Select-String -Path 'MDK-ARM\STM32F407-Demo\STM32F407-Demo.map' -Pattern '__heap | 层 | 现状 | 评价 | |----|------|------| -| 内容页 | `ui_content_page.c` 描述符表 `s_defs[]` **仅 7 页**(DEVICE_INFO/AC/DC/PASSWORD/NET_CFG/RUN_LOG/SELFTEST_LOG) | ❌ **42 个菜单叶子中仅 7 个有内容页**,`menu_content_page_for()`(`ui_menu.c:155/:205`)是**手写 switch 映射**。**2026-10-11 复核:未改善**——`ui_menu_tab.h:28` 仍 42 节点(**叶子 33 个**),有内容页的叶子 **6 个**(PASSWORD 非叶子映射);`menu_content_page_for()` 现位于 `ui_menu.c:118-128`,仍是手写 `case 1u/3u/4u/30u/39u/40u:` | -| 设计令牌 | **无**(每页自带 `CP_SCREEN_W/H 160`、`CP_TITLE_Y 2`、`CP_PAGE_LINE_Y 143`、`CP_ROW_BUF_LEN 32` 等布局常量) | ❌ 魔法数字分散在各页。**2026-10-11 复核:`Drivers/BSP/UI/ui_tokens.h` 仍不存在**(U1 未办) | -| golden 回归 | 已建成,但**不在文档承诺的 `tools/ui_mock/`**,而是 `simulator/tests/` 四脚本:`check_menu_rows.py`(菜单 8 状态全像素)、`check_content_pages.py`(内容页黄金参考逐字节 + 文本断言 + LVGL 池水位门禁)、`regen_ref_png.py`、`check_remo_sink.py` | 🟡 **有基线但未入门禁**:两个脚本未挂 pre-commit / `ui-verify`;`docs/UI设计/UI_界面制作流程.md` / `UI_自动化测试流程.md` 仍指向不存在的 `tools/ui_mock/`(语义断链) | +| 内容页 | `ui_content_page.c` 描述符表 `s_defs[]` **7 页**(DEVICE_INFO/AC/DC/PASSWORD/NET_CFG/RUN_LOG/SELFTEST_LOG) | ❌ `ui_menu_tab.h:28` 42 节点(**叶子 33 个**),有内容页的叶子 **6 个**(PASSWORD 非叶子映射);`menu_content_page_for()`(`ui_menu.c:118-128`)仍是手写 `case 1u/3u/4u/30u/39u/40u:` | +| 设计令牌 | **无**(每页自带 `CP_SCREEN_W/H 160`、`CP_TITLE_Y 2`、`CP_PAGE_LINE_Y 143`、`CP_ROW_BUF_LEN 32` 等布局常量) | ❌ 魔法数字分散在各页,`Drivers/BSP/UI/ui_tokens.h` 不存在(U1 未办) | +| golden 回归 | `simulator/tests/` 四脚本:`check_menu_rows.py`(菜单 8 状态全像素)、`check_content_pages.py`(内容页黄金参考逐字节 + 文本断言 + **LVGL 池水位门禁**)、`regen_ref_png.py`、`check_remo_sink.py` | 🟡 **有基线但未入门禁**:两个脚本未挂 pre-commit / `ui-verify` | ### 6.2 新增一个页面要走多少步(当前) 1. `ui_menu_tree.txt` 加节点 → 2. 重跑 `gen_menu.py` → 3. 新建 `ui_page_xxx.c` → 4. 在 `ui_content_page.c` 的枚举/`s_defs[]` 注册 → 5. 在 `ui_menu.c:menu_content_page_for()` 手写 `case N:`(**下标硬编码,表序一变即错**)→ 6. 注册到 `MDK-ARM/*.uvprojx` → 7. 注册到 `simulator/CMakeLists.txt` → 8. 新汉字跑 `font-regen`。 -**5 步是纯机械劳动且 2 处易错(步骤 5 的魔法下标、步骤 6/7 漏注册)。** - -> **2026-10-11 复核:8 步流程一步未减。** 步骤 5 的手写 `case` 仍在(`ui_menu.c:118-128`);步骤 6/7 的漏注册仍无机械校验(`check_registration.py` 未建)——但已新增一条**间接暴露路径**:若新文件落在新目录而没把目录加进 `uvprojx` 的 ``,消费者 TU 会报 `cannot open source input file`(AGENTS.md 2026-10-10 记录),这类报错落在**别的文件**上、易误判,仍不等于"存在但未注册"的检查。 +**8 步里 5 步是纯机械劳动且 2 处易错(步骤 5 的魔法下标、步骤 6/7 漏注册)。** 步骤 6/7 如今有 `registration-check` 钩子兜底(存量走白名单),但**新页面落在新目录而没把目录加进 `uvprojx` 的 ``** 时,报错落在**消费者 TU** 上(`cannot open source input file`),仍不等于"存在但未注册"的检查。 ### 6.3 标准化目标:四层 + 一个闭环 @@ -535,7 +473,7 @@ graph LR #define UI_COL_BG LV_COLOR_WHITE #define UI_COL_SEL_FG UI_COL_BG #define UI_COL_SEL_BG UI_COL_FG -/* 样式:只用 const 描述符(动态 lv_style 会每次泄漏 88 B,见 ui_content_page.c:100-104) */ +/* 样式:只用 const 描述符(动态 lv_style 会每次泄漏 88 B,见 ui_content_page.c) */ extern const lv_style_const_prop_t UI_STYLE_FLAT[]; ``` @@ -561,8 +499,7 @@ extern const lv_style_const_prop_t UI_STYLE_FLAT[]; | `records`(分页日志) | SOE/事故/操作/保护告警/保护启动/遥控/自检/运行记录/运行报告 | `record_provider_t{ count(); get(idx, ts, text); }`(sys_log 已是这个形态) | | `action`(确认型) | 强制复归、清除记录、手动录波 | 提示 → 二次确认 → 执行 → 结果日志 | -菜单树 42 个叶子中,**除已实现的 7 个外,其余 35 个可 100% 归入这 4 类原型**(form 21 个、records 9 个、detail 3 个、action 2 个)。这意味着**剩余页面工作量从"35 页 × 各自设计"降为"4 个原型 + 35 份数据绑定"**。 -> **2026-10-11 口径修正**:42 是 `ui_menu_tab.h:28 UI_MENU_TAB_COUNT`(根 + 41 节点)的**节点数**,**叶子实为 33 个**;已有内容页的叶子 **6 个** → 待落地 **27 页**(不是 35)。四类原型的分布按 27 页重算即可,结论不变。 +**口径**:`ui_menu_tab.h:28 UI_MENU_TAB_COUNT` = 42(根 + 41 节点),**叶子实为 33 个**;已有内容页的叶子 **6 个** → 待落地 **27 页**。四类原型的分布按 27 页重算即可。这意味着**剩余页面工作量从"27 页 × 各自设计"降为"4 个原型 + 27 份数据绑定"**。 **L3 页面实例** — 目标形态(以"遥测死区"为例,全部新代码约 30 行): @@ -584,117 +521,97 @@ const ui_page_desc_t ui_page_deadzone = UI_FORM_PAGE("遥测死区", s_items_dea 产物:`ui_page_registry.c` 含 `node_index → (page_id, archetype, desc*)` 常量表 + `ui_page_registry_for(node)`;`ui_menu.c` 只需一次调用。生成器同时校验:`@page` 指向的描述符是否存在、是否有节点声明了页但无实例、是否有实例未被任何节点引用(编译期断言或生成期报错)。 -**闭环门禁(1 天)**——未闭环 2.5 条: +**闭环门禁(1 天)**——未闭环 2 条: -1. `[计划] scripts/check_registration.py`(同 §2.3 S-3)保证新页面两端都注册 → **❌ 仍未建**(`scripts/` 现有 11 个脚本,无此项)。 -2. **golden 回归**(原方案:`simulator --shot` 导出 1bpp 帧 + PNG 逐像素比对)已按另一路径建成(`simulator/tests/` 四脚本,见 §6.1)。**未闭环**:`UI_界面制作流程.md:413` 与 `UI_自动化测试流程.md:104` 仍指向不存在的 `tools/ui_mock/`;这些脚本未挂进 pre-commit / `ui-verify` 门禁。 -3. 字符集自动校验:构建前扫描所有 `ui_*.c` 中的汉字,与 `ui_cn_chars.txt` 求差集,缺失即 FAIL(把 `font-regen` 从"记得跑"变成"漏跑就红") → **❌ 未入门禁**:`font-regen` skill 已把四步固化并写进 AGENTS.md("含新汉字的 UI 文案提交前先跑"),但**仍是约定不是门禁**(无 pre-commit 钩子做差集检查)。 +1. **golden 回归入门禁**:`simulator/tests/` 四脚本已建成,但未挂进 pre-commit / `ui-verify`。 +2. **字符集自动校验**:构建前扫描所有 `ui_*.c` 中的汉字,与 `ui_cn_chars.txt` 求差集,缺失即 FAIL(把 `font-regen` 从"记得跑"变成"漏跑就红")→ ❌ 未入门禁(`font-regen` skill 已把四步固化并写进 AGENTS.md,但**仍是约定不是门禁**)。 ### 6.4 UI 侧的内存纪律(必须写进规范) | 规则 | 依据 | |------|------| | 页面**不得**新增静态大缓冲;文本缓冲统一用 `UI_BUF_STRLEN` | 现有 `s_ts/s_desc` 已是范式 | -| 每页**只用** `lv_style_const_prop_t`,禁止运行期 `lv_style_init` | `ui_content_page.c:100-104` 已验证动态样式每次泄漏 88 B | -| 优先自绘(`lv_draw_*` + `lv_text_get_width`),新控件需评审(会破坏 §5.4 F-2 **已落地**的裁剪,且须两端同步) | LVGL 现占镜像 **41.6%**(87,706 / 210,623 B;裁剪前 55%) | +| 每页**只用** `lv_style_const_prop_t`,禁止运行期 `lv_style_init` | 已验证动态样式每次泄漏 88 B | +| 优先自绘(`lv_draw_*` + `lv_text_get_width`),新控件需评审(会破坏 §5.4 F-2 **已落地**的裁剪,且须两端同步) | LVGL 现占镜像 **38.0%**(87,706 / 231,036 B) | | 帧缓冲任何改动必须遵守 R-4(副本数 ≤ 2) | 当前 4 副本 12.8 KB | -| `LVGL 池`用量必须经 `lv_mem_monitor` 实测后才允许扩大 | 现 40 KB,预估可降到 16 KB | +| `LVGL 池`用量必须经 `lv_mem_monitor` / 池水位门禁实测后才允许扩大 | 现 40 KB,预估可降到 16 KB(R-9) | -### 6.5 实施顺序(UI 专项,总计约 8 人日;2026-10-11 复核) +### 6.5 实施顺序(UI 专项,总计约 8 人日) -| 步骤 | 内容 | 交付判据 | **2026-10-11 状态** | -|------|------|---------|---------------------| -| U1 | `ui_tokens.h` 令牌化(纯替换,不改行为) | `@build` PASS + 模拟器截图与替换前逐像素一致 | ❌ 未办(`ui_tokens.h` 不存在)。🟡 **但它的验收条件如今已可机械执行**——golden 基线已建,"逐像素一致"从口头判据变成可跑断言 | -| U2 | `ui_w_records`(合并 runlog/selftest) | 两页行为不变,代码 −1 个文件 | ❌ 未办(`ui_page_runlog.c` / `ui_page_selftest.c` 仍是两份结构几乎相同的代码) | -| U3 | `ui_w_form` + `ui_page_desc_t` 原型化 + 生成器产注册表 | 手工 `menu_content_page_for()` 删除;新增一页只需"改树 + 加实例文件" | ❌ 未办(`ui_page_registry.c` / `gen_page_registry.py` 均不存在;手写 `case` 仍在) | -| U4 | golden 回归(模拟器 `--shot` + PNG 比对)+ 字符集检查 + 注册检查入 pre-commit | 三个新 hook 全绿 | 🟡 **1/3 落地**:golden 已建(`simulator/tests/` 4 脚本,见 §6.1);❌ 字符集差集检查未入门禁;❌ 注册检查未入门禁 | -| U5 | 用原型批量落剩余 35 页(每页 ≤30 行) | 菜单树全绿、无 `CONTENT_PAGE_COUNT` 哨兵命中 | ❌ 未动。**复核修正口径**:42 节点 / **33 叶子** / 6 个叶子有内容页 → 待做 **27 页**(不是 35) | +| 步骤 | 内容 | 交付判据 | 状态 | +|------|------|---------|------| +| U1 | `ui_tokens.h` 令牌化(纯替换,不改行为) | `@build` PASS + 模拟器截图与替换前逐像素一致 | ❌ 未办。🟡 **验收条件如今可机械执行**——golden 基线已建,"逐像素一致"从口头判据变成可跑断言 | +| U2 | `ui_w_records`(合并 runlog/selftest) | 两页行为不变,代码 −1 个文件 | ❌ 未办(两份结构几乎相同的代码仍在) | +| U3 | `ui_w_form` + `ui_page_desc_t` 原型化 + 生成器产注册表 | 手工 `menu_content_page_for()` 删除;新增一页只需"改树 + 加实例文件" | ❌ 未办(`ui_page_registry.c` / `gen_page_registry.py` 均不存在) | +| U4 | golden + 字符集检查 + 注册检查入 pre-commit | 三个新 hook 全绿 | 🟡 **1/3 落地**:golden 已建;❌ 字符集差集、❌ 注册检查未入门禁(注册检查钩子已存在,缺的是把它算作 UI 门禁的一环) | +| U5 | 用原型批量落剩余 **27 页**(每页 ≤30 行) | 菜单树全绿、无 `CONTENT_PAGE_COUNT` 哨兵命中 | ❌ 未动 | + +> **本轮不排期**(2026-10-11 已确认,登记簿 U 簇 / W6):等功能主线出结果后按产品需求启动;U4 残余(两脚本入门禁)可逢窗口顺带。 --- ## 7. 统一改进路线图 -### P0(1 周内,必须) +> **顺序的唯一权威 = [`../任务规划/README.md`](../任务规划/README.md) §2**(W1 采集保护收尾 → W2 数据正确性 → W3 预算与内存 → W4 结构债 → W5 验证常态 → W6 UI 扩展 → W7 仓库决策;B2 RS-485 暂缓)。本节只保留"本报告条目 → 波次"的映射,不另立顺序。 -| 编号 | 事项 | 验收 | **2026-10-11 状态** | -|------|------|------|---------------------| -| N-02 | FTP 鉴权(或生产构建默认关闭 FTP 写) | 错误口令登录被 `530` 拒绝;`@test` 加 FTP 标记校验 | ❌ **未办,且未登记进登记簿** → 复核建议:补登为 P1(现场可被任意主机改写 IP/MAC 并断网) | -| N-06 | 5 个未注册 `.c`:注册或显式删除 + 白名单 | `check_registration.py` 零告警 | 🟡 **半**:3 个消解(2 删 1 注册);❌ `rs485_task.c`/`lftpd_log.c` 仍未处理,❌ 脚本与白名单未建 | - -### P1(1 个月内) - -| 编号 | 事项 | 预期 | **2026-10-11 状态** | -|------|------|------|---------------------| -| N-04 / R-2 | **CCM 启用**(LVGL 池 + UI 帧缓冲,`APP_CCM_ENABLE` 门控)+ N-11 DMA 边界文档与断言 | SRAM1 96.6% → ~55% | 🟡 **半**:堆 49,184 B 已进 CCM → SRAM1 **69.8%**;LVGL 池 + UI 缓冲未迁(CCM 只剩 16,304 B,**必须先做 R-9/R-4**);`APP_CCM_ENABLE` 未采用;N-11 侧改为**运行时守卫**,仍无编译期断言 | -| R-4 | UI 帧副本 4 → 2 | −6.4 KB | ❌ 未办(`ui_port.o` 6,420 + `ui_net_sink.o` 6,766 未变) | -| N-12/N-13 | `dhara_nand_is_free` 全页校验;`dhara_nand_copy` 处理 `DHARA_NO_PAGE` | 掉电注入测试通过 | ❌ 未办(两处代码原样) | -| N-16 | 日志常驻 `FIL` + 批量 flush;UI 记录读缓存升级 | 单条日志 NAND 操作次数下降 ≥50% | ❌ 未办(每条仍 open/write/close) | -| D-1 | 芯片手册图片降采样 / 拆 submodule | clone 40 MB → ~8 MB | ❌ 未办;⚠ 且 `.git` 已膨胀到 **149 MB** → 还需一次历史重写,优先级上升 | -| U1–U4 | UI 令牌化 + 原型库 + 生成器 + golden | 新增一页 ≤30 行 | 🟡 **U4 的 golden 已建**(`simulator/tests/` 4 脚本);U1/U2/U3 未动;U4 的注册检查与字符集检查未入门禁 | -| P1-7 / P1-8 / P1-10 | 沿用台账(MAC 写序 / 重建失败恢复 / 测试 RAM 溢出) | 台账闭环 | P1-7/P1-8 本轮未复核,仍按台账跟踪;**P1-10 🟡 收尾中**(登记簿唯一 P 类项,剩"套件构建实测链接确认 + `@test` 闭环"两步) | - -### P2(一个季度内) - -`R-3/R-5/R-6/R-9`、`F-4/F-5/F-6`、S-1/S-2/S-5、P2-1..P2-16 余项。 - -> **2026-10-11 复核**:`R-3/R-5/R-6/R-9`、`F-4/F-5/F-6`、**S-1/S-2** ❌ 未办(`NETCFG`/`UI` 的语义层错位、4 处裸 `extern` 原样);**S-5 🟡 已实现核心**(`App/fs/vol_lock.c` + 主机侧 33 例单测),"可选全序列卷锁"的剩余范围在登记簿。 - -### P3(机会性) - -UI 剩余页面批量落地(U5)、目录级层次迁移(S-1 的物理搬迁)、`app_cfg.h` 开关体系重构、`Lib/lv_conf.h` 迁至 `Config/`。 - -> **2026-10-11 复核**:四条全部未动。⚠ 优先级更新:`Lib/lv_conf.h` 迁至 `Config/` 原建议"与 F-1..F-3 合并做"——**F-1..F-3 已于同日落地但位置未迁**,现需单独做,且要一并处理 `simulator/lv_conf.h`(两份必须保持同一特性子集,见 `../UI设计/UI_设计架构.md` §1.4 第 10 条);U5 的口径改为 **27 页**(33 叶子 − 6 已实现)。 +| 波次 | 本报告条目 | 验收要点 | +|------|-----------|---------| +| **W2 数据正确性** | N-12 / N-13(登记簿 N-21) | 整页扫描 + `DHARA_E_UNSUPPORTED` + **主机侧 dhara 移植层单测**(一并补覆盖率缺口) | +| **W3 预算与内存** | R-9 → R-2 后半 → R-3/R-4/R-6 逐项评估 | 按 `../内存管理/RAM预算台账.md` §3 解锁链走,每步前后跑 `stack_report.py` / `ram_report.py` | +| **W4 结构债** | N-16、N-14、F-4/F-5/F-6、S-1/S-2/S-4/S-5;`rs485_task.c` 随 B2 出注册白名单 | `--strict` 默认化;`bsp_err_t` 进规范 | +| **W5 验证常态** | P1-10 收尾(构建实测链接 + `@test` 闭环) | 逢窗口顺手做 | +| **W6 UI 扩展** | U4 残余 → U1 → U2/U3 → U5(27 页) | 新增一页 ≤30 行;**本轮不排期** | +| **W7 仓库决策** | D-1(登记簿 N-29) | **已决策暂不处理**;推送/协作前重评 | ### 交付物清单(可直接排期) -| 交付物 | 类型 | 来源条目 | **2026-10-11 状态** | -|--------|------|---------|---------------------| -| `[计划] scripts/check_registration.py` | 新脚本 + pre-commit hook | S-3 / N-06 | ❌ **仍未建**(`scripts/` 11 个脚本里没有它)——这是本报告里**唯一一条"1 小时工作量、至今未做"**的机械检查 | -| `scripts/ram_baseline.tsv` + `docs/内存管理/RAM预算台账.md` | 文档承诺落地 | §5.5 | ✅ **2026-10-12 已建**(`ram_baseline.py` 三模式:`--gate`/`--update`/`--trend`/`--write`;另有 `mem-budget` 钩子) | +| 交付物 | 类型 | 来源条目 | 状态 | +|--------|------|---------|------| +| `test/host/dhara/`(移植层单测) | 主机侧单测 + pre-commit hook | N-12/N-13 | ❌ 未建——**这是唯一能防住"同类缺陷再犯"的手段** | | `Drivers/BSP/UI/ui_tokens.h` | 令牌层 | U1 | ❌ 未建 | | `Drivers/BSP/UI/ui_w_{list,kv,records,form,msgbox,title}.{c,h}` | 组件库(需双端注册) | U2/U3 | ❌ 未建 | -| `tools/menu_gen/gen_menu.py` 扩展 + `ui_page_registry.c` 生成 | 生成器 | U3 | ❌ 未建(`gen_menu.py` 未扩展,注册表不存在) | -| `[计划] docs/内存管理/DMA可达性清单.md` | 规范(含"哪些缓冲禁止进 CCM") | N-11 | 🟡 **半**:独立清单未建,但铁律已进 AGENTS.md + 代码有运行时守卫;⚠ 需补"堆缓冲不可 DMA"这一新条 | +| `tools/menu_gen/gen_menu.py` 扩展 + `ui_page_registry.c` 生成 | 生成器 | U3 | ❌ 未建 | +| `check_coding_standard.py` 加 `malloc(` 正则 | 门禁补强 | §5.5 第 5 条 | ❌ 未建 | +| 字符集差集检查 + golden 挂 pre-commit | 门禁补强 | U4 残余 | ❌ 未建 | --- ## 附录 A 关键证据索引 -| 结论 | 证据 | **2026-10-11 复核** | -|------|------|---------------------| -| SRAM1 96.6%、余 4,488 B | map:`Total RW Size (RW Data + ZI Data) 126584 (123.62kB)`;scatter `RW_IRAM1 0x20000000 0x00020000` | 🟡 已缓解:`RW_IRAM1` **91,488 / 131,072 = 69.8%(余 39,584 B)**;`Total RW` 140,720 B | -| MicroLIB C 堆 = 512 B | map `:17405 0x2001e878 - 0x00000200 Zero RW HEAP startup_stm32f407xx.o`;`__heap_limit 0x2001ea78`;`MDK-ARM/startup_stm32f407xx.s:43 Heap_Size EQU 0x200` | ✅ 结论不变:`Heap_Size EQU 0x200` 未改,map 现 `Removing startup_stm32f407xx.o(HEAP), (512 bytes)`(未被引用 → 整段移除) | -| lftpd 已迁 FreeRTOS 堆 | map `lftpd_io.o refers to heap_4.o(i.pvPortMalloc)` | 不变 | -| 任务栈全部动态分配 | `Src/freertos.c:64-105` 的 `osThreadAttr_t` 无 `.stack_mem`;`freertos.o` ZI 仅 1,104 B | 不变(R-6 未办);7 任务栈合计 10,176 words,其中 adcTask 已抬到 640 words | -| NAND 直接 DMA 调用方缓冲 | `gd5f2gq5ue.c:200`、`:258`(`GD5F_DMA_THRESHOLD = 32`) | 🟡 模式未变(`:325` 等),阈值改 **1024**(`gd5f2gq5ue.h:78`)+ 新增**运行时 DMA 可达性守卫**(`:110-136`,回落轮询) | -| CH395F memcpy 自有缓冲 | `ch395f.c:1132-1190` | 不变(UI 缓冲仍可迁 CCM 的前提依旧成立) | -| FTP 无鉴权 | `Drivers/BSP/NET/lftpd/lftpd.c:367-371` | ❌ 仍在:现 `lftpd.c:375-379`(`cmd_pass`)/ `:553-557`(`cmd_user`) | -| 端口分配 | `App/sync/time_sync.c:25 TIME_SYNC_PORT 8888`;`Drivers/BSP/UI/ui_net_sink.h:29 UI_NET_SINK_PORT 7003`;FTP 21 | 不变 | -| 42 菜单叶子 / 7 内容页 | `Drivers/BSP/UI/ui_menu_tab.c`(生成,42 节点);`ui_content_page.c:84-92 s_defs[]` | 🟡 口径修正:`ui_menu_tab.h:28 UI_MENU_TAB_COUNT 42`(根 + 41 节点)→ **叶子 33**、**有内容页的叶子 6**(枚举 7 页含 PASSWORD) | -| 手写映射 | `ui_menu.c:155/:205 menu_content_page_for()` | 仍在,现位于 `ui_menu.c:118-128`(手写 `case 1u/3u/4u/30u/39u/40u:`) | -| 文档体量 | 工作树字节统计(见 §3.1 表) | 见 §3.1 复核列:受控 2,363 文件 / 77.7 MB,`docs/` 849 / 44.4 MB,图片 703 张 / 36.3 MB,`.git` **149.3 MB** | +| 结论 | 证据(2026-10-14) | +|------|---------------------| +| SRAM1 74.9%、余 32,928 B;CCM 87.9%、余 7,904 B | map `Execution Region RW_IRAM1 Size: 0x00017f60, Max: 0x00020000` / `RW_CCM Size: 0x0000e020, Max: 0x0000ff00`;`ram_baseline.tsv` 末行 | +| MicroLIB C 堆 = 512 B | `MDK-ARM/startup_stm32f407xx.s` `Heap_Size EQU 0x200`;map `Removing startup_stm32f407xx.o(HEAP), (512 bytes)`(未被引用 → 整段移除) | +| 任务栈全部动态分配 | `Src/freertos.c` 的 `osThreadAttr_t` 无 `.stack_mem`;8 条产品任务栈合计 9,792 words(R-6 未办) | +| DMA 可达性三层防线(原 N-11 的处置) | 编译期窗口断言 `gd5f2gq5ue.h:96-107`(负数组 typedef)→ 启动期自检 `nand_ftl.c:241-243` → 运行时守卫 `gd5f_dma_buf_ok()`(`gd5f2gq5ue.c:121-139`);规则族 R1~R4 → `../内存管理/内存内容分配策略.md` §4;负路径用例 TC-GD5F-704/705 | +| CH395F memcpy 自有缓冲 | `ch395f.c` SPI2 DMA 收发缓冲(`s_spi2_dma_tx/rx[2052]`)→ UI 缓冲仍可迁 CCM 的前提成立 | +| dhara 两处缺陷 | `nand_ftl.c:152-179`(`uint8_t buf[64]`)/ `:204-216`(read→prog 直传,无哨兵判断) | +| 日志逐条开关 | `sys_log_mcu.c:124-141`(`FA_WRITE | FA_OPEN_APPEND` → `f_write` → `f_close`,失败重建重试一次);`wdev.log` 同模式 `:486-489` | +| 端口分配 | `App/sync/time_sync.c` `TIME_SYNC_PORT 8888`;`Drivers/BSP/UI/ui_net_sink.h` `UI_NET_SINK_PORT 7003`;FTP 21 | +| 菜单 42 节点 / 33 叶子 / 6 个有内容页 | `ui_menu_tab.h:28 UI_MENU_TAB_COUNT 42`;`ui_content_page.c:84-92 s_defs[]`(7 页含 PASSWORD) | +| 手写映射 | `ui_menu.c:118-128 menu_content_page_for()`(`case 1u/3u/4u/30u/39u/40u:`) | +| 注册反向校验已机械化,仅剩一条刻意缺口 | `scripts/check_registration.py`(`KNOWN_GAPS` 只含 `App/task/rs485_task.c`,E-59/B2 刻意未接线)+ pre-commit `registration-check`;`lftpd_log.c` 已删除并入 `lftpd_auth.c` | +| 仓库体量 | 受控 2,466 文件 / 79.3 MB;`docs/` 56.9 MB;`docs/芯片手册/` 图片 703 张 / 36.3 MB;`.git` **235.3 MB** | ## 附录 C 评审中确认"无需改动"的设计(避免后续误改) | 项 | 结论 | |----|------| | CH395F SPI 分频 = 8 | **PCB 约束,禁止"优化"**(AGENTS 铁律,实测一致) | -| 就绪屏障用 Event Flags 而非魔法延时 | 正确,保留 | +| 就绪屏障用 Event Flags 而非魔法延时 | 正确,保留(就绪位必须 `osFlagsNoClear`) | | FatFs `FF_FS_REENTRANT=1` + 真实互斥 | 正确,保留(多数移植偷工) | -| 配置引擎属主 = defaultTask | 正确(P1-3 已收敛),任何新代码不得跨任务触碰 | +| 配置引擎属主 = defaultTask | 正确,任何新代码不得跨任务触碰 | | UI 只用 `lv_style_const_prop_t` | 正确且必要,禁止"顺手改成动态样式" | | `ffsystem.c` 引用 `sys_clock.h` | 有意为之(中间件需要真实时间),保留但在分层图中显式标注 | | UI 自绘而非 LVGL 控件 | 正确(1 bpp + 小 RAM),并是 Flash 裁剪的前提 | +| `configMAX_PRIORITIES = 56` | **不可下调**:CMSIS-RTOS2 `freertos_os2.h` 硬性 `#error` 要求等于 56(原 R-5 建议因此关闭) | +| 两份 `lv_conf.h` 同一特性子集 | 必要(F-1/F-2/F-3 的前提),由 `lv-conf-sync` 钩子守 | --- **报告结束。** -> **2026-10-11 复核收尾**: -> - ⚠ **台账漏登两条**:本报告 **N-02(FTP 无鉴权)** 与 **N-12/N-13(dhara 移植缺陷)** 既未修复也未登记在 `../任务规划/README.md` → 建议补登。 -> - ⚠ **编号撞车**:本报告曾用 N-18 指"无 CI"(该条已销项删除),而登记簿的 N-18 = "8 路电气量分属两片转换器(V0.2 改版项)"。引用时一律带文档名。 -> - ✅ `docs/内存管理/RAM预算台账.md` 与 `scripts/ram_baseline.tsv` **已于 2026-10-12 创建**(见 §5.5 各条闭环标注)。 -> - 本报告按 `docs/README.md` 的定位是**体系层评审报告,不是待办池**——待办一律以 `../任务规划/README.md` 为准;本报告只保留**未闭环问题的发现 + 证据**,修复即销项。 +> 本报告按 `../README.md` 的定位是**体系层评审报告,不是待办池**——待办与顺序一律以 [`../任务规划/README.md`](../任务规划/README.md) 为准;本报告只保留**未闭环问题的发现 + 证据**,修复即销项。 --- @@ -702,28 +619,32 @@ UI 剩余页面批量落地(U5)、目录级层次迁移(S-1 的物理搬 | 日期 | 类型 | 说明 | |------|------|------| -| 2026-09-18 | 初版 | 全量审查;基线 `HEAD = 2d0ec35`、`.map` 链接于 2026-09-16 12:33;产出 N-01..N-18 / D-1..D-7 / S-1..S-5 / R-1..R-9 / F-1..F-6 / U1..U5 编号体系 | -| 2026-09-18 | 实施记录 | N-01 按方案 A 落地(两处必要调整:分配收进 `cfg_file_store` 后端、`_Static_assert` 改 typedef 负数组) | -| 2026-09-25 | 归档 | §3 文档分布章节标注"已归档(章节保留作历史)" | -| 2026-10-10 | 就地更新 | N-10 改走"门控"并销项 | -| 2026-10-11 | 现状复核 | 按当前代码与 `.map` 重跑全部数字并就地标注(§0.1 评级、§1.3 口径、§2.1 规模、§3.1 体量、§4.x 逐条、§5.1 基线双列 + 对象排行、§5.3/§5.4 状态列、§6/§7 状态);复核基线 `HEAD = 54d6d2d` + 00:38 `.map` | -| **2026-10-11** | **修复即销项(本次)** | 与登记簿同一规则:**已修复项的分析正文与表格行从本报告删除**,报告只呈现未闭环问题(**874 → 733 行**,114 KB → 84 KB)。删除范围:§4.4.1 N-01 方案与实施记录、§4.1 N-03a/b·N-07·N-08·P2-13、§4.2 N-10、§4.8 P2-14/P2-15/N-17/N-18、§3.3 D-2..D-7、§5.3 R-1/R-7、§6.1 已闭环四层与字体、§7 P0/P1 已办行、交付物清单已建项、附录 A 已修行、**附录 B 断链清单整节**。§0.2/§0.4 改写为**未闭环清单**。原始推理与取证在未闭环条目中一律保留 | -| **2026-10-11** | **实施 + 销项:F-1/F-2/F-3** | `Lib/lv_conf.h` 与 `simulator/lv_conf.h` 对齐为同一特性子集(只留 `LV_DRAW_SW_SUPPORT_I1`、5 个未用控件类全关、`LV_USE_THEME_SIMPLE 0`、`LV_USE_FLEX/GRID 0`,并删掉 9.3 不存在的 `LV_USE_THEME_BASIC` 与无效 `LV_USE_OBJ`);配套补 `lv_draw_sw_blend_to_i1.c` 上游漏的守卫 → **省 64,576 B**:`ER_IROM1` 274,724 → **210,148 B**,Flash 26.3% → **20.1%**(比原估 54,630 B 多约 10 KB)。验证:`@build` 0 错 0 警;ui-verify 五步全过(**11 页黄金参考在裁剪前生成、裁剪后逐字节一致**,菜单 8 状态像素一致,池水位 5440/5520 → 5368/5448);`@test` 真机 PASS。本报告同步:§5.4 只留 F-4/F-5/F-6(数字按新镜像重测)、§0.1 Flash 评级 B→A−、§0.2 Top 6→Top 5、§0.4 Flash 行、§5.1 加二次实测、附录 A 删两条已闭环证据行 | +| 2026-09-18 | 初版 | 全量审查;基线 `HEAD = 2d0ec35`、`.map` 链接于 2026-09-16;产出 N-01..N-18 / D-1..D-7 / S-1..S-5 / R-1..R-9 / F-1..F-6 / U1..U5 编号体系 | +| 2026-10-11 | 实施 + 销项:F-1/F-2/F-3 | 两份 `lv_conf.h` 对齐同一特性子集 → 省 64,576 B;验证链(`@build` / ui-verify / `@test`)与对象级对照见 `../内存管理/RAM使用分析与优化方案.md` §2.6.4 | +| **2026-10-15** | **登记簿同步销项** | 本报告的 N-03c / N-11 对应登记簿 **N-20 / N-26** 两行已按「完成即销项」从 `../任务规划/登记簿.md` §5 删除(并清 `../任务规划/README.md` §1/§2 与 `排期详述.md` §1 的引用,W4 链由 6 项减为 5 项、结构/健壮性计数 9→7)。删除前补齐去处记录:`../子系统设计/看门狗设计说明.md` §4.4 新增 `Error_Handler` 统一降级一节、`../内存管理/内存内容分配策略.md` §4 R2 补编译期窗口断言与启动期自检两层 + §8 落点索引两行。同批订正登记簿三处过时内容:N-23 残余去掉已删除的 `lftpd_log.c`、N-25 去掉已删除的 `NET_RECV_TIMEOUT_MS`、N-27 剔除不可行的 R-5 并把 CCM 余量改为实测 7,904 B、N-29 的 `.git` 由 149 MB 订正为实测 235.3 MB | +| **2026-10-14** | **清理:删除已完成与过时内容** | ① **已修复项按销项删除**(去处见下表):N-02(FTP 鉴权)、N-03c(`Error_Handler` 降级)、**N-11(DMA 可达性三层防线 + 规则族 R1~R4)**、S-3(注册反向校验)、P2-5/P2-6(SD2506 负温度与 WRTC)、P2-8(`NET_RECV_TIMEOUT_MS` 已删)、P2-9(`mkdir` 幂等重试)、§5.5 前三条(内存治理三件套)、交付物清单已建项。② **删除过时层**:2026-09-16 / 2026-10-11 双列基线表与"复核期间未提交改动"口径说明、旧"组合结果预估"表、逐条"2026-10-11 复核"批注、历史变更记录行——内存数字改由 `../内存管理/RAM预算台账.md` 与 `scripts/ram_baseline.tsv` 持有。③ **按 2026-10-14 `.map` 与代码重测**全部保留结论的数字(含 `lftpd_log.c` 已删除并入 `lftpd_auth.c`,N-06 残余只剩 `rs485_task.c` 一条刻意缺口)。④ **修正三处错误**:R-5(`configMAX_PRIORITIES` 56→32)**判定不可行**(CMSIS-RTOS2 `#error`);原 §7 把报告 S-5(驱动统一错误码)与登记簿 S-5(卷锁覆盖面)混为一谈;原 §5.5 第 4 条建议的 `_Static_assert` 形态在 AC5 上取不到链接期地址,已被"窗口断言 + 启动自检 + 运行时守卫"三层取代。⑤ 报告自有编号**不回改、不重排**(`../编号规范.md`) | ### 已销项条目的记录去处 -> 删除前已逐条核对:以下去处文档均含完整记录,删了不丢信息。 +> 删除前已逐条核对:以下去处文档/代码均含完整记录,删了不丢信息。 | 已销项条目 | 去处 | |-----------|------| -| N-01(配置引擎 C 堆必失败) | `../子系统设计/配置文件设计说明.md` §6.2 / §8(v1.13) | +| N-01(配置引擎 C 堆必失败) | `../子系统设计/配置文件设计说明.md` §6.2 / §8 | +| N-02(FTP 无鉴权,登记簿 N-19) | `../子系统设计/网络配置说明.md` §3.4(`[ftp] user/pwd_hash/write`、恒定时间比较、5 败锁 60 s、出厂默认口令告警、明文嗅探边界)+ `../产品需求/产品需求设计文档.md` §FTP 鉴权 + `Drivers/BSP/NET/lftpd/lftpd_auth.c` + 主机侧 `test/host/sha256/`、`test/host/ftp_auth/` | | N-03a/N-03b/N-07/N-08(IWDG、`configASSERT`、两个钩子、4 个 Fault 现场) | `../子系统设计/看门狗设计说明.md`(§4.4、§10.1/§10.2)+ AGENTS.md「平台约束」 | +| N-03c(`Error_Handler` 死循环,登记簿 **N-20**,2026-10-15 已销项) | `../子系统设计/看门狗设计说明.md` §4.4(统一降级:1 条自检 + `NVIC_SystemReset()`、三条实现约束含 `__builtin_return_address` 不可用的实证、内核前日志路径安全性、重生成存活三层)+ 实施代码 `Src/main.c` 的 `USER CODE Error_Handler_Debug` 区注释 + `scripts/check_cubemx_survival.py` 审计项 | +| N-06 主体(`crc.c`/`ringbuf.c`/`lftpd_log.c` 删除、`adc_task.c` 注册)+ S-3(注册反向校验,登记簿 N-23) | `scripts/check_registration.py`(`KNOWN_GAPS` 白名单 + `--strict`)+ `../代码规范/pre-commit使用说明.md`(`registration-check`);`lftpd_log.c` 并入 `Drivers/BSP/NET/lftpd/lftpd_auth.c` | +| **N-11**(NAND 直 DMA 调用方缓冲 / CCM 迁移硬边界,登记簿 **N-26**,2026-10-15 已销项) | `../内存管理/内存内容分配策略.md` §3(全板 DMA 使用方清点)+ §4 规则 R1~R4(**R3 堆/栈禁为 DMA 源/目的**,含"缓冲自身地址无法编译期断言"这条边界)+ §8 落点索引;代码三层 = `gd5f2gq5ue.h:96-107` 编译期窗口断言 → `nand_ftl.c:241-243` 启动期自检 → `gd5f_dma_buf_ok()` 运行时强制;负路径用例 TC-GD5F-704/705 | | N-10(UI accept 门控) | `../UI设计/RemoDispBus协议与接收设计说明.md` §3 + 看门狗设计说明 §11.4 | -| P1-1(ADC 数据路径) | `../子系统设计/ADC采样节拍设计说明.md` §10/§11 + `App/algo/README.md` | -| N-17(栈/堆水位观测) | `../内存管理/RAM使用分析与优化方案.md` + `../测试说明/实机测试实施指南.md` | -| N-18(本报告口径:无 CI) | `../测试说明/测试开发标准流程.md` §9 + `../环境与部署/CI配置指南.md` | +| N-17(栈/堆水位观测)+ R-8(观测常开) | `../内存管理/RAM使用分析与优化方案.md` + `../测试说明/实机测试实施指南.md`(`[STACK]` 水位 + `stack_report.py` + `ram_report.py`) | +| N-18(本报告口径:无 CI) | `../测试说明/测试开发标准流程.md` §9 + `../CI/README.md` | +| P2-5 / P2-6(SD2506 负温度解析、WRTC 解锁顺序) | `Drivers/BSP/SD2506/sd2506.c`(符号扩展 `val \| 0xF0`;WRTC1→WRTC2/3 顺序按手册,上电即写禁止态) | +| P2-8(`net_recv` 30 s 阻塞超时) | `Drivers/BSP/NET/net_config.h` 头注(2026-10-13 recv 语义主流化:删除 `NET_RECV_TIMEOUT_MS`,预算由调用方显式给)+ `../网络/Net层阻塞模型架构设计.md` | +| P2-9(`mkdir` 失败不再重试) | `App/log/sys_log_mcu.c`(`s_fs_ready` 幂等 + `mkdir /log fail (%d), will retry` + 上报自检) | | P2-13 / P2-14 / P2-15 | AGENTS.md(`test-suite-switch`、`keil-build`、`stlink-flash`)+ `../测试说明/ST-LINK_CLI_复位使用说明.md` | -| D-2 / D-3 / D-4 / D-5 / D-6 / D-7 | `../文档规范.md` + `../代码规范/pre-commit使用说明.md`;断链由 `scripts/check_doc_links.py` 门禁接管(`--all` = 129 文档 / 0 失效) | -| U4(golden 回归) | `simulator/tests/` 四脚本 + `../UI设计/UI_自动化测试流程.md` | +| D-2 / D-3 / D-4 / D-5 / D-6 / D-7 | `../文档规范.md` + `../代码规范/pre-commit使用说明.md`;断链由 `scripts/check_doc_links.py` 门禁接管 | +| U4 主体(golden 回归) | `simulator/tests/` 四脚本 + `../UI设计/UI_自动化测试流程.md`(残余 = 挂门禁,见 §6.3) | | R-1 / R-7 | `../子系统设计/看门狗设计说明.md` + AGENTS.md 任务表(`scripts/gen_task_table.py` 生成区) | -| F-1 / F-2 / F-3(LVGL 死代码裁剪,2026-10-11) | `../内存管理/RAM使用分析与优化方案.md` §2.6.4(对象级前后对照 + 上游漏守卫的配套补丁);`../UI设计/UI_设计架构.md` §1.4 第 10 条(**两份 `lv_conf.h` 必须同一特性子集**这条新硬约束);`../UI设计/UI_自动化测试流程.md` 变更记录(池水位基线 5440/5520 → 5368/5448 及理由) | +| §5.5 前三条(92% 门禁 / 趋势基线 / 预算台账,登记簿 N-27 前半) | `../内存管理/RAM预算台账.md`(阈值与配额的**唯一权威**)+ `scripts/ram_baseline.py`(`--gate/--update/--trend/--write`)+ `../代码规范/pre-commit使用说明.md`(`mem-budget`) | +| F-1 / F-2 / F-3(LVGL 死代码裁剪) | `../内存管理/RAM使用分析与优化方案.md` §2.6.4;`../UI设计/UI_设计架构.md` §1.4 第 10 条(**两份 `lv_conf.h` 必须同一特性子集**);`../UI设计/UI_自动化测试流程.md` 变更记录(池水位基线) | diff --git a/test/board/net_test_task.c b/test/board/net_test_task.c index 1b6c587f..d74c067a 100644 --- a/test/board/net_test_task.c +++ b/test/board/net_test_task.c @@ -292,12 +292,13 @@ static void phase7_test_timeout(void) } /* TC 704:多 Socket 并发 TCP Client。 - * Socket 0~7 由索引寻址 + 缓冲显式分配即可全部使用(FUN_PARA bit0 是保留位, - * 与 Socket 个数无关,见 TRAP-NET-15);本用例同时打开 Socket 0~7, - * 各自连 PC echo server,每路独立收发 PHASE7_EXCH_SIZE 字节回显并比对, - * 验证 8 个 Socket 均能独立工作。依赖 PC 端 tcp_server 支持并发连接。 */ + * 并发上限 = NET_MAX_SOCKETS(8) − 3 个常驻监听(FTP / 对时 / 远显)= 5; + * 本用例同时打开 Socket 0~4,各自连 PC echo server,每路独立收发 + * PHASE7_EXCH_SIZE 字节回显并比对,验证 5 个 Socket 均能独立工作。 + * 依赖 PC 端 tcp_server 支持并发连接(--max-conn ≥ 5)。 + * (2026-10-15:原值 8 超出并发上限,socket create #5 必失败。) */ #ifndef PHASE7_MULTI_COUNT -#define PHASE7_MULTI_COUNT 8 +#define PHASE7_MULTI_COUNT 5 #endif #ifndef PHASE7_MULTI_BASE_PORT #define PHASE7_MULTI_BASE_PORT 51000 @@ -853,6 +854,7 @@ void net_layer_test_main(void) int32_t sockfd = -1; int32_t phase_active = 0; uint32_t accept_retry = 0; + uint32_t gen = 0; /* 建立监听时的网络代次(代次变化 = 网络已重建) */ struct net_sockaddr_in client_addr; int32_t addrlen = sizeof(client_addr); @@ -880,6 +882,7 @@ void net_layer_test_main(void) return; } DBG_INFO("Test task: listening on 8080 (single-socket mode)"); + gen = net_get_generation(); /* 记录本监听对应的网络代次 */ /* ==================== 事件驱动阶段 ==================== * Phase 6/8~11(NET 层)在 TCP 监听循环中运行,需要 PC 客户端连接。 @@ -898,6 +901,25 @@ void net_layer_test_main(void) ) { int32_t acc = net_accept(sockfd, (struct net_sockaddr *)&client_addr, &addrlen); if (acc < 0) { + /* 网络重建(代次变化)→ 旧监听随芯片复位失效,重建 8080 监听 */ + if (net_get_generation() != gen) { + struct net_sockaddr_in addr; + DBG_INFO("Test task: network rebuilt, relisten 8080"); + net_close(sockfd); + sockfd = net_socket(NET_AF_INET, NET_SOCK_STREAM); + memset(&addr, 0, sizeof(addr)); + addr.sin_family = NET_AF_INET; + addr.sin_port = net_htons(8080); + if (sockfd < 0 || + net_bind(sockfd, (struct net_sockaddr *)&addr, sizeof(addr)) < 0 || + net_listen(sockfd) < 0) { + DBG_ERROR("Test task: relisten failed"); + return; + } + gen = net_get_generation(); + accept_retry = 0; + continue; + } if (accept_retry % 100 == 0) { DBG_INFO("Test task: waiting for connection..."); } diff --git a/test/host/README.md b/test/host/README.md index 1e48ad83..acc75113 100644 --- a/test/host/README.md +++ b/test/host/README.md @@ -10,7 +10,7 @@ |---|---| | `run_boot_test.py` | **`@test` 一键闭环**:开串口+清日志 → 烧录 → wait-idle → `verify_log` 校验(退出码 0=PASS) | | `verify_log.py` | **日志标记校验**:Required / Forbidden 标记 → PASS / FAIL;可选 `--require-suite-summary` 校验套件汇总行(`=== <名称>: N/M PASSED (failed=0) ===` 且 `N>0`),抓"没跑到汇总行/断言数为 0"的盲区(T-2) | -| `e02_probe.py` | E-02 专项探针(错误码传播审计) | +| `e02_probe.py` | E-02 专项探针(历史审计编号) | ### 网络 / FTP diff --git a/test/host/cfg_file/cfg_file_test.c b/test/host/cfg_file/cfg_file_test.c index 67f29680..25e587e6 100644 --- a/test/host/cfg_file/cfg_file_test.c +++ b/test/host/cfg_file/cfg_file_test.c @@ -33,6 +33,7 @@ * TC-CFG-021 非法键定义拒绝(UINT len 非 1/2/4、MAC len≠6、STR len>79) * TC-CFG-022 段名超长拒绝(≥ CFG_NAME_MAX) * TC-CFG-023 挂回调段 data_size > CFG_SNAP_MAX 拒绝 + * TC-CFG-024 键名超长拒绝(≥ CFG_NAME_MAX);恰好 15 字符(= CFG_FILE_MAX_NAME_LEN)接受 * —— load 契约(cfg_file_load)—— * TC-CFG-030 文件缺失 -> 生成出厂模板写盘 + 返回 0 * TC-CFG-031 文件存在 -> 解析有效键进 RAM 副本 @@ -574,6 +575,31 @@ static void tc_register(void) { r.p_cb = NULL; CHECK(cfg_file_register(&r) == 0, "same section without callback accepted"); } + + printf("[TC-CFG-024] key name length limit\n"); + { + cfg_section_reg_t r; + static const cfg_key_def_t long_key[] = { + { "0123456789abcdef", CFG_VAL_UINT, 0u, 1u } /* 16 chars >= CFG_NAME_MAX(16) */ + }; + static const cfg_key_def_t max_key[] = { + { "0123456789abcde", CFG_VAL_UINT, 0u, 1u } /* 15 chars = CFG_FILE_MAX_NAME_LEN */ + }; + + vfs_reset(); + memset(&r, 0, sizeof(r)); + r.name = "klen"; + r.p_data = &s_net_data; + r.p_defaults = &s_net_def; + r.data_size = 8u; + r.key_count = 1u; + + r.p_keys = long_key; + CHECK(cfg_file_register(&r) == -1, "key name >= 16 chars rejected"); + + r.p_keys = max_key; + CHECK(cfg_file_register(&r) == 0, "key name == 15 chars accepted"); + } } /* TC-CFG-030~034:load 契约 */ diff --git a/test/host/e02_probe.py b/test/host/e02_probe.py index 66d98220..ed01b73f 100644 --- a/test/host/e02_probe.py +++ b/test/host/e02_probe.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 """E-02 头阻塞专项台架探针(timeSync 侧,A/B 对照用)。 -场景(错误码传播审计.md E-02 的 timeSync 触发形态): +场景(历史审计 E-02 的 timeSync 触发形态,属主文档已于 2026-10-15 删除): 1. 一个"挂起"的对时客户端连上 :8888 后不发任何数据(空闲/故障客户端, 常见工况)——旧固件在此触发 30s 阻塞 net_recv 独占 netTask, 全体消费者的队列消息停摆最长 30s;