ESP32-S3 StackChan 两天踩坑实录:从链接错误到运行时崩溃、字体方块与 I2S 误报
本文记录了将StackChan Avatar与小智(xiaozhi-esp32)整合到ESP32-S3平台时遇到的关键问题及解决方案。主要修复了编译链接错误(HAL未定义、抽象类实例化、资源嵌入问题)、运行时崩溃(指针未初始化)、音频日志噪声以及中文显示异常等问题。通过实现虚函数、初始化关键指针、同步主题字体等修复措施,最终实现了稳定的中文对话气泡显示功能。项目涉及I2C/I2S音频架构、LVGL
·
文章目录
-
- 1. 背景与工程结构
- 2. 问题时间线(按出现顺序)
-
- 2.1 链接错误:`GetHAL()` / `Hal::init()` undefined reference
- 2.2 编译错误:`StackChanAvatarDisplay` 是抽象类,无法 `new`
- 2.3 编译错误:`StackChan` 抽象类(缺纯虚函数实现)
- 2.4 编译错误:`DefaultAvatar` 未声明(命名空间/包含)
- 2.5 链接错误:`_binary_camera_shutter_ogg_start/end` 未定义
- 2.6 运行时崩溃:Guru Meditation `LoadStoreAlignment`(MCP ParseCapabilities)
- 2.7 音频日志:`i2s_channel_disable(): the channel has not been enabled yet`
- 2.8 对话气泡中文是“方块/乱码”
- 2.9 链接错误:`StackChanAvatarDisplay::SetStatus/SetEmotion/SetTheme/SetPreviewImage` undefined reference
- 3. 关键机制解释(把“为什么”讲清楚)
- 4. 最终效果(我们达成了什么)
- 5. 经验总结(适合写在博客结尾)
工程:
E:\ESP\StackChanSample(ESP-IDF 工程,ESP32-S3)
目标:把 StackChan Avatar + 小智(xiaozhi-esp32)整合跑通,修复编译/链接/运行时问题,并把对话气泡接到 Avatar 上显示中文。
1. 背景与工程结构
这个仓库本质是“主工程 + 引入 xiaozhi-esp32 的 main 目录代码 + StackChan 自己的 hal/stackchan/assets”等混合构建。
关键目录:
main/:本工程自己的组件(包含hal/、stackchan/、assets/、main.cpp等)xiaozhi-esp32/main/:小智的核心实现(显示、音频、MCP、资源系统等)managed_components/:组件依赖(如esp_video、esp_codec_dev等)
2. 问题时间线(按出现顺序)
2.1 链接错误:GetHAL() / Hal::init() undefined reference
现象
- 链接阶段报
undefined reference,典型原因是:实现文件存在,但没有被编译进当前组件/目标。
定位
GetHAL()定义在main/hal/hal.cpp,声明在main/hal/hal.h- 重点检查
main/CMakeLists.txt是否把main/hal/*.cpp收进idf_component_register(SRCS ...)
修复思路
- 修正
file(GLOB_RECURSE ...)的源文件收集方式,确保hal/*.c|cc|cpp被编译进 main 组件
2.2 编译错误:StackChanAvatarDisplay 是抽象类,无法 new
现象
new StackChanAvatarDisplay(...)报“抽象类无法实例化”。
根因
StackChanAvatarDisplay继承LvglDisplayLvglDisplay有纯虚:Lock()/Unlock()
不实现就会变抽象类
修复
- 在
main/hal/board/stackchan_display.h/.cc实现:Lock()→lvgl_port_lock(timeout)Unlock()→lvgl_port_unlock()
2.3 编译错误:StackChan 抽象类(缺纯虚函数实现)
现象
static stackchan::StackChan stackchan;报抽象类。
根因
Modifiable接口里有hasAvatar()等纯虚StackChan没实现就会抽象
修复
- 在
main/stackchan/stackchan.h补齐hasAvatar()(以及后续逐步恢复attachAvatar/avatar()/update()/modifier pool等能力)
2.4 编译错误:DefaultAvatar 未声明(命名空间/包含)
现象
DefaultAvatar找不到。
根因
- 类型实际在
main/stackchan/avatar/skins/default/default.h - 命名空间:
stackchan::avatar::DefaultAvatar
修复
#include <.../default.h>- 或使用
using namespace stackchan::avatar; - 或全限定名
stackchan::avatar::DefaultAvatar
2.5 链接错误:_binary_camera_shutter_ogg_start/end 未定义
现象
- 链接时报:
_binary_camera_shutter_ogg_start_binary_camera_shutter_ogg_end
根因
main/assets/assets.h里用asm("_binary_camera_shutter_ogg_start")声明了嵌入资源符号- 但
main/assets/sfx/camera_shutter.ogg没有加入idf_component_register(EMBED_FILES ...) - 所以不会生成对应的
*.ogg.S.obj
修复
- 在
main/CMakeLists.txt增加:file(GLOB STACKCHAN_SFX_SOUNDS ${CMAKE_CURRENT_SOURCE_DIR}/assets/sfx/*.ogg)EMBED_FILES ... ${STACKCHAN_SFX_SOUNDS}
2.6 运行时崩溃:Guru Meditation LoadStoreAlignment(MCP ParseCapabilities)
现象
- 崩溃点:
McpServer::ParseCapabilities()调用camera->SetExplainUrl(...) - EXCVADDR 是一个“诡异的非对齐地址”
根因(非常典型)
- 板级类里
camera_指针没初始化(垃圾值) GetCamera()返回了随机地址 → “看起来非空” → 立刻调用虚函数/成员 → 对齐异常崩溃
修复
- 在板级实现
main/hal/board/stackchansample.cc:- 把
camera_等指针成员默认初始化为nullptr - 若要启用相机,再显式调用
InitializeCamera()给camera_赋值
- 把
2.7 音频日志:i2s_channel_disable(): the channel has not been enabled yet
现象
- 串口反复出现 I2S “disable 未 enable 通道”的报错。
关键理解
- Audio 通常同时用两条总线:
- I2C:控制 codec 寄存器(音量/增益/模式)
- I2S:真正的 PCM 音频数据通道(播放/录音数据流)
- 这条报错多发生在“内部为了重配/关闭先 disable 一下”,但通道当时未处于 enable 状态,属于非致命的噪声日志。
处理方式(消噪)
- 在
esp_codec_dev的 I2S 适配层里(managed_components/espressif__esp_codec_dev/platform/audio_codec_data_i2s.c)
让 disable 变成幂等:如果当前方向本来就没 enable,就直接返回 OK,不再调用底层i2s_channel_disable()。
2.8 对话气泡中文是“方块/乱码”
现象
- 气泡里中文显示成方块(缺字形)。
根因
DefaultAvatar::init(..., font = &lv_font_montserrat_16)默认用的是 Montserrat(基本无中文字库)- 气泡 Label 在创建时是
setTextFont(font)固定死的,不会自动跟随主题字体
修复
- 正确做法:实现并接入
Display::SetTheme(Theme*)的重载,在主题切换/资源刷新时,把LvglTheme的text_font()同步到 StackChan Avatar 的气泡 Label(setSpeechTextFont()),这样才能彻底消除方块字并保证后续主题变更也正常生效。 - 仅在创建 Avatar 时手动传入某个内置字体(例如
BUILTIN_TEXT_FONT)只能作为“启动阶段兜底”,字符覆盖不一定完整,无法保证对话内容里所有汉字/符号都不变方块。
2.9 链接错误:StackChanAvatarDisplay::SetStatus/SetEmotion/SetTheme/SetPreviewImage undefined reference
现象
- 链接阶段 vtable 报未定义引用:
SetStatusSetEmotionSetThemeSetPreviewImage
根因
- 头文件里声明了
override,但.cc没有对应实现 → vtable 需要符号,链接失败。
修复
- 在
main/hal/board/stackchan_display.cc补齐这些成员函数实现:SetStatus():复用LvglDisplay::SetStatusSetEmotion():字符串 →stackchan::avatar::Emotion映射 →avatar.setEmotion(...)SetTheme():保存主题 + 把主题字体下发给气泡(setSpeechTextFont(...))SetPreviewImage():显示/隐藏预览图,配合定时器自动隐藏
3. 关键机制解释(把“为什么”讲清楚)
3.1 为什么 I2C + I2S 会同时存在?
- I2C:控制通道(写寄存器、改音量、开关输入/输出)
- I2S:数据通道(真正搬运音频采样)
- 所以“音频 codec 用 I2C”并不等于“不会出现 I2S API”,I2S API 反而是播放/录音的核心。
3.2 SetTheme() 到底在哪被调用?
两条主路径:
- MCP 工具调用
xiaozhi-esp32/main/mcp_server.cc注册self.screen.set_theme- 工具被调用时执行
display->SetTheme(theme)
- 资产/皮肤应用后刷新
xiaozhi-esp32/main/assets.cc应用皮肤后会display->SetTheme(current_theme)刷新显示
这也是为什么你写了 SetTheme() 后,气泡字体能跟随主题变正常:因为这两条路径会触发你同步更新气泡字体。
4. 最终效果(我们达成了什么)
- 编译/链接层面:修复多个 undefined reference(HAL、资源嵌入、vtable 虚函数)
- 运行时稳定性:修复 camera 指针未初始化导致的对齐崩溃
- UI 体验:对话气泡中文不再显示方块;主题切换时气泡字体能同步更新
- 日志质量:消除 I2S disable 的误报噪声(避免串口被刷屏)
5. 经验总结(适合写在博客结尾)
- ESP-IDF 链接错误优先查“源文件有没有进组件”,而不是怀疑函数没写
- C++ vtable 链接错误几乎都来自:声明了
virtual/override但漏实现 - 板级指针成员必须初始化(
nullptr),否则“看起来能跑”但必炸(而且炸得很诡异) - 字体方块 = 字库缺 glyph,LVGL 不会“自动造字”,需要显式选对字体
- 音频 I2C≠音频数据链路,数据流是 I2S;控制才是 I2C
更多推荐


所有评论(0)