## 核心变更 ### 1. 双模式完全隔离 (Phase 2+4) - 拆分 InitializeButtons() 为 InitializeBadgeModeButtons() + InitializeAiModeButtons() - 构造函数按 device_mode 分支:吧唧模式不创建 PowerSaveTimer/BackgroundTask - 吧唧模式不注册音量/故事按键回调,避免调用 GetAudioCodec() 崩溃 - GPIO0 由 iot_button 统一处理,dzbj_button 仅注册 KEY2(GPIO4) - SetDeviceState() 中 background_task_ 空指针保护 ### 2. 吧唧模式 BOOT 按键崩溃修复 - 新增 dzbj_boot_click_handler()(C 函数,避免 lvgl.h 与 display.h 冲突) - 移植 dzbj 的唤醒屏幕/退出手电筒/返回Home 完整逻辑 ### 3. esp_timer 阻塞 LVGL 渲染修复 - iot_button 回调在 esp_timer 任务中执行,vTaskDelay 会阻塞 lv_tick_inc - 改为 xTaskCreate 派发到独立 FreeRTOS 任务,避免冻结 LVGL 渲染 ### 4. 触摸坐标日志 + SPIFFS 预烧录 - esp_lvgl_port_touch.c 添加触摸坐标打印 - CMakeLists.txt 添加 spiffs_create_partition_image 自动打包 spiffs_image/ ### 5. dzbj 模块文件新增 - device_mode: NVS 设备模式管理 (AI=0/吧唧=1) - dzbj_button: GPIO4 KEY2 中断 + BOOT 点击处理 - dzbj_ble: BLE GATT 图传服务 (0x0B00) - dzbj_battery: ADC 电池电压监测 - sleep_mgr: 10s 超时熄屏低功耗管理 - pages: 图片浏览/GIF播放/PWM亮度 - fatfs: SPIFFS 文件管理 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
292 lines
11 KiB
Markdown
292 lines
11 KiB
Markdown
# AI对话 + 电子吧唧 双模式适配说明
|
||
|
||
> 更新日期:2026-02-27
|
||
> 硬件平台:movecall-moji-esp32s3 (ESP32-S3-N16R8)
|
||
> ESP-IDF版本:5.4.2
|
||
> LVGL版本:8.3.11
|
||
|
||
---
|
||
|
||
## 一、项目现状
|
||
|
||
### 1.1 主项目 (Baji_Rtc_Toy)
|
||
|
||
基于 AI小智 开源项目改造,当前已集成功能:
|
||
- 火山引擎 RTC 语音对话(WiFi 连接)
|
||
- BLE 配网(Bluedroid,Service 0xABF0)
|
||
- 音频编解码(ES8311 + Opus)
|
||
- 唤醒词检测(esp-sr AFE)
|
||
- **LVGL 8.3.11 LCD 显示**(Phase 1 已完成,开机显示 ScreenHome)
|
||
- ST77916 QSPI 360×360 LCD + CST816S 触摸(已初始化)
|
||
|
||
### 1.2 dzbj 子项目 (电子吧唧)
|
||
|
||
独立的 ESP32-S3 LVGL 项目,位于 `/dzbj/` 目录,功能:
|
||
- 360×360 ST77916 QSPI LCD + CST816S 触摸
|
||
- LVGL 8.3.11 三屏界面(ScreenHome/ScreenImg/ScreenSet)
|
||
- BLE GATT 图片传输服务(Service 0x0B00)
|
||
- GIF 播放、JPEG 解码、SPIFFS 图片管理
|
||
- 低功耗休眠/唤醒管理(10s 超时熄屏)
|
||
- PWM 背光控制
|
||
|
||
### 1.3 实施进度
|
||
|
||
| 阶段 | 状态 | 说明 |
|
||
|------|------|------|
|
||
| Phase 1: 点亮屏幕 | **已完成** | LCD + LVGL + ScreenHome 显示 |
|
||
| Phase 2+4: 完整模式 + 切换 | **实施中** | 移植 dzbj 全模块 + 双模式切换 |
|
||
| Phase 3: AI 聊天 UI | 待定 | 基于 LVGL 的 emoji + 聊天气泡 |
|
||
|
||
---
|
||
|
||
## 二、双模式架构设计
|
||
|
||
### 2.1 架构概览
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────┐
|
||
│ LVGL 8.3.11 + LCD │
|
||
│ (常驻,两个模式共享显示硬件) │
|
||
├────────────────────┬────────────────────────────┤
|
||
│ AI 对话模式 │ 电子吧唧模式 │
|
||
│ (mode=0, 默认) │ (mode=1) │
|
||
│ │ │
|
||
│ WiFi + RTC 协议 │ BLE GATT Server │
|
||
│ ScreenHome (仅显示) │ ScreenHome/Img/Set │
|
||
│ 音频编解码 + 唤醒词 │ BLE 图片传输 (0x0B00) │
|
||
│ PowerSaveTimer │ sleep_mgr (10s熄屏) │
|
||
│ IMU 传感器 │ battery 电池监测 │
|
||
│ 电量检测 (板级) │ SPIFFS 图片管理 │
|
||
├────────────────────┴────────────────────────────┤
|
||
│ BOOT 双击 切换(写NVS + 重启) │
|
||
└─────────────────────────────────────────────────┘
|
||
```
|
||
|
||
### 2.2 模式定义
|
||
|
||
**AI 对话模式** (device_mode=0, 默认):
|
||
- 网络:WiFi 连接
|
||
- 协议:火山引擎 RTC 实时对话
|
||
- 音频:唤醒词检测 + Opus 编解码 + I2S 输出
|
||
- 显示:ScreenHome(仅显示,无触摸交互)
|
||
- BLE:**关闭**(仅配网时启动)
|
||
|
||
**电子吧唧模式** (device_mode=1):
|
||
- 网络:**WiFi 关闭**
|
||
- BLE:GATT Server(图片传输服务 0x0B00)
|
||
- 显示:ScreenHome → ScreenImg(图片浏览)→ ScreenSet(设置)
|
||
- 功能:JPEG 解码、GIF 播放、SPIFFS 图片管理、低功耗、电池监测
|
||
|
||
### 2.3 模式切换方案
|
||
|
||
**方案:重启切换(NVS 标志)**
|
||
|
||
```
|
||
BOOT 双击 → 读取 NVS device_mode → 切换 0↔1 → 写入 NVS → esp_restart()
|
||
```
|
||
|
||
切换时间约 3-4 秒(重启时间),NVS 擦写寿命 10-100万次,无限次切换。
|
||
|
||
**选择重启而非热切换的原因**:
|
||
1. WiFi + BLE Bluedroid 同时运行内部 SRAM 不足(约需 280KB,可用 ~334KB)
|
||
2. 热切换需处理大量资源释放/重建(协议、音频管道、FreeRTOS 任务),复杂度极高
|
||
3. Application 单例内部状态(event_group, opus 编解码器, background_task)难以干净重置
|
||
4. 重启方式简单可靠,避免内存泄漏和碎片化风险
|
||
|
||
---
|
||
|
||
## 三、内存预算分析
|
||
|
||
### 3.1 硬件规格
|
||
|
||
- **内部 SRAM**:~334KB DIRAM(可用)
|
||
- **PSRAM**:8MB OCT-SPI 80MHz
|
||
- **Flash**:16MB
|
||
|
||
### 3.2 各模式内存占用
|
||
|
||
| 场景 | 估算占用 | 剩余 | 可行性 |
|
||
|------|----------|------|--------|
|
||
| AI 对话模式(WiFi+RTC+音频+LVGL) | ~212-237KB | ~97-122KB | **可行**(偏紧) |
|
||
| 电子吧唧模式(BLE+LVGL+SPIFFS) | ~190KB | ~144KB | **可行**(充裕) |
|
||
| 两模式同时运行 | ~274-345KB | 不足 | **不可行** |
|
||
|
||
### 3.3 关键验证数据
|
||
|
||
- Phase 1 测试:WiFi + BLE 同时运行导致 `assert failed: vQueueDelete queue.c:2355`(FreeRTOS 信号量分配失败)
|
||
- BLE 配网成功后 `xTaskCreate` 分配 2048 栈失败(已改用 `esp_timer` 解决)
|
||
- 确认两模式必须互斥运行
|
||
|
||
---
|
||
|
||
## 四、启动流程
|
||
|
||
### 4.1 双模式启动序列
|
||
|
||
```
|
||
开机
|
||
│
|
||
├── 板级构造函数(通用)
|
||
│ ├── PowerSaveTimer 初始化
|
||
│ ├── InitializeButtons()(主项目 Button 类,双击注册在此)
|
||
│ ├── InitializeCodecI2c()
|
||
│ ├── dzbj_display_init() ← LCD + LVGL 始终初始化
|
||
│ │
|
||
│ ├── if device_mode == BADGE (吧唧模式)
|
||
│ │ └── InitializeBadgeMode()
|
||
│ │ ├── fatfs_init() // SPIFFS 文件系统
|
||
│ │ ├── init_spiffs_image_list() // 扫描图片
|
||
│ │ ├── dzbj_button_init() // ISR按键
|
||
│ │ ├── battery_init() // 电池检测
|
||
│ │ ├── dzbj_ble_init() // BLE 图传
|
||
│ │ └── sleep_mgr_init() // 低功耗管理
|
||
│ │
|
||
│ └── else (AI模式, 默认)
|
||
│ ├── InitializeIot()
|
||
│ ├── InitializeBatteryMonitor()
|
||
│ ├── InitializeImuSensor()
|
||
│ └── PowerSaveTimer 启用
|
||
│
|
||
├── Application::Start()
|
||
│ ├── if device_mode == BADGE
|
||
│ │ └── SetDeviceState(Idle); return; // 不启动WiFi/协议/音频
|
||
│ │
|
||
│ └── else (AI模式)
|
||
│ ├── Opus 编解码器初始化
|
||
│ ├── 音频管道启动
|
||
│ ├── board.StartNetwork() // WiFi 连接
|
||
│ ├── RTC 协议初始化
|
||
│ └── MainLoop + AudioLoop 启动
|
||
```
|
||
|
||
### 4.2 BOOT 按键行为
|
||
|
||
| 事件 | AI模式 | 吧唧模式 | 配网模式 |
|
||
|------|--------|---------|---------|
|
||
| 单击 | Idle↔Listening 切换 | 待定(返回 ScreenHome?) | 显示 MAC 地址 |
|
||
| 双击 | **切换到吧唧模式** | **切换到AI模式** | 无响应 |
|
||
| 长按5s | 无响应 | 无响应 | 进入生产测试 |
|
||
|
||
---
|
||
|
||
## 五、模块移植清单
|
||
|
||
### 5.1 从 dzbj 移植的模块
|
||
|
||
| 模块 | 源文件 | 目标文件 | 适配要点 |
|
||
|------|--------|---------|---------|
|
||
| fatfs | `dzbj/main/fatfs/` | `main/dzbj/fatfs.c/h` | `gpio.h` → `dzbj_gpio.h` |
|
||
| pages | `dzbj/main/pages/pages.c` | `main/dzbj/pages.c` | 移除 `wifi.h`,PWM 去重 |
|
||
| BLE图传 | `dzbj/main/ble/ble.c` | `main/dzbj/dzbj_ble.c/h` | **新增 deinit 函数** |
|
||
| sleep_mgr | `dzbj/main/sleep_mgr/` | `main/dzbj/sleep_mgr.c` | 按键回调适配 |
|
||
| button | `dzbj/main/button/` | `main/dzbj/dzbj_button.c/h` | ISR+队列+去抖 |
|
||
| battery | `dzbj/main/battery/` | `main/dzbj/battery.c/h` | ADC 校准 + UI 更新 |
|
||
|
||
### 5.2 新建模块
|
||
|
||
| 模块 | 文件 | 功能 |
|
||
|------|------|------|
|
||
| device_mode | `main/dzbj/device_mode.c/h` | NVS 模式读写 + 重启切换 |
|
||
|
||
### 5.3 修改的现有文件
|
||
|
||
| 文件 | 修改内容 |
|
||
|------|---------|
|
||
| `movecall_moji_esp32s3.cc` | 模式分支 + InitializeBadgeMode() + BOOT 双击回调 |
|
||
| `application.cc` | Start() 模式分支(吧唧模式早返回) |
|
||
| `main/CMakeLists.txt` | 添加新源文件 |
|
||
| `main/idf_component.yml` | 添加 esp_jpeg 依赖 |
|
||
| `main/sleep_mgr/include/sleep_mgr.h` | stub 改为真实函数声明 |
|
||
|
||
### 5.4 删除的文件
|
||
|
||
| 文件 | 原因 |
|
||
|------|------|
|
||
| `main/pages/pages_stub.c` | 被 `main/dzbj/pages.c` 真实实现替代 |
|
||
|
||
---
|
||
|
||
## 六、GPIO 引脚分配(已解决)
|
||
|
||
Phase 1 已完成的 GPIO 冲突解决:
|
||
|
||
| GPIO | 主项目原用途 | dzbj用途 | 解决方案 |
|
||
|------|------------|---------|---------|
|
||
| 21 | BUILTIN_LED | LCD D3 | LED 改为 GPIO_NUM_NC |
|
||
| 1 | Touch1 (电容触摸) | LCD 背光 EN | Touch1 改为 GPIO_NUM_NC |
|
||
| 7 | Touch4 (电容触摸) | LCD RST | Touch4 改为 GPIO_NUM_NC |
|
||
| 6 | Battery ADC | Touch RST | Battery ADC 改为 GPIO 3 |
|
||
| 17/18 | I2C_NUM_1 (音频) | I2C_NUM_0 (触摸) | 统一为 I2C_NUM_1 共享 |
|
||
|
||
---
|
||
|
||
## 七、风险评估
|
||
|
||
### 7.1 重启切换方案(已选定)
|
||
|
||
| 风险 | 等级 | 说明 |
|
||
|------|------|------|
|
||
| 内存泄漏 | **无** | 每次重启全新初始化,无残留 |
|
||
| 内存碎片化 | **无** | 重启清除所有堆分配 |
|
||
| WiFi/BLE deinit 不可靠 | **无** | 无需 deinit,重启自然释放 |
|
||
| NVS 擦写寿命 | **极低** | 10-100万次,日常使用完全足够 |
|
||
| 切换体验 | **低** | ~3-4秒重启时间,可加转场动画优化 |
|
||
|
||
### 7.2 其他风险
|
||
|
||
| 风险 | 等级 | 缓解方案 |
|
||
|------|------|---------|
|
||
| 符号冲突(pages_stub vs pages.c) | 中 | 删除 stub,真实实现始终编译 |
|
||
| button 模块冲突(C++ Button vs C ISR) | 中 | 条件初始化,两模式用不同实现 |
|
||
| SPIFFS 分区未配置 | 中 | 检查分区表是否有 spiffs 分区 |
|
||
| Flash 空间 | 低 | 当前 app 分区 5MB,固件 ~3.5MB,剩余充足 |
|
||
|
||
---
|
||
|
||
## 八、分区表
|
||
|
||
当前分区表:
|
||
|
||
```csv
|
||
# Name, Type, SubType, Offset, Size, Flags
|
||
nvs, data, nvs, 0x9000, 0x4000,
|
||
otadata, data, ota, 0xd000, 0x2000,
|
||
phy_init, data, phy, 0xf000, 0x1000,
|
||
model, data, spiffs, 0x10000, 0x300000,
|
||
ota_0, app, ota_0, 0x310000, 5M,
|
||
ota_1, app, ota_1, 0x820000, 5M,
|
||
```
|
||
|
||
dzbj 图片浏览功能需要 SPIFFS 存储。`model` 分区(3MB, spiffs 类型)可复用,或需新增 storage 分区。
|
||
|
||
---
|
||
|
||
## 九、验证计划
|
||
|
||
### 9.1 编译验证
|
||
```bash
|
||
idf.py build
|
||
```
|
||
|
||
### 9.2 AI 模式验证(默认 mode=0)
|
||
- [ ] 开机正常进入 WiFi 连接 + RTC 对话
|
||
- [ ] LVGL 显示 ScreenHome
|
||
- [ ] BOOT 单击切换对话状态
|
||
- [ ] BOOT 双击 → 切换到吧唧模式,设备重启
|
||
- [ ] 内存剩余 > 80KB
|
||
|
||
### 9.3 吧唧模式验证(mode=1)
|
||
- [ ] 开机日志显示"电子吧唧模式启动"
|
||
- [ ] 不连接 WiFi,不播放开机语音
|
||
- [ ] BLE 广播可见(手机搜索 "Airhub_XX:XX:XX")
|
||
- [ ] 手机 APP 可传输图片到设备
|
||
- [ ] 屏幕显示传输的图片
|
||
- [ ] 10s 无操作后屏幕熄灭
|
||
- [ ] 按键或触摸唤醒屏幕
|
||
- [ ] BOOT 双击 → 切换回 AI 模式,设备重启
|
||
- [ ] 内存剩余 > 150KB
|
||
|
||
### 9.4 稳定性验证
|
||
- [ ] 来回切换 10+ 次,功能正常
|
||
- [ ] 各模式下长时间运行(>1小时)无崩溃
|