Files
STM32F4-Base/docs/内存管理/内存内容分配策略.md
T
wandering1 a7f526569a perf(mem): R-9 落地——LVGL 池 40→16 KB,SRAM1 74.9%→56.1%
取值依据是实测水位,不是预估:菜单根框 5,368 B、开页-关页往返
5,448 B、最重页面峰值 6,568 B(占 16 KB 的 40%)、真机开机
used=3,192 B。注意 16,384 B 建池后 lv_mem_monitor 报的 total 是
15,020 B(TLSF 控制块与对齐元数据 ≈1,364 B),核算余量用后者。

两端 lv_conf.h 同步改值(lv-conf-sync 生效值比对 163 项 PASS),
Lib/lv_conf.h 的 UTF-8 BOM 已按原样补回(Edit 会静默剥掉)。

验证:
- @build 0 错 0 警;ZI 154,456 → 129,880,精确 −24,576 B
- SRAM1 98,144/74.9% → 73,568/56.1%(余 57,504 B);CCM 不变 87.9%
- 模拟器菜单回归 PASS(8 状态像素 + 池基线 5,368 B 未变);11 页
  黄金参考逐字节 10/11 一致,往返池水位 5,448 B 分毫未变
- 唯一差异 netcfg_save 保存态:用 40 KB 旧池做 A/B 复测同样失败,
  确认与本改动无关(属 506eccc 的 cfg_file 行为变化),未刷黄金
  参考掩盖,留给该改动属主处置
- @test PASS:fw build Oct 15 2026 07:31:34,无 [ERR]/Fault,
  开机 622 ms 到 net init: OK,menu build done: used=3192
- 本地 CI 全绿(L0 + L0.5,15 runner / 955 断言)
- 台账受管区与内存快照区已 --write 回填,mem-budget / mem-numbers
  由红转绿

连带结论:R-2 后半待迁体量 54,146 → 29,570 B,瓶颈由 SRAM1 换到
CCM(只剩 7,904 B);解锁链第 ① 步完成,下一步只差缩堆 56→32 KB
(B5 是反向扩容,缩回前须重跑 stack_report.py + 上板 min_ever)。

文档同步 20 处:本报告按修复即销项删除 R-9 行,并改写 §0.2/§0.3/
§0.4/§5.1/§5.2/§5.3/§6.4 与变更记录、去处表;RAM使用分析 §2.6.2
新增实测水位表、阶段 1.1 标已执行;台账 §3 解锁链、登记簿 N-27、
任务规划 README W3 行与链路图;UI 四篇、产品需求、内存内容分配
策略、运行与自检记录、项目部署环境要求、测试开发标准流程、
simulator/README、ui-verify skill 常量。顺带订正三处与脚本不符的
旧值:池水位常量 4784/4864 → 5368/5448、FreeRTOS 堆 48 → 56 KB。
2026-10-15 09:56:15 +08:00

15 KiB
Raw Blame History

STM32F4-Base 内存内容分配策略

版本:v1.0 日期:2026-09-21 适用工程:STM32F4-Base(STM32F407ZGTx @168MHz,FreeRTOS V10.3.1 + CMSIS-RTOS2,Keil MDK v5 / ARMCC) 状态:当前有效策略(台架验证:产品构建 gen68 干净启动 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 同速) 不可达(本机实证)

关键事实:

  1. CCM 对 CPU 是全速内存。Cortex-M4 无 L1 cache,"CCM 是慢内存"的直觉只适用于带 cache 的核(M7/L4/H7 的非 cacheable 旁路),在本芯片上不成立——任何"按速度分配区域"的论证对本文档无效。
  2. DMA 对 CCM 地址的传输静默不落地。2026-09-21 首轮 CCM 实验实证:以 CCM 上栈缓冲(dhara meta 132B / is_free 64B)为 DMA 源/目的时传输不生效,引发 FTL 重格式化循环与幻影坏块。此后 DMA 阈值提至 1024B 消除。工程规则按"DMA 不可达 CCM"执行,不论数据手册如何表述。
  3. CCM 存在性已用 ST-LINK_CLI -r32 0x10000000 本机实测可读写;克隆片可能缺 SRAM3,换料必须重测并回退布局。
  4. 工程不进入 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 L1 参数 configTOTAL_HEAP_SIZE=57344;全部任务栈/队列/定时器)。scatter 的 RW_CCM 节不得再列其他对象;任何新增必须经 §5 式裁决并走 §7 完整验证阶梯。

  • R2(DMA 缓冲):只允许文件作用域静态缓冲,必须落 SRAM。三层防线缺一不可:

    • 窗口防线(编译期):DMA 白名单窗口 GD5F_DMA_RAM_BASE/END(0x20000000~0x20020000,须与 scatter RW_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_free 64B)、且 < 页大小(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.c Error_Handler 注释)→ 静态缓冲落位靠 scatter + 启动期自检双层保证,别再试图"加个 _Static_assert"。

  • 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)——即当前布局

理由(按权重排序):

  1. 致命类优先:方案 B 把 48KB 调度内存放回 SRAM,与全部潜在溢出源(含 FatFS、dhara 两个有实证事故史的第三方组件)同处一域;本项目实证过的故障族正是 1 字节类越界。
  2. DMA 不可达对堆是防护:任何 DMA 野地址/错地址传输都无法落入 CCM(静默不落地,发起方表现为可检测的"数据未到"),调度器因此免疫 DMA 类故障;方案 B 中该防护失效。
  3. LVGL 残余风险已被现有手段兜住:32B 保护带 + canary 实证 + configCHECK_FOR_STACK_OVERFLOW=2 + malloc 失败钩子;已发生的 1 字节越界被完整 containment。
  4. "UI bug 最多弄坏屏幕、弄不死系统"是台架既成事实:越界发生时 CCM 中的调度器毫发无伤(gen68 干净启动、存储套件 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 的 R-4 帧副本去重(−6.4~9.6KB)与 LVGL 池缩减等低代价手段。 流程:改 scatter → 走 §7 完整验证阶梯 → 同步更新本文档、AGENTS.md、.sct 头注释。在此之前,方案 A 是唯一有效布局。

6. 当前容量台账(2026-10-12,产品构建;2026-09-21 最坏构建=存储套件的历史基线见 docs/内存管理/RAM预算台账.md 趋势)

区域 总量 已用 空闲 驻留内容
RW_IRAM1(SRAM) 131,072B (128KB) 95,816B (~93.6KB, 73.1%) 35,256B (~34.4KB) 全部静态(除堆),含 LVGL 集合 53,805B
RW_CCM 65,536B (64KB) 57,376B (56KB, 87.9%) 8,160B (~8KB) 仅 heap_4.o(ucHeap 56KB,2026-10-12 B5 扩容)

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 实测为准,布局变更后必须重测并更新本表。

7. 布局变更验证阶梯(强制执行)

任何 scatter / CCM / 堆大小变更后,依次通过以下全部关卡方可认定生效:

  1. @build(0 错误 0 警告)
  2. 产品构建 @test(.dsh/skills/serial-boot-test/scripts/run_boot_test.py):标记校验 PASS,且零 [ERR](默认 Forbidden 已含 [ERR])
  3. 存储套件(test_config.h → TEST_SUITE_STORAGE):只读模式实测 3/3 PASSED(破坏性关,卷内容不变);开启 ENABLE_DESTRUCTIVE_TESTS 后为全量集成用例(2026-09-20 实测 20/20 PASSED,含 TC-STO-01/02/03/05/06)
  4. 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")
  5. 恢复跑(ENABLE_STORAGE_RECOVERY_TESTS):期望 3/3 PASSED
  6. 破坏性用例默认关闭(见 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)