@@ -5,39 +5,44 @@
| 审查日期 | 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 文件)与构建产物内存画像 |
| 审查方式 | 静态审查 + 链接图(.map)量化分析 + 构建脚本/门禁核对;**未做上机动态验证**(除特别标注外) |
| 目标 | 结构合理性、文档分布合理性、模块设计合理性、改进路线图、内存优化、UI 标准化 |
> **与既有文档的关系**:本报告不重复 `docs/待处理内容.md`(逐条缺陷台账)与 `docs/内存管理/RAM使用分析与优化方案.md`(内存专项方案),而是做**体系层评审 + 量化补强 + 新增发现**。新增发现统一编号 `N-xx`,已在台账中的条目按 `P1-x/P2-x/P3-x` 引用。
> **落地进展(2026-09-18) **: **N-01 已修复**( L0 方案,台账 `P0-1`)——`cfg_file_store` 后端新增 `cfg_store_alloc/cfg_store_free/cfg_store_heap_free`, MCU 端落到 FreeRTOS 堆(`pvPortMalloc`),共享核心 C 库分配符号清零 + 编译期堆峰值预算 + 四条失败路径上报自检记录。双端 0 错 0 警、`--cfgtest` PASS; map 复核 `malloc.o` 已不参与链接且 `Removing startup_stm32f407xx.o(HEAP), (512 bytes)`, **SRAM1 由 126,584 B / 96.6%(余 4,488 B)降到 126,064 B / 96.2%(余 5,008 B)**。本报告其余内存数字仍以修复前的 2026-09-16 map 为基线,逐条换算时扣 512 B 即可;**真机 `@test` 正向确认已于 2026-10-03 执行并 PASS( 2026-10-04 独立复跑 PASS)**,证据见 `子系统设计/配置文件设计说明.md` v1.13 。
>
> **销项约定(2026-10-11 起)**:本报告采用与登记簿相同的**「修复即销项」**——问题修复后其分析正文与表格行**从本报告删除**,完整记录沉淀到对应模块文档(去处清单见文末**变更记录**)。因此本报告**只呈现未闭环问题**,不再随时间膨胀 。
---
## 0. 执行摘要
### 0.1 六维评分
### 0.1 六维评分( 2026-09-18 评级 + 2026-10-11 复核)
| 维度 | 评分 | 一句话结论 |
|------|------|-----------|
| 项目结构 | **B+ ** | 六层分层真实可执行、命名一致、门禁到位;主要问题是 **BSP 层承载业务模块 ** 、**双构建清单漂移**、**5 个源文件两边都不编译** |
| 文档分布 | **B- ** | 工程文档密度与深度高于同级嵌入式项目;主要问题是 **文档占仓库 60% 体积(其中芯片手册图片 40.5 MB) ** 、**README 任务表 4× 过期**、**约 18 条断链**、**文档承诺了不存在的工具/台账** |
| 模块设计 | **B ** | 配置引擎、网络串行化、就绪屏障、FatFs 互斥等设计成熟;主要问题是 **失败路径普遍"死循环/静默降级" ** 、**无看门狗**、**FTP 无鉴权**、**cfg_file 依赖注定失败的 malloc** |
| 内存使用 | **C ** | SRAM1 已用 96.6%(余 4,488 B),**64 KB CCM 完全空转**, FreeRTOS 堆被 6 条任务栈吃掉 2/3,**栈/堆水位零观测** |
| Flash 使用 | **B+ ** | 仅用 23.4%;但 * * ≈50 KB(20% 镜像)是可证明的死代码**(LVGL 配置漂移所致) |
| UI 标准化 | **B- ** | 已有"声明源→生成器→表驱动"的正确骨架;缺**设计令牌层**、**页面原型库**、**注册自动化**、**golden 回归**,42 个菜单叶子仅 7 个有内容页 |
| 维度 | 评分 | 一句话结论 | **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 个有内容页 ** |
### 0.2 必须优先处理的问题(Top 6 )
### 0.2 未闭环问题(Top 5, 2026-10-11 )
| # | 编号 | 问题 | 影响 | 状态 |
> 已修复/已消解的条目按「修复即销项」从本报告删除,完整记录见对应模块文档(去处清单见文末**变更记录**)。
| # | 编号 | 问题 | 影响 | 现状 |
|---|------|------|------|------|
| 1 | **N-01 ** | `App/cfg/cfg_file.c` 用 C 库 `malloc(4097/5120)` ,而本工程 MicroLIB C 堆只有 **512 B ** ( `startup_stm32f407xx.s:43 Heap_Size EQU 0x200` , map 中 `HEAP` ZI = `0x200` ) | 真机上配置**加载/模板生成/外科式保存全部失败**并静默回落工厂默认;PC 模拟器用宿主 CRT 堆 → 掩盖问题 | ✅ **已修复(2026-09-18, L0;台账 P0-1) ** , **真机 `@test` 正向确认 PASS( 2026-10-03; 2026-10-04 复跑 PASS) ** |
| 2 | **N-02 ** | `lftpd.c:367-371 cmd_pass` 对任意口令无条件回 `230` | 局域网内任意主机可读写 `/config.ini` (含 IP/MAC/网关)→ 可远程断网;单连接 CH395F 上还可挤掉 UI/对时服务 | 代码级确凿 |
| 3 | **N-0 3 ** | 无独立看门狗(IWDG); `configASSERT` 为 `taskDISABLE_INTERRUPTS(); for(;;);` ;多处初始化失败即 `for (;;) ` 挂死 | 现场死机不可自恢复,且断言时**连日志都没有** | 全局 |
| 4 | **N-04 ** | SRAM1 余 4,488 B( P0-1 修复后 5,008 B)而 **CCM 64 KB 未使用 ** ; **栈水位零观测**( `uxTaskGetStackHighWaterMark` 全库零调用);堆水位原本被链接器整体裁掉,随 P0-1 修复已由 `cfg_store_heap_free` 引入( `load: OK …, heap free NB` 行开始打印实测值) | 任何新功能都会撞 `L6406E` ;无法判断栈是过大还是不足 | 量化见 §5 |
| 5 | **N-05 ** | `Lib/lv_conf.h` 与 LVGL 9.3 漂移:设了不存在的 `LV_USE_THEME_BASIC` ,未设 `LV_USE_THEME_SIMPLE` (默认 1)→ 运行时强链 `lv_theme_simple` 并把 label/buttonmatrix/textarea/bar 拉进镜像; `LV_DRAW_SW_SUPPORT_*` 全默认 1 | ≈**50 KB 死代码**( 35,310 B 非 1bpp 混合核 + 13,389 B 未用控件 + 866 B 主题) | 见 §5.4 |
| 6 | **N-06 ** | 5 个 `.c` 既不在 uvprojx 也不在 simulator( `adc_task.c` /`crc.c` /`lftpd_log.c` /`ringbuf.c` /`rs485_task.c` ) | "写了不编译"= 死代码 + 误导;`crc` /`ringbuf` 是 RS485 协议的前置件却无人注册 | 见 §2.3 |
| 1 | **N-02 ** | `lftpd.c cmd_pass` 对任意口令无条件回 `230` (现 `:375-379` ; `cmd_user :553-557` 同) | 局域网内任意主机可读写 `/config.ini` (含 IP/MAC/网关)→ 可远程断网;单连接 CH395F 上还可挤掉 UI/对时服务 | ❌ 一行未改;⚠ 且**不在 `../待处理内容.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-1 3 ** | `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 栈) |
⚠ 另有两条**不在上表但需排期**: `Error_Handler` 仍 `__disable_irq(); while(1){}` ( N-03c,§4.1)、`.git` 已膨胀到 **149 MB ** ( D-1,§3.1)。
### 0.3 三条主线建议
@@ -47,6 +52,33 @@
---
## 0.4 未闭环清单(2026-10-11 复核)
> 只列**未办 / 半办**项;已修项已按「修复即销项」删除(去处见变更记录)。图例:❌ 未办 / 🟡 半办。
| 类别 | 编号 | 未闭环内容 | 下一步 |
|------|------|-----------|--------|
| **安全 ** | **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 ❌ | `ram_baseline.tsv` 、`RAM预算台账.md` 、92% 阈值门禁均未建 | 三条一起补,否则 R-2 后半段无法排期 |
| **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 **同号不同义 ** | 补登 + 引用一律带文档名 |
---
## 1. 审查方法与证据基线
### 1.1 数据源
@@ -81,8 +113,8 @@ 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);`ROM` 再加 `RW-data` 初始化表(= 246,016 B ) |
| RAM 占用 | `RW-data + ZI-data` = 126,584 B, 上限 128 KB( 0x20000000);**CCM 64 KB 不在 scatter 内, 计 0** |
| 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"已不成立 |
| "死代码" | 该对象在镜像中有码,但其功能被工程配置(如 `LV_COLOR_DEPTH 1` )或调用点扫描(零调用者)证明不可达 |
---
@@ -93,41 +125,40 @@ Select-String -Path 'MDK-ARM\STM32F407-Demo\STM32F407-Demo.map' -Pattern '__heap
| 分类 | 文件 | 行数 | 说明 |
|------|------|------|------|
| `App/` (自研应用) | 1 8 | 3,852 | 配置引擎、墙钟、日志、任务薄壳 |
| `Drivers/BSP/` (自研驱动/子系统) | 62 | 16,369 | 其中 **UI 子系统 5,875 行 ** (含 `ui_font_cn_12.c` 1,211) |
| `test/` (自研测试) | 13 | 4,132 | 13 个测试模块 |
| **自研合计 ** | **108 ** | **24,353 ** | |
| CubeMX `Src/` +`Inc/` | 22 | 4,286 | 受 USER CODE 标记保护 |
| `simulator/` | — | 2,29 2 | PC SDL2 宿主 |
| 第三方 | — | ≈55 万行 | LVGL 9.3( uvprojx 编译 201 个 `.c` )、FreeRTOS、FatFs、dhara、lftpd |
| `App/` (自研应用) | 3 8 | 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/` (自研测试) | 4 1 | 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,46 2 | PC SDL2 宿主 |
| 第三方 `Lib/` | 958 | 529,916 | LVGL 9.3( 423 个 `.c` / 421,546 行 )、FreeRTOS、FatFs、dhara、lftpd |
**评价 ** :自研体量与第三方体量比约 1:20,属于 典型的"薄自研 + 厚中间件"。这决定了**结构审查的重点不在自研代码内部,而在"自研如何约束第三方"**——而 §5.4 会证明 这层约束(`lv_conf.h` ) 目前是 失守的。
**评价 ** :自研体量与第三方体量比约 **1:14 ** (原 1:20,自研涨了一倍半),仍属 典型的"薄自研 + 厚中间件"。这决定了**结构审查的重点不在自研代码内部,而在"自研如何约束第三方"**——§5.4 曾量化 这层约束(`lv_conf.h` )失守的代价:**64,576 B 死代码**( 2026-10-11 已修) 。
### 2.2 分层与依赖方向
实测自研层间的**全部反向依赖**(BSP → App) 只有 4 处 :
实测自研层间的**全部反向依赖**(BSP → App) ——2026-10-11 复核为 **3 处 ** (原 4 处, `net_cfg_legacy.c` 已随旧档迁移路径整体删除,见 §2.4) :
| 位置 | 反向 include | 判定 |
|------|-------------|------|
| `Drivers/BSP/NETCFG/net_cfg.c:28 ` | `cfg_file.h` | 可接受但需说明:NETCFG 实为"业务配置模块",放在 BSP 层是历史命名 |
| `Drivers/BSP/NETCFG/net_cfg_legacy.c:19,21` | `cfg_file.h` 、`cfg_file_store.h` | 同上 |
| `Drivers/BSP/NETCFG/net_cfg.c:31 ` | `cfg_file.h` | 可接受但需说明:NETCFG 实为"业务配置模块",放在 BSP 层是历史命名 |
| `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` | `sys_clock.h` (App/util) | 中间件→应用;已在文件内注释声明为"④中间件层"有意为之 |
| `Lib/FatFs/ffsystem.c:19 ` | `sys_clock.h` (App/util) | 中间件→应用;已在文件内注释声明为"④中间件层"有意为之 |
**评价:良好 ** 。在 24 k 行规模上把反向依赖控制在 4 处,说明分层不是墙上的图。但存在**语义层错位**:
**评价:良好 ** 。在 38 k 行规模上把反向依赖控制在 3 处,说明分层不是墙上的图。但存在**语义层错位**:
- `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 的注册问题叠加)。
依赖方向裸引用(绕过头文件契约的 `extern` )实测 4 处:
依赖方向裸引用(绕过头文件契约的 `extern` )实测 4 处——**2026-10-11 复核:4 处全部仍在**(仅行号漂移),S-2 未办 :
| 位置 | 裸 extern |
|------|-----------|
| `Drivers/BSP/NET /ch395f.c:143 ` | `extern volatile uint8_t g_spi1_dma_done;` (实为 GD5F 的变量,跨模块反向 ) |
| `Drivers/BSP/GD5F2GQ5UE/gd5f2gq5ue.c:43 ` | `extern SPI_HandleTypeDef hspi1;` |
| `Drivers/BSP/NET/net_socket.c:51 ` | `extern osMessageQueueId_t netMsgQueueHandle;` |
| `Drivers/BSP/sd 2506/sd2506.c:17 ` | `extern I2C_HandleTypeDef hi2c1;` |
| `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/SD 2506/sd2506.c:20 ` | `extern I2C_HandleTypeDef hi2c1;` ( `Inc/i2c.h:35` 已导出,局部重复) |
**建议(S-2) ** : CubeMX 句柄类(`hspi1` /`hi2c1` )统一由 `main.h` 导出并 include,删除局部 extern; `g_spi1_dma_done` 应由 GD5F 模块自己定义并在头文件 `extern` 声明(当前跨模块裸 extern 是真正的耦合隐患);`netMsgQueueHandle` 改为 net_socket 内部持有 + 提供投递 API。
@@ -137,11 +168,11 @@ Select-String -Path 'MDK-ARM\STM32F407-Demo\STM32F407-Demo.map' -Pattern '__heap
| 类别 | 文件 | 后果 |
|------|------|------|
| **两边都不注册 ** | `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` | 永不编译 → 死代码; `crc.c` / `ringbuf.c` 是 RS485 帧协议前置件,无人注册即无人使用; `lftpd_log.c` 与 `lftpd_log.h` (现为 DBG 宏) 语义重复 |
| **两边都不注册 ** | 基线 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 宏语义重复)仍两边不编** |
| **仅 simulator 注册 ** | `cfg_file_store_sim.c` 、`net_socket_sim.c` 、`sim_port.c` 、`sim_syslog.c` 、`ui_net_sink_backend_sim.c` | 正常(后端适配层模式) |
| **uvprojx ** | 290 个 `.c` (含 201 个 LVGL) | LVGL 全量编译 → 编译时长;死代码问题被"全编"放大(见 §5.4) |
| **uvprojx ** | 306 个 `.c` (含 201 个 LVGL) (基线 290) | LVGL 全量编译 → 编译时长;死代码问题被"全编"放大(见 §5.4) |
**评价 ** : AGENTS.md 已把"新文件必须注册两处"写成铁律并有 `new-source-file` skill,但**缺少反向校验**( "存在但未注册")。这是纯粹的机械检查缺口。
**评价 ** : 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;
@@ -153,52 +184,32 @@ Select-String -Path 'MDK-ARM\STM32F407-Demo\STM32F407-Demo.map' -Pattern '__heap
| 观察 | 证据 | 判定 |
|------|------|------|
| 根目录散置工作文件 | `待处理内容.md` (仅 2 行的 RAM 待办)、 ` ui_menu_tree.txt` 、`ui_cn_chars.txt` | 菜单树是**生成器输入**,放根目录合理但需 在 `docs/` 有权威说明; `待处理内容.md` 内容已被 `docs/内存管理/` 覆盖 → 建议合并后删除 |
| 根目录散置工作文件 | `ui_menu_tree.txt` 、`ui_cn_chars.txt` | 菜单树/字符集 是**生成器输入**,放根目录合理且已 在 `docs/` 有权威说明(原点名的根目录 `待处理内容.md` 已并入 `docs/待处理内容.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 可裁剪" |
| 测试任务无条件创建 | `Src/freertos.c:171` | 与台账 **P2-13 ** 同源;产品镜像白耗 4 KB 栈 |
| 未使用的 CCM | `.sct` 只有 `ER_IROM1` + `RW_IRAM1 0x20000000 0x20000` | 见 §5 |
| 功能开关名存实亡 | `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` 仍在且仍零引用 |
### 2.5 结构结论
``` mermaid
graph TD
A[app_main_init CubeMX] --> B[osKernelStart]
B --> T1[defaultTask 512w<br/>FS挂载+配置引擎属主]
B --> T2[netTask 1024w<br/>net_poll+消息串行]
B --> T3[uiTask 4096w<br/>LVGL+UI sink]
B --> T4[ftpTask 1024w]
B --> T5[timeSyncTask 512w]
B --> T6[testTask 1024w<br/>产品构建也在跑]
T2 -.netMsgQueue 深8.-> T2
T3 -.10ms 轮询 accept.-> T2
T4 -.等待.-> E1[NET_READY & STORAGE_READY]
T5 -.等待.-> E1
```
**总体判断:结构合理,无需重构 ** 。要做的是四类**低成本纠偏**:S-1 层次定性、S-2 消裸 extern、S-3 注册反向校验、S-4 让 `app_cfg.h` 开关真正生效或删除(并把 `APP_CCM_ENABLE` /`APP_UI_NET_SINK` 纳入同一处)。
---
## 3. 文档分布合理性
### 3.1 体量画像(实测,工作树)
### 3.1 体量画像(实测,工作树; 2026-10-11 复核,括号内为 2026-09-18 基线)
| 对象 | 文件数 | 体积 | 占仓库 |
|------|--------|------|--------|
| 仓库受控文件总计 | 2,013 | **78.3 MB ** | 100% |
| `docs/` 合计 | 652 | **47.19 MB ** | **60.3 % ** |
| └ `docs/芯片手册/images` | 564 张 JPG | **40.5 MB ** | **51 .7% ** |
| └ `docs/images/引用关系图.json` | 1 | 1,052 KB | 1.3% |
| └ `docs/UI设计/mockups` | 15 | 1,110 KB | 1.4% |
| `.git` pack | — | 46.6 MB | — |
| 工程文档(Markdown,自研) | 40 | 14,124 行 | — |
| 外部资料(Markdown, datasheet 转录) | 16 | 59,142 行 | — |
| 仓库受控文件总计 | 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 行) | — | — |
**关键失衡 ** :工程文档 14,124 行 vs 外部资料 59,142 行;**图片体量 是全部 Markdown 的 3 倍**。这意味着:
- `git clone` 的净耗时几乎全部花在扫描手册图片上;
- 与工程决策相关的信息(40 篇)在体积上被完全淹没;
- `引用关系图.json` ( 1 MB)是**生成产物**,与 `.dot/.svg` 一并提交(AGENTS.md 明确要求),但它对阅读无价值、对 diff 有害。
**关键失衡(复核后仍在,但已缓解) ** :工程文档行数从 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` 类历史清理才能见效) 。
### 3.2 做得好的(应保留并显式制度化)
@@ -218,72 +229,17 @@ graph TD
2. **推荐 ** : `docs/芯片手册/` 拆为独立仓库 `dtu-datasheets` ,用 `git submodule` 挂回原路径(现有全部链接不需改写);
3. **最正 ** : Git LFS(需要托管端支持)。
#### D-2( P1) README 任务表 4× 过期,与 AGENTS.md 冲突
`README.md:71-76` 仍写 `defaultTask 2048 words / netTask 4096 / uiTask 16384` ,而 `Src/freertos.c:64-105` 与 `AGENTS.md` 均为 `512/1024/4096` 。台账 **P2-16 ** 只修了 AGENTS.md, README 漏修。
**建议 ** :
- 立即修正(一次性);
- **根治**:任务表由脚本从 `Src/freertos.c` 解析生成,README/AGENTS 引用同一生成块(`scripts/gen_task_table.py` 输出 Markdown 片段,pre-commit 校验一致性)。这与 §7 的"文档-代码一致性门禁"合并实施。
> ✅ **2026-09-25 已处理**: README 任务表已修正为 `1024/1024/1024/2048/4096/512`;两表均改为**受管生成区**(锚点 `<!-- TASK_TABLE:BEGIN/END -->`),`scripts/gen_task_table.py` 以 `Src/freertos.c` 的 `osThreadAttr_t` 为唯一事实源生成 / 校验(`--check` / `--write` / `--diff`),pre-commit `task-table` 钩子拦截漂移。改栈流程写入 `docs/文档规范.md` §4.2。
#### D-3(P1)约 18 条断链(目录搬迁与工具未落地导致)
分诊后**真实失效**清单(附录 B 有完整列表):
| 类型 | 代表位置 | 说明 |
|------|---------|------|
| 目录搬迁遗留 | `docs/测试说明/{FTP,GD5F2GQ5UE,SD2506}_Test_Guide.md` 、`测试开发标准流程.md:358` 、`问题记录/CH395F_Trap_Records.md:434` | 仍指旧路径(`docs/` 根下) |
| 路径片段错误 | `docs/代码规范/cppcheck使用说明.md:3` 、`docs/子系统设计/配置文件设计说明.md:159/160/494/568-570` | 相对层级算错或文件已改名 |
| **指向不存在的工具 ** | `UI_界面制作流程.md:413` 、`UI_自动化测试流程.md:104` → `tools/ui_mock/` | **文档承诺了未实现的 UI 渲染/比对工具 ** (§6.5 golden 回归的前置件) |
| **指向不存在的产物 ** | `docs/内存管理/RAM使用分析与优化方案.md:172/279/136` → `[计划] scripts/ram_baseline.tsv` 、`[计划] docs/内存管理/RAM预算台账.md` 、`[计划] App/util/app_mem_stats.*` | 方案写了、工具没建 → 读者按文档执行会失败 |
| 目标路径变更 | `图形绘制流程.md:135` (真实目标在 `docs/images/` )、`串口助手设计说明.md:200` | |
**建议 ** :新增 `scripts/check_doc_links.py` (校验仓库内相对链接与代码引用路径),挂 pre-commit;对"计划中的文件"统一用 `[计划]` 前缀标记而非直接链接,避免门禁长期红灯。
#### D-4( P2)孤儿文档
`内存管理/内存使用分析规划.md` 无任何入链(项目自有文档中唯一孤儿)。**建议**:并入 `RAM使用分析与优化方案.md` 的"规划"章节,或在 `docs/README.md` 索引中显式列出。
> ✅ **2026-09-21 已处理**:该文档与《STM32F4-Base 内存使用分析报告》一并并入 `RAM使用分析与优化方案.md`(现为内存管理主文档),两个文件已删除;口径与方法论见其 §8.4,快照数据见 `docs/内存管理/data/`。
#### D-5(P2)缺一个文档入口/索引
`docs/` 根下平铺 12 篇 md + 8 个子目录,无 `docs/README.md` 索引;权威入口实际是 `AGENTS.md` (面向 agent)与 `docs/架构设计/项目架构总览.md` 。**新成员的阅读顺序不可见**。
**建议 ** :新建 `docs/README.md` ,按"上手 → 架构 → 模块 → 测试 → 规范 → 台账 → 外部资料"给出有序清单(约 60 行即可),并从 README.md 链接过去。
> ✅ **2026-09-25 已处理**:已建 `docs/README.md`(目录结构树 + 推荐阅读顺序 + 按角色导航 + 9 大章节全量清单 + 新增文档约定 + 已知问题),并从根 `README.md` 链接过去。同轮把顶层 16 篇散落 md 归入 5 个新目录(`架构设计/`/`子系统设计/`/`网络/`/`审查记录/`/`环境与部署/`),全仓库 90 处引用同步。
#### D-6(P2)命名与目录约定不统一
中文目录名含空格(`docs/UI设计/` )在脚本/链接/CI 中易出错(本工程已出现多处需引号包裹的路径);同一目录混用 `UI_界面制作流程.md` (下划线+中文)与 `内容页架构.md` (纯中文)与 `RemoDispBus协议与接收设计说明.md` (中英连写)。
**建议 ** :确立 `docs/文档规范.md` :目录名一律**无空格**( `UI设计` 或 `ui` )、文件名"主题词+类型"、`snake_case` 用于英文段;新增文档必须出现在 `docs/README.md` 。存量文档按 D-3 修链接时**顺带**重命名,一次做完。
> ✅ **2026-09-25 已处理**:
> - **目录去空格**: `docs/UI 设计/` → `docs/UI设计/`,全仓库 67 处引用同步(含 markdown 链接的 URL 编码形态 `UI%20设计`);现全库**已无含空格目录**。
> - **`docs/文档规范.md` 已创建**:正式确立目录命名(无空格)、文件名风格(主题词+类型后缀 + 明确禁止字符)、编码换行、链接形态、门禁三档、结构要素、写作约定与常见错误速查。
> - **文件名风格统一**:按 §1.2 约定复核 `docs/` 全部文件(见规范 §1.2);`内容页架构.md` 等纯中文名保留(符合"主题词+类型"约定,未强制加下划线),仅纠正违反"无空格/无敏感字符"者。
#### D-7(P2)文档与代码之间没有一致性门禁
D-2/D-3 的共同根因:**没有任何机械检查**保证"文档说的 = 代码做的"。
**建议 ** :建立三档一致性检查(详见 §7 交付物):
| 检查 | 覆盖 | 手段 |
|------|------|------|
| 存在性 | 文档里的路径/文件/脚本必须存在 | `check_doc_links.py` |
| 数值 | 任务栈/优先级/端口/引脚表与代码一致 | `gen_task_table.py` + 端口常量表提取 |
| 体积 | 文档中引用的"内存/Flash 数字"与最新 map 一致 | 复用 `scripts/ram_report.py` 输出 |
> ✅ **2026-09-25 / 09-26 已处理(三档全部落地)**:
> - **存在性**: `scripts/check_doc_links.py` 已落地并接入 pre-commit `doc-links` 钩子。校验 md 互链(相对文档解析)与反引号内仓库根相对路径;豁免 `[计划]` 前缀、`[示例]` 标记、就近状态词、占位符、行内代码 span、外部 URL。全库 **67 文档 0 失效**。开发过程中实测抓出 **7 处迁移漏改的图片相对链接**( `images/` 未改 `../images/`)与 1 处真断链(`tools/ui_mock/render_mockup.py`,已标「已删除」)——门禁价值实证。
> - **数值**: `scripts/gen_task_table.py` 以 `Src/freertos.c` 为唯一事实源生成/校验 README/AGENTS 任务表,pre-commit `task-table` 钩子拦截。
> - **体积**( 2026-09-26):`scripts/check_mem_numbers.py` 以 `.map` 为唯一事实源,校验 `docs/内存管理/` 的受管快照区(`MEM_SNAPSHOT` 锚点)+ 扫描正文里的**陈旧区域数字**, pre-commit `mem-numbers` 钩子拦截。**上线即抓出真实失真**:文档写 `RW_IRAM1 82,048 B / 62.6%`、`ROM 249,432 / 23.8%`,实际 `.map` 已是 `87,040 / 66.4%`、`262,660 / 25.0%`(共修正 6 处)。
> - 文档体系规范(含门禁三档、受管区锚点、陈旧数字扫描的误报取舍)正式确立为 `docs/文档规范.md`。
> **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`)**;重写属破坏性操作(所有克隆需重取),须单独排期并先备份裸仓。
### 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** 项)。建议把这类计数也纳入生成/校验,否则报告与规范各说一套。
---
## 4. 功能模块设计评审
@@ -299,13 +255,8 @@ D-2/D-3 的共同根因:**没有任何机械检查**保证"文档说的 = 代
| 编号 | 问题 | 证据 | 影响/建议 |
|------|------|------|-----------|
| **N-03a ** | 无 IWDG(全库无 `MX_IWDG_Init` / `HAL_IWDG_Refresh` ) | grep 零命中 | 死机不可恢复。建议 1 s 超时 + 各任务在自身循环置心跳位图,defaultTask 校验"全部任务在 3 s 内动过"才喂狗 |
| **N-03b ** | `Inc/FreeRTOSConfi g.h` 的 `configASSERT(x)` = `taskDISABLE_INTERRUPTS(); for(;;);` | `:154` | 断言时**连串口都不输出**。建议改为: `DBG_ERROR` + 记录 `pcTaskGetName(xTaskGetCurrentTaskHandle())` 后再死循环(或直接触发 IWDG 复位) |
| **N-03c ** | 多处初始化失败即挂死(`Error_Handler()` at `main.c:221` ;net/ui 初始化失败路径) | | 建议统一降级策略:**外设失败 → 记自检日志 + 该子系统禁用 + 其余照常**(LED/屏上给出降级标识) |
| **N-07 ** | `configCHECK_FOR_STACK_OVERFLOW` 与 `configUSE_MALLOC_FAILED_HOOK` **均未开启 ** | `Inc/FreeRTOSConfig.h` 无此二宏 | 栈溢出/分配失败静默破坏内存。开销仅每任务几十字节 + 每上下文切换若干指令,**强烈建议立刻开启**(这是 §5 一切内存工作的安全网) |
| N-08 | 无 `HardFault_Handler` 自研覆盖(用 startup 默认死循环) | `Src/*.c` 无 | 建议加:把 `SP/PSP/LR/PC/xPSR` + 栈帧 `R0-R3,R12,LR` 打到串口(或写进 CCM 保留区,复位后可读),现场故障可定位 |
| P2-13 | testTask 无条件创建(`Src/freertos.c:171` ) | | 产品镜像白耗 4 KB;改 `#if defined(TEST_SUITE_*)` 包裹 |
| P2-12 | `App/app/app_cfg.h` 三个 `APP_*_ENABLE` 零引用 | | 开关要么生效要么删 |
| **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_cf g.h` 三个 `APP_*_ENABLE` 零引用 | | 开关要么生效要么删 → * * 🟡 部分处理**:已如实标注"预留占位、不门控任何代码"(v1.1),未接线也未删 |
### 4.2 网络子系统(CH395F + BSD 抽象层)
@@ -318,13 +269,11 @@ D-2/D-3 的共同根因:**没有任何机械检查**保证"文档说的 = 代
| 编号 | 问题 | 证据 | 建议 |
|------|------|------|------|
| **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` 式的只读模式) |
| N-09 | `cmd_pasv` 每次新建监听 `lftpd_inet_listen(0)` (随机端口) | `lftpd.c:373` | 在**单连接 socket** 上与 UI `:7003` 、对时 `:8888` 争用同一物理 socket;建议限定监听资源池 + 显式"FTP 会话独占"策略,并在文档写明"一次只允许一个 TCP 服务活动" |
| N-10 | uiTask 以 **10 ms ** 周期向 netMsgQueue 投递 accept 轮询 → ≈100 msg/s,与 FTP 共享深度 8 的队列 | `ui_ net_sink.c` 轮询逻辑 | 队列满时 `osErrorResource` → UI 丢帧/重投。建议:把"服务就绪"改为事件驱动(netTask 在 LISTEN 状态变化时置位),UI 侧改为等待事件;或给队列扩容并在 UI 侧背压。**→ 已销项(2026-10-10) **:未走"事件驱动",改用**门控**—— `net_can_accept() `/ `net_can_read()` 纯 RAM 读控制块,无活时**一次 RPC 都不发**(100 msg/s → 0);实测 `uis` 50 ms → **1 µs ** 。方案见 `../UI设计/RemoDispBus协议与接收设计说明.md` §3;判读口径与实测基线见 `../子系统设计/看门狗设计说明.md` §11.4 |
| P2-11 | `net_task.c` 对 `osEventFlagsGet` + Clear 非原子 | 台账 | 用 `osEventFlagsAcquire` /单次 Wait 消 TOCTOU |
| P2-8 | `net_recv` 阻塞超时 30 s | 台账 | 缩短并让上层可取消 |
| — | PHY 强制 100M 全双工,忽略自协商 | AGENTS/architecture | 与某些交换机会联不通;至少加"链路 up 失败 3 次后回退自协商" |
| P3-x | `NET_MAX_SOCKETS 8` ( `net_config.h:24-25` )是**软件表**,与硬件单连接语义不符 | | 文档已说明,但 API 层看不到限制 → 建议在 `net_socket.c` 断言"同时 LISTEN 数 ≤ N" |
| **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);⚠ 且**未登记进 `待处理内容.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) |
### 4.3 存储子系统(GD5F2GQ5UE + dhara + FatFs)
@@ -337,47 +286,38 @@ D-2/D-3 的共同根因:**没有任何机械检查**保证"文档说的 = 代
| 编号 | 问题 | 证据 | 影响/建议 |
|------|------|------|-----------|
| **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 失败 |
| **N-12 ** | `dhara_nand_is_free()` 只检查页首 64 B | `nand_ftl.c:137-154` | dhara 依赖该判定做映射恢复;页首 64 B 全 FF 但后半页有数据时会误判为空闲 → 数据丢失风险(低概率但后果重)。建议整页扫描或至少首/中/尾三处采样 |
| **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; }` (返回不支持是安全语义) |
| N-14 | 写回缓存 `s_cache_buf/s_cache_dirty` 无掉电保护策略:`CTRL_SYNC` 已实现(`:343-353` )但**没有周期性或掉电前的强制 flush** | `nand_ftl.c:291-353` | 掉电窗口 = 最后一次未跨页的写。建议:空闲 N ms 由 defaultTask flush + 掉电检测(若硬件支持)或至少"日志落盘后立即 fsync" |
| **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) |
| — | `disk_ioctl GET_BLOCK_SIZE = 1` | `nand_ftl.c` | FatFs 的擦除块提示失效(FAT32 对齐优化拿不到信息);建议返回 64 页(=1 物理块) |
| 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 物理块)。**❌ 复核仍在** |
### 4.4 配置引擎(`App/cfg/cfg_file.c`, 1,190 行)
**设计良好 ** :描述符 + 段注册表、外科式(surgical)保存、模板生成、首启旧档迁移、 2 s 轮询(`cfg_file_poll` )响应 `SYS_EVT_CFG_SAVE` ,属主单任务化。这是全项目设计最完整的一个模块。
**设计良好 ** :描述符 + 段注册表、外科式(surgical)保存、模板生成、2 s 轮询(`cfg_file_poll` )响应 `SYS_EVT_CFG_SAVE` ,属主单任务化。这是全项目设计最完整的一个模块。(原"首启旧档迁移"已于 2026-10-10 删除——该一次性路径把整条配置写链挂进启动期调用图,defaultTask 静态最坏链 +600 B。)
**问题 ** :
| 编号 | 问题 | 建议 |
|------|------|------|
| **N-01( P0) ** | **4 处 `malloc` 必失败 ** : `:485 malloc(CFG_FILE_MAX_LEN+1u)` = 4,097 B; `:522 malloc(CFG_OUT_MAX)` = 5,120 B( `CFG_OUT_MAX 5120` , `:44` );`:1038` /`:1050` 外科式保存两处。而 MicroLIB C 堆只有 **512 B ** ( `startup_stm32f407xx.s:43 Heap_Size EQU 0x200` ; map: `HEAP` ZI `0x200` @`0x2001e878` , `__heap_limit 0x2001ea78` ; `malloc.o refers to startup_stm32f407xx.o(HEAP)` )。map 交叉表确认 `cfg_file.o(i.s_load_file)/(i.s_write_template)/(i.cfg_file_save)` → `malloc.o(i.malloc)` | 见 §4.4.1 |
| P2-1 | 超长键行处理 | 台账 |
| — | 失败即静默回落工厂默认(`:955-988` :缺文件→模板→return -1;超长→模板→return -1;读失败→"factory defaults kept") | 语义本身合理(不阻塞组网),**但与 N-01 叠加后变成"永远出厂 IP" **, 用户改了 IP 重启就丢——这正是最难排查的现场故障形态 |
#### 4.4.1 N-01 修复方案(推荐 A, 2 小时)
| 方案 | 做法 | 评价 |
|------|------|------|
| **A(推荐) ** | `cfg_file.c` 的 4 处 `malloc/free` 改 `pvPortMalloc/vPortFree` ( FreeRTOS 堆 48 KB);并在 `CFG_FILE_MAX_LEN` /`CFG_OUT_MAX` 旁加 `_Static_assert` 约束峰值 | 与已完成的 `lftpd_io.c` 迁移一致(map 中 `lftpd_io.o refers to heap_4.o(i.pvPortMalloc)` 已证明该模式可用)。**注意**:48 KB 堆余量仅约 14– 15 KB, 9 KB 级分配需与 §5.3 的栈/池调整一并规划,否则会把"必失败"变成"偶发失败" |
| B(最稳) | 改**静态缓冲**:`static uint8_t s_cfg_in[CFG_FILE_MAX_LEN+1]` + `s_cfg_out[CFG_OUT_MAX]` 。属主已收敛到 defaultTask 单任务 → 无并发风险 | 但当前 SRAM1 只剩 4,488 B, **放不下 9 KB** → 必须与 §5.3 的 CCM 迁移或 LVGL 池收缩一起做 |
| C | 提高 `Heap_Size` 或 retarget `_sys_heap` 到 `pvPortMalloc` | 512 B 是 MicroLIB 的兜底(`printf` 内部也会用);单独放大不解决"两套堆"的根本问题 |
> **实施记录(2026-09-18,按方案 A 落地,两处必要调整)**:① 分配接口**不写在 `cfg_file.c` 里,而是加到 `cfg_file_store` 后端**( `cfg_store_alloc` / `cfg_store_free` / `cfg_store_heap_free`)——`cfg_file.c` 是双端共享源码,直接写 `pvPortMalloc` 会让 PC 模拟器断链;MCU 后端 = `pvPortMalloc/vPortFree`,模拟器后端 = `malloc/free`。② 编译期峰值约束改用 **typedef 负数组**( `CFG_HEAP_BUDGET = 10,240 B`)——ARMCC 5 只到 C99, **没有 `_Static_assert`**(方案 A 原文此处需修正)。③ 另补 4 条失败路径的 `syslog_post(SYSLOG_SELFTEST, …)` 与 `load: OK …, heap free NB` 实测行。台账:`docs/待处理内容.md` **P0-1**;设计文档:`docs/子系统设计/配置文件设计说明.md` §6.2/§8 + v1.11。
> **诚实标注**:本结论由静态分析得出(映射表 + 启动文件 + 调用图三重证据),**尚未上机确认**。`.dsh/skills/serial-boot-test/scripts/verify_log.py`( PowerShell 版 `test/verify_log.ps1:34-35` 已删除,2026-09-20 起为纯 Python)的 Forbidden 标记含 `FAIL`,而失败路径会打印 `read: malloc 4097B FAIL` / `template: malloc 5120B FAIL`,因此**一次 `@test` 即可判定**(若近期 `@test` 曾 PASS,需先确认当时是否真的进入了文件读取分支,例如 `/config.ini` 不存在或大小为 0)。**( 2026-09-18 已按上述实施;修复后的真机 `@test` 正向确认——`load: OK` 出现且 `FAIL` 零命中——已于 2026-10-03 执行 PASS, 2026-10-04 独立复跑再次 PASS: `[CFG] load: OK (523B, heap free 8840B)`、Forbidden `FAIL`/`ERROR`/`HardFault`/`[ERR]` 零命中、`selftest: no fault record`、ST-LINK 全片回读 `No difference found`。)**
| — | 失败即静默回落工厂默认(`:955-988` :缺文件→模板→return -1;超长→模板→return -1;读失败→"factory defaults kept") | 语义本身合理(不阻塞组网),但 **用户改了 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` 跨复位 → 现场故障从"不可知"变成"可回读"。
**问题 ** :
| 编号 | 问题 | 证据 | 建议 |
|------|------|------|------|
| **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` 雏形,可升级为页缓存) |
| **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 读并发 | 台账 | 轮转只在写者、读者持版本号 |
@@ -385,13 +325,15 @@ D-2/D-3 的共同根因:**没有任何机械检查**保证"文档说的 = 代
见 §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 改动从此有逐像素/逐字节基线可依赖。
### 4.7 外设驱动
| 模块 | 评价 | 问题 |
|------|------|------|
| `sd2506` (RTC) | I2C 超时齐备,降级策略明确(RTC 故障→默认时基 + 自检上报),**良好** | P2-5(负温度解析)、P2-6( WRTC 解锁) |
| `tpafe5160` (并行 ADC) | BUSY 超时、EXTI 读取路径齐备 | **P1-1:采样结果无人消费(数据路径死) ** ;并行总线无 DMA/ 批量采样框架,16 位端口读依赖 `gpio.c` 初值 |
| `rs485` | 结构简单 | DE 时序用 `HAL_Delay(1)` **阻塞 1 ms ** (在调用任务里);`crc.c` /`ringbuf.c` 未注册 → 无帧校验、无接收环 |
| `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 |
**驱动共性缺口 ** :没有统一的"错误码 + 超时 + 重试 + 自检上报"约定,各模块自定返回码(`GD5F_OK` / `0` / `bool` 混用)。**建议(S-5)**:定义 `bsp_err_t` 统一错误码 + `bsp_retry()` 辅助 + 所有失败必须走 `syslog(SYS_SELFTEST,...)` ,作为驱动编写规范写入 `docs/代码规范/` 。
@@ -400,131 +342,140 @@ D-2/D-3 的共同根因:**没有任何机械检查**保证"文档说的 = 代
**设计良好 ** : `test/board/test_config.h` 的 `TEST_SUITE_*` 互斥(`#error` 保护)、CH395F 硬件层/NET 层 Phase 互斥、`@test` 一键闭环(烧录→wait-idle→标记校验)、模拟器 `--cfgtest/--logtest` 。
> ⚠ 仍缺:**覆盖率概念**(原建议未办)、**注册检查**( `check_registration.py`)、**主机侧对 `lftpd`/`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` );`待处理内容.md` 的这条待办应升级为正式任务 |
| P2-14 | `build.bat` 用子串匹配判定 PASS → 假通过 | 改为解析 `"0 Error(s), 0 Warning(s)"` 精确串 + 退出码双条件 |
| P2-15 | `download.bat` 可能读旧日志 | 烧录前删除/打时间戳 |
| **N-17 ** | **无栈/堆水位断言 ** :测试通过不代表栈余量安全 | 在每个测试套件收尾打印 `uxTaskGetStackHighWaterMark(NULL)` + `xPortGetFreeHeapSize()` ,把"通过"变成"通过且有余量"(配合 §5.2 的自检任务) |
| N-18 | 无 CI(仅本地 pre-commit);无覆盖率概念 | 至少加一条 GitHub Actions/本地脚本:`cppcheck --all` + `build.bat` (若可 headless) + `check_registration.py` + `check_doc_links.py` |
| P1-10 | CH395F 硬件测试套件 RAM 溢出(`L6406E` ):HW 套 +8.7 KB、NET 层固件 +28 KB( `s_rx_buf` 32 KB) | 测试缓冲改为**分 Phase 复用同一块静态缓冲**(union 或分阶段 `memset` );`待处理内容.md` 的这条待办应升级为正式任务。**🟡 2026-10-11 复核:已升级为登记簿唯一 P 类收尾项**(RAM 侧因堆迁 CCM 大幅缓解,剩"CH395F 套件构建实测链接确认 + `@test` 闭环"两步) |
---
## 5. 内存优化(量化)
### 5.1 实测基线(map, 2026-09-16)
### 5.1 实测基线(map)
| 指标 | 数值 | 上限 | 占用 |
|------|------|------|------|
| `Code + RO-data` | 245,384 B( 239.6 KB) | 1 MB Flash | **23.4 % ** |
| `ROM` (含 RW 初始化表) | 246,016 B | 1 MB | 23.5 % |
| `RW-data` | 632 B | — | — |
| `ZI-data` | 125,952 B | — | — |
| **SRAM1( 0x20000000, 128 KB) ** | **126,584 B ** | ** 131,072 B** | **96.6%(余 4,488 B) ** |
| **CCM( 0x10000000, 64 KB) ** | **0 B ** | 65,536 B | **0% ** |
| 指标 | 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 ** | — | — |
`.sct` 只声明了 `ER_IROM1` + `RW _IRA M1 0x20000000 0x00020000 { .ANY (+RW +ZI) }` ;文件头注释说明"未使用 SRAM3(克隆片可能没有)"——但**CCM 是 F407 片上必定存在的 64 KB**,与克隆风险无关(GD32F407 亦有 CCM) 。
> **2026-10-11 二次实测(LVGL 死代码裁剪后,F-1/F-2/F-3 已落地)**: `Code=194,276 / RO=15,872 / RW=876 / ZI=139,892` → `ER _IRO M1` **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 的口径以此为准 。
**RW+ZI 主要对象(实测 obj ZI/RW) **
**复核结论: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 状态)。
| 对象 / 符号 | 字节 | 归属 | DMA 可达性 |
|-------------|------|------|-----------|
| `ucHeap` ( `heap_4.c` ,含 `heap_4.o` 合计 49,184) | 49,184 | FreeRTOS 堆 | 任意 |
| `work_mem_int` ( `lv_mem_core_builtin.o` , = `LV_MEM_SIZE 40*1024` ) | 40,960 | LVGL 池 | **CPU-only(可进 CCM) ** |
| `ui_port.o` : `g_ui_fbuf[3200]` + `g_ui_draw_buf[3200]` | 6,412 | UI 帧/绘制缓冲 | 可进 CCM |
| `ui_net_sink.o` : `g_tx[4+4+3200+1]` + `g_lcdmem_data[4+3200]` + `g_rx[64]` | 6,762 | UI 网络发送 | 可进 CCM( CH395F 侧 memcpy 到自己缓冲) |
| `nand_ftl.o` (3×2,048 页缓冲 + 控制块) | 6,216 | 存储 | **必须 SRAM1 ** |
| `ch395f.o` ( `s_spi2_dma_tx/rx[2052]` ) | 4,105 | 网络 | **必须 SRAM1 ** |
| `gd5f2gq5ue.o` ( `s_bbt_page_buf[2048]` + BBT 表) | 2,596 | 存储 | **必须 SRAM1 ** |
| `cmsis_os2.o` | 1,724 | RTOS 胶水 | 任意 |
| `startup_...o` : `STACK 0x400` + `HEAP 0x200` | 1,536 | MSP + MicroLIB 堆 | 任意 |
| `tasks.o` (含 `pxReadyTasksLists[56]` = 1,120) | 1,280 | RTOS | 任意 |
| `freertos.o` | 1,104 | 任务薄壳 | — |
| `default_fatfs` ( FATFS,含 `win[512]` ,定义于 `Src/freertos.c` ,计入 `freertos.o` 的 1,104 B) | ≈560 | FatFs 卷对象 | **必须 SRAM1 ** |
| `lftpd_io.o` ( `s_ftp_file` FIL) | 556 | FatFs 文件句柄 | **必须 SRAM1 ** |
| `cfg_file.o` | 724 | 配置引擎 | 任意 |
| `lv_init.o` ( `lv_global` ) | 496 | LVGL | 任意 |
| `net_socket.o` | 304 | 网络 | 任意 |
> **基线口径说明**:上表取 **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` 总量。
**两个静态池(49,184 + 40,960 = 90,144 B)就占了整个 128 KB 的 71% ** ——这是问题的核心。
**RW+ZI 主要对象(2026-10-11 复核,`scripts/ram_report.py` 排行;括号内为 2026-09-16 基线) **
**Flash 侧(Code+RO, 214 个含码对象) **
| 对象 / 符号 | 字节 | 所在区域 | 归属 | 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 | 任意 |
| `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 ** |
| `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 未办 ** ) |
| 子系统 | Code+RO | 占镜像 |
|--------|---------|--------|
| LVGL(含核心/绘制/控件/字体/缓存/图像) | ≈150,600 | **61.5% ** |
| 手写 UI 页面(`ui_*.o` , 5,875 行) | 17,520 | 7.2% |
| 网络(NET + NETCFG + lftpd) | 18,245 | 7.4% |
| HAL/CMSIS | 11,866 | 4.8% |
| 应用基础(App/ + Src/) | 10,468 | 4.3% |
| FreeRTOS + CMSIS-RTOS | 8,011 | 3.3% |
| FatFs + dhara + NAND | 8,222 | 3.4% |
**两个静态池(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/内存管理/` 。
### 5.2 结构性诊断(四条 )
**Flash 侧(Code+RO) ** :基线为 214 个含码对象;**2026-10-11 复核为 182 个含码对象**(按 `.map` 组件表对象名归类, `App/` + `Src/` + `Drivers/BSP/` 合并为"应用/驱动自研",算法层单列 )
| 子系统 | 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% |
**复核要点 ** :LVGL 的**绝对量几乎没动**( 150,600 → 152,185),占比从 61.5% 降到 55.4% 纯粹是因为**自研侧涨了 ≈30 KB**(看门狗 + Fault 采集 + ADC 全链 + 相量层 + 日志/观测)。→ * * §5.4 的裁剪结论依旧成立且更划算**:省下的 54.6 KB 现在相当于镜像的 **19.9% ** 。
### 5.2 结构性诊断(四条 + 未闭环部分)
1. **64 KB CCM 空转 ** ,而 96.6% 的负载压在 128 KB SRAM1 上。
2. **任务栈全部动态取自 FreeRTOS 堆 ** : `osThreadAttr_t` 只设 `stack_size` 不设 `stack_mem` ( `Src/freertos.c:64-105` )→ 6 条栈 **32,768 B ** + idle/timer 栈 + TCB + IPC 全部从 48 KB 池里扣,**留给动态分配的实际余量仅约 14–15 KB**。这也是为什么 N-01 若改走 `pvPortMalloc` 仍需先扩容/腾挪 。
3. **零观测 ** : `xPortGetFreeHeapSize` / `xPortGetMinimumEverFreeHeapSize` 在 map 中只以"linker removed"出现(从未被调用), `uxTaskGetStackHighWaterMark` 全库零调用, `lv_mem_monitor` 仅在 `ui_menu_init` 打过一次。**无法回答"还能加多少" **。
4. **MicroLIB 512 B C 堆是隐形地雷 ** :任何新代码写一个 `malloc` 都会中招(N-01 已是实例)。
→ **未闭环 ** : 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 条未落地。
### 5.3 RAM 优化路线(收益/风险/优先级)
| 编号 | 措施 | 收益 | 风险 | 优先级 |
|------|------|------|------|--------|
| **R-1 ** | **开启 `configCHECK_FOR_STACK_OVERFLOW = 2` + `configUSE_MALLOC_FAILED_HOOK = 1` ** (钩子里打印任务名 + 触发 IWDG 复位) | 0(安全网) | 极低;每任务 +几十 B、切换略增开销 | **P0,立即 ** |
| **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,本季度 ** |
| **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 |
| **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 |
| **R-5 ** | `configMAX_PRIORITIES` 56 → 32(实际最高用到 `osPriorityHigh` 级别) | * * − 480 B** | 低 | P2 |
| **R-6 ** | 任务栈改**静态分配**( `stack_mem` 指向各自 `.bss` 数组),从堆里释放 32,768 B | 堆可用 +32 KB( **总量不变**,但把"池内碎片"变成"可核算静态区") | 低-中:静态后溢出检测更难发现;建议配合 R-1 先测水位再定尺寸 | P2 |
| **R-7 ** | testTask 门控(P2-13) | * * − 4 KB** | 低 | P1 |
| **R-8 ** | 观测常开:新增 `memStatsTask` (最低优先级,1 Hz,512 w)打印 `xPortGetFreeHeapSize` / `xPortGetMinimumEverFreeHeapSize` / 各任务高水位 / `lv_mem_monitor` ,并支持串口命令按需打印 | 0(可核算) | 低 | **P1 ** (是 R-2/R-6 的前置) |
| R-9 | `LV_MEM_SIZE` 40 KB → 依 R-8 实测峰值下调(既有方案预估 16 KB) | −16 ~ − 24 KB | 低(有 R-8 兜底) | P2 |
| 编号 | 措施 | 收益 | 风险 | 优先级 | **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 ~ − 2 4 KB | 低(有 R-8 兜底) | P2 | ❌ 未办( `lv_conf.h:44` 仍 `(40 * 1024)` )。⚠ 复核: `simulator/tests/check_content_pages.py` 已带**池水位门禁** → 实测峰值数据已具备,本条只差改数值 + 真机复跑(⚠ 两端 `lv_conf.h` 要同步改) |
**组合结果预估(注意措施之间存在重叠,不可简单相加) **
| 组合 | SRAM1 变化 | 说明 |
|------|-----------|------|
| 基线 | 126,584 B / **96.6% ** | 余 4,488 B |
| + R-2( CCM 启用) | **72,462 B / 55.3 %** | − 54,122 B; CCM 用 54,122 B(仍余 11 KB) |
| + R-7( testTask 门控 ) | **68,366 B / 52.2 % ** | − 4,096 B |
| + R-3(存储页缓冲合并) | * * ≈66.3 KB / 50.6%** | − 2,048 ~ − 4,096 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 % ** | − 5 4,122 B; CCM 用 54,122 B(仍余 11 KB)。⚠ 复核:该预估**已不可直接执行**——堆先占了 CCM 49,184 B, CCM 只剩 16,304 B,装不下 LVGL 池 + UI 缓冲这 54 K B |
| + 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% 使用率**, `待处理内容.md` 里记的 ` L6406E` (P1-10 测试镜像溢出)随之消解,并为 ADC 数据路径( P1-1)、 RS485 协议栈( `crc.c` / `ringbuf.c` 注册) 预留出 预算空间。
最终形态(原预估) : **SRAM1 约 50%、CCM 约 20% 使用率**, `L6406E` (P1-10 测试镜像溢出)随之消解,并为 ADC 数据路径与 RS485 协议栈预留预算空间。
> 与 `docs/内存管理/RAM使用分析与优化方案.md` 的关系:本表**沿用其阶段划分**(阶段 0 测量 / 阶段 1.1 池收缩 / 阶段 1.2 CCM / 阶段 2 堆收缩),新增的是 ① R-4 帧副本去重(原文档未识别 4 份副本)②R-3 存储侧缓冲合并 ③N-11 的 **DMA 可达性硬边界**(原文档未提出,属实施风险关键点)④R-1/R-8 的观测前置条件 。
> **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` 已删除,**需从零重做**而非"注册" 。
### 5.4 Flash 优化(≈50 KB 可证明死代码)
> 与 `docs/内存管理/RAM使用分析与优化方案.md` 的关系:本表**沿用其阶段划分**(阶段 0 测量 / 阶段 1.1 池收缩 / 阶段 1.2 CCM / 阶段 2 堆收缩),新增的是 ①R-4 帧副本去重(原文档未识别 4 份副本)②R-3 存储侧缓冲合并 ③N-11 的 **DMA 可达性硬边界**(原文档未提出,属实施风险关键点)④观测前置条件(已落地,见 §5.5)。
**根因:`Lib/lv_conf.h` 与 LVGL 9.3 漂移。 ** 该文件手写裁剪过(126 行),但:
- 设了 `LV_USE_THEME_DEFAULT 0` 与**已不存在的** `LV_USE_THEME_BASIC 0` ( `:70-71` );
- **未设 `LV_USE_THEME_SIMPLE` ** → `lv_conf_internal.h:2606` 默认 1 → `lv_display.c:121-125` 在建 display 时执行 `disp->theme = lv_theme_simple_init(disp)` ;
- `lv_theme_simple.o(i.theme_apply)` 引用 `lv_label_class / lv_button_class / lv_buttonmatrix_class / lv_bar_class / lv_textarea_class` → 这些控件全被拉进镜像;
- 而 map 与源码扫描双重证明:**工程 UI 只创建 `lv_obj` **( 8 处 `lv_obj_create` ),**零控件构造函数**、**零 flex/grid 用法**( `ui_*.c` 无 `set_flex/set_grid/LV_LAYOUT` );密码页也是 `lv_draw_label/lv_draw_rect` 自绘。
### 5.4 Flash 优化(未闭环:F-4 / F-5 / F-6)
| 编号 | 措施 | 可省 Flash | 依据 |
|------|------|-----------|------|
| **F-1 ** | `LV_DRAW_SW_SUPPORT_I1 1` ,其余 `LV_DRAW_SW_SUPPORT_L8/RGB565/ARGB8888/X8888/ALPHA` 等显式设 0 | **35,310 B ** | `lv_draw_sw_blend_to_*` 合计 40,582 B,其中 `to_i1` 仅 5,272 B; `LV_COLOR_DEPTH 1` 下其余混合核不可达 |
| **F-2 ** | `LV_USE_THEME_SIMPLE 0` + 关闭未用控件 `LV_USE_LABEL/BUTTON/BUTTONMATRIX/BAR/TEXTAREA/SLIDER/ARC/TABLE` | **14,255 B ** | 控件 13,389 B( label 4,087 + buttonmatrix 3,707 + textarea 3,680 + bar 1,843 + button 72)全部由 `lv_theme_simple.o` (866 B) 锚定 |
| **F-3 ** | `LV_USE_FLEX 0` 、`LV_USE_GRID 0` | **5,368 B ** | 源码扫描零使用;注释"菜单依赖 flex/label/button"已过期(自绘后不再依赖) |
| **F-4 ** | 关闭 `LV_USE_TRANSITION/LV_USE_SNAPSHOT/LV_USE_OBJ_PROPERTY/LV_USE_FLOAT` (若为 1)、动画与 `lv_obj_style` 中未用部分 | 需实测 | `lv_draw_sw_transform` 4,340 / `mask` 3,862 / `box_shadow` 3,246 / `draw_sw_img` 2,960 / `bin_decoder` 1,972 + `image_decoder` 952 / `triangle` 930 / `grad` 420 —— 工程无图片、无阴影、无变换 |
| **F-5 ** | 从 uvprojx 中**移除未使用的 LVGL 控件源文件**(201 个 → 实际需要的子集) | 编译时长 −40% 左右 | 与 F-2 配合;`lv_conf` 关开关后文件仍编译,删文件才省编译时间 |
| **F-6 ** | 双精度软浮点:`dadd/dmul/ddiv/depilogue/dfixul` 合计 **1,018 B ** ( `mf_w.l` ),来自 `printf` 的 `%f` /double 提升 | − 1 KB + 运行期性能 | 日志与 UI 全用定点;把 `%f` 改整数刻度,或编译期加 `-D__MICROLIB` 下 `printf` 浮点裁剪选项 |
> **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 条。
**合计可回收 ≈ 50– 60 KB Flash(镜像的 20%+) ** ,且**不减少任何现有功能**。这是"低风险高收益"的一类改动,建议与 R-1 同批做(先 Flash 后 RAM,避免同时改两处难定位)。
| 编号 | 措施 | 可省 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` 全灭),删文件的收益更纯粹、风险更低 |
| **F-6 ** | 双精度软浮点:`dadd` /`dmul` /`ddiv` /`depilogue` /`dfixul` ( `mf_w.l` ),来自 `printf` 的 `%f` /double 提升 | − 1 KB + 运行期性能 | 日志与 UI 全用定点;把 `%f` 改整数刻度 | ❌ 未办(新镜像仍链入 `dadd.o` ) |
### 5.5 内存治理长效机制
**剩余可回收 ≈ 10– 11 KB Flash ** ( F-4 可评估部分 + F-6);F-5 省的是编译时长不是 Flash。原"≈50 KB 死代码"的主体(64,576 B)已在 2026-10-11 消除。
### 5.5 内存治理长效机制(+ 2026-10-11 复核)
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 残余 ** )。
2. **建立 `[计划] scripts/ram_baseline.tsv` ** ( `docs/内存管理/RAM使用分析与优化方案.md:172` 已承诺但未创建):每次构建记录 Code/RO/RW/ZI/Top 对象,形成趋势。
→ * * ❌ 未建**;🟡 等效物已存在:`docs/内存管理/data/` 下 5 份 `ram_report` /`stack_report` 快照留档(人工触发,非 tsv 趋势表)。
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 后半段无法排期。
4. **DMA 可达性清单 ** (新增,源于 N-11):文档化"哪些缓冲必须 SRAM1",并在 CCM 迁移时对该清单加 `_Static_assert` /地址断言(`assert((addr & 0xFFF00000) == 0x20000000)` )。
5. **禁止新增 C 库 `malloc` ** (源于 N-01):写进 `docs/代码规范/` ,并在 `check_coding_standard.py` 加正则检查( `App/` 、 `Drivers/` 内出现 `malloc(` 即告警 )。
→ * * 🟡 半落地**:独立清单文档未建,但**铁律已进 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(` 正则 ** → 目前只靠人评审,属机械检查缺口。
---
@@ -534,14 +485,9 @@ D-2/D-3 的共同根因:**没有任何机械检查**保证"文档说的 = 代
| 层 | 现状 | 评价 |
|----|------|------|
| 声明源 | `ui_menu_tree.txt` (缩进即层级,行尾 `#P` 标密码) | ✅ 正确的方向 |
| 生成器 | `tools/menu_gen/gen_menu.py` → `ui_menu_tab.c/.h` ( 42 节点,`{parent,pwd,name}` ) | ✅ 已闭环 |
| 视图 | `ui_menu_view.c` 自绘行 + 按需建毁框 | ✅ 1bpp 下的正确取舍 |
| 控制器 | `ui_menu.c:181-232 menu_key_handle` | ✅ |
| 内容页 | `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 映射** |
| 字体 | `tools/font_gen/make_font.py` + `ui_cn_chars.txt` (自动生成,文件头已声明"请勿手改") | ✅ 闭环 |
| 设计令牌 | **无 ** (每页自带 `CP_SCREEN_W/H 160` 、`CP_TITLE_Y 2` 、`CP_PAGE_LINE_Y 143` 、`CP_ROW_BUF_LEN 32` 等布局常量) | ❌ 魔法数字分散在各页 |
| golden 回归 | 文档指向 `tools/ui_mock/` (**不存在**) | ❌ 无视觉基线 |
| 内容页 | `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/` (语义断链) |
### 6.2 新增一个页面要走多少步(当前)
@@ -549,6 +495,8 @@ D-2/D-3 的共同根因:**没有任何机械检查**保证"文档说的 = 代
**5 步是纯机械劳动且 2 处易错(步骤 5 的魔法下标、步骤 6/7 漏注册)。 **
> **2026-10-11 复核:8 步流程一步未减。** 步骤 5 的手写 `case` 仍在(`ui_menu.c:118-128`);步骤 6/7 的漏注册仍无机械校验(`check_registration.py` 未建)——但已新增一条**间接暴露路径**:若新文件落在新目录而没把目录加进 `uvprojx` 的 `<IncludePath>`,消费者 TU 会报 `cannot open source input file`( AGENTS.md 2026-10-10 记录),这类报错落在**别的文件**上、易误判,仍不等于"存在但未注册"的检查。
### 6.3 标准化目标:四层 + 一个闭环
``` mermaid
@@ -608,6 +556,7 @@ extern const lv_style_const_prop_t UI_STYLE_FLAT[];
| `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 页重算即可,结论不变。
**L3 页面实例 ** — 目标形态(以"遥测死区"为例,全部新代码约 30 行):
@@ -629,10 +578,11 @@ 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 天) ** :
1. `[计划] scripts/check_registration.py` (同 §2.3 S-3)保证新页面两端都注册;
2. **golden 回归落地 ** (补上文档承诺的 `tools/ui_mock/` ):利用现成的 **PC 模拟器**做渲染基线——`simulator` 增加 `--shot <page> <out.bin>` (已有 `--standby shot.bin` 先例)导出 1bpp 帧,Python 脚本转 PNG 并与 `docs/UI设计/baseline/*.png` 逐像素比对,差异 > 0 即 FAIL。这样 `UI_界面制作流程.md:413` 与 `UI_自动化测试流程.md:104` 的引用由"空头承诺"变成可执行门禁;
3. 字符集自动校验:构建前扫描所有 `ui_*.c` 中的汉字,与 `ui_cn_chars.txt` 求差集,缺失即 FAIL(把 `font-regen` 从"记得跑"变成"漏跑就红") 。
**闭环门禁(1 天) ** ——未闭环 2.5 条 :
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 钩子做差集检查)。
### 6.4 UI 侧的内存纪律(必须写进规范)
@@ -640,19 +590,19 @@ const ui_page_desc_t ui_page_deadzone = UI_FORM_PAGE("遥测死区", s_items_dea
|------|------|
| 页面**不得**新增静态大缓冲;文本缓冲统一用 `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 已占 Flash 61% |
| 优先自绘(`lv_draw_*` + `lv_text_get_width` ),新控件需评审(会破坏 §5.4 F-2 **已落地**的裁剪,且须两端同步) | LVGL 现占镜像 **41.6% ** ( 87,706 / 210,623 B;裁剪前 55%) |
| 帧缓冲任何改动必须遵守 R-4(副本数 ≤ 2) | 当前 4 副本 12.8 KB |
| `LVGL 池` 用量必须经 `lv_mem_monitor` 实测后才允许扩大 | 现 40 KB,预估可降到 16 KB |
### 6.5 实施顺序(UI 专项,总计约 8 人日)
### 6.5 实施顺序(UI 专项,总计约 8 人日; 2026-10-11 复核)
| 步骤 | 内容 | 交付判据 |
|------|------|---------|
| U1 | `ui_tokens.h` 令牌化(纯替换,不改行为) | `@build` PASS + 模拟器截图与替换前逐像素一致 |
| U2 | `ui_w_records` (合并 runlog/selftest) | 两页行为不变,代码 −1 个文件 |
| U3 | `ui_w_form` + `ui_page_desc_t` 原型化 + 生成器产注册表 | 手工 `menu_content_page_for()` 删除;新增一页只需"改树 + 加实例文件" |
| U4 | golden 回归(模拟器 `--shot` + PNG 比对)+ 字符集检查 + 注册检查入 pre-commit | 三个新 hook 全绿 |
| U5 | 用原型批量落剩余 35 页(每页 ≤30 行) | 菜单树全绿、无 `CONTENT_PAGE_COUNT` 哨兵命中 |
| 步骤 | 内容 | 交付判据 | **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) |
---
@@ -660,91 +610,63 @@ const ui_page_desc_t ui_page_deadzone = UI_FORM_PAGE("遥测死区", s_items_dea
### P0( 1 周内,必须)
| 编号 | 事项 | 验收 |
|------|------|------|
| N-01 | `cfg_file.c` 4 处 `malloc` → `pvPortMalloc` (方案 A)或静态缓冲(方案 B,需先做 R-2) | 真机串口出现 `config loaded` 而非 `read: malloc 4097B FAIL` ; `@test` PASS |
| N-02 | FTP 鉴权(或生产构建默认关闭 FTP 写) | 错误口令登录被 `530` 拒绝; `@test` 加 FTP 标记校验 |
| N-03 | IWDG + 心跳位图;`configASSERT` 改为"先打印后复位" | 人为死循环 → 1 s 内复位并留下复位原因日志 |
| N-07 | 开 `configCHECK_FOR_STACK_OVERFLOW=2` + malloc-failed 钩子 | 连续 30 min 运行无钩子触发(同时得到真实栈水位) |
| D-2 | 修 README 任务表 | 与 `Src/freertos.c` 一致 |
| N-06 | 5 个未注册 `.c` :注册或显式删除 + 白名单 | `check_registration.py` 零告警 |
| 编号 | 事项 | 验收 | **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 个月内)
| 编号 | 事项 | 预期 |
|------|------|------|
| N-04 / R-2 | **CCM 启用 ** ( LVGL 池 + UI 帧缓冲,`APP_CCM_ENABLE` 门控)+ N-11 DMA 边界文档与断言 | SRAM1 96.6% → ~55% |
| R-4 | UI 帧副本 4 → 2 | − 6.4 KB |
| R-8 | `memStatsTask` / 串口命令 `mem` 常开 | 每次评审有数可依 |
| F -1/F-2/F-3 | `lv_conf.h` 与 LVGL 9.3 对齐 + 主题/控件/flex 裁剪 | Flash − 55 KB |
| N -12/N-13 | `dhara_nand_is_free` 全页校验; `dhara_nand_copy` 处理 `DHARA_NO_PAGE` | 掉电注入测试通过 |
| N-16 | 日志常驻 `FIL` + 批量 flush;UI 记录读缓存升级 | 单条日志 NAND 操作次数下降 ≥50% |
| N-10 | ~~UI accept 改事件驱动(脱离 100 msg/s 轮询)~~ **已落地(2026-10-10,改走"门控"而非事件驱动) ** | ✅ 无客户端时队列占用 **100 msg/s → 0 ** ;实测 `uis` 50 ms → **1 µs ** ( `../UI设计/RemoDispBus协议与接收设计说明.md` §3;判读见 `../子系统设计/看门狗设计说明.md` §11.4 ) |
| D-1 | 芯片手册图片降采样 / 拆 submodule | clone 40 MB → ~8 MB |
| D-3 | `check_doc_links.py` 入门禁 + 修 18 条断链 | 零断链 |
| U1– U4 | UI 令牌化 + 原型库 + 生成器 + golden | 新增一页 ≤30 行 |
| P1-1/P1-7/P1-8/P1-10 | 沿用台账(ADC 数据路径 / MAC 写序 / 重建失败恢复 / 测试 RAM 溢出) | 台账闭环 |
| 编号 | 事项 | 预期 | **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` 、D-4/D-5/D-6/D-7、 S-1/S-2/S-5、P2-1..P2-16 余项、N-17/N-18(测试内建水位 + CI 最小集) 。
`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 已实现)。
### 交付物清单(可直接排期)
| 交付物 | 类型 | 来源条目 |
|--------|------|---------|
| `[计划] scripts/check_registration.py` | 新脚本 + pre-commit hook | S-3 / N-06 |
| `scripts/check_doc_links.py` | 新脚本 + pre-commit hook | D-3 |
| `[计划] scripts/gen_task_table.py` | 文档一致性生成器 | D-2 / D-7 |
| `[计划] scripts/ram_baseline.tsv` + `[计划] docs/内存管理/RAM预算台账.md` | 文档承诺落地 | §5.5 |
| `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 |
| `tools/ui_mock/{render_mockup.py,i1_to_png.py}` + `docs/UI设计/baseline/` | golden 回归 | U4 |
| `[计划] docs/内存管理/DMA可达性清单.md` | 规范(含"哪些缓冲禁止进 CCM") | N-11 |
| `docs/README.md` (文档索引) | 文档 | D-5 |
| 交付物 | 类型 | 来源条目 | **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 | ❌ **未建 ** ;🟡 等效物 = `docs/内存管理/data/` 5 份快照 + `MEM_SNAPSHOT` 受管区(无趋势、无配额) |
| `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"这一新条 |
---
## 附录 A 关键证据索引
| 结论 | 证据 |
|------|------|
| SRAM1 96.6%、余 4,488 B | map: `Total RW Size (RW Data + ZI Data) 126584 (123.62kB)` ; scatter `RW_IRAM1 0x20000000 0x00020000` |
| CCM 未使用 | `MDK-ARM/STM 32F 407-Demo/STM 32F 407-Demo.sct` 仅两个区域,无 ` 0x1 0000000 ` |
| 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` |
| cfg_file 走 C 库 malloc | map `cfg_file.o(i.s_load_file) ref ers to malloc.o(i.malloc)` 、 `(i.s_write_template)` 、 `(i.cfg_file_save)` ;源码 `App/cfg/cfg_file.c:19 <stdlib.h>` 、 `:485/:522/:1038/:1050` |
| lftpd 已迁 FreeRTOS 堆 | map `lftpd_io.o refers to heap_4.o(i.pvPortMalloc)` |
| 堆/栈零观测 | 全库 grep `xPortGetFreeHeapSize\|uxTaskGetStackHighWaterMark\|configCHECK_FOR_STACK\|configUSE_MALLOC_FAILED` 在 `App/Src/BSP/test` 零命中;map 中相关库函数标记为 linker-removed |
| 任务栈全部动态分配 | `Src/freertos.c:64-105` 的 `osThreadAttr_t` 无 `.stack_mem `; `freertos.o` ZI 仅 1,104 B |
| 非 I1 混合核死代码 35,310 B | map 对象 `lv_draw_sw_blend_to_*` 合计 40,582 − `to_i1` 5,272 |
| 未用控件 13,389 B 由主题锚定 | `lv_theme_simple.o(i.theme_apply) refers to lv_{label,button,buttonmatrix,bar,textarea}_class` ;源码零控件构造函数(仅 8 处 `lv_obj_create` ) |
| NAND 直接 DMA 调用方缓冲 | `gd5f2gq5ue.c:200` 、 `:258` ( `GD5F_DMA_THRESHOLD = 32 `) |
| CH395F memcpy 自有缓冲 | `ch395f.c:1132-1190` |
| FTP 无鉴权 | `Drivers/BSP/NET/lftpd/lftpd.c:367-371` |
| 端口分配 | `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.c:155/:205 menu_content_page_for()` |
| 文档体量 | 工作树字节统计(见 §3.1 表) |
## 附录 B 断链清单(P1 修复项)
| 文件:行 | 失效链接 | 处置 |
|---------|---------|------|
| `docs/代码规范/cppcheck使用说明.md:3` | 相对路径层级错误 | 改 `../` 层级 |
| `docs/子系统设计/配置文件设计说明.md:159,160,494,568,569,570` | 指向已改名/搬迁文件 | 逐项改指 `net_cfg.c` /`cfg_file_store.h` 等 |
| `docs/测试说明/FTP_Test_Guide.md` 、`GD5F2GQ5UE_Test_Guide.md` 、`SD2506_Test_Guide.md` | 目录搬迁遗留(旧 `docs/` 根路径) | 改新相对路径 |
| `docs/测试说明/测试开发标准流程.md:358` | 同上 | 同上 |
| `docs/问题记录/CH395F_Trap_Records.md:434` | 同上 | 同上 |
| `docs/UI设计/UI_界面制作流程.md:413` | `tools/ui_mock/` (不存在) | 与 U4 一并实现,或标注 `[计划]` |
| `docs/UI设计/UI_自动化测试流程.md:104` | 同上 | 同上 |
| `docs/内存管理/RAM使用分析与优化方案.md:136,172,279` | `[计划] App/util/app_mem_stats.*` 、`[计划] scripts/ram_baseline.tsv` 、`[计划] docs/内存管理/RAM预算台账.md` (均不存在) | 随 §5.5 创建;未创建前标 `[计划]` |
| `docs/测试说明/串口助手设计说明.md:200` | 目标路径变更 | 改指现路径 |
| `docs/架构设计/图形绘制流程.md:135` | 目标实际在 `docs/images/` | 改路径 |
| 结论 | 证据 | **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_stm 32f 407xx.o` ; `__heap_limit 0x2001ea78` ; `MDK-ARM/startup_stm 32f 407xx.s:43 Heap_Size EQU 0x200` | ✅ 结论不变: `Heap_Size EQU 0x2 00` 未改,map 现 `Removing startup_stm32f407xx.o(HEAP), (512 bytes)` (未被引用 → 整段移除) |
| lftpd 已迁 FreeRTOS 堆 | map `lftpd_io.o refers to heap_4.o(i.pvPortMalloc)` | 不变 |
| 任务栈全部动态分配 | `Src/f reertos.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 ** |
## 附录 C 评审中确认"无需改动"的设计(避免后续误改)
@@ -760,4 +682,42 @@ UI 剩余页面批量落地(U5)、目录级层次迁移(S-1 的物理搬
---
**报告结束。 ** 建议把 §7 的 P0 六条直接追加进 `docs/待处理内容.md` (沿用其编号体系,本报告编号 `N-01…N-18` 作为交叉引用),并把 §5/§6 的落地项写进 `[计划] docs/内存管理/RAM预算台账.md` 与 `[计划] docs/文档规范.md` 。
**报告结束。 **
> **2026-10-11 复核收尾**:
> - ⚠ **台账漏登两条**:本报告 **N-02( FTP 无鉴权)** 与 **N-12/N-13( dhara 移植缺陷)** 既未修复也未登记在 `../待处理内容.md` → 建议补登。
> - ⚠ **编号撞车**:本报告曾用 N-18 指"无 CI"(该条已销项删除),而登记簿的 N-18 = "8 路电气量分属两片转换器(V0.2 改版项)"。引用时一律带文档名。
> - `[计划] docs/内存管理/RAM预算台账.md` 与 `scripts/ram_baseline.tsv` **仍未创建**(见 §5.5)。
> - 本报告按 `docs/README.md` 的定位是**体系层评审报告,不是待办池**——待办一律以 `../待处理内容.md` 为准;本报告只保留**未闭环问题的发现 + 证据**,修复即销项。
---
## 变更记录
| 日期 | 类型 | 说明 |
|------|------|------|
| 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 删两条已闭环证据行 |
### 已销项条目的记录去处
> 删除前已逐条核对:以下去处文档均含完整记录,删了不丢信息。
| 已销项条目 | 去处 |
|-----------|------|
| N-01(配置引擎 C 堆必失败) | `../子系统设计/配置文件设计说明.md` ( v1.13,§6.2/§8) |
| N-03a/N-03b/N-07/N-08( IWDG、`configASSERT` 、两个钩子、4 个 Fault 现场) | `../子系统设计/看门狗设计说明.md` (§4.4、§10.1/§10.2) + AGENTS.md「平台约束」 |
| 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` |
| 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` |
| 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 及理由) |