门禁扩容:新增 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)
This commit is contained in:
2026-10-11 08:48:49 +08:00
parent 13a4e61c27
commit c936441c83
36 changed files with 2220 additions and 565 deletions
+4 -4
View File
@@ -211,7 +211,7 @@ docs/
| 文档 | 内容 |
|---|---|
| [`问题记录/项目审查与优化建议报告.md`](问题记录/项目审查与优化建议报告.md) | 项目全景审查:文档/代码/架构/可靠性问题清单(D-1~D-7、N-xx、Px-xx)——**体系层评审报告,非待办池**(待办见 §9.3 `待处理内容.md`) |
| [`问题记录/项目审查与优化建议报告.md`](问题记录/项目审查与优化建议报告.md) | 项目全景审查:文档/代码/架构/可靠性问题清单(未闭环项:D-1、N-xx、Px-xx;**已修复项按「修复即销项」删除**,去处见其变更记录)——**体系层评审报告,非待办池**(待办见 §9.3 `待处理内容.md`) |
### 9.2 环境与部署
@@ -246,19 +246,19 @@ docs/
## 11. 已知文档问题(待清理)
> 来源:《项目审查与优化建议报告》D-3/D-7。
> 来源:《项目审查与优化建议报告》D-3/D-7(两条已销项,正文已移入 [`文档规范.md`](文档规范.md) 与 pre-commit 门禁)。
> **D-3 / D-6 / D-7 已处理完毕**(2026-09-25):全库链接存在性检查通过(66 文档 0 失效)、任务表与
> `Src/freertos.c` 一致、文档规范已建立。下表保留**仍需跟踪**的项。
| 问题 | 位置 | 说明 |
|---|---|---|
| 指向未实现工具 | `UI_界面制作流程.md`、`UI_自动化测试流程.md` → `tools/ui_mock/` | 文档承诺了未实现的 UI 渲染/比对工具(其引用已按 §3.3 标注「已删除」状态词) |
| 指向未实现工具 | `UI_界面制作流程.md`、`UI_自动化测试流程.md` → `tools/ui_mock/` | 该目录**始终不存在**;golden 回归实际落在 `simulator/tests/`(四脚本)。引用待改指,见报告 §6.1 |
| 产物缺失 | `内存管理/RAM使用分析与优化方案.md` → `scripts/ram_baseline.tsv` | 方案写了、工具未建(引用已加 `[计划]` 标记) |
| ~~目录名含空格~~ | ~~`UI 设计/`~~ | ✅ 已修复(2026-09-25,D-6):改名为 `UI设计/`,全仓库 67 处引用同步;命名约定见 [`文档规范.md`](文档规范.md) §1 |
| ~~缺一致性门禁~~ | — | ✅ 已落地(2026-09-25/26,D-7 三档齐):`scripts/check_doc_links.py`(存在性)+ `scripts/gen_task_table.py`(数值)+ `scripts/check_mem_numbers.py`(体积),接入 pre-commit `doc-links` / `task-table` / `mem-numbers` 钩子 |
| ~~`docs/文档规范.md` 未创建~~ | — | ✅ 已创建(2026-09-25):[`文档规范.md`](文档规范.md) |
> 完整清单与建议见 [`问题记录/项目审查与优化建议报告.md`](问题记录/项目审查与优化建议报告.md) §3.3。
> 未闭环的文档类问题见 [`问题记录/项目审查与优化建议报告.md`](问题记录/项目审查与优化建议报告.md) §3.1(体量与 D-1)与 §3.4(复核后仅剩三条:D-1、语义断链、元信息漂移)。
---
+8 -3
View File
@@ -25,7 +25,7 @@ cmake --build simulator/build --config Debug # 0) 构建模拟器(须 0 警
| 步 | 脚本/命令 | 门禁内容 | 预期输出 |
|---|---|---|---|
| 1 | `simulator/tests/check_menu_rows.py` | 菜单 8 状态:选中行白带 / 非选行黑底白字 / 框体 1px 边框 / 往返像素一致(S3==S1、S6==S1、S7==S2)+ S0 池基线 | `PASS: 8 状态全部断言通过(8 图,池基线 4784 B)` |
| 1 | `simulator/tests/check_menu_rows.py` | 菜单 8 状态:选中行白带 / 非选行黑底白字 / 框体 1px 边框 / 往返像素一致(S3==S1、S6==S1、S7==S2)+ S0 池基线 | `PASS: 8 状态全部断言通过(8 图,池基线 5368 B)` |
| 2 | `simulator/tests/check_content_pages.py` | ① 文本级断言(字形模板匹配,50 条,独立于 `ref/`)→ ② 11 页截图与 `ref/` 黄金参考**逐字节**比对 + 开页-关页往返池水位 | `PASS: 11 内容页 + 往返池水位全部通过`(文本断言先于像素比对,失败即中止) |
| 3 | `keil-build` skill(`keil_build.py`) | MCU 端编译(UI 源码双端共用,改一端必须过另一端) | 退出码 0 = BUILD_OK |
| 4 | `scripts/check_coding_standard.py` | 代码规范 V1.0 机械检查 | PASS |
@@ -44,8 +44,12 @@ cmake --build simulator/build --config Debug # 0) 构建模拟器(须 0 警
| 常量 | 所在脚本 | 当前值 | 含义 |
|---|---|---|---|
| `MENU_POOL_BASELINE` | check_menu_rows.py | **4784 B** | 菜单根框建完的 LVGL 池 used(x64 模拟器) |
| `POOL_AFTER_ROUNDTRIP` | check_content_pages.py | **4864 B** | 开页-关页往返后水位 = 基线 + 一次性残留 80 B |
| `MENU_POOL_BASELINE` | check_menu_rows.py | **5368 B** | 菜单根框建完的 LVGL 池 used(x64 模拟器) |
| `POOL_AFTER_ROUNDTRIP` | check_content_pages.py | **5448 B** | 开页-关页往返后水位 = 基线 + 一次性残留 80 B |
> 水位历史:4784 →(2026-09-14 待机模块常驻 +656)5440 / 5520 →(2026-10-11 裁剪 LVGL 控件类 −72)5368 / 5448。
> 后一次漂移已核实为**有意且更优**:像素逐字节未变(11 页黄金参考在裁剪**前**生成、裁剪后全部一致),
> 且一次性残留仍是 80 B(5448 − 5368)→ 泄漏特征未变,只是 `lv_theme_apply` 不再为已不存在的控件类登记样式条目。
**漂移处理**:菜单/页面结构或样式变更导致门禁失败时,先确认新值合理(残留分解见
`菜单页面设计.md` §9.5),再更新脚本常量并在本文变更记录登记。**禁止为过门禁盲目改常量**——
@@ -102,3 +106,4 @@ cmake --build simulator/build --config Debug # 0) 构建模拟器(须 0 警
| 2026-09-10 | 新增 `simulator/tests/regen_ref_png.py`(由 `ref/*.bin` 批量重生成目检 PNG,默认 `--scale 6 --invert`);`ref/*.png` 加入 `.gitignore`(本地调试产物不入库);§四 补"重生成目检 PNG"步骤(修复"刷了 `.bin` 参考但 `.png` 仍是旧渲染 → 看似乱码") |
| 2026-09-10 | `simulator/tests/ref/` **整目录不入库**(`.bin` 黄金参考 + `.png` 目检图均为本地生成物):`git rm --cached` 原有 3 个 `.bin`,`.gitignore` 改为忽略整目录;首次/换机后先 `--update` 生成参考;`--update` 先跑文本断言防误刷;§四 重写 |
| 2026-09-13 | 管线同步:`UI_界面制作流程.md` 移除 HTML 样机阶段(五阶段 → 四阶段 P0~P4),本文定位改为"P2/P3 门禁的可执行版本";§六 关系描述更新为 P0 规格 → P1 编码 → P2 本文回归 → P3 上机目检 → P4 归档;移除 `tools/ui_mock/` 引用 |
| 2026-10-11 | **裁剪 LVGL 死代码(F-1/F-2/F-3)时的门禁记录**:先用**未裁剪**配置 `--update` 生成 11 页黄金参考,再切到裁剪配置重编重跑 → **逐字节全部一致**(菜单 8 状态像素一致),证明裁剪零视觉影响。池水位门禁随之 5440/5520 → **5368/5448**(−72 B;一次性残留仍 80 B,故非泄漏变化,见 §三 水位历史)。顺带揪出并修掉 `Lib/lv_conf.h` 与 `simulator/lv_conf.h` 的**长期漂移**(模拟器早已 I1-only、MCU 端未裁)→ 规则见 `UI_设计架构.md` §1.4 第 10 条,量化见 `../内存管理/RAM使用分析与优化方案.md` §2.6.4 |
+10 -1
View File
@@ -23,7 +23,7 @@ MCU **无物理屏幕**:LVGL 做 160×160 **1bpp 单色离屏渲染**,帧缓
| `Drivers/BSP/UI/ui_net_sink.c/h` | **共用** | RemoDispBus 服务端(收键 + 回送帧缓冲),MCU/PC 双端同一份;平台后端 `net_socket` + `ui_net_sink_backend`(见 RemoDispBus 协议文档) |
| `Drivers/BSP/UI/ui_font_cn_12.c` | **共用** | 中文 1bpp 子集字体(`lv_font_conv` 生成物) |
| `App/task/ui_task.c/h` | MCU | UI 任务:等 NET_READY → 初始化 → 10ms 刷新循环 |
| `Lib/lv_conf.h` | MCU | LVGL 配置(1bpp / 40KB 内存池 / 默认字体) |
| `Lib/lv_conf.h` | MCU | LVGL 配置(1bpp / 40KB 内存池 / 默认字体);⚠ **必须与 `simulator/lv_conf.h` 同一特性子集**,见 §1.4 第 10 条 |
| `simulator/`(main.c / sim/ / lv_conf.h / CMakeLists.txt) | PC | SDL2 模拟器(替代 ui_port,共用全部 UI 源码);支持 `--screenshot` / `--keys <seq>` 无头截图验证 |
| `tools/font_gen/`(extract_cn.py / make_font.py)+ `ui_cn_chars.txt` | 工具 | 字符集提取 / 字体生成 |
| `tools/menu_gen/gen_menu.py` + `ui_menu_tree.txt` | 工具 | 菜单声明源 → 生成 ui_menu_tab.c/h |
@@ -64,6 +64,15 @@ MCU **无物理屏幕**:LVGL 做 160×160 **1bpp 单色离屏渲染**,帧缓
`lv_display_set_theme()` + 对**活动屏幕**再 `lv_theme_apply()`(实测 `bg_before=F5F5F5` → `bg_after=000000`)。
另外 I1 缓冲头 8 B 调色板 LVGL 不替调用方初始化(上电全 0 = 两项透明黑),须显式写 `[0]=黑 [1]=白`;
渲染器本身按亮度阈值置位(`lv_draw_sw_blend_to_i1.c`:亮 → 位=1),与客户端"位=1 即亮"的约定一致。
10. **两份 `lv_conf.h` 必须是同一特性子集(2026-10-11 定;F-1/F-2/F-3 落地时暴露的漂移)**:`Lib/lv_conf.h`(MCU)与 `simulator/lv_conf.h`(PC)是**两份独立文件**,此前已漂移——模拟器早已 I1-only,MCU 端却让 11 个 `LV_DRAW_SW_SUPPORT_*`、5 个未用控件类、`LV_USE_THEME_SIMPLE`、`LV_USE_FLEX/GRID` 全靠 `lv_conf_internal` 默认值隐式生效,把用不到的代码整份链进镜像 = **实测 64,576 B 死代码(旧镜像的 23.5%)**。现两端对齐为同一子集:
* 绘制核只留 `LV_DRAW_SW_SUPPORT_I1`(显示是 1bpp;字形 A8 位图是作为 **mask** 传入、混合按**目的**格式分派,故不需要 A8/L8/AL88);
* 控件全关(`label`/`button`/`buttonmatrix`/`bar`/`textarea`/`image`/`keyboard`)——全仓(`Drivers/BSP/UI`、`App`、`simulator`)**零控件构造函数**,只用 `lv_obj_create` + `lv_theme_mono_init`;
* `LV_USE_THEME_SIMPLE 0`:它是把上述控件类拉进镜像的**锚点**(`lv_display_create()` 会强链 `lv_theme_simple_init`);⚠ 9.3 **没有** `LV_USE_THEME_BASIC`,旧配置里那条是无效项,已删;
* `LV_USE_FLEX 0` / `LV_USE_GRID 0`:全仓无 `lv_obj_set_flex_*` / `lv_obj_set_grid_*` 调用。
⚠ 裁剪后 ARMCC 报 `#177-D`(上游 9.3.0 在 `lv_draw_sw_blend_to_i1.c` 漏了 `lv_color16_from_u16` 的定义处守卫)→ 本仓已按使用点补 `#if LV_DRAW_SW_SUPPORT_RGB565_SWAPPED`(原型 + 定义两处),升级 LVGL 时若上游已修可去掉。
⚠ 将来引入控件型页面(U2 原型库)时**两端同步**逐项打开并复跑 `ui-verify`。量化与验证记录见 `../内存管理/RAM使用分析与优化方案.md` §2.6.4。
✅ **已入门禁**(2026-10-11):`scripts/check_lv_conf_sync.py` = pre-commit 钩子 `lv-conf-sync`。比对的是**生效值**(文件未定义则取 `lv_conf_internal.h` 兜底默认),并抓"写了 LVGL 不认的宏名"与同文件重复定义。首次跑即揪出两端共 **26 个 9.3 不存在的选项**(LVGL 8 / 9.4 模板残留,与 `LV_USE_THEME_BASIC` 同类)、**3 处同文件重复定义**、**10 项 MCU 端靠默认值隐式生效的开关**(含默认 =1 的 `LV_USE_OBSERVER` / `LV_USE_DRAW_PXP` / `LV_USE_IMAGEBUTTON`)——已全部清理/显式固化。
✅ **门禁触发范围**(2026-10-11):`lv-conf-sync` 的 `files:` 同时匹配 `Lib/lv_conf.h`、**`simulator/lv_conf.h`** 与脚本自身 → **两端任一改动都会跑**。此前顶层 `exclude` 整目录挡掉 `simulator/`,模拟器侧改动拦不到;已删该条并把 `simulator/` 下自研代码整体纳入 pre-commit(cppcheck 当天即揪出 `sim_syslog.c` 一处真实空指针解引用),详见 `../代码规范/pre-commit使用说明.md` §5.5。
---
+34 -3
View File
@@ -21,6 +21,7 @@ git commit
├─ doc-links 文档链接存在性(D-3/D-7,.md)
├─ task-table FreeRTOS 任务表一致性(D-2/D-7)
├─ mem-numbers 内存/Flash 数字一致性(D-7 体积档)
├─ lv-conf-sync 两份 lv_conf.h 特性子集一致性(比对生效值)
├─ cubemx-survival CubeMX 重生成存活审计(易失定制清单 + 基线漂移)
├─ watchdog-unit-test 看门狗判定状态机主机侧单测(L0.5)
└─ test-suite-switch 测试套件开关=产品构建(防测试固件静默提交)
@@ -162,7 +163,7 @@ cppcheck 是开源 C/C++ 静态分析工具,**不运行代码**,通过语法
| S8 | 4.1.1 | 禁止 Tab 缩进(4 空格) |
| S9 | 5.3 | 函数定义必须有函数注释(含「函数功能」) |
| S10 | 3.3.1 | 函数名必须全小写(HAL_ 厂商 API 回调名豁免) |
| S11 | 2.3.5 | 禁止相对/绝对路径 include |
| S11 | 2.3.5 | 禁止相对/绝对路径 include(2026-10-11 细化判定形态:禁的是"用路径找文件"——引号 include 带 `/`、任意形态带 `..` / `\` / 绝对路径或盘符;`<sys/stat.h>` 这类**尖括号系统头允许带子目录**,由 `-I` 解析。原实现把任何含 `/` 的 include 一律判违规,误伤模拟器侧 2 处;全仓库复核:细化后仅这 2 处项目代码判定发生变化,其余 100 处均在已排除的 `Lib/lvgl/`) |
| S12 | 7.6 | 禁止可变参数函数(`...`) |
| S13 | 7.7 | 禁止递归函数 |
| S15 | 6.1.4 | 文件作用域指针变量必须初始化为 NULL |
@@ -188,7 +189,8 @@ cppcheck 是开源 C/C++ 静态分析工具,**不运行代码**,通过语法
**豁免**(不算失效):`[计划]` 前缀;该行含 `[示例]` 标记(引用报错样例/日志片段时用);引用**附近 ±40 字符**内的状态词(未创建/尚未创建/已删除/待排期);占位符(`*`、`<模块>`、`NN_`、`${...}`);**行内代码 span 里的链接写法**(`![标题](相对路径.svg)` 这类语法示例);外部 URL 与纯锚点;`docs/芯片手册/` 等外部资料目录。
**排除文件**:`.opencode/node_modules/`、`Lib/lvgl/`、`simulator/`、`MDK-ARM/RTE/`、`docs/芯片手册/`。
**排除文件**:`.opencode/node_modules/`、`Lib/lvgl/`、`MDK-ARM/RTE/`、`docs/芯片手册/`。
(`simulator/` 已于 2026-10-11 从排除列表移除——其下 3 个 README 是本项目文档,见 §5。)
```powershell
# 全库扫描(开发时自检)
@@ -318,6 +320,20 @@ cppcheck 是开源 C/C++ 静态分析工具,**不运行代码**,通过语法
| **典型报错** | `T1 Title exceeds max length (159>100)` |
| **修复方式** | 精简 commit message 标题到 100 字符以内,或拆分为多行(标题+空行+body) |
### 2.15 lv-conf-sync — 两份 `lv_conf.h` 特性子集一致性(2026-10-11 加)
| 项 | 说明 |
|---|---|
| **背景** | `Lib/lv_conf.h`(MCU)与 `simulator/lv_conf.h`(PC)是**两份独立文件**,长期漂移:模拟器早已 I1-only,MCU 端却让 11 个 `LV_DRAW_SW_SUPPORT_*`、5 个未用控件类、`LV_USE_THEME_SIMPLE`、`LV_USE_FLEX/GRID` 靠 `lv_conf_internal.h` 默认值隐式生效 → 固件白链 **64,576 B 死代码**;更糟的是模拟器 golden 回归跑的是另一套特性子集,**验证不到固件实际编译的配置** |
| **触发条件** | `Lib/lv_conf.h` 或 `scripts/check_lv_conf_sync.py` 变动 |
| **判定口径** | 比对**生效值**:文件里没定义的宏,取 `Lib/lvgl/src/lv_conf_internal.h` 的兜底默认。⚠ 该文件每个选项都先套一层 `#ifdef LV_KCONFIG_PRESENT` → `CONFIG_LV_*` 分支**不是**默认值,脚本取块内**非 `CONFIG_` 的最后一个字面量**(首版按"紧跟 `#ifndef` 的那行"取,结果整表都是 `CONFIG_*`,教训) |
| **四类问题** | `MISMATCH` 生效值不同 → FAIL;`INVALID` 写了 LVGL 不认的宏名 → FAIL(`LV_USE_THEME_BASIC` 同类);`DUPLICATE` 同文件定义两次 → FAIL(后者生效,极易误读);`IMPLICIT` 一端靠默认值 → 警告(默认值随 LVGL 版本变,建议显式固化) |
| **端专属白名单** | SDL2 驱动 / 演示工程 / include 替换 / 调试辅助(`LV_USE_OBJ_NAME` 等)/ 断言(固件关、模拟器开)/ `LV_USE_SNAPSHOT`(模拟器 `--screenshot` 需要)——见脚本 `IGNORE_PREFIX` 与 `ALLOW_DIFF`(后者必须写理由) |
| **典型报错** | `FAIL: N 项不一致` + `[MISMATCH] LV_USE_OBSERVER MCU=1(默认) vs SIM=0(显式)` |
| **修复方式** | 把两端拉到同一生效值(**优先显式写死**,别依赖默认),或在 `ALLOW_DIFF` 里登记理由 |
| **触发范围** | `Lib/lv_conf.h`、**`simulator/lv_conf.h`** 或脚本自身变动 → 两端任一改动都会跑。⚠ 早期版本只能由 MCU 端触发,因为顶层 `exclude` 整目录挡掉了 `simulator/`(已删,见 §5);pre-commit 4.x 又移除了 `always: true`(实测对 `local` 钩子只告警并跳过),所以没有"总是跑"的兜底写法——**必须让文件模式覆盖两端** |
| **首次落地实测** | 两端共清掉 **26 个 9.3 不存在的选项**(LVGL 8 / 9.4 模板残留)、3 处同文件重复定义,并把 10 项 MCU 端靠默认值隐式生效的开关显式固化为 0(含默认 =1 的 `LV_USE_OBSERVER` / `LV_USE_DRAW_PXP` / `LV_USE_IMAGEBUTTON`)。本次清理对镜像体积**中性**(那些 TU 本就不在 uvprojx,或编成 0 字节空对象),价值在防漂移与可读性 |
---
## 3. 日常工作流
@@ -425,13 +441,27 @@ $env:PATH = [Environment]::GetEnvironmentVariable("Path","Machine") + ";" + [Env
**计划**:Route B —— 全库统一格式化后形成独立基线 commit,再启用 clang-format 钩子。启用时取消 `.pre-commit-config.yaml` 中的注释即可。
### 5.5 顶层 `exclude` 曾整目录豁免 `simulator/`(2026-10-11 已修)
**现象**:改 `simulator/` 下任何文件(含 `simulator/lv_conf.h`)的提交**一个钩子都不跑**——`pre-commit run --files simulator/lv_conf.h` 里所有钩子都是 `(no files to check) Skipped`。
**根因**:顶层 `exclude` 写了 `simulator/`。当初是为了挡 SDL2 预编译库、`build/`、截图产物,但这些**已在 `.gitignore` 里**,本就进不了 git 索引;pre-commit 只看索引中的文件,所以那条豁免唯一实际效果是把**本项目自研的 21 个 tracked 文件**(`main.c`、`sim/*.c|h`、`tests/*.py`、`lv_conf.h`、`dbg_log.h`、3 个 README)整体挡在门禁之外。
**为什么必须修**:模拟器与固件**共用同一套 UI 源码**,且它是 UI 回归唯一的像素级证据来源——它落在门禁外,等于验证链的地基不受检。
**纳入后的实测收益(当天)**:`cppcheck` 立刻揪出 `sim_syslog.c` 一处**真实空指针解引用**(`if (p_epoch == NULL || …) { *p_epoch = 0u; … }`——判空分支里紧接着解引用),另有 2 处 `localtime_s` 未初始化告警、1 处 `px` 可加 `const`;`coding-standard` 报出 61 处(44 处函数左大括号 Allman 风格、8 处缺函数注释、3 处缺文件头注释、2 处 `__` 前缀保护宏、2 处 `sprintf`、2 处 S11 误报),已全部处理。
**顺带修掉的规则缺陷**:S11 原实现把**任何含 `/` 的 include** 判违规,误伤 `<sys/stat.h>` 这类系统头;已按 include 形态细化,判定见 §2.8。
**教训**:`exclude` 只写**真正不该进钩子的路径**。已被 `.gitignore` 覆盖的生成物不需要再挡一遍——写上去只会连带豁免同一目录里的自研代码。
---
## 6. 文件清单
| 文件 | 位置 | 说明 |
|---|---|---|
| `.pre-commit-config.yaml` | 仓库根 | 钩子配置(15 个钩子 + 1 个预留) |
| `.pre-commit-config.yaml` | 仓库根 | 钩子配置(**26 个钩子**,2026-10-11 清点;本文 §2 详解非单测类,主机侧单测 10 个见 AGENTS.md 门禁条) |
| `.gitlint` | 仓库根 | gitlint 规则(宽松,兼容中文) |
| `.cubemx_survival_baseline.json` | 仓库根 | CubeMX 易失定制基线(哈希;`--write` 重建) |
| `scripts/check_mojibake.py` | scripts/ | U+FFFD 检测脚本 |
@@ -440,6 +470,7 @@ $env:PATH = [Environment]::GetEnvironmentVariable("Path","Machine") + ";" + [Env
| `scripts/gen_task_table.py` | scripts/ | FreeRTOS 任务表生成/校验(D-2/D-7) |
| `scripts/check_mem_numbers.py` | scripts/ | 内存/Flash 数字一致性(D-7 体积档) |
| `scripts/check_cubemx_survival.py` | scripts/ | CubeMX 重生成存活审计 |
| `scripts/check_lv_conf_sync.py` | scripts/ | 两份 `lv_conf.h` 特性子集一致性(比对生效值 + 无效宏名 + 重复定义) |
| `scripts/check_test_suite.py` | scripts/ | 测试套件开关门禁(防测试固件静默提交) |
| `docs/代码规范/嵌入式C语言代码规范_V1.0.md` | docs/代码规范/ | 代码规范原文(S1~S19 规则来源) |
| `docs/代码规范/cppcheck使用说明.md` | docs/代码规范/ | cppcheck 钩子详解(规则/配置/抑制) |
@@ -36,10 +36,10 @@
<!-- MEM_SNAPSHOT:BEGIN generated by scripts/check_mem_numbers.py -->
| 区域 | 用量 | 容量 | 占比 |
|---|---|---|---|
| ER_IROM1 | 274,728 B (268.29 KB) | 1,048,576 B (1024 KB) | 26.2% |
| RW_IRAM1 | 91,544 B (89.40 KB) | 131,072 B (128 KB) | 69.8% |
| ER_IROM1 | 210,148 B (205.22 KB) | 1,048,576 B (1024 KB) | 20.0% |
| RW_IRAM1 | 91,536 B (89.39 KB) | 131,072 B (128 KB) | 69.8% |
| RW_CCM | 49,184 B (48.03 KB) | 65,280 B (64 KB) | 75.3% |
| **Flash (ROM 合计)** | **275,608 B (269.1 KB)** | 1,048,576 B (1024 KB) | **26.3%** |
| **Flash (ROM 合计)** | **211,024 B (206.1 KB)** | 1,048,576 B (1024 KB) | **20.1%** |
<!-- MEM_SNAPSHOT:END -->
各区的容量与常量来源:
@@ -176,13 +176,19 @@
| 段 | 大小 (B) | 占比(对 1 MB) |
|---|---|---|
| Code (inc. data) | 246,456 | 23.5% |
| RO Data | 15,476 | 1.5% |
| RW 初值 | 728 | — |
| **合计 ROM** | **262,660** | **25.0%**(余 ~786 KB) |
| Code (inc. data) | 194,276 | 18.5% |
| RO Data | 15,872 | 1.5% |
| RW 初值 | 876 | — |
| **合计 ROM** | **211,024** | **20.1%**(余 ~813 KB) |
> 本表随代码变化;权威数字与占比见 §2.1 受管快照(`ER_IROM1` / **Flash (ROM 合计)** 行)。
> 历史快照:2026-09-21 为 Code 234,976 / RO 13,824 / 合计 ROM 249,432(23.8%)。
> 历史快照:2026-09-21 为 Code 234,976 / RO 13,824 / 合计 ROM 249,432(23.8%);2026-10-10 为 Code 258,516 / RO 16,208 / 合计 ROM 275,604(26.3%)。
> **2026-10-11 裁剪 LVGL 死代码(原审查报告 F-1/F-2/F-3,已销项)**:`Lib/lv_conf.h` 显式关掉 10 个 `LV_DRAW_SW_SUPPORT_*`(只留 I1)、5 个未用控件类(label / button / buttonmatrix / bar / textarea)、`LV_USE_THEME_SIMPLE`、`LV_USE_FLEX` / `LV_USE_GRID` → **`ER_IROM1` 274,724 → 210,148 B(省 64,576 B = 旧镜像的 23.5%)**,Flash 占比 26.3% → **20.1%**。
> 对象级对照(`.map` 组件表,Code+RO+RW):`blend_to_*` 40,582 → 1,338(其中 `blend_to_i1` 自身 5,272 → 1,338——它按**源**格式的分支也一并消失);控件类 13,397 → 0;`lv_theme_simple` 866 → 0;`lv_draw_sw_transform` 4,340 → 598;`lv_theme_mono` 1,400 → 1,036;镜像内对象数 227 → 212。实测比原报告估算(54,630 B)多省约 10 KB,差额正是这些"锚点消失后连带消失的分支"。
> ⚠ **配套本地补丁**:`lv_draw_sw_blend_to_i1.c` 的 `lv_color16_from_u16` 上游 9.3.0 漏了定义处守卫(唯一使用点全在 `#if LV_DRAW_SW_SUPPORT_RGB565_SWAPPED` 内)→ 关掉该格式后 ARMCC 报 `#177-D declared but never referenced`(破坏"0 警告"门禁)。已按使用点补同一守卫,升级 LVGL 时若上游已修可去掉。
> 验证:ui-verify 五步全过——**11 页内容黄金参考在裁剪前生成、裁剪后逐字节一致**,菜单 8 状态像素一致,LVGL 池水位 5,440 → 5,368 B(−72 B,样式表更小,一次性残留仍 80 B);`@build` 0 错 0 警;`@test` 真机 PASS。详见 `../UI设计/UI_设计架构.md` §1.4 第 10 条。
> **长效防复发**:已建门禁 `scripts/check_lv_conf_sync.py`(pre-commit 钩子 `lv-conf-sync`)——比对两份 `lv_conf.h` 的**生效值**(未定义则取 `lv_conf_internal.h` 兜底默认),并抓无效宏名与重复定义;首次跑揪出两端共 26 个 9.3 不存在的选项、3 处重复定义、10 项靠默认值隐式生效的开关,已清理(本次清理对镜像体积中性)。详解见 `../代码规范/pre-commit使用说明.md` §2.15。
![Flash 布局](../images/内存布局_FLASH.svg)
@@ -12,7 +12,7 @@
> [`../测试说明/实机测试实施指南.md`](../测试说明/实机测试实施指南.md)(故障注入台架流程)、
> [`../架构设计/项目启动顺序管理.md`](../架构设计/项目启动顺序管理.md)(启动时序与就绪屏障)、
> [`../测试说明/ST-LINK_CLI_复位使用说明.md`](../测试说明/ST-LINK_CLI_复位使用说明.md)(复位语义)、
> [`../问题记录/项目审查与优化建议报告.md`](../问题记录/项目审查与优化建议报告.md)(N-03 原始登记,已销项)
> [`../问题记录/项目审查与优化建议报告.md`](../问题记录/项目审查与优化建议报告.md)(N-03 的立项出处;该条已修复并按「修复即销项」从报告删除,**完整记录即本文**)
---
@@ -55,7 +55,7 @@
### 1.3 立项前状态(历史,2026-09-29)
当时全库无 IWDG/WWDG 初始化、HAL IWDG 驱动源码不在仓库、`.ioc` 无看门狗条目、无复位原因读取——缺陷登记为 N-03(`../问题记录/项目审查与优化建议报告.md`,已销项)。**该节只作立项背景**:现状是 IWDG 已启用(`.ioc` + `HAL_IWDG_MODULE_ENABLED` + 驱动已挂载),复位原因每次开机都在上报。
当时全库无 IWDG/WWDG 初始化、HAL IWDG 驱动源码不在仓库、`.ioc` 无看门狗条目、无复位原因读取——缺陷登记为 N-03(原登记于《项目审查与优化建议报告》,已修复并按销项删除正文)。**该节只作立项背景**:现状是 IWDG 已启用(`.ioc` + `HAL_IWDG_MODULE_ENABLED` + 驱动已挂载),复位原因每次开机都在上报。
---
@@ -623,7 +623,7 @@ enter/exit 段还能读出"此刻仍在段内已多久"——**永久阻塞的
| `Inc/FreeRTOSConfig.h:165` | `configASSERT` → `vApplicationAssertFailed` |
| `App/task/net_task.c` / `default_task.c` / `sync/time_sync.c` / `NET/lftpd/*` / `GD5F2GQ5UE/gd5f2gq5ue.c` | 心跳与喂狗埋点(见 §5.5) |
> 立项时(2026-09-29)的"仓库里根本没有 IWDG 驱动"一类取证已随实施失效,不再列出;当时的原始登记见 `../问题记录/项目审查与优化建议报告.md` §4.1(N-03,已销项)。
> 立项时(2026-09-29)的"仓库里根本没有 IWDG 驱动"一类取证已随实施失效,不再列出;当时的原始登记在《项目审查与优化建议报告》§4.1,已随「修复即销项」删除——**取证与实施记录以本文为准**。
## 附录 B:备选方案取舍记录
@@ -456,7 +456,7 @@ void adc_cfg_init(void)
**P0-1(2026-09-18)实现纠偏**:本表原设计即"暂态走堆",但实现落在 C 库 `malloc` 上——本机工具链为 **MicroLIB**,其 C 堆只有 `Heap_Size = 0x200`(512 B),真机上 4 处 KB 级分配**必然返回 NULL**,症状是"配置读不到 → 静默回落工厂默认值"(模拟器用宿主 CRT 堆,故两端表现不一致)。现全部改走 `cfg_store_alloc`;map 复核:`malloc.o` 不再参与链接,`Removing startup_stm32f407xx.o(HEAP), (512 bytes)` → **静态区反而净增 512 B 余量**。
**结论**:配置引擎自身不需要内存优化专项;但 9,217 B 暂态峰值已占 FreeRTOS 堆空闲的一半以上,且与 lftpd 的每命令分配共存——**流式读写(免整文件驻留内存)仍列为后续优化项**,方案见《项目审查与优化建议报告》§4.4.1 与 §5.3。
**结论**:配置引擎自身不需要内存优化专项;但 9,217 B 暂态峰值已占 FreeRTOS 堆空闲的一半以上,且与 lftpd 的每命令分配共存——**流式读写(免整文件驻留内存)仍列为后续优化项**,方案见本文 §6.2/§8 与《项目审查与优化建议报告》§5.3(原报告 §4.4.1 已随 N-01 修复按销项删除,完整记录沉淀在本文)。
## 9. 兼容与迁移
+16 -13
View File
@@ -4,7 +4,7 @@
> **参考来源**:`D:\Code\DTU\参考资料\huaiyuan\huaiyuan\Scatter_P_HV03.01_SV01.01\项目分析文档\04-算法与采样专题\ADC采集至数据使用全流程详解`(同专题另有《项目详细分析-交流采样与有效值计算》《傅氏算法详解》《项目详细分析-阶段6-继电器保护单元分析》)。
> **阅读前提**:先读 `docs/算法/傅氏算法详解.md`(算法原理)与 `docs/硬件平台/ADC通道映射说明.md`(采样通道权威口径)。
> **相关文档**:`docs/产品需求/产品需求设计文档.md`、`docs/待处理内容.md`、`docs/内存管理/RAM使用分析与优化方案.md`。
> **状态**:**P0 采样与相量底座已台架闭环(2026-10-11)**——L1 采集链路(B1,2026-10-10)与 L2 相量层(`sample_ring` + `phasor`,57 项主机单测 + 台架 `@test` 含 `phasor(first)/phasor(hb)` 标记闭环)均已落地;**L3 保护元件 / L4 出口记录 / L5 自检闭锁与定值模型尚未实现**(下一步 = P1,见 §7)。
> **状态**:**P0 采样与相量底座已台架闭环(2026-10-11)**——L1 采集链路(B1,2026-10-10)、L1-6 采样有效性校验与 L2 相量层(`sample_ring` + `phasor`,57 项主机单测 + 台架 `@test` 含 `phasor(first)/phasor(hb)` 标记闭环)均已落地;**P1/P2 保护元件与定值模型软件侧完成(2026-10-11,`prot_set` + `prot_elm`,57 项主机单测)**,板端接线(protectTask)与出口记录(L4)为下一步(见 §7)。
---
@@ -38,7 +38,7 @@
| 已实现子系统 | 网络(CH395F)、存储(GD5F2GQ5UE SPI NAND + dhara FTL + FatFs)、时间(SD2506 RTC + `sys_clock`)、配置(cfg_file)、日志、UI(远端显示)、看门狗(IWDG)、FTP |
| 采样硬件 | TPAFE5160(16 位、8 通道**同步**、±5 V 双极性、并行 `GPIOG[15:0]`)+ STM32 内部 ADC1(12 位、9 通道**顺序**扫描、0~3.3 V 单极性) |
| 采样软件 | `Drivers/BSP/TPAFE5160/tpafe5160.h` 驱动就绪(原子快照 API);`App/task/adc_task.c` **已实现真实消费者(B1,2026-10-10)**——`tpafe5160_snapshot()` 取数 + 帧统计;**采样节拍由 TIM3 更新事件(1 kHz)硬件触发**(2026-10-10;同一事件经 TRGO 触发 ADC1 + DMA);STM32 侧 **`App/algo/adc_input.c` 已实现语义映射 + ADC1 DMA 乒乓帧消费(L1-3/L1-4,v2.0)**,`HAL_ADC_Start_DMA` 已出现 |
| 保护算法层 | **L2 相量层已实现并台架闭环**(`App/algo/sample_ring.c` + `phasor.c`:全周傅氏 5 实例、双区交换、每 1 ms 刷新,2026-10-11 `@test`);**保护元件 / 出口 / 定值模型仍缺失**(P1 起) |
| 保护算法层 | **L2 相量层已实现并台架闭环**(`App/algo/sample_ring.c` + `phasor.c`:全周傅氏 5 实例、双区交换、每 1 ms 刷新,2026-10-11 `@test`);**L3 元件框架 + 过流/过负荷/过压/欠压/零序过压 + 定值模型软件侧完成**(`prot_elm.c` + `prot_set.c`,2026-10-11,57 项主机单测);**出口 / SOE 记录仍缺失**(P4) |
| 已知硬件缺陷 | **N-18**:8 路电气量分属两片转换器 → V0.1 上禁止跨转换器做相位/功率/矢量运算 |
### 1.3 复现范围界定
@@ -277,12 +277,12 @@
| # | 元件 | 判据要点 | 关键定值 | 状态 |
| --- | --- | --- | --- | --- |
| L3-1 | 三段式相间过流 | 相电流基波 > 定值,经延时出口;返回系数滞回 | Ⅰ/Ⅱ/Ⅲ 段电流 + 延时 | ⬜ |
| L3-2 | 三段式零序过流 | 零序电流 > 定值,经延时出口 | Ⅰ/Ⅱ/Ⅲ 段零序电流 + 延时 | ⬜ |
| L3-3 | 过负荷 | 相电流 > 定值,告警/跳闸可整定 | 过负荷电流 + 延时 | ⬜ |
| L3-4 | 过压 / 欠压 | 相/线电压越限,经延时出口 | 过压定值、失压定值 + 延时 | ⬜ |
| L3-5 | 过频 / 低频 | 频率越限,经延时出口 | 过频、低频定值 + 延时 | ⬜ |
| L3-6 | 零序过压 | 零序电压 > 定值 | 零序过压定值 + 延时 | ⬜ |
| L3-1 | 三段式相间过流 | 相电流基波 > 定值,经延时出口;返回系数滞回 | Ⅰ/Ⅱ/Ⅲ 段电流 + 延时 | 🔶 2026-10-11 主机实现:`App/algo/prot_elm.c` 通用"滞回+定时限"核 + `prot_set.c` 定值(Ⅰ/Ⅱ/Ⅲ 段,A 相 `IA`),单测 27 用例含 1.01× 启动/100 ms 到点/滞回保持/返回释放;**板端接线与出口待 protectTask 立项** |
| L3-2 | 三段式零序过流 | 零序电流 > 定值,经延时出口 | Ⅰ/Ⅱ/Ⅲ 段零序电流 + 延时 | ⬜(N-18:`IN` 走 STM32,V0.2 使能;框架已就绪,加实例表一行即可) |
| L3-3 | 过负荷 | 相电流 > 定值,告警/跳闸可整定 | 过负荷电流 + 延时 | 🔶 同 L3-1(`OL-IA` 实例,默认告警不跳闸) |
| L3-4 | 过压 / 欠压 | 相/线电压越限,经延时出口 | 过压定值、失压定值 + 延时 | 🔶 同 L3-1(`OV-UA`/`UV-UA`,欠量方向单测覆盖;沿用参考告警段 0x8027/0x8029) |
| L3-5 | 过频 / 低频 | 频率越限,经延时出口 | 过频、低频定值 + 延时 | ⬜(随频率测量算法 P2;框架已就绪) |
| L3-6 | 零序过压 | 零序电压 > 定值 | 零序过压定值 + 延时 | 🔶 同 L3-1(`3U0-UN` 实例,外部零序电压通道,不受 N-18 限制) |
**扩展集(P2/P3)**
@@ -320,10 +320,10 @@
| # | 功能点 | 说明 | 状态 |
| --- | --- | --- | --- |
| L5-1 | 定值校验和 | `tagPRS_XXX` 的 `dwTagSum` 自检,定值非法闭锁 | ⬜ |
| L5-2 | 采样通道自检 | 通道码值越界/静默检出 | ⬜ |
| L5-1 | 定值校验和 | `tagPRS_XXX` 的 `dwTagSum` 自检,定值非法闭锁 | 🔶 2026-10-11:`prot_set.c` 已实现 sum32 正/反码封存 + selfcheck 失配置闭锁(参考 `dwTagSum/dwTagSumN` 模式);元件级 tagPRS 常量表校验随板端接线并入 |
| L5-2 | 采样通道自检 | 通道码值越界/静默检出 | 🔶 静默/丢帧/序号倒退/错误增量已由 L1-6 `adc_health` 检出(2026-10-11);码值越界待做 |
| L5-3 | 硬件自检 | 出口回路、电源、时钟 | ⬜ |
| L5-4 | 自检失败闭锁 | 任一项失败 → 闭锁相关保护并告警 | ⬜ |
| L5-4 | 自检失败闭锁 | 任一项失败 → 闭锁相关保护并告警 | 🔶 定值闭锁 → 全元件复位已联动(`prot_elm_eval` 读 `prot_set_locked()`,单测覆盖);采样故障闭锁(`adc_health_flags` 电平位 → 元件使锁)随板端接线 |
---
@@ -408,6 +408,8 @@
| 公共 | 返回系数、PT/CT 变比、通道标定系数 | — |
> ⚠️ **待补**:逐项**取值范围 / 步长 / 默认值**取自参考项目 `APP/EquipCfg/Const_CommPara.c` 的 `CommPara_ConfigTable`(`0x8020~0x8882`);本表在实现各保护元件时逐项落实(见 §10.3)。
>
> **2026-10-11 落实进度**:`App/algo/prot_set.c` 已建 RAM 模型(Q10 定点,31 项),**通讯地址逐项对照参考 `Comm/Pub/Const_CommSetPara.c` `g_tCommSetParaTab`**(基址 0x8820:过流 Ⅰ/Ⅱ/Ⅲ = 0x8856~0x8861、过负荷 = 0x8871~0x8874、零序过压 = 0x888E~0x8890;过压/欠压沿用告警段 0x8027~0x802A)。⚠ **默认值/范围为拟定值**(参考 EEPROM 出厂默认表不在本仓库,按 DTU 馈线典型量级拟定),待对照参考出厂表逐项修订。
---
@@ -464,8 +466,8 @@
| 阶段 | 名称 | 内容 | 前置 | 里程碑 |
| --- | --- | --- | --- | --- |
| **P0** | 采样与相量底座 | B1(周期 CONVST + `adcTask` 消费 + 建任务 + 栈预算)+ L1 + L2 | B0 + B1 ✅ | 每 1 ms 产出正确的基波相量 —— **✅ 2026-10-11 台架闭环**(L1-6 同日落地 ✅;L1-5 标定 / L1-7 等周波为 P0 遗留项,精度复核依赖标准源) |
| **P1** | 基础保护元件 | **V0.1 有效子集**:过流(A 相,`IA`)、过负荷(A 相)+ 定值模型;B/C 相过流**建接口不使能** | P0 | 标准源注入 A 相可正确动作 |
| **P2** | 电压/频率类保护 | 过压/欠压(`UA`)、过频/低频、零序过压(`UN`) | P0 | 电压/频率越限正确动作 |
| **P1** | 基础保护元件 | **V0.1 有效子集**:过流(A 相,`IA`)、过负荷(A 相)+ 定值模型;B/C 相过流**建接口不使能** | P0 | 标准源注入 A 相可正确动作 —— 🔶 **软件侧 2026-10-11 完成**(`prot_set` 定值模型 30 用例 + `prot_elm` 元件框架/过流三段/过负荷 27 用例,主机全绿);板端接线(protectTask 立项)与标准源注入待做 |
| **P2** | 电压/频率类保护 | 过压/欠压(`UA`)、过频/低频、零序过压(`UN`) | P0 | 电压/频率越限正确动作 —— 🔶 过压/欠压/零序过压随 P1 同批主机完成(同框架实例);**过频/低频待频率测量算法** |
| **P3** | 异常检测与闭锁 | L5 自检闭锁、采样通道自检;三相类(涌流/PT·CT 断线/母线接地)**建接口不使能** | P1/P2 | 自检失败闭锁;未使能元件不误动 |
| **P4** | 出口、记录与定值集成 | L4 出口与记录、§5 定值模型落 cfg_file、UI 显示、上送 | P1 | 动作出口 + SOE 记录 + UI 显示闭环 |
| **P5** | 方向/功率类(延后) | L3-13 方向元件等 | V0.2 硬件 / 子板计量 | 跨转换器相位一致后实现 |
@@ -603,3 +605,4 @@ B0 产品定义 ✅(2026-10-10 已确认,见 §1.5)
| 2026-10-10 | **L1-1/L1-3 节拍源与搬运方式升级(v2.8)** | 节拍源 **TIM6 → TIM3**(更正硬事实:F4 上 ADC1 的外部触发清单里没有 TIM6/TIM7 TRGO,只接 DAC),TIM3 更新事件同时触发 TPAFE CONVST 与(经 TRGO)ADC1 一轮 9 通道;L1-3 由"软件触发 + 逐通道轮询"改为 **DMA2_Stream4 循环搬运 + 乒乓双缓冲**,`stm32_scan()` → `stm32_take()`,单拍 `adcp` **122 µs → 8 µs**。§1.4 采样软件行、§3.2 L1-1/L1-3/L1-4 同步。完整设计与实测见 `docs/子系统设计/ADC采样节拍设计说明.md` §11 |
| 2026-10-11 | **L2 台架闭环:P0 收尾** | `@build` 0 错 0 警 → `@test` 闭环把 `phasor(first)`/`phasor(hb)` 列为 Required 并校验顺序 `App init OK>phasor(first)>phasor(hb)`(⚠ `net init: OK` **不在**该序内——adcTask 与 netTask 并行,`phasor(first)` ≈+500 ms 早于 `net init: OK` ≈+574 ms 属正常)→ **PASS**:5 实例相量开机即产出、心跳 5 实例持续刷新、`frames` 每 10 s +10000(1000.0 Hz)、`idle=0`、`frst_err=0`、`adc1_err=0`、`adc1_hw=0`、`fw build` 身份戳匹配、回读一致、锚点 ×1、零 `[ERR]`/HardFault;证据 `boot_log_data/20261011_prod_phasor_boot_log.txt`。栈复测:adcTask 静态 832 B / 实测峰值 **928/2048(余 54%)**,7 任务全 PASS(RAM 方案 §2.3 已同步)。§3.1 L1-9 置 ✅(`C04-IA-F2`/`C05-IA-F5` 台架产出);§3.2 L2-1~L2-4/L2-6/L2-7 置 ✅(精度判据保留"待标准源"注),**L2-5 更正为"V0.1 不适用、随 V0.2"**(跨转换器合成被 N-18 禁止、零序走外部专用通道——原表下注"L2-1~L2-7 实现完成"对 L2-5 系误记);§7.1 P0 里程碑置 ✅(遗留 L1-5/L1-6/L1-7)。下一步 = **P1**(A 相过流/过负荷 + 定值模型)与 **L1-6 采样有效性校验** |
| 2026-10-11 | **L1-6 采样有效性校验落地** | 新增 `App/algo/adc_health.c/h`(**纯逻辑判定层**,零平台依赖免桩单测):① 静默——两侧帧序号连续 ≥100 ms 无前进(电平位,可自愈;`adc_health_flags()` 为 L5-4 闭锁预留);② 丢帧——TPAFE 序号一次前进 >1 计 `tgap`(与 STM32 侧 `adc1_gap` 对称,边沿事件不告警——消费者超拍是已知现象,告警会淹没真故障);③ 单调性——序号倒退锁存位;④ 错误增量——FRSTDATA / HAL 错误回调 / STM32 停摆计数增量(事件位与锁存位**编号对齐**,消费者按位图"每类每次开机留痕一次")。`adc_task.c` 只做告警编排:严重事件/上升沿 `DBG_ERROR` + `SYSLOG_SELFTEST`("adc sampling fault"),下降沿 `DBG_INFO`,心跳行增 `tgap`。主机单测 `test/host/adc_health/` **31 用例全绿**(100 ms 门限两侧、tick 回绕、边沿语义、NULL 守卫、init 幂等;`adc-health-unit-test` 钩子登记)。台架 `@test` PASS:零 `[ERR]`(健康不误报)、`tgap=1→2` 与 `adc1_gap` 同步可见、`frames` 1000.0 Hz;证据 `boot_log_data/20261011_prod_adc_health_boot_log.txt`。⚠ **抬栈**:`adc_health_report`→`syslog_post` 控制台回显尾使 adcTask 静态最坏链 832→**1080 B**(512 words 余 968 B < 1024 B 底线)→ **512→640 words**,复测余 **1480 B / 57%**、堆 min_ever 7152→6640 B(AGENTS 任务表 / RAM 方案 §2.3 已同步)。§3.1 L1-6 置 ✅ |
| 2026-10-11 | **P1/P2 软件侧:定值模型 + 保护元件框架** | 新增 `App/algo/prot_set.c/h`(**定值模型**:Q10 定点与参考 2^10 口径一致;31 项元数据表**通讯地址逐项对照参考 `Comm/Pub/Const_CommSetPara.c`**——过流 Ⅰ/Ⅱ/Ⅲ = 0x8856~0x8861、过负荷 = 0x8871~0x8874、零序过压 = 0x888E~0x8890、过压/欠压沿用告警段 0x8027~0x802A;范围校验 + 整表装载 all-or-nothing + **sum32 正/反码封存自检闭锁**(参考 `dwTagSum/dwTagSumN` 模式,L5-1);⚠ 默认值/范围为**拟定**,参考 EEPROM 出厂表不在本仓库,待对照修订)+ `App/algo/prot_elm.c/h`(**元件框架**:通用"越限 + 返回系数滞回 + 定时限"核,数据驱动实例表 7 行 = OC1/OC2/OC3/OL(IA)+ OV/UV(UA)+ 3U0(UN);`prot_elm_eval()` 纯判据可主机单测,`prot_elm_run()` 为 phasor→测量薄适配(相量未就绪整拍跳过);定值闭锁 → 全元件复位)。主机单测 `test/host/prot_set/`(30 用例)+ `test/host/prot_elm/`(27 用例:1.01× 启动 / 100 ms 到点 / 滞回保持与释放 / 欠量方向 / 投退 / 闭锁联动 / 时基回绕)全绿,钩子 `prot-set-unit-test` / `prot-elm-unit-test` 登记。`@build` 0 错 0 警(**未接线 → 链接器整对象移除,零 Flash/RAM 足迹**);板端接线随 protectTask 立项(栈预算 + IWDG 门禁),标准源注入验收随其后。§3.3 L3-1/3/4/6 置 🔶、L3-2/5 注明框架就绪待通道/频率算法;§3.5 L5-1/2/4 置 🔶;§5.4 落实进度注 |
@@ -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-03** | 无独立看门狗(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-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 三条主线建议
@@ -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/`(自研应用) | 18 | 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,292 | PC SDL2 宿主 |
| 第三方 | — | ≈55 万行 | LVGL 9.3(uvprojx 编译 201 个 `.c`)、FreeRTOS、FatFs、dhara、lftpd |
| `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: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) | 中间件→应用;已在文件内注释声明为"④中间件层"有意为之 |
**评价:良好**。在 24k 行规模上把反向依赖控制在 4 处,说明分层不是墙上的图。但存在**语义层错位**:
**评价:良好**。在 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 处:
依赖方向裸引用(绕过头文件契约的 `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/sd2506/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/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。
@@ -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/FreeRTOSConfig.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_cfg.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_IRAM1 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_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 的口径以此为准。
**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 ~ −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 |
| + 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%** | −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% 使用率**,`待处理内容.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/STM32F407-Demo/STM32F407-Demo.sct` 仅两个区域,无 `0x10000000` |
| 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) refers 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_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 评审中确认"无需改动"的设计(避免后续误改)
@@ -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 及理由) |