963 lines
29 KiB
Markdown
963 lines
29 KiB
Markdown
# 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` 分区用途,并为应用与资源版本建立明确兼容机制。
|