ESP32-C5圆屏音频共存的引脚分配如何验
ESP32-C5圆屏音频共存的引脚分配如何验
给一块带圆屏、扬声器、麦克风和电池的桌面设备分配 GPIO,最容易掉进一个陷阱:每个模块单独看都能工作,合在一起却出现黑屏、底噪、按键失灵,甚至刷写后无法启动。Moji 2.0 的硬件把这些接口集中到一块很小的双层板上,正好适合用来演示一套可复用的核验方法。
本文围绕同一版本的 ESP32-C5 工程,做三件事:从嘉立创 EDA 工程确认屏幕和主板的物理连接,再用固件配置反查 GPIO 是否冲突,最后把 QSPI、ES8311 音频和省电逻辑拆成可以单独检查的代码片段。重点是核对过程,文章没有把“网页上写着支持”当成实测结论。

图 1 项目实物总览,作为外观和器件布局参考
先把版本锁住
今天核对的工程是 Moji 2.0 小智AI桌面机器人,开源广场页面显示硬件许可为 CC BY-SA 4.0,更新时间为 2026-01-28。对应的嘉立创 EDA 工程在 专业版编辑器 中打开,工程树同时包含 Schematic_Moji2 和 PCB_Moji2。
这一步看似简单,却能避免一个常见错误:拿 1.0 版的 ESP32-S3 接口表去解释 2.0 版的 ESP32-C5。项目页面也明确写了 2.0 的屏幕改成 360×360、QSPI 插接,主控换成 ESP32-C5,外壳和 1.0 不通用。

图 2 装配结构图,先确认 FPC 插座、主板和屏幕板的相对位置
从物理连接反推引脚表
Moji 2.0 把显示、音频、按键、背光和指示灯放在同一颗主控上。先看引脚,再看初始化代码,顺序不能反过来。下面是从工程配置中整理出的最小核对表。
| 功能 | 引脚 | 代码中的名字 | 核对要点 |
|---|---|---|---|
| QSPI 时钟 | GPIO0 | DISPLAY_QSPI_SCLK_PIN |
与复位脚、片选脚分开 |
| QSPI 复位 | GPIO1 | DISPLAY_QSPI_RESET_PIN |
上电时序必须先于面板初始化 |
| QSPI 数据 | GPIO6~9 | D3/D2/D1/D0 |
四根数据线不能与音频 I2S 复用 |
| QSPI 片选 | GPIO3 | DISPLAY_QSPI_CS_PIN |
只由屏幕总线使用 |
| 背光 PWM | GPIO2 | DISPLAY_BACKLIGHT_PIN |
不要误当作屏幕数据线 |
| ES8311 I2C | GPIO26/27 | SDA/SCL |
配置中打开内部上拉,仍需核对板上电阻 |
| ES8311 I2S | GPIO11/12/23/24/25 | BCLK/DIN/DOUT/WS/MCLK |
MCLK、BCLK、WS、数据方向要一一对应 |
| 按键 | GPIO28 | BOOT_BUTTON_GPIO |
启动阶段和对话阶段含义不同 |

图 3 主板 PCB 资料图。文章将其与 EDA 工程的 PCB 图层一起核对,不能把资料图当成新版改板的实测照片
下面这段代码保留了项目中的命名方式,但只摘出与核对有关的定义。它的价值不在于复制粘贴,而在于让表格和后续初始化拥有同一份“事实源”。
#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_25
#define AUDIO_I2S_GPIO_WS GPIO_NUM_24
#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_11
#define AUDIO_I2S_GPIO_DIN GPIO_NUM_12
#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_23
#define AUDIO_CODEC_PA_PIN GPIO_NUM_5
#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_26
#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_27
#define BOOT_BUTTON_GPIO GPIO_NUM_28
#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_2
#define DISPLAY_QSPI_SCLK_PIN GPIO_NUM_0
#define DISPLAY_QSPI_RESET_PIN GPIO_NUM_1
#define DISPLAY_QSPI_D0_PIN GPIO_NUM_9
#define DISPLAY_QSPI_D1_PIN GPIO_NUM_8
#define DISPLAY_QSPI_D2_PIN GPIO_NUM_7
#define DISPLAY_QSPI_D3_PIN GPIO_NUM_6
#define DISPLAY_QSPI_CS_PIN GPIO_NUM_3
QSPI 显示链路要检查什么
页面介绍中把屏幕写成“360×360、QSPI、ST77916”。真正落到 ESP-IDF 时,至少要检查三个边界:SPI 总线初始化使用的 host 是否一致,四根数据线是否按 D0~D3 传入,单次 DMA 传输大小是否覆盖一行像素。
项目代码中用 DISPLAY_QSPI_H_RES * 80 * sizeof(uint16_t) 作为 max_transfer_sz。按 360 像素宽、16 位颜色计算,一次缓冲上限是 360×80×2 = 57,600 字节,能够覆盖 80 行的 RGB565 数据。这个数值不是“越大越好”:盲目放大会增加 DMA 内存压力,过小又会让刷新被切碎。

图 4 同版本 EDA 工程预览。原理图和 PCB 顶底层均在嘉立创 EDA 专业版工程中重新核对
对应的初始化骨架如下:
static void InitializeSpi() {
const spi_bus_config_t bus_config =
MOJI2_ST77916_PANEL_BUS_QSPI_CONFIG(
DISPLAY_QSPI_SCLK_PIN, DISPLAY_QSPI_D0_PIN,
DISPLAY_QSPI_D1_PIN, DISPLAY_QSPI_D2_PIN,
DISPLAY_QSPI_D3_PIN,
DISPLAY_QSPI_H_RES * 80 * sizeof(uint16_t));
ESP_ERROR_CHECK(spi_bus_initialize(
DISPLAY_QSPI_HOST, &bus_config, SPI_DMA_CH_AUTO));
}
static void InitializeSt77916Display() {
esp_lcd_panel_io_handle_t panel_io = nullptr;
esp_lcd_panel_handle_t panel = nullptr;
esp_lcd_panel_io_spi_config_t io_config =
ST77916_PANEL_IO_QSPI_CONFIG(DISPLAY_QSPI_CS_PIN, NULL, NULL);
ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(
(esp_lcd_spi_bus_handle_t)DISPLAY_QSPI_HOST,
&io_config, &panel_io));
}
这里有一个容易被忽略的细节:代码把 io_config.pclk_hz 留在默认值,而不是直接套用 40 MHz 宏。文章只记录源码事实,不把宏名当作实测时钟;如果要提高刷新率,应在目标板上用逻辑分析仪确认时序,再决定是否启用该配置。
ES8311 音频和显示为什么要分开验
音频链路有两套接口:I2C 用于寄存器配置,I2S 用于音频采样。显示屏走 QSPI,理论上互不占线,但它们共享电源、地和高频边沿。排查“有画面但有底噪”时,先把问题分成两层:
- GPIO 和外设驱动是否接对;
- PCB 回流、功放使能和供电去耦是否满足。
只改软件初始化不能替代第二层验证。项目的 EDA 图中可以看到主板和屏幕板的分工,屏幕板只承担显示连接,音频器件和主控集中在主板。

图 5 屏幕板 PCB 资料图,用于核对 FPC 和 QSPI 相关连接
省电逻辑的正确边界
项目固件把省电定时器和充电状态绑定:未充电时允许计时进入息屏,充电时关闭省电计时。这个策略比简单地“无操作就关屏”更安全,因为充电过程需要持续观察状态。
void InitializePowerSaveTimer() {
power_save_timer_ = new PowerSaveTimer(240, -1, -1);
power_save_timer_->OnEnterSleepMode([this]() {
GetDisplay()->SetPowerSaveMode(true);
});
power_save_timer_->OnExitSleepMode([this]() {
GetDisplay()->SetPowerSaveMode(false);
});
power_save_timer_->SetEnabled(true);
}
void InitializeBatteryMonitor() {
adc_battery_monitor_ =
new AdcBatteryMonitor(ADC_UNIT_1, ADC_CHANNEL_3,
5100000, 5100000, GPIO_NUM_NC);
adc_battery_monitor_->OnChargingStatusChanged(
[this](bool is_charging) {
if (power_save_timer_ != nullptr)
power_save_timer_->SetEnabled(!is_charging);
});
}
这段逻辑可以迁移到其他电池设备,但不能直接推导出续航时间。续航还取决于射频占空比、屏幕亮度、音频功放和电池内阻;本次没有目标板实测,文章不做续航承诺。

图 6 实物正面和 PCB 顶层的对应关系,便于装配后检查器件方向

图 7 PCB 顶层资料图

图 8 PCB 底层资料图。顶底层必须和同一 EDA 版本对应,不能混用旧版截图
我做了哪些可复验检查
这次没有把目标板“已经能跑”写成结论,而是把能在当前环境复验的部分单独列出:
- 从 EDA 工程树确认存在
Schematic_Moji2和PCB_Moji2,并在编辑器中查看工程预览、主板和屏幕板资料。 - 从
config.h逐项解析 GPIO 宏,检查 QSPI、I2C、I2S、按键和背光的编号没有重复。 - 从 README 和
config.json核对构建目标为 ESP32-C5、ESP-IDF v5.5、16 MB Flash 和 80 MHz PSRAM 配置。 - 用主机脚本验证 57,600 字节 DMA 行缓存计算和省电逻辑的状态转换。
没有完成的部分也必须说清:当前环境没有 Moji 2.0 实物、逻辑分析仪和声学测试设备,因此没有宣称 QSPI 波形、音频底噪、温升、续航或整机语音效果已经实测。需要复刻时,建议先按引脚表做 ERC/DRC,再用低亮度、无功放输出的顺序逐步上电。
复刻时的检查顺序
- 先在 EDA 中确认工程版本和 PCB 厚度,核对 FPC 插座方向。
- 不接电池,先用限流电源确认 3.3 V、背光和 QSPI 复位脚没有短路。
- 只烧录最小显示测试,确认 QSPI 四线和屏幕初始化。
- 再接 ES8311,先验证 I2C 应答,再打开 I2S 录放音。
- 最后启用省电和联网功能,观察充电状态切换时是否误入息屏。
资料与许可
本文基于 OSHWHub Moji 2.0 工程 和对应 嘉立创 EDA 工程 独立核验编写。硬件页面标注 CC BY-SA 4.0;固件片段来自 MoveCall/xiaozhi-esp32,仓库许可证为 MIT,引用代码保留原版权与许可要求。本文图片仅用于技术说明,图注已区分项目资料图与 EDA 工程预览;本次改动、代码整理和验证范围以文中说明为准。
技术交流,欢迎关注微信公众号:美男子玩编程。



