Files
lucky-cat/docs/knowledge vault/flash-and-memory-capacity-analysis.md
T

963 lines
29 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Flash 与内存容量分析、风险及后续开发建议
> 历史容量快照:文中远程日志、`logfs` 和独立 `gifpack` 分区已从当前固件移除。下文的“当前”“建议”和旧构建步骤仅针对当时版本,不能作为现行操作指南。现行分区以 `16m.csv` 与最新构建报告为准,资源操作见 [应用内嵌素材使用与添加指南](../应用内嵌素材使用与添加指南.md)。
本文记录当前固件的 Flash 分区使用情况、静态 RAM 使用情况、运行时内存风险、可优化内容和后续开发约束,用于功能评审、资源接入、OTA 设计和长期容量管理。
历史 LittleFS 资源流程归档见:
- [`docs/littlefs-resource-build-flash-mount.md`](littlefs-resource-build-flash-mount.md)
## 1. 分析基线与数据边界
本次分析基于:
```text
分支:feature/st7789p3-new-board
提交:d5409ea
目标:ESP32-S3
ESP-IDF:v5.5.4
Flash 配置:16 MiB
分区表:16m.csv
应用镜像:build/TQ_Cat_Code.bin
链接映射:build/TQ_Cat_Code.map
资源镜像:build/gifpack.bin
```
当前 `build/` 目录中的应用产物生成于 2026-08-03,资源镜像生成于 2026-08-04。后续代码或资源变化后,应重新构建并更新本文数据。
需要明确区分两类数据:
### 1.1 本地构建可以准确得到的数据
- Flash 分区的地址和容量;
- 应用程序二进制大小;
- `gifpack` 镜像大小;
- LittleFS 镜像内的文件数量、文件内容大小和已用块数;
- 静态 IRAM、D/IRAM、`.data`、`.bss` 使用量;
- 链接阶段放入 PSRAM 的静态 BSS 大小。
### 1.2 必须连接设备运行后才能得到的数据
- 芯片实际检测到的 PSRAM 总容量;
- 启动完成后的内部堆剩余量;
- PSRAM 剩余量;
- 最小历史空闲堆;
- 最大连续内部内存块;
- 最大连续 DMA 内存块;
- 每个任务的真实栈高水位;
- Wi-Fi、TLS、WebSocket、音频同时工作时的峰值占用;
- `logfs`、`model`、`coredump` 在具体设备上的真实内容和占用。
因此,本文的静态分析可以判断容量趋势和结构性风险,但不能替代真机峰值内存测试。
## 2. Flash 总体布局
设备使用 16 MiB Flash:
```text
16 MiB = 16,777,216 bytes = 0x1000000
```
`16m.csv` 已经把全部 16 MiB 空间分配完毕,没有未划分空间。
| 区域 | 地址范围 | 分配大小 | 占 Flash 比例 | 用途 |
| --- | ---: | ---: | ---: | --- |
| Bootloader、分区表、NVS、OTA data、PHY | `0x000000-0x010000` | 64 KiB | 0.39% | 系统启动和持久化基础数据 |
| `gifpack` | `0x010000-0xA90000` | 10.5 MiB | 65.63% | LCD 图片和提示音 LittleFS |
| `logfs` | `0xA90000-0xAD0000` | 256 KiB | 1.56% | 远程日志落盘队列 |
| `ota_0` | `0xAD0000-0xCA0000` | 1.8125 MiB | 11.33% | 应用 OTA 槽位 0 |
| `ota_1` | `0xCA0000-0xE70000` | 1.8125 MiB | 11.33% | 应用 OTA 槽位 1 |
| `model` | `0xE70000-0xFF0000` | 1.5 MiB | 9.38% | 模型数据分区 |
| `coredump` | `0xFF0000-0x1000000` | 64 KiB | 0.39% | 崩溃转储 |
Flash 布局可以简化为:
```text
0x000000
├── 系统区域 64 KiB
├── gifpack 10.5 MiB
├── logfs 256 KiB
├── ota_0 1.8125 MiB
├── ota_1 1.8125 MiB
├── model 1.5 MiB
└── coredump 64 KiB
0x1000000
```
## 3. 应用程序 Flash 使用情况
单个 OTA 应用分区大小:
```text
0x1d0000 = 1,900,544 bytes = 1.8125 MiB
```
当前应用二进制:
```text
build/TQ_Cat_Code.bin
实际文件大小:1,657,280 bytes
```
链接工具报告:
```text
Flash .text: 1,134,436 bytes
Flash .rodata: 388,744 bytes
其他: 256 bytes
Flash 非 RAM 合计:1,523,436 bytes
总镜像大小: 1,657,135 bytes
最终 .bin: 1,657,280 bytes
```
单个 OTA 槽位占用:
```text
使用率:87.20%
剩余:243,264 bytes
≈ 237.56 KiB
```
两个 OTA 槽位必须具有相同容量。`ota_1` 即使当前没有运行,也不能简单视为空闲空间,因为 OTA 升级需要把新固件完整写入另一个槽位,并保留切换和回滚能力。
### 3.1 应用分区风险判断
风险等级:**高**。
原因:
- 当前只剩约 238 KiB;
- 新增网络库、编解码库、证书、字体或大型只读表可能快速消耗空间;
- Debug 配置、日志字符串和完整证书包都会增加镜像;
- OTA 要求每个新版本都必须装入同样大小的备用槽;
- 链接成功不代表长期有充足增长空间。
建议设置容量门槛:
| 使用率 | 建议处理 |
| ---: | --- |
| `< 80%` | 正常 |
| `80%-85%` | 开始跟踪增长趋势 |
| `85%-90%` | 构建警告;每个新增依赖必须解释体积影响 |
| `>= 90%` | 阻止普通功能合入,先做容量治理 |
当前 87.20%,已经处于“必须持续跟踪”的区间。
## 4. `gifpack` 资源分区使用情况
`gifpack` 分区容量:
```text
0xa80000 = 11,010,048 bytes = 10.5 MiB
```
当前临时资源目录中:
| 资源类型 | 文件数 | 文件内容大小 |
| --- | ---: | ---: |
| LCD RLE | 41 | 9,379,380 bytes |
| Opus 提示音 | 30 | 378,918 bytes |
| 合计 | 71 | 9,758,298 bytes |
读取 `gifpack.bin` 的 LittleFS 块信息后:
```text
块大小:4,096 bytes
总块数:2,688
已用块:2,426
空闲块:262
LittleFS 已用:9,936,896 bytes ≈ 9.48 MiB
LittleFS 空闲:1,073,152 bytes ≈ 1.02 MiB
使用率:90.25%
```
资源文件内容为约 9.31 MiB,LittleFS 已用空间约 9.48 MiB,两者差值来自目录、索引、元数据、块对齐和文件系统管理开销。
`build/gifpack.bin` 的文件大小始终等于整个分区大小,即 10.5 MiB。不能通过 `ls -lh build/gifpack.bin` 判断资源真实占用,因为镜像会填充到分区全尺寸。
### 4.1 资源分区风险判断
风险等级:**高**。
原因:
- 当前使用率已经超过 90%;
- 理论空闲约 1.02 MiB,实际新增文件还需要 LittleFS 管理开销;
- 动画通常一次增加多帧,每帧可能为 200-380 KiB;
- 新增 3-5 张全屏 RLE 图片就可能耗尽剩余空间;
- 资源分区没有 A/B 设计,未来直接在线更新存在断电损坏风险;
- 普通应用 OTA 不会更新 `gifpack`,容易出现应用代码与资源版本不匹配。
建议容量门槛:
| 使用率 | 建议处理 |
| ---: | --- |
| `< 80%` | 正常 |
| `80%-90%` | 新增资源必须记录大小 |
| `90%-95%` | 高风险;新增动画前必须优化或清理 |
| `>= 95%` | 阻止资源合入 |
当前 90.25%,已经进入高风险区间。
## 5. 当前完整烧录包含什么
当前 `build/flasher_args.json` 登记:
```text
0x000000 bootloader/bootloader.bin
0x008000 partition_table/partition-table.bin
0x00D000 ota_data_initial.bin
0x010000 gifpack.bin
0xAD0000 TQ_Cat_Code.bin
```
常规完整烧录会写入:
- Bootloader;
- 分区表;
- OTA 初始状态;
- `gifpack` 资源镜像;
- `ota_0` 应用程序。
以下分区没有预生成镜像加入当前完整烧录清单:
- `logfs`:设备运行时挂载、格式化和写入;
- `ota_1`:应用 OTA 时写入;
- `model`:需要独立的模型写入流程;
- `coredump`:设备崩溃时写入。
因此,本地构建无法判断具体设备上这几个动态分区的真实使用量。
## 6. 静态 RAM 使用情况
链接映射报告:
| RAM 区域 | 已使用 | 总容量 | 剩余 | 使用率 |
| --- | ---: | ---: | ---: | ---: |
| 独立 IRAM | 16,384 B | 16,384 B | 0 B | 100% |
| D/IRAM | 173,387 B | 341,760 B | 168,373 B | 50.73% |
| PSRAM 静态 BSS | 14,056 B | 取决于硬件 | 未知 | 未知 |
D/IRAM 组成:
```text
.data: 23,944 bytes
.bss: 56,072 bytes
.text: 93,371 bytes
合计: 173,387 bytes
剩余: 168,373 bytes ≈ 164.43 KiB
```
其中:
- `.data`:有初始值的全局和静态变量;
- `.bss`:零初始化的全局和静态变量;
- `.text`:必须驻留 RAM 执行的函数;
- 剩余 D/IRAM 会参与形成运行时可用内部内存,但启动后还会被任务栈、DMA、驱动和堆分配继续使用。
### 6.1 IRAM 风险
独立 IRAM 当前为:
```text
.text: 15,356 bytes
.vectors: 1,028 bytes
合计: 16,384 bytes
剩余: 0 bytes
```
风险等级:**高**。
这不表示设备运行时马上耗尽普通堆,但意味着独立 IRAM 链接区域没有静态增长余量。后续新增以下内容必须谨慎:
- `IRAM_ATTR` 函数;
- ISR 中必须执行的代码;
- Flash cache 关闭期间仍需运行的函数;
- 驱动要求放入 IRAM 的回调;
- 性能优化时强制进入 IRAM 的热点代码。
不要为了普通性能优化随意添加 `IRAM_ATTR`。添加后必须重新运行 size 分析并检查链接结果。
### 6.2 D/IRAM 风险
静态使用率约 50.7%,从链接角度尚可,但运行时内部 RAM 压力预计较高,因为以下内容不能完全依赖 PSRAM:
- Wi-Fi 和部分 lwIP 缓冲;
- SPI/I2S DMA 描述符和 DMA 缓冲;
- TLS 硬件 AES bounce buffer;
- 中断相关数据;
- 部分 FreeRTOS 和驱动对象;
- 显式创建在内部 RAM 的任务栈;
- 小于内部优先阈值的普通分配。
风险等级:**中高**,需要真机峰值数据确认。
## 7. PSRAM 配置与使用策略
当前配置启用:
```text
Octal PSRAM
80 MHz
启动初始化
加入 malloc 堆
允许 BSS 放入外部内存
允许任务栈放入外部内存
Wi-Fi/lwIP 尝试使用 PSRAM
```
关键参数:
```text
CONFIG_SPIRAM_USE_MALLOC=y
CONFIG_SPIRAM_MALLOC_ALWAYSINTERNAL=4096
CONFIG_SPIRAM_MALLOC_RESERVE_INTERNAL=32768
```
设计意图:
- 大型普通内存申请可以进入 PSRAM;
- 较小申请倾向内部 RAM;
- 为 DMA 和硬件驱动保留 32 KiB 内部 RAM;
- 音频、WebSocket、HTTP 等大栈和队列优先放入 PSRAM。
链接阶段已有约 13.7 KiB BSS 放入 PSRAM。
### 7.1 当前未知项
配置使用自动 PSRAM 类型检测,没有在项目中写死物理容量。必须查看真机启动日志或调用 `esp_psram_get_size()` 才能确认实际是 2、4 还是 8 MiB。
风险等级:**中**。
后续量产前必须把实际模组型号、PSRAM 容量和启动检测结果纳入硬件验收,避免开发板和量产板容量不同。
## 8. 运行时主要内存消费者
### 8.1 项目任务栈
当前较大的显式任务栈:
| 任务 | 配置栈 | 默认内存策略 |
| --- | ---: | --- |
| 音频采集 | 32 KiB | PSRAM 优先,失败回退内部 RAM |
| 音频发送 | 12 KiB | PSRAM 优先,失败回退内部 RAM |
| 音频播放 | 12 KiB | PSRAM 优先,失败回退内部 RAM |
| WebSocket ordered worker | 12 KiB | PSRAM 优先 |
| WebSocket client | 8-32 KiB 候选 | 第三方组件内部创建,主要消耗内部 RAM |
| LCD 渲染 | 12 KiB | 内部 RAM |
| Wi-Fi 状态 | 12 KiB | 内部 RAM |
| LAN HTTP | 8 KiB | PSRAM 优先 |
| 远程日志 | 8 KiB | 内部 RAM |
| 每日运势 | 6 KiB | 内部 RAM |
| 多模态 watchdog | 6 KiB | 内部 RAM |
| 系统事件 | 4 KiB | 内部 RAM |
| 按键 | 4 KiB | 内部 RAM |
| 模型命令 | 4 KiB | PSRAM 优先 |
仅项目显式任务的配置栈总量已经超过 130 KiB。除此之外,ESP-IDF 还会创建:
- main task;
- 两个 IDLE task;
- timer task;
- system event task;
- Wi-Fi task;
- TCP/IP task;
- HTTP/WebSocket 内部任务;
- 驱动相关任务。
不能把所有配置栈简单相加后与静态剩余 RAM 比较,因为:
- 一部分任务不同时存在;
- 一部分栈优先放入 PSRAM;
- 任务控制块和队列还有额外开销;
- 第三方组件可能自行创建内部任务。
但这些数据说明,任务栈是运行时内存治理的重点。
### 8.2 音频队列
音频上行发送队列:
```text
长度:24
单项数据容量:约 1 KiB
队列数据:约 24 KiB
分配策略:PSRAM 优先
```
音频下行播放队列:
```text
长度:1,024
队列项保存长度和数据指针
队列描述符约 16 KiB
音频正文另外动态分配
分配策略:PSRAM 优先
```
下行正文占用取决于服务器下发速度、网络抖动和播放消费速度。极端积压时,真实内存占用会明显高于队列描述符本身。
### 8.3 WebSocket 队列
```text
消息队列上限:512
单个描述符约 20 bytes
描述符队列约 10 KiB
消息正文动态分配,单条最大值可达数 KiB
```
描述符队列优先放入 PSRAM,但正文积压量仍可能成为峰值风险。
### 8.4 远程日志静态队列
远程日志启用:
```text
CONFIG_TQ_REMOTE_LOG_ENABLE=y
队列长度:128
单条文本容量:256 bytes
```
`remote_log_entry_t` 每项还包含时间戳、日志等级和状态字段,整个静态队列约占 33 KiB 内部 BSS。这是当前 `control_plane` 静态 BSS 约 37.8 KiB 的主要来源。
风险等级:**中高**。
这块内存在启用远程日志后始终常驻,即使没有积压日志也不会释放。
### 8.5 LCD 缓冲
LCD 采用分行读取,不加载整张 320×240 图片:
```text
行缓冲:320 × 8 × 2 bytes = 5,120 bytes
RLE chunk:128 × 4 bytes = 512 bytes
```
这部分设计较合理。若整帧 RGB565 全部加载,将需要:
```text
320 × 240 × 2 = 153,600 bytes
```
因此,后续不要轻易把当前流式绘制改成整帧内部 RAM 缓冲。确实需要双缓冲时,应评估 PSRAM、DMA 约束和帧率收益。
## 9. 运行时内存风险
### 9.1 内部连续内存不足
风险等级:**高**。
系统可能出现以下情况:
```text
总 free_heap 看起来很多
但 largest_internal 或 largest_dma 很小
导致 TLS、AES、SPI、I2S 或 Wi-Fi 分配失败
```
重点场景:
- Wi-Fi 刚连接时进行 HTTPS Token 请求;
- TLS 握手和 WebSocket 建连重叠;
- 音频采集、编码、播放同时工作;
- LCD 正在 SPI DMA 传输;
- 远程日志同时上传 HTTPS;
- LAN HTTP 服务同时处理请求。
判断内存是否健康,不能只看 `esp_get_free_heap_size()`,还必须观察:
```text
free_internal
largest_internal
largest_dma
free_spiram
largest_spiram
minimum_free_heap
```
### 9.2 内存碎片
风险等级:**中高**。
TLS、JSON、WebSocket 消息、音频包和 HTTP body 都会产生不同大小、不同生命周期的动态分配。长时间连接、重连、失败重试后,可能出现总空闲量尚可但最大连续块下降的情况。
判断方法:
- 多轮重连后比较 `free_internal`;
- 比较 `largest_internal` 是否持续下降;
- 比较 `largest_dma` 是否持续下降;
- 比较连接前、握手中、交互中、断开后的数值能否恢复;
- 重复语音交互 100 次以上观察最低值和趋势。
### 9.3 PSRAM 回退导致内部 RAM 突增
很多任务和队列采用“PSRAM 优先,失败回退内部 RAM”。如果 PSRAM 初始化失败、容量不足或碎片严重,多个对象可能同时回退到内部 RAM,使内部堆瞬间恶化。
必须关注日志:
```text
PSRAM allocation failed
fallback to internal
Task create with PSRAM stack failed
```
量产版本不应把这些日志当成无害告警。
### 9.4 任务栈过大或过小
风险等级:**中**。
- 栈过大:浪费内部 RAM 或 PSRAM;
- 栈过小:发生栈溢出、随机崩溃;
- 只在正常路径测量不足以覆盖 TLS 错误、JSON 大包、日志上传等深调用路径。
当前 `CONFIG_TQ_TASK_MONITOR_ENABLE` 关闭,缺少持续任务栈可观测性。建议在压力测试固件中启用,不一定默认用于量产版本。
## 10. 跨 Flash 与 RAM 的组合风险
### 10.1 资源与应用版本不一致
普通应用 OTA 只更新 `ota_0/ota_1`,不更新 `gifpack`。如果新代码引用新资源路径,而设备仍保留旧 `gifpack`,会出现:
```text
open LCD asset failed
Open prompt failed
```
建议:
- 应用中声明资源包版本;
- `gifpack` 中保存资源 manifest;
- 启动时比较应用要求版本和资源版本;
- 不匹配时进入明确降级路径,不要反复尝试打开不存在的文件。
### 10.2 单份资源分区在线更新风险
当前只有一个 `gifpack` 分区。若未来在线覆盖更新:
- 更新中断电可能损坏整个资源文件系统;
- 新镜像校验失败时没有旧资源可回退;
- 应用和资源切换无法原子完成。
在没有完整方案前,不建议直接实现“边运行边覆盖单个 gifpack”。可选方向:
1. 资源仅随工厂完整烧录更新;
2. 下载到备用区域,校验后再切换;
3. 设计 `gifpack_0/gifpack_1` 双资源分区;
4. 使用版本化资源容器和原子状态记录;
5. 将少量新增资源并入应用,但需评估 OTA 槽容量。
### 10.3 LittleFS 挂载依赖开机音流程
当前 `/littlefs` 主要由播放开机音前的 `voice_prompt_play_file()` 触发挂载。LCD 随后假定 `/littlefs` 已经可用。
如果开机音被关闭或音频初始化提前失败,LCD 可能在文件系统未挂载时读取资源。
建议把 `platform_mount_littlefs()` 提升为明确的启动阶段,由 `app_composition` 在音频和 LCD 之前编排,并保持调用幂等。
风险等级:**中高**。
## 11. 优化建议与优先级
### P0:立即建立容量门禁
在 CI 或构建检查中记录并比较:
```text
应用 .bin 大小
应用分区使用率
gifpack LittleFS 已用块数
gifpack 使用率
IRAM 使用量
D/IRAM 静态使用量
```
建议规则:
- 应用分区达到 85% 发出警告;
- 应用分区达到 90% 阻止合入;
- `gifpack` 达到 90% 发出高风险警告;
- `gifpack` 达到 95% 阻止合入;
- IRAM 增长必须在 PR 中说明;
- 新增库必须附带 size diff;
- 新增动画必须附带新增资源字节数和预计剩余空间。
### P0:采集真机峰值内存
至少在以下时间点采集:
1. Bootloader 后、应用初始化前;
2. NVS 和 LittleFS 挂载后;
3. Wi-Fi 初始化后;
4. Wi-Fi 已连接;
5. TLS Token 请求前和握手中;
6. WebSocket 已连接;
7. 音频采集和播放同时进行;
8. LCD 动画播放中;
9. 远程日志上传中;
10. 所有业务进入稳态后;
11. 多次断线重连后;
12. 长时间运行 24 小时后。
每次至少记录:
```text
esp_get_free_heap_size()
esp_get_minimum_free_heap_size()
heap_caps_get_free_size(MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT)
heap_caps_get_largest_free_block(MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT)
heap_caps_get_largest_free_block(MALLOC_CAP_DMA)
heap_caps_get_free_size(MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT)
heap_caps_get_largest_free_block(MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT)
```
不要一开始就设定绝对通过阈值。先收集多台设备、多种网络条件的基线,再根据 TLS、DMA 和音频最大单次申请量设定安全阈值。
### P1:优化远程日志静态队列
当前约 33 KiB 内部 BSS 常驻,可考虑:
1. 将队列动态分配到 PSRAM;
2. PSRAM 失败时回退到更短内部队列,而不是同等大小回退;
3. 将 `CONFIG_TQ_REMOTE_LOG_QUEUE_LEN` 从 128 调低;
4. 将单条文本上限从 256 调低;
5. 使用变长块池或分层队列;
6. 评估是否需要在所有构建类型中默认启用远程日志。
修改时要保持日志钩子并发安全,不能为了省内存破坏日志回调的锁和生命周期。
### P1:基于高水位调整任务栈
压力测试时开启任务监控,记录所有任务最小剩余栈。
调优原则:
- 不以一次正常启动数据为依据;
- 覆盖错误路径、重连、超时和大 JSON;
- 保留安全余量;
- 优先优化长期存活且栈明显过大的任务;
- 对 ISR、DMA、Flash cache 关闭相关任务,不要盲目迁移到 PSRAM;
- 修改后执行长时间和异常路径测试。
### P1:治理资源体积
当前 LCD 资源占 `gifpack` 的绝大部分。建议:
- 清理白名单中已不再播放的资源;
- 检查是否存在内容重复的帧;
- 对相邻帧考虑差分、局部刷新或共享背景;
- 检查 RLE 色彩和扫描方向是否影响压缩率;
- 避免把相同首帧复制到多个动画组;
- 新增资源时记录原图大小、RLE 大小、run 数和压缩率;
- 建立资源 manifest,自动检查 CMake 白名单和代码映射一致性。
当前 `tools/png_to_rle.py` 生成标准 TGA RLE,而 `lcd_direct` 读取原始 `uint16_t color + uint16_t count` 数据,两者不兼容。应统一并验证资源转换工具,避免开发者误用。
### P1:确认 `model` 分区是否仍需要 1.5 MiB
当前 `model` 分区没有进入常规烧录清单。需要确认:
- 是否由工厂工具单独烧录;
- 当前功能是否读取该分区;
- 量产设备中是否有有效模型数据;
- 1.5 MiB 是否为未来预留;
- 能否缩减并重新分配给应用或资源。
如果确认未使用,可以评估重新分区。但调整前必须同时考虑:
- 两个 OTA 分区需要同步增大;
- 已出货设备变更分区表的迁移风险;
- OTA 是否能够安全改变分区布局;
- 模型恢复和工厂烧录流程;
- Flash 擦除和回滚策略。
不要只修改 CSV 就认为升级安全。
### P2:优化队列和积压策略
音频和 WebSocket 队列容量较大。建议根据真实峰值观测:
- 最大队列深度;
- 平均排队时间;
- 丢包数量;
- 消费速率;
- 网络抖动时的峰值;
- 语音播放延迟。
如果队列长期只使用很小比例,可以缩短;如果积压很深,应优先解决背压和消费速率,不要单纯继续增大队列。
### P2:增加资源版本和完整性校验
在 `gifpack` 中增加小型 manifest,例如:
```json
{
"resource_version": "1.3.0",
"format_version": 1,
"target": "esp32s3-st7789p3",
"files": {
"lcd/jichubiaoqing0.rle": {
"size": 21644,
"run_count": 5411,
"sha256": "..."
}
}
}
```
启动时不必校验所有大文件,但至少应检查:
- manifest 可读取;
- 资源版本兼容;
- 关键启动资源存在;
- 文件格式版本正确;
- 必要时抽样或按需校验哈希。
## 12. 后续开发准入要求
### 12.1 新增普通代码或第三方库
PR 中应记录:
- 应用 `.bin` 增量;
- Flash `.text` 和 `.rodata` 增量;
- IRAM 增量;
- D/IRAM `.data/.bss/.text` 增量;
- 是否新增长期任务;
- 任务栈配置和内存区域;
- 是否新增大队列或大缓冲;
- 是否在 TLS、音频、LCD 并发路径中运行。
### 12.2 新增 LCD 或音频资源
PR 中应记录:
- 新增文件数;
- 新增资源总字节数;
- 新 `gifpack` 使用率;
- 预计剩余空间;
- LCD `run_count` 是否更新;
- CMake 白名单是否更新;
- 平台资源映射是否更新;
- domain 播放顺序是否更新;
- 完整 `idf.py flash` 硬件验证结果。
### 12.3 新增长期任务
必须定义:
- 明确 owner 层;
- 创建、启动、停止和销毁流程;
- 栈大小依据;
- 内部 RAM 或 PSRAM 选择依据;
- PSRAM 失败回退行为;
- 有界停止时间;
- 栈高水位观测方式;
- 队列和动态内存上限。
### 12.4 新增大型动态缓冲
必须回答:
- 最大申请尺寸是多少;
- 是否要求 DMA 能力;
- 是否必须在内部 RAM;
- 生命周期多长;
- 是否会与 TLS、音频、LCD 同时存在;
- PSRAM 失败时如何降级;
- 是否存在无上限增长;
- 分配失败是否能安全恢复。
### 12.5 修改分区表
必须进行完整评审:
- 总大小是否仍为 16 MiB;
- 两个 OTA 槽是否等大;
- 当前和历史固件是否兼容;
- 已部署设备如何迁移;
- 工厂烧录脚本是否更新;
- OTA、资源、模型和 coredump 流程是否更新;
- 地址变化是否影响任何硬编码工具;
- 断电和回滚是否安全。
## 13. 推荐测试矩阵
| 测试场景 | 重点观察 |
| --- | --- |
| 冷启动 | PSRAM 检测、LittleFS 挂载、初始内部堆 |
| 无网络启动 | 重试期间是否泄漏、LCD 是否仍能读资源 |
| Wi-Fi 弱网 | 队列积压、重连内存恢复 |
| HTTPS Token | `largest_dma`、`largest_internal` |
| WebSocket 重连 100 次 | 内存碎片、任务和队列是否释放 |
| 连续语音交互 | 音频队列峰值、PSRAM 趋势 |
| 上下行音频同时工作 | 内部 DMA 和 PSRAM 峰值 |
| LCD 动画和语音同时工作 | SPI/I2S DMA 竞争 |
| 远程日志上传 | TLS 与日志 body 峰值 |
| LAN HTTP 并发访问 | HTTP task 和响应缓冲 |
| 运行 24/72 小时 | 最小堆、碎片、任务泄漏 |
| 人为断电资源更新 | 资源完整性和恢复策略 |
每轮测试都应保存:
- 固件提交;
- `sdkconfig`;
- 应用和资源镜像大小;
- 完整串口日志;
- 各阶段堆快照;
- 任务栈高水位;
- 崩溃转储;
- 是否出现 PSRAM 回退。
## 14. 建议的持续观测指标
当前代码已经在认证、WebSocket、音频和远程日志路径中记录部分堆信息。建议统一成一个结构化内存快照接口,由 platform 层负责采集,control_plane 决定在何时上报。
推荐字段:
```text
timestamp
phase
free_heap
minimum_free_heap
free_internal
largest_internal
largest_dma
free_spiram
largest_spiram
task_count
active_audio_state
websocket_state
lcd_state
```
避免多个模块重复实现不同格式的 heap 日志。长期应形成单一内存观测入口,并支持:
- 启动阶段快照;
- 关键操作前后快照;
- 低内存阈值告警;
- 崩溃前最近快照保留;
- Debug 构建中的任务栈报表。
## 15. 当前风险汇总
| 风险 | 等级 | 当前证据 | 建议 |
| --- | --- | --- | --- |
| `gifpack` 空间不足 | 高 | 已用 90.25%,仅余约 1.02 MiB | 资源门禁、清理和压缩 |
| OTA 应用槽空间不足 | 高 | 已用 87.20%,仅余约 238 KiB | 代码 size 门禁、评估重新分区 |
| 独立 IRAM 无余量 | 高 | 16 KiB 已用 100% | 限制新增 `IRAM_ATTR` |
| 内部连续/DMA 内存不足 | 高 | Wi-Fi、TLS、音频、LCD 并发竞争 | 真机峰值监控和压力测试 |
| 应用和资源版本不一致 | 高 | 普通 OTA 不更新 `gifpack` | 资源 manifest 和兼容检查 |
| 单资源分区更新断电风险 | 高 | `gifpack` 无 A/B | 不直接在线覆盖;设计安全更新机制 |
| 远程日志常驻内部 BSS 较大 | 中高 | 静态队列约 33 KiB | PSRAM 化或缩小队列 |
| LittleFS 挂载依赖开机音 | 中高 | 挂载由提示音路径触发 | 独立启动阶段显式挂载 |
| 动态内存真实峰值未知 | 中高 | 尚无本次真机数据 | 建立基线和长稳测试 |
| PSRAM 实际容量未固化 | 中 | 自动检测,项目未声明容量 | 量产硬件验收和启动校验 |
| 任务栈缺少持续观测 | 中 | task monitor 默认关闭 | 压测构建启用并调优 |
| RLE 转换工具格式不一致 | 中 | TGA RLE 与 raw RLE 不兼容 | 统一转换和自动校验工具 |
## 16. 推荐执行顺序
### 第一阶段:立即完成
1. 将应用和 `gifpack` 大小检查加入构建或 CI;
2. 在真机完成一次全流程内存快照;
3. 确认实际 PSRAM 容量;
4. 统计所有任务栈高水位;
5. 清点 `gifpack` 中是否存在未使用资源;
6. 确认 `model` 分区真实用途;
7. 将 LittleFS 挂载改为明确启动阶段。
### 第二阶段:容量治理
1. 优化或迁移远程日志静态队列;
2. 根据高水位缩减过大的任务栈;
3. 根据实测调整音频/WebSocket 队列;
4. 统一 RLE 转换工具和格式检查;
5. 增加资源 manifest;
6. 建立应用和资源版本兼容规则。
### 第三阶段:量产和长期升级
1. 决定资源是否需要 OTA;
2. 如需要,设计资源 A/B 或安全下载切换机制;
3. 评审并冻结量产分区表;
4. 建立 24/72 小时长稳测试;
5. 建立多轮 Wi-Fi/WebSocket 重连压力测试;
6. 保存每个发布版本的 Flash/RAM 基线报告。
## 17. 复现本分析的方法
### 17.1 应用镜像
完整构建后检查:
```text
build/TQ_Cat_Code.bin
build/TQ_Cat_Code.map
```
使用当前 ESP-IDF v5.5.4 环境运行 size 工具:
```bash
idf.py size
idf.py size-components
```
### 17.2 Flash 分区
检查:
```text
16m.csv
build/partition_table/partition-table.bin
build/flasher_args.json
```
不要假定空 Offset 分区的地址永久不变,应以当前构建生成结果为准。
### 17.3 LittleFS 资源
检查临时目录文件总量:
```text
build/esp-idf/platform/asset_image/
```
使用构建环境中的 `littlefs-python` 查看:
```bash
build/littlefs_py_venv/bin/littlefs-python list \
build/gifpack.bin \
--block-size 4096 \
--name-max 64
```
真实空闲块应通过 LittleFS API或设备挂载后的 `esp_littlefs_info()` 获取,不能只依据资源文件内容之和。
### 17.4 真机运行时内存
关注串口日志中的:
```text
Heap before_ws_client_start
free_internal
largest_internal
largest_dma
free_spiram
largest_spiram
stack headroom
PSRAM allocation failed
fallback to internal
```
## 18. 当前结论
目前 16 MiB Flash 的规划已经非常紧凑:`gifpack` 占整个 Flash 的 65.63%,自身使用率达到 90.25%;两个 OTA 槽位合计占 22.66%,当前应用已占单槽 87.20%。Flash 没有未划分空间,任何扩容都必须以压缩或缩减其他分区为代价。
静态 RAM 方面,D/IRAM 使用率约 50.7%,表面仍有空间,但独立 IRAM 已经 100% 使用。真正的运行风险集中在内部连续内存和 DMA 内存:Wi-Fi、TLS、音频、LCD 和远程日志同时工作时,即使 PSRAM 仍有余量,也可能因为内部连续块不足而失败。
后续开发应把容量视为正式约束,而不是等链接或运行失败后再处理。最优先的工作是建立容量门禁、收集真机峰值内存、优化远程日志静态队列、清理资源、确认 `model` 分区用途,并为应用与资源版本建立明确兼容机制。