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

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

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

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

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

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

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

81 KiB
Raw Blame History

STM32F4-Base 项目审查与优化建议报告

项目 内容
审查日期 2026-09-18
代码基线 HEAD = 2d0ec35(工作树干净,98+ 提交)
固件基线 MDK-ARM/STM32F407-Demo/STM32F407-Demo.map,构建于 2026-09-16 12:33,0 Error / 0 Warning
现状复核 2026-10-11,复核基线 HEAD = 54d6d2d(分支领先 origin/v2-on-ui,未推送);固件基线换为 2026-10-11 00:38 链接的 .map。⚠ 复核期间分支又前进(ADC L2 相量层 / L1-6 有效性校验 / adcTask 抬栈),其数字影响已在 §5.1"基线口径说明"逐项量化,不改变任何结论
销项状态 本报告采用修复即销项:已修复项的正文与表格行已删除(874 → 733 行,114 KB → 84 KB),去处清单见文末变更记录;未闭环清单见 §0.2 / §0.4
审查范围 仓库全量(2,013 个受版本控制文件);重点为自研代码 24,353 行(108 文件)与构建产物内存画像
审查方式 静态审查 + 链接图(.map)量化分析 + 构建脚本/门禁核对;未做上机动态验证(除特别标注外)
目标 结构合理性、文档分布合理性、模块设计合理性、改进路线图、内存优化、UI 标准化

与既有文档的关系:本报告不重复 docs/待处理内容.md(逐条缺陷台账)与 docs/内存管理/RAM使用分析与优化方案.md(内存专项方案),而是做体系层评审 + 量化补强 + 新增发现。新增发现统一编号 N-xx,已在台账中的条目按 P1-x/P2-x/P3-x 引用。

销项约定(2026-10-11 起):本报告采用与登记簿相同的**「修复即销项」——问题修复后其分析正文与表格行从本报告删除**,完整记录沉淀到对应模块文档(去处清单见文末变更记录)。因此本报告只呈现未闭环问题,不再随时间膨胀。


0. 执行摘要

0.1 六维评分(2026-09-18 评级 + 2026-10-11 复核)

维度 评分 一句话结论 2026-10-11 复核
项目结构 B+ 六层分层真实可执行、命名一致、门禁到位;主要问题是 BSP 层承载业务模块、双构建清单漂移、5 个源文件两边都不编译 A−:反向依赖 4→3 处、net_cfg_legacy 删除、CI + 22 钩子成型、App/algo/ 分层补齐;❌ 但"存在但未注册"的反向校验仍缺(check_registration.py 未建),rs485_task.c / lftpd_log.c 仍两边不编
文档分布 B- 工程文档密度与深度高于同级嵌入式项目;主要问题是 文档占仓库 60% 体积(其中芯片手册图片 40.5 MB)、README 任务表 4× 过期、约 18 条断链、文档承诺了不存在的工具/台账 A−:工程文档 28,895 行(翻倍)、docs/README.md 索引 + docs/文档规范.md + 三档一致性门禁全部落地,check_doc_links --all = 129 文档 0 失效;⚠ 新风险:AGENTS.md 自身数字开始落后实测("18 钩子"实为 22、"清单 23 项"实为 29)
模块设计 B 配置引擎、网络串行化、就绪屏障、FatFs 互斥等设计成熟;主要问题是 失败路径普遍"死循环/静默降级"、无看门狗、FTP 无鉴权、cfg_file 依赖注定失败的 malloc B+:看门狗(IWDG + 心跳位图 + 4 个 Fault 现场 + .noinit)、ADC 全链(TIM3 事件驱动 + DMA 乒乓)、卷锁、墙钟均已落地;❌ FTP 仍无鉴权、RS485 仍无协议栈(E-50/E-49 未修)、Error_Handler 仍 while(1)
内存使用 C SRAM1 已用 96.6%(余 4,488 B),64 KB CCM 完全空转,FreeRTOS 堆被 6 条任务栈吃掉 2/3,栈/堆水位零观测 B:堆 49,184 B 迁 CCM → SRAM1 降到 69.8%(余 39,584 B)、CCM 用 75.1%;栈/堆三面观测常开([STACK] + stack_report.py + configCHECK_FOR_STACK_OVERFLOW=2);🟡 LVGL 池 40,960 B 未迁也未下调;⚠ 新硬约束:从堆分配的缓冲不可被 DMA 直接寻址
Flash 使用 B+ 仅用 23.4%;但 ≈50 KB(20% 镜像)是可证明的死代码(LVGL 配置漂移所致) A−:2026-10-11 落地 F-1/F-2/F-3(两份 lv_conf.h 对齐、只留 I1 绘制核、零控件、无 flex/grid)→ 省 64,576 B,占用降到 20.1%(211,024 B);剩余 F-4/F-5/F-6 ≈10 KB 待逐项实测
UI 标准化 B- 已有"声明源→生成器→表驱动"的正确骨架;缺设计令牌层、页面原型库、注册自动化、golden 回归,42 个菜单叶子仅 7 个有内容页 B:golden 回归已落地(simulator/tests/ 4 脚本:菜单 8 状态全像素 + 内容页逐字节黄金参考 + 协议 e2e);❌ 令牌层 / 组件库 / 页注册表未动,现口径为 33 叶子 / 6 个有内容页

0.2 未闭环问题(Top 5,2026-10-11)

已修复/已消解的条目按「修复即销项」从本报告删除,完整记录见对应模块文档(去处清单见文末变更记录)。

# 编号 问题 影响 现状
1 N-02 lftpd.c cmd_pass 对任意口令无条件回 230(现 :375-379;cmd_user :553-557 同) 局域网内任意主机可读写 /config.ini(含 IP/MAC/网关)→ 可远程断网;单连接 CH395F 上还可挤掉 UI/对时服务 ❌ 一行未改;⚠ 且不在 ../待处理内容.md 登记簿内 → 建议补登
2 N-04 / R-2 剩余 LVGL 池 work_mem_int 40,960 B + UI 帧/绘制 ui_port.o 6,420 + UI 发送 ui_net_sink.o 6,766 = 54,146 B 仍在 SRAM1 堆迁 CCM 后 CCM 只剩 16,304 B,这 54 KB 装不下 → R-2 后半段被堵死 🟡 必须先做 R-9(LV_MEM_SIZE 40 KB→16 KB)或 R-4(帧副本 4→2)腾出空间
3 N-12 / N-13 dhara_nand_is_free() 只查页首 64 B;dhara_nand_copy() 不处理 DHARA_NO_PAGE 哨兵 映射恢复误判 → 数据丢失风险(低概率、后果重);哨兵页号被当真实页 → 未定义行为 ❌ 两处原样;⚠ 也不在登记簿
4 N-06 残余 rs485_task.c、lftpd_log.c 既不在 uvprojx 也不在 simulator "写了不编译"= 死代码 + 误导 ❌ 未处理;❌ check_registration.py(S-3)仍未建
5 N-16 日志每条记录一次 f_open + f_write + f_close(sys_log_mcu.c:123-140) 每条日志 2–4 次 NAND 页读写;UI 翻记录逐条 open/seek/close ❌ 未办(仅 FIL static 化省 528 B 栈)

⚠ 另有两条不在上表但需排期:Error_Handler 仍 __disable_irq(); while(1){}(N-03c,§4.1)、.git 已膨胀到 149 MB(D-1,§3.1)。

0.3 三条主线建议

  1. 把内存从"卡死"变"富裕":启用 CCM 承载 LVGL 池与 UI 帧缓冲(≈54 KB),同时补齐栈/堆水位自检,作为后续功能的预算来源(§5.3)。
  2. 把失败路径从"死循环"变"可恢复":IWDG + 任务心跳位图 + 统一的"降级但活着"策略(§4.1)。
  3. 把 UI 从"每页手写"变"声明 + 原型库":令牌层 → 原型库 → 生成器补全注册 → golden 回归,使新增一页的成本从 8 处手工改动降到 1 处声明(§6)。

0.4 未闭环清单(2026-10-11 复核)

只列未办 / 半办项;已修项已按「修复即销项」删除(去处见变更记录)。图例:❌ 未办 / 🟡 半办。

类别 编号 未闭环内容 下一步
安全 N-02 ❌ FTP 无条件 230,任意主机可读写 /config.ini 补登登记簿 + [ftp] user/pwd_hash 定长比较 + 失败计数锁定 + 生产默认只读
安全 N-03c ❌ Error_Handler 仍 __disable_irq(); while(1){}(Src/main.c:274-282) 统一降级策略:记自检 + 禁用该子系统 + 其余照常
数据正确性 N-12 / N-13 ❌ is_free 只查页首 64 B;copy 不处理 DHARA_NO_PAGE 补登登记簿 + 整页(或首/中/尾)扫描 + 返回 DHARA_E_UNSUPPORTED
数据正确性 N-14 🟡 写回缓存无空闲/掉电强制 flush(整页写与换页已 flush) 空闲 N ms 由 defaultTask flush
内存 RAM R-2 剩余 🟡 LVGL 池 + UI 两份缓冲 54,146 B 仍在 SRAM1;CCM 只剩 16,304 B 先做 R-9 或 R-4 腾 CCM,再迁
内存 RAM R-3 / R-4 / R-5 / R-6 / R-9 ❌ 页缓冲未合并 / UI 帧 4 副本 / configMAX_PRIORITIES 仍 56 / 栈仍动态 / LV_MEM_SIZE 仍 40 KB R-9 最省力(池水位门禁数据已具备);R-6 前置条件已满足
内存 RAM N-11 🟡 NAND 仍直 DMA 调用方缓冲(已加运行时守卫、阈值 1024) 补编译期/启动期地址断言 + 新铁律:堆缓冲不可被 DMA 直接寻址
内存 RAM §5.5 ❌ ram_baseline.tsv、RAM预算台账.md、92% 阈值门禁均未建 三条一起补,否则 R-2 后半段无法排期
Flash F-4 / F-5 / F-6 ❌ 剩余可评估 ≈10 KB(box_shadow 3,246 / draw_sw_img 2,960 / bin_decoder 1,972 / image_decoder 952 / triangle 930)+ 双精度 dadd 等 ≈1 KB;uvprojx 仍挂 201 个 LVGL .c(F-2 后均为空 TU,省的是编译时长) 逐项实测再关;⚠ mask/mask_rect/grad 是文字与矩形绘制的必经路径,不可盲关
性能/资源 N-09 / N-16 / P2-8 / PHY / P3-x / rs485 ❌ PASV 每次新建监听;日志每条 open/write/close;NET_RECV_TIMEOUT_MS 30000;PHY 强制 100M 无自协商回退;NET_MAX_SOCKETS 8 与单连接语义不符;HAL_Delay(1) 阻塞 DE 见 §4.2 / §4.5 / §4.7 各行建议
结构 S-1 / S-2 / S-3 / S-4 / N-06 残余 ❌ NETCFG/UI 语义层错位;4 处裸 extern;无注册反向校验;app_cfg.h 开关未接线未删;rs485_task.c/lftpd_log.c 两边不编 check_registration.py 是其中唯一"1 小时工作量"的一条
文档/仓库 D-1 ❌ 图片 36.3 MB 仍在仓;.git 已 149 MB 降采样 / 拆 submodule / LFS 三选一 + 一次 git filter-repo 历史重写
文档/仓库 语义断链 ❌ 两份 UI 文档仍指 tools/ui_mock/(不存在,实为 simulator/tests/) 改指 + 挂进 ui-verify
文档/仓库 元信息漂移 ⚠ AGENTS.md 写"18 钩子"(实 22)、"清单 23 项"(实 29) 纳入生成/校验(同任务表做法)
UI U1 / U2 / U3 / U5 ❌ 无令牌层、无组件库、无页注册表(手写 case 仍在);27 页未落地 按 §6.5 顺序,U4 的 golden 已可当 U1 的验收工具
UI U4 残余 🟡 golden 已建但未挂 pre-commit;字符集差集检查仍是约定 两个脚本入门禁
测试 P1-10 🟡 / 覆盖率 ❌ CH395F 套件收尾两步;无覆盖率概念;主机侧对 lftpd/dhara 移植层无测试 N-12/N-13 这类缺陷至今只能靠人工读代码发现
台账 ⚠ N-02、N-12/N-13 漏登;报告 N-18 与登记簿 N-18 同号不同义 补登 + 引用一律带文档名

1. 审查方法与证据基线

1.1 数据源

数据 来源 可信度
内存/Flash 占用 .map 的 component 表(214 个含码对象,列序 Code incData RO RW ZI Debug Name) 高(构建产物,权威)
总量 map 尾部 Total RO / Total RW / Total ROM + Program Size 高
依赖方向 逐文件 #include 扫描 + map 的 refers to 交叉表 高
构建注册 MDK-ARM/STM32F407-Demo.uvprojx + simulator/CMakeLists.txt + git 文件清单三方对齐 高
文档链接 全量 md 链接解析(剔除设备路径 PB12/通配/占位后 706 → 94 未解析 → 人工分诊) 中高(人工分诊)

1.2 复现命令(本报告所有数字可重跑)

# 1) 构建体量
Select-String -Path 'MDK-ARM\STM32F407-Demo\STM32F407-Demo.map' -Pattern 'Total RO Size|Total RW Size|Total ROM Size'
# 2) MicroLIB C 堆真实大小(应为 0x200 = 512 B)
Select-String -Path 'MDK-ARM\STM32F407-Demo\STM32F407-Demo.map' -Pattern '__heap_base|__heap_limit|HEAP'
# 3) 对象级 RW+ZI 排行(本报告脚本:obj_sizes2.py,注意 map 组件表是 6 个数值列)
# 4) 三方文件注册对齐(uvprojx / CMakeLists / git)
# 5) 仓库体量:git ls-files -z | 逐个统计字节(必须用 -z,否则中文名会被控制台代码页破坏)

⚠️ 踩坑记录(供后续审查复用): ① map 组件表是 6 列数值(含 incData 与 Debug),按 5 列解析会得到 0 个对象; ② git ls-files 输出经 PowerShell 管道会按控制台代码页破坏中文路径,导致"文件不存在"的假结论,必须用 -z + 字节流解码; ③ 终端渲染的中文乱码不等于文件损坏,判定损坏须统计 EF BF BD 字节数(本报告统计结果:0,全库无 U+FFFD)。

1.3 口径定义

术语 定义
自研代码 App/、Drivers/BSP/(不含 Lib/、Middlewares/、HAL/CMSIS)、test/;不含 CubeMX 生成的 Src/、Inc/
Flash 占用 Code + RO-data(基线 245,384 B → 2026-10-11 复核 273,860 B);ROM 再加 RW-data 初始化表(246,016 → 274,060 B)
RAM 占用 RW-data + ZI-data(基线 126,584 B,全部压在 SRAM1)。2026-10-11 复核:140,720 B,已跨两个区域 = SRAM1 RW_IRAM1 91,488 / 131,072(69.8%)+ CCM RW_CCM 49,184 / 65,280(75.3%)+ UNINIT_CCM 48 B(.noinit 崩溃现场保留区)。⚠ 口径变化:原报告"CCM 不在 scatter 内计 0"已不成立
"死代码" 该对象在镜像中有码,但其功能被工程配置(如 LV_COLOR_DEPTH 1)或调用点扫描(零调用者)证明不可达

2. 项目结构合理性

2.1 规模与分布(实测)

分类 文件 行数 说明
App/(自研应用) 38 8,516 配置引擎、墙钟、日志、任务薄壳;新增 App/algo/(L1 采集 + L2 相量,6 文件 1,090 行)
Drivers/BSP/(自研驱动/子系统) 67 19,104 其中 UI 子系统 6,036 行(含 ui_font_cn_12.c 1,211)、NET 6,302、CH395F 2,144、GD5F 1,978、SD2506 835、TPAFE 680、NETCFG 578、RS485 310
test/(自研测试) 41 10,855 test/host/ 主机侧单测 25 文件 / 4,505 行(2026-09-18 时不存在)+ test/board/ 板级套件 16 文件 / 6,350 行
自研合计 146 38,475 2026-09-18 基线为 108 / 24,353 → +38 文件 / +14,122 行
CubeMX Src/+Inc/ 26 5,459 受 USER CODE 标记保护
simulator/ 10 2,462 PC SDL2 宿主
第三方 Lib/ 958 529,916 LVGL 9.3(423 个 .c / 421,546 行)、FreeRTOS、FatFs、dhara、lftpd

评价:自研体量与第三方体量比约 1:14(原 1:20,自研涨了一倍半),仍属典型的"薄自研 + 厚中间件"。这决定了结构审查的重点不在自研代码内部,而在"自研如何约束第三方"——§5.4 曾量化这层约束(lv_conf.h)失守的代价:64,576 B 死代码(2026-10-11 已修)。

2.2 分层与依赖方向

实测自研层间的全部反向依赖(BSP → App)——2026-10-11 复核为 3 处(原 4 处,net_cfg_legacy.c 已随旧档迁移路径整体删除,见 §2.4):

位置 反向 include 判定
Drivers/BSP/NETCFG/net_cfg.c:31 cfg_file.h 可接受但需说明:NETCFG 实为"业务配置模块",放在 BSP 层是历史命名
Drivers/BSP/UI/ui_page_runlog.c:19 sys_log.h UI 读日志属正常消费者,但 sys_log 在 App 层 → 层次倒置
Drivers/BSP/UI/ui_page_selftest.c:19 sys_log.h 同上
Lib/FatFs/ffsystem.c:19 sys_clock.h(App/util) 中间件→应用;已在文件内注释声明为"④中间件层"有意为之

评价:良好。在 38k 行规模上把反向依赖控制在 3 处,说明分层不是墙上的图。但存在语义层错位:

  • Drivers/BSP/NETCFG/、Drivers/BSP/UI/ 名字是"板级支持包",内容是业务(网络配置语义、菜单/页面/权限/内容页框架)。后果:sys_log(App)与 net_cfg(BSP)互相引用,层次图自相矛盾。
  • 建议(S-1):把 NETCFG、UI 在概念上重新定性为 App/ 或新增 Service/ 层,Drivers/BSP/ 只留下与引脚/芯片强绑定的 ch395f、gd5f2gq5ue、sd2506、tpafe5160、rs485、dbg_*。迁移成本最低的做法:先改文档与分层图(零风险),目录迁移留到有 CI 兜底之后(避免与 §2.3 的注册问题叠加)。

依赖方向裸引用(绕过头文件契约的 extern)实测 4 处——2026-10-11 复核:4 处全部仍在(仅行号漂移),S-2 未办:

位置 裸 extern
Drivers/BSP/CH395F/ch395f.c:178 extern volatile uint8_t g_spi1_dma_done;(实为 GD5F 在 gd5f2gq5ue.c:50 定义的变量,跨模块反向且未进任何头文件)
Drivers/BSP/GD5F2GQ5UE/gd5f2gq5ue.c:47 extern SPI_HandleTypeDef hspi1;(Inc/spi.h:35 已导出,局部重复)
Drivers/BSP/NET/net_socket.c:56 extern osMessageQueueId_t netMsgQueueHandle;
Drivers/BSP/SD2506/sd2506.c:20 extern I2C_HandleTypeDef hi2c1;(Inc/i2c.h:35 已导出,局部重复)

建议(S-2):CubeMX 句柄类(hspi1/hi2c1)统一由 main.h 导出并 include,删除局部 extern;g_spi1_dma_done 应由 GD5F 模块自己定义并在头文件 extern 声明(当前跨模块裸 extern 是真正的耦合隐患);netMsgQueueHandle 改为 net_socket 内部持有 + 提供投递 API。

2.3 双构建清单漂移(结构性风险最高的一条)

把 git 文件清单 × uvprojx × simulator/CMakeLists 三方对齐后:

类别 文件 后果
两边都不注册 基线 5 个:App/task/adc_task.c、App/util/crc.c、App/util/ringbuf.c、Drivers/BSP/NET/lftpd/lftpd_log.c、App/task/rs485_task.c 2026-10-11 复核:剩 2 个——crc.c/ringbuf.c 已整体删除(fac9d1c4,2026-10-02,全库无调用者);adc_task.c 已挂 uvprojx(仅 MCU,模拟器不编,符合"任务层不入模拟器"的既有做法);❌ rs485_task.c(等 E-50/E-49 修复)与 lftpd_log.c(与 lftpd_log.h 的 DBG 宏语义重复)仍两边不编
仅 simulator 注册 cfg_file_store_sim.c、net_socket_sim.c、sim_port.c、sim_syslog.c、ui_net_sink_backend_sim.c 正常(后端适配层模式)
uvprojx 306 个 .c(含 201 个 LVGL)(基线 290) LVGL 全量编译 → 编译时长;死代码问题被"全编"放大(见 §5.4)

评价:AGENTS.md 已把"新文件必须注册两处"写成铁律并有 new-source-file skill,但缺少反向校验("存在但未注册")。这是纯粹的机械检查缺口——2026-10-11 复核:该缺口未补(scripts/ 现有 11 个脚本,无 check_registration.py)。

建议(S-3,1 小时工作量):新增 [计划] scripts/check_registration.py,规则:

  1. App/**.c、Drivers/BSP/**.c(排除 *_sim.c、*_mcu.c 之外的模式白名单)必须同时出现在 uvprojx 与 CMakeLists;
  2. 例外必须写进 [计划] scripts/registration_allowlist.txt 并注明原因(如"待 RS485 协议启用");
  3. 挂入 pre-commit 的 local hooks(与 no-mojibake 同列)。 这样 §2.3 的 5 个文件会立刻变成"要么注册、要么显式豁免"。

2.4 目录组织与根目录卫生

观察 证据 判定
根目录散置工作文件 ui_menu_tree.txt、ui_cn_chars.txt 菜单树/字符集是生成器输入,放根目录合理且已在 docs/ 有权威说明(原点名的根目录 待处理内容.md 已并入 docs/待处理内容.md 统一登记簿)
Lib/ 命名 Lib/ 下混放 lvgl/、lv_conf.h、FatFs/、dhara/ lv_conf.h 是工程配置不是库文件;建议移到 Config/lv_conf.h 并同步 include 路径(低风险、提升可读性)
功能开关名存实亡 App/app/app_cfg.h 的 APP_NET_ENABLE/APP_ADC_ENABLE/APP_RS485_ENABLE 全库零引用 与台账 P2-12 同源。开关不生效比没有开关更糟——评审者会误信"ADC 可裁剪"。2026-10-11 复核:🟡 已订正表述但未接线——文件头 v1.1(2026-10-04)已如实标注"预留占位、不门控任何代码",APP_CCM_ENABLE/APP_UI_NET_SINK 已删除,三个 APP_*_ENABLE 仍在且仍零引用

2.5 结构结论

总体判断:结构合理,无需重构。要做的是四类低成本纠偏:S-1 层次定性、S-2 消裸 extern、S-3 注册反向校验、S-4 让 app_cfg.h 开关真正生效或删除(并把 APP_CCM_ENABLE/APP_UI_NET_SINK 纳入同一处)。


3. 文档分布合理性

3.1 体量画像(实测,工作树;2026-10-11 复核,括号内为 2026-09-18 基线)

对象 文件数 体积 占仓库
仓库受控文件总计 2,363(2,013) 77.7 MB(78.3 MB) 100%
docs/ 合计 849(652) 44.4 MB(47.19 MB) 57.1%(60.3%)
└ docs/芯片手册/ 图片 703 张(564 张) 36.3 MB(40.5 MB) 46.7%(51.7%)
└ docs/images/引用关系图.json 1 1,072 KB(1,052 KB) 1.3%
└ docs/UI设计/mockups 15 1,110 KB(1,110 KB) 1.4%
.git 目录 1,027 149.3 MB(46.6 MB pack) —
工程文档(Markdown,自研) 77 篇 / 28,895 行(40 篇 / 14,124 行) — —
外部资料(Markdown,datasheet 转录) 14 篇 / 58,244 行(16 篇 / 59,142 行) — —

关键失衡(复核后仍在,但已缓解):工程文档行数从 14,124 涨到 28,895(翻倍),外部资料行数基本不变 → 比例从 1:4.2 改善到 1:2.0;但图片体量(36.3 MB)仍是全部 Markdown 的 4 倍,git clone 的净耗时依旧几乎全花在扫描手册图片上。⚠ 新增事实:.git 已膨胀到 149 MB(受控文件才 77.7 MB),说明历史里存过多版图片/产物——D-1 的优先级因此上升(拆仓或 LFS 之外,还需 git filter-repo 类历史清理才能见效)。

3.2 做得好的(应保留并显式制度化)

  1. 文档密度与可执行性:每篇都带"步骤 + 判据"(如 UI 制作流程有 P0→P4 Gate);测试说明按模块拆分并给出预期串口标记,可直接接 @test。
  2. 陷阱台账:docs/问题记录/*_Trap_Records.md + docs/待处理内容.md(P1-1..P3-13,带 ✅ 状态与日期)——这是本工程最有价值的资产,多数嵌入式项目没有。
  3. 图与脚本同提交:tools/arch_diagram/、tools/refgraph/ 生成器 + 产物入库,图可再生而非手绘。
  4. UI 设计资产链完整:原始参考图 → 说明 md → 代码,可追溯。

3.3 问题清单

D-1(P1)外部资料与工程文档同仓同树

影响:clone 慢、备份慢、code review 时 diff 噪音大;docs/芯片手册/ 的 564 张图属于只读输入资料,与代码演进无关。

建议(三选一,按代价递增):

  1. 最省:把 564 张 JPG 降采样到长边 1,200 px(40.5 MB → 约 8 MB),一次性提交并在 [计划] docs/文档规范.md 写入"入库图片上限 200 KB/张";
  2. 推荐:docs/芯片手册/ 拆为独立仓库 dtu-datasheets,用 git submodule 挂回原路径(现有全部链接不需改写);
  3. 最正:Git LFS(需要托管端支持)。

2026-10-11 复核:未办,且问题变大。图片增至 703 张 / 36.3 MB(受控文件总量 77.7 MB,图片仍占 46.7%);⚠ 新事实:.git 目录已达 149.3 MB(1,027 个对象文件),远大于工作树受控内容 → 历史里存了多版图片/日志/产物。只做 1/2/3 中任一项都不足以解决 clone 耗时,还需一次历史重写(git filter-repo);重写属破坏性操作(所有克隆需重取),须单独排期并先备份裸仓。

3.4 文档结论

内容质量高、分布形态差。按 D-1..D-7 处理后可从 B- 提升到 A-:工程量约 1 人日(不含拆仓),收益是 clone 时间与"文档可信度"。

2026-10-11 复核:内容质量侧已到位(评级 A−,见 §0.1;D-2..D-7 已销项),分布形态侧未动——未闭环的文档类问题只剩三条:

  • D-1 ❌:docs/ 仍占仓库 57.1%、图片仍 36.3 MB,且 .git 已膨胀到 149 MB → 已从"P1 优化"变成影响日常 clone/备份的实际痛点;单靠拆仓或 LFS 不够,还需一次历史重写。
  • 语义断链 ❌:两份 UI 文档仍指向 tools/ui_mock/(该目录从未存在,实现落在 simulator/tests/)——路径检查抓不到,属语义层。
  • 元信息漂移 ⚠:门禁与文档越多,自身数字也会过期——AGENTS.md 现写"18 个钩子"(实测 .pre-commit-config.yaml 22 个)、"清单 23 项"(实测 check_cubemx_survival.py 29 项)。建议把这类计数也纳入生成/校验,否则报告与规范各说一套。

4. 功能模块设计评审

4.1 启动与任务框架

设计良好:

  • app_main_init 顺序有因(墙钟优先,使后续日志带时间戳);
  • Event Flags 就绪屏障(NET_READY/STORAGE_READY/FTP_READY/TIMESYNC_READY/CFG_SAVE/NET_RECONFIG)取代魔法延时,是本项目最漂亮的设计之一;
  • 配置引擎属主收敛到 defaultTask 单任务(P1-3 已修),消除了跨任务竞态。

问题:

编号 问题 证据 影响/建议
N-03c 多处初始化失败即挂死(Error_Handler() at main.c:221;net/ui 初始化失败路径) 建议统一降级策略:外设失败 → 记自检日志 + 该子系统禁用 + 其余照常(LED/屏上给出降级标识)。❌ 2026-10-11 复核仍在:Src/main.c:274-282 仍 __disable_irq(); while(1){}(IWDG 独立于中断,最终仍会被硬复位,但无降级、无定位信息)
P2-12 App/app/app_cfg.h 三个 APP_*_ENABLE 零引用 开关要么生效要么删 → 🟡 部分处理:已如实标注"预留占位、不门控任何代码"(v1.1),未接线也未删

4.2 网络子系统(CH395F + BSD 抽象层)

设计良好:

  • 所有 net_* 经 netMsgQueue(深 8,Src/freertos.c:154)串行到 netTask 执行 → 单线程语义清晰;
  • 全部 CH395F 中断集中在 net_poll() 内 GET_GLOB_INT_STATUS 排空(AGENTS 铁律,已落实);
  • 网络代次号机制使"改 IP 免重启"能重建各监听。

问题:

编号 问题 证据 建议
N-02 FTP 无鉴权:cmd_pass 无条件 send_simple_response(socket, 230, STATUS_230) lftpd.c:367-371(cmd_user :539 亦不校验) 最小可用方案:config.ini 增 [ftp] user/pwd_hash,cmd_pass 用定长比较 + 失败计数锁定;生产固件默认关闭 FTP 写权限(FF_FS_READONLY 式的只读模式)。❌ 2026-10-11 复核:一行未改(现 lftpd.c:375-379 / :553-557,全文件无 password/auth/attempt/read_only);⚠ 且未登记进 待处理内容.md → 建议补登为 P0/P1
N-09 cmd_pasv 每次新建监听 lftpd_inet_listen(0)(随机端口) lftpd.c:373 在单连接 socket 上与 UI :7003、对时 :8888 争用同一物理 socket;建议限定监听资源池 + 显式"FTP 会话独占"策略,并在文档写明"一次只允许一个 TCP 服务活动"。❌ 复核仍在(现 lftpd.c:392,3 次重试 + 200 ms 间隔)
P2-8 net_recv 阻塞超时 30 s 台账 缩短并让上层可取消。❌ 复核仍在:net_config.h:39 NET_RECV_TIMEOUT_MS 30000
— PHY 强制 100M 全双工,忽略自协商 AGENTS/architecture 与某些交换机会联不通;至少加"链路 up 失败 3 次后回退自协商"。❌ 复核仍在:net_task.c:110 无条件 ch395f_set_phy(CH395F_PHY_100M_FULL),仅 3 s 链路等待 + DISCONN 时上报 comms fault 自检
P3-x NET_MAX_SOCKETS 8(net_config.h:24-25)是软件表,与硬件单连接语义不符 文档已说明,但 API 层看不到限制 → 建议在 net_socket.c 断言"同时 LISTEN 数 ≤ N"。❌ 复核仍在(现 net_config.h:27,值仍 8)

4.3 存储子系统(GD5F2GQ5UE + dhara + FatFs)

设计良好:

  • 分层正确:gd5f2gq5ue(芯片)→ nand_ftl(dhara map/journal + 页缓存)→ FatFs R0.14 → 应用;
  • Lib/FatFs/ffsystem.c 用真实 osMutexNew 实现 FF_FS_REENTRANT=1 的卷锁(多数移植是空实现)——这是本工程移植质量的一个亮点;get_fattime() 接系统墙钟,使 FAT 时间戳有意义;
  • BBT 保留块池(gd5f2gq5ue.c:28-40)把坏块表存储区从 dhara 可见空间中排除,避免被 FTL 擦写。

问题:

编号 问题 证据 影响/建议
N-11 NAND 驱动直接对调用方缓冲做 DMA:HAL_SPI_Receive_DMA(&hspi1, p_buf, size)(:200)、HAL_SPI_Transmit_DMA(&hspi1, (uint8_t*)p_buf, size)(:258),阈值仅 32 B gd5f2gq5ue.c:185-217,246-270 这是 CCM 迁移的硬边界:所有走 NAND 的缓冲区(含 FIL、FatFs window、任务栈上的临时缓冲)必须留在 SRAM1。必须在 docs/内存管理/ 写成铁律,否则一旦把某任务栈搬进 CCM 就会静默 DMA 失败。2026-10-11 复核:🟡 模式未变但已加固——阈值改为 GD5F_DMA_THRESHOLD 1024(gd5f2gq5ue.h:78),并新增运行时 DMA 可达性守卫(:110-136,不可达自动回落轮询)。⚠ 复核新发现:堆已迁 CCM(ucHeap @0x10000020)→ "从 FreeRTOS 堆分配的缓冲不可被任何 DMA 直接寻址"成为全局铁律,比原结论更硬
N-12 dhara_nand_is_free() 只检查页首 64 B nand_ftl.c:137-154 dhara 依赖该判定做映射恢复;页首 64 B 全 FF 但后半页有数据时会误判为空闲 → 数据丢失风险(低概率但后果重)。建议整页扫描或至少首/中/尾三处采样。❌ 复核仍在(现 nand_ftl.c:152-179,仍 uint8_t buf[64])
N-13 dhara_nand_copy() 未处理 DHARA_NO_PAGE 语义(dst 为哨兵值时仍执行"读+编程") nand_ftl.c:174-186 dhara map 在回收/擦除路径会用 DHARA_NO_PAGE 试探;当前实现会把它当作真实页号 → 未定义行为。建议:if (dst == DHARA_NO_PAGE) { dhara_set_error(err, DHARA_E_UNSUPPORTED); return -1; }(返回不支持是安全语义)。❌ 复核仍在(现 :204-216,read→prog 直传)
N-14 写回缓存 s_cache_buf/s_cache_dirty 无掉电保护策略:CTRL_SYNC 已实现(:343-353)但没有周期性或掉电前的强制 flush nand_ftl.c:291-353 掉电窗口 = 最后一次未跨页的写。建议:空闲 N ms 由 defaultTask flush + 掉电检测(若硬件支持)或至少"日志落盘后立即 fsync"。🟡 复核:部分改善——整页写立即 flush(:377-381)、换页时 flush(:365-368)、CTRL_SYNC 走 flush + dhara_map_sync(:400-406);仍无空闲定时 flush
P2-3/P2-4 部分返回值丢失 台账 逐条修
N-15 页缓冲重复:nand_ftl 3×2,048(:s_page_buf/s_cache_buf/s_copy_buf)+ gd5f 的 s_bbt_page_buf[2048](仅 init 期使用) 对象 ZI:nand_ftl 6,216 + gd5f 2,596 BBT 页缓冲可与 s_copy_buf 复用或 init 后归还(§5.3 R-3,省 2–4 KB)。🟡 复核:已省一半——s_bbt_page_buf 由 2,048 B 收紧为 272 B(2026-09-21);nand_ftl 三块 2 KB 未合并(对象 ZI 仍 6,216)
— disk_ioctl GET_BLOCK_SIZE = 1 nand_ftl.c FatFs 的擦除块提示失效(FAT32 对齐优化拿不到信息);建议返回 64 页(=1 物理块)。❌ 复核仍在

4.4 配置引擎(App/cfg/cfg_file.c,1,190 行)

设计良好:描述符 + 段注册表、外科式(surgical)保存、模板生成、2 s 轮询(cfg_file_poll)响应 SYS_EVT_CFG_SAVE,属主单任务化。这是全项目设计最完整的一个模块。(原"首启旧档迁移"已于 2026-10-10 删除——该一次性路径把整条配置写链挂进启动期调用图,defaultTask 静态最坏链 +600 B。)

问题:

编号 问题 建议
P2-1 超长键行处理 台账
— 失败即静默回落工厂默认(:955-988:缺文件→模板→return -1;超长→模板→return -1;读失败→"factory defaults kept") 语义本身合理(不阻塞组网),但用户改了 IP 重启就丢且无告警——建议回落时必写一条自检记录并在 UI 装置信息页给出"当前为出厂配置"标识

4.5 日志子系统(App/log/sys_log_mcu.c)

设计良好:定长 64 B 记录(SYSLOG_REC_SIZE)、单写者(defaultTask)、RAM 队列 + syslog_drain、容量满时删除重建、/log 目录惰性创建。

2026-10-11 复核:日志侧新增了两个基线没有的东西,值得记一笔。 ① 控制台单写者日志(App/log/console_log.c,对象 ZI 4,120 B)——把"多任务直接 printf 到 USART1"收敛为单写者队列,配套 DBG_* 宏;代价已量化进 AGENTS.md(每条日志约 90 B Flash + 216 B 栈,深调用链模块另以 #define DBG_LINE_BUF_MAX 128U 收紧)。⚠ 这条代价模型正是 defaultTask 静态最坏链从 5,312 → 2,776 B 的主要杠杆,属本报告"内存"章节应当吸收的新事实。 ② 自检记录可查:selftest.log + 开机 selftest: no fault record 标记 + Fault 现场 .noinit 跨复位 → 现场故障从"不可知"变成"可回读"。

问题:

编号 问题 证据 建议
N-16 每条记录一次 f_open + f_write + f_close(:89-95),读侧同样每条 f_open/f_lseek/f_close(:118-126) 每条日志触发 FatFs 目录项读写 → 2–4 次 NAND 页读写;UI 翻记录时逐条 open/seek/close 写侧:常驻 FIL(仅 defaultTask 持有)+ 批量 flush;读侧:打开一次 + 缓存目录偏移(UI 已有 s_cache_valid/s_last_rec 雏形,可升级为页缓存)。❌ 2026-10-11 复核仍在:sys_log_mcu.c:123-140 每条仍 open/write/close(失败重建重试一次),wdev.log 同模式(:481-484);🟡 唯一改善 = FIL 已 static 化(:70,省 528 B 栈),无常驻打开句柄、无批量 flush
P2-9 mkdir 失败后不再重试(s_fs_ready 逻辑,:158-165) 台账 失败时保持未就绪 + 定期重试 + 上报自检
P2-10 容量轮转 f_unlink(:64)与 UI 读并发 台账 轮转只在写者、读者持版本号

4.6 UI 子系统

见 §6 专题(同时是"标准化"章节)。此处仅记设计层结论:三层拆分(数据 ui_menu_tab / 视图 ui_menu_view / 控制器 ui_menu)+ 内容页描述符框架方向正确,但框架未收敛到"新页面零框架改动",且 4 份 3,200 B 帧副本占 12.8 KB RAM(§5)。

2026-10-11 复核:两条结论均未变化。 框架仍要求"改 ui_menu.c 的手写 case + 加实例文件 + 双端注册"才能新增一页(§6.2 的 8 步一字未减);帧副本仍 4 份(ui_port.o ZI 6,420 + ui_net_sink.o 6,766 = 13,186 B,R-4 未办)。✅ 唯一实质改善在验证侧:golden 回归已建(§6.1),UI 改动从此有逐像素/逐字节基线可依赖。

4.7 外设驱动

模块 评价 问题
sd2506(RTC) I2C 超时齐备,降级策略明确(RTC 故障→默认时基 + 自检上报),良好 P2-5(负温度解析)、P2-6(WRTC 解锁)
tpafe5160(并行 ADC) BUSY 超时、EXTI 读取路径齐备;取数已收口为 tpafe5160_snapshot() 原子快照(帧序号只增不清 + FRSTDATA 自检) ⚠ V0.1 硬件缺陷(登记簿 N-18,V0.2 改版项;注意与本报告 N-18 同号不同义):8 路电气量被分给两片转换器 → 主板侧禁止跨转换器做相位/功率/功率因数运算;并行总线无批量采样框架,16 位端口读依赖 gpio.c 初值
rs485 结构简单 DE 时序用 HAL_Delay(1) 阻塞 1 ms(在调用任务里);crc.c/ringbuf.c 未注册 → 无帧校验、无接收环。❌ 复核:HAL_Delay(1) 仍在(rs485.c:91);crc.c/ringbuf.c 已删除(2026-10-02)→ 帧校验/接收环从零重做;rs485_task.c 仍未注册,消费者被 E-50(包撕裂)/E-49(TX 超时拉 DE)挡住
ch395f 事务包裹 begin/end、SPI 分频 8 为 PCB 约束(已文档化,勿动) 静态 DMA 缓冲 2×2,052 B(obj ZI 4,105),必须留 SRAM1

驱动共性缺口:没有统一的"错误码 + 超时 + 重试 + 自检上报"约定,各模块自定返回码(GD5F_OK / 0 / bool 混用)。建议(S-5):定义 bsp_err_t 统一错误码 + bsp_retry() 辅助 + 所有失败必须走 syslog(SYS_SELFTEST,...),作为驱动编写规范写入 docs/代码规范/。

4.8 测试体系

设计良好:test/board/test_config.h 的 TEST_SUITE_* 互斥(#error 保护)、CH395F 硬件层/NET 层 Phase 互斥、@test 一键闭环(烧录→wait-idle→标记校验)、模拟器 --cfgtest/--logtest。

⚠ 仍缺:覆盖率概念(原建议未办)、注册检查(check_registration.py)、主机侧对 lftpd/dhara 移植层的测试(N-12/N-13 这类缺陷至今只能靠人工读代码发现)。

问题:

编号 问题 建议
P1-10 CH395F 硬件测试套件 RAM 溢出(L6406E):HW 套 +8.7 KB、NET 层固件 +28 KB(s_rx_buf 32 KB) 测试缓冲改为分 Phase 复用同一块静态缓冲(union 或分阶段 memset);待处理内容.md 的这条待办应升级为正式任务。🟡 2026-10-11 复核:已升级为登记簿唯一 P 类收尾项(RAM 侧因堆迁 CCM 大幅缓解,剩"CH395F 套件构建实测链接确认 + @test 闭环"两步)

5. 内存优化(量化)

5.1 实测基线(map)

指标 2026-09-16(审查基线) 2026-10-11 复核 上限 复核占用
Code + RO-data 245,384 B(239.6 KB) 273,860 B(267.4 KB) 1 MB Flash 26.1%
ROM(含 RW 初始化表) 246,016 B 274,060 B 1 MB 26.1%
RW-data 632 B 824 B — —
ZI-data 125,952 B 139,896 B — —
SRAM1(0x20000000,128 KB) 126,584 B / 96.6%(余 4,488 B) 91,488 B 131,072 B 69.8%(余 39,584 B)
CCM(0x10000000,64 KB) 0 B / 0% 49,232 B(RW_CCM 49,184 + UNINIT_CCM 48) 65,536 B 75.1%(余 16,304 B)
Total RW (RW+ZI) 126,584 B 140,720 B — —

2026-10-11 二次实测(LVGL 死代码裁剪后,F-1/F-2/F-3 已落地):Code=194,276 / RO=15,872 / RW=876 / ZI=139,892 → ER_IROM1 210,148 B(20.0%)、ROM 合计 211,024 B(20.1%);RW_IRAM1 91,536 B(69.8%)与 RW_CCM 49,184 B(75.3%)不变(本次只动 Flash)。§5.4 的口径以此为准。

复核结论:R-2(启用 CCM)已部分落地——.sct 现为手工 scatter,除 ER_IROM1 + RW_IRAM1 0x20000000 0x00020000 外新增 RW_CCM 0x10000000 0x0000FF00(只收 heap_4.o (+RW +ZI))与 UNINIT_CCM 0x1000FF00 UNINIT 0x00000100(*(.noinit),崩溃现场跨复位保留区)。→ FreeRTOS 堆 ucHeap 49,152 B 已迁 CCM(.map ucHeap 0x10000020),任务栈从此不占 SRAM1,SRAM1 因此从 96.6% 降到 69.8%。⚠ 未迁的部分仍在 SRAM1:LVGL 池 work_mem_int 40,960 B(0x20008d00)、UI 帧/绘制缓冲 ui_port.o 6,420 B、UI 网络发送 ui_net_sink.o 6,766 B——即 R-2 的"−54,122 B"只兑现了 49,184 B(堆),LVGL 池与 UI 缓冲这 54 KB 里还剩 54,146 B 在 SRAM1(见 §5.3 R-2 状态)。

基线口径说明:上表取 2026-10-11 00:38 链接的 .map(HEAD 54d6d2d,干净树)。复核期间工作区另有未提交改动(adcTask 栈 512→640 words、新增 App/algo/adc_health.c/h + 主机侧套件),其 01:12 重链接结果为 Code=258,520 / RO=16,208 / RW=880 / ZI=139,896 → Code+RO 274,728 B(26.2%)、RW_IRAM1 91,544 B(69.8%)、RW_CCM 49,184 B(75.3%)。差异仅 +868 B Code / +56 B SRAM1,不影响本节任何结论;栈迁 CCM 后任务栈大小变化只影响堆内占用,不改 ZI 总量。

RW+ZI 主要对象(2026-10-11 复核,scripts/ram_report.py 排行;括号内为 2026-09-16 基线)

对象 / 符号 字节 所在区域 归属 DMA 可达性
ucHeap(heap_4.o) 49,184(49,184) CCM(0x10000020) FreeRTOS 堆 DMA 不可达——凡从堆里分配且被 DMA 直接寻址的缓冲都会失效(N-11 由此升级为结构性约束,见下)
work_mem_int(lv_mem_core_builtin.o,= LV_MEM_SIZE 40*1024) 40,960(40,960) SRAM1(0x20008d00) LVGL 池 纯 CPU,可迁 CCM(R-2 未完成项)
ui_net_sink.o(g_tx + g_lcdmem_data + g_rx) 6,766(6,762) SRAM1 UI 网络发送 可迁 CCM(CH395F 侧 memcpy 到自有 SRAM1 缓冲)
ui_port.o(g_ui_fbuf + g_ui_draw_buf) 6,420(6,412) SRAM1 UI 帧/绘制缓冲 可迁 CCM
nand_ftl.o(3×2,048 页缓冲 + 控制块) 6,216(6,216) SRAM1 存储 必须 SRAM1(N-11)
lftpd.o 4,768 SRAM1 FTP 任意
console_log.o(行环形缓冲) 4,120 SRAM1 日志 任意(TX 走 DMA → 实际必须 SRAM1)
ch395f.o(s_spi2_dma_tx/rx[2052]) 4,106(4,105) SRAM1 网络 必须 SRAM1
default_task.o 2,728 SRAM1 任务薄壳 —
gd5f2gq5ue.o(s_bbt_page_buf[2048] + BBT 表) ≈2,596(2,596) SRAM1 存储 必须 SRAM1
sys_log_mcu.o 1,924 SRAM1 日志 任意
cmsis_os2.o 1,724(1,724) SRAM1 RTOS 胶水 任意
sample_ring.o(L2 输入 2 周波环缓冲,新增) 1,448 SRAM1 算法 任意
startup_...o:STACK 0x400(HEAP 0x200 已被链接器移除) 1,024(1,536) SRAM1 MSP + MicroLIB 堆 任意
tasks.o(含 pxReadyTasksLists[56] = 1,120) 1,280(1,280) SRAM1 RTOS 任意(configMAX_PRIORITIES 仍 56 → R-5 未办)

两个静态池(49,184 + 40,960 = 90,144 B)仍是 RAM 问题的核心,但已分居两区:堆在 CCM(占 CCM 的 75.3%)、LVGL 池在 SRAM1(占 SRAM1 的 45%)。→ 下一步收益点在把 LVGL 池 + UI 两份缓冲(54,146 B)也搬进 CCM,或按 R-9 先实测下调 LV_MEM_SIZE。⚠ 堆进 CCM 带来一条新铁律:从 FreeRTOS 堆分配的缓冲不可被 DMA 直接寻址(DMA2 不可达 0x10000000)——本仓已实测到同款约束(ADC1 DMA 目的缓冲必须在 RW_IRAM1),应写入 docs/内存管理/。

Flash 侧(Code+RO):基线为 214 个含码对象;2026-10-11 复核为 182 个含码对象(按 .map 组件表对象名归类,App/+Src/+Drivers/BSP/ 合并为"应用/驱动自研",算法层单列)

子系统 Code+RO(基线) 占镜像 2026-10-11 占镜像
LVGL(含核心/绘制/控件/字体/缓存/图像) ≈150,600 61.5% 152,185 55.4%
应用/驱动自研(App/ + Src/ + Drivers/BSP/,不含 UI 页面) 10,468(仅 App+Src) 4.3% 27,076 9.9%
网络(NET + NETCFG + lftpd) 18,245 7.4% 21,629 7.9%
手写 UI 页面/框架(ui_*.o,现 6,036 行) 17,520 7.2% 18,535 6.8%
存储(FatFs + dhara + NAND) 8,222 3.4% 15,133 5.5%
HAL/CMSIS 11,866 4.8% 14,391 5.2%
FreeRTOS + CMSIS-RTOS 8,011 3.3% 12,258 4.5%
算法层 L1/L2(adc_input/phasor/sample_ring,新增) — — 1,589 0.6%
其他(startup/CMSIS 胶水等) — — 5,842 2.1%
合计(含库成员与填充) 245,384 100% 274,728 100%

复核要点:LVGL 的绝对量几乎没动(150,600 → 152,185),占比从 61.5% 降到 55.4% 纯粹是因为自研侧涨了 ≈30 KB(看门狗 + Fault 采集 + ADC 全链 + 相量层 + 日志/观测)。→ §5.4 的裁剪结论依旧成立且更划算:省下的 54.6 KB 现在相当于镜像的 19.9%。

5.2 结构性诊断(四条 + 未闭环部分)

  1. 64 KB CCM 空转,而 96.6% 的负载压在 128 KB SRAM1 上。 → 未闭环:LVGL 池 40,960 B + UI 两份缓冲 13,186 B 仍在 SRAM1;堆迁入后 CCM 只剩 16,304 B,装不下这 54 KB → R-2 的剩余部分**必须先做 R-9(下调 LV_MEM_SIZE)或 R-4(帧副本去重)**才有余地。
  2. 任务栈全部动态取自 FreeRTOS 堆:osThreadAttr_t 只设 stack_size 不设 stack_mem(Src/freertos.c:64-105)→ 栈 + idle/timer 栈 + TCB + IPC 全部从堆里扣,留给动态分配的实际余量很紧。 → 未闭环:7 条任务(defaultTask/netTask/ftpTask/testTask/uiTask/timeSyncTask/adcTask)栈合计 10,176 words = 40,704 B,仍全部动态取自堆。实测 load: OK (523B, heap free 8840B) → 空闲约 8.8 KB / 49,152 B,历史最低水位(xPortGetMinimumEverFreeHeapSize)6,640 B(2026-10-11 L2 相量 + L1-6 上链后由 7,152 B 降下来);同日 adcTask 栈由 512 抬到 640 words(静态最坏链 1,080 B),继续蚕食堆余量。R-6(栈静态化)是把"池内碎片"变"可核算静态区"的正解,且其前置条件(观测常开)已满足。
  3. 零观测:xPortGetFreeHeapSize/xPortGetMinimumEverFreeHeapSize/uxTaskGetStackHighWaterMark 全库零调用,无法回答"还能加多少"。 → 已解决(三面观测:[STACK] 运行时水位 + scripts/stack_report.py 静态最坏链 + ram_report.py/check_mem_numbers.py 门禁),不再列为待办。
  4. MicroLIB 512 B C 堆是隐形地雷:任何新代码写一个 malloc 都会中招。 → 未闭环:Heap_Size EQU 0x200 未变、.map 仍 Removing startup_stm32f407xx.o(HEAP), (512 bytes);铁律已进 AGENTS.md(只用 pvPortMalloc/vPortFree 或收进平台后端),但没有机械化检查——check_coding_standard.py 未含 malloc( 正则 → §5.5 第 5 条未落地。

5.3 RAM 优化路线(收益/风险/优先级)

编号 措施 收益 风险 优先级 2026-10-11 状态
R-2 启用 CCM:.sct 增 RW_IRAM2 0x10000000 0x00010000 { *(CCM_DATA) *(.bss.CCM_DATA) };对 LVGL 池用 LV_MEM_ATTR __attribute__((section("CCM_DATA"))),UI 四份帧缓冲同样加段属性;用 APP_CCM_ENABLE 宏包裹(克隆片兜底 = 全部回 SRAM1) −54,122 B(52.9 KiB) = 40,960 + 6,400 + 6,762 中:必须确保这些缓冲不被任何 DMA 直接寻址。已核实 UI 发送路径 memcpy 进 CH395F 自有 SRAM1 DMA 缓冲(ch395f.c:1132-1190),LVGL 为纯 CPU;NAND 路径(N-11)绝对不可迁入 P1,本季度 🟡 只做一半:.sct 加了 RW_CCM,但只收 heap_4.o(49,184 B)→ SRAM1 −49,184 B。LVGL 池 + UI 两份缓冲(54,146 B)未迁;APP_CCM_ENABLE 宏未采用(该宏已从 app_cfg.h 删除)。⚠ 复核新增前置条件:CCM 只剩 16,304 B,这 54 KB 装不下 → 必须先做 R-9 或 R-4
R-3 存储页缓冲合并:gd5f.s_bbt_page_buf 与 nand_ftl.s_copy_buf 复用(BBT 仅在 init 期使用);nand_ftl 三块 2 KB 中 s_page_buf(dhara scratch) 与 s_copy_buf 不可并发 −2 ~ −4 KB 中:需严格审查 dhara 调用序列(copy 期间不会用 map scratch) P2 ❌ 未办(合并未做);🟡 但 s_bbt_page_buf 已由 2,048 B 收紧为 272 B(2026-09-21,等效省 1,776 B)
R-4 UI 帧副本去重:同一 3,200 B 帧当前存 4 份(g_ui_draw_buf → g_ui_fbuf → g_lcdmem_data → g_tx)。让 ui_flush_cb 直接把 color_p 交给 sink、sink 在发送时才拼帧头(就地写 8 B 头 + 复用同一 buffer) −6.4 ~ −9.6 KB 中:需要 sink 与 LVGL 刷新生命周期对齐(同任务,可控) P1 ❌ 未办(ui_port.o 6,420 + ui_net_sink.o 6,766 = 13,186 B,副本数未变)
R-5 configMAX_PRIORITIES 56 → 32(实际最高用到 osPriorityHigh 级别) −480 B 低 P2 ❌ 未办(FreeRTOSConfig.h:69 仍 56;pxReadyTasksLists[56] = 1,120 B 仍在)
R-6 任务栈改静态分配(stack_mem 指向各自 .bss 数组),从堆里释放 32,768 B 堆可用 +32 KB(总量不变,但把"池内碎片"变成"可核算静态区") 低-中:静态后溢出检测更难发现;先测水位再定尺寸 P2 ❌ 未办(仍全部 stack_size 动态)。⚠ 前置条件已满足:观测已三面常开,stack_report.py 已给出每任务最坏链 → 可直接按实测值定尺寸
R-8 观测常开:新增 memStatsTask(最低优先级,1 Hz,512 w)打印 xPortGetFreeHeapSize / xPortGetMinimumEverFreeHeapSize / 各任务高水位 / lv_mem_monitor,并支持串口命令按需打印 0(可核算) 低 P1(是 R-2/R-6 的前置) 🟡 目标达成、形态不同:未建 memStatsTask,改为 ① 开机/自检打印 [STACK] 水位 + 堆空闲;② scripts/stack_report.py(静态最坏链)+ ram_report.py;③ check_mem_numbers.py 门禁 + docs/内存管理/data/ 快照。已足够支撑 R-2/R-6 决策
R-9 LV_MEM_SIZE 40 KB → 依 R-8 实测峰值下调(既有方案预估 16 KB) −16 ~ −24 KB 低(有 R-8 兜底) P2 ❌ 未办(lv_conf.h:44 仍 (40 * 1024))。⚠ 复核:simulator/tests/check_content_pages.py 已带池水位门禁 → 实测峰值数据已具备,本条只差改数值 + 真机复跑(⚠ 两端 lv_conf.h 要同步改)

组合结果预估(注意措施之间存在重叠,不可简单相加)

组合 SRAM1 变化 说明
基线 126,584 B / 96.6% 余 4,488 B
+ 实际执行(2026-10-11 实测) 91,488 B / 69.8% 余 39,584 B。实际做的 ≠ 预估路径:R-2 只搬堆(−49,184 B → CCM 49,232 B / 75.1%) + R-7(−8,192 B)+ BBT 缓冲收紧(−1,776 B);抵消项 = adcTask 栈、sample_ring 1,448 B、console_log 4,120 B、Fault/看门狗/日志新增
+ R-2(CCM 启用,原预估全量) 72,462 B / 55.3% −54,122 B;CCM 用 54,122 B(仍余 11 KB)。⚠ 复核:该预估已不可直接执行——堆先占了 CCM 49,184 B,CCM 只剩 16,304 B,装不下 LVGL 池 + UI 缓冲这 54 KB
+ R-3(存储页缓冲合并) ≈66.3 KB / 50.6% −2,048 ~ −4,096 B(BBT 收紧已等效省 1,776 B)
+ R-5(优先级 56→32) ≈65.8 KB / 50.2% −480 B

⚠️ R-2 与 R-4 不可叠加计算:R-4(UI 帧副本 4→2)省的是"副本份数",R-2 已把这四份缓冲整体搬到 CCM,因此 R-4 在 R-2 之后省的是 CCM 而非 SRAM1(CCM 侧 13,162 B → 6,762 B,为后续 UI 扩展腾出 6.4 KB CCM 空间)。若只做 R-4 不做 R-2,则直接为 SRAM1 省 6.4 KB——两条路线对 RAM 总量等价,但顺序决定收益落在哪个区域:先做 R-2 时,R-4 应重新定性为"CCM 优化"。

最终形态(原预估):SRAM1 约 50%、CCM 约 20% 使用率,L6406E(P1-10 测试镜像溢出)随之消解,并为 ADC 数据路径与 RS485 协议栈预留预算空间。

2026-10-11 修正(未闭环部分):原预估的执行顺序已不可按原样走——实际走到 SRAM1 69.8% / CCM 75.3%,因为选了"先搬堆"而非"先搬 LVGL 池 + UI 缓冲",CCM 已被堆占掉 75%(只剩 16,304 B)。要拿到剩余收益,顺序必须改为 R-9(下调 LV_MEM_SIZE,最省事的杠杆)或 R-4(帧副本去重)→ 再做 R-2 后半段。另两条仍开放:L6406E 未完全消解(登记簿留 P1-10 收尾:CH395F 套件构建实测链接确认 + @test 闭环);RS485 协议栈前置件 crc.c/ringbuf.c 已删除,需从零重做而非"注册"。

与 docs/内存管理/RAM使用分析与优化方案.md 的关系:本表沿用其阶段划分(阶段 0 测量 / 阶段 1.1 池收缩 / 阶段 1.2 CCM / 阶段 2 堆收缩),新增的是 ①R-4 帧副本去重(原文档未识别 4 份副本)②R-3 存储侧缓冲合并 ③N-11 的 DMA 可达性硬边界(原文档未提出,属实施风险关键点)④观测前置条件(已落地,见 §5.5)。

5.4 Flash 优化(未闭环:F-4 / F-5 / F-6)

F-1 / F-2 / F-3 已落地并销项(2026-10-11):Lib/lv_conf.h 与 simulator/lv_conf.h 对齐为同一特性子集(只留 LV_DRAW_SW_SUPPORT_I1、零控件、LV_USE_THEME_SIMPLE 0、LV_USE_FLEX/GRID 0)→ 实测省 64,576 B:ER_IROM1 274,724 → 210,148 B,Flash 26.3% → 20.1%。比本报告原估(54,630 B)多省约 10 KB——锚点消失后,blend_to_i1 内部按源格式的分支与 transform 的格式分支也一并退出。完整记录(对象级对照、上游 9.3.0 漏守卫的配套补丁、ui-verify 证据)见 ../内存管理/RAM使用分析与优化方案.md §2.6.4 与 ../UI设计/UI_设计架构.md §1.4 第 10 条。

编号 措施 可省 Flash 依据 2026-10-11 状态
F-4 关闭未用的绘制子模块与 LV_USE_SNAPSHOT(MCU 端)/LV_USE_OBJ_PROPERTY/LV_USE_FLOAT 等 可评估部分 ≈10 KB 新镜像对象实测:mask 3,862 + box_shadow 3,246 + draw_sw_img 2,960 + bin_decoder 1,972 + image_decoder 952 + triangle 930 + transform 598 + grad 420 + mask_rect 372 = 15,312 B;工程无图片、无阴影、无变换 ❌ 未办(数字已按 F-1 后的新镜像重测,transform 已从 4,340 降到 598)。⚠ 不可盲目关:mask/mask_rect/grad 是文字与矩形绘制的必经路径;真正可评估的是 box_shadow + draw_sw_img + bin_decoder + image_decoder + triangle ≈ 10.1 KB
F-5 从 uvprojx 中移除未使用的 LVGL 控件源文件(201 个 → 实际需要的子集) 编译时长 −40% 左右 与 F-2 配合;lv_conf 关开关后文件仍参与编译,删文件才省编译时间 ❌ 未办(uvprojx 现挂 306 个 .c,其中 LVGL 仍 201 个)。⚠ F-2 落地后这些 TU 现在编出来是空目标文件(文件级 #if 全灭),删文件的收益更纯粹、风险更低
F-6 双精度软浮点:dadd/dmul/ddiv/depilogue/dfixul(mf_w.l),来自 printf 的 %f/double 提升 −1 KB + 运行期性能 日志与 UI 全用定点;把 %f 改整数刻度 ❌ 未办(新镜像仍链入 dadd.o)

剩余可回收 ≈ 10–11 KB Flash(F-4 可评估部分 + F-6);F-5 省的是编译时长不是 Flash。原"≈50 KB 死代码"的主体(64,576 B)已在 2026-10-11 消除。

5.5 内存治理长效机制(+ 2026-10-11 复核)

  1. scripts/ram_report.py 升级为门禁:超阈值(如 SRAM1 > 92%)即失败,并在 PR 描述里自动附 Top-10 对象表。 → 🟡 部分实现:scripts/check_mem_numbers.py 已挂 pre-commit(以 .map 为唯一事实源,校验 docs/内存管理/RAM使用分析与优化方案.md 的 MEM_SNAPSHOT:BEGIN/END 受管区 + 散句陈旧数字扫描)——防的是"文档数字漂移",不是"占用超阈值"。⚠ 无 92% 阈值失败逻辑;且 CI 上无 .map 时该钩子自适应跳过(登记簿 A-5 残余)。
  2. 建立 [计划] scripts/ram_baseline.tsv(docs/内存管理/RAM使用分析与优化方案.md:172 已承诺但未创建):每次构建记录 Code/RO/RW/ZI/Top 对象,形成趋势。 → ❌ 未建;🟡 等效物已存在:docs/内存管理/data/ 下 5 份 ram_report/stack_report 快照留档(人工触发,非 tsv 趋势表)。
  3. RAM 预算台账(同上,文档已承诺 [计划] docs/内存管理/RAM预算台账.md):按子系统配额(网络 12 KB / 存储 12 KB / UI 16 KB / RTOS 48 KB / 应用 8 KB),超配额必须先申请。 → ❌ 未建。⚠ 复核:这条的价值在上升——堆已进 CCM 且只剩 16,304 B CCM 余量,"谁还能用 CCM"必须有台账,否则 R-2 后半段无法排期。
  4. DMA 可达性清单(新增,源于 N-11):文档化"哪些缓冲必须 SRAM1",并在 CCM 迁移时对该清单加 _Static_assert/地址断言(assert((addr & 0xFFF00000) == 0x20000000))。 → 🟡 半落地:独立清单文档未建,但铁律已进 AGENTS.md("DMA 目的缓冲必须在 SRAM,DMA2 不可达 CCM 0x10000000"、"改 ADC 流后回 NVIC 核对");代码侧另有运行时守卫(gd5f2gq5ue.c:110-136 不可达回落轮询)。⚠ 仍缺编译期/启动期断言,且需补一条新铁律:从 FreeRTOS 堆分配的缓冲不可被 DMA 直接寻址(堆已在 CCM)。
  5. 禁止新增 C 库 malloc:写进 docs/代码规范/,并在 check_coding_standard.py 加正则检查(App/、Drivers/ 内出现 malloc( 即告警)。 → 🟡 前半已做、后半未做:AGENTS.md 已有"自研代码禁用 C 库堆分配"铁律(含模拟器照不出来的原因);❌ check_coding_standard.py 未含 malloc( 正则 → 目前只靠人评审,属机械检查缺口。

6. UI 界面标准化实现方案

6.1 现状(框架已有,但未"闭环")

层 现状 评价
内容页 ui_content_page.c 描述符表 s_defs[] 仅 7 页(DEVICE_INFO/AC/DC/PASSWORD/NET_CFG/RUN_LOG/SELFTEST_LOG) ❌ 42 个菜单叶子中仅 7 个有内容页,menu_content_page_for()(ui_menu.c:155/:205)是手写 switch 映射。2026-10-11 复核:未改善——ui_menu_tab.h:28 仍 42 节点(叶子 33 个),有内容页的叶子 6 个(PASSWORD 非叶子映射);menu_content_page_for() 现位于 ui_menu.c:118-128,仍是手写 case 1u/3u/4u/30u/39u/40u:
设计令牌 无(每页自带 CP_SCREEN_W/H 160、CP_TITLE_Y 2、CP_PAGE_LINE_Y 143、CP_ROW_BUF_LEN 32 等布局常量) ❌ 魔法数字分散在各页。2026-10-11 复核:Drivers/BSP/UI/ui_tokens.h 仍不存在(U1 未办)
golden 回归 已建成,但不在文档承诺的 tools/ui_mock/,而是 simulator/tests/ 四脚本:check_menu_rows.py(菜单 8 状态全像素)、check_content_pages.py(内容页黄金参考逐字节 + 文本断言 + LVGL 池水位门禁)、regen_ref_png.py、check_remo_sink.py 🟡 有基线但未入门禁:两个脚本未挂 pre-commit / ui-verify;docs/UI设计/UI_界面制作流程.md / UI_自动化测试流程.md 仍指向不存在的 tools/ui_mock/(语义断链)

6.2 新增一个页面要走多少步(当前)

  1. ui_menu_tree.txt 加节点 → 2. 重跑 gen_menu.py → 3. 新建 ui_page_xxx.c → 4. 在 ui_content_page.c 的枚举/s_defs[] 注册 → 5. 在 ui_menu.c:menu_content_page_for() 手写 case N:(下标硬编码,表序一变即错)→ 6. 注册到 MDK-ARM/*.uvprojx → 7. 注册到 simulator/CMakeLists.txt → 8. 新汉字跑 font-regen。

5 步是纯机械劳动且 2 处易错(步骤 5 的魔法下标、步骤 6/7 漏注册)。

2026-10-11 复核:8 步流程一步未减。 步骤 5 的手写 case 仍在(ui_menu.c:118-128);步骤 6/7 的漏注册仍无机械校验(check_registration.py 未建)——但已新增一条间接暴露路径:若新文件落在新目录而没把目录加进 uvprojx 的 <IncludePath>,消费者 TU 会报 cannot open source input file(AGENTS.md 2026-10-10 记录),这类报错落在别的文件上、易误判,仍不等于"存在但未注册"的检查。

6.3 标准化目标:四层 + 一个闭环

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:

/* 屏幕与骨架 */
#define UI_SCREEN_W            160
#define UI_SCREEN_H            160
#define UI_TITLE_Y             2
#define UI_TITLE_H             11
#define UI_PAGE_LINE_Y         143     /* 页脚分隔线 */
#define UI_ROW_PITCH           13      /* 行距(12px 字体 + 1) */
#define UI_ROW_H               12
#define UI_LIST_TOP            15
#define UI_SCROLLBAR_W         2
#define UI_PAD_X               3
#define UI_BUF_STRLEN          32      /* = CP_ROW_BUF_LEN,统一一处 */
/* 语义色(1bpp:只有黑白,但保留语义名以便换深度时平移) */
#define UI_COL_FG              LV_COLOR_BLACK
#define UI_COL_BG              LV_COLOR_WHITE
#define UI_COL_SEL_FG          UI_COL_BG
#define UI_COL_SEL_BG          UI_COL_FG
/* 样式:只用 const 描述符(动态 lv_style 会每次泄漏 88 B,见 ui_content_page.c:100-104) */
extern const lv_style_const_prop_t UI_STYLE_FLAT[];

要点:保留现有"只用 lv_style_const_prop_t 常量样式"的纪律(这是已被验证的必要约束:动态样式每个 88 B 泄漏);令牌化后所有页面自动获得统一观感,改一行即全局生效。

L1 组件库(3 天) — 把 ui_menu_view.c / ui_content_page.c 中重复的自绘逻辑抽成无状态组件(纯 lv_layer_t 绘制,不创建 LVGL 对象,保持零控件、零池压力):

组件 职责 从何处抽取
ui_w_title 标题栏 + 分隔线 + 页码 ui_content_page.c 通用绘制链
ui_w_list 行列表 + 选中反白 + 滚动条 ui_menu_view.c 行绘制
ui_w_kv "名称: 值" 两列(装置信息/实时数据) ui_page_device_info.c、ui_page_ac.c
ui_w_records 定长记录分页浏览(含 s_cache_valid/s_last_rec 缓存范式) ui_page_runlog.c、ui_page_selftest.c(两文件结构几乎相同 → 直接合并)
ui_w_form 键值 + 光标 + 可编辑(含十进制/IP 输入约束) ui_page_net_cfg.c
ui_w_msgbox 确认/错误提示("密码输入错误"等) ui_page_password.c

L2 页面原型(4 类,覆盖全部剩余页面):

原型 适用菜单 数据契约
detail(KV 只读) 装置信息、交流量、直流量、遥信量 kv_provider_t{ count(); get(i, name, val); }
form(可编辑) 整定/定值/控制字/软压板、遥测死区/系数、遥信类型/防抖/双点、装置维护、通讯参数、网口/SNTP 直接绑定 cfg_file 描述符(复用配置引擎的段注册表,无需新数据结构)
records(分页日志) SOE/事故/操作/保护告警/保护启动/遥控/自检/运行记录/运行报告 record_provider_t{ count(); get(idx, ts, text); }(sys_log 已是这个形态)
action(确认型) 强制复归、清除记录、手动录波 提示 → 二次确认 → 执行 → 结果日志

菜单树 42 个叶子中,除已实现的 7 个外,其余 35 个可 100% 归入这 4 类原型(form 21 个、records 9 个、detail 3 个、action 2 个)。这意味着剩余页面工作量从"35 页 × 各自设计"降为"4 个原型 + 35 份数据绑定"。

2026-10-11 口径修正:42 是 ui_menu_tab.h:28 UI_MENU_TAB_COUNT(根 + 41 节点)的节点数,叶子实为 33 个;已有内容页的叶子 6 个 → 待落地 27 页(不是 35)。四类原型的分布按 27 页重算即可,结论不变。

L3 页面实例 — 目标形态(以"遥测死区"为例,全部新代码约 30 行):

static const ui_form_item_t s_items_deadzone[] = {
    /* cfg 段/键, 显示名, 类型, 最小, 最大, 权限 */
    { "deadzone", "ia", "A 相死区", UI_T_UINT, 0, 1000, UI_PERM_MODIFY },
    { "deadzone", "ib", "B 相死区", UI_T_UINT, 0, 1000, UI_PERM_MODIFY },
    { "deadzone", "ic", "C 相死区", UI_T_UINT, 0, 1000, UI_PERM_MODIFY },
};
const ui_page_desc_t ui_page_deadzone = UI_FORM_PAGE("遥测死区", s_items_deadzone);

生成器补全(1.5 天,关键收益) — 扩展 ui_menu_tree.txt 支持页绑定注解,让 gen_menu.py 一并产出注册表,消灭步骤 4/5 的手工 switch:

遥测死区 #P @page=deadzone @arch=form

产物:ui_page_registry.c 含 node_index → (page_id, archetype, desc*) 常量表 + ui_page_registry_for(node);ui_menu.c 只需一次调用。生成器同时校验:@page 指向的描述符是否存在、是否有节点声明了页但无实例、是否有实例未被任何节点引用(编译期断言或生成期报错)。

闭环门禁(1 天)——未闭环 2.5 条:

  1. [计划] scripts/check_registration.py(同 §2.3 S-3)保证新页面两端都注册 → ❌ 仍未建(scripts/ 现有 11 个脚本,无此项)。
  2. golden 回归(原方案:simulator --shot 导出 1bpp 帧 + PNG 逐像素比对)已按另一路径建成(simulator/tests/ 四脚本,见 §6.1)。未闭环:UI_界面制作流程.md:413 与 UI_自动化测试流程.md:104 仍指向不存在的 tools/ui_mock/;这些脚本未挂进 pre-commit / ui-verify 门禁。
  3. 字符集自动校验:构建前扫描所有 ui_*.c 中的汉字,与 ui_cn_chars.txt 求差集,缺失即 FAIL(把 font-regen 从"记得跑"变成"漏跑就红") → ❌ 未入门禁:font-regen skill 已把四步固化并写进 AGENTS.md("含新汉字的 UI 文案提交前先跑"),但仍是约定不是门禁(无 pre-commit 钩子做差集检查)。

6.4 UI 侧的内存纪律(必须写进规范)

规则 依据
页面不得新增静态大缓冲;文本缓冲统一用 UI_BUF_STRLEN 现有 s_ts/s_desc 已是范式
每页只用 lv_style_const_prop_t,禁止运行期 lv_style_init ui_content_page.c:100-104 已验证动态样式每次泄漏 88 B
优先自绘(lv_draw_* + lv_text_get_width),新控件需评审(会破坏 §5.4 F-2 已落地的裁剪,且须两端同步) LVGL 现占镜像 41.6%(87,706 / 210,623 B;裁剪前 55%)
帧缓冲任何改动必须遵守 R-4(副本数 ≤ 2) 当前 4 副本 12.8 KB
LVGL 池用量必须经 lv_mem_monitor 实测后才允许扩大 现 40 KB,预估可降到 16 KB

6.5 实施顺序(UI 专项,总计约 8 人日;2026-10-11 复核)

步骤 内容 交付判据 2026-10-11 状态
U1 ui_tokens.h 令牌化(纯替换,不改行为) @build PASS + 模拟器截图与替换前逐像素一致 ❌ 未办(ui_tokens.h 不存在)。🟡 但它的验收条件如今已可机械执行——golden 基线已建,"逐像素一致"从口头判据变成可跑断言
U2 ui_w_records(合并 runlog/selftest) 两页行为不变,代码 −1 个文件 ❌ 未办(ui_page_runlog.c / ui_page_selftest.c 仍是两份结构几乎相同的代码)
U3 ui_w_form + ui_page_desc_t 原型化 + 生成器产注册表 手工 menu_content_page_for() 删除;新增一页只需"改树 + 加实例文件" ❌ 未办(ui_page_registry.c / gen_page_registry.py 均不存在;手写 case 仍在)
U4 golden 回归(模拟器 --shot + PNG 比对)+ 字符集检查 + 注册检查入 pre-commit 三个新 hook 全绿 🟡 1/3 落地:golden 已建(simulator/tests/ 4 脚本,见 §6.1);❌ 字符集差集检查未入门禁;❌ 注册检查未入门禁
U5 用原型批量落剩余 35 页(每页 ≤30 行) 菜单树全绿、无 CONTENT_PAGE_COUNT 哨兵命中 ❌ 未动。复核修正口径:42 节点 / 33 叶子 / 6 个叶子有内容页 → 待做 27 页(不是 35)

7. 统一改进路线图

P0(1 周内,必须)

编号 事项 验收 2026-10-11 状态
N-02 FTP 鉴权(或生产构建默认关闭 FTP 写) 错误口令登录被 530 拒绝;@test 加 FTP 标记校验 ❌ 未办,且未登记进登记簿 → 复核建议:补登为 P1(现场可被任意主机改写 IP/MAC 并断网)
N-06 5 个未注册 .c:注册或显式删除 + 白名单 check_registration.py 零告警 🟡 半:3 个消解(2 删 1 注册);❌ rs485_task.c/lftpd_log.c 仍未处理,❌ 脚本与白名单未建

P1(1 个月内)

编号 事项 预期 2026-10-11 状态
N-04 / R-2 CCM 启用(LVGL 池 + UI 帧缓冲,APP_CCM_ENABLE 门控)+ N-11 DMA 边界文档与断言 SRAM1 96.6% → ~55% 🟡 半:堆 49,184 B 已进 CCM → SRAM1 69.8%;LVGL 池 + UI 缓冲未迁(CCM 只剩 16,304 B,必须先做 R-9/R-4);APP_CCM_ENABLE 未采用;N-11 侧改为运行时守卫,仍无编译期断言
R-4 UI 帧副本 4 → 2 −6.4 KB ❌ 未办(ui_port.o 6,420 + ui_net_sink.o 6,766 未变)
N-12/N-13 dhara_nand_is_free 全页校验;dhara_nand_copy 处理 DHARA_NO_PAGE 掉电注入测试通过 ❌ 未办(两处代码原样)
N-16 日志常驻 FIL + 批量 flush;UI 记录读缓存升级 单条日志 NAND 操作次数下降 ≥50% ❌ 未办(每条仍 open/write/close)
D-1 芯片手册图片降采样 / 拆 submodule clone 40 MB → ~8 MB ❌ 未办;⚠ 且 .git 已膨胀到 149 MB → 还需一次历史重写,优先级上升
U1–U4 UI 令牌化 + 原型库 + 生成器 + golden 新增一页 ≤30 行 🟡 U4 的 golden 已建(simulator/tests/ 4 脚本);U1/U2/U3 未动;U4 的注册检查与字符集检查未入门禁
P1-7 / P1-8 / P1-10 沿用台账(MAC 写序 / 重建失败恢复 / 测试 RAM 溢出) 台账闭环 P1-7/P1-8 本轮未复核,仍按台账跟踪;P1-10 🟡 收尾中(登记簿唯一 P 类项,剩"套件构建实测链接确认 + @test 闭环"两步)

P2(一个季度内)

R-3/R-5/R-6/R-9、F-4/F-5/F-6、S-1/S-2/S-5、P2-1..P2-16 余项。

2026-10-11 复核:R-3/R-5/R-6/R-9、F-4/F-5/F-6、S-1/S-2 ❌ 未办(NETCFG/UI 的语义层错位、4 处裸 extern 原样);S-5 🟡 已实现核心(App/fs/vol_lock.c + 主机侧 33 例单测),"可选全序列卷锁"的剩余范围在登记簿。

P3(机会性)

UI 剩余页面批量落地(U5)、目录级层次迁移(S-1 的物理搬迁)、app_cfg.h 开关体系重构、Lib/lv_conf.h 迁至 Config/。

2026-10-11 复核:四条全部未动。⚠ 优先级更新:Lib/lv_conf.h 迁至 Config/ 原建议"与 F-1..F-3 合并做"——F-1..F-3 已于同日落地但位置未迁,现需单独做,且要一并处理 simulator/lv_conf.h(两份必须保持同一特性子集,见 ../UI设计/UI_设计架构.md §1.4 第 10 条);U5 的口径改为 27 页(33 叶子 − 6 已实现)。

交付物清单(可直接排期)

交付物 类型 来源条目 2026-10-11 状态
[计划] scripts/check_registration.py 新脚本 + pre-commit hook S-3 / N-06 ❌ 仍未建(scripts/ 11 个脚本里没有它)——这是本报告里**唯一一条"1 小时工作量、至今未做"**的机械检查
[计划] scripts/ram_baseline.tsv + [计划] docs/内存管理/RAM预算台账.md 文档承诺落地 §5.5 ❌ 未建;🟡 等效物 = docs/内存管理/data/ 5 份快照 + MEM_SNAPSHOT 受管区(无趋势、无配额)
Drivers/BSP/UI/ui_tokens.h 令牌层 U1 ❌ 未建
Drivers/BSP/UI/ui_w_{list,kv,records,form,msgbox,title}.{c,h} 组件库(需双端注册) U2/U3 ❌ 未建
tools/menu_gen/gen_menu.py 扩展 + ui_page_registry.c 生成 生成器 U3 ❌ 未建(gen_menu.py 未扩展,注册表不存在)
[计划] docs/内存管理/DMA可达性清单.md 规范(含"哪些缓冲禁止进 CCM") N-11 🟡 半:独立清单未建,但铁律已进 AGENTS.md + 代码有运行时守卫;⚠ 需补"堆缓冲不可 DMA"这一新条

附录 A 关键证据索引

结论 证据 2026-10-11 复核
SRAM1 96.6%、余 4,488 B map:Total RW Size (RW Data + ZI Data) 126584 (123.62kB);scatter RW_IRAM1 0x20000000 0x00020000 🟡 已缓解:RW_IRAM1 91,488 / 131,072 = 69.8%(余 39,584 B);Total RW 140,720 B
MicroLIB C 堆 = 512 B map :17405 0x2001e878 - 0x00000200 Zero RW HEAP startup_stm32f407xx.o;__heap_limit 0x2001ea78;MDK-ARM/startup_stm32f407xx.s:43 Heap_Size EQU 0x200 ✅ 结论不变:Heap_Size EQU 0x200 未改,map 现 Removing startup_stm32f407xx.o(HEAP), (512 bytes)(未被引用 → 整段移除)
lftpd 已迁 FreeRTOS 堆 map lftpd_io.o refers to heap_4.o(i.pvPortMalloc) 不变
任务栈全部动态分配 Src/freertos.c:64-105 的 osThreadAttr_t 无 .stack_mem;freertos.o ZI 仅 1,104 B 不变(R-6 未办);7 任务栈合计 10,176 words,其中 adcTask 已抬到 640 words
NAND 直接 DMA 调用方缓冲 gd5f2gq5ue.c:200、:258(GD5F_DMA_THRESHOLD = 32) 🟡 模式未变(:325 等),阈值改 1024(gd5f2gq5ue.h:78)+ 新增运行时 DMA 可达性守卫(:110-136,回落轮询)
CH395F memcpy 自有缓冲 ch395f.c:1132-1190 不变(UI 缓冲仍可迁 CCM 的前提依旧成立)
FTP 无鉴权 Drivers/BSP/NET/lftpd/lftpd.c:367-371 ❌ 仍在:现 lftpd.c:375-379(cmd_pass)/ :553-557(cmd_user)
端口分配 App/sync/time_sync.c:25 TIME_SYNC_PORT 8888;Drivers/BSP/UI/ui_net_sink.h:29 UI_NET_SINK_PORT 7003;FTP 21 不变
42 菜单叶子 / 7 内容页 Drivers/BSP/UI/ui_menu_tab.c(生成,42 节点);ui_content_page.c:84-92 s_defs[] 🟡 口径修正:ui_menu_tab.h:28 UI_MENU_TAB_COUNT 42(根 + 41 节点)→ 叶子 33、有内容页的叶子 6(枚举 7 页含 PASSWORD)
手写映射 ui_menu.c:155/:205 menu_content_page_for() 仍在,现位于 ui_menu.c:118-128(手写 case 1u/3u/4u/30u/39u/40u:)
文档体量 工作树字节统计(见 §3.1 表) 见 §3.1 复核列:受控 2,363 文件 / 77.7 MB,docs/ 849 / 44.4 MB,图片 703 张 / 36.3 MB,.git 149.3 MB

附录 C 评审中确认"无需改动"的设计(避免后续误改)

项 结论
CH395F SPI 分频 = 8 PCB 约束,禁止"优化"(AGENTS 铁律,实测一致)
就绪屏障用 Event Flags 而非魔法延时 正确,保留
FatFs FF_FS_REENTRANT=1 + 真实互斥 正确,保留(多数移植偷工)
配置引擎属主 = defaultTask 正确(P1-3 已收敛),任何新代码不得跨任务触碰
UI 只用 lv_style_const_prop_t 正确且必要,禁止"顺手改成动态样式"
ffsystem.c 引用 sys_clock.h 有意为之(中间件需要真实时间),保留但在分层图中显式标注
UI 自绘而非 LVGL 控件 正确(1 bpp + 小 RAM),并是 Flash 裁剪的前提

报告结束。

2026-10-11 复核收尾:

  • ⚠ 台账漏登两条:本报告 N-02(FTP 无鉴权) 与 N-12/N-13(dhara 移植缺陷) 既未修复也未登记在 ../待处理内容.md → 建议补登。
  • ⚠ 编号撞车:本报告曾用 N-18 指"无 CI"(该条已销项删除),而登记簿的 N-18 = "8 路电气量分属两片转换器(V0.2 改版项)"。引用时一律带文档名。
  • [计划] docs/内存管理/RAM预算台账.md 与 scripts/ram_baseline.tsv 仍未创建(见 §5.5)。
  • 本报告按 docs/README.md 的定位是体系层评审报告,不是待办池——待办一律以 ../待处理内容.md 为准;本报告只保留未闭环问题的发现 + 证据,修复即销项。

变更记录

日期 类型 说明
2026-09-18 初版 全量审查;基线 HEAD = 2d0ec35、.map 链接于 2026-09-16 12:33;产出 N-01..N-18 / D-1..D-7 / S-1..S-5 / R-1..R-9 / F-1..F-6 / U1..U5 编号体系
2026-09-18 实施记录 N-01 按方案 A 落地(两处必要调整:分配收进 cfg_file_store 后端、_Static_assert 改 typedef 负数组)
2026-09-25 归档 §3 文档分布章节标注"已归档(章节保留作历史)"
2026-10-10 就地更新 N-10 改走"门控"并销项
2026-10-11 现状复核 按当前代码与 .map 重跑全部数字并就地标注(§0.1 评级、§1.3 口径、§2.1 规模、§3.1 体量、§4.x 逐条、§5.1 基线双列 + 对象排行、§5.3/§5.4 状态列、§6/§7 状态);复核基线 HEAD = 54d6d2d + 00:38 .map
2026-10-11 修复即销项(本次) 与登记簿同一规则:已修复项的分析正文与表格行从本报告删除,报告只呈现未闭环问题(874 → 733 行,114 KB → 84 KB)。删除范围:§4.4.1 N-01 方案与实施记录、§4.1 N-03a/b·N-07·N-08·P2-13、§4.2 N-10、§4.8 P2-14/P2-15/N-17/N-18、§3.3 D-2..D-7、§5.3 R-1/R-7、§6.1 已闭环四层与字体、§7 P0/P1 已办行、交付物清单已建项、附录 A 已修行、附录 B 断链清单整节。§0.2/§0.4 改写为未闭环清单。原始推理与取证在未闭环条目中一律保留
2026-10-11 实施 + 销项:F-1/F-2/F-3 Lib/lv_conf.h 与 simulator/lv_conf.h 对齐为同一特性子集(只留 LV_DRAW_SW_SUPPORT_I1、5 个未用控件类全关、LV_USE_THEME_SIMPLE 0、LV_USE_FLEX/GRID 0,并删掉 9.3 不存在的 LV_USE_THEME_BASIC 与无效 LV_USE_OBJ);配套补 lv_draw_sw_blend_to_i1.c 上游漏的守卫 → 省 64,576 B:ER_IROM1 274,724 → 210,148 B,Flash 26.3% → 20.1%(比原估 54,630 B 多约 10 KB)。验证:@build 0 错 0 警;ui-verify 五步全过(11 页黄金参考在裁剪前生成、裁剪后逐字节一致,菜单 8 状态像素一致,池水位 5440/5520 → 5368/5448);@test 真机 PASS。本报告同步:§5.4 只留 F-4/F-5/F-6(数字按新镜像重测)、§0.1 Flash 评级 B→A−、§0.2 Top 6→Top 5、§0.4 Flash 行、§5.1 加二次实测、附录 A 删两条已闭环证据行

已销项条目的记录去处

删除前已逐条核对:以下去处文档均含完整记录,删了不丢信息。

已销项条目 去处
N-01(配置引擎 C 堆必失败) ../子系统设计/配置文件设计说明.md(v1.13,§6.2/§8)
N-03a/N-03b/N-07/N-08(IWDG、configASSERT、两个钩子、4 个 Fault 现场) ../子系统设计/看门狗设计说明.md(§4.4、§10.1/§10.2)+ AGENTS.md「平台约束」
N-10(UI accept 门控) ../UI设计/RemoDispBus协议与接收设计说明.md §3 + 看门狗设计说明 §11.4
P1-1(ADC 数据路径) ../子系统设计/ADC采样节拍设计说明.md §10/§11 + App/algo/README.md
N-17(栈/堆水位观测) ../内存管理/RAM使用分析与优化方案.md + ../测试说明/实机测试实施指南.md
N-18(本报告口径:无 CI) ../测试说明/测试开发标准流程.md §9 + ../环境与部署/CI配置指南.md
P2-13 / P2-14 / P2-15 AGENTS.md(test-suite-switch、keil-build、stlink-flash)+ ../测试说明/ST-LINK_CLI_复位使用说明.md
D-2 / D-3 / D-4 / D-5 / D-6 / D-7 ../文档规范.md + ../代码规范/pre-commit使用说明.md;断链由 scripts/check_doc_links.py 门禁接管(--all = 129 文档 / 0 失效)
U4(golden 回归) simulator/tests/ 四脚本 + ../UI设计/UI_自动化测试流程.md
R-1 / R-7 ../子系统设计/看门狗设计说明.md + AGENTS.md 任务表(scripts/gen_task_table.py 生成区)
F-1 / F-2 / F-3(LVGL 死代码裁剪,2026-10-11) ../内存管理/RAM使用分析与优化方案.md §2.6.4(对象级前后对照 + 上游漏守卫的配套补丁);../UI设计/UI_设计架构.md §1.4 第 10 条(两份 lv_conf.h 必须同一特性子集这条新硬约束);../UI设计/UI_自动化测试流程.md 变更记录(池水位基线 5440/5520 → 5368/5448 及理由)