Files
STM32F4-Base/docs/问题记录/项目审查与优化建议报告.md
T
wandering1 c936441c83 门禁扩容:新增 lv-conf-sync、simulator/ 纳入全部钩子;含 L3 定值/元件框架落地
本轮把"自研代码落在门禁之外"的口子收掉,同时落地 LVGL 特性裁剪与
L3 保护定值/元件框架。

门禁(.pre-commit-config.yaml,26 个钩子)
- 新增 scripts/check_lv_conf_sync.py + 钩子 lv-conf-sync:比对两份
  lv_conf.h 的**生效值**(未定义则取 lv_conf_internal.h 兜底默认),
  抓 MISMATCH / LVGL 不认的宏名 / 同文件重复定义
- 顶层 exclude 删掉 simulator/:其下 21 个 tracked 文件全是本项目自研
  代码,生成物(build/、SDL2/、tests/ref/、shot*)已在 .gitignore;
  doc-links 的 exclude 同步删除
- lv-conf-sync 的 files: 覆盖两端配置 → 只改 simulator/lv_conf.h 也触发
  (pre-commit 4.x 已移除 always: true,实测只告警并跳过,无兜底开关)
- 新增 prot-set-unit-test / prot-elm-unit-test 两个主机侧单测钩子
- S11(2.3.5) 判定细化:禁的是"用路径找文件",尖括号系统头带子目录
  (如 <sys/stat.h>)不再误报;全仓库复核仅 2 处项目代码判定变化

模拟器代码整改(纳入钩子后暴露,全部修代码而非豁免)
- cppcheck 4 处:sim_syslog.c 判空分支内解引用 p_epoch(真实 bug)、
  2 处 struct tm 未初始化、1 处指针可加 const
- coding-standard 61 处:44 处函数左大括号并到右括号同行、8 处补函数
  注释、3 处补文件头注释块、2 处 __ 前缀保护宏改 XXX_H、2 处 sprintf
  改 snprintf(写入字节数与原来完全一致)

LVGL 特性裁剪(F-1/F-2/F-3)
- 两端 lv_conf.h 对齐同一特性子集:删 26 个 LVGL 9.3 不存在的选项名、
  3 处重复定义,10 项靠默认值隐式生效的改为显式固化
- 实测对镜像体积中性;价值在防漂移,且模拟器 golden 回归跑的正是固件
  实际编译的配置

L3 软件侧
- App/algo/prot_set.* Q10 定值模型(范围校验 / 整表 all-or-nothing /
  校验和封存-自检-闭锁)+ prot_elm.* 通用"滞回+定时限"元件框架 7 实例
- test/host/prot_set、test/host/prot_elm 主机侧单测;uvprojx 挂载两处

文档同步
- pre-commit使用说明:§2.8 S11 口径、§2.15 lv-conf-sync 详解、新增 §5.5
  复盘(教训:exclude 只写真正不该进钩子的路径)
- AGENTS.md、UI_设计架构 §1.4 第 10 条、RAM 分析、审查报告刷新与销项

验证
- @build BUILD_OK 0/0(Code=194276 RO=15872 RW=876 ZI=139892)
- 模拟器重建:菜单 8 状态像素逐字节一致(池 5368 B)、11 内容页黄金参考
  全过(往返 5448 B)、remo sink 0/25600 位差异、--logtest/--cfgtest PASS
- 全仓库 cppcheck / coding-standard / no-mojibake --all-files 全过
- check_doc_links --all 129/0;check_mem_numbers --check 内存数字一致
- 端到端:往 simulator/lv_conf.h 注入一行漂移 → 钩子 FAIL(exit 1)
2026-10-11 08:48:49 +08:00

724 lines
81 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# STM32F4-Base 项目审查与优化建议报告
| 项目 | 内容 |
|------|------|
| 审查日期 | 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-10-11 起)**:本报告采用与登记簿相同的**「修复即销项」**——问题修复后其分析正文与表格行**从本报告删除**,完整记录沉淀到对应模块文档(去处清单见文末**变更记录**)。因此本报告**只呈现未闭环问题**,不再随时间膨胀。
---
## 0. 执行摘要
### 0.1 六维评分(2026-09-18 评级 + 2026-10-11 复核)
| 维度 | 评分 | 一句话结论 | **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 5,2026-10-11)
> 已修复/已消解的条目按「修复即销项」从本报告删除,完整记录见对应模块文档(去处清单见文末**变更记录**)。
| # | 编号 | 问题 | 影响 | 现状 |
|---|------|------|------|------|
| 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-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 栈) |
⚠ 另有两条**不在上表但需排期**:`Error_Handler` 仍 `__disable_irq(); while(1){}`(N-03c,§4.1)、`.git` 已膨胀到 **149 MB**(D-1,§3.1)。
### 0.3 三条主线建议
1. **把内存从"卡死"变"富裕"**:启用 CCM 承载 LVGL 池与 UI 帧缓冲(≈54 KB),同时补齐栈/堆水位自检,作为后续功能的预算来源(§5.3)。
2. **把失败路径从"死循环"变"可恢复"**:IWDG + 任务心跳位图 + 统一的"降级但活着"策略(§4.1)。
3. **把 UI 从"每页手写"变"声明 + 原型库"**:令牌层 → 原型库 → 生成器补全注册 → golden 回归,使新增一页的成本从 8 处手工改动降到 1 处声明(§6)。
---
## 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 数据源
| 数据 | 来源 | 可信度 |
|------|------|--------|
| 内存/Flash 占用 | `.map` 的 component 表(214 个含码对象,列序 `Code incData RO RW ZI Debug Name`) | 高(构建产物,权威) |
| 总量 | map 尾部 `Total RO / Total RW / Total ROM` + `Program Size` | 高 |
| 依赖方向 | 逐文件 `#include` 扫描 + map 的 `refers to` 交叉表 | 高 |
| 构建注册 | `MDK-ARM/STM32F407-Demo.uvprojx` + `simulator/CMakeLists.txt` + git 文件清单三方对齐 | 高 |
| 文档链接 | 全量 md 链接解析(剔除设备路径 `PB12`/通配/占位后 706 → 94 未解析 → 人工分诊) | 中高(人工分诊) |
### 1.2 复现命令(本报告所有数字可重跑)
```powershell
# 1) 构建体量
Select-String -Path 'MDK-ARM\STM32F407-Demo\STM32F407-Demo.map' -Pattern 'Total RO Size|Total RW Size|Total ROM Size'
# 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,否则中文名会被控制台代码页破坏)
```
> ⚠️ **踩坑记录**(供后续审查复用):
> ① map 组件表是 **6 列数值**(含 `incData` 与 `Debug`),按 5 列解析会得到 0 个对象;
> ② `git ls-files` 输出经 PowerShell 管道会按控制台代码页破坏中文路径,导致"文件不存在"的假结论,必须用 `-z` + 字节流解码;
> ③ 终端渲染的中文乱码不等于文件损坏,判定损坏须统计 `EF BF BD` 字节数(本报告统计结果:**0**,全库无 U+FFFD)。
### 1.3 口径定义
| 术语 | 定义 |
|------|------|
| 自研代码 | `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"已不成立 |
| "死代码" | 该对象在镜像中有码,但其功能被工程配置(如 `LV_COLOR_DEPTH 1`)或调用点扫描(零调用者)证明不可达 |
---
## 2. 项目结构合理性
### 2.1 规模与分布(实测)
| 分类 | 文件 | 行数 | 说明 |
|------|------|------|------|
| `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 |
**评价**:自研体量与第三方体量比约 **1:14**(原 1:20,自研涨了一倍半),仍属典型的"薄自研 + 厚中间件"。这决定了**结构审查的重点不在自研代码内部,而在"自研如何约束第三方"**——§5.4 曾量化这层约束(`lv_conf.h`)失守的代价:**64,576 B 死代码**(2026-10-11 已修)。
### 2.2 分层与依赖方向
实测自研层间的**全部反向依赖**(BSP → App)——2026-10-11 复核为 **3 处**(原 4 处,`net_cfg_legacy.c` 已随旧档迁移路径整体删除,见 §2.4):
| 位置 | 反向 include | 判定 |
|------|-------------|------|
| `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:19` | `sys_clock.h`(App/util) | 中间件→应用;已在文件内注释声明为"④中间件层"有意为之 |
**评价:良好**。在 38k 行规模上把反向依赖控制在 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 处——**2026-10-11 复核: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` 已导出,局部重复) |
**建议(S-2)**:CubeMX 句柄类(`hspi1`/`hi2c1`)统一由 `main.h` 导出并 include,删除局部 extern;`g_spi1_dma_done` 应由 GD5F 模块自己定义并在头文件 `extern` 声明(当前跨模块裸 extern 是真正的耦合隐患);`netMsgQueueHandle` 改为 net_socket 内部持有 + 提供投递 API。
### 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 宏语义重复)仍两边不编** |
| **仅 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) |
**评价**: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 个文件会立刻变成"要么注册、要么显式豁免"。
### 2.4 目录组织与根目录卫生
| 观察 | 证据 | 判定 |
|------|------|------|
| 根目录散置工作文件 | `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 可裁剪"。**2026-10-11 复核:🟡 已订正表述但未接线**——文件头 v1.1(2026-10-04)已如实标注"预留占位、不门控任何代码",`APP_CCM_ENABLE`/`APP_UI_NET_SINK` 已删除,三个 `APP_*_ENABLE` 仍在且仍零引用 |
### 2.5 结构结论
**总体判断:结构合理,无需重构**。要做的是四类**低成本纠偏**:S-1 层次定性、S-2 消裸 extern、S-3 注册反向校验、S-4 让 `app_cfg.h` 开关真正生效或删除(并把 `APP_CCM_ENABLE`/`APP_UI_NET_SINK` 纳入同一处)。
---
## 3. 文档分布合理性
### 3.1 体量画像(实测,工作树;2026-10-11 复核,括号内为 2026-09-18 基线)
| 对象 | 文件数 | 体积 | 占仓库 |
|------|--------|------|--------|
| 仓库受控文件总计 | 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 涨到 **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 做得好的(应保留并显式制度化)
1. **文档密度与可执行性**:每篇都带"步骤 + 判据"(如 UI 制作流程有 P0→P4 Gate);测试说明按模块拆分并给出预期串口标记,可直接接 `@test`。
2. **陷阱台账**:`docs/问题记录/*_Trap_Records.md` + `docs/待处理内容.md`(P1-1..P3-13,带 ✅ 状态与日期)——这是本工程最有价值的资产,多数嵌入式项目没有。
3. **图与脚本同提交**:`tools/arch_diagram/`、`tools/refgraph/` 生成器 + 产物入库,图可再生而非手绘。
4. **UI 设计资产链完整**:原始参考图 → 说明 md → 代码,可追溯。
### 3.3 问题清单
#### D-1(P1)外部资料与工程文档同仓同树
**影响**:clone 慢、备份慢、code review 时 diff 噪音大;`docs/芯片手册/` 的 564 张图属于**只读输入资料**,与代码演进无关。
**建议**(三选一,按代价递增):
1. **最省**:把 564 张 JPG 降采样到长边 1,200 px(40.5 MB → 约 8 MB),一次性提交并在 `[计划] docs/文档规范.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`)**;重写属破坏性操作(所有克隆需重取),须单独排期并先备份裸仓。
### 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. 功能模块设计评审
### 4.1 启动与任务框架
**设计良好**:
- `app_main_init` 顺序有因(墙钟优先,使后续日志带时间戳);
- **Event Flags 就绪屏障**(`NET_READY/STORAGE_READY/FTP_READY/TIMESYNC_READY/CFG_SAVE/NET_RECONFIG`)取代魔法延时,是本项目最漂亮的设计之一;
- 配置引擎属主收敛到 defaultTask 单任务(P1-3 已修),消除了跨任务竞态。
**问题**:
| 编号 | 问题 | 证据 | 影响/建议 |
|------|------|------|-----------|
| **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),未接线也未删 |
### 4.2 网络子系统(CH395F + BSD 抽象层)
**设计良好**:
- 所有 `net_*` 经 `netMsgQueue`(深 8,`Src/freertos.c:154`)串行到 netTask 执行 → 单线程语义清晰;
- 全部 CH395F 中断集中在 `net_poll()` 内 `GET_GLOB_INT_STATUS` 排空(AGENTS 铁律,已落实);
- 网络代次号机制使"改 IP 免重启"能重建各监听。
**问题**:
| 编号 | 问题 | 证据 | 建议 |
|------|------|------|------|
| **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)
**设计良好**:
- 分层正确:`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 擦写。
**问题**:
| 编号 | 问题 | 证据 | 影响/建议 |
|------|------|------|-----------|
| **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 物理块)。**❌ 复核仍在** |
### 4.4 配置引擎(`App/cfg/cfg_file.c`,1,190 行)
**设计良好**:描述符 + 段注册表、外科式(surgical)保存、模板生成、2 s 轮询(`cfg_file_poll`)响应 `SYS_EVT_CFG_SAVE`,属主单任务化。这是全项目设计最完整的一个模块。(原"首启旧档迁移"已于 2026-10-10 删除——该一次性路径把整条配置写链挂进启动期调用图,defaultTask 静态最坏链 +600 B。)
**问题**:
| 编号 | 问题 | 建议 |
|------|------|------|
| P2-1 | 超长键行处理 | 台账 |
| — | 失败即静默回落工厂默认(`: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` 雏形,可升级为页缓存)。**❌ 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 读并发 | 台账 | 轮转只在写者、读者持版本号 |
### 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 改动从此有逐像素/逐字节基线可依赖。
### 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 |
**驱动共性缺口**:没有统一的"错误码 + 超时 + 重试 + 自检上报"约定,各模块自定返回码(`GD5F_OK` / `0` / `bool` 混用)。**建议(S-5)**:定义 `bsp_err_t` 统一错误码 + `bsp_retry()` 辅助 + 所有失败必须走 `syslog(SYS_SELFTEST,...)`,作为驱动编写规范写入 `docs/代码规范/`。
### 4.8 测试体系
**设计良好**:`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` 的这条待办应升级为正式任务。**🟡 2026-10-11 复核:已升级为登记簿唯一 P 类收尾项**(RAM 侧因堆迁 CCM 大幅缓解,剩"CH395F 套件构建实测链接确认 + `@test` 闭环"两步) |
---
## 5. 内存优化(量化)
### 5.1 实测基线(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** | — | — |
> **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 的口径以此为准。
**复核结论: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 基线)**
| 对象 / 符号 | 字节 | 所在区域 | 归属 | 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 未办**) |
**两个静态池(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/内存管理/`。
**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 上。
→ **未闭环**: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 优化路线(收益/风险/优先级)
| 编号 | 措施 | 收益 | 风险 | 优先级 | **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` 要同步改) |
**组合结果预估(注意措施之间存在重叠,不可简单相加)**
| 组合 | 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)。
### 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 条。
| 编号 | 措施 | 可省 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`) |
**剩余可回收 ≈ 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)`)。
→ **🟡 半落地**:独立清单文档未建,但**铁律已进 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(` 正则** → 目前只靠人评审,属机械检查缺口。
---
## 6. UI 界面标准化实现方案
### 6.1 现状(框架已有,但未"闭环")
| 层 | 现状 | 评价 |
|----|------|------|
| 内容页 | `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 新增一个页面要走多少步(当前)
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` 的 `<IncludePath>`,消费者 TU 会报 `cannot open source input file`(AGENTS.md 2026-10-10 记录),这类报错落在**别的文件**上、易误判,仍不等于"存在但未注册"的检查。
### 6.3 标准化目标:四层 + 一个闭环
```mermaid
graph LR
A[L0 设计令牌<br/>ui_tokens.h] --> B[L1 组件库<br/>ui_w_* 自绘控件]
B --> C[L2 页面原型<br/>list / detail / form / records]
C --> D[L3 页面实例<br/>ui_page_xxx.c 仅填数据]
D --> E[生成器 gen_page_registry.py]
E --> F[ui_page_registry.c<br/>菜单节点→页 映射]
F --> G[门禁: check_registration + golden]
```
**L0 设计令牌(1 天,零行为变化)** — 新建 `Drivers/BSP/UI/ui_tokens.h`:
```c
/* 屏幕与骨架 */
#define UI_SCREEN_W 160
#define UI_SCREEN_H 160
#define UI_TITLE_Y 2
#define UI_TITLE_H 11
#define UI_PAGE_LINE_Y 143 /* 页脚分隔线 */
#define UI_ROW_PITCH 13 /* 行距(12px 字体 + 1) */
#define UI_ROW_H 12
#define UI_LIST_TOP 15
#define UI_SCROLLBAR_W 2
#define UI_PAD_X 3
#define UI_BUF_STRLEN 32 /* = CP_ROW_BUF_LEN,统一一处 */
/* 语义色(1bpp:只有黑白,但保留语义名以便换深度时平移) */
#define UI_COL_FG LV_COLOR_BLACK
#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) */
extern const lv_style_const_prop_t UI_STYLE_FLAT[];
```
要点:**保留现有"只用 `lv_style_const_prop_t` 常量样式"的纪律**(这是已被验证的必要约束:动态样式每个 88 B 泄漏);令牌化后所有页面自动获得统一观感,改一行即全局生效。
**L1 组件库(3 天)** — 把 `ui_menu_view.c` / `ui_content_page.c` 中重复的自绘逻辑抽成无状态组件(纯 `lv_layer_t` 绘制,不创建 LVGL 对象,保持零控件、零池压力):
| 组件 | 职责 | 从何处抽取 |
|------|------|-----------|
| `ui_w_title` | 标题栏 + 分隔线 + 页码 | `ui_content_page.c` 通用绘制链 |
| `ui_w_list` | 行列表 + 选中反白 + 滚动条 | `ui_menu_view.c` 行绘制 |
| `ui_w_kv` | "名称: 值" 两列(装置信息/实时数据) | `ui_page_device_info.c`、`ui_page_ac.c` |
| `ui_w_records` | 定长记录分页浏览(含 `s_cache_valid/s_last_rec` 缓存范式) | `ui_page_runlog.c`、`ui_page_selftest.c`(两文件结构几乎相同 → 直接合并) |
| `ui_w_form` | 键值 + 光标 + 可编辑(含十进制/IP 输入约束) | `ui_page_net_cfg.c` |
| `ui_w_msgbox` | 确认/错误提示("密码输入错误"等) | `ui_page_password.c` |
**L2 页面原型(4 类,覆盖全部剩余页面)**:
| 原型 | 适用菜单 | 数据契约 |
|------|---------|---------|
| `detail`(KV 只读) | 装置信息、交流量、直流量、遥信量 | `kv_provider_t{ count(); get(i, name, val); }` |
| `form`(可编辑) | 整定/定值/控制字/软压板、遥测死区/系数、遥信类型/防抖/双点、装置维护、通讯参数、网口/SNTP | 直接绑定 `cfg_file` 描述符(**复用配置引擎的段注册表,无需新数据结构**) |
| `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 页重算即可,结论不变。
**L3 页面实例** — 目标形态(以"遥测死区"为例,全部新代码约 30 行):
```c
static const ui_form_item_t s_items_deadzone[] = {
/* cfg 段/键, 显示名, 类型, 最小, 最大, 权限 */
{ "deadzone", "ia", "A 相死区", UI_T_UINT, 0, 1000, UI_PERM_MODIFY },
{ "deadzone", "ib", "B 相死区", UI_T_UINT, 0, 1000, UI_PERM_MODIFY },
{ "deadzone", "ic", "C 相死区", UI_T_UINT, 0, 1000, UI_PERM_MODIFY },
};
const ui_page_desc_t ui_page_deadzone = UI_FORM_PAGE("遥测死区", s_items_deadzone);
```
**生成器补全(1.5 天,关键收益)** — 扩展 `ui_menu_tree.txt` 支持页绑定注解,让 `gen_menu.py` 一并产出注册表,**消灭步骤 4/5 的手工 switch**:
```
遥测死区 #P @page=deadzone @arch=form
```
产物:`ui_page_registry.c` 含 `node_index → (page_id, archetype, desc*)` 常量表 + `ui_page_registry_for(node)`;`ui_menu.c` 只需一次调用。生成器同时校验:`@page` 指向的描述符是否存在、是否有节点声明了页但无实例、是否有实例未被任何节点引用(编译期断言或生成期报错)。
**闭环门禁(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 侧的内存纪律(必须写进规范)
| 规则 | 依据 |
|------|------|
| 页面**不得**新增静态大缓冲;文本缓冲统一用 `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%) |
| 帧缓冲任何改动必须遵守 R-4(副本数 ≤ 2) | 当前 4 副本 12.8 KB |
| `LVGL 池`用量必须经 `lv_mem_monitor` 实测后才允许扩大 | 现 40 KB,预估可降到 16 KB |
### 6.5 实施顺序(UI 专项,总计约 8 人日;2026-10-11 复核)
| 步骤 | 内容 | 交付判据 | **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) |
---
## 7. 统一改进路线图
### P0(1 周内,必须)
| 编号 | 事项 | 验收 | **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 已实现)。
### 交付物清单(可直接排期)
| 交付物 | 类型 | 来源条目 | **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 关键证据索引
| 结论 | 证据 | **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** |
## 附录 C 评审中确认"无需改动"的设计(避免后续误改)
| 项 | 结论 |
|----|------|
| CH395F SPI 分频 = 8 | **PCB 约束,禁止"优化"**(AGENTS 铁律,实测一致) |
| 就绪屏障用 Event Flags 而非魔法延时 | 正确,保留 |
| FatFs `FF_FS_REENTRANT=1` + 真实互斥 | 正确,保留(多数移植偷工) |
| 配置引擎属主 = defaultTask | 正确(P1-3 已收敛),任何新代码不得跨任务触碰 |
| UI 只用 `lv_style_const_prop_t` | 正确且必要,禁止"顺手改成动态样式" |
| `ffsystem.c` 引用 `sys_clock.h` | 有意为之(中间件需要真实时间),保留但在分层图中显式标注 |
| UI 自绘而非 LVGL 控件 | 正确(1 bpp + 小 RAM),并是 Flash 裁剪的前提 |
---
**报告结束。**
> **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 及理由) |