Files
STM32F4-Base/docs/问题记录/项目审查与优化建议报告.md
T
wandering1 52976bdec3 refactor(编号): 批 4 陷阱号统一为 TRAP-<模块码>-<nn>
本批是 ② 形态迁移清的第一族(用户裁定顺序:② 里的 Trap 一族先做)

统一了什么(数字不变、只补模块码,§7.1)——改名工具 23 处 / 11 文件
- Trap 01~07 → TRAP-GD5F-01~07:属主文档 7 个标题行从此成为定义位
- 陷阱 1 → TRAP-RTC-01(SD2506 属 RTC 域,§4.2)、见陷阱 01 → TRAP-GD5F-01
- Trap 21 / Trap 19 → TRAP-NET-21 / TRAP-NET-19(引用的是活登记处
  CH395F_Trap_Records.md,号本身不变)
- 波及 Drivers/BSP/GD5F2GQ5UE/*(4 处注释)与 test/board/*(2 处)→ 已重编

三处判读后**刻意不改**(写进 §8.3,不让人当成漏网)
- docs/环境与部署/并行任务工作树方案.md 的 wip/trap22-fix 是 git 分支名,不是编号;
  这类非编号字面量要靠类型处理,与位号 T1、IEEE 负序 S2、gitlint 规则 ID 同一批
- docs/UI设计/菜单页面设计.md 变更记录行的「陷阱 3」按 §8.1 冻结不改写
- docs/硬件平台/V0.0、V0.1 网表报告里 6 处 Trap05 语义确指 TRAP-NET-05,但该目录
  是门禁与改名工具共同的排除语料 → 不越界动笔,盲区登记进注册表 TRAP 族 cursor_note

两处不是改名、是改错话(映射表类工具的典型失效:字面对了、意思错了)
- scripts/README.md 原写「Trap 19 从未存在」;改名后字面变成 TRAP-NET-19,而它是
  **真实条目**(SET_MAC 必须在 INIT_CH395 之前)→ 那句话成了假话,改用站得住的例子
- docs/编号规范.md 同一处事故叙述补准确说法:当年裸形不在册 ≠ 这个坑不存在
- 菜单页面设计.md 的「见 §8.2 陷阱 1」本就不是 TRAP-* 族的号(指本文小节第 1 条),
  改写成「见本文 §8.2 第 1 条(lv_text_get_width 的 length=0 陷阱)」

关闸第一次真拦人:咬的是自己家的审计表
- scripts/rename_maps/batch3-trap.json 里必然写着 Trap 21 / 陷阱 1 等旧形态,
  被当成 6 类「台账外新账」→ 提交被拦(拦得对,但表不是语料)
- 第 4 次同一类自循环(前三次:别名表、注册表、债务台账)→ 整个 rename_maps/
  目录入 excluded_paths;这条规律写进 §8.4:**登记号的表一律不得当引用语料**

实测变化
- 错误级 659 → 632(−27);② 空格/中文形 Trap 残余 2 处(都是上面刻意保留的)
- ③ 悬空错误 44 → 42:TRAP-GD5F-01/05/06 三处历史悬空引用自动落地为已定义
- 编号字面量 8277 → 8312(识别面没有缩水,这是改名最容易踩的坑);
  可判定比例 82.92% → 82.99%(基线 82.6%);⑦ 定义无引用 100 → 103(如 TRAP-GD5F-02
  只有定义位无人引用,如实挂 warn 不压掉)
- 台账 --prune-backlog 收掉已还清 42 条,剩 298 条(只减不增);--strict 通过

另修:core.autocrlf=true 把 SD2506_Trap_Records.md 检出成 CRLF(41 处)→ 还原 LF,
该文件 diff 只剩 1 行实质改动(仓库权威行尾是 LF,见 .gitattributes)

验证(全绿)
- @build:0 错误 0 警告(Program Size Code=213356 RO=18580 RW=1184 ZI=131480)
- ci_local.py:L0 + L0.5 全过(16 runner / 1104 断言)
- check_doc_links.py --all:143 篇文档 0 失效、0 待确认
- 改动文件字节卫生逐项与 HEAD 对比:BOM / CR / U+FFFD 异常 0

审计轨迹与文档
- 映射表 scripts/rename_maps/batch3-trap.json(含作用域切分理由与刻意不改清单)
- 别名 11 条全部带 scope;docs/编号规范.md §7.1 行状态、§8.3 批 4 行 + 说明段、
  §8.4 自循环第 4 条、变更记录
2026-10-21 12:32:13 +08:00

696 lines
92 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# STM32F4-Base 项目审查与优化建议报告
| 项目 | 内容 |
|------|------|
| 审查日期 | 2026-09-18(初版) |
| **最近复核** | **2026-10-15**(R-9 落地后重链接),基线 `HEAD = a7f5265`(分支 `v2-on-ui`,工作树干净;其后的 `0cece5a` 文档提交与 `172b3f4` 模拟器侧改动都不影响固件);固件基线 = `MDK-ARM/STM32F407-Demo/STM32F407-Demo.map`(0 Error / 0 Warning,Code 212,344 = 已含 `506eccc` 的 `cfg_file` / `sd2506` 改动,比 2026-10-14 那次干净基线多 48 B)。本报告只在**内存/Flash 水位**上追这次构建,其余结论仍属 2026-10-14 复核 |
| **销项状态** | 本报告采用**修复即销项**:已修复项的正文与表格行已删除,去处见文末**已销项条目的记录去处**;未闭环清单见 **§0.4** |
| 审查范围 | 仓库全量(2,466 个受版本控制文件 / 79.3 MB);重点为自研代码 50,874 行(191 文件)与构建产物内存画像 |
| 审查方式 | 静态审查 + 链接图(.map)量化分析 + 构建脚本/门禁核对;**未做上机动态验证**(除特别标注外) |
| 目标 | 结构合理性、文档分布合理性、模块设计合理性、改进路线图、内存优化、UI 标准化 |
> **与既有文档的关系**:本报告不重复 [`../任务规划/README.md`](../任务规划/README.md)(逐条缺陷台账 + **推进顺序唯一权威**)与 `../内存管理/RAM使用分析与优化方案.md`(内存专项方案),而是做**体系层评审 + 量化补强 + 新增发现**。
>
> **编号口径**:本报告自有编号 `N-xx / D-x / S-x / R-x / F-x / U1~U5`,⚠ **与登记簿同名不同义**(报告 N-16 = 日志逐条开关,登记簿 N-16 = A2/A4 负向台架;报告 S-3 = 注册反向校验,登记簿 S-3 = USART1 TX-only 根治)→ **引用一律带文档名**。未闭环项已于 2026-10-11 并轨进登记簿(新编 `N-19`~`N-29` + U 簇,出处列保留本报告编号);**本报告编号本身不回改、不重排**(`../编号规范.md`)。
>
> **销项约定**:问题修复后其分析正文与表格行**从本报告删除**,完整记录沉淀到对应模块文档。因此本报告**只呈现未闭环问题**,不随时间膨胀。
---
## 0. 执行摘要
### 0.1 六维评分(2026-10-15 复核)
| 维度 | 评分 | 一句话结论 | 仍开放的主要缺口 |
|------|------|-----------|-----------------|
| 项目结构 | **A−** | 六层分层真实可执行、命名一致、门禁到位;反向依赖控制在 4 处 | `NETCFG`/`UI` 语义层错位(S-1)、4 处裸 `extern`(S-2)、`rs485_task.c` 仍在注册白名单里等 B2 |
| 文档分布 | **A−** | 工程文档 **87 篇 / 30,781 行**(2026-10-15 随 `错误码传播审计.md` 删除而 −1 篇 / −635 行),`docs/README.md` 索引 + `docs/文档规范.md` + 三档一致性门禁全部落地,`check_doc_links --all` = 0 失效 | **D-1**:`docs/` 占仓库 **71.8%**、手册图片 **36.3 MB**、`.git` 已 **235 MB**(已决策暂不处理,登记观察) |
| 模块设计 | **A−** | 看门狗(IWDG + 心跳位图 + 4 个 Fault 现场 + `.noinit`)、就绪屏障、卷锁、FTP 鉴权、`Error_Handler` 自恢复、ADC 全链均已落地;**原列的 dhara 移植层两处缺陷(N-12/N-13)与日志逐条开关(N-16)已于 2026-10-15 修复** → `GD5F2GQ5UE_Trap_Records.md` TRAP-GD5F-07、`../架构设计/STM32F4-Base存储架构说明.md` §5.3、`../子系统设计/运行与自检记录设计说明.md` §4.4/§4.5 | 驱动层无统一错误码约定(S-5)、RS485 无协议栈(随 B2 暂缓) |
| 内存使用 | **B+** | 堆迁 CCM + 栈/堆三面观测常开 + `mem-budget` 92% 门禁 + 预算台账与趋势基线;**R-9 已落地(2026-10-15)→ SRAM1 由 74.9% 降到 56.1%;同日移植层与日志修复 +1.6 KB → 现 **57.4%(余 55,840 B)**,不再是卡点** | **内存优化簇已整簇暂缓**(用户裁定 2026-10-15):R-2 后半(待迁 29,570 B → CCM,只差缩堆 56→32 KB)与 R-3 / R-4 / R-6 不再排期,理由与重开条件见 §5.3;**当前真正的约束是 CCM 只剩 7,904 B**——加任务 / 抬栈 / 加堆会先在这里撞墙,届时按 §5.3 重开;MicroLIB 512 B C 堆仍无机械检查(§5.5) |
| Flash 使用 | **A−** | F-1/F-2/F-3 落地省 64,576 B → 占用 **22.2%(232,280 B)** | F-4/F-5/F-6 ≈10 KB + 编译时长(§5.4) |
| UI 标准化 | **B+** | W6 第一波已落地(2026-10-15):**L0 设计令牌层** `ui_tokens.h` 建成、**L2 页面注册表** `ui_page_registry.{c,h}` 由生成器产出并**删除了手写 `case` 分发**、`font-charset` 钩子 + `registration-check` 第 ④ 类接线校验上线、字体子集瘦身 −80 B | L1 组件库已起步(U2 首个 `ui_w_records`,2026-10-20),但仅此 1 个组件、**目标页未落地**(现存 7 个 `ui_page_*.c`)、**golden 像素回归已于 2026-10-20 挂入提交门禁**(`ui-golden`)、`ui_w_form` 推迟 U5(§6) |
### 0.3 主线建议
> **推进顺序的唯一权威是 [`../任务规划/README.md`](../任务规划/README.md) §2**;本节只是体系评审视角的一条结构性建议,不另立顺序。
1. **把 UI 从"每页手写"变"声明 + 原型库"**:令牌层 → 原型库 → 生成器补全注册 → golden 入门禁,使新增一页的成本从 8 处手工改动降到 1 处声明(§6)。
---
## 0.4 未闭环清单(2026-10-15 复核)
> 只列**未办 / 半办**项;已修项按「修复即销项」删除(去处见文末)。图例:❌ 未办 / 🟡 半办。
> **顺序权威在 [`../任务规划/README.md`](../任务规划/README.md) §2,本表为评审快照**;登记簿编号在括号内。
| 类别 | 编号 | 未闭环内容 | 下一步 |
|------|------|-----------|--------|
| 内存 RAM | **R-2 剩余** ⏸ **暂缓**(N-27,原 W3) | 待迁体量已由 54,146 B 降到 **29,570 B**(R-9 落地后);CCM 只剩 7,904 B | **用户裁定 2026-10-15:暂不做**——没有功能在等这块预算;重开条件 → §5.3 |
| 内存 RAM | R-3 / R-4 / R-6 ⏸ **暂缓**(N-27,原 W3) | 页缓冲未合并 / UI 帧 4 副本 / 栈仍动态 | **同上裁定,暂不做**。本簇已落地的只有 R-9(2026-10-15,SRAM1 74.9%→56.1%,见 §5.3);**R-5 已判定不可行**(§5.3) |
| 内存 RAM | N-14 🟡(N-22,W4) | 写回缓存无空闲/掉电强制 flush(整页写与换页已 flush) | 空闲 N ms 由 defaultTask flush |
| **Flash** | **F-4 / F-5 / F-6** ❌(N-28,W4) | 剩余可评估 ≈10 KB(`box_shadow` 3,246 / `draw_sw_img` 2,960 / `bin_decoder` 1,972 / `image_decoder` 952 / `triangle` 930)+ 双精度 `dadd` 等 ≈1 KB;uvprojx 仍挂 201 个 LVGL `.c`(F-2 后均为空 TU,省的是编译时长) | 逐项实测再关;⚠ `mask`/`mask_rect`/`grad` 是文字与矩形绘制的必经路径,**不可盲关** |
| **性能/资源** | N-09 / PHY / P3-x / rs485 DE ❌(N-25,W4) | PASV 每次新建监听;PHY 强制 100M 无自协商回退;`NET_MAX_SOCKETS 8` 与单连接语义不符;`HAL_Delay(1)` 阻塞 DE(DE 项随 B2 暂缓) | 见 §4.2 / §4.7 各行建议 |
| **结构** | S-1 / S-2 / S-4 ❌ | `NETCFG`/`UI` 语义层错位;4 处裸 `extern`;`app_cfg.h` 开关未接线未删 | 随登记簿 C5 处置;`rs485_task.c` 随 B2 一并出白名单 |
| **存储/日志** | N-15 🟡 / P2-10 ❌ | `nand_ftl` 三块 2 KB 页缓冲未合并(BBT 缓冲已收紧);容量轮转 `f_unlink` 与 UI 读并发未纳入卷锁 | R-3 评估;卷锁覆盖面 → 登记簿 S-5 |
| **驱动规范** | S-5 ❌ | 无统一"错误码 + 超时 + 重试 + 自检上报"约定(`GD5F_OK` / `0` / `bool` 混用) | 定义 `bsp_err_t` + `bsp_retry()` 写入 `docs/代码规范/` |
| **仓库** | D-1 ❌(N-29,W7) | 图片 36.3 MB 仍在仓;**`.git` 已 235 MB** | 降采样 / 拆 submodule / LFS 三选一 **+ 一次 `git filter-repo` 历史重写**;**已决策暂不处理、登记观察** |
| **UI** | U1 / U2 / U3 / U5 ❌(U 簇,W6) | 无令牌层、无组件库、无页注册表(手写 `case` 仍在);**27 页**未落地 | 按 §6.5 顺序,U4 的 golden 已可当 U1 的验收工具;**本轮不排期** |
| UI | U4 残余 🟡(U 簇,W6) | golden 已建但未挂 pre-commit;字符集差集检查仍是约定 | 两个脚本入门禁 |
| **测试** | P1-10 🟡 / 覆盖率 ❌ | CH395F 套件收尾两步(构建实测链接 + `@test` 闭环);无覆盖率概念;主机侧对 `dhara` 移植层无测试 | N-12/N-13 这类缺陷至今只能靠人工读代码发现 |
---
## 1. 审查方法与证据基线
### 1.1 数据源
| 数据 | 来源 | 可信度 |
|------|------|--------|
| 内存/Flash 占用 | `.map` 的 component 表(237 个含码对象,列序 `Code incData RO RW ZI Debug Name`) | 高(构建产物,权威) |
| 总量 | map 尾部 `Total RO / Total RW / Total ROM` + `Execution Region` 表 | 高 |
| 依赖方向 | 逐文件 `#include` 扫描 + map 的 `refers to` 交叉表 | 高 |
| 构建注册 | `MDK-ARM/STM32F407-Demo.uvprojx` + `simulator/CMakeLists.txt` + git 文件清单三方对齐(已机械化:`scripts/check_registration.py`) | 高 |
| 文档链接 | `scripts/check_doc_links.py --all`(含 `--sections-strict`) | 高(门禁) |
### 1.2 复现命令(本报告所有数字可重跑)
```powershell
# 1) 构建体量(三区水位 + Top-10 RW+ZI)
& '.venv\Scripts\python.exe' scripts/ram_report.py
& '.venv\Scripts\python.exe' scripts/ram_baseline.py --trend
# 2) MicroLIB C 堆真实大小(应为 0x200 = 512 B)
Select-String -Path 'MDK-ARM\STM32F407-Demo\STM32F407-Demo.map' -Pattern '__heap_base|__heap_limit|HEAP'
# 3) 三方文件注册对齐(uvprojx / CMakeLists / git)
& '.venv\Scripts\python.exe' scripts/check_registration.py --strict
# 4) 仓库体量:git ls-files -z | 逐个统计字节(必须用 -z,否则中文名会被控制台代码页破坏)
```
> ⚠️ **踩坑记录**(供后续审查复用):
> ① map 组件表是 **6 列数值**(含 `incData` 与 `Debug`),按 5 列解析会得到 0 个对象;
> ② `git ls-files` 输出经 PowerShell 管道会按控制台代码页破坏中文路径,导致"文件不存在"的假结论,必须用 `-z` + 字节流解码;
> ③ 终端渲染的中文乱码不等于文件损坏,判定损坏须统计 `EF BF BD` 字节数(本报告统计结果:**0**,全库无 U+FFFD)。
### 1.3 口径定义
| 术语 | 定义 |
|------|------|
| 自研代码 | `App/`、`Drivers/BSP/`(不含 `Lib/`、`Middlewares/`、HAL/CMSIS)、`test/`;不含 CubeMX 生成的 `Src/`、`Inc/` |
| Flash 占用 | `Code + RO-data` = `ER_IROM1` **232,280 B(22.2% of 1 MB)**;`ROM` 再加 `RW-data` 初始化表 = 233,416 B |
| RAM 占用 | `RW-data + ZI-data`,**跨两个区域** = SRAM1 `RW_IRAM1` **75,232 / 131,072(57.4%)** + CCM `RW_CCM` **57,376 / 65,280(87.9%)** + `UNINIT_CCM` 48 B(`.noinit` 崩溃现场保留区)。⚠ 原报告"CCM 不在 scatter 内计 0"的口径已不成立 |
| "死代码" | 该对象在镜像中有码,但其功能被工程配置(如 `LV_COLOR_DEPTH 1`)或调用点扫描(零调用者)证明不可达 |
> **内存数字的权威源**:区域水位与子系统配额由 `../内存管理/RAM预算台账.md`(受管区,`ram_baseline.py --write` 生成)持有,趋势在 `scripts/ram_baseline.tsv`。本报告只在支撑结论处引用,不与之竞争事实源。
---
## 2. 项目结构合理性
### 2.1 规模与分布(2026-10-14 实测)
| 分类 | 文件 | 行数 | 说明 |
|------|------|------|------|
| `App/`(自研应用) | 64 | 13,841 | 配置引擎、墙钟、日志、任务编排;**`App/algo/`(L1~L3 + 测频 + 定值/SOE)14 文件 / 2,855 行** |
| `Drivers/BSP/`(自研驱动/子系统) | 76 | 21,845 | NET 8,264、UI 6,073(含 `ui_font_cn_12.c`)、CH395F 2,641、GD5F 2,035、SD2506 835、NETCFG 766、TPAFE 680、RS485 310 |
| `test/`(自研测试) | 51 | 15,188 | **`test/host/` 主机侧单测 33 文件 / 7,634 行** + `test/board/` 板级套件 18 文件 / 7,554 行 |
| **自研合计** | **191** | **50,874** | 2026-09-18 基线为 108 / 24,353 |
| CubeMX `Src/`+`Inc/` | 26 | 5,528 | 受 USER CODE 标记保护 |
| `simulator/`(自研部分,不含 SDL2 SDK) | 10 | 2,665 | PC SDL2 宿主 |
| 第三方 `Lib/` | 958 | 529,983 | LVGL 9.3、FreeRTOS、FatFs、dhara、lftpd |
**评价**:自研体量与第三方体量比约 **1:10.4**(基线 1:20),仍属"薄自研 + 厚中间件"。这决定了**结构审查的重点不在自研代码内部,而在"自研如何约束第三方"**——§5.4 量化的 `lv_conf.h` 约束失守代价(64,576 B 死代码)已修,但**两份 `lv_conf.h` 必须同一特性子集**这条硬约束如今靠 `lv-conf-sync` 钩子守住,属"约束被机械化"的正例。
### 2.2 分层与依赖方向
实测自研层间的**全部反向依赖**(BSP → App)——2026-10-14 复核为 **4 处**:
| 位置 | 反向 include | 判定 |
|------|-------------|------|
| `Drivers/BSP/NETCFG/net_cfg.c:31` | `cfg_file.h` | 可接受但需说明:NETCFG 实为"业务配置模块",放在 BSP 层是历史命名 |
| `Drivers/BSP/NETCFG/ftp_cfg.c:17` | `cfg_file.h` | 同上(FTP 鉴权配置随 `net_cfg` 落在同一目录) |
| `Drivers/BSP/UI/ui_page_runlog.c:19` | `sys_log.h` | UI 读日志属正常消费者,但 `sys_log` 在 App 层 → 层次倒置 |
| `Drivers/BSP/UI/ui_page_selftest.c:19` | `sys_log.h` | 同上 |
| `Lib/FatFs/ffsystem.c:19` | `sys_clock.h`(App/util) | 中间件→应用;已在文件内注释声明为"④中间件层"有意为之 |
**评价:良好**。在 50k 行规模上把反向依赖控制在 4 处,说明分层不是墙上的图。但存在**语义层错位**:
- `Drivers/BSP/NETCFG/`、`Drivers/BSP/UI/` 名字是"板级支持包",内容是**业务**(网络/FTP 配置语义、菜单/页面/权限/内容页框架)。后果:`sys_log`(App)与 `net_cfg`(BSP)互相引用,层次图自相矛盾。
- **建议(S-1)**:把 `NETCFG`、`UI` 在概念上重新定性为 `App/` 或新增 `Service/` 层,`Drivers/BSP/` 只留下与引脚/芯片强绑定的 `ch395f`、`gd5f2gq5ue`、`sd2506`、`tpafe5160`、`rs485`、`dbg_*`。迁移成本最低的做法:**先改文档与分层图(零风险),目录迁移留到有 CI 兜底之后**。
依赖方向裸引用(绕过头文件契约的 `extern`)实测 4 处——**2026-10-14 复核:4 处全部仍在**,S-2 未办:
| 位置 | 裸 extern |
|------|-----------|
| `Drivers/BSP/CH395F/ch395f.c:177` | `extern volatile uint8_t g_spi1_dma_done;`(实为 GD5F 在 `gd5f2gq5ue.c:50` 定义的变量,跨模块反向且**未进任何头文件**) |
| `Drivers/BSP/GD5F2GQ5UE/gd5f2gq5ue.c:47` | `extern SPI_HandleTypeDef hspi1;`(`Inc/spi.h` 已导出,局部重复) |
| `Drivers/BSP/NET/net_socket.c:77` | `extern osMessageQueueId_t netMsgQueueHandle;` |
| `Drivers/BSP/SD2506/sd2506.c:20` | `extern I2C_HandleTypeDef hi2c1;`(`Inc/i2c.h` 已导出,局部重复) |
**建议(S-2)**:CubeMX 句柄类(`hspi1`/`hi2c1`)统一由 `main.h` 导出并 include,删除局部 extern;`g_spi1_dma_done` 应由 GD5F 模块自己定义并在头文件 `extern` 声明(当前跨模块裸 extern 是真正的耦合隐患);`netMsgQueueHandle` 改为 net_socket 内部持有 + 提供投递 API。
### 2.3 双构建清单漂移
把 **git 文件清单 × uvprojx × simulator/CMakeLists** 三方对齐后:
| 类别 | 文件 | 后果 |
|------|------|------|
| **两边都不注册** | 基线 5 个 → 现剩 **1 个**:`App/task/rs485_task.c`(E-59 门禁下的**刻意**缺口,随 B2 暂缓,已登记 `KNOWN_GAPS`) | `crc.c`/`ringbuf.c` 已整体删除(全库无调用者)、`lftpd_log.c` 已删除并入 `lftpd_auth.c`、`adc_task.c` 已挂 uvprojx(仅 MCU,符合"任务层不入模拟器")——**"写了不编译"的盲区已消** |
| **仅 simulator 注册** | `cfg_file_store_sim.c`、`net_socket_sim.c`、`sim_port.c`、`sim_syslog.c`、`ui_net_sink_backend_sim.c` | 正常(后端适配层模式) |
| **uvprojx** | 323 个 `.c`(含 201 个 LVGL) | LVGL 全量编译 → 编译时长;F-2 落地后未用控件类 TU 均为空目标文件(见 §5.4 F-5) |
**评价**:AGENTS.md 的"新文件必须注册两处"铁律如今**两端都有机械保障**——正向由 `new-source-file` skill 约束,反向由 `scripts/check_registration.py`(pre-commit `registration-check` 钩子,2026-10-12)校验"存在但未注册",并反向查"注册了但文件不存在"的幽灵注册。**残余只有一条刻意缺口**:`rs485_task.c` 在 `KNOWN_GAPS` 里等 B2 立项,随该簇一并处置即可(清完后把 `--strict` 设为默认)。
### 2.4 目录组织与根目录卫生
| 观察 | 证据 | 判定 |
|------|------|------|
| 根目录散置工作文件 | `ui_menu_tree.txt`、`ui_cn_chars.txt` | 菜单树/字符集是**生成器输入**,放根目录合理且已在 `docs/` 有权威说明 |
| `Lib/` 命名 | `Lib/` 下混放 `lvgl/`、`lv_conf.h`、`FatFs/`、`dhara/` | `lv_conf.h` 是**工程配置**不是库文件;建议移到 `Config/lv_conf.h` 并同步 include 路径(低风险;⚠ 须一并处理 `simulator/lv_conf.h`,两份必须同一特性子集) |
| 功能开关名存实亡 | `App/app/app_cfg.h` 的 `APP_NET_ENABLE/APP_ADC_ENABLE/APP_RS485_ENABLE` **全库零引用** | 开关不生效比没有开关更糟——评审者会误信"ADC 可裁剪"。文件头 v1.1 已如实标注"预留占位、不门控任何代码",但**仍未接线也未删**(S-4) |
### 2.5 结构结论
**总体判断:结构合理,无需重构**。要做的是三类**低成本纠偏**:S-1 层次定性、S-2 消裸 extern、S-4 让 `app_cfg.h` 开关真正生效或删除。
---
## 3. 文档分布合理性
### 3.1 体量画像(2026-10-14 实测,工作树)
| 对象 | 文件数 | 体积 | 占仓库 |
|------|--------|------|--------|
| 仓库受控文件总计 | 2,465 | **79.2 MB** | 100% |
| `docs/` 合计 | — | **56.9 MB** | **71.8%** |
| └ `docs/芯片手册/` 图片 | 703 张 | **36.3 MB** | **45.8%** |
| `.git` 目录 | — | **235.3 MB** | — |
| 工程文档(Markdown,自研) | 87 篇 / **30,781 行**(2026-10-15 随 `错误码传播审计.md` 删除,−1 篇 / −635 行) | — | — |
| 外部资料(Markdown,datasheet 转录) | 14 篇 / 58,244 行 | — | — |
**关键失衡**:工程文档与外部资料的行数比已从 1:4.2 改善到 **1:1.9**,但**全部 Markdown 只有 5.4 MB,图片体量(36.3 MB)是它的 6.7 倍、占仓库近一半**,`git clone` 的净耗时几乎全花在扫描手册图片上。⚠ `.git` 已达 **235 MB**(受控文件才 79.3 MB),说明历史里存过多版图片/产物——**D-1 单靠拆仓或 LFS 不足以解决 clone 耗时,还需一次历史重写**。
### 3.2 做得好的(应保留并显式制度化)
1. **文档密度与可执行性**:每篇都带"步骤 + 判据"(如 UI 制作流程有 P0→P4 Gate);测试说明按模块拆分并给出预期串口标记,可直接接 `@test`。
2. **陷阱台账**:`docs/问题记录/*_Trap_Records.md` + `docs/任务规划/`(带状态与日期)——这是本工程最有价值的资产,多数嵌入式项目没有。
3. **图与脚本同提交**:`tools/arch_diagram/`、`tools/refgraph/` 生成器 + 产物入库,图可再生而非手绘。
4. **UI 设计资产链完整**:原始参考图 → 说明 md → 代码,可追溯。
5. **文档一致性已机械化**:`doc-links`(含 `--sections-strict`)、`task-table`、`mem-numbers`、`lv-conf-sync`、`cubemx-survival` 等只读钩子守住"文档说的 == 代码做的"。
### 3.3 问题清单
#### D-1(P1)外部资料与工程文档同仓同树
**影响**:clone 慢、备份慢、code review 时 diff 噪音大;`docs/芯片手册/` 的 703 张图属于**只读输入资料**,与代码演进无关。
**建议**(三选一,按代价递增):
1. **最省**:把手册 JPG 降采样到长边 1,200 px(约 36 MB → 8 MB),一次性提交并在 `../文档规范.md` 写入"入库图片上限 200 KB/张";
2. **推荐**:`docs/芯片手册/` 拆为独立仓库 `dtu-datasheets`,用 `git submodule` 挂回原路径(现有全部链接不需改写);
3. **最正**:Git LFS(需要托管端支持)。
> ⚠ 三项都不解决历史膨胀:`.git` 235 MB 需一次 `git filter-repo` 历史重写(破坏性操作,所有克隆需重取)。**2026-10-11 已决策:暂不处理、登记观察**(登记簿 N-29),推送/协作前重评——届时未推送提交更少、重写代价最低。
### 3.4 文档结论
**内容质量高(A−)、分布形态差**。D-1 处理后可到 A:工程量约 1 人日(不含拆仓与历史重写),收益是 clone 时间与"文档可信度"。
---
## 4. 功能模块设计评审
### 4.1 启动与任务框架
**设计良好**:
- `app_main_init` 顺序有因(墙钟优先,使后续日志带时间戳);
- **Event Flags 就绪屏障**(`NET_READY/STORAGE_READY/FTP_READY/TIMESYNC_READY`)取代魔法延时,是本项目最漂亮的设计之一;
- 配置引擎属主收敛到 defaultTask 单任务,消除了跨任务竞态;
- 初始化失败路径已收口:`Error_Handler` 打 1 条自检日志 + `NVIC_SystemReset()`(不再 `__disable_irq(); while(1)`)。
**问题**:
| 编号 | 问题 | 影响/建议 |
|------|------|-----------|
| S-4 | `App/app/app_cfg.h` 三个 `APP_*_ENABLE` 零引用(文件头已标注"预留占位") | 开关要么生效要么删;产品定义(登记簿 B0)落定前至少别让评审者误信"可裁剪" |
### 4.2 网络子系统(CH395F + BSD 抽象层)
**设计良好**:
- 所有 `net_*` 经 `netMsgQueue` 串行到 netTask 执行 → 单线程语义清晰;
- 全部 CH395F 中断集中在 `net_poll()` 内 `GET_GLOB_INT_STATUS` 排空(AGENTS 铁律,已落实);
- 网络代次号机制使"改 IP 免重启"能重建各监听;
- **挂起 recv/send 模型**(`net_pending.c/h`)取代"强加 30 s 默认预算",recv 语义已主流化。
**问题**:
| 编号 | 问题 | 证据 | 建议 |
|------|------|------|------|
| N-09 | `cmd_pasv` 每次新建监听 `lftpd_inet_listen(0)`(随机端口) | `lftpd.c:472` | 在**单连接 socket** 上与 UI `:7003`、对时 `:8888` 争用同一物理 socket;建议限定监听资源池 + 显式"FTP 会话独占"策略,并在文档写明"一次只允许一个 TCP 服务活动" |
| — | PHY 强制 100M 全双工,忽略自协商 | `App/task/net_task.c:110` 无条件 `ch395f_set_phy(CH395F_PHY_100M_FULL)` | 与某些交换机会联不通;至少加"链路 up 失败 3 次后回退自协商" |
| P3-x | `NET_MAX_SOCKETS 8`(`net_config.h:33`)是**软件表**,与硬件单连接语义不符 | | 文档已说明,但 API 层看不到限制 → 建议在 `net_socket.c` 断言"同时 LISTEN 数 ≤ N" |
### 4.3 存储子系统(GD5F2GQ5UE + dhara + FatFs)
**设计良好**:
- 分层正确:`gd5f2gq5ue`(芯片)→ `nand_ftl`(dhara map/journal + 页缓存)→ FatFs R0.14 → 应用;
- `Lib/FatFs/ffsystem.c` 用真实 `osMutexNew` 实现 `FF_FS_REENTRANT=1` 的卷锁(多数移植是空实现)——**这是本工程移植质量的一个亮点**;`get_fattime()` 接系统墙钟,使 FAT 时间戳有意义;
- BBT 保留块池把坏块表存储区从 dhara 可见空间中排除,避免被 FTL 擦写;
- **DMA 可达性三层防线**(原 N-11 的处置,2026-10-11):编译期窗口断言(`gd5f2gq5ue.h:96-107` 负数组 typedef,DMA 白名单窗口与 CCM 永不得重叠 + 阈值契约)→ 启动期自检(`nand_ftl.c:241-243` 三块静态页缓冲逐一过守卫)→ 运行时强制(`gd5f_dma_buf_ok()` 不可达即回落轮询 + `[ERR]`);规则本身(**R2 DMA 缓冲只允许 SRAM 静态**、**R3 堆/栈禁为 DMA 源/目的**)已写进 `../内存管理/内存内容分配策略.md` §4,并有 TC-GD5F-704/705 负路径用例实证。**缓冲自身地址无法编译期断言**(`.bss` 地址链接期才定,AC5 取不到)——这条边界在头文件注释里写明,别再试图"加个 `_Static_assert`"。
**问题**:
| 编号 | 问题 | 证据 | 影响/建议 |
|------|------|------|-----------|
| N-14 | 写回缓存 `s_cache_buf/s_cache_dirty` 无掉电保护策略:`CTRL_SYNC` 已实现但**没有空闲时强制 flush** | `nand_ftl.c:418` | 掉电窗口 = 最后一次未跨页的写。已改善:整页写立即 flush、换页时 flush、`CTRL_SYNC` 走 flush + `dhara_map_sync`;仍无空闲定时 flush → 建议 defaultTask 空闲 N ms flush |
| N-15 | 页缓冲重复:`nand_ftl` 3×2,048(`s_page_buf/s_cache_buf/s_copy_buf`) | 对象 ZI:nand_ftl 6,216 | 与 `s_copy_buf` 复用可省 2 KB(§5.3 R-3)。🟡 `gd5f` 的 `s_bbt_page_buf` 已由 2,048 B 收紧为 272 B |
| — | `disk_ioctl GET_BLOCK_SIZE = 1` | `nand_ftl.c:442-444` | FatFs 的擦除块提示失效(FAT32 对齐优化拿不到信息);建议返回 64 页(=1 物理块) |
### 4.4 配置引擎(`App/cfg/cfg_file.c`,1,343 行)
**设计良好**:描述符 + 段注册表、外科式(surgical)保存、模板生成、2 s 轮询(`cfg_file_poll`)响应 `SYS_EVT_CFG_SAVE`,属主单任务化。这是全项目设计最完整的一个模块。销毁用户文件的路径(超长/损坏)已补自检留痕(`cfg load oversized` / `cfg load read fail` → `SYSLOG_SELFTEST`),现场无调试器时可追溯。
**问题**:
| 编号 | 问题 | 建议 |
|------|------|------|
| — | 读失败仍静默回落工厂默认(`:1071-1102`),语义合理(不阻塞组网),但**用户改了 IP 重启就丢,串口之外无提示** | 装置信息页给出"当前为出厂配置"标识(自检记录已有,UI 侧未接) |
### 4.5 日志子系统(`App/log/sys_log_mcu.c`)
**设计良好**:定长 64 B 记录(`SYSLOG_REC_SIZE`)、单写者(defaultTask)、RAM 队列 + `syslog_drain`、容量满时删除重建、`/log` 目录惰性创建(`mkdir` 失败保持未就绪 + 幂等重试 + 上报);`/log/selftest.log` + 开机自检锚点 + Fault 现场 `.noinit` 跨复位 → 现场故障从"不可知"变成"可回读"。控制台侧另有**单写者队列**(`App/log/console_log.c`,对象 ZI 4,120 B)把"多任务直接 `printf` 到 USART1"收敛为一条流;代价已量化进 AGENTS.md(**每条日志约 90 B Flash + 216 B 栈**,深调用链模块另以 `#define DBG_LINE_BUF_MAX 128U` 收紧)。
**问题**:
| 编号 | 问题 | 证据 | 建议 |
|------|------|------|------|
| P2-10 | 容量轮转 `f_unlink` 与 UI 读并发 | | 轮转只在写者、读者持版本号;卷锁覆盖面评估 → 登记簿 S-5 |
### 4.6 UI 子系统
见 §6 专题(同时是"标准化"章节)。此处仅记设计层结论:三层拆分(数据 `ui_menu_tab` / 视图 `ui_menu_view` / 控制器 `ui_menu`)+ 内容页描述符框架**方向正确**,但**框架未收敛到"新页面零框架改动"**(`menu_content_page_for()` 仍是手写 `case`,`ui_menu.c:118-128`),且 4 份 3,200 B 帧副本占 12.8 KB RAM(§5)。✅ 验证侧已到位:golden 回归已建(§6.1),UI 改动从此有逐像素/逐字节基线可依赖。
### 4.7 外设驱动
| 模块 | 评价 | 未闭环问题 |
|------|------|-----------|
| `sd2506`(RTC) | I2C 超时齐备,降级策略明确(RTC 故障→默认时基 + 自检上报),WRTC 解锁顺序按手册、负温度符号扩展已处理 | — |
| `tpafe5160`(并行 ADC) | BUSY 超时、EXTI 读取路径齐备;取数已收口为 `tpafe5160_snapshot()` 原子快照(帧序号只增不清 + FRSTDATA 自检) | ⚠ **V0.1 硬件缺陷(登记簿 N-18,V0.2 改版项)**:8 路电气量被分给两片转换器 → 主板侧**禁止**跨转换器做相位/功率/功率因数运算;并行总线无批量采样框架,16 位端口读依赖 `gpio.c` 初值 |
| `rs485` | 结构简单 | DE 时序用 `HAL_Delay(1)` **阻塞 1 ms**(在调用任务里,`rs485.c`);无帧校验、无接收环(`crc.c`/`ringbuf.c` 已删除 → **需从零重做**);`rs485_task.c` 仍未注册,消费者被 E-50(包撕裂)/E-49(TX 超时拉 DE)挡住(随 B2 暂缓) |
| `ch395f` | 事务包裹 `begin/end`、SPI 分频 8 为 PCB 约束(已文档化,勿动) | 静态 DMA 缓冲 2×2,052 B(obj ZI 4,162),必须留 SRAM1 |
**驱动共性缺口**:没有统一的"错误码 + 超时 + 重试 + 自检上报"约定,各模块自定返回码(`GD5F_OK` / `0` / `bool` 混用)。**建议(S-5)**:定义 `bsp_err_t` 统一错误码 + `bsp_retry()` 辅助 + 所有失败必须走自检记录,作为驱动编写规范写入 `docs/代码规范/`。
### 4.8 测试体系
**设计良好**:`test/board/test_config.h` 的 `TEST_SUITE_*` 互斥(`#error` 保护)、CH395F 硬件层/NET 层 Phase 互斥、`@test` 一键闭环(烧录→wait-idle→标记校验)、模拟器 `--cfgtest/--logtest`、主机侧单测 16 个钩子(含 `sha256` / `ftp_auth` / `vol_lock` / 采集算法族 / **`nand_port` dhara 移植层**)。
> ⚠ 仍缺:**覆盖率概念**、**主机侧对 `dhara` 移植层的测试**(N-12/N-13 这类缺陷至今只能靠人工读代码发现)。
**问题**:
| 编号 | 问题 | 建议 |
|------|------|------|
| P1-10 | CH395F 硬件测试套件静态 RAM 预算(`L6406E` 历史:HW 套 +8.7 KB、NET 层固件 +28 KB) | 基线已重建、测试缓冲已瘦身、RAM 侧因堆迁 CCM 大幅缓解;**剩"套件构建实测链接确认 + `@test` 闭环"两步**(登记簿唯一 P 类收尾项,W5) |
---
## 5. 内存优化(量化)
### 5.1 实测基线(2026-10-15 `.map`,R-9 落地后)
| 指标 | 数值 | 上限 | 占用 |
|------|------|------|------|
| `Code + RO-data`(`ER_IROM1`) | 232,280 B | 1 MB Flash | **22.2%** |
| `ROM`(`Code + RO + RW` 初始化表) | 233,416 B | 1 MB | 22.3% |
| **SRAM1(0x20000000,128 KB)** `RW_IRAM1` | **75,232 B** | 131,072 B | **57.4%(余 55,840 B)** |
| **CCM(0x10000000,64 KB)** `RW_CCM` | **57,376 B** | 65,280 B | **87.9%(余 7,904 B)** |
| `UNINIT_CCM`(`.noinit` 崩溃现场) | 48 B | 256 B | 18.8% |
> **R-9 已落地(2026-10-15)**:`LV_MEM_SIZE` 40→16 KB → SRAM1 由 98,144 B / **74.9%** 降到 **73,568 B / 56.1%**(−24,576 B,与池缩减量逐字节吻合)。SRAM1 首次从"最紧的一块"变成**余 57.5 KB**(现 75,232 B / 57.4%、余 55,840 B,增量来自同日移植层守卫与日志缓冲 +1,664 B);**当前瓶颈换到 CCM(87.9%,只剩 7,904 B)**。⏸ 该簇后续动作(缩堆 → 迁 CCM)已按用户裁定 2026-10-15 **整簇暂缓**,见 §5.3。
> **权威源**:区域水位与子系统配额 → `../内存管理/RAM预算台账.md`(受管区);趋势 → `scripts/ram_baseline.tsv`(`ram_baseline.py --trend` 高亮 RW+ZI 增长 >1 KB 的对象)。本节只列支撑结论所需的数。
**RW+ZI 主要对象(`ram_baseline.tsv` 末行 Top-10)**
| 对象 / 符号 | 字节 | 所在区域 | 归属 | DMA 可达性 |
|-------------|------|---------|------|-----------|
| `ucHeap`(`heap_4.o`) | 57,376 | **CCM**(`0x10000020`) | FreeRTOS 堆(`configTOTAL_HEAP_SIZE` 56 KB + 元数据) | **DMA 不可达**——规则 R3「堆/栈禁为 DMA 源/目的」已写进 `../内存管理/内存内容分配策略.md` §4,并由 `gd5f_dma_buf_ok()` 运行时强制 |
| `work_mem_int`(`lv_mem_core_builtin.o`,= `LV_MEM_SIZE 16*1024`,**R-9 后**) | 16,384 | **SRAM1** | LVGL 池(实测峰值 6,568 B = 40%) | 纯 CPU,**可迁 CCM(R-2 未完成项)** |
| `lftpd.o` | 7,884 | SRAM1 | FTP(含鉴权) | 任意 |
| `ui_net_sink.o`(`g_tx` + `g_lcdmem_data` + `g_rx`) | 6,766 | SRAM1 | UI 网络发送 | 可迁 CCM(CH395F 侧 memcpy 到自有 SRAM1 缓冲) |
| `ui_port.o`(`g_ui_fbuf` + `g_ui_draw_buf`) | 6,420 | SRAM1 | UI 帧/绘制缓冲 | 可迁 CCM |
| `nand_ftl.o`(3×2,048 页缓冲 + 控制块) | 6,216 | SRAM1 | 存储 | **必须 SRAM1**(规则 R2/R3) |
| `ch395f.o`(`s_spi2_dma_tx/rx[2052]`) | 4,162 | SRAM1 | 网络 | **必须 SRAM1** |
| `console_log.o`(行环形缓冲) | 4,120 | SRAM1 | 日志 | 任意(TX 走 DMA → 实际必须 SRAM1) |
| `default_task.o` | 2,736 | SRAM1 | 任务薄壳 | — |
| `sys_log_mcu.o` | 1,924 | SRAM1 | 日志 | 任意 |
**两个静态池(57,376 + 16,384 = 73,760 B)仍是 RAM 问题的核心**,且**分居两区**:堆在 CCM(占 CCM 的 87.9%)、LVGL 池在 SRAM1(R-9 后只占 SRAM1 的 22%)。→ 下一步收益点仍是**把 LVGL 池 + UI 两份缓冲搬进 CCM**,但体量已由 54,146 B 降到 **29,570 B**(16,384 + 6,420 + 6,766),而 CCM 只剩 7,904 B → **R-9 已做完,还差"缩堆"这一步**(56→32 KB 可腾 24 KB → CCM 余 ≈31.9 KB ≥ 29,570 B;解锁链 → `../内存管理/RAM预算台账.md` §3)。⚠ 迁移前必须过 DMA 可达性清单:区域放置与"堆/栈禁为 DMA 源/目的"的规则族(R1~R4)以 `../内存管理/内存内容分配策略.md` §4 为准,本仓另有两条实证同族约束(ADC1 DMA 目的缓冲必须在 `RW_IRAM1`;CCM 上做 DMA 静默不落地)。
**Flash 侧(Code+RO,按 `.map` 组件表对象名归类)**
| 子系统 | Code+RO | 占镜像 |
|--------|---------|--------|
| LVGL(核心/绘制/字体/缓存/图像) | 87,706 | **38.0%** |
| 网络(NET + CH395F + NETCFG + lftpd) | 27,561 | 11.9% |
| 存储(FatFs + dhara + NAND + 卷锁) | 19,265 | 8.3% |
| 手写 UI 页面/框架(`ui_*.o`) | 18,507 | 8.0% |
| FreeRTOS + CMSIS-RTOS | 13,562 | 5.9% |
| HAL/CMSIS | 13,532 | 5.9% |
| 算法层 L1~L3(`adc_input`/`phasor`/`sample_ring`/`prot_*`) | 5,454 | 2.4% |
| 其余(`App/` 应用与驱动自研、startup、C 库、填充) | ≈45,497 | ≈19.7% |
| **合计** | **231,084** | 100% |
> 归类按 `.map` 组件表对象名前缀,合计与区域总量可差几十字节(链接器填充)。前 7 行逐字节与 2026-10-14 那次构建相同;并行未提交改动(`cfg_file` / `sd2506`)的 +48 B 落在"其余"。⚠ **本表是那次构建的构成快照**(合计 231,084 B);当前镜像 **232,280 B / 22.2%**,增量 +1,192 B `Code` 来自同日 dhara 移植层守卫与日志批量化(登记簿原 N-21 / N-24),**不改变"LVGL 是最大单一子系统"的结论**。
**要点**:LVGL 的绝对量自 F-1/F-2/F-3 裁剪后**未再增长**(87,706 B),但仍是镜像最大单一子系统 → §5.4 的剩余裁剪(≈10 KB)与 F-5 的编译时长收益依旧成立。
### 5.2 结构性诊断(三条)
1. **瓶颈已从 SRAM1 换到 CCM**:R-9 落地后 SRAM1 只剩 56.1%(**现 57.4%、余 55,840 B**),而 **CCM 被堆占掉 87.9%(只剩 7,904 B)**,待迁的 LVGL 池 + UI 两份缓冲 = **29,570 B**。
→ R-2 的剩余部分**只差"缩堆"这一步**(56→32 KB,`../内存管理/RAM使用分析与优化方案.md` §2.2;⚠ 2026-10-12 B5 是反向扩容,缩回前必须重跑 `stack_report.py` + 上板 `min_ever` 复测);顺序不可颠倒(`../内存管理/RAM预算台账.md` §3)。
⏸ **本簇已按用户裁定 2026-10-15 整簇暂缓**(§5.3)——这条诊断保留作将来重开时的现状基线,**不代表现在要动**。
2. **任务栈全部动态取自 FreeRTOS 堆**:`osThreadAttr_t` 只设 `stack_size` 不设 `stack_mem`(`Src/freertos.c`)→ 8 条产品任务栈合计 **9,792 words = 39,168 B**(另 testTask 2,048 words 仅测试构建)连同 idle/timer 栈、TCB、IPC 全部从堆里扣,**留给动态分配的实际余量很紧**(堆 56 KB 是 2026-10-12 B5 扩容后的值)。
→ **R-6(栈静态化)是把"池内碎片"变"可核算静态区"的正解,前置条件(观测常开 + `stack_report.py` 每任务最坏链)已满足。** 实时水位以串口 `[STACK]` / 自检打印为准,不在本文固化快照。
3. **MicroLIB 512 B C 堆是隐形地雷**:任何新代码写一个 `malloc` 都会中招。
→ `Heap_Size EQU 0x200` 未变、`.map` 仍 `Removing startup_stm32f407xx.o(HEAP), (512 bytes)`;铁律已进 AGENTS.md(只用 `pvPortMalloc/vPortFree` 或收进平台后端),但**没有机械化检查**——`check_coding_standard.py` 未含 `malloc(` 正则 → §5.5 第 5 条未落地。
### 5.3 RAM 优化路线(收益/风险/优先级)
> ⏸ **本表除已落地的 R-9 外整簇暂缓(用户裁定 2026-10-15)**,理由三条:
> ① **没有功能在等这块预算**——SRAM1 已降到 57.4%(余 55,840 B)、Flash 22.2%,离 `mem-budget` 的 92% 红线很远;
> ② **搬迁本身有风险和工时**——R-2 要动 scatter 并逐条过 DMA 可达性清单(规则 R2/R3,违例是**静默传错**不是报错),R-4 要重排 sink 与 LVGL 刷新的生命周期,R-6 静态化后栈溢出更难发现,缩堆还得上板复测 `min_ever`;
> ③ **收益与代价不对称**——腾出的 CCM 是给"以后可能有的功能",代价是现在动存储与显示两条主链。
>
> **保留常开的是治理面**(不是优化动作):`mem-budget` 92% 门禁、`../内存管理/RAM预算台账.md` 受管区、`scripts/ram_baseline.tsv` 趋势、`[STACK]` 水位与 `stack_report.py` / `ram_report.py`。它们是传感器,会在真正吃紧时报警。
>
> **重开条件(任一成立即重新评估,不必现在预防)**:
> ① `mem-budget` 逼近 92%,或某项**已定功能**的增量装不进现有余量——最可能先撞的是 **CCM**:只剩 **7,904 B**,而 FreeRTOS 堆实测 `min_ever` 仅 8,464 B(2026-10-15 台架),**加任务 / 抬栈 / 加堆都会先在这里撞墙**;
> ② 出现必须把 UI 帧缓冲搬离 SRAM1 才能做的显示功能;③ 送检或现场暴露 RAM 相关的稳定性问题。
| 编号 | 措施 | 收益 | 风险 | 优先级 | 状态 |
|------|------|------|------|--------|------|
| **R-2** | **启用 CCM 承载 LVGL 池与 UI 缓冲**:对 `work_mem_int` 与 UI 帧/绘制缓冲加段属性(`.sct` 已有 `RW_CCM`) | **−29,570 B**(R-9 后的待迁体量:16,384 + 6,420 + 6,766) | 中:必须确保这些缓冲**不被任何 DMA 直接寻址**。已核实 UI 发送路径 memcpy 进 CH395F 自有 SRAM1 DMA 缓冲,LVGL 为纯 CPU;**NAND 路径(规则 R2/R3)绝对不可迁入** | ~~P1(W3)~~ → **⏸ 暂缓** | ⏸ **暂缓(2026-10-15 裁定)**。现状仍是**只做一半**:`RW_CCM` 只收 `heap_4.o`(57,376 B)→ SRAM1 已减 49 KB+;剩余一步是缩堆 56→32 KB(CCM 只剩 7,904 B,29.5 KB 装不下) |
| **R-3** | 存储页缓冲合并:`nand_ftl` 三块 2 KB 中 `s_page_buf`(dhara scratch) 与 `s_copy_buf` 不可并发,可复用 | **−2 KB** | 中:需严格审查 dhara 调用序列(copy 期间不会用 map scratch) | ~~P2~~ → **⏸ 暂缓** | ⏸ **暂缓(2026-10-15 裁定)**;未办(`s_bbt_page_buf` 已单独收紧为 272 B) |
| **R-4** | UI 帧副本去重:同一 3,200 B 帧当前存 **4 份**(`g_ui_draw_buf` → `g_ui_fbuf` → `g_lcdmem_data` → `g_tx`)。让 `ui_flush_cb` 直接把 `color_p` 交给 sink、sink 发送时才拼帧头 | **−6.4 ~ −9.6 KB** | 中:需要 sink 与 LVGL 刷新生命周期对齐(同任务,可控) | ~~P1~~ → **⏸ 暂缓** | ⏸ **暂缓(2026-10-15 裁定)**;未办(`ui_port.o` 6,420 + `ui_net_sink.o` 6,766 = 13,186 B,副本数未变) |
| **R-6** | 任务栈改**静态分配**(`stack_mem` 指向各自 `.bss` 数组),从堆里释放 ≈39 KB | 堆可用 +39 KB(**总量不变**,但把"池内碎片"变成"可核算静态区") | 低-中:静态后溢出检测更难发现;先测水位再定尺寸 | ~~P2~~ → **⏸ 暂缓** | ⏸ **暂缓(2026-10-15 裁定)**。若将来重开,前置已满足:观测三面常开 + `stack_report.py` 已给出每任务最坏链 → 可直接按实测值定尺寸 |
> ✅ **R-9 已落地并销项(2026-10-15)**:`LV_MEM_SIZE` 40→16 KB(两份 `lv_conf.h` 同步,`lv-conf-sync` 生效值比对 163 项 PASS)。依据 = 池水位门禁实测(菜单基线 5,368 B / 开页-关页往返 5,448 B / 最重页面峰值 6,568 B = 池的 40%),**≈2.5× 余量**;PC 侧 11 页黄金参考逐字节比对 10/11 一致(唯一差异是 `netcfg_save` 保存态文本,A/B 复测证明与本次改动无关——真因是 `--cfgtest` 留在工作目录的 4000 B 伪造配置档触发了引擎"拒写超长文档"这条**正确**行为,同日已修,见 `../UI设计/UI_自动化测试流程.md` §五)、往返池水位 5,448 B **分毫未变**;`@build` 0 错 0 警,ZI 精确 −24,576 B。**真机复跑 ✅(2026-10-15 `@test` PASS)**:`menu build done: total=15020 free=11828 used=3192`——菜单根框只占建池后 15,020 B 的 **21%**(TLSF 元数据 ≈1,364 B),全程无 `[ERR]`/Fault。再往下试 8 KB 需先跑全量 UI 回归(PC + 真机),**不在本条范围**。
> ⚠️ **R-2 与 R-4 不可叠加计算**:R-4 省的是"副本份数",R-2 已把这四份缓冲整体搬到 CCM,因此 **R-4 在 R-2 之后省的是 CCM 而非 SRAM1**(CCM 侧 13,186 B → ≈6.8 KB,为后续 UI 扩展腾空间)。若只做 R-4 不做 R-2,则直接为 SRAM1 省 6.4 KB——两条路线对 RAM 总量等价,**但顺序决定收益落在哪个区域**。
>
> **R-5 已判定不可行(原建议 `configMAX_PRIORITIES` 56→32 省 480 B)**:CMSIS-RTOS2 包装层 `freertos_os2.h:293` 有 `#if (configMAX_PRIORITIES != 56) #error`——改小直接编译失败。为 480 B 去改第三方包装层不划算,**本条关闭**(登记簿 N-27 的评估清单已据此剔除 R-5)。
与 `../内存管理/RAM使用分析与优化方案.md` 的关系:本表**沿用其阶段划分**(阶段 0 测量 / 阶段 1.1 池收缩 / 阶段 1.2 CCM / 阶段 2 堆收缩),本报告新增的是 ①R-4 帧副本去重(原文档未识别 4 份副本)②R-3 存储侧缓冲合并 ③**DMA 可达性硬边界**(原报告 N-11 提出,现已沉淀为 `../内存管理/内存内容分配策略.md` §4 规则 R2/R3,属实施风险关键点)。
### 5.4 Flash 优化(未闭环:F-4 / F-5 / F-6)
> **F-1 / F-2 / F-3 已落地并销项(2026-10-11)**:两份 `lv_conf.h` 对齐为同一特性子集 → **实测省 64,576 B**,Flash 26.3% → 20.1%(现 22.2%,增量来自 B4/B5 功能代码)。完整记录见 `../内存管理/RAM使用分析与优化方案.md` §2.6.4 与 `../UI设计/UI_设计架构.md` §1.4 第 10 条。
| 编号 | 措施 | 可省 Flash | 依据 | 状态 |
|------|------|-----------|------|------|
| **F-4** | 关闭未用的绘制子模块与 `LV_USE_SNAPSHOT`(MCU 端)/`LV_USE_OBJ_PROPERTY`/`LV_USE_FLOAT` 等 | 可评估部分 **≈10 KB** | 对象实测:`box_shadow` 3,246 + `draw_sw_img` 2,960 + `bin_decoder` 1,972 + `image_decoder` 952 + `triangle` 930 ≈ **10.1 KB**;工程无图片、无阴影、无变换 | ❌ 未办。⚠ **不可盲目关**:`mask` 3,862 / `mask_rect` / `grad` 是文字与矩形绘制的必经路径 |
| **F-5** | 从 uvprojx 中**移除未使用的 LVGL 控件源文件**(201 个 → 实际需要的子集) | 编译时长 −40% 左右 | 与 F-2 配合;`lv_conf` 关开关后文件仍参与编译,删文件才省编译时间 | ❌ 未办(uvprojx 现挂 **323 个 `.c`,其中 LVGL 仍 201 个**)。⚠ F-2 落地后这些 TU 现在编出来是**空目标文件**,删文件的收益更纯粹、风险更低 |
| **F-6** | 双精度软浮点:`dadd`/`dmul`/`ddiv`/`depilogue`/`dfixul`(`mf_w.l`),来自 `printf` 的 `%f`/double 提升 | −1 KB + 运行期性能 | 日志与 UI 全用定点;把 `%f` 改整数刻度 | ❌ 未办(新镜像仍链入 `dadd.o`) |
**剩余可回收 ≈ 10–11 KB Flash**(F-4 可评估部分 + F-6);F-5 省的是编译时长不是 Flash。
### 5.5 内存治理长效机制
> **已闭环(2026-10-12,登记簿 N-27 前半 / W1-0)**:① 超阈值即失败的 **`mem-budget` 钩子**(`ram_baseline.py --gate`,**92% 红线** + Top-10 RW+ZI 附表,无 `.map` 自适应跳过)② 趋势基线 **`scripts/ram_baseline.tsv`**(`--update` / `--trend`)③ **`../内存管理/RAM预算台账.md`**(子系统配额 + CCM 分配台账 + 超配额流程)。
> **阈值与配额的权威值现在只在那份台账里**(本报告不再重复数值);`mem-numbers` 钩子继续防"文档数字漂移"。
>
> **已闭环(2026-10-11,登记簿 N-26)**:**DMA 可达性清单与规则族**——`../内存管理/内存内容分配策略.md` §3「全板 DMA 使用方清点」+ §4 规则 R1~R4(含 **R3 堆/栈禁为 DMA 源/目的**),落地为编译期窗口断言 + 启动期自检 + 运行时守卫三层(见 §4.3)。原"独立清单文档 + `_Static_assert`"的建议形态**已被这份更好的实现取代**。
仍开放一条:
5. **禁止新增 C 库 `malloc`**:写进 `docs/代码规范/`,并在 `check_coding_standard.py` 加正则检查(`App/`、`Drivers/` 内出现 `malloc(` 即告警)。
→ **🟡 前半已做、后半未做**:AGENTS.md 已有铁律(含模拟器照不出来的原因);❌ `check_coding_standard.py` **未含 `malloc(` 正则** → 目前只靠人评审,属机械检查缺口。
---
## 6. UI 界面标准化实现方案
> ⚠ **本节"建议 / 目标结构 / 草图"不可照抄——落地前必须逐条对代码核实**(2026-10-15 立此口径)。
>
> 本节是**体系评审视角的建议**,不是实现规格。启动 W6 后按 §6 实际执行 U1 / U3 / U4 三包,**四次证伪**:
>
> | # | 报告写的 | 代码实况 | 照抄的后果 |
> |---|---|---|---|
> | 1 | §6.3 L0 清单要新建 `UI_SCREEN_W/H` | `ui_port.h:21-22` **早有同名宏** | 同名宏二次定义 |
> | 2 | `UI_COL_FG = LV_COLOR_BLACK` / `UI_COL_BG = LV_COLOR_WHITE` | LVGL 9.3 **无这两个宏**(只有 `lv_color_black()/white()` 函数);且**极性反了**——代码里所有墨色恒用 `lv_color_white()`,选中行才是黑字 | 先编译失败,改完若按报告极性则**整屏反色** |
> | 3 | §6.5 U1 行"27 页未落地" | 现存 `ui_page_*.c` 实测 **7 个**;27 是**目标页数**不是文件数 | 工作量与拆包按错数排 |
> | 4 | §6.3 生成器草图 `@page=deadzone` | **密码页不是节点绑定的**(任何 `#P` 叶子进同一页),注册表必须单独出口 | 做不出能进密码页的注册表 |
>
> 另有 §6.3 L0 清单 **9 条取值/存在性错误**(行距 13→实为 14、列表顶 15→实为 24、滚动条宽 2→实为 4、`UI_PAD_X 3` 不存在、`UI_ROW_H 12` 不存在、`UI_TITLE_H 11` 不存在、`UI_STYLE_FLAT` 在 U1 阶段做不了……)**逐条明细见 §6.3 更正块**,此处不重复。
>
> **执行规则**:① 按本节动手前,先 grep 代码核一遍建议值与建议结构是否已存在、取值是否对;② 发现不符**一律以代码为准**,不要"先按报告做再改";③ 把差集**回报并记进本节**,不要静默修正(否则下一个人还会再踩);④ 本节"发现"栏(现状描述、问题定位、成本量化)经复核**仍然可信**,不可照抄的是**建议栏**。
>
> **同一规律在本报告之外也复现过**:§4 的 N-13 建议用了本仓裁剪版 dhara 里**不存在**的 `DHARA_E_UNSUPPORTED`(照抄编译失败)。所以这不是 UI 章节的局部问题,而是**"建议"这一体裁的通病**——写建议时没有对代码核。后续任何按本报告排期的工作包,任务卡都应显式写一句"以代码实况为准,报告建议值不可直接采用"。
### 6.1 现状(框架已有,但未"闭环")
| 层 | 现状 | 评价 |
|----|------|------|
| 内容页 | 描述符 `content_page_def_t` 仍是**唯一**页面描述符(刻意不新建第二套);节点→页面映射由**生成器产出** `ui_page_registry.c`,`{node, desc}` 表 + `ui_page_registry_for()` + `ui_page_registry_password()` | ✅(2026-10-15 U3)**手写 `menu_content_page_for()` 已删除**,仓库内仅存 2 处历史说明注释;密码页按代码实况**单独出口**(非节点绑定),不进 `node→desc` 映射 |
| 设计令牌 | `Drivers/BSP/UI/ui_tokens.h` **已建**(2026-10-15 U1,20 个宏),框架 + 7 页 + `App/task/ui_task.c` 全部接入;**屏幕几何复用 `ui_port.h` 既有 `UI_SCREEN_W/H`**,不重复定义 | ✅ 纯替换验收:`@build` 0/0 且 Program Size 逐字节不变、golden 逐像素一致、池水位未变。⚠ 落地时修正本报告 L0 清单 9 处取值/存在性错误(见 §6.3 更正块) |
| golden 回归 | `simulator/tests/` 四脚本:`check_menu_rows.py`(菜单 8 状态全像素)、`check_content_pages.py`(内容页黄金参考逐字节 + 文本断言 + **LVGL 池水位门禁**)、`regen_ref_png.py`、`check_remo_sink.py` | ✅ **已入自动门禁(2026-10-20)**:新钩子 `ui-golden` = 增量构建模拟器 + 两条 golden 串成一条,实测改一个 UI 源文件后合计 2~4 秒;有牙实测 = 注入 3 px 位移 → FAIL 并点名页面与差异像素数 |
### 6.2 新增一个页面要走多少步(当前)
1. `ui_menu_tree.txt` 加节点 → 2. 重跑 `gen_menu.py` → 3. 新建 `ui_page_xxx.c` → 4. 在 `ui_content_page.c` 的枚举/`s_defs[]` 注册 → 5. 在 `ui_menu.c:menu_content_page_for()` 手写 `case N:`(**下标硬编码,表序一变即错**)→ 6. 注册到 `MDK-ARM/*.uvprojx` → 7. 注册到 `simulator/CMakeLists.txt` → 8. 新汉字跑 `font-regen`。
**8 步里 5 步是纯机械劳动且 2 处易错(步骤 5 的魔法下标、步骤 6/7 漏注册)。** 步骤 6/7 如今有 `registration-check` 钩子兜底(存量走白名单),但**新页面落在新目录而没把目录加进 `uvprojx` 的 `<IncludePath>`** 时,报错落在**消费者 TU** 上(`cannot open source input file`),仍不等于"存在但未注册"的检查。
### 6.3 标准化目标:四层 + 一个闭环
```mermaid
graph LR
A[L0 设计令牌<br/>ui_tokens.h] --> B[L1 组件库<br/>ui_w_* 自绘控件]
B --> C[L2 页面原型<br/>list / detail / form / records]
C --> D[L3 页面实例<br/>ui_page_xxx.c 仅填数据]
D --> E[生成器 gen_page_registry.py]
E --> F[ui_page_registry.c<br/>菜单节点→页 映射]
F --> G[门禁: check_registration + golden]
```
**L0 设计令牌(1 天,零行为变化)** — 新建 `Drivers/BSP/UI/ui_tokens.h`:
```c
/* 屏幕与骨架 */
#define UI_SCREEN_W 160
#define UI_SCREEN_H 160
#define UI_TITLE_Y 2
#define UI_TITLE_H 11
#define UI_PAGE_LINE_Y 143 /* 页脚分隔线 */
#define UI_ROW_PITCH 13 /* 行距(12px 字体 + 1) */
#define UI_ROW_H 12
#define UI_LIST_TOP 15
#define UI_SCROLLBAR_W 2
#define UI_PAD_X 3
#define UI_BUF_STRLEN 32 /* = CP_ROW_BUF_LEN,统一一处 */
/* 语义色(1bpp:只有黑白,但保留语义名以便换深度时平移) */
#define UI_COL_FG LV_COLOR_BLACK
#define UI_COL_BG LV_COLOR_WHITE
#define UI_COL_SEL_FG UI_COL_BG
#define UI_COL_SEL_BG UI_COL_FG
/* 样式:只用 const 描述符(动态 lv_style 会每次泄漏 88 B,见 ui_content_page.c) */
> ⚠ **上面这份 L0 清单与代码实况有 9 处不符,U1 落地时已按代码为准修正**(2026-10-15 实测,`ui-tokens` 报回)。**照抄会翻车的两条**:
> ① **`UI_SCREEN_W/H` 不是新建**——`ui_port.h:21-22` 早有同名宏,在 `ui_tokens.h` 再定义一次就是同名宏二次定义;实际做法是 `ui_tokens.h` 只 include `ui_port.h` 复用,并把散落的 7 处 `160`(`CP_/MENU_/SB_/DECO_/PW_SCREEN_`、推流协议 `UI_W/H`)收敛到这一个来源。
> ② **`UI_COL_FG = LV_COLOR_BLACK` / `UI_COL_BG = LV_COLOR_WHITE` 两处都错**——本仓 LVGL 9.3 **没有** `LV_COLOR_BLACK/WHITE` 宏(全库 0 处定义),只有函数 `lv_color_black()/lv_color_white()`,照抄直接编不过;而且**极性反了**:代码里所有墨(文本 / 分隔线 / 边框 / 滚动条)恒用 `lv_color_white()`,选中行才是黑字。按本报告写会把整屏反色。落地定义:`FG=white`、`BG=black`、`SEL_FG=black`、`SEL_BG=white`。
>
> 其余 7 条是取值/存在性错误(报告值 → 代码实况):`UI_TITLE_H 11` → 代码无此值,真实重复量是 `+15` 文本条带(建为 `UI_TEXT_BAND_H 15`);`UI_ROW_PITCH 13` → 列表行距实为 **14**(13 是字体行高/选中带高);`UI_ROW_H 12` → 代码无 12 作行高,选中白带高 **13**(`UI_ROW_BAND_H`);`UI_LIST_TOP 15` → 列表首行顶实为 **24**;`UI_SCROLLBAR_W 2` → 实为 **4**;`UI_PAD_X 3` → 不存在"统一内边距 3",那个 3 是选中白带**右内缩**(建为 `UI_SEL_BAND_INSET_R 3` 配 `_L 2`);**`extern const lv_style_const_prop_t UI_STYLE_FLAT[]` 在 U1 做不了**——共享 `extern` 数组需新增 `.c` 落定义 → 必须双端注册(U1 禁碰注册文件),退化成头里 `static const` 又会在"包含但未使用"的 TU 上招 `#177-D` 破 0 警,已并入 U3。
>
> **教训**:本报告的 UI 章节是**体系评审视角的建议**,不是实现规格。落地前必须逐条对代码核一遍——本次 9 条里若照抄,两条编译失败、一条整屏反色。U3/U5 执行时同样以代码为准,发现与本节不符**直接按代码做并在回报里列出**。
>
> ⚠ **U3 开工侦察又补两条**(2026-10-15,`ui-tokens`):① **密码页不是节点绑定的**——任何 `#P` 叶子都进同一个密码页,所以注册表除 `node→desc` 映射外**必须单独出口一个"密码页"入口**;本节生成器草图的 `@page=deadzone` 没覆盖这条,照抄会做不出能进密码页的注册表。② **`ui_w_form` 从 U3 推迟到 U5**(Lead 裁定):全仓只有 **N=1** 个表单实例(`ui_page_net_cfg.c`),且它的"表单"实质是"5 行地址列表 + 逐字节编辑光标"、**并未走配置引擎的段注册表**,与本节设想的"cfg 段/键绑定 + 十进制/IP 输入约束"**并不同形**。从单一实例反推接口等于猜,而这是要动像素的重构——**重复三次再抽象**。U3 范围因此收敛为「注册表 + 删手工分发 + 共享 flat 样式」三件(这才是 U5 能并行的真前提);`ui_w_form` 等 U5 有 2~3 个真实 form 实例可对照时再抽。
> 同批裁定:**不新建 `ui_page_desc_t`**——代码里 `content_page_def_t`(title/count_cb/refresh/draw_cb/key_cb/edit_capable…,7 个页面全在用)已是唯一描述符,再造一套等于一个页面两套描述符;注册表只加 `ui_page_entry_t{node, desc}` 做节点绑定。**不加 archetype 枚举**(无消费者的元数据,真正需要它的是 U5 的"由原型自动实例化页面")。
extern const lv_style_const_prop_t UI_STYLE_FLAT[];
```
要点:**保留现有"只用 `lv_style_const_prop_t` 常量样式"的纪律**(这是已被验证的必要约束:动态样式每个 88 B 泄漏);令牌化后所有页面自动获得统一观感,改一行即全局生效。
**L1 组件库(3 天)** — 把 `ui_menu_view.c` / `ui_content_page.c` 中重复的自绘逻辑抽成无状态组件(纯 `lv_layer_t` 绘制,不创建 LVGL 对象,保持零控件、零池压力):
| 组件 | 职责 | 从何处抽取 |
|------|------|-----------|
| `ui_w_title` | 标题栏 + 分隔线 + 页码 | `ui_content_page.c` 通用绘制链 |
| `ui_w_list` | 行列表 + 选中反白 + 滚动条 | `ui_menu_view.c` 行绘制 |
| `ui_w_kv` | "名称: 值" 两列(装置信息/实时数据) | `ui_page_device_info.c`、`ui_page_ac.c` |
| `ui_w_records` | 定长记录分页浏览(含 `s_cache_valid/s_last_rec` 缓存范式) | ✅ **已建成(2026-10-20 U2)**:运行/自检记录两页按实例复用,两页净 −62 行、逻辑单源;缓存内嵌实例而非组件级静态(共享会致切页读到另一页的陈旧行)。`@build` 0/0 + golden 11 页逐字节一致 + `stack_report` FAIL 0;`ER_IROM1` −116 B、`RW_IRAM1` +8 B(`.id` 有初值使实例落 .data 而非 ZI,非误差) ||
| `ui_w_form` | 键值 + 光标 + 可编辑(含十进制/IP 输入约束) | `ui_page_net_cfg.c` |
| `ui_w_msgbox` | 确认/错误提示("密码输入错误"等) | `ui_page_password.c` |
**L2 页面原型(4 类,覆盖全部剩余页面)**:
| 原型 | 适用菜单 | 数据契约 |
|------|---------|---------|
| `detail`(KV 只读) | 装置信息、交流量、直流量、遥信量 | `kv_provider_t{ count(); get(i, name, val); }` |
| `form`(可编辑) | 整定/定值/控制字/软压板、遥测死区/系数、遥信类型/防抖/双点、装置维护、通讯参数、网口/SNTP | 直接绑定 `cfg_file` 描述符(**复用配置引擎的段注册表,无需新数据结构**) |
| `records`(分页日志) | SOE/事故/操作/保护告警/保护启动/遥控/自检/运行记录/运行报告 | `record_provider_t{ count(); get(idx, ts, text); }`(sys_log 已是这个形态) |
| `action`(确认型) | 强制复归、清除记录、手动录波 | 提示 → 二次确认 → 执行 → 结果日志 |
**口径**:`ui_menu_tab.h:28 UI_MENU_TAB_COUNT` = 42(根 + 41 节点),**叶子实为 33 个**;已有内容页的叶子 **6 个** → 待落地 **27 页**。四类原型的分布按 27 页重算即可。这意味着**剩余页面工作量从"27 页 × 各自设计"降为"4 个原型 + 27 份数据绑定"**。
**L3 页面实例** — 目标形态(以"遥测死区"为例,全部新代码约 30 行):
```c
static const ui_form_item_t s_items_deadzone[] = {
/* cfg 段/键, 显示名, 类型, 最小, 最大, 权限 */
{ "deadzone", "ia", "A 相死区", UI_T_UINT, 0, 1000, UI_PERM_MODIFY },
{ "deadzone", "ib", "B 相死区", UI_T_UINT, 0, 1000, UI_PERM_MODIFY },
{ "deadzone", "ic", "C 相死区", UI_T_UINT, 0, 1000, UI_PERM_MODIFY },
};
const ui_page_desc_t ui_page_deadzone = UI_FORM_PAGE("遥测死区", s_items_deadzone);
```
**生成器补全(1.5 天,关键收益)** — 扩展 `ui_menu_tree.txt` 支持页绑定注解,让 `gen_menu.py` 一并产出注册表,**消灭步骤 4/5 的手工 switch**:
```
遥测死区 #P @page=deadzone @arch=form
```
产物:`ui_page_registry.c` 含 `node_index → (page_id, archetype, desc*)` 常量表 + `ui_page_registry_for(node)`;`ui_menu.c` 只需一次调用。生成器同时校验:`@page` 指向的描述符是否存在、是否有节点声明了页但无实例、是否有实例未被任何节点引用(编译期断言或生成期报错)。
**闭环门禁(1 天)**——未闭环 2 条:
1. **golden 回归入门禁**:`simulator/tests/` 四脚本已建成,但未挂进 pre-commit / `ui-verify`。
2. **字符集自动校验**:构建前扫描所有 `ui_*.c` 中的汉字,与 `ui_cn_chars.txt` 求差集,缺失即 FAIL(把 `font-regen` 从"记得跑"变成"漏跑就红")→ ❌ 未入门禁(`font-regen` skill 已把四步固化并写进 AGENTS.md,但**仍是约定不是门禁**)。
### 6.4 UI 侧的内存纪律(必须写进规范)
| 规则 | 依据 |
|------|------|
| 页面**不得**新增静态大缓冲;文本缓冲统一用 `UI_BUF_STRLEN` | 现有 `s_ts/s_desc` 已是范式 |
| 每页**只用** `lv_style_const_prop_t`,禁止运行期 `lv_style_init` | 已验证动态样式每次泄漏 88 B |
| 优先自绘(`lv_draw_*` + `lv_text_get_width`),新控件需评审(会破坏 §5.4 F-2 **已落地**的裁剪,且须两端同步) | LVGL 现占镜像 **37.8%**(87,706 / 232,280 B) |
| 帧缓冲任何改动必须遵守 R-4(副本数 ≤ 2) | 当前 4 副本 12.8 KB |
| `LVGL 池`用量必须经 `lv_mem_monitor` / 池水位门禁实测后才允许扩大 | **16 KB**(R-9 于 2026-10-15 由 40 KB 降下;实测峰值 6,568 B = 池的 40%,余 ≈9.8 KB) |
### 6.5 实施顺序(UI 专项,总计约 8 人日)
| 步骤 | 内容 | 交付判据 | 状态 |
|------|------|---------|------|
| U1 | `ui_tokens.h` 令牌化(纯替换,不改行为) | `@build` PASS + 模拟器截图与替换前逐像素一致 | ✅ **已落地(2026-10-15)**:`ui_tokens.h` 20 个宏 + 15 个文件接令牌。**零行为变化证据**:`@build` 0 错 0 警且 `Program Size` 与改前**逐字节相同**(Code=213540 / RO=18740 / RW=1136 / ZI=131520);golden 菜单 8 状态全像素一致 + 内容页 **11 页逐字节一致**,且比对对象是改前备份的 sha256 基线(**不是 `--update` 刷出来的**);池水位 5368 / 5448 B 未变。⚠ 落地时修正本节 L0 清单 9 处与代码不符(见上) |
| U2 | `ui_w_records`(合并 runlog/selftest) | 两页行为不变,代码 −1 个文件 | ✅ **已落地(2026-10-20)**:见本节 `ui_w_records` 行。两页各留一个实例 + 两个薄跳板,`.count_cb/.refresh` 名不变 => 注册表与生成器零改动 ||
| U3 | `ui_w_form` + `ui_page_desc_t` 原型化 + 生成器产注册表 | 手工 `menu_content_page_for()` 删除;新增一页只需"改树 + 加实例文件" | ✅ **已落地(2026-10-15)**:`ui_page_registry.{c,h}` + `ui_style.{c,h}`(原"U1 做不了"的共享 flat 样式一并落)+ 扩 `gen_menu.py` 产注册表**并自动写 `uvprojx`/`CMakeLists.txt`**;手工 `menu_content_page_for()` 已删。**实测单位成本:新增一页 = 3 处人工**(新 `.c` + 新 `.h` + 菜单树那一行加 ` #page=<名>`)**+ 1 条生成器命令,手工编辑注册文件 0 处**;生成器**双向对账**(增 `registered` / 删 `pruned`)且双向校验死代码。验收:`@build` 0/0、golden 菜单 8 状态 + 内容页 11 页逐字节一致、池水位 5368/5448 B 未变、`ER_IROM1` 232,052 B(较已提交 HEAD −228 B)、SRAM1 零增量。⚠ **本报告原口径"8 处降到 1 处"须修正**:那个 1 只在"1 处对既有文件的修改"意义下成立,页面自身 2 个文件本就要写,真实改动处数是 **3** |
| U4 | golden + 字符集检查 + 注册检查入 pre-commit | 三个新 hook 全绿 | ✅ **3/3 落地(2026-10-15 起,10-20 补齐)**:`font-charset` 新钩子(字符集差集,防缺字白框)+ `registration-check` 补第 ④ 类"UI 页面描述符表接线"检查。钩子数 34 → **35** 。钩子数 34 → **35**。⚠ **修正我自己上一版的错**:此处原写"3/3 落地",是把"golden 脚本早已存在"误当成"golden 已进自动门禁"。实况是**两个 golden 脚本至今未挂 pre-commit**(一直在人工跑,无自动门禁),故当时改回 **2/3**。⚠ **10-20 已补齐第三件**:新钩子 `ui-golden` 把"增量构建模拟器 + 两条 golden"串成一条自动门禁(实测 2~4 秒、注入 3 px 位移可 FAIL 并点名页面),故 3/3 此刻才真正成立——**这次是真的进了自动门禁,不是"脚本存在"**。⚠ **`--strict` 的验收口径已改**:判据是"**未知**冗余 = 0"而非"冗余 = 0"——`ui_cn_chars.txt` 里 异/常/片/芯 4 字属**刻意保留**(2026-09-14 `9eb1bd63` 写入的中文自检描述曾落盘 `/log/selftest.log`,定长 64 B 记录跨固件持久,删字形会使历史故障行渲染空白),按 `check_registration.py` 的 `KNOWN_GAPS` 同构做法进带理由的保留清单;上/电/重 3 字已证从未出现在任何字符串字面量(仅注释),照删。为几百字节牺牲已落盘审计记录的可读性不划算——同一权衡口径见 §5.3 内存簇暂缓裁定 |
| U5 | 用原型批量落剩余 **27 页**(每页 ≤30 行) | 菜单树全绿、无 `CONTENT_PAGE_COUNT` 哨兵命中 | ❌ **暂缓(等页清单)** | ⚠ **U5 暂缓(2026-10-20 用户裁定)**:剩余页面**尚未规划**,本轮不补页 —— 现存 7 个 `ui_page_*.c` 即当前全部内容,报告"27 页"是目标数不可当差集清单。U1–U4 已全部落地,故 U5 未来开工时是纯增量活(实测新增一页 = 3 处人工 + 1 条生成器命令,且 `ui-golden` 已在提交门禁兜像素)。 |
> ✅ **2026-10-15 用户裁定启动**(覆盖 2026-10-11 的「本轮不排期」,登记簿 U 簇 / W6):已开智能体团队推进。**并行度受注册文件约束**——新增一个 `.c` 必须同时改 `MDK-ARM/STM32F407-Demo.uvprojx` 与 `simulator/CMakeLists.txt` 这两个共享文件,所以任何时刻只允许一个成员碰它们。实际排法:**第一波 U1(令牌层,不新增 .c)∥ U4 残余(只动 scripts/ 与门禁文档)真并行** → U3(原型化 + 生成器,独占注册文件,串行段)→ U2 → U5(页面批量,须等 U3 的生成器把注册自动化,否则无法并行)。⚠ 本节原写「27 页」是**目标页数**,现存 `ui_page_*.c` 实测只有 **7 个** → U5 开工前先对齐目标页清单差集。
---
## 7. 统一改进路线图
> **顺序的唯一权威 = [`../任务规划/README.md`](../任务规划/README.md) §2**(W1 采集保护收尾 → ~~W2 数据正确性~~ **✅ 已清空销项 2026-10-15** → ~~W3 预算与内存~~ **⏸ 已整簇暂缓** → W4 结构债 → W5 验证常态 → W6 UI 扩展 → W7 仓库决策;B2 RS-485 暂缓)。本节只保留"本报告条目 → 波次"的映射,不另立顺序。
| 波次 | 本报告条目 | 验收要点 |
|------|-----------|---------|
| ~~**W3 预算与内存**~~ ⏸ | **整簇暂缓(用户裁定 2026-10-15)**:R-9 ✅ 已落地不回退;R-2 后半 / R-3 / R-4 / R-6 不再排期 | 暂缓期间**无验收动作**;`mem-budget` 92% 门禁与台账、趋势、`stack_report.py` / `ram_report.py` 观测**照常跑**(它们是判断何时重开的输入)→ 重开条件见 §5.3 |
| **W4 结构债** | N-14、F-4/F-5/F-6、S-1/S-2/S-4/S-5;`rs485_task.c` 随 B2 出注册白名单 | `--strict` 默认化;`bsp_err_t` 进规范 |
| **W5 验证常态** | P1-10 收尾(构建实测链接 + `@test` 闭环) | 逢窗口顺手做 |
| **W6 UI 扩展** | U4 残余 → U1 → U2/U3 → U5(27 页) | 新增一页 ≤30 行;**2026-10-15 启动**(见 §6.5 注) |
| **W7 仓库决策** | D-1(登记簿 N-29) | **已决策暂不处理**;推送/协作前重评 |
### 交付物清单(可直接排期)
| 交付物 | 类型 | 来源条目 | 状态 |
|--------|------|---------|------|
| `test/host/nand_port/`(dhara 移植层单测) | 主机侧单测 + pre-commit hook | N-12/N-13 | ✅ **已建(2026-10-15,提交 `720f841`)**——原写 `test/host/dhara/` 是拟名,落地为 `nand_port/`。149 断言 / RAM 假芯片 / 真实 dhara map+journal 源 / 含"旧算法只看页首 64 B"**对照用例**,已挂为第 16 个 `-unit-test` 钩子。**这条正是"防同类缺陷再犯"的手段,现已到位** |
| `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 | ❌ 未建 |
| `check_coding_standard.py` 加 `malloc(` 正则 | 门禁补强 | §5.5 第 5 条 | ❌ 未建 |
| `scripts/check_font_charset.py`(字符集差集)+ `registration-check` 第 ④ 类 UI 接线检查 | 门禁补强 | U4 残余 | ✅ **已建(2026-10-15)**——提取与 cmap 解析复用 `tools/font_gen/` 同一套规则(`importlib` 直载,零第二套规则);牙齿实测三例(缺字 / 字体缺码位 / 稀疏 cmap 缺偏移)均 FAIL 并点名到具体字符 |
---
## 附录 A 关键证据索引
| 结论 | 证据(2026-10-15 `.map`) |
|------|---------------------|
| SRAM1 57.4%、余 55,840 B;CCM 87.9%、余 7,904 B | map `Execution Region RW_IRAM1 Size: 0x00012600, Max: 0x00020000` / `RW_CCM Size: 0x0000e020, Max: 0x0000ff00`;`ram_baseline.tsv` 末行 |
| MicroLIB C 堆 = 512 B | `MDK-ARM/startup_stm32f407xx.s` `Heap_Size EQU 0x200`;map `Removing startup_stm32f407xx.o(HEAP), (512 bytes)`(未被引用 → 整段移除) |
| 任务栈全部动态分配 | `Src/freertos.c` 的 `osThreadAttr_t` 无 `.stack_mem`;8 条产品任务栈合计 9,792 words(R-6 未办) |
| DMA 可达性三层防线(原 N-11 的处置) | 编译期窗口断言 `gd5f2gq5ue.h:96-107`(负数组 typedef)→ 启动期自检 `nand_ftl.c:241-243` → 运行时守卫 `gd5f_dma_buf_ok()`(`gd5f2gq5ue.c:121-139`);规则族 R1~R4 → `../内存管理/内存内容分配策略.md` §4;负路径用例 TC-GD5F-704/705 |
| CH395F memcpy 自有缓冲 | `ch395f.c` SPI2 DMA 收发缓冲(`s_spi2_dma_tx/rx[2052]`)→ UI 缓冲仍可迁 CCM 的前提成立 |
| dhara 两处缺陷 | `nand_ftl.c:152-179`(`uint8_t buf[64]`)/ `:204-216`(read→prog 直传,无哨兵判断) |
| 日志逐条开关 | `sys_log_mcu.c:124-141`(`FA_WRITE | FA_OPEN_APPEND` → `f_write` → `f_close`,失败重建重试一次);`wdev.log` 同模式 `:486-489` |
| 端口分配 | `App/sync/time_sync.c` `TIME_SYNC_PORT 8888`;`Drivers/BSP/UI/ui_net_sink.h` `UI_NET_SINK_PORT 7003`;FTP 21 |
| 菜单 42 节点 / 33 叶子 / 6 个有内容页 | `ui_menu_tab.h:28 UI_MENU_TAB_COUNT 42`;`ui_content_page.c:84-92 s_defs[]`(7 页含 PASSWORD) |
| 手写映射 | `ui_menu.c:118-128 menu_content_page_for()`(`case 1u/3u/4u/30u/39u/40u:`) |
| 注册反向校验已机械化,仅剩一条刻意缺口 | `scripts/check_registration.py`(`KNOWN_GAPS` 只含 `App/task/rs485_task.c`,E-59/B2 刻意未接线)+ pre-commit `registration-check`;`lftpd_log.c` 已删除并入 `lftpd_auth.c` |
| 仓库体量 | 受控 2,466 文件 / 79.3 MB;`docs/` 56.9 MB;`docs/芯片手册/` 图片 703 张 / 36.3 MB;`.git` **235.3 MB** |
## 附录 C 评审中确认"无需改动"的设计(避免后续误改)
| 项 | 结论 |
|----|------|
| CH395F SPI 分频 = 8 | **PCB 约束,禁止"优化"**(AGENTS 铁律,实测一致) |
| 就绪屏障用 Event Flags 而非魔法延时 | 正确,保留(就绪位必须 `osFlagsNoClear`) |
| FatFs `FF_FS_REENTRANT=1` + 真实互斥 | 正确,保留(多数移植偷工) |
| 配置引擎属主 = defaultTask | 正确,任何新代码不得跨任务触碰 |
| UI 只用 `lv_style_const_prop_t` | 正确且必要,禁止"顺手改成动态样式" |
| `ffsystem.c` 引用 `sys_clock.h` | 有意为之(中间件需要真实时间),保留但在分层图中显式标注 |
| UI 自绘而非 LVGL 控件 | 正确(1 bpp + 小 RAM),并是 Flash 裁剪的前提 |
| `configMAX_PRIORITIES = 56` | **不可下调**:CMSIS-RTOS2 `freertos_os2.h` 硬性 `#error` 要求等于 56(原 R-5 建议因此关闭) |
| 两份 `lv_conf.h` 同一特性子集 | 必要(F-1/F-2/F-3 的前提),由 `lv-conf-sync` 钩子守 |
---
**报告结束。**
> 本报告按 `../README.md` 的定位是**体系层评审报告,不是待办池**——待办与顺序一律以 [`../任务规划/README.md`](../任务规划/README.md) 为准;本报告只保留**未闭环问题的发现 + 证据**,修复即销项。
---
## 变更记录
| 日期 | 类型 | 说明 |
|------|------|------|
| 2026-09-18 | 初版 | 全量审查;基线 `HEAD = 2d0ec35`、`.map` 链接于 2026-09-16;产出 N-01..N-18 / D-1..D-7 / S-1..S-5 / R-1..R-9 / F-1..F-6 / U1..U5 编号体系 |
| 2026-10-11 | 实施 + 销项:F-1/F-2/F-3 | 两份 `lv_conf.h` 对齐同一特性子集 → 省 64,576 B;验证链(`@build` / ui-verify / `@test`)与对象级对照见 `../内存管理/RAM使用分析与优化方案.md` §2.6.4 |
| **2026-10-15** | **登记簿同步销项** | 本报告的 N-03c / N-11 对应登记簿 **N-20 / N-26** 两行已按「完成即销项」从 `../任务规划/登记簿.md` §5 删除(并清 `../任务规划/README.md` §1/§2 与 `排期详述.md` §1 的引用,W4 链由 6 项减为 5 项、结构/健壮性计数 9→7)。删除前补齐去处记录:`../子系统设计/看门狗设计说明.md` §4.4 新增 `Error_Handler` 统一降级一节、`../内存管理/内存内容分配策略.md` §4 R2 补编译期窗口断言与启动期自检两层 + §8 落点索引两行。同批订正登记簿三处过时内容:N-23 残余去掉已删除的 `lftpd_log.c`、N-25 去掉已删除的 `NET_RECV_TIMEOUT_MS`、N-27 剔除不可行的 R-5 并把 CCM 余量改为实测 7,904 B、N-29 的 `.git` 由 149 MB 订正为实测 235.3 MB |
| **2026-10-15** | **执行本报告条目:R-9 落地并销项** | `LV_MEM_SIZE` 40→16 KB(`Lib/lv_conf.h` + `simulator/lv_conf.h` 同步,`lv-conf-sync` 生效值比对 PASS)。**依据**:池水位门禁实测菜单基线 5,368 B / 开页-关页往返 5,448 B / 最重页面峰值 6,568 B。**验证**:`@build` 0 错 0 警,ZI 精确 −24,576 B → SRAM1 98,144/74.9% → **73,568/56.1%(余 57,504 B)**,CCM 不变;PC 侧 11 页黄金参考逐字节 10/11 一致、往返池水位 5,448 B 未变(唯一差异 `netcfg_save` 经 40 KB A/B 复测确认与本次改动无关,真因见下一行)。**连带改写**:§0.2 Top3 第 2 条、§0.3 主线一、§0.4 内存两行、§5.1 基线表(追 2026-10-15 `.map`)、§5.2 诊断 1(瓶颈由 SRAM1 换到 CCM)、§5.3(R-9 行删除、R-2 收益改 −29,570 B)、§6.4 池容量行;同步 `../内存管理/RAM使用分析与优化方案.md`、`../内存管理/RAM预算台账.md` §3、`../UI设计/UI_设计架构.md`、`../UI设计/UI_界面制作流程.md`、`../UI设计/LVGL模拟器部署与使用说明.md`、`simulator/README.md`、`../产品需求/产品需求设计文档.md`。**真机复跑 ✅**:`@test` PASS(`fw build: Oct 15 2026 07:31:34`,无 `[ERR]`/Fault,开机 622 ms 到 `net init: OK`),`menu build done: used=3192` = 建池后 15,020 B 的 21%。台账受管区与内存快照区已 `--write` 回填 → `mem-budget` / `mem-numbers` 两钩子转绿 |
| **2026-10-15** | **修 `netcfg_save` 假失败:回归改为自洽运行** | 上一条那个"唯一差异"查到真因,**不是代码回归**:`--cfgtest` 的 E-01 场景 2 伪造一份 4000 B 纯注释配置档并断言"拒写后原文件不动",**连通过路径也不删除它**;留在工作目录后每次运行都被加载 → 外科手术保存要在 4000 B 注释后补写受管段 → 输出必超 `CFG_FILE_MAX_LEN` → 引擎**按设计拒写** → 保存行显示 `ERR`。修三处:`simulator/main.c` 给 `--cfgtest` 加外壳(无论成败一律删除伪造档)、`check_menu_rows.py` 与 `check_content_pages.py` 改在**空临时目录**里跑(`cwd=tmp`)、脚本里已作废的 `sim_netcfg.bin` 兼容清理改为只提示不删除。修后 **11/11 页 + 往返池水位 5,448 B 全过,未刷任何黄金参考**(参考本来就是对的)。排查口径 → `../UI设计/UI_自动化测试流程.md` §五 |
| **2026-10-15** | **补漏:§0.1 六维评分与口径/附录数字追平 R-9 后状态** | 前面那条只改了 §0.2/§0.3/§0.4/§5.x,**漏掉 §0.1、§1 术语表、附录 A 证据表**(用户指出):① 内存使用行仍写"54,146 B 仍在 SRAM1 / SRAM1 74.9% / 须先 R-9"→ 改为 SRAM1 **56.1%(余 57,504 B)**、瓶颈收敛到 CCM 单点、只差"缩堆"一步,评级 **B → B+**(理由:SRAM1 由卡点变富裕,治理三件套与观测齐备;扣分项换成 CCM 单点 + C 堆无机械检查)② Flash `231,036` → **`231,084`**(§0.1 / §1 术语表 / §6.4 三处),并修掉术语表里 `ROM = 231,324 B` 这个**算术错**(`Code + RO + RW` 实为 **232,196 B**)③ RAM 占用定义 `98,144 / 74.9%` → **`73,568 / 56.1%`** ④ 附录 A 证据行改 `RW_IRAM1 Size: 0x00011f60`、余量 57,504 B ⑤ 文档分布 **88 篇 / 31,416 行 → 87 篇 / 30,781 行**、§3.1 体量表受控文件 **2,466 / 79.3 MB → 2,465 / 79.2 MB**(均随 `错误码传播审计.md` 删除,−1 篇 / −635 行;同表 `docs/` **56.9 MB**、手册图片 **703 张 / 36.3 MB** 复核未变)⑥ §0.1/§0.2/§0.4 与附录的复核日期统一标 **2026-10-15**,最近复核行的 `HEAD` 改指 `a7f5265` 并注明"后续文档与模拟器侧提交不影响固件",避免每提交一次就过期 |
| **2026-10-15** | **裁定:内存优化簇整簇暂缓** | 用户判定"把 LVGL 池与 UI 帧缓冲(现 ≈29.6 KB)搬进 CCM 作为后续功能预算来源"这个决定不合适,内存优化的事先放一放。据此:§0.2 **Top 3 → Top 2**(R-2 剩余移出名列,表下加一条 ⏸ 说明);§0.3 主线一由"把内存从卡死变富裕"改写为"**这条线到此为止**";§0.4 两条内存条目 🟡/❌ → **⏸ 暂缓**;§5.1 结论段与 §5.2 诊断 1 补"不代表现在要动";**§5.3 加暂缓横幅**(三条理由 + 保留常开的治理面 + 三条重开条件),R-2 / R-3 / R-4 / R-6 优先级列一律 ~~P1/P2~~ → **⏸ 暂缓**。**已落地的 R-9 不回退**(−24,576 B 是净收益且已台架验收);`mem-budget` 92% 门禁、预算台账、趋势基线、栈/堆观测**全部保留常开**——它们是传感器,不是优化动作。同步 `../任务规划/登记簿.md` N-27、`../任务规划/README.md` W3、`../内存管理/RAM预算台账.md` §3、`../内存管理/RAM使用分析与优化方案.md` 阶段 1.2 / 2 |
| **2026-10-15** | **执行本报告条目:N-12 / N-13 / N-16 落地并销项** | **N-12**:`dhara_nand_is_free()` 改整页扫描(栈上 64 B 分块、页首命中即早退,成本与旧实现同价)。**N-13:本报告原建议经核对不成立,已按实际契约改修**——本仓裁剪版 dhara 既没有 `DHARA_NO_PAGE`(哨兵实为 `DHARA_PAGE_NONE`,`journal.h:46`)也没有 `DHARA_E_UNSUPPORTED`(`error.h:20-31` 枚举里没有,照抄直接编译失败),且 `map.c` 三处 `dhara_journal_copy` 调用点各自已挡掉哨兵(`map.c:260`、`:434`+`:440`、`:242`);真风险是**移植层零上界校验**(越界页号被地址位截断成真实页 → 静默读写错页),故改为六个 HAL 入口统一几何上界守卫 + `DHARA_E_CORRUPT_MAP`(不能用 `BAD_BLOCK`:`journal.c:904` 会据此标坏块;不能用 `RECOVER`:`map.c:275` 会据此启动回收)。**N-16**:写侧一次排空合并成"每文件一次 open/write/close"(≤8 条 = 512 B 静态缓冲;**掉电语义不变**——批次仍在同一次 drain 内 close,close 回写目录项这个不同 LPN 会逼出 FTL 单页写回缓存;失败尾部回投队列,条目不丢);读侧 8 条滑动窗口 + 写者代数失效(API 与 UI 零改动)。**刻意不批量**:`sys_log_evidence()` 的 `wdev.log` 仍逐条落盘——取证路径要求立刻进 NAND 并靠 flush 诱导逼出脏页。**验证**:`@build` 0 错 0 警(Code 212,344→**213,536**、ZI 129,880→**131,520**,+1,640 B 全为静态缓冲,不吃栈);`@test` PASS 且**台架 journal 恢复点与修复前同轨**(`sectors used` 恒 14,094、`Map head` 每轮 +128 单调推进、`epoch` 稳定 3 → 整页 `is_free` 未改变恢复语义、无双编程);`stack_report.py` 9 个任务全 PASS(defaultTask 静态最坏链 3,024 B / 余 40%,实测峰值 2,164 B / 余 57%);新增主机侧移植层单测 `test/host/nand_port/`(假芯片 + 旧算法对照用例)。实施记录 → `../架构设计/STM32F4-Base存储架构说明.md` §5.3、`../子系统设计/运行与自检记录设计说明.md` §4.4/§4.5 |
| **2026-10-15** | **删除 §0.2「未闭环问题」小节** | Top 2 修复销项后该节只剩指针与暂缓说明,而三条内容都已在别处**单点详述**:未闭环清单 → §0.4;内存簇暂缓 → §0.3 主线一与 §5.3 横幅;Top 2 修复证据 → 上一条变更记录与文末「已销项条目的记录去处」。按 `../文档规范.md`「同一事实只在一处详述」删除,不留空壳。**§0.3 / §0.4 编号保持不变、不重排**——`../任务规划/README.md` §2 前言引用本报告 §0.3,本报告内部另有 §0.4 交叉引用,重排会全断(与本表 2026-10-14 行第 ⑤ 条"报告自有编号不回改、不重排"同一口径)。文首「销项状态」行的指针由「§0.2 / §0.4」改为「§0.4」 |
| **2026-10-15** | **删除 §0.3 主线建议第 1 条("内存这条线到此为止")** | 该条是同日暂缓裁定的**复述层**,同一事实已有三处单点详述:§0.4 内存两行(⏸ 暂缓)、§5.3 暂缓横幅(三条理由 + 保留常开的治理面 + 重开条件)、登记簿 N-27 与 `../内存管理/` 两份文档的横幅。按 `../文档规范.md`「同一事实只在一处详述」删复述、留权威。⚠ 上一条变更记录里"内存簇暂缓 → §0.3 主线一"这个去处**自此失效,以本行为准**(变更记录本身不改,它是历史)。§0.3 现只剩 UI 一条主线;标题「两条主线建议」→「主线建议」,**§0.3 编号不变**(`../任务规划/README.md` §2 前言引用的是编号不是标题) |
| **2026-10-15** | **启动 W6 UI 标准化簇**(用户裁定,覆盖 2026-10-11 的「本轮不排期」) | §0.3 删掉内存主线后只剩 UI 这一条,随即启动。已按 §6.5 的 U1~U5 拆成 5 个工作包上共享任务板,第一波 **U1 令牌层 ∥ U4 门禁补强** 两名成员并行(写范围严格不相交:U1 只写 `Drivers/BSP/UI/` + `App/task/ui_task.c`,U4 只写 `scripts/` + 门禁与规范文档)。**并行度的真实约束不是任务数量,而是注册文件**:新增 `.c` 必须改 `uvprojx` + `CMakeLists.txt` 两个共享文件,同工作树两人同改必互相踩,故 U3(生成器/注册表)是必须串行的一段,U2/U5 排在它后面——U5 的页面批量要等 U3 把注册自动化之后才谈得上并行。同步 §6.5 注与本表 W6 行;`../任务规划/README.md` §2.0 W6 行同步。另修正交付物清单:移植层单测拟名 `test/host/dhara/` 实际落地为 `test/host/nand_port/`,状态 ❌ → ✅ |
| **2026-10-15** | **§6 顶部加"建议栏不可照抄"总警示** | 启动 W6 后按 §6 执行 U1/U3/U4,**四次证伪**:① §6.3 要新建 `UI_SCREEN_W/H` 而 `ui_port.h:21-22` 早有同名宏;② `LV_COLOR_BLACK/WHITE` 在 LVGL 9.3 不存在**且极性反了**(墨色实为 white),照抄先编译失败、改完整屏反色;③ "27 页未落地"是**目标页数**、现存 `ui_page_*.c` 实测 7 个;④ 生成器草图漏掉**密码页非节点绑定**。另有 L0 清单 9 条取值错,明细在 §6.3 更正块。**不再逐条打补丁**,改为节顶立总口径:动手前逐条 grep 对代码核、不符一律以代码为准、差集必须回报不得静默修正;并明确**"发现"栏可信、"建议"栏不可照抄**。同一规律在 §4 的 N-13(`DHARA_E_UNSUPPORTED` 不存在)已复现,故这不是 UI 章节局部问题,而是"建议"这一体裁的通病 → 后续任务卡须显式写"报告建议值不可直接采用" |
| **2026-10-15** | **W6 第一波落地:U1 + U3 验证通过并回填现状表** | Lead 亲自验证(成员被模型路由变更连续打断,最终停用):`@build` **0 错 0 警**;golden **菜单 8 状态全像素 + 内容页 11 页逐字节一致**、池水位 **5368 / 5448 B 与 U1 基线相同**、参考图 sha256 **差异 0 条**(证明无人用 `--update` 刷基线);`registration-check`(含第 ④ 类"页面必须被表引用")与 `font-charset` **Passed**;`ER_IROM1` **232,052 B**(较已提交 HEAD −228 B)、`RW_IRAM1` **75,232 B 零增量**;对 38 个在途修改文件逐个核对 **BOM / CRLF / U+FFFD 全部与 HEAD 一致**。**单位成本实测**(临时页 `zzprobe` 绑到 `遥信量` 叶子 → 只跑生成器 → 带页 `@build` 0/0 → 注册表出现 `{5u, &ui_page_zzprobe_desc}` → 回滚后 6 个产出文件的 `zzprobe` 残留计数均为 0、重编回基线)得出 **新增一页 = 3 处人工(新 `.c` + 新 `.h` + 树那一行加 ` #page=<名>`)+ 1 条生成器命令,手工编辑注册文件 0 处**,并确认生成器 `registered` / `pruned` **双向对账**(不只增、也减)。据此修正 §0.1 UI 行(B → **B+**)、§6.1「内容页」与「设计令牌」两行、§6.5 U3 行;并把 §6.5 U4 从我上一版**误写的 3/3 改回 2/3**(golden 两脚本至今未挂 pre-commit,"脚本已存在"≠"已进自动门禁");§6.5 U3 原口径"8 处降到 1 处"改为**真实 3 处**。**"分三笔提交"计划作废**:U1 与 U3 改同一批文件、文件内交叠,硬切会产生不可编译的中间态,改为整波一笔 |
| **2026-10-20** | **补上 U4 缺的第三件:golden 像素回归入提交门禁(35→36 钩子)** | 先实测成本再定形态:改一个 UI 源文件后「增量重编模拟器 + 两条 golden」合计 **2~4 秒**,支持走自动门禁(若实测要几分钟,正确做法是明确不挂、改为文档化人工门禁——一个逼人 `--no-verify` 的门禁等于没有门禁)。新增 `scripts/check_ui_golden.py` 包装器(**有意忽略 argv 并 `pass_filenames: false`**:两条 golden 按整棵菜单树 + 全量参考图工作,不接受文件参数),新钩子 `ui-golden` 的 `files:` 覆盖 UI 源 + `ui_task.{c,h}` + 模拟器 tests/sim/src + `lv_conf.h` + 包装器与本配置文件自身。
⚠ **端到端验的是钩子不是脚本**:干净树连跑 3 次 `rc=0` 全 Passed;注入 `ui_page_dc.c` 的 `.top_y +5` → **钩子 Failed** 并点名 `[dc] FAIL 与参考页不一致(1882 px 差异)`;还原后 `git diff` 干净、`simulator/tests/ref` 的 `git status` 为空(**全程未用 `--update` 刷基线**)。诚实记录一次瑕疵:首次实测在连续注入/还原的紧邻重建之间偶发失败一次,疑似 MSBuild 节点争用,已把"单次失败先复跑、连续两次才当真、不许直接绕过"写进钩子注释。
据此 §6.5 U4 由 **2/3 → 3/3**(这次是"真进了自动门禁",不是"脚本存在")、§6.1 golden 行转 ✅、§0.1 UI 行缺口描述更新、`AGENTS.md` 钩子数与文末索引 35→36、`pre-commit使用说明.md` 概览计数同步。**W6 剩余**:U2 合并两页记录、U5 落地目标页(须先对齐"目标页 vs 现存 7 个 `ui_page_*.c`"差集)。
| **2026-10-14** | **清理:删除已完成与过时内容** | ① **已修复项按销项删除**(去处见下表):N-02(FTP 鉴权)、N-03c(`Error_Handler` 降级)、**N-11(DMA 可达性三层防线 + 规则族 R1~R4)**、S-3(注册反向校验)、P2-5/P2-6(SD2506 负温度与 WRTC)、P2-8(`NET_RECV_TIMEOUT_MS` 已删)、P2-9(`mkdir` 幂等重试)、§5.5 前三条(内存治理三件套)、交付物清单已建项。② **删除过时层**:2026-09-16 / 2026-10-11 双列基线表与"复核期间未提交改动"口径说明、旧"组合结果预估"表、逐条"2026-10-11 复核"批注、历史变更记录行——内存数字改由 `../内存管理/RAM预算台账.md` 与 `scripts/ram_baseline.tsv` 持有。③ **按 2026-10-14 `.map` 与代码重测**全部保留结论的数字(含 `lftpd_log.c` 已删除并入 `lftpd_auth.c`,N-06 残余只剩 `rs485_task.c` 一条刻意缺口)。④ **修正三处错误**:R-5(`configMAX_PRIORITIES` 56→32)**判定不可行**(CMSIS-RTOS2 `#error`);原 §7 把报告 S-5(驱动统一错误码)与登记簿 S-5(卷锁覆盖面)混为一谈;原 §5.5 第 4 条建议的 `_Static_assert` 形态在 AC5 上取不到链接期地址,已被"窗口断言 + 启动自检 + 运行时守卫"三层取代。⑤ 报告自有编号**不回改、不重排**(`../编号规范.md`) |
### 已销项条目的记录去处
> 删除前已逐条核对:以下去处文档/代码均含完整记录,删了不丢信息。
| 已销项条目 | 去处 |
|-----------|------|
| N-01(配置引擎 C 堆必失败) | `../子系统设计/配置文件设计说明.md` §6.2 / §8 |
| N-02(FTP 无鉴权,登记簿 N-19) | `../子系统设计/网络配置说明.md` §3.4(`[ftp] user/pwd_hash/write`、恒定时间比较、5 败锁 60 s、出厂默认口令告警、明文嗅探边界)+ `../产品需求/产品需求设计文档.md` §FTP 鉴权 + `Drivers/BSP/NET/lftpd/lftpd_auth.c` + 主机侧 `test/host/sha256/`、`test/host/ftp_auth/` |
| N-03a/N-03b/N-07/N-08(IWDG、`configASSERT`、两个钩子、4 个 Fault 现场) | `../子系统设计/看门狗设计说明.md`(§4.4、§10.1/§10.2)+ AGENTS.md「平台约束」 |
| N-03c(`Error_Handler` 死循环,登记簿 **N-20**,2026-10-15 已销项) | `../子系统设计/看门狗设计说明.md` §4.4(统一降级:1 条自检 + `NVIC_SystemReset()`、三条实现约束含 `__builtin_return_address` 不可用的实证、内核前日志路径安全性、重生成存活三层)+ 实施代码 `Src/main.c` 的 `USER CODE Error_Handler_Debug` 区注释 + `scripts/check_cubemx_survival.py` 审计项 |
| N-06 主体(`crc.c`/`ringbuf.c`/`lftpd_log.c` 删除、`adc_task.c` 注册)+ S-3(注册反向校验,登记簿 N-23) | `scripts/check_registration.py`(`KNOWN_GAPS` 白名单 + `--strict`)+ `../代码规范/pre-commit使用说明.md`(`registration-check`);`lftpd_log.c` 并入 `Drivers/BSP/NET/lftpd/lftpd_auth.c` |
| **N-11**(NAND 直 DMA 调用方缓冲 / CCM 迁移硬边界,登记簿 **N-26**,2026-10-15 已销项) | `../内存管理/内存内容分配策略.md` §3(全板 DMA 使用方清点)+ §4 规则 R1~R4(**R3 堆/栈禁为 DMA 源/目的**,含"缓冲自身地址无法编译期断言"这条边界)+ §8 落点索引;代码三层 = `gd5f2gq5ue.h:96-107` 编译期窗口断言 → `nand_ftl.c:241-243` 启动期自检 → `gd5f_dma_buf_ok()` 运行时强制;负路径用例 TC-GD5F-704/705 |
| N-10(UI accept 门控) | `../UI设计/RemoDispBus协议与接收设计说明.md` §3 + 看门狗设计说明 §11.4 |
| N-17(栈/堆水位观测)+ R-8(观测常开) | `../内存管理/RAM使用分析与优化方案.md` + `../测试说明/实机测试实施指南.md`(`[STACK]` 水位 + `stack_report.py` + `ram_report.py`) |
| N-18(本报告口径:无 CI) | `../测试说明/测试开发标准流程.md` §9 + `../CI/README.md` |
| P2-5 / P2-6(SD2506 负温度解析、WRTC 解锁顺序) | `Drivers/BSP/SD2506/sd2506.c`(符号扩展 `val \| 0xF0`;WRTC1→WRTC2/3 顺序按手册,上电即写禁止态) |
| P2-8(`net_recv` 30 s 阻塞超时) | `Drivers/BSP/NET/net_config.h` 头注(2026-10-13 recv 语义主流化:删除 `NET_RECV_TIMEOUT_MS`,预算由调用方显式给)+ `../网络/Net层阻塞模型架构设计.md` |
| P2-9(`mkdir` 失败不再重试) | `App/log/sys_log_mcu.c`(`s_fs_ready` 幂等 + `mkdir /log fail (%d), will retry` + 上报自检) |
| P2-13 / P2-14 / P2-15 | AGENTS.md(`test-suite-switch`、`keil-build`、`stlink-flash`)+ `../测试说明/ST-LINK_CLI_复位使用说明.md` |
| D-2 / D-3 / D-4 / D-5 / D-6 / D-7 | `../文档规范.md` + `../代码规范/pre-commit使用说明.md`;断链由 `scripts/check_doc_links.py` 门禁接管 |
| U4 主体(golden 回归) | `simulator/tests/` 四脚本 + `../UI设计/UI_自动化测试流程.md`(残余 = 挂门禁,见 §6.3) |
| R-1 / R-7 | `../子系统设计/看门狗设计说明.md` + AGENTS.md 任务表(`scripts/gen_task_table.py` 生成区) |
| §5.5 前三条(92% 门禁 / 趋势基线 / 预算台账,登记簿 N-27 前半) | `../内存管理/RAM预算台账.md`(阈值与配额的**唯一权威**)+ `scripts/ram_baseline.py`(`--gate/--update/--trend/--write`)+ `../代码规范/pre-commit使用说明.md`(`mem-budget`) |
| F-1 / F-2 / F-3(LVGL 死代码裁剪) | `../内存管理/RAM使用分析与优化方案.md` §2.6.4;`../UI设计/UI_设计架构.md` §1.4 第 10 条(**两份 `lv_conf.h` 必须同一特性子集**);`../UI设计/UI_自动化测试流程.md` 变更记录(池水位基线) |
| **R-9(`LV_MEM_SIZE` 40→16 KB,登记簿 N-27 原 W3 的第一步,2026-10-15 已执行;该簇其余部分同日整簇暂缓)** | 取值依据与再降 8 KB 的前置写在 `Lib/lv_conf.h` 与 `simulator/lv_conf.h` 的池注释;实测水位 → `../内存管理/RAM使用分析与优化方案.md` §2.6.2 与 §阶段 1.1(已执行);解锁链第 ① 步 ✅(后续步骤暂缓)→ `../内存管理/RAM预算台账.md` §3;池容量口径 → `../UI设计/UI_设计架构.md` §1.2、`../UI设计/UI_界面制作流程.md` C4 |
| **N-12 / N-13**(dhara 移植层 `is_free` 精度 + HAL 越界守卫,登记簿 **N-21**,2026-10-15 已销项) | `../架构设计/STM32F4-Base存储架构说明.md` §5.3(移植层两条硬约束:整页精确的原因链 + 错误码取舍与"为什么不会静默清卷")+ `Drivers/BSP/GD5F2GQ5UE/nand_ftl.c` 的 `ftl_page_in_range` / `ftl_block_in_range` / `dhara_nand_is_free` 注释 + 主机侧单测 `test/host/nand_port/`(含"旧算法只看页首 64 B"的对照用例,防止将来被改回去) |
| **N-16**(日志读写放大,登记簿 **N-24**,2026-10-15 已销项) | `../子系统设计/运行与自检记录设计说明.md` §4.4(写侧批量合并 + **掉电语义不变**的论证 + 失败回投不丢条目 + 容量轮转只在慢路径)与 §4.5(读侧 8 条窗口 + 写者代数失效 + API 不变)+ `App/log/sys_log_mcu.c` 的 `s_format_rec` / `s_append_rec` / `s_append_many` / `s_read_raw` 注释 |