Baji_Rtc_Toy/06-AI对话和电子吧唧双模式适配说明.md
Rdzleo c24a9bc162 feat: 集成 dzbj LVGL 显示模块 + 配网模式内存优化
阶段1: 将 dzbj 项目的 LVGL 8.3.11 LCD 显示集成到 AI小智 主项目,
开机显示 ScreenHome 界面,同时优化配网模式下的内存使用,
确保 WiFi+BLE+LVGL 三者共存运行。

## 新增功能

### dzbj 显示模块集成
- 新增 main/dzbj/ 目录,移植 LCD 驱动(ST77916 QSPI)、触摸驱动(CST816S)、
  LVGL 初始化和 SquareLine Studio UI 界面
- I2C 总线共享:dzbj 触摸控制器复用主项目的 I2C_NUM_1 总线
- GPIO 冲突解决:LED(GPIO21)、Touch1(GPIO1)、Touch4(GPIO7) 改为 NC,
  电池 ADC 从 GPIO6 改为 GPIO3
- 添加 LVGL、esp_lcd_st77916、esp_lcd_touch_cst816s 等组件依赖
- managed_components 纳入版本管理

### 配网模式轻量化启动
- BoxAudioCodec: 新增 output_only 模式,仅创建 I2S TX 通道(省 ~13KB DMA)
  跳过 ES7210 ADC 初始化(省 ~2-4KB)
- AudioCodec: 新增 StartOutputOnly() 方法,仅启用扬声器输出
- Application: 配网模式跳过 Opus 编码器、输入重采样器、协议初始化、
  天气位置检测等网络业务
- 板级构造函数: 配网模式跳过电池检测、IMU传感器、PowerSaveTimer

### WifiBoard 配网流程修复
- NeedsProvisioning() 静态方法: 读取 NVS force_ap 和 SSID 列表,
  用于提前判断配网模式
- force_ap 竞态修复: 构造函数不再清零 force_ap,改在 StartNetwork() 清零,
  确保 NeedsProvisioning() 能正确读到 force_ap=1
- Application 缓存 provisioning_mode_ 成员变量,避免重复读 NVS

### BLE 配网重启修复
- 配网成功后用 esp_timer 延迟重启替代 xTaskCreate,
  避免内存紧张时任务创建失败导致设备不重启
- 注释掉 WiFi 连接成功后的 MAC 地址发送步骤

### sdkconfig 内存优化
- BT_ALLOCATION_FROM_SPIRAM_FIRST=y (BLE 动态分配优先 PSRAM)
- SPIRAM_MALLOC_RESERVE_INTERNAL=32768
- NVS_ALLOCATE_CACHE_IN_SPIRAM=y
- WiFi 静态缓冲区数量优化 (RX=10, TX=8)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 17:07:51 +08:00

395 lines
16 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 (dzbj项目)
---
## 一、项目现状
### 1.1 主项目 (Baji_Rtc_Toy)
基于 AI小智 开源项目改造,当前功能:
- 火山引擎 RTC 语音对话WiFi 连接)
- BLE 配网BluedroidService 0xABF0
- 音频编解码ES8311 + Opus
- 唤醒词检测esp-sr AFE
- **无 LCD 显示**`lcd_display.cc` 已注释managed_components 中无 LVGL
关键文件:
- `main/application.cc` — 应用主类,状态管理
- `main/boards/movecall-moji-esp32s3/` — 板级实现
- `main/boards/common/wifi_board.cc` — WiFi/BLE 网络管理
- `main/protocols/volc_rtc_protocol.cc` — 火山 RTC 协议
- `main/bluetooth_provisioning.cc` — BLE 配网
- `main/display/` — Display 抽象层(已预留 LCD 接口,未编译)
### 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 图片管理
- 低功耗休眠/唤醒管理
- PWM 背光控制、手电筒功能
关键文件:
- `dzbj/main/lcd/lcd.c` — ST77916 QSPI + CST816S 驱动
- `dzbj/main/ui/` — SquareLine Studio 生成的 UI 代码
- `dzbj/main/pages/pages.c` — 图片处理 + PWM 亮度
- `dzbj/main/ble/ble.c` — BLE 图片传输 GATT Server
- `dzbj/main/sleep_mgr/sleep_mgr.c` — 低功耗管理
### 1.3 AI小智原生 LVGL 版本
**AI小智原生项目不使用 LVGL 9.2.2**。实际情况:
| 项目 | LVGL 版本 | 状态 |
|------|-----------|------|
| 主项目 (Baji_Rtc_Toy) | **无 LVGL** | `lcd_display.cc` 已注释managed_components 中无 lvgl |
| dzbj 子项目 | **8.3.11** | 完整集成 LVGL + esp_lvgl_port 2.5.0 |
| AI小智 Display 框架 | **预留接口** | `Display` 基类已编译,方法为空操作(no-op) |
`main/CMakeLists.txt` 第11-12行明确注释了 LCD 支持:
```cmake
#"display/lcd_display.cc" # 移除LCD显示器支持
#"display/oled_display.cc" # 移除OLED显示器支持
```
AI小智框架的 `LcdDisplay` 类已预留 emoji 表情(21种)、聊天气泡(微信风格)、主题切换(深色/浅色) 功能,但当前未编译链接。
---
## 二、双模式架构设计
### 2.1 架构概览
```
┌─────────────────────────────────────────────────┐
│ LVGL 8.3.11 │
│ (常驻,两个模式共享) │
├────────────────────┬────────────────────────────┤
│ AI 聊天模式 │ 电子吧唧模式 │
│ │ │
│ WiFi + RTC 协议 │ BLE GATT Server │
│ emoji 表情显示 │ ScreenHome/Img/Set │
│ 聊天气泡文本 │ 图片浏览 + GIF │
│ 唤醒词检测 │ BLE 图片传输 │
│ 音频编解码 │ 手电筒/低功耗 │
├────────────────────┴────────────────────────────┤
│ 长按 BOOT 5秒 切换 │
│ AI→吧唧: 关WiFi+RTC → 启BLE → 切换UI │
│ 吧唧→AI: 关BLE → 启WiFi+RTC → 切换UI │
└─────────────────────────────────────────────────┘
```
### 2.2 模式定义
**AI 聊天模式**
- 网络WiFi 连接
- 协议:火山引擎 RTC 实时对话
- 音频:唤醒词检测 + Opus 编解码 + I2S 输出
- 显示emoji 表情 + 聊天气泡文本 + 状态栏
- BLE**关闭**
**电子吧唧模式**
- 网络:**WiFi 关闭**
- BLEGATT Server图片传输 + 配网服务)
- 显示ScreenHome主界面→ ScreenImg图片浏览→ ScreenSet设置
- 功能GIF 播放、JPEG 解码、SPIFFS 图片管理、手电筒、低功耗
---
## 三、内存预算分析(核心瓶颈)
### 3.1 硬件规格
- **内部 SRAM**~334KB DIRAM可用
- **PSRAM**8MB OCT-SPI 80MHz
- **Flash**16MB
### 3.2 常驻组件(两模式共享)
| 组件 | DIRAM 占用 | 说明 |
|------|-----------|------|
| LVGL 库 (.bss/.data) | ~34KB | 图形引擎核心 |
| FreeRTOS | ~22KB | 内核 + idle/timer 任务 |
| HAL + SPI Flash | ~30KB | 硬件抽象 + Flash 驱动 |
| Heap 管理器 | ~8KB | 内存分配器 |
| esp_system / esp_hw | ~18KB | 系统支持 |
| lwip 协议栈 | ~4KB | TCP/IP即使 WiFi 关闭也常驻) |
| 主应用 (main) | ~5KB | Application 框架 |
| LVGL 任务栈 | 8KB | LVGL 刷新任务 |
| **常驻小计** | **~129KB** | |
### 3.3 AI 聊天模式额外占用
| 组件 | DIRAM 占用 | 说明 |
|------|-----------|------|
| WiFi 驱动 (net80211+pp) | ~13KB 静态 + ~40-50KB 动态 | TX/RX 缓冲区 |
| RTC 协议 | ~5-10KB | 火山引擎连接 |
| 音频处理 | ~10-15KB | Opus编解码 + 管道 |
| 唤醒词 (esp-sr) | ~15-20KB | AFE + 模型加载 |
| **AI模式小计** | **~83-108KB** | |
| **AI模式总计** | **~212-237KB** | 常驻 + AI |
| **AI模式剩余** | **~97-122KB** | 用于堆分配 |
### 3.4 电子吧唧模式额外占用
| 组件 | DIRAM 占用 | 说明 |
|------|-----------|------|
| BLE Bluedroid 静态 | ~13KB | libbt.a 静态数据 |
| BLE 控制器 | ~15KB | 动态分配PSRAM优先后减少 |
| BLE 任务栈 | ~15KB | BTC(3KB) + BTU(4KB) + 控制器 |
| dzbj 业务任务 | ~18KB | GIF(4KB) + 按键(3KB) + 电池(4KB) + 睡眠(3KB) + BLE处理(8KB) |
| **吧唧模式小计** | **~61KB** | |
| **吧唧模式总计** | **~190KB** | 常驻 + 吧唧 |
| **吧唧模式剩余** | **~144KB** | 充裕 |
### 3.5 关键结论
| 场景 | 内存占用 | 剩余 | 可行性 |
|------|----------|------|--------|
| AI 聊天模式(单独) | ~212-237KB | ~97-122KB | **可行**(偏紧) |
| 电子吧唧模式(单独) | ~190KB | ~144KB | **可行**(充裕) |
| 两模式同时运行 | ~274-345KB | 不足 | **不可行** |
| 模式切换(互斥) | 单次一个模式 | 够用 | **可行,需验证** |
**验证数据**:之前测试中 WiFi + BLE 同时运行导致 `assert failed: vQueueDelete queue.c:2355`FreeRTOS 信号量分配失败),确认内部 SRAM 不足以支撑两者同时运行。
---
## 四、模式切换技术方案
### 4.1 AI → 电子吧唧 切换流程
```
用户长按 BOOT 5秒
├─ 1. 关闭 AI 资源
│ ├─ protocol_->CloseAudioChannel() // 关闭 RTC 音频通道
│ ├─ volc_rtc_stop() + volc_rtc_destroy() // 销毁 RTC 实例
│ ├─ StopAudioProcessor() // 停止音频处理器
│ ├─ 停止唤醒词检测
│ └─ esp_wifi_stop() + esp_wifi_deinit() // 完全释放 WiFi
│ → 释放 ~83-108KB 内部 SRAM
├─ 2. 启动吧唧资源
│ ├─ esp_bt_controller_init() // 初始化 BLE 控制器
│ ├─ esp_bluedroid_init() + enable() // 启动 Bluedroid
│ ├─ 注册 GATT Server图片传输服务
│ ├─ 启动 BLE 广播
│ └─ 启动 dzbj 业务任务(按键/电池/睡眠管理)
└─ 3. 切换界面
└─ lv_scr_load_anim(ui_ScreenHome, ...)
```
### 4.2 电子吧唧 → AI 切换流程
```
用户长按 BOOT 5秒
├─ 1. 关闭吧唧资源
│ ├─ esp_ble_gap_stop_advertising() // 停止广播
│ ├─ esp_ble_gatts_app_unregister() // 注销 GATT
│ ├─ esp_bluedroid_disable() + deinit() // 关闭 Bluedroid
│ ├─ esp_bt_controller_disable() + deinit() // 关闭控制器
│ └─ 停止 dzbj 业务任务
│ → 释放 ~43-61KB 内部 SRAM
├─ 2. 启动 AI 资源
│ ├─ esp_wifi_init() + esp_wifi_start() // 初始化 WiFi
│ ├─ WifiStation::Start() // 连接 WiFi
│ ├─ WaitForConnected() // 等待连接
│ ├─ 创建 VolcRtcProtocol 实例
│ ├─ protocol_->Start() // 启动 RTC 连接
│ └─ 启动音频处理器 + 唤醒词检测
└─ 3. 切换界面
└─ lv_scr_load_anim(ai_chat_screen, ...)
```
### 4.3 BOOT 按键检测逻辑
```
当前逻辑movecall_moji_esp32s3.cc:
- 单击: Idle↔Listening 切换
- 长按 5s仅配网模式: 生产测试
需要改为:
- 单击: 保持原逻辑AI模式下 Idle↔Listening吧唧模式下返回 ScreenHome
- 长按 5s: 模式切换AI ↔ 吧唧)
```
---
## 五、实施方案
### 5.1 改动清单
#### 第一步:硬件层(改动小)
| 文件 | 改动内容 | 改动程度 |
|------|----------|----------|
| `movecall-moji-esp32s3/config.h` | 添加 LCD/Touch GPIO 定义 | 小 |
| `movecall_moji_esp32s3.cc` | 初始化 LCD 驱动(参考 dzbj lcd.c | 中 |
**GPIO 冲突注意**
- movecall-moji `BUILTIN_LED_GPIO = GPIO_NUM_21` 与 dzbj LCD D3 (GPIO 21) 冲突
- 需要重新映射 LED 引脚或调整 LCD 引脚
- dzbj 触摸用独立 I2C 引脚(GPIO 5/6),与音频 ES8311 (GPIO 17/18) 不冲突
#### 第二步LVGL 集成(改动中等)
| 文件 | 改动内容 | 改动程度 |
|------|----------|----------|
| `main/idf_component.yml` | 添加 lvgl 8.3.11 + esp_lvgl_port 2.5.0 + esp_lcd_st77916 | 小 |
| `main/CMakeLists.txt` | 取消注释 lcd_display.cc添加 dzbj 模块源文件 | 中 |
| `main/ui/` | 从 dzbj 复制 SquareLine 生成的 UI 代码 | 复制 |
| `main/pages/` | 从 dzbj 复制页面管理模块 | 复制+小改 |
| `main/sleep_mgr/` | 从 dzbj 复制低功耗管理模块 | 复制+小改 |
#### 第三步AI 聊天 UI新开发
| 内容 | 说明 |
|------|------|
| 创建 AI 聊天屏幕 | 基于 LVGL 8.3.11,包含 emoji 显示区 + 聊天气泡容器 + 状态栏 |
| emoji 表情渲染 | 参考 `LcdDisplay::SetEmotion()` 实现21种表情映射 |
| 聊天气泡 | 参考 `LcdDisplay::SetChatMessage()` 实现微信风格气泡 |
| 对接 Application | SetEmotion/SetChatMessage 调用新 UI |
**注意**`main/display/lcd_display.h` 中使用了 `lv_draw_buf_t`LVGL 9.x 类型),需要适配为 8.3.11 的 `lv_disp_draw_buf_t`
#### 第四步:模式切换管理(核心改动)
| 文件 | 改动内容 | 改动程度 |
|------|----------|----------|
| 新增 `mode_manager.h/cc` | 双模式状态机 + WiFi/BLE init/deinit | 新建 |
| `movecall_moji_esp32s3.cc` | BOOT 长按 5s 检测 | 中 |
| `application.cc` | 添加模式切换回调 | 中 |
### 5.2 不需要大改动的模块
| 模块 | 改动程度 | 说明 |
|------|----------|------|
| dzbj UI 代码 (ui_ScreenHome/Img/Set) | **几乎不改** | 直接复制使用 |
| dzbj pages.c (GIF/JPEG/PWM) | **小改** | 适配新的 GPIO 定义 |
| dzbj sleep_mgr | **小改** | 与 AI小智的 PowerSaveTimer 整合 |
| dzbj ble.c (图片传输) | **不改** | 电子吧唧模式下直接使用 |
| bluetooth_provisioning.cc | **不改** | 配网逻辑保持不变 |
| VolcRtcProtocol | **不改** | AI模式下原样使用 |
---
## 六、风险评估
### 6.1 高风险
| 风险 | 影响 | 缓解方案 |
|------|------|----------|
| **WiFi deinit 内存泄漏** | 每次切换泄漏几KB多次切换后崩溃 | 实测 `esp_wifi_deinit()` 后用 `heap_caps_get_free_size()` 验证回收量 |
| **BLE deinit 内存泄漏** | Bluedroid 完全释放困难 | 考虑使用 NimBLE 替代 Bluedroid更轻量deinit 更可靠) |
| **内部 SRAM 碎片化** | 反复 init/deinit 导致碎片,大块分配失败 | 用 `heap_caps_get_largest_free_block()` 监控最大连续块 |
### 6.2 中等风险
| 风险 | 影响 | 缓解方案 |
|------|------|----------|
| **GPIO 引脚冲突** | LCD 引脚与现有外设冲突 (GPIO 21) | 仔细对照两个项目的 GPIO 分配表,重新映射 |
| **LVGL API 版本差异** | `lcd_display.h` 用了 `lv_draw_buf_t` (9.x) | 适配为 8.3.11 的 `lv_disp_draw_buf_t` |
| **Flash 空间** | 新增 LVGL(323KB) + emoji字体 + UI资源 | 当前 app 分区 5MB固件 ~3.5MB,剩余 ~1.5MB 充足 |
### 6.3 低风险
| 风险 | 影响 | 缓解方案 |
|------|------|----------|
| I2C 总线共享 | 音频 ES8311 (GPIO 17/18) vs 触摸 CST816S | dzbj 触摸用独立 I2C 引脚(GPIO 5/6),不冲突 |
| PSRAM 带宽 | LVGL DMA + 音频 + WiFi 并行 | AI模式无 GIF 播放PSRAM 带宽充足 |
| LVGL 界面切换 | 两套 UI 共存 | LVGL 对象可存放 PSRAM界面切换无需重启 LVGL |
---
## 七、NimBLE vs Bluedroid 选型建议
当前 dzbj 和配网都使用 Bluedroid但在双模式切换场景下 NimBLE 更优:
| 对比项 | Bluedroid | NimBLE |
|--------|-----------|--------|
| Flash 占用 | ~277KB (libbt+libbtdm) | ~120KB |
| 内部 SRAM | ~35-45KB 动态 | ~15-20KB 动态 |
| deinit 可靠性 | 一般(可能有泄漏) | 较好 |
| Classic BT | 支持 | 不支持(仅 BLE |
| GATT Server | 支持 | 支持 |
| 迁移工作量 | — | 中等API 不同,逻辑相同) |
**建议**:如果不需要经典蓝牙,优先考虑迁移到 NimBLE。节省 ~150KB Flash + ~20KB 内部 SRAM并且 deinit 更可靠。
---
## 八、分区表设计
当前分区表(已移除 storage
```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,
```
**建议**:如果需要 SPIFFS 存储图片dzbj 的图片浏览功能),需要重新添加 storage 分区,或复用 model 分区的一部分空间。
---
## 九、推荐实施路线
```
阶段 1: 点亮屏幕(基础验证) 预计改动量: 小
├─ 确认 LCD GPIO 映射(解决 GPIO 21 冲突)
├─ 在 main/idf_component.yml 添加 LVGL 8.3.11 依赖
├─ 在 movecall-moji 板上初始化 LCD + LVGL
├─ 显示 dzbj ScreenHome 界面
└─ 验证 LVGL + WiFi 内存占用(确认不冲突)
阶段 2: 电子吧唧模式完整复制 预计改动量: 中
├─ 复制 dzbj 的 UI/pages/ble/sleep_mgr 模块到主项目
├─ 关闭 WiFi 后启动 BLE + ScreenHome
├─ 验证 BLE 图片传输功能
└─ 验证低功耗管理
阶段 3: AI 聊天 UI 开发 预计改动量: 中
├─ 基于 LVGL 8.3.11 创建聊天屏幕emoji + 气泡)
├─ 对接 Application 的 SetEmotion/SetChatMessage
├─ 关闭 BLE 后启动 WiFi + RTC
└─ 验证语音对话 + 屏幕显示联动
阶段 4: 模式切换集成 预计改动量: 中
├─ 实现 BOOT 长按 5s 检测
├─ 实现 WiFi ↔ BLE 完整 init/deinit 切换
├─ 界面切换 + 资源释放验证
└─ 长时间稳定性测试(反复切换 100+ 次)
```
---
## 十、结论
| 问题 | 结论 |
|------|------|
| AI小智用 LVGL 9.2.2 | **不是**,当前项目无 LVGL框架预留了 LCD 接口 |
| 显示 emoji 需要 LVGL | **是**emoji 字体渲染和聊天气泡都依赖 LVGL |
| 保持 LVGL 8.3.11 | **可行**dzbj 代码直接复用 |
| 双模式切换可行? | **可行**,但需验证 WiFi/BLE deinit 的内存回收 |
| 内存够用? | **单模式够用**AI剩余~100KB吧唧剩余~144KB同时运行不够 |
| dzbj 代码大改? | **不需要**UI/pages/ble 模块几乎原样复制 |
| 最大技术风险? | **WiFi/BLE 反复 init/deinit 的内存泄漏和碎片化** |
**总体评估**:双模式互斥切换方案在技术上可行,资源预算满足单模式运行。最大不确定性在于 WiFi/BLE 的完整 deinit 是否能可靠回收内存需要实际编码验证。建议从阶段1点亮屏幕开始逐步推进。