## 核心变更 ### 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>
11 KiB
11 KiB
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万次,无限次切换。
选择重启而非热切换的原因:
- WiFi + BLE Bluedroid 同时运行内部 SRAM 不足(约需 280KB,可用 ~334KB)
- 热切换需处理大量资源释放/重建(协议、音频管道、FreeRTOS 任务),复杂度极高
- Application 单例内部状态(event_group, opus 编解码器, background_task)难以干净重置
- 重启方式简单可靠,避免内存泄漏和碎片化风险
三、内存预算分析
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,剩余充足 |
八、分区表
当前分区表:
# 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 编译验证
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小时)无崩溃