初始提交卡包 USB 工程

纳入固件源码、屏幕组件、nand_xfer 工具和文档。忽略本机构建产物、sdkconfig,以及含本机路径的依赖锁。

Co-authored-by: Cursor <[email protected]>
This commit is contained in:
zhengxin
2026-10-13 21:54:50 +08:00
co-authored by Cursor
commit 851e347cf5
260 changed files with 269349 additions and 0 deletions
+142
View File
@@ -0,0 +1,142 @@
# 外部 Flash (NAND) 访问时序与并发分析报告
## 1. 概述
本报告分析 `/extflash` 挂载的 NAND Flash 在动画切换、UI 操作、下载解压等场景下的访问时序与潜在并发问题。
---
## 2. extflash 访问点汇总
### 2.1 读访问(Lottie JSON、stat、opendir)
| 调用位置 | 任务/上下文 | 操作 | 触发时机 |
|----------|-------------|------|----------|
| `switch_lottie_animation_cross_path_internal` | change_task (core 0) | `stat()` + `lv_lottie_set_src_file()` (fopen/fread) | BLE 指令、动画完成回调、定时器、下载成功 |
| `main_task` | main_task (core 1) | `stat(path_buf)` | 处理 DOWNLOAD_SUCCESS_BIT 时 |
| `extract_task` | extract_task (core 1) | `list_files_in_directory()` (opendir/readdir), `stat()` | 解压完成后 |
| `emoji_storage_sync_downloaded_packs_from_fs` | 调用者上下文 | opendir/readdir/stat | emoji_storage 初始化 |
| `zip_extractor` | extract_task 内 | `stat(zip_file_path)` | 解压前检查 ZIP 存在 |
| `change_task` 路径解析 | change_task | 多处 `stat()` | 解析表情包路径时 |
### 2.2 写访问
| 调用位置 | 任务/上下文 | 操作 | 触发时机 |
|----------|-------------|------|----------|
| `download_file` | MQTT 回调上下文 | fopen/fwrite (ZIP 下载) | MQTT 收到下载 URL 后 |
| `extract_zip_file` | extract_task (core 1) | fopen/fwrite (解压 JSON) | 下载完成后 |
| `check_flash_integrity` | flash_main 初始化 | fopen/fwrite | 启动时 |
| OTA `extract_running_app_to_storage` | OTA 流程 | 写入 `/extflash/flash.bin` | OTA 升级时 |
---
## 3. 并发场景分析
### 3.1 典型并发组合
| 场景 | 任务 A | 任务 B | 是否可能并发 |
|------|--------|--------|--------------|
| 1 | change_task 读 Lottie | extract_task 写解压文件 | 是 |
| 2 | change_task 读 Lottie | download_file 写 ZIP | 是 |
| 3 | main_task stat | extract_task 写 | 是 |
| 4 | 动画切换 | 用户手势/UI 操作 | 是(逻辑上并发) |
### 3.2 底层串行化
- **VFS/FAT 层**:ESP-IDF 的 fatfs 挂载使用 `ff_mutex`,同一时刻只允许一个文件操作。
- **NAND 硬件**:单 SPI 总线,单设备,物理上只能串行传输。
因此,**不会出现真正的硬件级并发读写**,但会出现**多个任务排队等待**同一 NAND 访问。
---
## 4. 关键问题
### 4.1 持锁期间进行 NAND 读(UI 卡顿)
`lvgl_init.c` 中:
```c
// 约 1185-1188 行
_lock_acquire(&lvgl_api_lock);
lv_lottie_set_src_file(current_lottie, file_path); // 内部会 fopen/fread 从 NAND 读
// ...
_lock_release(&lvgl_api_lock);
```
- `lv_lottie_set_src_file` 会从 NAND 读取整个 Lottie JSON,耗时较长。
- 整个读取过程在持有 `lvgl_api_lock` 期间执行。
- LVGL 任务需要该锁才能刷新界面,因此**动画切换期间 UI 会冻结**。
- 若用户在切换过程中滑动手势,UI 回调会卡在等待锁释放。
### 4.2 写操作阻塞读
- `extract_task` 解压大量 JSON 时,会长时间占用 FAT 锁。
- 此时 `change_task` 若执行 `stat()` 或 `lv_lottie_set_src_file()`,会阻塞在 FAT 层。
- 导致动画切换延迟或卡顿。
### 4.3 读操作阻塞写
- `change_task` 正在读取 Lottie 时,若 `extract_task` 或 `download_file` 开始写,会阻塞在 FAT 层。
- 下载/解压时间被拉长。
### 4.4 UI 手势与 extflash 的关系
- `ui_event_gesture_cb` 不直接访问 extflash,只做 `ui_change_page`(lv_screen_load_anim)。
- 但若此时 `change_task` 正在执行 `switch_lottie_animation_cross_path_internal` 并持有 `lvgl_api_lock`,LVGL 任务会阻塞,手势处理会延迟。
---
## 5. 任务与核心分布
| 任务 | 核心 | 优先级 | 主要 extflash 操作 |
|------|------|--------|--------------------|
| change_task | 0 | CHANGE_TASK_PRIORITY | 读 Lottie、stat |
| 主 LVGL 任务 | 0 | EXAMPLE_LVGL_TASK_PRIORITY | 无直接访问 |
| main_task | 1 | - | stat |
| extract_task | 1 | configMAX_PRIORITIES - 1 | 写解压、list_files、stat |
| download_file | 调用者上下文 (MQTT) | - | 写 ZIP |
---
## 6. 建议措施
### 6.1 短期(缓解 UI 卡顿)
1. **分离锁与文件读取**:
- 先 `stat()` 检查文件是否存在(不持 lvgl 锁)。
- 在持锁前,将文件内容读入内存缓冲区(或使用 LVGL 支持的内存源)。
- 持锁期间只做 `lv_lottie_set_src_buffer` 或等效 API,不进行 NAND 读。
2. **缩短持锁时间**:
- 若无法改为内存源,至少将 `lv_lottie_set_src_file` 移出锁外,仅把 LVGL 对象创建/修改等必须持锁的部分放在锁内。
### 6.2 中期(统一访问顺序)
1. **增加 extflash 访问互斥锁**:
- 所有对 `/extflash` 的 fopen/fread/fwrite/stat/opendir 等操作统一通过一个 `extflash_mutex` 保护。
- 避免长时间写阻塞短时读,可考虑读写锁(如支持)或优先级策略。
2. **解压时暂停动画切换**:
- 解压开始前设置标志,`change_task` 收到切换请求时若发现解压中则排队或延后。
- 解压完成后再处理排队请求。
### 6.3 长期(架构优化)
1. **预加载/缓存**:
- 常用 Lottie 预先读入内存或临时文件,切换时减少 NAND 访问。
2. **异步加载**:
- 先显示占位或上一帧,后台异步读 Lottie,完成后切换。
3. **NAND 性能调优**:
- 检查 SPI 时钟、DMA、队列等配置,在稳定前提下提升吞吐。
---
## 7. 结论
- **并发访问**:不会出现硬件级并发,但多任务会排队访问 NAND,存在相互阻塞。
- **动画切换**:在持 `lvgl_api_lock` 期间进行 NAND 读,会导致 UI 卡顿。
- **UI 操作**:手势本身不访问 extflash,但会因等待 `lvgl_api_lock` 而受动画切换影响。
- **建议**:优先将持锁与 NAND 读分离,并考虑引入 extflash 访问互斥与解压/切换协调逻辑。