chore: archive diagnostics and project documentation
This commit is contained in:
@@ -0,0 +1,810 @@
|
||||
# LittleFS 资源构建、烧录与挂载流程
|
||||
|
||||
本文说明项目中的 LCD 图片和提示音资源如何从源码目录进入设备 Flash,以及设备运行时如何通过 LittleFS 访问这些资源。同时给出新增、替换和排查资源问题的完整操作步骤。
|
||||
|
||||
## 1. 先建立整体认识
|
||||
|
||||
项目不会把每一张图片分别写入 Flash,也不会在设备启动时从电脑复制资源。
|
||||
|
||||
实际流程是:构建系统先将需要的资源整理成一个临时目录,再将整个目录制作成 LittleFS 文件系统镜像 `gifpack.bin`,最后把这个镜像作为一个独立分区烧录到设备。设备启动后,将该分区挂载到 `/littlefs`,应用程序便可以像读取普通文件一样读取图片和音频。
|
||||
|
||||
```text
|
||||
源码资源
|
||||
components/platform/assets/
|
||||
|
|
||||
| CMake 选择并复制资源
|
||||
v
|
||||
构建临时目录
|
||||
build/esp-idf/platform/asset_image/
|
||||
|
|
||||
| 制作 LittleFS 文件系统镜像
|
||||
v
|
||||
build/gifpack.bin
|
||||
|
|
||||
| idf.py flash
|
||||
v
|
||||
设备 Flash 中的 gifpack 分区
|
||||
|
|
||||
| esp_vfs_littlefs_register()
|
||||
v
|
||||
设备文件路径 /littlefs/
|
||||
```
|
||||
|
||||
需要区分以下三个概念:
|
||||
|
||||
| 名称 | 所在位置 | 作用 |
|
||||
| --- | --- | --- |
|
||||
| 源码资源目录 | `components/platform/assets/` | Git 管理的原始待烧录资源 |
|
||||
| 构建临时目录 | `build/esp-idf/platform/asset_image/` | 生成镜像前整理出来的目录树 |
|
||||
| LittleFS 镜像 | `build/gifpack.bin` | 最终烧入设备 `gifpack` 分区的文件系统镜像 |
|
||||
|
||||
临时目录只存在于开发电脑上。设备真正挂载的是 Flash 中的 `gifpack.bin` 内容,不是电脑上的 `build` 目录。
|
||||
|
||||
## 2. 相关文件在哪里
|
||||
|
||||
| 文件或目录 | 作用 |
|
||||
| --- | --- |
|
||||
| `components/platform/assets/lcd/` | LCD 的 `.rle` 图片资源 |
|
||||
| `components/platform/assets/audio_prompts/` | `.opus` 提示音资源 |
|
||||
| `components/platform/CMakeLists.txt` | 选择资源、整理临时目录、生成 LittleFS 镜像 |
|
||||
| `components/platform/idf_component.yml` | 声明 `joltwallet/littlefs` 组件依赖 |
|
||||
| `managed_components/joltwallet__littlefs/project_include.cmake` | 定义 `littlefs_create_partition_image()` |
|
||||
| `16m.csv` | 定义 `gifpack` 分区的类型、大小和位置 |
|
||||
| `components/platform/src/platform_behavior_stubs.c` | 运行时挂载 `gifpack` 分区 |
|
||||
| `components/platform/src/lcd_direct_assets.c` | 将 LCD 资源名映射到 `/littlefs/lcd/*.rle` |
|
||||
| `components/platform/src/lcd_direct.c` | 打开、读取、解码并绘制 RLE 文件 |
|
||||
| `components/domain/src/runtime/domain_runtime_adapters.c` | 将业务表达式映射为 LCD 资源播放请求 |
|
||||
|
||||
## 3. CMake 如何选中资源
|
||||
|
||||
### 3.1 定义资源目录
|
||||
|
||||
`components/platform/CMakeLists.txt` 中定义:
|
||||
|
||||
```cmake
|
||||
set(platform_audio_prompt_src_dir "${CMAKE_CURRENT_SOURCE_DIR}/assets/audio_prompts")
|
||||
set(platform_lcd_asset_src_dir "${CMAKE_CURRENT_SOURCE_DIR}/assets/lcd")
|
||||
set(platform_asset_image_dir "${CMAKE_CURRENT_BINARY_DIR}/asset_image")
|
||||
```
|
||||
|
||||
在当前组件中,这些变量最终大致对应:
|
||||
|
||||
```text
|
||||
platform_audio_prompt_src_dir
|
||||
-> components/platform/assets/audio_prompts
|
||||
|
||||
platform_lcd_asset_src_dir
|
||||
-> components/platform/assets/lcd
|
||||
|
||||
platform_asset_image_dir
|
||||
-> build/esp-idf/platform/asset_image
|
||||
```
|
||||
|
||||
### 3.2 LCD 使用显式白名单
|
||||
|
||||
LCD 资源由 `platform_lcd_flash_assets` 明确列出:
|
||||
|
||||
```cmake
|
||||
set(platform_lcd_flash_assets
|
||||
buyunqian1
|
||||
jichubiaoqing0
|
||||
jichubiaoqing1
|
||||
jichubiaoqing2
|
||||
muyu0
|
||||
muyu1
|
||||
muyu2
|
||||
# ...
|
||||
)
|
||||
```
|
||||
|
||||
列表中不写 `.rle` 后缀。后续循环会自动拼出完整文件名:
|
||||
|
||||
```cmake
|
||||
"${platform_lcd_asset_src_dir}/${lcd_rle_asset}.rle"
|
||||
```
|
||||
|
||||
例如白名单中的:
|
||||
|
||||
```text
|
||||
jichubiaoqing0
|
||||
```
|
||||
|
||||
最终对应:
|
||||
|
||||
```text
|
||||
components/platform/assets/lcd/jichubiaoqing0.rle
|
||||
```
|
||||
|
||||
因此,把新 `.rle` 文件放进 `assets/lcd/` 并不代表它会自动进入 Flash。只有加入 `platform_lcd_flash_assets` 的 LCD 文件才会进入 `gifpack.bin`。
|
||||
|
||||
### 3.3 音频使用自动扫描
|
||||
|
||||
音频资源采用另一种策略:
|
||||
|
||||
```cmake
|
||||
file(GLOB_RECURSE platform_audio_prompt_files CONFIGURE_DEPENDS
|
||||
"${platform_audio_prompt_src_dir}/*.opus"
|
||||
)
|
||||
```
|
||||
|
||||
这会递归选中 `assets/audio_prompts/` 下的全部 `.opus` 文件。因此:
|
||||
|
||||
- 新增 LCD 文件时,需要手动加入 CMake 白名单;
|
||||
- 新增 `.opus` 文件时,一般不需要修改资源白名单。
|
||||
|
||||
## 4. CMake 如何形成临时目录
|
||||
|
||||
构建时,CMake 会先清理旧的临时目录:
|
||||
|
||||
```cmake
|
||||
COMMAND ${CMAKE_COMMAND} -E rm -rf "${platform_asset_image_dir}"
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory "${platform_asset_image_dir}"
|
||||
```
|
||||
|
||||
这样可以避免已经从白名单删除的旧资源继续残留在镜像中。
|
||||
|
||||
随后,LCD 文件被复制到临时目录的 `lcd/` 子目录:
|
||||
|
||||
```cmake
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||
"${platform_lcd_asset_src_dir}/${lcd_rle_asset}.rle"
|
||||
"${platform_asset_image_dir}/lcd/${lcd_rle_asset}.rle"
|
||||
```
|
||||
|
||||
音频文件被复制到 `audio_prompts/` 子目录。完成后,临时目录大致如下:
|
||||
|
||||
```text
|
||||
build/esp-idf/platform/asset_image/
|
||||
├── audio_prompts/
|
||||
│ ├── boot.opus
|
||||
│ ├── network_connect_success.opus
|
||||
│ └── ...
|
||||
└── lcd/
|
||||
├── jichubiaoqing0.rle
|
||||
├── jichubiaoqing1.rle
|
||||
├── muyu0.rle
|
||||
└── ...
|
||||
```
|
||||
|
||||
这个目录结构会原样成为 LittleFS 镜像内部的目录结构。
|
||||
|
||||
资源文件还被加入 `DEPENDS`:
|
||||
|
||||
```cmake
|
||||
DEPENDS
|
||||
${platform_audio_prompt_files}
|
||||
${platform_lcd_flash_asset_files}
|
||||
```
|
||||
|
||||
因此,白名单内的资源发生变化时,构建系统能够重新整理目录并生成镜像。
|
||||
|
||||
## 5. `littlefs_create_partition_image()` 从哪里来
|
||||
|
||||
项目在 `components/platform/idf_component.yml` 中声明:
|
||||
|
||||
```yaml
|
||||
dependencies:
|
||||
joltwallet/littlefs: 1.20.4
|
||||
```
|
||||
|
||||
ESP-IDF Component Manager 因此引入:
|
||||
|
||||
```text
|
||||
managed_components/joltwallet__littlefs/
|
||||
```
|
||||
|
||||
这个组件包含特殊文件:
|
||||
|
||||
```text
|
||||
managed_components/joltwallet__littlefs/project_include.cmake
|
||||
```
|
||||
|
||||
ESP-IDF 构建系统会自动加载各组件的 `project_include.cmake`。该文件定义了:
|
||||
|
||||
```cmake
|
||||
function(littlefs_create_partition_image partition base_dir)
|
||||
# ...
|
||||
endfunction()
|
||||
```
|
||||
|
||||
所以 `littlefs_create_partition_image()`:
|
||||
|
||||
- 不是标准 CMake 内置函数;
|
||||
- 也不是本项目自行定义的普通函数;
|
||||
- 它是 `joltwallet/littlefs` 组件提供并由 ESP-IDF 自动导入的构建扩展。
|
||||
|
||||
## 6. 如何生成 `gifpack.bin`
|
||||
|
||||
项目调用:
|
||||
|
||||
```cmake
|
||||
littlefs_create_partition_image(
|
||||
gifpack
|
||||
"${platform_asset_image_dir}"
|
||||
FLASH_IN_PROJECT
|
||||
DEPENDS "${platform_asset_stage_stamp}"
|
||||
)
|
||||
```
|
||||
|
||||
参数含义如下:
|
||||
|
||||
| 参数 | 含义 |
|
||||
| --- | --- |
|
||||
| `gifpack` | 要匹配的分区名称 |
|
||||
| `platform_asset_image_dir` | 要制作成文件系统镜像的目录 |
|
||||
| `FLASH_IN_PROJECT` | 将镜像加入项目的完整 `flash` 目标 |
|
||||
| `DEPENDS` | 确保资源整理完成后再生成镜像 |
|
||||
|
||||
该函数会根据分区名查询分区表:
|
||||
|
||||
```cmake
|
||||
partition_table_get_partition_info(size "--partition-name ${partition}" "size")
|
||||
partition_table_get_partition_info(offset "--partition-name ${partition}" "offset")
|
||||
```
|
||||
|
||||
随后调用 `littlefs-python`,大致执行:
|
||||
|
||||
```text
|
||||
littlefs-python create \
|
||||
build/esp-idf/platform/asset_image \
|
||||
build/gifpack.bin \
|
||||
--fs-size=<gifpack分区大小> \
|
||||
--block-size=4096
|
||||
```
|
||||
|
||||
得到:
|
||||
|
||||
```text
|
||||
build/gifpack.bin
|
||||
```
|
||||
|
||||
`gifpack.bin` 不是简单压缩包,而是一个包含目录、文件名、文件内容和文件系统元数据的完整 LittleFS 镜像。
|
||||
|
||||
## 7. 分区表如何决定烧录位置
|
||||
|
||||
项目通过 `sdkconfig.defaults` 选择自定义分区表:
|
||||
|
||||
```text
|
||||
CONFIG_PARTITION_TABLE_CUSTOM=y
|
||||
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="16m.csv"
|
||||
```
|
||||
|
||||
`16m.csv` 中定义:
|
||||
|
||||
```csv
|
||||
gifpack, data, littlefs, , 0xa80000,
|
||||
```
|
||||
|
||||
字段含义:
|
||||
|
||||
| 字段 | 当前值 | 含义 |
|
||||
| --- | --- | --- |
|
||||
| Name | `gifpack` | 分区标签,构建和挂载都依赖它 |
|
||||
| Type | `data` | 数据分区 |
|
||||
| SubType | `littlefs` | LittleFS 文件系统分区 |
|
||||
| Offset | 空 | 由 ESP-IDF 根据前面的分区自动计算 |
|
||||
| Size | `0xa80000` | 分区大小,约 10.5 MiB |
|
||||
|
||||
当前生成结果中,`gifpack` 的起始地址是 `0x10000`。由于分区 Offset 没有写死,如果前面的分区布局发生变化,实际地址也可能变化;应以当前构建生成的分区表和 `build/flasher_args.json` 为准。
|
||||
|
||||
下面三处名称必须一致:
|
||||
|
||||
```text
|
||||
CMake:
|
||||
littlefs_create_partition_image(gifpack ...)
|
||||
|
||||
分区表:
|
||||
gifpack, data, littlefs, ...
|
||||
|
||||
运行时:
|
||||
.partition_label = "gifpack"
|
||||
```
|
||||
|
||||
任何一处名称不一致,都可能导致镜像生成失败、烧错位置或运行时找不到分区。
|
||||
|
||||
## 8. `FLASH_IN_PROJECT` 如何让镜像随项目烧录
|
||||
|
||||
`FLASH_IN_PROJECT` 会让 LittleFS 组件执行类似以下注册:
|
||||
|
||||
```cmake
|
||||
esptool_py_flash_target_image(
|
||||
flash
|
||||
"${partition}"
|
||||
"${offset}"
|
||||
"${image_file}"
|
||||
)
|
||||
```
|
||||
|
||||
它的含义是:把 `gifpack.bin` 加入 ESP-IDF 的完整 `flash` 目标。
|
||||
|
||||
当前 `build/flasher_args.json` 中可看到类似内容:
|
||||
|
||||
```json
|
||||
"0x10000": "gifpack.bin"
|
||||
```
|
||||
|
||||
所以执行:
|
||||
|
||||
```bash
|
||||
idf.py flash
|
||||
```
|
||||
|
||||
时,ESP-IDF 会分别烧录 bootloader、分区表、应用固件和 `gifpack.bin`。`gifpack.bin` 并没有编入应用程序 `.bin`,它始终是一个独立的分区镜像。
|
||||
|
||||
需要特别区分:
|
||||
|
||||
| 命令 | 对资源分区的影响 |
|
||||
| --- | --- |
|
||||
| `idf.py build` | 生成或更新 `gifpack.bin`,不写设备 |
|
||||
| `idf.py flash` | 按完整烧录清单写入 `gifpack.bin` |
|
||||
| `idf.py app-flash` | 通常只更新应用分区,不更新 `gifpack` |
|
||||
| 普通应用 OTA | 通常只更新 OTA 应用分区,不更新 `gifpack` |
|
||||
|
||||
因此,修改资源后只执行 `app-flash` 或普通应用 OTA,设备中的资源可能仍然是旧版本。
|
||||
|
||||
## 9. 设备运行时如何挂载
|
||||
|
||||
挂载函数位于 `components/platform/src/platform_behavior_stubs.c`:
|
||||
|
||||
```c
|
||||
#define VOICE_PROMPT_LITTLEFS_BASE_PATH "/littlefs"
|
||||
#define VOICE_PROMPT_LITTLEFS_PARTITION "gifpack"
|
||||
|
||||
esp_vfs_littlefs_conf_t conf = {
|
||||
.base_path = VOICE_PROMPT_LITTLEFS_BASE_PATH,
|
||||
.partition_label = VOICE_PROMPT_LITTLEFS_PARTITION,
|
||||
.partition = NULL,
|
||||
.format_if_mount_failed = false,
|
||||
.dont_mount = false,
|
||||
};
|
||||
|
||||
esp_err_t err = esp_vfs_littlefs_register(&conf);
|
||||
```
|
||||
|
||||
### 9.1 `partition_label = "gifpack"`
|
||||
|
||||
LittleFS 驱动根据标签在运行时分区表中查找 `gifpack`,得到该分区的实际 Flash 地址和大小。
|
||||
|
||||
### 9.2 `partition = NULL`
|
||||
|
||||
调用方没有直接传入 `esp_partition_t`,由 LittleFS 驱动根据 `partition_label` 自动查找分区。
|
||||
|
||||
### 9.3 `base_path = "/littlefs"`
|
||||
|
||||
将 LittleFS 注册到 ESP-IDF VFS 的 `/littlefs` 路径。挂载成功后,可以使用标准文件 API:
|
||||
|
||||
```c
|
||||
open("/littlefs/lcd/jichubiaoqing0.rle", O_RDONLY);
|
||||
fopen("/littlefs/audio_prompts/boot.opus", "rb");
|
||||
```
|
||||
|
||||
### 9.4 `format_if_mount_failed = false`
|
||||
|
||||
挂载失败时不格式化分区。这对预置资源分区非常重要,因为格式化会清空已经烧录的图片和音频。
|
||||
|
||||
### 9.5 `dont_mount = false`
|
||||
|
||||
注册后立即执行挂载,而不是只注册驱动。
|
||||
|
||||
### 9.6 VFS 路径如何对应到 LittleFS 内部路径
|
||||
|
||||
临时目录中的:
|
||||
|
||||
```text
|
||||
asset_image/lcd/jichubiaoqing0.rle
|
||||
```
|
||||
|
||||
进入 LittleFS 后对应:
|
||||
|
||||
```text
|
||||
/lcd/jichubiaoqing0.rle
|
||||
```
|
||||
|
||||
挂载到 `/littlefs` 后,应用程序看到:
|
||||
|
||||
```text
|
||||
/littlefs/lcd/jichubiaoqing0.rle
|
||||
```
|
||||
|
||||
应用调用 `open()` 时,ESP-IDF VFS 识别 `/littlefs` 前缀,并将剩余的 `/lcd/jichubiaoqing0.rle` 交给 LittleFS 驱动读取。
|
||||
|
||||
## 10. 当前启动时机和隐式依赖
|
||||
|
||||
当前 `platform_mount_littlefs()` 不是由 `platform_init()` 直接调用,而是在 `voice_prompt_play_file()` 播放提示音之前调用:
|
||||
|
||||
```c
|
||||
esp_err_t mount_err = platform_mount_littlefs();
|
||||
```
|
||||
|
||||
当前启动配置启用了 `boot.opus`,启动顺序大致是:
|
||||
|
||||
```text
|
||||
准备运行时存储
|
||||
-> 初始化音频
|
||||
-> 播放 boot.opus
|
||||
-> platform_mount_littlefs()
|
||||
-> 挂载 gifpack 到 /littlefs
|
||||
-> 初始化 LCD
|
||||
-> LCD 渲染线程读取 /littlefs/lcd/*.rle
|
||||
```
|
||||
|
||||
这意味着当前 LCD 成功读取资源,隐式依赖“开机音流程已经触发 LittleFS 挂载”。如果以后禁用开机音,或者在调用 `voice_prompt_play_file()` 之前就失败,LCD 可能在尚未挂载时访问 `/littlefs/lcd/`。
|
||||
|
||||
更稳妥的长期方案是把 `platform_mount_littlefs()` 作为独立启动阶段,在音频和 LCD 使用资源之前显式调用。修改启动流程前,应保持现有分层规则,并由 `app_composition` 编排启动顺序。
|
||||
|
||||
## 11. LCD RLE 文件格式
|
||||
|
||||
当前 `lcd_direct` 驱动读取的 `.rle` 是原始二元组序列:
|
||||
|
||||
```c
|
||||
typedef struct {
|
||||
uint16_t color;
|
||||
uint16_t count;
|
||||
} lcd_direct_rle_run_t;
|
||||
```
|
||||
|
||||
每个 run 占 4 字节:
|
||||
|
||||
```text
|
||||
2 字节 RGB565 color,little-endian
|
||||
2 字节重复像素数 count,little-endian
|
||||
```
|
||||
|
||||
全屏图片解压后的像素总数应与屏幕绘制区域匹配。当前横屏画布通常为:
|
||||
|
||||
```text
|
||||
320 x 240 = 76800 像素
|
||||
```
|
||||
|
||||
图标资源则应与 `lcd_direct_icon_t` 中配置的宽高匹配。
|
||||
|
||||
### 11.1 `run_count` 必须与文件一致
|
||||
|
||||
`components/platform/src/lcd_direct_assets.c` 不仅保存文件路径,还保存 `run_count`:
|
||||
|
||||
```c
|
||||
{
|
||||
.path = LCD_ASSET_BASE_PATH "jichubiaoqing0.rle",
|
||||
.run_count = 5411U,
|
||||
},
|
||||
```
|
||||
|
||||
对于当前原始格式:
|
||||
|
||||
```text
|
||||
run_count = 文件字节数 / 4
|
||||
```
|
||||
|
||||
替换资源后,即使文件名没有变化,只要编码后的 run 数发生变化,就必须同步更新 `run_count`。否则驱动可能少读、读到文件末尾失败或显示异常。
|
||||
|
||||
可以用下面的方法检查:
|
||||
|
||||
```bash
|
||||
python3 -c 'from pathlib import Path; p=Path("components/platform/assets/lcd/jichubiaoqing0.rle"); print(p.stat().st_size // 4)'
|
||||
```
|
||||
|
||||
还应确认文件大小能被 4 整除。
|
||||
|
||||
### 11.2 不要误用 `tools/png_to_rle.py`
|
||||
|
||||
当前仓库的 `tools/png_to_rle.py` 使用 Pillow 生成标准 TGA RLE 文件;而 `lcd_direct.c` 读取的是上面描述的原始 `color + count` 二元组格式。
|
||||
|
||||
两种格式不兼容。不要直接用 `tools/png_to_rle.py` 的输出替换 `components/platform/assets/lcd/*.rle`,除非先统一转换工具和驱动格式。
|
||||
|
||||
`tools/extract_lcd_assets.py` 能写出当前原始二元组格式,但其中包含旧工程的固定 Windows 路径和固定资源映射,使用前必须检查和调整,不能假设它在所有开发环境中可直接运行。
|
||||
|
||||
## 12. 替换现有 LCD 资源
|
||||
|
||||
适用场景:资源名、帧数和业务行为不变,只替换画面内容。
|
||||
|
||||
以替换 `jichubiaoqing0.rle` 为例:
|
||||
|
||||
1. 使用与当前驱动兼容的原始 RLE 转换流程生成新文件。
|
||||
2. 确认解压像素总数与目标画布一致。
|
||||
3. 用新文件替换:
|
||||
|
||||
```text
|
||||
components/platform/assets/lcd/jichubiaoqing0.rle
|
||||
```
|
||||
|
||||
4. 计算新文件的 `run_count`。
|
||||
5. 更新 `components/platform/src/lcd_direct_assets.c` 中对应条目的 `run_count`。
|
||||
6. 因为文件名没有变化且已经在 CMake 白名单中,一般不需要修改 `platform_lcd_flash_assets`。
|
||||
7. 执行完整构建和烧录:
|
||||
|
||||
```bash
|
||||
eim run "idf.py build" v5.5.4
|
||||
eim run "idf.py flash" v5.5.4
|
||||
```
|
||||
|
||||
8. 在串口日志中确认 LittleFS 挂载成功,并检查 LCD 是否有 `open` 或 `read` 失败日志。
|
||||
|
||||
如果当前环境没有 `eim`,按照项目 `AGENTS.md` 中规定的 ESP-IDF v5.5.4 激活流程执行相同命令。
|
||||
|
||||
## 13. 给已有动画新增一帧
|
||||
|
||||
适用场景:例如已有 `muyu0.rle`、`muyu1.rle`、`muyu2.rle`,现在增加 `muyu3.rle`。
|
||||
|
||||
### 第一步:添加文件
|
||||
|
||||
放到:
|
||||
|
||||
```text
|
||||
components/platform/assets/lcd/muyu3.rle
|
||||
```
|
||||
|
||||
### 第二步:加入 CMake 白名单
|
||||
|
||||
在 `components/platform/CMakeLists.txt` 的 `platform_lcd_flash_assets` 中加入:
|
||||
|
||||
```cmake
|
||||
muyu3
|
||||
```
|
||||
|
||||
否则文件不会进入 `gifpack.bin`。
|
||||
|
||||
### 第三步:加入平台资源映射
|
||||
|
||||
在 `components/platform/src/lcd_direct_assets.c` 的对应数组中增加:
|
||||
|
||||
```c
|
||||
{
|
||||
.path = LCD_ASSET_BASE_PATH "muyu3.rle",
|
||||
.run_count = <实际 run 数>,
|
||||
},
|
||||
```
|
||||
|
||||
平台通过数组下标解释 `frame_index`,所以新条目在数组中的位置就是它的帧索引。
|
||||
|
||||
### 第四步:确认业务播放顺序
|
||||
|
||||
如果希望业务流程实际播放新帧,还需要检查 `components/domain/src/runtime/domain_runtime_adapters.c` 中的帧顺序,例如:
|
||||
|
||||
```c
|
||||
static const uint8_t s_muyu_frame_order[] = {0, 1, 2, 3, 2, 1};
|
||||
```
|
||||
|
||||
只增加平台资源映射但不修改播放顺序,新帧可能已经烧入设备,却永远不会被请求。
|
||||
|
||||
### 第五步:完整构建并烧录
|
||||
|
||||
```bash
|
||||
eim run "idf.py build" v5.5.4
|
||||
eim run "idf.py flash" v5.5.4
|
||||
```
|
||||
|
||||
## 14. 新增一组 LCD 资源
|
||||
|
||||
适用场景:增加一个新的动画组,例如:
|
||||
|
||||
```text
|
||||
happy0.rle
|
||||
happy1.rle
|
||||
happy2.rle
|
||||
```
|
||||
|
||||
需要同时完成以下工作:
|
||||
|
||||
1. 将所有文件放入 `components/platform/assets/lcd/`。
|
||||
2. 将 `happy0`、`happy1`、`happy2` 加入 `platform_lcd_flash_assets`。
|
||||
3. 在 `components/platform/src/lcd_direct_assets.c` 中建立资源数组,填写每帧路径和 `run_count`。
|
||||
4. 在 `lcd_direct_asset_frame()` 中增加资源名到数组的查找映射,使 `asset_dir = "happy"` 能找到对应帧。
|
||||
5. 如果这是新的业务表达式,按照 `docs/lcd-expressions.md` 的分层要求:
|
||||
- 在 `domain` 公共契约中增加语义表达式 ID;
|
||||
- 在 `domain_runtime_adapters.c` 中映射到 `asset_dir = "happy"`;
|
||||
- 由 `control_plane` 决定何时请求该表达式;
|
||||
- 不要让 `control_plane` 直接调用 `platform_lcd_*`。
|
||||
6. 构建并完整烧录。
|
||||
7. 在硬件上验证帧序、帧间隔、中断优先级和播放结束后的返回行为。
|
||||
|
||||
这里涉及两个彼此独立的问题:
|
||||
|
||||
```text
|
||||
资源是否进入 Flash
|
||||
-> 由 CMake 白名单决定
|
||||
|
||||
资源是否会被业务播放
|
||||
-> 由 platform 资源映射和 domain 表达式映射决定
|
||||
```
|
||||
|
||||
文件成功烧入 Flash,不代表业务代码一定能找到或播放它。
|
||||
|
||||
## 15. 新增或替换音频资源
|
||||
|
||||
### 15.1 替换已有音频
|
||||
|
||||
如果文件名和调用路径不变:
|
||||
|
||||
1. 替换 `components/platform/assets/audio_prompts/` 下对应的 `.opus` 文件。
|
||||
2. 执行 `idf.py build` 重新生成 `gifpack.bin`。
|
||||
3. 执行完整 `idf.py flash` 更新资源分区。
|
||||
|
||||
一般不需要修改 CMake,因为 `.opus` 文件会被自动扫描。
|
||||
|
||||
### 15.2 新增音频
|
||||
|
||||
1. 将 `.opus` 文件加入 `components/platform/assets/audio_prompts/`。
|
||||
2. 确认播放代码引用的路径正确:
|
||||
|
||||
```text
|
||||
/littlefs/audio_prompts/<文件名>.opus
|
||||
```
|
||||
|
||||
3. 如果传给 `voice_prompt_play_file()` 的是相对文件名,它会自动补上 `/littlefs/audio_prompts/`。
|
||||
4. 完整构建并烧录。
|
||||
|
||||
## 16. 推荐验证流程
|
||||
|
||||
资源修改后,建议按以下顺序验证。
|
||||
|
||||
### 16.1 检查源码资源
|
||||
|
||||
确认:
|
||||
|
||||
- 文件名和大小写正确;
|
||||
- LCD 文件已加入白名单;
|
||||
- LCD `run_count` 与文件一致;
|
||||
- 资源映射路径与文件名一致。
|
||||
|
||||
### 16.2 构建镜像
|
||||
|
||||
```bash
|
||||
eim run "idf.py build" v5.5.4
|
||||
```
|
||||
|
||||
检查是否生成:
|
||||
|
||||
```text
|
||||
build/gifpack.bin
|
||||
```
|
||||
|
||||
也可检查临时目录中是否存在目标文件:
|
||||
|
||||
```text
|
||||
build/esp-idf/platform/asset_image/lcd/<资源名>.rle
|
||||
build/esp-idf/platform/asset_image/audio_prompts/<资源名>.opus
|
||||
```
|
||||
|
||||
### 16.3 检查烧录清单
|
||||
|
||||
检查 `build/flasher_args.json` 中是否包含:
|
||||
|
||||
```json
|
||||
"gifpack": {
|
||||
"offset": "...",
|
||||
"file": "gifpack.bin"
|
||||
}
|
||||
```
|
||||
|
||||
不要把 `0x10000` 永久当作固定值,应以当前分区表生成结果为准。
|
||||
|
||||
### 16.4 完整烧录
|
||||
|
||||
```bash
|
||||
eim run "idf.py flash" v5.5.4
|
||||
```
|
||||
|
||||
不要只执行 `app-flash`。
|
||||
|
||||
### 16.5 检查运行日志
|
||||
|
||||
挂载成功时应出现类似日志:
|
||||
|
||||
```text
|
||||
LittleFS mounted at /littlefs, used=... total=...
|
||||
```
|
||||
|
||||
LCD 资源异常时重点关注:
|
||||
|
||||
```text
|
||||
open LCD asset failed
|
||||
read LCD asset failed
|
||||
LCD presentation draw failed
|
||||
```
|
||||
|
||||
音频异常时重点关注:
|
||||
|
||||
```text
|
||||
Mount LittleFS failed
|
||||
Open prompt failed
|
||||
```
|
||||
|
||||
## 17. 常见问题排查
|
||||
|
||||
### 17.1 源码目录里有文件,但设备提示找不到
|
||||
|
||||
按顺序检查:
|
||||
|
||||
1. LCD 文件是否加入 `platform_lcd_flash_assets`;
|
||||
2. 是否重新执行了 `idf.py build`;
|
||||
3. 是否执行了完整 `idf.py flash`;
|
||||
4. `lcd_direct_assets.c` 中路径是否完全一致;
|
||||
5. `/littlefs` 是否已经挂载。
|
||||
|
||||
### 17.2 替换图片后还是显示旧图
|
||||
|
||||
最常见原因是只执行了 `app-flash` 或 OTA,只更新了应用分区,没有更新 `gifpack`。
|
||||
|
||||
重新执行完整烧录:
|
||||
|
||||
```bash
|
||||
eim run "idf.py flash" v5.5.4
|
||||
```
|
||||
|
||||
### 17.3 能打开文件,但读取失败
|
||||
|
||||
检查:
|
||||
|
||||
- `run_count` 是否与新文件一致;
|
||||
- 文件大小是否能被 4 整除;
|
||||
- 文件是否为当前驱动要求的原始二元组 RLE;
|
||||
- 是否错误使用了 TGA RLE 文件;
|
||||
- RLE 解压后的像素数量是否匹配目标画布。
|
||||
|
||||
### 17.4 构建时报分区空间不足
|
||||
|
||||
说明临时目录中的总资源超过 `gifpack` 容量。处理方式包括:
|
||||
|
||||
- 删除未使用资源并从白名单移除;
|
||||
- 优化资源编码;
|
||||
- 在确认整个 Flash 布局允许后调整 `16m.csv`;
|
||||
- 调整分区表时同时检查 OTA、模型和日志分区是否仍有足够空间。
|
||||
|
||||
不要只增大 `gifpack` 而忽略后续分区的地址和总 Flash 容量。
|
||||
|
||||
### 17.5 挂载失败
|
||||
|
||||
检查:
|
||||
|
||||
1. 是否已经完整烧录 `gifpack.bin`;
|
||||
2. CMake、分区表、运行时使用的分区名是否都是 `gifpack`;
|
||||
3. 分区表是否和当前固件一起烧录;
|
||||
4. 镜像大小是否与当前分区表一致;
|
||||
5. 是否使用旧构建目录生成的镜像;
|
||||
6. 必要时执行 `idf.py fullclean` 后重新构建和完整烧录。
|
||||
|
||||
由于资源分区设置了 `format_if_mount_failed = false`,挂载失败不会自动格式化,这可以避免资源被意外清空。
|
||||
|
||||
## 18. 修改资源时的最终检查清单
|
||||
|
||||
### 只替换现有 LCD 文件
|
||||
|
||||
- [ ] 替换 `components/platform/assets/lcd/<name>.rle`
|
||||
- [ ] 确认文件格式为原始 `uint16_t color + uint16_t count`
|
||||
- [ ] 更新 `lcd_direct_assets.c` 中的 `run_count`
|
||||
- [ ] 执行 `idf.py build`
|
||||
- [ ] 执行完整 `idf.py flash`
|
||||
- [ ] 检查挂载和 LCD 读取日志
|
||||
|
||||
### 新增 LCD 帧或资源组
|
||||
|
||||
- [ ] 添加 `.rle` 文件
|
||||
- [ ] 加入 `platform_lcd_flash_assets`
|
||||
- [ ] 在 `lcd_direct_assets.c` 中增加路径和 `run_count`
|
||||
- [ ] 在 `lcd_direct_asset_frame()` 中增加或确认名称映射
|
||||
- [ ] 在 domain 层配置播放顺序或表达式映射
|
||||
- [ ] 执行完整构建和烧录
|
||||
- [ ] 在硬件上验证实际播放
|
||||
|
||||
### 替换或新增音频
|
||||
|
||||
- [ ] 添加或替换 `.opus` 文件
|
||||
- [ ] 确认调用路径正确
|
||||
- [ ] 执行 `idf.py build`
|
||||
- [ ] 执行完整 `idf.py flash`
|
||||
- [ ] 检查提示音打开和播放日志
|
||||
|
||||
## 19. 一句话总结
|
||||
|
||||
资源交付链路可以概括为:
|
||||
|
||||
```text
|
||||
源码资源
|
||||
-> CMake 选择并整理目录
|
||||
-> LittleFS 组件生成 gifpack.bin
|
||||
-> ESP-IDF 根据分区表烧入 gifpack 分区
|
||||
-> 设备把 gifpack 挂载为 /littlefs
|
||||
-> LCD 和音频通过标准文件 API 读取资源
|
||||
```
|
||||
|
||||
新增资源时要同时确认“是否进入 `gifpack.bin`”和“运行时代码是否会找到并使用它”;替换资源时则要特别注意更新 LCD `run_count`,并执行完整 `idf.py flash` 更新独立的资源分区。
|
||||
Reference in New Issue
Block a user