Baji_Rtc_Toy/06-AI对话和电子吧唧双模式适配说明.md
Rdzleo 14776acb0a feat: 完成 AI/吧唧双模式完全隔离重构 + 触摸坐标日志 + SPIFFS 预烧录
## 核心变更

### 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>
2026-02-28 10:23:04 +08:00

292 lines
11 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.

# 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 配网BluedroidService 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 关闭**
- BLEGATT 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小时无崩溃