数据面(App/algo,纯逻辑,双端编译) - wave_ring:8 路 × 320 点列优先环(1 kHz,预触发 160 + 后触发 160),adcTask 唯一写者;触发/冻结/封存/冷却四态,冻结持续到落盘回执才解冻,滞留 2 s 自弃 兜底——下一次真故障无波可录比丢一段更贵 - wave_store:段头编解码 + 拉模式打包游标 + 自带无表位运算 CRC-16/MODBUS(带 锚点自检)+ 段名与整段长度等式;格式不依赖平台类型宽度 - 段头只存原始码值与封存瞬间生效系数,工程量只当单位标签、不进任何算式 - 三套主机单测(环 113 / 编解码 170 / 触发状态机 110),全部无桩、生产源原样 纳入;新文件补 stddef 是 ARMCC 与主机差异,主机侧照不出这条分叉 - test/host/wave_read.py 是按 §6.2 的独立第二实现(dump/fft/shape/plot),92 条 自洽断言;三份合成段互读双校验全过、哈希钉死,格式面双向闭合 - 三套新单测不加专属钩子,由覆盖率总闸全量真跑(沿用既有裁定) 接线(任务层) - adc_cal:新增生效系数的 32 位定点原值入口(与既有浮点入口共用生效视图), 段头按最低有效位精确记账,不经浮点回算 - adcTask:每拍压 8 路语义原始码值并推进状态机,封存那一拍同拍交棒封存瞬间事实; 台架定周期自抓走双层编译门,产品镜像里零存在;串口新增一条 wave: 周期行 - defaultTask:卷空闲后一次心跳整段搬完(try 锁 + 256 字节分片 + 一次完整开档到 关档),成败都回执;失败先删半成品再计错,段号只在成功时推进;持锁超 50 ms 补一条告警并记历史峰值(喂狗余量可核) - Lead 复核补第四道并发陷阱:采集任务优先级高于落盘道,搬段中途它可能正好把滞留 超时的本段自弃、甚至封出下一段——数据区校验和是对「实际读到的字节」算的, 半新半旧的段照样自洽,故关档后复检环侧身份判废:删文件、不补账、段号不推进 - Lead 复核抓到触发挂号不是原子操作(旧实现分三个字节写,消费者可在窗口里读到 上一段的触发源与元件号):已把载荷压进一个字节,越界当场拒且不记任何计数; 段文件字节布局零改动,三份合成段哈希逐字节不变 - protectTask:保护启动上升沿同点请求录波,一拍一次、元件号取最低位;判据本体 零改动 人机(160×160 单色屏) - 菜单树节点 26「手动录波」绑页面 + 记录查询组末新增「录波回放」叶,重跑生成器 得 43 节点 / 34 叶 / 30 已绑,页面映射 28 → 30 - 回放页一屏一路、触发点前后各一周波共 40 点、每点 3 像素阶梯线(只用水平与 垂直两种线段——单色屏斜线会被阈值裁掉);零点虚线、触发光标、窗口峰值、 单位徽标与形状受限徽标;空态与坏段各一句显式文案,绝不用半截波形冒充数据 - ui_data 新增录波注入通道(六件操作表、三态返回);执行侧胶水带段头缓存、 手动请求后连缓存一起作废、逐点走全仓唯一的码值换算入口 - 模拟器接真实环与编解码合成两段(含坏段与空态旗标),6 张新黄金参考,既有 60 张逐字节零漂移未用更新模式;字库 218 → 224 字 / 319 字形 - 两份图形配置里图表组件仍为关;零堆分配;页面池水位不变 - 回放读路径的 uiTask 阻塞上界按实码写清:一次渲染 = 段表刷新 + 未缓存段头最坏 22 次小读;已验段只剩一次开窗读 + 40 点解码;无实测数不编,实测归台架、 仪器用已在跑的单轮工作耗时探针,不新加埋点 台账与文档 - 内存台账:录波环与落盘/回放静态逐条入册,三档对照 81,536 → 88,296 → 89,936 字节(62.2% → 68.6%),累计 +8,400;预估 5.9 KB 与实测 +6,760 的差额点名; 散列文件头注释同步;任务栈表核对后无需抬栈;体积趋势行与同码凭证由权威构建 重打(0 错误 0 警告,四项体积与上一轮相同——本轮改动只有注释与声明位置) - 设计说明:§5 补第四条并发陷阱;§6.3 定字段口径(两份独立实现的共同依据); §7 回填实测;§9 周期行写实;§10 台架判据改两段式且不比绝对相位;§12 补读路径 风险行;抬头状态改成「代码面已交付、未做完的只有板级那一轮」 - 卡片:软件闭环一栏列证据;台架三条按用户裁定保持未完成,不做虚假销项 - 口径同步:数据可信、UI 补齐、页面补齐规划、产品需求、采集短板评测、任务规划 索引与两份 README;陈旧现状句清扫 16 处(带日期戳的历史取数行一律不动) - 门禁说明:nested-comment 记仓库根探测必须兼容 linked worktree 的 gitdir 文件 形态(修前该钩子在任务树里恒报环境错、静默不可用);coding-standard 记 「函数头注释超过 24 行就把函数功能挤出回看窗口」这条机制与本次现场 - 覆盖率基线登记两个新覆盖模块(套件 41 → 44 套);环单测重打后该文件有效行 227 → 226、覆盖行 226 → 225,比率仍 99.6%,是分母变小不是覆盖率回退 为什么是单一提交:文档链接、双端注册、生成器幂等(菜单与字库)、内存数字与 体积事实源、编号门禁这五道整仓门禁要求代码、页面、菜单树、字库、文档与台账 同处一个提交才自洽,按数据面/接线/人机/台账拆成四笔必然红其中至少四道 编号门禁新增行 14 处命中已按语义改写:魔数常量按字符命名、段名样例改由格式串 生成、频谱表头改 f / 2x / 5x 并加一行图例;改写后自洽断言 92 条仍全过
19 KiB
STM32F4-Base 内存内容分配策略
版本:v1.0 日期:2026-09-21 适用工程:STM32F4-Base(STM32F407ZGTx @168MHz,FreeRTOS V10.3.1 + CMSIS-RTOS2,Keil MDK v5 / ARMCC) 状态:当前有效策略(台架验证:产品构建台架日志代次 68 干净启动 61 行;存储套件 20/20 PASSED;恢复跑 3/3 PASSED) 关联文档:
RAM使用分析与优化方案.md(内存管理主文档:容量审计 + 优化方案 + 观测工具 + 基线数据) 布局实现:MDK-ARM/STM32F407-Demo/STM32F407-Demo.sct
1. 本文档目的
定义"哪类内存内容放进哪个 RAM 区域"的分配规则,并记录 2026-09-21 "CCM 驻留者:FreeRTOS 堆 vs LVGL" 的裁决过程与理由。任何 scatter 布局调整、新增大缓冲、换料(芯片批次变化)前必须先查本文档。
2. 硬件区域事实(本机实测)
| 区域 | 地址 | 大小 | CPU 访问 | DMA1/DMA2 |
|---|---|---|---|---|
| RW_IRAM1(SRAM1+SRAM2) | 0x20000000 | 128KB | 168MHz 零等待 | 可达 |
| RW_CCM(SRAM3/CCM) | 0x10000000 | 64KB | 168MHz 零等待(与主 SRAM 同速) | 不可达(本机实证) |
关键事实:
- CCM 对 CPU 是全速内存。Cortex-M4 无 语义取数与有效性 cache,"CCM 是慢内存"的直觉只适用于带 cache 的核(M7/出口与记录/H7 的非 cacheable 旁路),在本芯片上不成立——任何"按速度分配区域"的论证对本文档无效。
- DMA 对 CCM 地址的传输静默不落地。2026-09-21 首轮 CCM 实验实证:以 CCM 上栈缓冲(dhara meta 132B / is_free 64B)为 DMA 源/目的时传输不生效,引发 FTL 重格式化循环与幻影坏块。此后 DMA 阈值提至 1024B 消除。工程规则按"DMA 不可达 CCM"执行,不论数据手册如何表述。
- CCM 存在性已用
ST-LINK_CLI -r32 0x10000000本机实测可读写;克隆片可能缺 SRAM3,换料必须重测并回退布局。 - 工程不进入 Stop/Standby 低功耗模式,两区域掉电保留行为无差异,不构成分配判据。
勘误(原《STM32F4-Base 内存使用分析报告》2026-09-11 快照,随主文档合并保留于此):该报告内存地图中 "SRAM3 64KB (0x2001_C000)" 为笔误——F407 的 SRAM3 即 CCM,位于
0x10000000(AXI);AHB2 侧只有 SRAM1(112KB)+SRAM2(16KB)。以本文为准(RAM使用分析与优化方案.md§8.4 与内存布局_RAM.svg均按此口径)。
3. 全板 DMA 使用方清点(DMA 缓冲必须为 SRAM 静态)
| 使用方 | 外设 | DMA 缓冲 | 位置 |
|---|---|---|---|
| GD5F2GQ5UE NAND | SPI1 | 文件作用域静态页缓冲(2048B×3,gd5f2gq5ue.c) |
SRAM |
| CH395F 以太网 | SPI2 | s_spi2_dma_tx_buf / s_spi2_dma_rx_buf(文件静态,ch395f.c) |
SRAM |
| RS485 / USART1 调试 | UART4 / UART1 | 纯轮询,无 DMA | — |
推论:全板 DMA 只寻址驱动文件静态缓冲;堆内存(pvPortMalloc)、任务栈、UI 缓冲均不被 DMA 寻址。UI 帧下行路径 g_ui_fbuf →(CPU memcpy)→ g_tx →(CPU memcpy)→ s_spi2_dma_tx_buf → SPI2 DMA 已逐跳核实为 CPU 拷贝 + 静态 DMA 缓冲。该结论在 GD5F 驱动内由 gd5f_dma_buf_ok() 持续强制(见 R2),并有 TC-GD5F-704/705 负路径用例实证。
4. 内容分配规则
-
R1(CCM 驻留):CCM 只放"CPU-only 且损坏即致命、无自愈"的内存。当前唯一驻留者 = FreeRTOS 堆(
heap_4.o,56KB——2026-10-12 B5 由 48KB 扩容,.ioc语义取数与有效性参数configTOTAL_HEAP_SIZE=57344;全部任务栈/队列/定时器)。scatter 的 RW_CCM 节不得再列其他对象;任何新增必须经 §5 式裁决并走 §7 完整验证阶梯。 -
R2(DMA 缓冲):只允许文件作用域静态缓冲,必须落 SRAM。三层防线缺一不可:
- 窗口防线(编译期):DMA 白名单窗口
GD5F_DMA_RAM_BASE/END(0x20000000~0x20020000,须与 scatterRW_IRAM1一致)与 CCM 窗口永不得重叠,另锁死阈值契约132 ≤ GD5F_DMA_THRESHOLD < GD5F_PAGE_SIZE——两条都用项目惯用的负数组 typedef 断言(AC5 不支持 C11_Static_assert),有人改窗口常量或阈值当场编译失败(gd5f2gq5ue.h)。 - 尺寸防线:GD5F DMA 阈值
GD5F_DMA_THRESHOLD=1024(>1024B 才走 DMA;阈值以下一律 CPU 轮询)。取值须 ≥ 存储路径上可能出现在任务栈的最大缓冲(dhara meta 132B、dhara_nand_is_free64B)、且 < 页大小(2048,保住页级热路径的 DMA)。 - 地址防线(运行时 + 启动期):
gd5f_dma_buf_ok()运行时校验整段缓冲是否落在 SRAM 区间(0x20000000~0x2001FFFF),越界者拒走 DMA、回落 CPU 轮询,并打印[ERR][NAND] RX/TX DMA buffer not DMA-reachable (p=… size=…) fallback poll;nand_ftl.c在 init 期对三块静态页缓冲预先过同一守卫,把"scatter 被改动导致静态缓冲落进 CCM"变成开机即报,而不是静默传错。
阈值只挡得住"当前已知的小栈缓冲",挡不住"将来新加的 >1024B 非 SRAM 缓冲";地址守卫把 R2/R3 从约定升级为运行时强制。守卫负路径已由 TC-GD5F-704/705 在台架实证(读/写两方向各一,探针缓冲取自 CCM 堆,数据仍正确落位)。 ⚠ 缓冲自身地址无法编译期断言:
.bss地址链接期才确定,AC5 的常量表达式取不到(__builtin_*系在 MicroLIB 下亦不可用,实证见Src/main.cError_Handler注释)→ 静态缓冲落位靠 scatter + 启动期自检双层保证,别再试图"加个_Static_assert"。 - 窗口防线(编译期):DMA 白名单窗口
-
R3(堆/栈隔离):禁止以
pvPortMalloc所得内存或任务栈为 DMA 源/目的(09-21 事故类:DMA 静默不落地 = 静默数据损坏,比报错更难查)。该约束在 GD5F 驱动内已由gd5f_dma_buf_ok()运行时强制(违反即回落轮询 +[ERR]日志),并有 TC-GD5F-704/705 覆盖读、写两个方向。 -
R4(UI/LVGL 留 SRAM):LVGL 池 + 帧缓冲为自愈型内容(每帧由 widget 树全量重渲染,脏帧下一帧即消失),不需要保护区;且留 SRAM 保留 DMA 门(将来帧推送若要 DMA 加速必须 SRAM 可达);其越界风险已由 32B 保护带 + canary + 溢出钩子兜住(见 §5.3)。
-
R5(新内容放置判定表):
| 内容性质(判定依据) | 放置 | 理由 |
|---|---|---|
| DMA 源/目的 | SRAM(强制),文件作用域静态 | CCM 不可达;堆/栈禁 DMA(R2/R3) |
| CPU-only + 损坏即致命、无自愈(调度器类) | CCM(容量允许时) | 保护区:DMA 野地址到不了、SRAM 溢出跨不过来 |
| CPU-only + 自愈/瞬态(帧缓冲、渲染状态) | SRAM | 无需保护;DMA 门保留(R4) |
| CPU-only + 持久程序状态(配置引擎、FAT 卷状态、网络状态机) | SRAM(默认) | 损坏属中级事故(可自愈/可重格式化恢复);CCM 配额留给致命类 |
- R6(容量台账):任何新增静态缓冲 >4KB、或改动 scatter/堆大小,必须先核 §6 台账余量,改后同步更新 §6 数字、
.sct头注释与AGENTS.md栈表。
5. 决策记录:2026-09-21 CCM 驻留者裁决(堆 vs LVGL)
5.1 议题
CCM 64KB 只能容纳 {FreeRTOS 堆 48KB} 与 {LVGL 集合 ~52.5KB} 之一(两者合计 101KB > 64KB)。候选方案:
- 方案 A(现行):堆 → CCM;LVGL 集合 → SRAM
- 方案 B(提议,未采纳):LVGL 集合 → CCM;堆 → SRAM
5.2 候选比较
当时提出的两条判据经核实均为事实但不具区分力:
- "UI 不用 DMA" ✓ 属实(§3 路径核实:DMA 只寻址驱动静态缓冲)→ 堆同样不用 DMA,两候选在 DMA 轴等价
- "图形不紧迫速度" ✗ 无效判据(§2:M4 无 cache,CCM 与主 SRAM 对 CPU 同速)→ 两候选在速度轴等价
真正可判别的轴是**"该内存损坏后的后果" × "DMA 不可达带来的隔离收益"**:
| 内存块 | 大小 | 损坏后果 | 自愈能力 | DMA 不可达的收益 |
|---|---|---|---|---|
| FreeRTOS 堆(调度器控制块 + 6 任务栈 + 队列/定时器) | 48KB | 调度静默损坏 = HardFault/复位循环/死系统,最坏结果 | 无 | 大:DMA 野地址传输到不了 CCM(表现为"数据没到",可检测),调度器字节免疫 DMA 类故障 |
| LVGL 集合(16KB 池 + 帧 4 份副本) | ~28.5KB | 最坏 = 屏幕花屏 | 强(每帧全量重渲染;已实证的 1 字节越界由 32B 保护带兜住) | 无(永不用 DMA,放 CCM 只会关上 DMA 门) |
分配规则:"损坏即致命 + 无自愈"的内存进保护区;"自愈型"内存留 SRAM。
5.3 裁决:方案 A(堆 → CCM)——即当前布局
理由(按权重排序):
- 致命类优先:方案 B 把 48KB 调度内存放回 SRAM,与全部潜在溢出源(含 FatFS、dhara 两个有实证事故史的第三方组件)同处一域;本项目实证过的故障族正是 1 字节类越界。
- DMA 不可达对堆是防护:任何 DMA 野地址/错地址传输都无法落入 CCM(静默不落地,发起方表现为可检测的"数据未到"),调度器因此免疫 DMA 类故障;方案 B 中该防护失效。
- LVGL 残余风险已被现有手段兜住:32B 保护带 + canary 实证 +
configCHECK_FOR_STACK_OVERFLOW=2+ malloc 失败钩子;已发生的 1 字节越界被完整 containment。 - "UI bug 最多弄坏屏幕、弄不死系统"是台架既成事实:越界发生时 CCM 中的调度器毫发无伤(台架日志代次 68 干净启动、存储套件 20/20),这正是方案 A 的直接收益。
5.4 方案 B 的胜出点(如实记录,不足以翻盘)
- 最坏构建(存储套件)下多腾 ~4.5KB SRAM 余量(LVGL 集合 52.5KB > 堆 48KB,CCM 装得下更大的块)
- LVGL 若再次越界且超出 32B 保护带,溅射域收敛在 LVGL 私有 CCM 域(方案 A 下溅入 SRAM 程序状态)
两者均为中级收益,不足以抵消"调度器暴露于 SRAM 全域溢出"引入的顶级风险。
5.5 一并否决的混合方案
- 仅 LVGL 池(16KB)进 CCM、堆回 SRAM:16+56=72KB > 64KB 无法两全,调度器失去保护区 → 劣于方案 A
- 仅帧副本进 CCM:帧副本是自愈型内容,放 CCM 只关 DMA 门、零防护收益 → 无意义
5.6 若未来确需改判
触发条件:测试构建 SRAM 余量不足,且已耗尽 RAM使用分析与优化方案.md 的帧副本去重(−6.4~9.6KB)与 LVGL 池缩减等低代价手段。
流程:改 scatter → 走 §7 完整验证阶梯 → 同步更新本文档、AGENTS.md、.sct 头注释。在此之前,方案 A 是唯一有效布局。
6. 当前容量台账(2026-10-29,产品构建 task/wave-record 分支终值;2026-09-21 最坏构建=存储套件的历史基线见 docs/内存管理/RAM预算台账.md 趋势)
| 区域 | 总量 | 已用 | 空闲 | 驻留内容 |
|---|---|---|---|---|
| RW_IRAM1(SRAM) | 131,072B (128KB) | 89,936B (87.83KB, 68.6%) | 41,136B (~40.2KB) | 全部静态(除堆),含录波环 wave_ring.o 5,300B 与 LVGL 池 16,384B(lv_mem_core_builtin.o 实测) |
| RW_CCM | 65,280B(64KB 减 256B UNINIT_CCM 尾,与 .sct 一致) |
57,376B (56.03KB, 87.9%) | 7,904B | 仅 heap_4.o(ucHeap 56KB,2026-10-12 B5 扩容);256B 尾区现用 48B |
数字身份:RW_IRAM1 / RW_CCM / ER_IROM1 三行取自 lead 2026-10-29 权威构建(本工作树 MDK-ARM/STM32F407-Demo/STM32F407-Demo.map,0 错误 0 警告,scripts/ram_baseline.py --update 同批入 tsv)。受管快照区在 docs/内存管理/RAM使用分析与优化方案.md,子系统配额表在 docs/内存管理/RAM预算台账.md,两处由脚本渲染、本卡不手改。
LVGL 集合明细(均在 SRAM):LVGL 池 16,384B(lv_mem_core_builtin.o,LV_MEM_ADR=0 内部静态)+ g_ui_draw_buf 3,232B(3200+32 保护带)+ g_ui_fbuf 3,200B + g_tx 3,209B + g_lcdmem_data 3,204B。
产品构建比套件构建少 testTask/测试缓冲(约 40KB 级),SRAM 余量更大。数字以 map 实测为准,布局变更后必须重测并更新本表。
录波支路静态(2026-10-29 入册,R6 要求;全部落 RW_IRAM1、无 section 属性,CCM 增量 0):
| 归属对象 | 增量 | 构成 |
|---|---|---|
wave_ring.o(App/algo/wave_ring.c) |
5,300 B(ZI 5,256 + RW 44) | 录波环 int16×320 点×8 路 = 5,120 B 主体 + 已封存段头副本 136 B + 状态/索引/观测计数;map 比代码侧手算多 2 B,是链接器对齐 |
default_task.o(落盘道,函数内 static) |
+1,204 B(2,736 → 3,940,同 tsv 的 RW+ZI 口径) | FIL 528 B(FF_FS_TINY=0,刻意不上栈)+ 分片搬运缓冲 256 B + 段头副本 136 B + 段路径 17 B + 打包上下文/DIR/FILINFO/状态字节约 267 B(对象级增量,未逐项拆分实测) |
adc_task.o 及其余数据面 |
约 256 B | 8 路语义原码暂存与封存瞬间交棒结构(App/task/adc_task.c);与上两行相加 = 数据面实测 +6,760 B |
UI 道:ui_page_wave_play.o 92 B、ui_page_wave_manual.o 28 B、ui_task.o、ui_data.o |
区级合计 +1,640 B | 两个新页面模块按 map 计 120 B;ui_task.o/ui_data.o 的增量拆分需要本波之前的 per-file 快照,本卡没有 → 只记区级增量,不猜分项 |
-
三档对照(SRAM1):81,536 B / 62.2 %(
scripts/ram_baseline.tsv2026-10-28 行)→ 88,296 B(录波数据面终值)→ 89,936 B / 68.6 %(含 UI 道终值),累计 +8,400 B;RW_CCM三档恒 57,376 B —— 设计文档docs/子系统设计/故障录波设计说明.md§7 的「不进 CCM」落位达成,也没有挤动 CCM。 -
预估 vs 实测都要留:设计文档 §7 原估静态增量约 5.9 KB(环 5,120 + 索引状态 32 + 双缓冲 512 + 读窗 256),实测数据面 +6,760 B。差额构成:
FIL528 B、DIR/FILINFO、段头副本 136 B、打包上下文(内含一份 136 B 段头)原估未记账;反向一项——落盘实际是单缓冲 256 B,不是预估的 512 B 双缓冲。只写实测会让下一次估算照着 6.7 KB 走、看不到原估漏了哪几项。 -
Flash:
ER_IROM1264,580 → 270,248(数据面 +5,668 B)→ 272,300(UI 道 +2,052 B)→ 272,360(落盘判废复检 +60 B)→ 272,336 B(cppcheck 返工:把变量声明搬进最小作用域,纯搬声明 −24 B),累计 +7,756 B、占 26.0 %;Total ROM274,308 B(26.2 %)、Code243,552 B。RAM 三项(RW 1,972 / ZI 145,388 / RW_IRAM1 89,936 B / 68.6 %)与 CCM 57,376 B / 87.9 % 均未变。 -
栈:9 任务全 PASS、未抬栈(
scripts/stack_report.py静态最坏链)——adcTask 1,424 B(余 1,136 B / 44 %)、defaultTask 3,040 B 不变、protectTask 880 B、uiTask 1,336 B(余 15,048 B / 91 %)。R6 三处同步里AGENTS.md栈表这一处的结论是核对过、数值未变。 -
为什么比上一版低:本表 2026-10-12 版记 95,816 B / 73.1 %,高于今天的 89,936 B。原因是此后 LVGL 内部池由 40 KB 缩到 16 KB(
docs/内存管理/RAM预算台账.md§3「缩池 ✅ 2026-10-15」行;scripts/ram_baseline.tsv2026-10-12 与 2026-10-24 两行的top_rwzi列可见lv_mem_core_builtin.o40960 → 16384)——不是本波回收了什么。 -
子系统归集的坑(本卡实测,值得记一笔):
scripts/ram_baseline.py的subsystem_of()按对象名前缀首匹配,表里没有wave_前缀 → 录波环wave_ring.o5,300 B 落进 「其它」(15,853 B,参考值、无配额),配额 8 KB 的「应用」行看不见它;落盘道default_task.o(+1,204 B)与adc_task.o才计入「应用」(现值 16,888 B,本波前该行已在配额外,成因见docs/内存管理/RAM预算台账.md§2「应用 ⚠ 小幅超」条)。结论:别拿子系统表当录波预算的判据,要按本节 per-file 台账读。要让配额表真看见录波支路,得给subsystem_of()加wave_前缀——那是脚本改动,本卡不动,已报 lead。
7. 布局变更验证阶梯(强制执行)
任何 scatter / CCM / 堆大小变更后,依次通过以下全部关卡方可认定生效:
@build(0 错误 0 警告)- 产品构建
@test(.dsh/skills/serial-boot-test/scripts/run_boot_test.py):标记校验 PASS,且零[ERR](默认 Forbidden 已含[ERR]) - 存储套件(
test_config.h→TEST_SUITE_STORAGE):只读模式实测 3/3 PASSED(破坏性关,卷内容不变);开启ENABLE_DESTRUCTIVE_TESTS后为全量集成用例(2026-09-20 实测 20/20 PASSED,含 TC-STORAGE-01/02/03/05/06) - GD5F 套件(
TEST_SUITE_GD5F):只读模式实测 16/16 PASSED(含只读的守卫负路径 TC-GD5F-704);开启ENABLE_DESTRUCTIVE_TESTS后 68/68 PASSED,并确认日志出现 2 条守卫负路径[ERR](TC-GD5F-704/705)——破坏性模式运行须放宽 Forbidden(--forbidden "FAIL;HardFault") - 恢复跑(
ENABLE_STORAGE_RECOVERY_TESTS):期望 3/3 PASSED - 破坏性用例默认关闭(见
test_config.h的ENABLE_DESTRUCTIVE_TESTS):写/擦 NAND、格式化、坏块注入与 FatFS 写类用例只在首次上电/产线首启或明确需要时开启——2026-09-21 实测一轮破坏性 GD5F 运行即让 dhara 多记一个坏块(容量 95248 → 95200,且该计数持久化在 dhara journal checkpoint 头里,nand_ftl_format清不掉);日常回归请保持只读模式。容量满值 = 96064 pages(187 MB),被扣掉的容量可用ENABLE_STORAGE_RECOVERY_TESTS+STORAGE_RECOVERY_FULL_WIPE(全片擦除 + 重建 BBT)恢复(实测 95200 → 96064 ✓)
通过全部关卡后:更新 §6 台账、AGENTS.md 栈表、.sct 头注释,并把本次裁决补入 §5。
8. 附:策略落点索引
| 策略要素 | 代码位置 |
|---|---|
| CCM 布局(仅 heap_4.o) | MDK-ARM/STM32F407-Demo/STM32F407-Demo.sct |
| DMA 阈值 1024B | Drivers/BSP/GD5F2GQ5UE/gd5f2gq5ue.h(GD5F_DMA_THRESHOLD,公开供测试对齐上下界) |
| DMA 窗口/阈值编译期断言 | Drivers/BSP/GD5F2GQ5UE/gd5f2gq5ue.h(gd5f_dma_window_no_ccm_check_t 窗口不重叠 + gd5f_dma_threshold_contract_check_t 阈值契约,负数组 typedef) |
| DMA 可达性运行时守卫 | Drivers/BSP/GD5F2GQ5UE/gd5f2gq5ue.c(gd5f_dma_buf_ok,gd5f_read_from_cache/gd5f_program_load 两处接入) |
| 静态缓冲落位启动期自检 | Drivers/BSP/GD5F2GQ5UE/nand_ftl.c(init 期对三块 2KB 页缓冲过 gd5f_dma_buf_ok) |
| 守卫负路径用例(读/写) | test/board/gd5f_test_task.c(TC-GD5F-704/705) |
| 错误级日志纳入自动闭环 | .dsh/skills/serial-boot-test/scripts/verify_log.py + .dsh/skills/serial-boot-test/scripts/run_boot_test.py(默认 Forbidden 含 [ERR],[FAIL] 仍拦截断言失败) |
| LVGL 越界保护带 32B | Drivers/BSP/UI/ui_port.c(UI_DRAW_BUF_GUARD) |
| 栈溢出/分配失败钩子 | Inc/FreeRTOSConfig.h + Src/freertos.c(vApplicationStackOverflowHook / vApplicationMallocFailedHook) |
| HardFault 现场上报 | Src/stm32f4xx_it.c(HardFault_Handler → hard_fault_report) |
已知未决风险:
.ioc未与freertos.c的栈值直改同步——CubeMX 重新生成会覆盖栈值(已注释标记,未修)- CCM 存在性依赖芯片批次(克隆片可能缺 SRAM3,见 §2)