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