docs(部署): 删任务栈副本表改指针;纠 CCM/FPU 事实并刷新配图基线

范围:AGENTS.md 一处指针、部署环境要求 §2.2/§3.1/§3.3、RAM 方案 6 处现状
数字、内存配图生成器 + 3 张 SVG、两份报告脚本的行尾与路径修正、新增两份
.map/.htm 数据快照。未改任何 .c/.h(仍跑 @build 复核:0 错误 0 警告)。

部署环境要求.md
- §3.3 删"任务栈预算"副本表、改为指针:该表已缺 adcTask/iec104Task/
  protectTask,defaultTask/timeSyncTask 两值停在旧数,注释却写"由
  gen_task_table.py 同源"——该钩子的校验面只有 README.md / AGENTS.md
  两处受管块,抄出来的副本没有门禁、改栈后必漂。指针指向 Src/freertos.c
  + 两份受管表 + RAM 方案 §2.3/§0.6,并留一条"别再抄"的成因说明。
- §2.2 链接布局补 `RW_CCM 0x10000000`(scatter 给 0x0000FF00 = 65,280 B,
  2026-10-11 起承载 FreeRTOS 堆;另有 256 B UNINIT_CCM 尾区),写明 CCM
  不可 DMA。原文"SRAM3/CCM 故意未映射"与 STM32F407-Demo.sct 直接矛盾。
- §3.1 MCU 行更正为**单精度 FPU(VFPv4-SP,无硬件双精度)、无 TrustZone**
  (原写"双精度 FPU / TrustZone"是芯片事实错误,也正是报告 F-6 双精度软
  浮点被长期误判的土壤);SRAM 行改为"两块都在用,仅 SRAM3 未映射"。
- §3.3 资源预算表补 CCM 行,并把"主 SRAM 125,104 B / 95.4% / 极度紧张"
  更正为 75,264 B / 57.4%(余 55,808 B);红线改标在 CCM(余 7,904 B)。
- 变更记录补 2026-10-22 行(含上一提交里的资源表刷新那一笔)。

AGENTS.md:任务表注里 "RAM使用分析与优化方案.md §1.4" 是**指错小节**——§1.4 确实
存在(该文档 §4「方案」下的阶段 1 子项,讲 lftpd 大局部缓冲静态化),但栈余量
台账在 §2.3(任务栈配置与实测峰值)与 §0.6(静态最坏链与判定),故改指这两处
再加 内存内容分配策略.md §6。根因是该文档里 §1(背景)与 §4 下的 1.1~1.5 同号
撞面,人和 doc-links(自引用放过)都会看错——本会话我上一轮就被它带错过一次。

配图与数据(draw-diagram 流程:改生成器顶部数据 → 重跑 → 逐文本核越界)
- 新增 data/ram_report_20261022.md、data/stack_report_20261022.md;后者
  运行时水位取自 2026-10-22 真机 [STACK] 心跳(8 任务高水位 + heap
  free=15,800 / min_ever=8,464),静态侧来自同日 .htm(全局最深链 3,024 B)。
- gen_memory_svg.py:三组图数据从 2026-09-21 / 10-10 快照换成 2026-10-22
  实测;任务栈 6 → 9 行,堆总量 48KB → 56KB,判定 6/6 → 9/9 PASS,新增
  AboveNormal 配色;Flash 子条改为"窄段不压字 + 下方四列图例";任务栈
  画布 440 → 620 高。CCM 段总量按 scatter 用 65,280 B(与台账同口径)。
- 三张 SVG 重生成;用"CJK≈1.0em / 其余≈0.58em + 锚点"逐文本估算边界,
  修掉 5 处文字越界/压字(生成器内置 ElementTree 自检亦通过)。
- ram_report.py / stack_report.py 两处工具性修正:write_text 未指定 newline
  参数 → Windows 下把 data/ 快照写成 CRLF(与 .gitattributes 及既有快照
  不符);stack_report 的 relative_to 在传相对 -i 时抛 ValueError,且会把
  本机绝对路径写进入库快照 → 统一走 resolve+relative_to 的相对路径回退。

RAM 方案:6 处"当前状态"随 .map/[STACK] 更正——RW_IRAM1 89,168 → 75,264、
RW_CCM 49,184 → 57,376、FreeRTOS 堆 48KB → 56KB(两处)、CCM 余量 ~16KB →
7,904 B、min_ever 4,848 → 8,464 B、6 任务全过 → 9/9 PASS;S-1 历史句保留。

门禁:pre-commit 对全部改动文件 Passed(doc-links --sections-strict、
task-table、mem-numbers、mem-budget、id-check 增量拦截);字节卫生自查
BOM / CR / U+FFFD / 行尾空格全清。已提交本地,未推送。
This commit is contained in:
2026-10-22 12:09:54 +08:00
parent d4867ddbe2
commit f4b9bd57d5
11 changed files with 606 additions and 191 deletions
+1 -1
View File
@@ -63,7 +63,7 @@
| protectTask | 512 | Normal |
<!-- TASK_TABLE:END -->
(实际值以 `Src/freertos.c` 为准,本表由 `scripts/gen_task_table.py` 生成、`task-table` 钩子校验;testTask 仅在启用测试套件时创建。FreeRTOS 堆已迁 CCM,任务栈不占 SRAM;**每次抬栈的实测最坏链与余量台账** → `docs/内存管理/RAM使用分析与优化方案.md` §1.4 与 `docs/内存管理/内存内容分配策略.md` §6)
(实际值以 `Src/freertos.c` 为准,本表由 `scripts/gen_task_table.py` 生成、`task-table` 钩子校验;testTask 仅在启用测试套件时创建。FreeRTOS 堆已迁 CCM,任务栈不占 SRAM;**每次抬栈的实测最坏链与余量台账** → `docs/内存管理/RAM使用分析与优化方案.md` §2.3(任务栈配置与实测峰值)与 §0.6(静态最坏链与判定)、`docs/内存管理/内存内容分配策略.md` §6(容量台账))
## 铁律:源码编辑与编码
+50 -35
View File
@@ -1,4 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 880 440" width="880" height="440" style="font-family:Consolas,'Courier New',monospace; font-size:12px; background:#fff">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 880 620" width="880" height="620" style="font-family:Consolas,'Courier New',monospace; font-size:12px; background:#fff">
<defs>
<style>
.title { font-size:14px; font-weight:bold; }
@@ -7,45 +7,60 @@
.bar-seg { stroke:#fff; stroke-width:0.5; }
</style>
</defs>
<text x="440.0" y="22" text-anchor="middle" class="title">FreeRTOS 任务栈预算(48KB 堆,2026-09-21 已迁 CCM)</text>
<text x="440.0" y="40" text-anchor="middle" class="legend-text" fill="#777">数据: 2026-10-10 @build + 开机/空闲实测(data/stack_report_20261010.md);最坏 = max(静态最坏链, 实测峰值)</text>
<text x="440.0" y="22" text-anchor="middle" class="title">FreeRTOS 任务栈预算(9 任务;栈全部从 56KB FreeRTOS 堆分配,堆位于 CCM)</text>
<text x="440.0" y="40" text-anchor="middle" class="legend-text" fill="#777">数据: 2026-10-22 @build + 同日真机 [STACK] 水位(data/stack_report_20261022.md);最坏 = max(静态最坏链, 实测峰值);判定线 = 余量 ≥ max(1024B ISR, 配置×30%)</text>
<text x="190" y="54" class="legend-text" fill="#555">浅色条 = 分配栈 深色条 = 最坏占用(max(静态最坏, 实测峰值)) 下方文字 = 实测余量/判定</text>
<text x="186" y="72" text-anchor="end" dominant-baseline="central" class="label" fill="#333">defaultTask</text>
<rect class="bar-seg" x="190" y="64" width="162.5" height="16" fill="#BBDEFB" rx="3"/>
<rect x="190" y="64" width="110.1" height="16" fill="#2196F3" rx="3"/>
<text x="190" y="91" dominant-baseline="central" class="legend-text" fill="#333">配置 4096B | 实测峰值 2152B | 最坏 2776B | 余量 1320B (32%) <tspan fill="#2E7D32" font-weight="bold">PASS</tspan></text>
<rect class="bar-seg" x="190" y="64" width="203.1" height="16" fill="#BBDEFB" rx="3"/>
<rect x="190" y="64" width="120.0" height="16" fill="#2196F3" rx="3"/>
<text x="190" y="91" dominant-baseline="central" class="legend-text" fill="#333">配置 5120B | 实测峰值 2164B | 最坏 3024B | 余量 2096B (40%) <tspan fill="#2E7D32" font-weight="bold">PASS</tspan></text>
<text x="186" y="112" text-anchor="end" dominant-baseline="central" class="label" fill="#333">netTask</text>
<rect class="bar-seg" x="190" y="104" width="152.3" height="16" fill="#BBDEFB" rx="3"/>
<rect x="190" y="104" width="74.3" height="16" fill="#2196F3" rx="3"/>
<text x="190" y="131" dominant-baseline="central" class="legend-text" fill="#333">配置 3840B | 实测峰值 920B | 最坏 1872B | 余量 1968B (51%) <tspan fill="#2E7D32" font-weight="bold">PASS</tspan></text>
<rect x="190" y="104" width="75.9" height="16" fill="#2196F3" rx="3"/>
<text x="190" y="131" dominant-baseline="central" class="legend-text" fill="#333">配置 3840B | 实测峰值 1116B | 最坏 1912B | 余量 1928B (50%) <tspan fill="#2E7D32" font-weight="bold">PASS</tspan></text>
<text x="186" y="152" text-anchor="end" dominant-baseline="central" class="label" fill="#333">ftpTask</text>
<rect class="bar-seg" x="190" y="144" width="162.5" height="16" fill="#BBDEFB" rx="3"/>
<rect x="190" y="144" width="48.2" height="16" fill="#2196F3" rx="3"/>
<text x="190" y="171" dominant-baseline="central" class="legend-text" fill="#333">配置 4096B | 实测峰值 740B | 最坏 1216B | 余量 2880B (70%) <tspan fill="#2E7D32" font-weight="bold">PASS</tspan></text>
<text x="186" y="192" text-anchor="end" dominant-baseline="central" class="label" fill="#333">testTask</text>
<rect class="bar-seg" x="190" y="184" width="325.0" height="16" fill="#FFE0B2" rx="3"/>
<rect x="190" y="184" width="19.7" height="16" fill="#FF9800" rx="3"/>
<text x="190" y="211" dominant-baseline="central" class="legend-text" fill="#333">配置 8192B | 实测峰值 496B | 最坏 496B | 余量 7696B (93%) <tspan fill="#2E7D32" font-weight="bold">PASS</tspan></text>
<text x="186" y="232" text-anchor="end" dominant-baseline="central" class="label" fill="#333">uiTask</text>
<rect class="bar-seg" x="190" y="224" width="650.0" height="16" fill="#FFE0B2" rx="3"/>
<rect x="190" y="224" width="159.3" height="16" fill="#FF9800" rx="3"/>
<text x="190" y="251" dominant-baseline="central" class="legend-text" fill="#333">配置 16384B | 实测峰值 4016B | 最坏 4016B | 余量 12368B (75%) <tspan fill="#2E7D32" font-weight="bold">PASS</tspan></text>
<rect x="190" y="144" width="57.8" height="16" fill="#2196F3" rx="3"/>
<text x="190" y="171" dominant-baseline="central" class="legend-text" fill="#333">配置 4096B | 实测峰值 756B | 最坏 1456B | 余量 2640B (64%) <tspan fill="#2E7D32" font-weight="bold">PASS</tspan></text>
<text x="186" y="192" text-anchor="end" dominant-baseline="central" class="label" fill="#333">uiTask</text>
<rect class="bar-seg" x="190" y="184" width="650.0" height="16" fill="#FFE0B2" rx="3"/>
<rect x="190" y="184" width="160.8" height="16" fill="#FF9800" rx="3"/>
<text x="190" y="211" dominant-baseline="central" class="legend-text" fill="#333">配置 16384B | 实测峰值 4052B | 最坏 4052B | 余量 12332B (75%) <tspan fill="#2E7D32" font-weight="bold">PASS</tspan></text>
<text x="186" y="232" text-anchor="end" dominant-baseline="central" class="label" fill="#333">testTask</text>
<rect class="bar-seg" x="190" y="224" width="325.0" height="16" fill="#FFE0B2" rx="3"/>
<rect x="190" y="224" width="29.2" height="16" fill="#FF9800" rx="3"/>
<text x="190" y="251" dominant-baseline="central" class="legend-text" fill="#333">配置 8192B | 实测峰值 736B | 最坏 736B | 余量 7456B (91%) <tspan fill="#2E7D32" font-weight="bold">PASS</tspan></text>
<text x="186" y="272" text-anchor="end" dominant-baseline="central" class="label" fill="#333">timeSyncTask</text>
<rect class="bar-seg" x="190" y="264" width="81.2" height="16" fill="#BBDEFB" rx="3"/>
<rect x="190" y="264" width="40.6" height="16" fill="#2196F3" rx="3"/>
<text x="190" y="291" dominant-baseline="central" class="legend-text" fill="#333">配置 2048B | 实测峰值 616B | 最坏 1024B | 余量 1024B (50%) <tspan fill="#2E7D32" font-weight="bold">PASS</tspan></text>
<text x="186" y="314" text-anchor="end" dominant-baseline="central" class="label" fill="#333">idle</text>
<rect class="bar-seg" x="190" y="308" width="20.3" height="12" fill="#90A4AE" rx="2"/>
<text x="214.3125" y="314" dominant-baseline="central" class="legend-text" fill="#333">512B(未实测)</text>
<text x="186" y="336" text-anchor="end" dominant-baseline="central" class="label" fill="#333">timer</text>
<rect class="bar-seg" x="190" y="330" width="40.6" height="12" fill="#90A4AE" rx="2"/>
<text x="234.625" y="336" dominant-baseline="central" class="legend-text" fill="#333">1024B(未实测)</text>
<text x="190" y="360" class="label" fill="#555">堆总量: 49152B (48KB, RW_CCM) 任务栈合计: 38656B (37.8KB) + 内核栈 1536B</text>
<text x="190" y="376" class="label" fill="#555">实测: free=8784B / min_ever=4848B(2026-09-21,含 FTP 负载) 判定: 6/6 PASS(余量 ≥ max(1024B, 配置×30%))</text>
<rect x="190" y="400" width="10" height="10" fill="#2196F3" rx="2"/>
<text x="204" y="408" class="legend-text" fill="#333">Normal 优先级</text>
<rect x="320" y="400" width="10" height="10" fill="#FF9800" rx="2"/>
<text x="334" y="408" class="legend-text" fill="#333">BelowNormal 优先级</text>
<rect x="480" y="400" width="10" height="10" fill="#90A4AE" rx="2"/>
<text x="494" y="408" class="legend-text" fill="#333">内核任务栈 (idle/timer)</text>
<rect class="bar-seg" x="190" y="264" width="101.6" height="16" fill="#BBDEFB" rx="3"/>
<rect x="190" y="264" width="50.1" height="16" fill="#2196F3" rx="3"/>
<text x="190" y="291" dominant-baseline="central" class="legend-text" fill="#333">配置 2560B | 实测峰值 616B | 最坏 1264B | 余量 1296B (50%) <tspan fill="#2E7D32" font-weight="bold">PASS</tspan></text>
<text x="186" y="312" text-anchor="end" dominant-baseline="central" class="label" fill="#333">adcTask</text>
<rect class="bar-seg" x="190" y="304" width="101.6" height="16" fill="#C8E6C9" rx="3"/>
<rect x="190" y="304" width="52.4" height="16" fill="#4CAF50" rx="3"/>
<text x="190" y="331" dominant-baseline="central" class="legend-text" fill="#333">配置 2560B | 实测峰值 952B | 最坏 1320B | 余量 1240B (48%) <tspan fill="#2E7D32" font-weight="bold">PASS</tspan></text>
<text x="186" y="352" text-anchor="end" dominant-baseline="central" class="label" fill="#333">iec104Task</text>
<rect class="bar-seg" x="190" y="344" width="101.6" height="16" fill="#BBDEFB" rx="3"/>
<rect x="190" y="344" width="57.8" height="16" fill="#2196F3" rx="3"/>
<text x="190" y="371" dominant-baseline="central" class="legend-text" fill="#333">配置 2560B | 实测峰值 544B | 最坏 1456B | 余量 1104B (43%) <tspan fill="#2E7D32" font-weight="bold">PASS</tspan></text>
<text x="186" y="392" text-anchor="end" dominant-baseline="central" class="label" fill="#333">protectTask</text>
<rect class="bar-seg" x="190" y="384" width="81.2" height="16" fill="#BBDEFB" rx="3"/>
<rect x="190" y="384" width="34.6" height="16" fill="#2196F3" rx="3"/>
<text x="190" y="411" dominant-baseline="central" class="legend-text" fill="#333">配置 2048B | 实测峰值 828B | 最坏 872B | 余量 1176B (57%) <tspan fill="#2E7D32" font-weight="bold">PASS</tspan></text>
<text x="186" y="434" text-anchor="end" dominant-baseline="central" class="label" fill="#333">idle</text>
<rect class="bar-seg" x="190" y="428" width="20.3" height="12" fill="#90A4AE" rx="2"/>
<text x="214.3125" y="434" dominant-baseline="central" class="legend-text" fill="#333">512B(未实测)</text>
<text x="186" y="456" text-anchor="end" dominant-baseline="central" class="label" fill="#333">timer</text>
<rect class="bar-seg" x="190" y="450" width="40.6" height="12" fill="#90A4AE" rx="2"/>
<text x="234.625" y="456" dominant-baseline="central" class="legend-text" fill="#333">1024B(未实测)</text>
<text x="190" y="480" class="label" fill="#555">堆总量: 57344B(configTOTAL_HEAP_SIZE=56KB,位于 RW_CCM) 任务栈合计: 47360B(含 testTask 8192B)</text>
<text x="190" y="496" class="label" fill="#555">产品构建 8 任务栈 = 39168B + 内核栈 1536B 实测: free=15800B / min_ever=8464B(2026-10-22)</text>
<text x="190" y="512" class="label" fill="#555">判定: 9/9 PASS(testTask 无实测,取静态最坏链)</text>
<rect x="190" y="536" width="10" height="10" fill="#2196F3" rx="2"/>
<text x="204" y="544" class="legend-text" fill="#333">Normal 优先级</text>
<rect x="320" y="536" width="10" height="10" fill="#FF9800" rx="2"/>
<text x="334" y="544" class="legend-text" fill="#333">BelowNormal 优先级</text>
<rect x="490" y="536" width="10" height="10" fill="#4CAF50" rx="2"/>
<text x="504" y="544" class="legend-text" fill="#333">AboveNormal 优先级(adcTask)</text>
<rect x="710" y="536" width="10" height="10" fill="#90A4AE" rx="2"/>
<text x="724" y="544" class="legend-text" fill="#333">内核栈 idle/timer</text>
</svg>

Before

Width:  |  Height:  |  Size: 5.1 KiB

After

Width:  |  Height:  |  Size: 6.9 KiB

+43 -28
View File
@@ -7,32 +7,47 @@
.bar-seg { stroke:#fff; stroke-width:0.5; }
</style>
</defs>
<text x="410.0" y="24" text-anchor="middle" class="title">Flash (1MB) 使用布局 — 2026-09-21 @build</text>
<rect class="bar-seg" x="60" y="50" width="165.8" height="40" fill="#2196F3" rx="2"/>
<text x="142.9" y="70.0" text-anchor="middle" dominant-baseline="central" class="label" fill="#000">代码段 (Code) 229.5KB</text>
<rect class="bar-seg" x="225.8270263671875" y="50" width="9.8" height="40" fill="#4CAF50" rx="2"/>
<text x="239.6" y="70.0" text-anchor="start" dominant-baseline="central" class="legend-text" fill="#333">只读数据 (RO) 13.5KB</text>
<rect class="bar-seg" x="235.5828857421875" y="50" width="564.0" height="40" fill="#E0E0E0" rx="2"/>
<text x="517.6" y="70.0" text-anchor="middle" dominant-baseline="central" class="label" fill="#000">Flash 空闲 780.4KB</text>
<text x="60" y="108" class="label" fill="#333">已用: 243.6KB (23.8%) 空闲: 780.4KB (76.2%) 总量: 1024KB</text>
<text x="60" y="124" class="label" fill="#555">代码段明细 (229.5KB)</text>
<rect class="bar-seg" x="60" y="130" width="308.6" height="24" fill="#4CAF50" rx="2"/>
<text x="214.3" y="142.0" text-anchor="middle" dominant-baseline="central" class="label" fill="#000">LVGL 库 95.7KB</text>
<rect class="bar-seg" x="368.6272640610105" y="130" width="107.4" height="24" fill="#2196F3" rx="2"/>
<text x="422.3" y="142.0" text-anchor="middle" dominant-baseline="central" class="label" fill="#000">应用代码/UI 33.3KB</text>
<rect class="bar-seg" x="476.0484475010214" y="130" width="25.7" height="24" fill="#9C27B0" rx="2"/>
<text x="505.7" y="142.0" text-anchor="start" dominant-baseline="central" class="legend-text" fill="#333">FatFS 8.0KB</text>
<rect class="bar-seg" x="501.7023015116438" y="130" width="15.7" height="24" fill="#FF9800" rx="2"/>
<text x="521.4" y="142.0" text-anchor="start" dominant-baseline="central" class="legend-text" fill="#333">dhara / FTL 4.9KB</text>
<rect class="bar-seg" x="517.3919038540107" y="130" width="50.4" height="24" fill="#607D8B" rx="2"/>
<text x="542.6" y="136.0" text-anchor="middle" dominant-baseline="central" class="label" fill="#000">HAL 驱动</text>
<text x="542.6" y="148.0" text-anchor="middle" dominant-baseline="central" class="label" fill="#000">15.6KB</text>
<rect class="bar-seg" x="567.7800285986655" y="130" width="31.9" height="24" fill="#00BCD4" rx="2"/>
<text x="603.7" y="142.0" text-anchor="start" dominant-baseline="central" class="legend-text" fill="#333">FreeRTOS 内核 9.9KB</text>
<rect class="bar-seg" x="599.6883085932182" y="130" width="8.6" height="24" fill="#F44336" rx="2"/>
<text x="612.3" y="142.0" text-anchor="start" dominant-baseline="central" class="legend-text" fill="#333">CH395F 网络 2.7KB</text>
<rect class="bar-seg" x="608.3109764401472" y="130" width="13.3" height="24" fill="#795548" rx="2"/>
<text x="625.6" y="142.0" text-anchor="start" dominant-baseline="central" class="legend-text" fill="#333">C 库 (microlib) 4.1KB</text>
<rect class="bar-seg" x="621.6323369195153" y="130" width="178.4" height="24" fill="#E0E0E0" rx="2"/>
<text x="710.8" y="142.0" text-anchor="middle" dominant-baseline="central" class="label" fill="#000">其他 (长尾) 55.3KB</text>
<text x="410.0" y="24" text-anchor="middle" class="title">Flash (1MB) 使用布局 — 2026-10-22 @build</text>
<rect class="bar-seg" x="60" y="50" width="150.5" height="40" fill="#2196F3" rx="2"/>
<text x="135.2" y="70.0" text-anchor="middle" dominant-baseline="central" class="label" fill="#000">代码段 (Code) 208.2KB</text>
<rect class="bar-seg" x="210.45082092285156" y="50" width="13.1" height="40" fill="#4CAF50" rx="2"/>
<text x="227.6" y="70.0" text-anchor="start" dominant-baseline="central" class="legend-text" fill="#333">只读数据 (RO) 18.1KB</text>
<rect class="bar-seg" x="223.56307983398438" y="50" width="575.6" height="40" fill="#E0E0E0" rx="2"/>
<text x="511.4" y="70.0" text-anchor="middle" dominant-baseline="central" class="label" fill="#000">Flash 空闲 796.5KB</text>
<text x="60" y="108" class="label" fill="#333">已用: 227.5KB (22.2%) 空闲: 796.5KB (77.8%) ER_IROM1(Code+RO): 226.3KB (22.1%) 总量: 1MB (= ROM 合计口径)</text>
<text x="60" y="124" class="label" fill="#555">Code + RO 分布(ER_IROM1 226.3KB,按 .map 对象归类)</text>
<rect class="bar-seg" x="60" y="130" width="280.0" height="24" fill="#4CAF50" rx="2"/>
<text x="200.0" y="142.0" text-anchor="middle" dominant-baseline="central" class="label" fill="#000">85.7KB</text>
<rect class="bar-seg" x="340.0319284802044" y="130" width="182.0" height="24" fill="#2196F3" rx="2"/>
<text x="431.0" y="142.0" text-anchor="middle" dominant-baseline="central" class="label" fill="#000">55.7KB</text>
<rect class="bar-seg" x="522.0593869731802" y="130" width="81.9" height="24" fill="#F44336" rx="2"/>
<text x="563.0" y="142.0" text-anchor="middle" dominant-baseline="central" class="label" fill="#000">25.1KB</text>
<rect class="bar-seg" x="603.9623243933589" y="130" width="57.1" height="24" fill="#607D8B" rx="2"/>
<text x="632.5" y="142.0" text-anchor="middle" dominant-baseline="central" class="label" fill="#000">17.5KB</text>
<rect class="bar-seg" x="661.0376756066412" y="130" width="34.8" height="24" fill="#FF9800" rx="2"/>
<rect class="bar-seg" x="695.8620689655173" y="130" width="34.4" height="24" fill="#00BCD4" rx="2"/>
<rect class="bar-seg" x="730.2234993614305" y="130" width="27.0" height="24" fill="#9C27B0" rx="2"/>
<rect class="bar-seg" x="757.2605363984676" y="130" width="17.5" height="24" fill="#795548" rx="2"/>
<rect class="bar-seg" x="774.7765006385698" y="130" width="1.5" height="24" fill="#B0BEC5" rx="2"/>
<rect class="bar-seg" x="776.2707535121331" y="130" width="23.7" height="24" fill="#E0E0E0" rx="2"/>
<rect x="60" y="167" width="10" height="10" fill="#4CAF50" rx="2"/>
<text x="74" y="176" class="legend-text" fill="#333">LVGL 库 85.7KB</text>
<rect x="255" y="167" width="10" height="10" fill="#2196F3" rx="2"/>
<text x="269" y="176" class="legend-text" fill="#333">应用代码/UI 55.7KB</text>
<rect x="450" y="167" width="10" height="10" fill="#F44336" rx="2"/>
<text x="464" y="176" class="legend-text" fill="#333">CH395F+FTP 25.1KB</text>
<rect x="645" y="167" width="10" height="10" fill="#607D8B" rx="2"/>
<text x="659" y="176" class="legend-text" fill="#333">HAL 驱动 17.5KB</text>
<rect x="60" y="183" width="10" height="10" fill="#FF9800" rx="2"/>
<text x="74" y="192" class="legend-text" fill="#333">dhara / FTL 10.7KB</text>
<rect x="255" y="183" width="10" height="10" fill="#00BCD4" rx="2"/>
<text x="269" y="192" class="legend-text" fill="#333">FreeRTOS 内核 10.5KB</text>
<rect x="450" y="183" width="10" height="10" fill="#9C27B0" rx="2"/>
<text x="464" y="192" class="legend-text" fill="#333">FatFS 8.3KB</text>
<rect x="645" y="183" width="10" height="10" fill="#795548" rx="2"/>
<text x="659" y="192" class="legend-text" fill="#333">C 库 (microlib) 5.4KB</text>
<rect x="60" y="199" width="10" height="10" fill="#B0BEC5" rx="2"/>
<text x="74" y="208" class="legend-text" fill="#333">startup 0.5KB</text>
<rect x="255" y="199" width="10" height="10" fill="#E0E0E0" rx="2"/>
<text x="269" y="208" class="legend-text" fill="#333">其他 (长尾) 7.3KB</text>
</svg>

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 4.4 KiB

+36 -35
View File
@@ -7,50 +7,51 @@
.bar-seg { stroke:#fff; stroke-width:0.5; }
</style>
</defs>
<text x="410.0" y="24" text-anchor="middle" class="title">SRAM (192KB) 使用布局 — 2026-09-21 @build</text>
<text x="410.0" y="24" text-anchor="middle" class="title">SRAM (192KB) 使用布局 — 2026-10-22 @build</text>
<text x="60" y="42" class="label" fill="#555">主 SRAM RW_IRAM1 @0x20000000(128KB,DMA 可达)</text>
<rect class="bar-seg" x="60" y="50" width="234.1" height="44" fill="#4CAF50" rx="2"/>
<text x="177.0" y="72.0" text-anchor="middle" dominant-baseline="central" class="label" fill="#000">LVGL</text>
<rect class="bar-seg" x="294.0728759765625" y="50" width="76.4" height="44" fill="#FF9800" rx="2"/>
<text x="332.3" y="72.0" text-anchor="middle" dominant-baseline="central" class="label" fill="#000">UI</text>
<rect class="bar-seg" x="370.47119140625" y="50" width="49.8" height="44" fill="#9C27B0" rx="2"/>
<rect class="bar-seg" x="420.2215576171875" y="50" width="30.1" height="44" fill="#8BC34A" rx="2"/>
<rect class="bar-seg" x="450.279541015625" y="50" width="28.3" height="44" fill="#00BCD4" rx="2"/>
<rect class="bar-seg" x="478.5986328125" y="50" width="25.1" height="44" fill="#F44336" rx="2"/>
<rect class="bar-seg" x="503.6940002441406" y="50" width="7.2" height="44" fill="#607D8B" rx="2"/>
<rect class="bar-seg" x="510.9205627441406" y="50" width="5.8" height="44" fill="#795548" rx="2"/>
<rect class="bar-seg" x="516.7018127441406" y="50" width="6.5" height="44" fill="#9E9E9E" rx="2"/>
<rect class="bar-seg" x="523.22265625" y="50" width="276.8" height="44" fill="#E0E0E0" rx="2"/>
<text x="661.6" y="72.0" text-anchor="middle" dominant-baseline="central" class="label" fill="#000">free</text>
<text x="60" y="112" class="label" fill="#333">已用: 82048B (62.6%) 空闲: 49024B (37.4%) 总量: 128KB</text>
<text x="60" y="136" class="label" fill="#555">CCM RW_CCM @0x10000000(64KB,仅 CPU 可达,DMA 不可达)</text>
<rect class="bar-seg" x="60" y="144" width="277.7" height="44" fill="#2196F3" rx="2"/>
<text x="198.8" y="166.0" text-anchor="middle" dominant-baseline="central" class="label" fill="#000">heap</text>
<rect class="bar-seg" x="337.6806640625" y="144" width="92.3" height="44" fill="#E0E0E0" rx="2"/>
<text x="383.8" y="166.0" text-anchor="middle" dominant-baseline="central" class="label" fill="#000">free</text>
<text x="60" y="206" class="label" fill="#333">已用: 49184B (75.0%) 空闲: 16352B (25.0%) 总量: 64KB</text>
<rect class="bar-seg" x="60" y="50" width="95.3" height="44" fill="#4CAF50" rx="2"/>
<text x="107.6" y="72.0" text-anchor="middle" dominant-baseline="central" class="label" fill="#000">LVGL 池 16KB</text>
<rect class="bar-seg" x="155.2777099609375" y="50" width="76.5" height="44" fill="#FF9800" rx="2"/>
<text x="193.5" y="72.0" text-anchor="middle" dominant-baseline="central" class="label" fill="#000">UI</text>
<rect class="bar-seg" x="231.75506591796875" y="50" width="47.7" height="44" fill="#8BC34A" rx="2"/>
<text x="255.6" y="72.0" text-anchor="middle" dominant-baseline="central" class="label" fill="#000">lftpd</text>
<rect class="bar-seg" x="279.40521240234375" y="50" width="39.7" height="44" fill="#9C27B0" rx="2"/>
<rect class="bar-seg" x="319.12872314453125" y="50" width="29.6" height="44" fill="#F44336" rx="2"/>
<rect class="bar-seg" x="348.7689208984375" y="50" width="9.7" height="44" fill="#00BCD4" rx="2"/>
<rect class="bar-seg" x="358.502197265625" y="50" width="5.8" height="44" fill="#795548" rx="2"/>
<rect class="bar-seg" x="364.283447265625" y="50" width="120.2" height="44" fill="#9E9E9E" rx="2"/>
<text x="424.4" y="72.0" text-anchor="middle" dominant-baseline="central" class="label" fill="#000">other</text>
<rect class="bar-seg" x="484.4476318359375" y="50" width="315.1" height="44" fill="#E0E0E0" rx="2"/>
<text x="642.0" y="72.0" text-anchor="middle" dominant-baseline="central" class="label" fill="#000">free</text>
<text x="60" y="112" class="label" fill="#333">已用: 75264B (57.4%) 空闲: 55808B (42.6%) 总量: 128KB</text>
<text x="60" y="136" class="label" fill="#555">CCM RW_CCM @0x10000000(段 65,280 B;仅 CPU 可达、DMA 不可达 → R2/R3)</text>
<rect class="bar-seg" x="60" y="144" width="323.9" height="44" fill="#2196F3" rx="2"/>
<text x="222.0" y="166.0" text-anchor="middle" dominant-baseline="central" class="label" fill="#000">FreeRTOS 堆</text>
<rect class="bar-seg" x="383.9306640625" y="144" width="44.6" height="44" fill="#E0E0E0" rx="2"/>
<text x="406.2" y="166.0" text-anchor="middle" dominant-baseline="central" class="label" fill="#000">free</text>
<text x="60" y="206" class="label" fill="#333">已用: 57376B (87.9%) 空闲: 7904B (12.1%) 总量: 63.75KB(+256 B UNINIT_CCM 尾区)——CCM 是当前全局卡点</text>
<rect x="60" y="226" width="12" height="12" fill="#4CAF50" rx="2"/>
<text x="76" y="236" class="legend-text" fill="#333">主SRAM LVGL: 40.5KB (31.6%)</text>
<text x="76" y="236" class="legend-text" fill="#333">主SRAM LVGL 池 16KB: 16.5KB (12.9%)</text>
<rect x="60" y="242" width="12" height="12" fill="#FF9800" rx="2"/>
<text x="76" y="252" class="legend-text" fill="#333">主SRAM UI: 13.2KB (10.3%)</text>
<rect x="60" y="258" width="12" height="12" fill="#9C27B0" rx="2"/>
<text x="76" y="268" class="legend-text" fill="#333">主SRAM FTL/BBT: 8.6KB (6.7%)</text>
<rect x="60" y="274" width="12" height="12" fill="#8BC34A" rx="2"/>
<text x="76" y="284" class="legend-text" fill="#333">主SRAM lftpd: 5.2KB (4.1%)</text>
<rect x="60" y="290" width="12" height="12" fill="#00BCD4" rx="2"/>
<text x="76" y="300" class="legend-text" fill="#333">主SRAM CMSIS: 4.9KB (3.8%)</text>
<rect x="420" y="226" width="12" height="12" fill="#F44336" rx="2"/>
<text x="436" y="236" class="legend-text" fill="#333">主SRAM NET: 4.3KB (3.4%)</text>
<rect x="60" y="258" width="12" height="12" fill="#8BC34A" rx="2"/>
<text x="76" y="268" class="legend-text" fill="#333">主SRAM lftpd: 8.2KB (6.4%)</text>
<rect x="60" y="274" width="12" height="12" fill="#9C27B0" rx="2"/>
<text x="76" y="284" class="legend-text" fill="#333">主SRAM FTL/BBT: 6.9KB (5.4%)</text>
<rect x="60" y="290" width="12" height="12" fill="#F44336" rx="2"/>
<text x="76" y="300" class="legend-text" fill="#333">主SRAM NET/CH395F: 5.1KB (4.0%)</text>
<rect x="420" y="226" width="12" height="12" fill="#00BCD4" rx="2"/>
<text x="436" y="236" class="legend-text" fill="#333">主SRAM CMSIS-RTOS: 1.7KB (1.3%)</text>
<rect x="420" y="242" width="12" height="12" fill="#607D8B" rx="2"/>
<text x="436" y="252" class="legend-text" fill="#333">主SRAM HAL: 1.2KB (1.0%)</text>
<text x="436" y="252" class="legend-text" fill="#333">主SRAM HAL: 0.1KB (0.1%)</text>
<rect x="420" y="258" width="12" height="12" fill="#795548" rx="2"/>
<text x="436" y="268" class="legend-text" fill="#333">主SRAM C stack: 1.0KB (0.8%)</text>
<rect x="420" y="274" width="12" height="12" fill="#9E9E9E" rx="2"/>
<text x="436" y="284" class="legend-text" fill="#333">主SRAM other: 1.1KB (0.9%)</text>
<text x="436" y="284" class="legend-text" fill="#333">主SRAM other: 20.8KB (16.2%)</text>
<rect x="420" y="290" width="12" height="12" fill="#E0E0E0" rx="2"/>
<text x="436" y="300" class="legend-text" fill="#333">主SRAM free: 47.9KB (37.4%)</text>
<text x="436" y="300" class="legend-text" fill="#333">主SRAM free: 54.5KB (42.6%)</text>
<rect x="60" y="322" width="12" height="12" fill="#2196F3" rx="2"/>
<text x="76" y="332" class="legend-text" fill="#333">CCM heap: 48.0KB (75.0%)</text>
<text x="76" y="332" class="legend-text" fill="#333">CCM FreeRTOS 堆: 56.0KB (87.9%)</text>
<rect x="60" y="338" width="12" height="12" fill="#E0E0E0" rx="2"/>
<text x="76" y="348" class="legend-text" fill="#333">CCM free: 16.0KB (25.0%)</text>
<text x="76" y="348" class="legend-text" fill="#333">CCM free: 7.7KB (12.1%)</text>
</svg>

Before

Width:  |  Height:  |  Size: 4.5 KiB

After

Width:  |  Height:  |  Size: 4.8 KiB

@@ -18,7 +18,7 @@
- F407 另有 **64KB CCM RAM**:**已于 2026-09-21 启用**并把 FreeRTOS 堆迁入(§1.2)
- 起步时没有任何运行时测量手段:不知道每个任务实际用了多少栈,改栈全靠拍脑袋
当前状态(**以 §2.1 受管快照为准**,由 `.map` 自动生成):RW_IRAM1 **89,168 / 131,072 B(68.0%)**、RW_CCM **49,184 / 65,280 B(75.3%)**。✅ **任务栈:2026-10-10 削链后 `defaultTask` 实测峰值 2152B/4096(余 47%)、静态最坏链 2776B(余 32%)**,6 任务全过 `stack_report.py` 判据(曾于 2026-10-04 升至 3664B/4096 ≈ 10%,登记为 S-1,现已销项),详见 §2.3 与"2026-10-10 复测"小节。
当前状态(**以 §2.1 受管快照为准**,由 `.map` 自动生成;2026-10-22 `@build`):RW_IRAM1 **75,264 / 131,072 B(57.4%)**、RW_CCM **57,376 / 65,280 B(87.9%)**。✅ **任务栈:`stack_report.py` 9/9 PASS**(2026-10-22 静态最坏链 + 同日真机 `[STACK]` 水位,`defaultTask` 最坏 3024B/5120(余 40%)、`protectTask` 872B/2048(余 57%),见 `data/stack_report_20261022.md`);**卡点已从 SRAM1 移到 CCM**(余 7,904 B)。历史:`defaultTask` 曾于 2026-10-04 达 3664B/4096 ≈10%,登记为 S-1,2026-10-10 削链后销项(详见"2026-10-10 复测"小节)。
本方案三个目标:
@@ -121,7 +121,7 @@
| idle(内核) | `configMINIMAL_STACK_SIZE=128` | 0.5KB | 空闲 | 未观测(未取句柄) |
| timer(内核) | `configTIMER_TASK_STACK_DEPTH=256` | 1KB | 软定时器 | 未观测 |
> 任务栈全部从 FreeRTOS 堆(48KB,2026-09-21 已迁 CCM)分配。**"配置栈"列以 `Src/freertos.c` 为唯一来源;实测峰值/余量判定以 §0.6 的 `stack_report` 基线为准**(本表数值同步自该基线)。
> 任务栈全部从 FreeRTOS 堆(56KB = `configTOTAL_HEAP_SIZE` 57,344 B;2026-09-21 迁 CCM、2026-10-12 扩容)分配。**"配置栈"列以 `Src/freertos.c` 为唯一来源;实测峰值/余量判定以 §0.6 的 `stack_report` 基线为准**(本表数值同步自该基线)。
### 2.4 动态分配点与隐患
@@ -135,12 +135,12 @@
|---|---|---|
| ① LVGL 内存池 40KB 对 1bpp 小屏菜单过大 | 40KB → **16KB ✅ 2026-10-15** | 高(目标 8~16KB;**开机实测 UI 仅用 ~3KB**,见 §2.6.2) |
| ② 任务栈"往大了给",无实测依据 | 38,912 B(38 KB 现值,§2.6.1) | 高(§0.6 已有实测:uiTask 3.8/16KB、netTask 0.6/4KB → 阶段 1.3 缩减) |
| ③ 64KB CCM 曾完全未利用,缓冲挤在主 SRAM | — | ✅ 2026-09-21 已启用(堆迁入 CCM),CCM 余量 ~16KB |
| ③ 64KB CCM 曾完全未利用,缓冲挤在主 SRAM | — | ✅ 2026-09-21 已启用(堆迁入 CCM);2026-10-22 CCM 余量 7,904 B(**全局卡点**)|
量化补充(来源:2026-09-11 审计快照,口径与方法见 §8.4):
- 两池(FreeRTOS 48KB + LVGL 40KB)曾占主 SRAM 的 **68%**,是当时"SRAM 几乎耗尽(95.4%,余 5.8KB)"的直接原因;**堆迁 CCM 后该结构性压力已解除**(现 RW_IRAM1 66.4%,见 §2.1)
- heap_4 **不合并相邻空闲块** → 长期运行需盯碎片,`min_ever`(现 4,848 B)是硬指标而非 `free`
- 两池(FreeRTOS 48KB + LVGL 40KB)曾占主 SRAM 的 **68%**,是当时"SRAM 几乎耗尽(95.4%,余 5.8KB)"的直接原因;**堆迁 CCM 后该结构性压力已解除**(2026-10-22 RW_IRAM1 57.4%,见 §2.1)
- heap_4 **不合并相邻空闲块** → 长期运行需盯碎片,`min_ever`(2026-10-22 实测 8,464 B,56KB 池)是硬指标而非 `free`
- 未映射资源:SRAM3 64KB(scatter 未映射;clone 芯片可能无此区);CCM 已用
- 曾经的三条中风险项:新任务无处安置(已缓解)、堆碎片(仍存)、栈溢出检测缺失 → **已于阶段 0 开启 `configCHECK_FOR_STACK_OVERFLOW=2` 并实测抓到过溢出**(§0.1)
@@ -148,7 +148,7 @@
### 2.6 堆 / LVGL 池 / C 库 / Flash 明细
#### 2.6.1 FreeRTOS 堆(48 KB;2026-09-21 已迁 CCM `RW_CCM`)
#### 2.6.1 FreeRTOS 堆(56 KB;2026-09-21 已迁 CCM `RW_CCM`)
`configTOTAL_HEAP_SIZE = 49,152`(heap_4)。内部构成:
@@ -0,0 +1,245 @@
# STM32F4-Base RAM/Flash Usage Report
> Generated: 2026-10-22 12:06 Map: `STM32F407-Demo.map`
## 1. Grand Totals (全局总量)
| Metric | Value |
|---|---|
| Code (inc. data) | 213,188 B (208.2 KB) |
| RO Data | 18,580 B (18.1 KB) |
| RW Data (initialized) | 1,184 B (1.16 KB) |
| ZI Data (zero-init) | 131,504 B (128.4 KB) |
| **Total RO (Code + RO)** | **231,768 B (226.3 KB)** |
| **Total RW (RW + ZI) = SRAM used** | **132,688 B (129.58 KB)** |
| **Total ROM (Code + RO + RW init)** | **232,952 B (227.5 KB)** |
## 2. 执行区水位 (Execution Regions)
| Region | Base | Size | Max | Util% |
|---|---|---|---|---|
| ER_IROM1 | 0x08000000 | 231,768 B (226.34 KB) | 1,048,576 B (1024 KB) | 22.1% |
| RW_CCM | 0x10000000 | 57,376 B (56.03 KB) | 65,280 B (64 KB) | 87.9% |
| UNINIT_CCM | 0x1000FF00 | 48 B (0.05 KB) | 256 B (0 KB) | 18.8% |
| RW_IRAM1 | 0x20000000 | 75,264 B (73.50 KB) | 131,072 B (128 KB) | 57.4% |
## 3. RW_IRAM1 逐符号明细(ZI + RW,按大小降序)
| # | Address | Size (B) | Size (KB) | Type | Section | Object |
|---|---|---|---|---|---|---|
| 1 | 0x2000ADC8 | 16,384 | 16.00 | Zero | .bss | lv_mem_core_builtin.o |
| 2 | 0x20007EB4 | 7,724 | 7.54 | Zero | .bss | lftpd.o |
| 3 | 0x200107A4 | 6,746 | 6.59 | Zero | .bss | ui_net_sink.o |
| 4 | 0x2000EDC8 | 6,408 | 6.26 | Zero | .bss | ui_port.o |
| 5 | 0x2000667C | 6,200 | 6.05 | Zero | .bss | nand_ftl.o |
| 6 | 0x20004E18 | 4,152 | 4.05 | Zero | .bss | ch395f.o |
| 7 | 0x20002054 | 4,096 | 4.00 | Zero | .bss | console_log.o |
| 8 | 0x20001274 | 3,552 | 3.47 | Zero | .bss | sys_log_mcu.o |
| 9 | 0x20003640 | 2,724 | 2.66 | Zero | .bss | default_task.o |
| 10 | 0x2000A524 | 1,720 | 1.68 | Zero | .bss | cmsis_os2.o |
| 11 | 0x2000444C | 1,440 | 1.41 | Zero | .bss | sample_ring.o |
| 12 | 0x20009F48 | 1,220 | 1.19 | Zero | .bss | tasks.o |
| 13 | 0x20000D74 | 1,104 | 1.08 | Zero | .bss | cfg_file_store_mcu.o |
| 14 | 0x20003054 | 1,064 | 1.04 | Zero | .bss | soe_store.o |
| 15 | 0x20012200 | 1,024 | 1.00 | Zero | | startup_stm32f407xx.o |
| 16 | 0x20005E50 | 800 | 0.78 | Zero | .bss | gd5f2gq5ue.o |
| 17 | 0x200040E8 | 712 | 0.70 | Zero | .bss | iec104_serv.o |
| 18 | 0x20000AB4 | 704 | 0.69 | Zero | .bss | cfg_file.o |
| 19 | 0x200005CC | 560 | 0.55 | Zero | .bss | spi.o |
| 20 | 0x20000844 | 552 | 0.54 | Zero | .bss | usart.o |
| 21 | 0x20009CE0 | 552 | 0.54 | Zero | .bss | lftpd_io.o |
| 22 | 0x2000ABDC | 492 | 0.48 | Zero | .bss | lv_init.o |
| 23 | 0x20004C38 | 480 | 0.47 | Zero | .bss | freq.o |
| 24 | 0x2000629C | 416 | 0.41 | Zero | .bss | net_socket.o |
| 25 | 0x200064DC | 416 | 0.41 | Zero | .bss | net_pending.o |
| 26 | 0x200034C0 | 384 | 0.38 | Zero | .bss | task_prof.o |
| 27 | 0x20004A8C | 320 | 0.31 | Zero | .bss | prot_set.o |
| 28 | 0x2000A40C | 280 | 0.27 | Zero | .bss | timers.o |
| 29 | 0x20006190 | 268 | 0.26 | Zero | .bss | rs485.o |
| 30 | 0x200011C4 | 176 | 0.17 | Zero | .bss | prot_set_store.o |
| 31 | 0x200004C4 | 168 | 0.16 | Zero | .bss | adc.o |
| 32 | 0x200002B0 | 160 | 0.16 | Data | .data | lftpd.o |
| 33 | 0x200049EC | 160 | 0.16 | Zero | .bss | phasor.o |
| 34 | 0x200106D0 | 152 | 0.15 | Zero | .bss | ui_menu_view.o |
| 35 | 0x2000643C | 128 | 0.12 | Zero | .bss | net_listener.o |
| 36 | 0x20004BCC | 108 | 0.11 | Zero | .bss | prot_elm.o |
| 37 | 0x2000056C | 84 | 0.08 | Zero | .bss | i2c.o |
| 38 | 0x200043F8 | 84 | 0.08 | Zero | .bss | adc_input.o |
| 39 | 0x2000024C | 83 | 0.08 | Data | .data | ftp_cfg.o |
| 40 | 0x200007FC | 72 | 0.07 | Zero | .bss | tim.o |
| 41 | 0x20000A6C | 72 | 0.07 | Zero | .bss | stm32f4xx_hal_timebase_tim.o |
| 42 | 0x2000347C | 68 | 0.07 | Zero | .bss | task_watchdog.o |
| 43 | 0x20000480 | 65 | 0.06 | Zero | .bss | freertos.o |
| 44 | 0x20009F08 | 64 | 0.06 | Zero | .bss | queue.o |
| 45 | 0x20000354 | 60 | 0.06 | Data | .data | tasks.o |
| 46 | 0x2000040C | 60 | 0.06 | Data | .data | ui_page_selftest.o |
| 47 | 0x20010768 | 60 | 0.06 | Zero | .bss | ui_page_runlog.o |
| 48 | 0x20000150 | 48 | 0.05 | Data | .data | adc_health.o |
| 49 | 0x200043C8 | 48 | 0.05 | Zero | .bss | iec104_pts.o |
| 50 | 0x20000000 | 44 | 0.04 | Data | .data | freertos.o |
| 51 | 0x2000009C | 44 | 0.04 | Data | .data | task_watchdog.o |
| 52 | 0x20000044 | 36 | 0.04 | Data | .data | sys_log_mcu.o |
| 53 | 0x20000228 | 36 | 0.04 | Data | .data | net_cfg.o |
| 54 | 0x20000448 | 32 | 0.03 | Data | .data | ui_standby.o |
| 55 | 0x20006170 | 32 | 0.03 | Zero | .bss | tpafe5160.o |
| 56 | 0x200064BC | 32 | 0.03 | Zero | .bss | net_events.o |
| 57 | 0x20000128 | 28 | 0.03 | Data | .data | adc_input.o |
| 58 | 0x20000068 | 24 | 0.02 | Data | .data | console_log.o |
| 59 | 0x200000E4 | 24 | 0.02 | Data | .data | adc_task.o |
| 60 | 0x20000180 | 24 | 0.02 | Data | .data | prot_set.o |
| 61 | 0x2000019C | 24 | 0.02 | Data | .data | freq.o |
| 62 | 0x200001FC | 24 | 0.02 | Data | .data | net_listener.o |
| 63 | 0x200043B0 | 24 | 0.02 | Zero | .bss | protect_task.o |
| 64 | 0x20000030 | 20 | 0.02 | Data | .data | cfg_file.o |
| 65 | 0x20000080 | 20 | 0.02 | Data | .data | soe_store.o |
| 66 | 0x200001D0 | 20 | 0.02 | Data | .data | gd5f2gq5ue.o |
| 67 | 0x20000390 | 20 | 0.02 | Data | .data | timers.o |
| 68 | 0x20000468 | 20 | 0.02 | Data | .data | ui_net_sink.o |
| 69 | 0x200003D0 | 18 | 0.02 | Data | .data | ui_menu.o |
| 70 | 0x200000FC | 16 | 0.02 | Data | .data | iec104_serv.o |
| 71 | 0x20000118 | 16 | 0.02 | Data | .data | iec104_pts.o |
| 72 | 0x20000214 | 16 | 0.02 | Data | .data | net_events.o |
| 73 | 0x200002A0 | 16 | 0.02 | Data | .data | nand_ftl.o |
| 74 | 0x200003EC | 16 | 0.02 | Data | .data | ui_content_page.o |
| 75 | 0x200000D4 | 12 | 0.01 | Data | .data | default_task.o |
| 76 | 0x200001B4 | 12 | 0.01 | Data | .data | stm32f4xx_hal.o |
| 77 | 0x200001E4 | 12 | 0.01 | Data | .data | tpafe5160.o |
| 78 | 0x200001F0 | 12 | 0.01 | Data | .data | net_socket.o |
| 79 | 0x200003A8 | 12 | 0.01 | Data | .data | port.o |
| 80 | 0x200003C4 | 12 | 0.01 | Data | .data | ui_port.o |
| 81 | 0x200003FC | 12 | 0.01 | Data | .data | ui_page_password.o |
| 82 | 0x200005C0 | 12 | 0.01 | Zero | .bss | iwdg.o |
| 83 | 0x200001C4 | 10 | 0.01 | Data | .data | ch395f.o |
| 84 | 0x20000094 | 8 | 0.01 | Data | .data | sys_clock.o |
| 85 | 0x200000C8 | 8 | 0.01 | Data | .data | task_prof.o |
| 86 | 0x2000010C | 8 | 0.01 | Data | .data | protect_task.o |
| 87 | 0x20000144 | 8 | 0.01 | Data | .data | sample_ring.o |
| 88 | 0x200003B4 | 8 | 0.01 | Data | .data | ff.o |
| 89 | 0x200003BC | 8 | 0.01 | Data | .data | ffsystem.o |
| 90 | 0x200003E4 | 8 | 0.01 | Data | .data | ui_menu_view.o |
| 91 | 0x2000002C | 4 | 0.00 | Data | .data | app_main.o |
| 92 | 0x200000D0 | 4 | 0.00 | Data | .data | vol_lock.o |
| 93 | 0x200000E0 | 4 | 0.00 | Data | .data | net_task.o |
| 94 | 0x200001C0 | 4 | 0.00 | Data | .data | system_stm32f4xx.o |
| 95 | 0x20000224 | 4 | 0.00 | Data | .data | net_pending.o |
| 96 | 0x20000350 | 4 | 0.00 | Data | .data | lftpd_io.o |
| 97 | 0x200003A4 | 4 | 0.00 | Data | .data | cmsis_os2.o |
| 98 | 0x2000047C | 4 | 0.00 | Data | .data | mc_w.l(errno.o) |
| 99 | 0x2000014C | 2 | 0.00 | Data | .data | phasor.o |
| 100 | 0x20000408 | 2 | 0.00 | Data | .data | ui_page_net_cfg.o |
| 101 | 0x20000198 | 1 | 0.00 | Data | .data | prot_elm.o |
| | | **75,239** | **73.48** | | | **Total** |
## 4. Image Component Sizes(RW + ZI > 0,按大小降序)
| # | Object | Code | RO | RW | ZI | RW+ZI |
|---|---|---|---|---|---|---|
| 1 | heap_4.o | 512 | 69 | 32 | 57,344 | 57,376 |
| 2 | lv_mem_core_builtin.o | 410 | 0 | 0 | 16,384 | 16,384 |
| 3 | lftpd.o | 6,108 | 509 | 160 | 7,724 | 7,884 |
| 4 | ui_net_sink.o | 1,126 | 67 | 20 | 6,746 | 6,766 |
| 5 | ui_port.o | 418 | 0 | 12 | 6,408 | 6,420 |
| 6 | nand_ftl.o | 2,352 | 175 | 16 | 6,200 | 6,216 |
| 7 | ch395f.o | 3,986 | 603 | 10 | 4,152 | 4,162 |
| 8 | console_log.o | 980 | 55 | 24 | 4,096 | 4,120 |
| 9 | sys_log_mcu.o | 3,074 | 83 | 36 | 3,552 | 3,588 |
| 10 | default_task.o | 2,332 | 280 | 12 | 2,724 | 2,736 |
| 11 | cmsis_os2.o | 1,496 | 0 | 4 | 1,720 | 1,724 |
| 12 | sample_ring.o | 292 | 0 | 8 | 1,440 | 1,448 |
| 13 | tasks.o | 4,342 | 0 | 60 | 1,220 | 1,280 |
| 14 | cfg_file_store_mcu.o | 380 | 24 | 0 | 1,104 | 1,104 |
| 15 | soe_store.o | 816 | 28 | 20 | 1,064 | 1,084 |
| 16 | startup_stm32f407xx.o | 36 | 392 | 0 | 1,024 | 1,024 |
| 17 | gd5f2gq5ue.o | 3,930 | 478 | 20 | 800 | 820 |
| 18 | iec104_serv.o | 1,188 | 16 | 16 | 712 | 728 |
| 19 | cfg_file.o | 5,970 | 810 | 20 | 704 | 724 |
| 20 | spi.o | 584 | 0 | 0 | 560 | 560 |
| 21 | lftpd_io.o | 1,198 | 0 | 4 | 552 | 556 |
| 22 | usart.o | 884 | 0 | 0 | 552 | 552 |
| 23 | freq.o | 764 | 8 | 24 | 480 | 504 |
| 24 | lv_init.o | 156 | 0 | 0 | 492 | 492 |
| 25 | net_socket.o | 4,916 | 443 | 12 | 416 | 428 |
| 26 | net_pending.o | 1,400 | 0 | 4 | 416 | 420 |
| 27 | task_prof.o | 638 | 185 | 8 | 384 | 392 |
| 28 | prot_set.o | 844 | 640 | 24 | 320 | 344 |
| 29 | timers.o | 1,232 | 0 | 20 | 280 | 300 |
| 30 | rs485.o | 120 | 0 | 0 | 268 | 268 |
| 31 | prot_set_store.o | 236 | 74 | 0 | 176 | 176 |
| 32 | adc.o | 580 | 0 | 0 | 168 | 168 |
| 33 | phasor.o | 420 | 258 | 2 | 160 | 162 |
| 34 | task_watchdog.o | 1,110 | 137 | 44 | 116 | 160 |
| 35 | ui_menu_view.o | 2,034 | 16 | 8 | 152 | 160 |
| 36 | net_listener.o | 948 | 207 | 24 | 128 | 152 |
| 37 | adc_input.o | 592 | 27 | 28 | 84 | 112 |
| 38 | freertos.o | 712 | 592 | 44 | 65 | 109 |
| 39 | prot_elm.o | 708 | 227 | 1 | 108 | 109 |
| 40 | i2c.o | 168 | 0 | 0 | 84 | 84 |
| 41 | ftp_cfg.o | 152 | 225 | 83 | 0 | 83 |
| 42 | stm32f4xx_hal_timebase_tim.o | 152 | 0 | 0 | 72 | 72 |
| 43 | tim.o | 168 | 0 | 0 | 72 | 72 |
| 44 | iec104_pts.o | 1,082 | 132 | 16 | 48 | 64 |
| 45 | queue.o | 2,840 | 0 | 0 | 64 | 64 |
| 46 | ui_page_runlog.o | 28 | 105 | 0 | 60 | 60 |
| 47 | ui_page_selftest.o | 28 | 109 | 60 | 0 | 60 |
| 48 | adc_health.o | 432 | 0 | 48 | 0 | 48 |
| 49 | net_events.o | 1,994 | 809 | 16 | 32 | 48 |
| 50 | tpafe5160.o | 808 | 67 | 12 | 32 | 44 |
| 51 | net_cfg.o | 832 | 103 | 36 | 0 | 36 |
| 52 | protect_task.o | 840 | 68 | 8 | 24 | 32 |
| 53 | ui_standby.o | 1,208 | 67 | 32 | 0 | 32 |
| 54 | (incl. Padding) | 284 | 96 | 16 | 9 | 25 |
| 55 | adc_task.o | 1,520 | 234 | 24 | 0 | 24 |
| 56 | ui_menu.o | 760 | 137 | 18 | 0 | 18 |
| 57 | ui_content_page.o | 1,664 | 81 | 16 | 0 | 16 |
| 58 | iwdg.o | 28 | 0 | 0 | 12 | 12 |
| 59 | port.o | 762 | 73 | 12 | 0 | 12 |
| 60 | stm32f4xx_hal.o | 116 | 0 | 12 | 0 | 12 |
| 61 | ui_page_password.o | 686 | 175 | 12 | 0 | 12 |
| 62 | ff.o | 7,962 | 282 | 8 | 0 | 8 |
| 63 | ffsystem.o | 204 | 20 | 8 | 0 | 8 |
| 64 | sys_clock.o | 1,788 | 286 | 8 | 0 | 8 |
| 65 | app_main.o | 2,272 | 65 | 4 | 0 | 4 |
| 66 | net_task.o | 1,460 | 72 | 4 | 0 | 4 |
| 67 | system_stm32f4xx.o | 16 | 24 | 4 | 0 | 4 |
| 68 | vol_lock.o | 92 | 0 | 4 | 0 | 4 |
| 69 | errno.o | 20 | 0 | 4 | 0 | 4 |
| 70 | ui_page_net_cfg.o | 1,004 | 105 | 2 | 0 | 2 |
| | **All .o files** | **213,188** | **18,580** | **1,184** | **131,504** | **132,688** |
## 5. Flash (Code + RO Data) Top-30(按大小降序)
| # | Object | Code | RO Data | Code+RO |
|---|---|---|---|---|
| 1 | ff.o | 7,962 | 282 | 8,244 |
| 2 | cfg_file.o | 5,970 | 810 | 6,780 |
| 3 | lftpd.o | 6,108 | 509 | 6,617 |
| 4 | net_socket.o | 4,916 | 443 | 5,359 |
| 5 | lv_indev.o | 4,928 | 0 | 4,928 |
| 6 | ch395f.o | 3,986 | 603 | 4,589 |
| 7 | ui_font_cn_12.o | 0 | 4,545 | 4,545 |
| 8 | lv_obj_pos.o | 4,526 | 16 | 4,542 |
| 9 | lv_refr.o | 4,486 | 0 | 4,486 |
| 10 | gd5f2gq5ue.o | 3,930 | 478 | 4,408 |
| 11 | tasks.o | 4,342 | 0 | 4,342 |
| 12 | lv_obj_scroll.o | 4,126 | 0 | 4,126 |
| 13 | lv_obj_style.o | 4,106 | 0 | 4,106 |
| 14 | lv_draw_sw_mask.o | 3,862 | 0 | 3,862 |
| 15 | stm32f4xx_hal_spi.o | 3,586 | 0 | 3,586 |
| 16 | lv_draw_sw_box_shadow.o | 3,246 | 0 | 3,246 |
| 17 | sys_log_mcu.o | 3,074 | 83 | 3,157 |
| 18 | lv_indev_scroll.o | 3,068 | 0 | 3,068 |
| 19 | lv_obj.o | 2,982 | 43 | 3,025 |
| 20 | lv_draw_sw_img.o | 2,960 | 0 | 2,960 |
| 21 | queue.o | 2,840 | 0 | 2,840 |
| 22 | journal.o | 2,834 | 0 | 2,834 |
| 23 | iec104_link.o | 2,824 | 0 | 2,824 |
| 24 | net_events.o | 1,994 | 809 | 2,803 |
| 25 | default_task.o | 2,332 | 280 | 2,612 |
| 26 | nand_ftl.o | 2,352 | 175 | 2,527 |
| 27 | app_main.o | 2,272 | 65 | 2,337 |
| 28 | printfa.o | 2,312 | 0 | 2,312 |
| 29 | stm32f4xx_hal_i2c.o | 2,294 | 0 | 2,294 |
| 30 | lv_display.o | 2,284 | 0 | 2,284 |
| | **All .o files** | **213,188** | **18,580** | **231,768** |
> 自洽校验:per-file 213,188/18,580/1,184/131,504 vs Grand Totals 213,188/18,580/1,184/131,504(CodeΔ=+0 / ROΔ=+0 / RWΔ=+0 / ZIΔ=+0;精确一致(含 '('incl. Generated)'-'(incl. Padding)' 台账行))→ **PASS**
@@ -0,0 +1,100 @@
# Stack budget report
- Source: `MDK-ARM\STM32F407-Demo\STM32F407-Demo.htm` (Keil static call graph)
- Linker run: Thu Oct 22 10:36:17 2026
- Global maximum stack usage: **3024 B** (any call tree)
- Runtime watermark: 8 task(s) from 串口采集日志 `.dsh\skills\serial-assistant\scripts\state\serial.log`
Rule: `margin = cfg - max(static_max_depth, runtime_peak)` must be >= max(1024 B ISR, 30% of cfg).
| Task | Entry | Cfg (B) | Static max (B) | Runtime peak (B) | Worst (B) | Margin (B) | Margin % | Verdict |
|---|---|---|---|---|---|---|---|---|
| defaultTask | `StartDefaultTask` | 5120 | 3024 | 2164 | 3024 | 2096 | 40% | PASS |
| netTask | `StartNetTask` | 3840 | 1912 | 1116 | 1912 | 1928 | 50% | PASS |
| ftpTask | `StartFtpTask` | 4096 | 1456 | 756 | 1456 | 2640 | 64% | PASS |
| uiTask | `StartUiTask` | 16384 | 1336 | 4052 | 4052 | 12332 | 75% | PASS |
| testTask | `start_test_task` | 8192 | 736 | - | 736 | 7456 | 91% | PASS |
| timeSyncTask | `time_sync_task` | 2560 | 1264 | 616 | 1264 | 1296 | 50% | PASS |
| adcTask | `adc_task_func` | 2560 | 1320 | 952 | 1320 | 1240 | 48% | PASS |
| iec104Task | `iec104_serv_task` | 2560 | 1456 | 544 | 1456 | 1104 | 43% | PASS |
| protectTask | `protect_task` | 2048 | 872 | 828 | 872 | 1176 | 57% | PASS |
`Static max` = armlink worst-case call-tree depth; `Runtime peak` = measured high-water mark (boot + whatever workload ran before the sample); `Worst` = max of the two, which is what the verdict is computed from.
Runtime heap: free=15800 B, min_ever=8464 B
## Worst call chain per task
### defaultTask: 3024 B (own frame 0 B)
```
StartDefaultTask -> default_task_func -> storage_bring_up -> net_cfg_load -> cfg_file_load -> s_write_template -> s_write_doc -> cfg_store_write_atomic -> f_rename -> sync_fs -> disk_ioctl -> ftl_flush_cache -> dhara_map_write -> prepare_write -> auto_gc -> dhara_map_gc -> try_recover -> raw_gc -> trace_path -> dhara_journal_read_meta -> dhara_nand_read -> gd5f_read_from_cache -> dbg_console_write_line -> con_append -> dbg_console_drain -> MX_USART1_UART_Init -> HAL_UART_Init -> HAL_UART_MspInit -> Error_Handler -> dbg_console_write_line (Cycle)
```
### netTask: 1912 B (own frame 0 B)
```
StartNetTask -> net_task_func -> net_process_messages -> net_listener_tick -> net_relisten_locked -> ch395f_read_recv_buf -> ch395f_spi_dma_transfer -> ch395f_spi_fault_set -> dbg_console_write_line -> con_append -> dbg_console_drain -> MX_USART1_UART_Init -> HAL_UART_Init -> HAL_UART_MspInit -> Error_Handler -> dbg_console_write_line (Cycle)
```
### ftpTask: 1456 B (own frame 24 B)
```
StartFtpTask -> lftpd_start -> lftpd_inet_listen -> net_bind -> ch395f_open_socket -> ch395f_poll_cmd_status -> ch395f_get_cmd_status -> ch395f_write_cmd -> ch395f_spi_fault_set -> dbg_console_write_line -> con_append -> dbg_console_drain -> MX_USART1_UART_Init -> HAL_UART_Init -> HAL_UART_MspInit -> Error_Handler -> dbg_console_write_line (Cycle)
```
### uiTask: 1336 B (own frame 0 B)
```
StartUiTask -> ui_task_func -> ui_menu_init -> ui_menu_view_init -> lv_obj_create -> lv_obj_class_init_obj -> lv_theme_apply -> lv_obj_remove_style_all -> lv_obj_remove_style -> lv_obj_refresh_style -> refresh_children_style -> refresh_children_style (Cycle)
```
### testTask: 736 B (own frame 224 B)
```
start_test_task -> dbg_console_write_line -> con_append -> dbg_console_drain -> MX_USART1_UART_Init -> HAL_UART_Init -> HAL_UART_MspInit -> Error_Handler -> dbg_console_write_line (Cycle)
```
### timeSyncTask: 1264 B (own frame 152 B)
```
time_sync_task -> time_sync_open_listener -> net_bind -> ch395f_open_socket -> ch395f_poll_cmd_status -> ch395f_get_cmd_status -> ch395f_write_cmd -> ch395f_spi_fault_set -> dbg_console_write_line -> con_append -> dbg_console_drain -> MX_USART1_UART_Init -> HAL_UART_Init -> HAL_UART_MspInit -> Error_Handler -> dbg_console_write_line (Cycle)
```
### adcTask: 1320 B (own frame 312 B)
```
adc_task_func -> adc_health_report -> syslog_post -> dbg_console_write_line -> con_append -> dbg_console_drain -> MX_USART1_UART_Init -> HAL_UART_Init -> HAL_UART_MspInit -> Error_Handler -> dbg_console_write_line (Cycle)
```
### iec104Task: 1456 B (own frame 160 B)
```
iec104_serv_task -> s_serv_poll -> s_service_link -> s_drop_conn -> s_bind_listen -> net_bind -> ch395f_open_socket -> ch395f_poll_cmd_status -> ch395f_get_cmd_status -> ch395f_write_cmd -> ch395f_spi_fault_set -> dbg_console_write_line -> con_append -> dbg_console_drain -> MX_USART1_UART_Init -> HAL_UART_Init -> HAL_UART_MspInit -> Error_Handler -> dbg_console_write_line (Cycle)
```
### protectTask: 872 B (own frame 160 B)
```
protect_task -> s_log_active_edge -> dbg_console_write_line -> con_append -> dbg_console_drain -> MX_USART1_UART_Init -> HAL_UART_Init -> HAL_UART_MspInit -> Error_Handler -> dbg_console_write_line (Cycle)
```
## Frame-size ranking (own frame, top 15)
| Bytes | Function |
|---|---|
| 624 | `lv_draw_sw_arc` |
| 400 | `draw_line_skew` |
| 352 | `lv_draw_sw_triangle` |
| 352 | `net_stack_bring_up` |
| 328 | `lv_obj_redraw` |
| 328 | `view_frame_draw_cb` |
| 324 | `s_transform` |
| 320 | `wd_stall_evidence` |
| 312 | `adc_task_func` |
| 312 | `nc_draw_cb` |
| 304 | `lv_draw_label_iterate_characters` |
| 304 | `lv_draw_sw_box_shadow` |
| 304 | `soe_store_drain` |
| 304 | `sys_clock_init` |
| 296 | `net_relisten_locked` |
@@ -117,8 +117,12 @@ Graphviz / Cppcheck 的安装位置差异一律用 `DTU_*` 环境变量表达(
- 链接器使用**手动 scatter 文件** `MDK-ARM/STM32F407-Demo/STM32F407-Demo.sct`(在仓库,`.uvprojx` 已启用 `useFile=1`):
- `ER_IROM1` 0x08000000,1 MB(代码 + 只读数据)
- `RW_IRAM1` 0x20000000,**128 KB**(SRAM1 112K + SRAM2 16K 连续;SRAM3/CCM 故意未映射——克隆芯片可能无 SRAM3)
- Keil `Options for Target → Target` 页显示的片上内存来自 `.uvprojx` 的 `<Cpu>` 行(IRAM 112 KB / IRAM2 16 KB / IROM 1 MB,同样在仓库)+ Device Pack
- `RW_IRAM1` 0x20000000,**128 KB**(SRAM1 112K + SRAM2 16K 连续,**DMA 可达区**:存储/网络/串口 DMA
缓冲只能放这里;克隆芯片可能无 SRAM3,换料必须重测)
- `RW_CCM` 0x10000000,**64 KB 减 256 B 尾**(2026-10-11 起启用,承载 FreeRTOS 堆与 `.noinit` 崩溃现场;
**CCM 不可被 DMA 寻址**——规则 R2/R3 见 `docs/内存管理/内存内容分配策略.md` §4,并有编译期窗口断言 +
启动期自检 + 运行时守卫三层强制)
- Keil `Options for Target → Target` 页显示的片上内存来自 `.uvprojx` 的 `<Cpu>` 行(IRAM 112 KB / IRAM2 16 KB / IROM 1 MB,同样在仓库)+ Device Pack(**GUI 那几格不含 CCM 段,别据此判断"CCM 没用"**)
- ⚠ 新机器**不要在 GUI 里重填 RAM 区**,也**勿改 Target 页**(会回写共享的 `.uvprojx` 影响他人);确需调整内存布局(如日后启用 SRAM3/CCM)时修改 `.sct` 并提交
### 2.3 PC 侧编译环境(模拟器 + 主机侧单元测试,必需)
@@ -476,10 +480,10 @@ $env:DTU_KEIL_UV4 = 'D:\NotExist\UV4.exe' # 仅当前会话,不动用户级
| 项 | 值 | 说明 |
|---|---|---|
| MCU | STM32F407ZGTx | Cortex-M4(双精度 FPU / TrustZone),LQFP144 |
| MCU | STM32F407ZGTx | Cortex-M4,**单精度** FPU(VFPv4-SP,**无硬件双精度**,`double` 走软件模拟;无 TrustZone),LQFP144 |
| 主频 | 168 MHz | HSE 8 MHz → PLL M=4 / N=168 / P=2(`Inc/stm32f4xx_hal_conf.h`) |
| Flash | 1 MB(0x08000000) | 片内 |
| SRAM | 192 KB(128 KB 主 + 64 KB CCM) | 当前仅用主 SRAM 128 KB;SRAM3/CCM 未映射(§3.3) |
| SRAM | 192 KB(128 KB 主 + 64 KB CCM) | **两块都在用**:主 SRAM1 128 KB(DMA 可达)+ CCM(FreeRTOS 堆);SRAM3 仍未映射 → §3.3 |
| RTOS | FreeRTOS V10.3.1(CMSIS-RTOS V2) | HAL 时基 TIM7,tick = 1 ms;NVIC 4 位抢占,外设中断优先级 ≥5 |
### 3.2 外设硬件要求(引脚级)
@@ -505,22 +509,18 @@ $env:DTU_KEIL_UV4 = 'D:\NotExist\UV4.exe' # 仅当前会话,不动用户级
| CCM(`RW_CCM` 段) | 57,376 B(56.0 KB) | 65,280 B | **87.9%** | **7,904 B** | **当前真正的红线**:加任务 / 抬栈 / 加堆会先在这里撞墙;`mem-budget` 钩子在 92% 拦提交 |
- LVGL 内存池 16 KB(静态占用,2026-10-15 由 40 KB 收缩);FreeRTOS 堆 56 KB(CCM)
- 任务栈预算(words,1 word = 4 B):
- **任务栈配置不在本文重复**:唯一事实源是 `Src/freertos.c`(`.ioc` 的 `FREERTOS.Tasks01`),
受管表只有两份——根 `README.md` 与 `AGENTS.md` 里由 `scripts/gen_task_table.py --write` 生成的
`TASK_TABLE` 块,由 `task-table` 钩子校验;每任务的静态最坏链、实测峰值与余量台账在
`docs/内存管理/RAM使用分析与优化方案.md` §2.3(配置与实测峰值)与 §0.6(静态最坏链与判定)。
- ⚠ **不要再在本文抄一份任务表**:`task-table` 钩子的校验面只有上面那两份受管块,抄出来的副本
没有门禁、改栈后必然漂(2026-10-22 删除本文历史副本时,它已缺 `adcTask` / `iec104Task` /
`protectTask` 三个任务,`defaultTask`/`timeSyncTask` 两值也停在旧数)。
| 任务 | 栈大小 | 优先级 |
|---|---|---|
| defaultTask | 1024 | Normal |
| netTask | 960 | Normal |
| ftpTask | 1024 | Normal |
| testTask | 2048 | BelowNormal |
| uiTask | 4096 | BelowNormal |
| timeSyncTask | 512 | Normal |
> 上表由 `scripts/gen_task_table.py` 同源(唯一来源 `Src/freertos.c` / `.ioc` 的 `FREERTOS.Tasks01`);
> 2026-10-10 栈整改**未改任何栈值**,靠削调用链达标(详见 `docs/内存管理/RAM使用分析与优化方案.md`)。
> 挖潜方向(未实施):SRAM3 64 KB / CCM 64 KB 未映射——详见 `docs/内存管理/`。
> 部署侧含义:**不要做任何需要额外 RAM 的固件定制**,新增功能先过内存预算。
> 挖潜方向:**SRAM3 64 KB 仍未映射**;CCM 64 KB **已启用**(2026-10-11 起承载 FreeRTOS 堆,见上表
> `RW_CCM` 行),配额与台账 → `docs/内存管理/RAM预算台账.md`。
> 部署侧含义:**新增需要额外 RAM 的固件定制前必须先过内存预算**——余量集中在 SRAM1,CCM 只剩
> 7,904 B,加任务 / 抬栈 / 加堆会先在 CCM 撞墙。
### 3.4 下载与调试
@@ -649,6 +649,7 @@ $env:DTU_KEIL_UV4 = 'D:\NotExist\UV4.exe' # 仅当前会话,不动用户级
| 日期 | 变更 |
|---|---|
| 2026-10-22 | **§3.2 / §3.3 与 §2.2 内存事实纠偏**:① §3.3 资源预算表由 2026-09-11 快照刷新为 2026-10-22 实测——Flash 231,768 B / 22.1%,主 SRAM1 由"125,104 B / 95.4% / 极度紧张"更正为 **75,264 B / 57.4%(余 55,808 B)**,并**补上 CCM 行**(57,376 B / 87.9%,余 7,904 B)——当前红线在 CCM 而非 SRAM1;② 删除 §3.3 的任务栈副本表(无门禁、已缺 `adcTask`/`iec104Task`/`protectTask`),改指针到 `README.md`/`AGENTS.md` 受管表与 `RAM使用分析与优化方案.md` §2.3/§0.6;③ §2.2 链接布局补 `RW_CCM 0x10000000` 段(2026-10-11 已启用、**不可 DMA**),§3.1 MCU 行更正为**单精度 FPU、无 TrustZone**(原写"双精度 FPU / TrustZone"是芯片事实错误,也正是报告 F-6 双精度软浮点被长期误判的土壤) |
| 2026-10-12 | **远程 CI 文档迁出本目录**:`CI配置指南.md` 与 `Gitea_Runner部署指南.md` 合并为 **`docs/CI/远程CI完整指南.md`**(入口 `docs/CI/README.md`),本文三处引用改指新路径与新章节号(§4.1.3 机器级变量、§7 相关文档登记)。原因:远程 CI 只涉及本机一台 runner,不应与"任何机器都要过"的部署核对清单混在一起 |
| 2026-10-10 | **删除旧档 NCFG 迁移**(升级/迁移口径变更):旧固件遗留的 `/cfg/netcfg.bin` 不再"首启自动迁移"——旧档若存在则被忽略(不读、不删、不迁移),配置直接回落工厂默认(`/config.ini` `[network]` 段模板)。`net_cfg_legacy.c/h` 与 `cfg_store_legacy_path()` 已移除;携带旧档的设备升级后需经 UI 或 FTP 重新配置网口。§五 现场部署 IP 行同步 |
| 2026-10-02 | **模拟器构建修复**(配置引擎重构遗留):`simulator/CMakeLists.txt` 的配置文件引擎引用同步到迁移后位置——包含目录 `App/`→`App/cfg/`(`cfg_file.h`/`cfg_file_store.h`)、源文件 `App/cfg_file.c`→`App/cfg/cfg_file.c`、包含目录 `test/`→`test/board/`(`test_config.h`/`net_latency_test.h`);全量 include 离线核查无其他断链,模拟器全量构建恢复(`--cfgtest` 四场景 PASS,rc=0)。§2.12 验证链 ⑤ 行同步更新 |
+3 -1
View File
@@ -365,7 +365,9 @@ def main():
if args.output:
pathlib.Path(args.output).parent.mkdir(parents=True, exist_ok=True)
pathlib.Path(args.output).write_text(report, encoding="utf-8")
# newline="\n":Windows 下 write_text 默认把 \n 翻成 \r\n,会跟 .gitattributes
# (* text=auto eol=lf)与 data/ 目录既有快照的行尾不一致(2026-10-22 实测)。
pathlib.Path(args.output).write_text(report, encoding="utf-8", newline="\n")
print(f"OK: Report written to {args.output}", file=sys.stderr)
else:
print(report)
+12 -3
View File
@@ -176,12 +176,20 @@ def main():
add = out.append
add('# Stack budget report')
add('')
add(f'- Source: `{html_path.relative_to(REPO_ROOT)}` (Keil static call graph)')
# 显示用相对路径:传相对 -i 时 relative_to 会抛 ValueError(2026-10-22 实测踩到),
# 且绝不把本机绝对路径写进入库的 data/ 快照。
def _disp(pth):
try:
return pathlib.Path(pth).resolve().relative_to(REPO_ROOT)
except (ValueError, OSError):
return pathlib.Path(pth).name
add(f'- Source: `{_disp(html_path)}` (Keil static call graph)')
if meta["updated"]:
add(f'- Linker run: {meta["updated"]}')
add(f'- Global maximum stack usage: **{meta["max_stack"]} B** (any call tree)')
if runtime:
add(f'- Runtime watermark: {len(runtime)} task(s) from `{args.serial}`')
add(f'- Runtime watermark: {len(runtime)} task(s) from 串口采集日志 `{_disp(args.serial)}`')
add('')
add('Rule: `margin = cfg - max(static_max_depth, runtime_peak)` must be '
f'>= max({args.isr_margin} B ISR, {args.min_free_pct}% of cfg).')
@@ -252,7 +260,8 @@ def main():
report = '\n'.join(out)
if args.output:
pathlib.Path(args.output).write_text(report, encoding='utf-8')
# newline="\n":避免 Windows 换行翻译污染 data/ 快照的 LF 行尾(与 .gitattributes 一致)。
pathlib.Path(args.output).write_text(report, encoding='utf-8', newline='\n')
print(f'written: {args.output}')
else:
print(report)
+88 -61
View File
@@ -17,39 +17,41 @@ import sys
# Diagram data
# ---------------------------------------------------------------------------
# ── RAM breakdown (2026-09-21 @build, map Grand Totals; data/ram_report_20260921.md) ──
# ── RAM breakdown (2026-10-22 @build, map Grand Totals; data/ram_report_20261022.md) ──
# Region 1: main SRAM RW_IRAM1 @0x20000000 (128KB = 131,072 B, DMA-reachable)
SRAM1_TOTAL = 131072
SRAM1_USED = 82048
SRAM1_USED = 75264
SRAM1_FREE = SRAM1_TOTAL - SRAM1_USED
# Region 2: CCM RW_CCM @0x10000000 (64KB = 65,536 B, CPU only, DMA NOT reachable)
CCM_TOTAL = 65536
CCM_USED = 49184
# Region 2: CCM RW_CCM @0x10000000 —— scatter 给的是 0x0000FF00 = 65,280 B(CPU only,
# DMA 不可达);另有 0x1000FF00 起 256 B 的 UNINIT_CCM 尾区(.noinit 崩溃现场用 48 B)
CCM_TOTAL = 65280
CCM_USED = 57376
CCM_FREE = CCM_TOTAL - CCM_USED
RAM1_CATEGORIES = [
("LVGL", 41460, "#4CAF50"),
("UI", 13532, "#FF9800"),
("FTL/BBT", 8812, "#9C27B0"),
("lftpd", 5324, "#8BC34A"),
("CMSIS", 5016, "#00BCD4"),
("NET", 4445, "#F44336"),
("HAL", 1280, "#607D8B"),
("LVGL 池 16KB", 16876, "#4CAF50"),
("UI", 13546, "#FF9800"),
("lftpd", 8440, "#8BC34A"),
("FTL/BBT", 7036, "#9C27B0"),
("NET/CH395F", 5250, "#F44336"),
("CMSIS-RTOS", 1724, "#00BCD4"),
("HAL", 84, "#607D8B"),
("C stack", 1024, "#795548"),
("other", 1155, "#9E9E9E"),
("other", 21284, "#9E9E9E"),
("free", SRAM1_FREE, "#E0E0E0"),
]
RAM2_CATEGORIES = [
("heap", 49184, "#2196F3"),
("FreeRTOS 堆", CCM_USED, "#2196F3"),
("free", CCM_FREE, "#E0E0E0"),
]
# ── Flash breakdown (1MB = 1,048,576 B; 2026-09-21 @build) ──────────────────
# ── Flash breakdown (1MB = 1,048,576 B; 2026-10-22 @build) ──────────────────
FLASH_TOTAL = 1048576
FLASH_CODE = 234976
FLASH_RO = 13824
FLASH_RW = 632
FLASH_USED = FLASH_CODE + FLASH_RO + FLASH_RW
FLASH_CODE = 213188
FLASH_RO = 18580
FLASH_RW = 1184
FLASH_USED = FLASH_CODE + FLASH_RO + FLASH_RW # ROM 合计(含 RW 初始化表)
FLASH_IROM = FLASH_CODE + FLASH_RO # ER_IROM1 区用量 = 231,768 B(22.1%)
FLASH_FREE = FLASH_TOTAL - FLASH_USED
FLASH_CATEGORIES = [
@@ -59,37 +61,42 @@ FLASH_CATEGORIES = [
("Flash 空闲", FLASH_FREE, "#E0E0E0"),
]
# Flash code sub-breakdown (2026-09-21; top items from data/ram_report_20260921.md,
# long-tail .o objects below the report's top-44 threshold are counted in "其他")
# Flash Code+RO 分布(= ER_IROM1 231,768 B;2026-10-22 按 .map 对象表逐对象归类,
# 口径同 data/ram_report_20261022.md;"其他"含链接器填充)
FLASH_CODE_SUB = [
("LVGL 库", 98000, "#4CAF50"),
("应用代码/UI", 34110, "#2196F3"),
("FatFS", 8146, "#9C27B0"),
("dhara / FTL", 4982, "#FF9800"),
("HAL 驱动", 16000, "#607D8B"),
("FreeRTOS 内核", 10132, "#00BCD4"),
("CH395F 网络", 2738, "#F44336"),
("C 库 (microlib)", 4230, "#795548"),
("其他 (长尾)", 56638, "#E0E0E0"), # remainder
("LVGL 库", 87706, "#4CAF50"),
("应用代码/UI", 57011, "#2196F3"),
("CH395F+FTP", 25652, "#F44336"),
("HAL 驱动", 17876, "#607D8B"),
("dhara / FTL", 10907, "#FF9800"),
("FreeRTOS 内核", 10762, "#00BCD4"),
("FatFS", 8468, "#9C27B0"),
("C 库 (microlib)", 5486, "#795548"),
("startup", 468, "#B0BEC5"),
("其他 (长尾)", 7432, "#E0E0E0"), # remainder
]
# ── Task stack budgets (2026-10-10 @build + 开机/空闲实测; data/stack_report_20261010.md) ──
# ── Task stack budgets (2026-10-22 @build + 同日真机 [STACK] 实测; data/stack_report_20261022.md) ──
# (name, cfg, prio, worst, peak_used, margin, margin_pct, verdict, desc)
# worst = max(静态最坏深度, 实测峰值); margin = cfg - worst
# 9 行含 testTask(仅测试构建创建、无实测 → 取静态最坏链,峰位列填同值)
TASK_STACKS = [
("defaultTask", 4096, "Normal", 2776, 2152, 1320, 32, "PASS", "FatFS 挂载/探针 + 配置引擎 + syslog + 栈探针"),
("netTask", 3840, "Normal", 1872, 920, 1968, 51, "PASS", "CH395F 轮询、消息处理"),
("ftpTask", 4096, "Normal", 1216, 740, 2880, 70, "PASS", "lftpd FTP 服务(大缓冲已静态化)"),
("testTask", 8192, "BelowNormal", 496, 496, 7696, 93, "PASS", "测试套件(产品构建不创建,取静态最坏链)"),
("uiTask", 16384, "BelowNormal", 4016, 4016, 12368, 75, "PASS", "LVGL UI 主循环"),
("timeSyncTask", 2048, "Normal", 1024, 616, 1024, 50, "PASS", "TCP 时间同步(余量正好卡在 ISR 底线)"),
("defaultTask", 5120, "Normal", 3024, 2164, 2096, 40, "PASS", "FatFS 挂载/探针 + 配置引擎 + syslog + LED"),
("netTask", 3840, "Normal", 1912, 1116, 1928, 50, "PASS", "CH395F 轮询、消息处理"),
("ftpTask", 4096, "Normal", 1456, 756, 2640, 64, "PASS", "lftpd FTP 服务(大缓冲已静态化)"),
("uiTask", 16384, "BelowNormal", 4052, 4052, 12332, 75, "PASS", "LVGL UI 主循环 + 远程显示推流"),
("testTask", 8192, "BelowNormal", 736, 736, 7456, 91, "PASS", "测试套件(产品构建不创建,取静态最坏链)"),
("timeSyncTask", 2560, "Normal", 1264, 616, 1296, 50, "PASS", "TCP 时间同步(8888)"),
("adcTask", 2560, "AboveNormal", 1320, 952, 1240, 48, "PASS", "1 kHz 节拍消费者(L1 采集,纯取数)"),
("iec104Task", 2560, "Normal", 1456, 544, 1104, 43, "PASS", "IEC 60870-5-104 上行(2404)"),
("protectTask", 2048, "Normal", 872, 828, 1176, 57, "PASS", "20 ms 保护判据 + 闭锁 + 定值校验和巡检"),
]
KERNEL_STACKS = [("idle", 512), ("timer", 1024)] # 128/256 words
KERNEL_STACKS = [("idle", 512), ("timer", 1024)] # 128/256 words(未实测)
HEAP_TOTAL = 49152 # configTOTAL_HEAP_SIZE(位于 RW_CCM)
HEAP_FREE = 8784 # 2026-09-21 实测(含 FTP 负载)
HEAP_MIN_EVER = 4848
TOTAL_STACKS = sum(t[1] for t in TASK_STACKS) # 38,656
HEAP_TOTAL = 57344 # configTOTAL_HEAP_SIZE 56 KB(位于 RW_CCM;段实占 57,376 B 含元数据)
HEAP_FREE = 15800 # 2026-10-22 真机 [STACK](启动 + 网络/FTP/104 就绪后)
HEAP_MIN_EVER = 8464
TOTAL_STACKS = sum(t[1] for t in TASK_STACKS) # 47,360(含 testTask;产品构建 8 任务 = 39,168)
# ---------------------------------------------------------------------------
# SVG helpers
@@ -171,7 +178,7 @@ def gen_ram_svg():
SCALE = (W - 80) / SRAM1_TOTAL # 128KB 占满宽;CCM (64KB) 占半宽
svg = svg_header(W, H)
svg += f'<text x="{W/2}" y="24" text-anchor="middle" class="title">SRAM (192KB) 使用布局 — 2026-09-21 @build</text>\n'
svg += f'<text x="{W/2}" y="24" text-anchor="middle" class="title">SRAM (192KB) 使用布局 — 2026-10-22 @build</text>\n'
# Region 1: 主 SRAM
y1 = 50
@@ -191,7 +198,7 @@ def gen_ram_svg():
# Region 2: CCM
y2 = y1 + BAR_H + 50
svg += f'<text x="{BAR_X}" y="{y2-8}" class="label" fill="#555">CCM RW_CCM @0x10000000(64KB,仅 CPU 可达,DMA 不可达)</text>\n'
svg += f'<text x="{BAR_X}" y="{y2-8}" class="label" fill="#555">CCM RW_CCM @0x10000000(段 65,280 B;仅 CPU 可达、DMA 不可达 → R2/R3)</text>\n'
x = BAR_X
for label, size, color in RAM2_CATEGORIES:
w = size * SCALE
@@ -203,7 +210,7 @@ def gen_ram_svg():
svg += f'<text x="{BAR_X}" y="{y2+BAR_H+18}" class="label" fill="#333">'
svg += f'已用: {CCM_USED}B ({CCM_USED/CCM_TOTAL*100:.1f}%)'
svg += f' 空闲: {CCM_FREE}B ({CCM_FREE/CCM_TOTAL*100:.1f}%)'
svg += f' 总量: 64KB</text>\n'
svg += f' 总量: 63.75KB(+256 B UNINIT_CCM 尾区)——CCM 是当前全局卡点</text>\n'
# Legend(主 SRAM 两列 + CCM 两行)
ly = y2 + BAR_H + 40
@@ -239,7 +246,7 @@ def gen_flash_svg():
SCALE = (W - 80) / FLASH_TOTAL
svg = svg_header(W, H)
svg += f'<text x="{W/2}" y="24" text-anchor="middle" class="title">Flash (1MB) 使用布局 — 2026-09-21 @build</text>\n'
svg += f'<text x="{W/2}" y="24" text-anchor="middle" class="title">Flash (1MB) 使用布局 — 2026-10-22 @build</text>\n'
x = BAR_X
for label, size, color in FLASH_CATEGORIES:
@@ -255,29 +262,39 @@ def gen_flash_svg():
svg += f'<text x="{BAR_X}" y="{BAR_Y+BAR_H+18}" class="label" fill="#333">'
svg += f'已用: {FLASH_USED/1024:.1f}KB ({FLASH_USED/FLASH_TOTAL*100:.1f}%)'
svg += f' 空闲: {FLASH_FREE/1024:.1f}KB ({FLASH_FREE/FLASH_TOTAL*100:.1f}%)'
svg += f' 总量: 1024KB</text>\n'
svg += f' ER_IROM1(Code+RO): {FLASH_IROM/1024:.1f}KB ({FLASH_IROM/FLASH_TOTAL*100:.1f}%)'
svg += f' 总量: 1MB (= ROM 合计口径)'
svg += f'</text>\n'
# Code sub-breakdown mini bar
Y2 = 130
svg += f'<text x="{BAR_X}" y="{Y2-6}" class="label" fill="#555">代码段明细 ({FLASH_CODE/1024:.1f}KB)</text>\n'
SCALE2 = (W - 80) / FLASH_CODE
svg += f'<text x="{BAR_X}" y="{Y2-6}" class="label" fill="#555">Code + RO 分布(ER_IROM1 {FLASH_IROM/1024:.1f}KB,按 .map 对象归类)</text>\n'
SCALE2 = (W - 80) / FLASH_IROM
x2 = BAR_X
for label, size, color in FLASH_CODE_SUB:
w = size * SCALE2
if w < 1:
continue
inside = f"{label} {size/1024:.1f}KB" if w > 50 else ""
outside = "" if w > 50 else f"{label} {size/1024:.1f}KB"
svg += _draw_bar_segment(x2, Y2, w, 24, color, inside, outside)
# 窄段(多数桶 < 40px)不放内嵌文字,统一列到下方图例,避免互相压字
inside = f"{size/1024:.1f}KB" if w > 40 else ""
svg += _draw_bar_segment(x2, Y2, w, 24, color, inside, "")
x2 += w
# 图例:四列三行(10 个桶)
for i, (label, size, color) in enumerate(FLASH_CODE_SUB):
col, row = i % 4, i // 4
lx = 60 + col * 195
ly = 176 + row * 16
svg += f' <rect x="{lx}" y="{ly-9}" width="10" height="10" fill="{color}" rx="2"/>\n'
svg += f' <text x="{lx+14}" y="{ly}" class="legend-text" fill="#333">{label} {size/1024:.1f}KB</text>\n'
svg += svg_footer()
return svg
def gen_taskstack_svg():
"""任务栈预算.svg — 每任务:分配栈条 + 最坏占用叠加条 + 实测余量/判定。"""
W, H = 880, 440
W, H = 880, 620 # 9 个任务 + 2 内核栈 + 堆汇总,行高 40 需要 ~600 高
BAR_X = 190
MAX_STACK = 16384
SCALE = (W - BAR_X - 40) / MAX_STACK
@@ -286,16 +303,19 @@ def gen_taskstack_svg():
BAR_H = 16
svg = svg_header(W, H)
svg += f'<text x="{W/2}" y="22" text-anchor="middle" class="title">FreeRTOS 任务栈预算(48KB 堆,2026-09-21 已迁 CCM)</text>\n'
svg += f'<text x="{W/2}" y="40" text-anchor="middle" class="legend-text" fill="#777">数据: 2026-10-10 @build + 开机/空闲实测(data/stack_report_20261010.md);最坏 = max(静态最坏链, 实测峰值)</text>\n'
svg += f'<text x="{W/2}" y="22" text-anchor="middle" class="title">FreeRTOS 任务栈预算(9 任务;栈全部从 56KB FreeRTOS 堆分配,堆位于 CCM)</text>\n'
svg += f'<text x="{W/2}" y="40" text-anchor="middle" class="legend-text" fill="#777">数据: 2026-10-22 @build + 同日真机 [STACK] 水位(data/stack_report_20261022.md);最坏 = max(静态最坏链, 实测峰值);判定线 = 余量 ≥ max(1024B ISR, 配置×30%)</text>\n'
svg += f'<text x="{BAR_X}" y="{Y_START-10}" class="legend-text" fill="#555">浅色条 = 分配栈 深色条 = 最坏占用(max(静态最坏, 实测峰值)) 下方文字 = 实测余量/判定</text>\n'
y = Y_START
for name, cfg, prio, worst, peak, margin, pct, verdict, desc in TASK_STACKS:
w = cfg * SCALE
wc = worst * SCALE
dark = "#2196F3" if prio == "Normal" else "#FF9800"
light = "#BBDEFB" if prio == "Normal" else "#FFE0B2"
# 三档优先级配色:Normal 蓝 / BelowNormal 琥珀 / AboveNormal 绿(adcTask)
dark = {"Normal": "#2196F3", "BelowNormal": "#FF9800",
"AboveNormal": "#4CAF50"}[prio]
light = {"Normal": "#BBDEFB", "BelowNormal": "#FFE0B2",
"AboveNormal": "#C8E6C9"}[prio]
vc = "#2E7D32" if verdict == "PASS" else "#C62828"
# 任务名(左侧)
@@ -321,18 +341,25 @@ def gen_taskstack_svg():
# 堆汇总(实测值)
y += 8
svg += f'<text x="{BAR_X}" y="{y}" class="label" fill="#555">'
svg += f'堆总量: {HEAP_TOTAL}B (48KB, RW_CCM) 任务栈合计: {TOTAL_STACKS}B ({TOTAL_STACKS/1024:.1f}KB) + 内核栈 {512+1024}B</text>\n'
svg += f'堆总量: {HEAP_TOTAL}B(configTOTAL_HEAP_SIZE=56KB,位于 RW_CCM)'
svg += f' 任务栈合计: {TOTAL_STACKS}B(含 testTask 8192B)</text>\n'
y += 16
svg += f'<text x="{BAR_X}" y="{y}" class="label" fill="#555">'
svg += f'实测: free={HEAP_FREE}B / min_ever={HEAP_MIN_EVER}B(2026-09-21,含 FTP 负载) 判定: 6/6 PASS(余量 ≥ max(1024B, 配置×30%))</text>\n'
svg += f'产品构建 8 任务栈 = 39168B + 内核栈 {512+1024}B'
svg += f' 实测: free={HEAP_FREE}B / min_ever={HEAP_MIN_EVER}B(2026-10-22)</text>\n'
y += 16
svg += f'<text x="{BAR_X}" y="{y}" class="label" fill="#555">'
svg += '判定: 9/9 PASS(testTask 无实测,取静态最坏链)</text>\n'
y += 24
# 颜色图例
svg += f' <rect x="{BAR_X}" y="{y}" width="10" height="10" fill="#2196F3" rx="2"/>\n'
svg += f' <text x="{BAR_X+14}" y="{y+8}" class="legend-text" fill="#333">Normal 优先级</text>\n'
svg += f' <rect x="{BAR_X+130}" y="{y}" width="10" height="10" fill="#FF9800" rx="2"/>\n'
svg += f' <text x="{BAR_X+144}" y="{y+8}" class="legend-text" fill="#333">BelowNormal 优先级</text>\n'
svg += f' <rect x="{BAR_X+290}" y="{y}" width="10" height="10" fill="#90A4AE" rx="2"/>\n'
svg += f' <text x="{BAR_X+304}" y="{y+8}" class="legend-text" fill="#333">内核任务栈 (idle/timer)</text>\n'
svg += f' <rect x="{BAR_X+300}" y="{y}" width="10" height="10" fill="#4CAF50" rx="2"/>\n'
svg += f' <text x="{BAR_X+314}" y="{y+8}" class="legend-text" fill="#333">AboveNormal 优先级(adcTask)</text>\n'
svg += f' <rect x="{BAR_X+520}" y="{y}" width="10" height="10" fill="#90A4AE" rx="2"/>\n'
svg += f' <text x="{BAR_X+534}" y="{y+8}" class="legend-text" fill="#333">内核栈 idle/timer</text>\n'
svg += svg_footer()
return svg