ES7111 Codec 集成完整流程总结
基于 yopen 嵌入式音频框架,集成 ES7111 纯 I2S DAC 的完整步骤、踩坑点和原理解析
硬件:ES7111 (SBCLK=30, LRCK=31, SDATA=33, PA=22)
固件:build.bat YM310_W09C.A60 es7111_mp3✅ 编译通过
一、整体流程概览
步骤 1: 新增板型宏 BOARD_ES7111
├── yopen_config.h.in # 注册宏到 cmake 变量映射
├── audio_config.h # 配置 codec 类型 / 引脚
└── audio_config.c # 新增 codec 初始化 case
步骤 2: 创建构建配置 es7111_mp3.cmake
├── 设置 BOARD_ES7111
├── 指定源文件
└── 指定依赖模块
步骤 3: 创建应用 demo_es7111_mp3.c
├── 定义 MP3 数据源(复用 demo_mp3_buf.c)
└── 配置播放链路(reader → decoder → player)
步骤 4: 编译验证 & 调试
├── 第一次编译失败 → 修复宏传递问题
├── 第二次编译成功
└── 调整 MCLK/LRCK 比率(frame_size)
步骤 5: 原理理解
├── MCLK 频率来源
├── frame_size 参数详解
└── AUDIO_CODEC_I2S 的特殊性
二、步骤 1:新增板型宏 BOARD_ES7111
1.1 注册宏到 cmake 变量映射
文件:[yopen_config.h.in](file:///g:/x09/yopen/project/config/yopen_config.h.in#L47)
这是板型宏系统的核心机制:顶层 CMakeLists.txt 通过 configure_file 把 .in 中的 #cmakedefine 转换成 #define。
// 在 #cmakedefine BOARD_W09_PWM 之后新增:
#cmakedefine BOARD_ES7111
⚠️ 关键原理:
set(BOARD_ES7111 TRUE)只是一个 CMake 变量,#cmakedefine才把它变成预处理器宏-DBOARD_ES7111。少了这步,audio_config.h里的#if defined(BOARD_ES7111)永远不会命中。
1.2 配置 codec 类型和引脚
文件:[audio_config.h](file:///g:/x09/yopen/components/audio/include/audio_config.h)
参考硬件手册:

① Codec 类型选择(第 38 行附近)
#elif defined (BOARD_AI_CORE_V1_3) || defined (BOARD_W09_EVB_V1_0)
#define AUDIO_CODEC_TYPE AUDIO_CODEC_GX8006
#elif defined (BOARD_ES7111)
// ES7111 纯I2S DAC,仅需I2S控制,无需I2C配置寄存器
#define AUDIO_CODEC_TYPE AUDIO_CODEC_I2S // ← 复用现有的 I2S codec 类型
#elif defined (BOARD_QOSA)
选择 AUDIO_CODEC_I2S 的原因:ES7111 没有 I2C 寄存器,只是一个纯 I2S DAC,不需要 codec 驱动初始化逻辑,正好匹配 AUDIO_CODEC_I2S 的语义。
② PA 引脚配置(第 112 行附近)
#elif defined (BOARD_TM8211) || defined (BOARD_CJC4344)
#define BOARD_PA_SUPPORT
#define YOPEN_PIN_PA 63
#define YOPEN_PIN_PA_FUNC 0
#define YOPEN_PIN_PA_GPIO 1
#elif defined (BOARD_ES7111)
#define BOARD_PA_SUPPORT
#define YOPEN_PIN_PA 22 // ← 功放使能脚
#define YOPEN_PIN_PA_FUNC 4
#define YOPEN_PIN_PA_GPIO 19
#else
#undef BOARD_PA_SUPPORT
#endif
③ I2S 引脚配置(第 143 行附近)
#elif defined (BOARD_ES7111)
// ES7111 纯I2S DAC: SCLK(BCLK)=30, LRCK=31, SDATA(DOUT)=33
// ES7111 用 SCLK 作内部 MCLK,无需独立 MCLK 引脚;纯 DAC 无需 DIN
#define YOPEN_PIN_I2S_MCLK -1 // 无效(无独立 MCLK 引脚)
#define YOPEN_PIN_I2S_MCLK_FUNC 0
#define YOPEN_PIN_I2S_BCLK 30 // 串行位时钟 (SCLK)
#define YOPEN_PIN_I2S_BCLK_FUNC 1
#define YOPEN_PIN_I2S_LRCK 31 // 左右声道帧时钟
#define YOPEN_PIN_I2S_LRCK_FUNC 1
#define YOPEN_PIN_I2S_DIN -1 // 无效(纯 DAC,无录音输入)
#define YOPEN_PIN_I2S_DIN_FUNC 0
#define YOPEN_PIN_I2S_DOUT 33 // 主控→DAC 数据输出 (SDATA)
#define YOPEN_PIN_I2S_DOUT_FUNC 1
#else

引脚对照表:
| ES7111 引脚名 | MCU 引脚号 | FUNC 复用 | 说明 |
|---|---|---|---|
| SCLK (BCLK) | 30 | 1 | 串行位时钟(也是 ES7111 的内部 MCLK) |
| LRCK | 31 | 1 | 左右声道帧时钟 |
| SDIN (DOUT) | 33 | 1 | MCU→DAC 数据输出 |
| PA (功放) | 22 | 4 (GPIO 19) | 功放使能控制 |
| MCLK | -1 | — | 无效(ES7111 无此引脚) |
| DIN | -1 | — | 无效(纯 DAC 不需要) |
1.3 新增 codec 初始化分支
文件:[audio_config.c:131-136](file:///g:/x09/yopen/components/audio/src/audio_config.c#L131-L136)
case AUDIO_CODEC_I2S:
s_audConfigParam.type = AUDCONFIG_CODEC_I2S;
// 纯I2S: 复用ES8311的I2S参数
SET_I2S_PARAM(s_audConfigParam.codec.es8311_param.i2s_param, YOPEN_CODEC_I2S_PORT);
// ⚠️ ES7111 特有:MCLK/LRCK 比率调整(详见步骤 5)
s_audConfigParam.codec.es8311_param.i2s_param.i2s_config.frame_size
= YOPEN_FRAME_SIZE_16_32;
break;
为什么复用
es8311_param?AUDIO_CODEC_I2S类型没有独立的参数结构体,它复用了es8311_param中的 I2S 参数部分(忽略 I2C 参数)。这是框架设计者的设计——纯 I2S 设备共用一套参数结构。
三、步骤 2:创建构建配置
文件:[es7111_mp3.cmake](file:///g:/x09/yopen/project/es7111_mp3.cmake)
set(PRODUCT_NAME ES7111_MP3_DEMO)
add_compile_definitions(SW_VER="${PRODUCT_NAME}_${BUILD_TIMESTAMP}")
set(YOPEN_PROJECT_OUT_FILE ${PRODUCT_NAME}.bin)
# ① 激活板型宏
set(BOARD_ES7111 TRUE)
# ② 编译源文件
set(DEMO_COMPONENT_SRCS
${YOPEN_TOP_DIR}/components/demo/demo_es7111_mp3.c
${YOPEN_TOP_DIR}/components/demo/demo_mp3_buf.c)
# ③ 依赖模块
set(DEMO_COMPONENT_DEPS
components/pipe
components/audio)
# ④ MP3 解码需要 libmad
if(YOPEN_BUILD_BASE)
list(APPEND DEMO_COMPONENT_DEPS components/libmad)
endif()
foreach(SUBDIR ${DEMO_COMPONENT_DEPS})
add_subdirectory(${SUBDIR})
endforeach()
add_subdirectory(bsp/${YOPEN_BSP})
add_subdirectory(components/demo)
关键配置说明:
| 配置项 | 作用 | 参考来源 |
|---|---|---|
set(BOARD_ES7111 TRUE) | 设置 cmake 变量,配合 yopen_config.h.in 生成 -DBOARD_ES7111 编译宏 | [player.cmake](file:///g:/x09/yopen/project/player.cmake) |
demo_mp3_buf.c | MP3 音频数据数组(player_buf[]),以 C 数组形式内置 | 现有 demo,可直接复用 |
components/libmad | MP3 解码器(libmad) | player demo 也依赖此库 |
四、步骤 3:创建应用 Demo
文件:[demo_es7111_mp3.c](file:///g:/x09/yopen/components/demo/demo_es7111_mp3.c)
4.1 架构设计
┌──────────────────────────────────────────────────────────────┐
│ demo_es7111_mp3.c │
│ │
│ 数据源层 (auReader) │
│ ├── mp3ReaderOpen() 打开 reader,重置索引 │
│ ├── mp3ReaderRead() 从 player_buf[] 读取 MP3 数据 │
│ ├── mp3ReaderIsEof() 检查是否读完 │
│ └── mp3ReaderClose() 关闭 reader │
│ │
│ 播放层 │
│ ├── auPlayerCreate() 创建播放器 │
│ ├── auDecoderCreate() 创建 MP3 解码器 (libmad) │
│ ├── auPlayerStart() 启动播放 → 内部调用 audevStartPlay│
│ └── auPlayerWaitFinish() 等待播放完成 │
│ │
│ 硬件层 (自动处理) │
│ ├── audioDevOpen() 配置 I2S 引脚 + 采样率 │
│ ├── yopen_i2s_open() 初始化 I2S 控制器 │
│ └── 播放线程 PCM → I2S → ES7111 → PA → 喇叭 │
└──────────────────────────────────────────────────────────────┘
4.2 核心代码
/* MP3 数据来自 demo_mp3_buf.c */
extern const unsigned char player_buf[];
extern uint32_t player_buf_len;
static uint32_t s_mp3_index;
/* --- auReader 回调实现 --- */
static int mp3ReaderRead(struct auReader *d, void *buf, unsigned size)
{
uint32_t left;
if (s_mp3_index + size <= player_buf_len)
left = size;
else
left = player_buf_len - s_mp3_index;
if (left == 0) return 0;
memcpy(buf, player_buf + s_mp3_index, left);
s_mp3_index += left;
return (int)left;
}
/* --- 主播放任务 --- */
void es7111_mp3_task(void *param)
{
auReader_t reader = {
.ops = {
.open = mp3ReaderOpen,
.close = mp3ReaderClose,
.read = mp3ReaderRead,
.is_eof = mp3ReaderIsEof
},
.context = NULL
};
yopen_aon_power_on();
while (1) {
player = auPlayerCreate();
decoder = auDecoderCreate(&reader, AUSTREAM_FORMAT_MP3);
if (auPlayerStart(player, decoder)) {
auPlayerWaitFinish(player, YOPEN_WAIT_FOREVER);
}
auPlayerDelete(player);
auDecoderDelete(decoder);
yopen_rtos_task_sleep_ms(3000); // 间隔重播
}
}
application_init(es7111_mp3_task, "DEMO.ES7111MP3", 8, 4, NULL);
application_init 参数:
| 参数 | 值 | 含义 |
|---|---|---|
| 第 1 个 | es7111_mp3_task | 任务入口函数 |
| 第 2 个 | "DEMO.ES7111MP3" | 任务名称 |
| 第 3 个 | 8 | 栈大小(8KB) |
| 第 4 个 | 4 | 启动优先级 |
| 第 5 个 | NULL | 参数(无) |
五、步骤 4:编译与踩坑
5.1 第一次编译:宏未传递导致失败
报错信息:
audio_config.h:42: error: #error "AUDIO_CODEC_TYPE UNDEFINE"
根因:板型宏传递机制需要两步,只做了第一步:
① es7111_mp3.cmake: set(BOARD_ES7111 TRUE) ← CMake 变量(已做)
② yopen_config.h.in: #cmakedefine BOARD_ES7111 ← 转成编译宏(❌ 漏了)
修复:在 [yopen_config.h.in:47](file:///g:/x09/yopen/project/config/yopen_config.h.in#L47) 新增:
#cmakedefine BOARD_ES7111
5.2 第二次编译:成功 ✅
[14/16] Linking C static library lib\libcomponents.audio.a
OBJCOPY ... ap_yopen_app.bin
Image file: ... ap_yopen_app.bin, real image size: 1774020
build successfully
六、步骤 5:MCLK 频率与 MCLK/LRCK 比率
6.1 ES7111 没有独立 MCLK
ES7111 数据手册明确:SCLK 用作内部 MCLK(“SCLK is used as MCLK”)。同时 yopen_i2s_config_s 结构体([yopen_i2s.h:98-106](file:///g:/x09/yopen/api/yopen_i2s.h#L98-L106))也没有 MCLK 字段。
所以 ES7111 的 MCLK = BCLK,频率和比率由 I2S 帧格式决定:
MCLK (BCLK) 频率 = 采样率 × SlotSize × 声道数
MCLK/LRCK 比率 = SlotSize × 声道数
6.2 frame_size 参数详解
yopen_i2s_frame_size_e([yopen_i2s.h:79-84](file:///g:/x09/yopen/api/yopen_i2s.h#L79-L84))决定了两个关键参数:
| frame_size | WordSize(数据位深) | SlotSize(槽位时钟) | BCLK/LRCK(单声道) |
|---|---|---|---|
YOPEN_FRAME_SIZE_16_16 | 16 bit | 16 bit | 16 |
YOPEN_FRAME_SIZE_16_32 | 16 bit | 32 bit | 32 |
YOPEN_FRAME_SIZE_24_32 | 24 bit | 32 bit | 32 |
YOPEN_FRAME_SIZE_32_32 | 32 bit | 32 bit | 32 |
- WordSize = 实际数据位宽,必须匹配 PCM 数据
- SlotSize = 每个槽位的 BCLK 周期数,可以大于 WordSize
6.3 ES7111 对 MCLK/LRCK 的要求
ES7111 数据手册 Table 1 规定:
| 采样率 | 支持的 MCLK/LRCK 比率 |
|---|---|
| 8k–32k | 32, 48, 64, 96, 100, 125, 128, 192, 250, 256, 320, 384, 400, 500, 512, 640, 750, 768, 1024, 1152, 1280, 1536, 2048, 2304, 3072 |
| 44.1k–48k | 32, 64, 125, 128, 192, 250, 256, 320, 384, 500, 512, 640, 750, 768, 1024 |
最低要求:MCLK/LRCK ≥ 32。
6.4 为什么不能用 YOPEN_FRAME_SIZE_32_32
系统数据流恒为 16bit([audio_types.h:97](file:///g:/x09/yopen/components/audio/include/audio_types.h#L97)):
#define AUFRAME_BIT_PER_SAMPLE(frame) (16)
如果用 32_32,I2S 控制器按 WordSize=32bit 工作,每采样读取 4 字节,但缓冲区每采样只有 2 字节,导致:
- 播放速度变 2 倍
- 音调升高一个八度
- 高低字节错位产生噪声
正确选择:YOPEN_FRAME_SIZE_16_32
| 维度 | 16_32 的值 | 说明 |
|---|---|---|
| WordSize | 16bit | ✅ 匹配 PCM 数据位深 |
| SlotSize | 32bit | ✅ 使 MCLK/LRCK = 32,满足 ES7111 |
16bit 数据放在 32bit 槽的低 16bit,高 16bit 由 I2S 控制器自动填 0。
6.5 修改位置:local(改 case)vs global(改宏)
SET_I2S_PARAM 宏被 4 个 codec 共用(ES8311/TM8211/GX8006/I2S),frame_size 的修改有两种方式:
| 方式 | 修改位置 | 影响范围 | 推荐 |
|---|---|---|---|
| 改宏 | [audio_config.c:29](file:///g:/x09/yopen/components/audio/src/audio_config.c#L29) | 所有 codec 都变 16_32 | ❌ |
| 改 case | [audio_config.c:135](file:///g:/x09/yopen/components/audio/src/audio_config.c#L135) 之后 | 仅 ES7111 变 16_32 | ✅ |
推荐 local 修改的原因:
- ES8311/GX8006 有独立 MCLK,16_16 工作正常,不需要改
- 改宏会改变所有 codec 的 BCLK 频率,可能引入隐患
- 源码清晰,一眼可见这是 ES7111 的特殊需求
6.6 最终 MCLK 频率计算(48k 采样率)
MCLK/LRCK = SlotSize(32) × 声道(1) = 32 ✅ 满足 ES7111 要求
MCLK 频率 = 48000 × 32 × 1 = 1,536,000 Hz = 1.536 MHz
💡 这正好对应 ES7111 数据手册中 48k 采样率支持的第一个比率:32 Fs = 1.536 MHz
七、完整改动文件清单
| 文件 | 操作 | 作用 |
|---|---|---|
| [yopen_config.h.in](file:///g:/x09/yopen/project/config/yopen_config.h.in#L47) | 新增 #cmakedefine BOARD_ES7111 | 板型宏注册 |
| [audio_config.h](file:///g:/x09/yopen/components/audio/include/audio_config.h) | 3 处编辑:codec 类型 + PA 引脚 + I2S 引脚 | 硬件配置 |
| [audio_config.c](file:///g:/x09/yopen/components/audio/src/audio_config.c#L131-L137) | 新增 AUDIO_CODEC_I2S case + frame_size 覆盖 | codec 初始化 |
| [demo_es7111_mp3.c](file:///g:/x09/yopen/components/demo/demo_es7111_mp3.c) | 新建 | MP3 播放 demo |
| [es7111_mp3.cmake](file:///g:/x09/yopen/project/es7111_mp3.cmake) | 新建 | 构建配置 |
八、经验总结与注意事项
8.1 板型宏传递机制
cmake 变量 预处理器宏 编译命令行
─────────────────────────────────────────────────────────────────────────
set(BOARD_ES7111 TRUE) → #cmakedefine BOARD_ES7111 → -DBOARD_ES7111
(es7111_mp3.cmake) (yopen_config.h.in) (auto by cmake)
两处必须同时有,缺一不可。参考现有板型(BOARD_W09_EVB_V1_0 等)的配置方式。
8.2 ES7111 vs ES8311 的核心区别
| 方面 | ES8311 | ES7111 |
|---|---|---|
| 类型 | Codec(ADC+DAC) | 纯 DAC |
| 控制 | I2C 配置寄存器 | 无寄存器(上电即工作) |
| MCLK | 独立引脚(可配分频) | 复用 SCLK |
| 录音 | ✅ 支持 | ❌ 不支持 |
| Codec 类型 | AUDIO_CODEC_ES8311 | AUDIO_CODEC_I2S |
8.3 AUDIO_CODEC_I2S 的特殊性
AUDIO_CODEC_I2S 类型在 [audio_device.c:635-638](file:///g:/x09/yopen/components/audio/src/audio_device.c#L635-L638) 中走 else 分支:
- 跳过 codec 初始化(无 I2C 配置)
- 跳过 codec 关闭
- 直接由主控 I2S 输出数据
这正好适合 ES7111(无寄存器的纯 I2S DAC)。
8.4 数据流完整链路
player_buf[] (MP3 C 数组)
│
▼ auDecoderCreate(AUSTREAM_FORMAT_MP3)
libmad MP3 解码 → S16 PCM 数据
│
▼ auPlayerStart(player, decoder)
│ 内部:构造 audevPlayOps_t 回调
│
▼ audevStartPlay(ops, ctx, frame)
├── audioDevOpen(frame, TRUE)
│ ├── audConfigParamGetI2sConfig() → 获取 BOARD_ES7111 的引脚/格式配置
│ ├── audioDevsampleRateConvert() → 采样率转枚举
│ └── yopen_i2s_open(YOPEN_I2S_PORT_0, config)
│ ├── 配置 BCLK/LRCK/DOUT 引脚 (30/31/33)
│ ├── 设置 16_32 frame format (MCLK/LRCK=32)
│ └── 启动 I2S 控制器
│
▼ 播放线程 audioPlayerPlayWork()
├── ops->get_data() → 从 decoder 取 PCM
├── yopen_i2s_write() → PCM → I2S
│ └── BCLK=1.536MHz, LRCK=48kHz, DOUT=16bit
│
▼ ES7111 芯片
├── 自动检测 MCLK/LRCK=32 ✅
├── 数模转换
└── 差分输出 OUTP/OUTN
│
▼ PA 功放 (PA=22 脚, 自动控制)
│
▼ 喇叭 🔊
九、关键问答深度解析(09:38)
以下对话记录了集成过程中关于 MCLK、frame_size、WordSize vs SlotSize 的深度探讨,帮助理解"I2S 帧格式"这个核心概念。
Q1:这个 demo 的 MCLK 频率和 MCLK/LRCK 比率在哪里设置?
核心结论:ES7111 没有独立的 MCLK 配置。
ES7111 用 AUDIO_CODEC_I2S 类型,不走 ES8311 的 coeffDiv 表,因为:
- MCLK 引脚被禁用:
YOPEN_PIN_I2S_MCLK = -1,主控不输出 MCLK - ES7111 用 BCLK 作内部 MCLK(数据手册:“SCLK is used as MCLK”)
- I2S 配置结构体没有 MCLK 字段:
yopen_i2s_config_s只有 mode/role/sample_rate/frame_size/polarity/channel_sel
所以 MCLK 频率 = BCLK 频率,MCLK/LRCK 比率 = BCLK/LRCK 比率,由 SET_I2S_PARAM 宏决定:
// audio_config.c:19-30
#define SET_I2S_PARAM(i2s, port_num) do { \
...
(i2s).i2s_config.frame_size = YOPEN_FRAME_SIZE_16_16; // ← 决定比率
(i2s).i2s_config.channel_sel = YOPEN_MONO; // ← 决定比率
计算公式:
BCLK/LRCK 比率 = SlotSize × 声道数
BCLK 频率 = 采样率 × SlotSize × 声道数
当前默认值(48k):
BCLK/LRCK = 16 × 1 = 16 ← 不满足 ES7111 的最小要求 32
BCLK 频率 = 48000 × 16 × 1 = 768 kHz
Q2:为什么不满足 ES7111 的要求?
ES7111 数据手册 Table 1 规定 48k 时 MCLK/LRCK 最小为 32,但默认是 16。
修复方法:在 AUDIO_CODEC_I2S case 里覆盖 frame_size:
s_audConfigParam.codec.es8311_param.i2s_param.i2s_config.frame_size
= YOPEN_FRAME_SIZE_16_32;
修改后:
BCLK/LRCK = SlotSize(32) × 声道(1) = 32 ✅
BCLK 频率 = 48000 × 32 × 1 = 1.536 MHz ← 正是 1.536MHz!
Q3:为什么不能把 YOPEN_FRAME_SIZE_16_16 改成 YOPEN_FRAME_SIZE_32_32?
核心原理:frame_size 有两个维度——WordSize(数据位深) 和 SlotSize(槽位时钟)。
| frame_size | WordSize | SlotSize | BCLK/LRCK(单声道) |
|---|---|---|---|
16_16 | 16 bit | 16 bit | 16 |
16_32 | 16 bit | 32 bit | 32 |
32_32 | 32 bit | 32 bit | 32 |
- WordSize = 实际数据位宽 → 必须等于 PCM 数据位深
- SlotSize = 每个槽位的 BCLK 周期数 → 决定 MCLK/LRCK 比率,可以大于 WordSize
系统数据位深恒为 16bit:
// audio_types.h:97
#define AUFRAME_BIT_PER_SAMPLE(frame) (16)
整个 audio 框架的 PCM 数据流都是 16bit(MP3 解码输出 S16)。缓冲区每采样只有 2 字节。
Q4:如果强行用 32_32 会发生什么?
I2S 控制器按 WordSize=32bit 工作,每采样从缓冲区读 4 字节,但数据实际是每采样 2 字节:
缓冲区数据(16bit 采样,每帧2字节):
[采样0_L][采样0_H][采样1_L][采样1_H][采样2_L][采样2_H] ...
I2S 按 32bit WordSize 读取(每帧4字节):
[采样0_L 采样0_H 采样1_L 采样1_H] → 当成一个32bit采样发送
[采样2_L 采样2_H 采样3_L 采样3_H] → 当成一个32bit采样发送
后果:
- 播放速度变成 2 倍
- 音调升高一个八度
- 高低字节错位产生噪声
Q5:正确选择是什么?
YOPEN_FRAME_SIZE_16_32:
| 维度 | 16_32 的值 | 说明 |
|---|---|---|
| WordSize | 16bit | ✅ 匹配 16bit PCM 数据 |
| SlotSize | 32bit | ✅ 使 MCLK/LRCK = 32,满足 ES7111 |
16bit 数据放在 32bit 槽的低 16bit,高 16bit 由 I2S 控制器自动填 0(I2S 标准行为,对 ES7111 透明)。
Q6:frame_size 的两处修改方式有什么区别?
SET_I2S_PARAM 宏被 4 个 codec case 共用:
| case | 调用位置 | 传入的参数 |
|---|---|---|
| ES8311 | L75 | es8311_param.i2s_param |
| TM8211 | L92 | tm8211_param.i2s_param |
| GX8006 | L120 | gx8006_param.i2s_param |
| I2S(ES7111) | L135 | es8311_param.i2s_param |
两种修改方式:
| 方式 | 改哪里 | 影响范围 |
|---|---|---|
改宏 (i2s).i2s_config.frame_size = 16_32 | 宏定义 L29 | 所有 codec 都变 16_32 |
改 case s_audConfigParam.codec.es8311_param...frame_size = 16_32 | L135 之后 | 仅 ES7111 变 16_32 |
推荐改 case 的原因:
- 精确隔离:ES8311/GX8006 有独立 MCLK,16_16 工作正常,没必要让它们也变 16_32
- 避免隐患:改 BCLK 频率可能影响时序,ES8311/GX8006 可能已针对 16_16 调试过
- 源码清晰:改 case 一眼可见"16_32 是 ES7111 的特殊需求"
Q7:对 ES7111 而言,两处修改效果一样吗?
效果完全一样。
AUDIO_CODEC_I2S case 里宏展开后操作的变量就是:
s_audConfigParam.codec.es8311_param.i2s_param.i2s_config.frame_size
所以无论是「改宏里的值」还是「在 case 里加覆盖行」,最终改的都是同一个字段,ES7111 运行结果相同。
但工程层面,改 case 不污染其他 codec 的默认值,是更好的实践。
核心要点总结
一句话理解 frame_size:
WordSize 是"数据口径",SlotSize 是"时钟窗口"。
- 数据是 16bit(系统硬编码),所以 WordSize 必须是 16
- 要让 MCLK/LRCK 达到 32,只能扩大 SlotSize(用 16_32),不能扩大 WordSize(不能用 32_32)
就像给 16mm 的水管配一个 32mm 的外壳——数据(16mm)不变,但外观(32mm)满足设备要求。
附录:调试 Checklist
| 检查项 | 验证方法 |
|---|---|
BOARD_ES7111 宏是否生效 | 查看编译日志中是否有 -DBOARD_ES7111 |
| I2S 引脚配置正确 | 用示波器测 BCLK(30)/LRCK(31)/DOUT(33) 是否有信号 |
| MCLK/LRCK 比率正确 | 测 BCLK 频率应为 1.536MHz(48k×32) |
| frame_size 生效 | 编译日志 trace 中是否显示正确的采样率 |
| PA 控制正常 | 播放时测 PA(22) 脚是否为高电平 |
| 无寄存器配置 | 确认 ES7111 初始化不会触发 I2C 通信 |
十、auPlayerStart 与 audevStartPlay 的关联深度解析
这两个函数的关系是理解音频播放链路的关键,也是集成过程中最容易混淆的部分。
10.1 两个函数的定位
| 对比项 | auPlayerStart | audevStartPlay |
|---|---|---|
| 声明位置 | [audio_player.h:90](file:///g:/x09/yopen/components/audio/include/audio_player.h#L90) | [audio_device.h:194](file:///g:/x09/yopen/components/audio/include/audio_device.h#L194) |
| 实现位置 | 预编译库 audio_player.c.obj | [audio_device.c:567](file:///g:/x09/yopen/components/audio/src/audio_device.c#L567) |
| 输入参数 | auPlayer_t* + auDecoder_t*(解码器) | audevPlayOps_t*(回调操作集)+ auFrame_t* |
| 职责 | 管理解码器、播放状态机、事件回调 | 打开硬件、初始化 codec、驱动播放线程 |
| 调用者 | 应用层(demo、AI 等) | 由 auPlayerStart 内部调用 |
一句话:auPlayerStart 是上层播放器 API,audevStartPlay 是底层设备驱动 API。前者内部调用后者。
10.2 调用链路全景
┌─────────────────────────────────────────────────────────────────────┐
│ 应用层 (demo_es7111_mp3.c:121) │
│ │
│ auPlayerStart(player, decoder) │
│ │
│ ① 设置解码器到 player │
│ ② 从 decoder 获取音频帧格式 (auFrame_t) │
│ ③ 构造 audevPlayOps_t 回调: │
│ ├── .get_data() → 内部调用 decoder->read() │
│ ├── .data_consumed()→ 通知 decoder 数据已消费 │
│ └── .handle_event() → 处理播放事件(完成/错误) │
│ ④ 调用 audevStartPlay(ops, ctx, frame) ← 关键跳转 │
│ │
└──────────────────────────────┬──────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────┐
│ 驱动层 audevStartPlay() [audio_device.c:567] │
│ │
│ ① 检查参数合法性 │
│ ② 配置 codec 相关参数 │
│ └── audConfigParamGetCodecType() → 获取 codec 类型 │
│ ③ 根据 codec 类型分支处理: │
│ ├── ES8311: es8311Init() + es8311Config() (I2C 配置寄存器) │
│ ├── GX8006: gx8006Init() + gx8006Config() (I2C 配置寄存器) │
│ ├── PWMAUD: 配置 PWM 播放 │
│ └── I2S(ES7111): 跳过 codec 初始化 ⭐ │
│ ④ audioDevOpen(frame, TRUE) ← 打开硬件 │
│ ├── audConfigParamGetI2sConfig() → 获取 I2S 配置 │
│ ├── audioDevsampleRateConvert() → 采样率转换 │
│ ├── yopen_i2s_open() → 初始化 I2S 控制器 │
│ └── audioDevSetPA(TRUE) → 打开功放 │
│ ⑤ 创建播放线程 audioPlayerPlayWork │
│ │
└──────────────────────────────┬──────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────┐
│ 播放线程 audioPlayerPlayWork [audio_device.c:338] │
│ │
│ while(1) { │
│ ① ops->get_data(param, buf, len) ← 回调上层取 PCM 数据 │
│ └── decoder->read() → 从 mp3ReaderRead() → player_buf[] │
│ ② yopen_i2s_write(channel, buf, len) → 写入 I2S → ES7111 → 喇叭 │
│ ③ 检查是否读完 → if(is_eof) break; │
│ } │
│ │
│ ④ 播放结束: │
│ ├── audioDevSetPA(FALSE) → 关闭功放 │
│ ├── ops->handle_event(AUDEV_PLAY_EVENT_FINISH) → 通知上层 │
│ └── 等待上层调用 audevStopPlay() 关闭 │
│ │
└─────────────────────────────────────────────────────────────────────┘
10.3 关键衔接点:audevPlayOps_t 回调机制
两者通过 [audio_device.h:31-67](file:///g:/x09/yopen/components/audio/include/audio_device.h#L31-L67) 定义的回调结构体衔接:
typedef struct {
int (*get_data)(void *param, uint8_t* data, uint32_t len);
// ⬆ 播放线程每帧调用,向上层请求 PCM 数据
// 返回实际读取的字节数,0 表示没有数据
void (*data_consumed)(void *param, unsigned bytes);
// ⬆ 数据已消费通知(可选)
void (*handle_event)(void *param, audevPlayEvent_t event);
// ⬆ 事件通知:播放完成、错误等
} audevPlayOps_t;
10.4 数据流向:从 auPlayerStart 到 audevStartPlay
数据流方向
◄───────────────────────
player_buf[] (MP3 数据)
│
│ mp3ReaderRead() ← demo_es7111_mp3.c 实现的回调
▼
auDecoder (libmad)
│
│ decoder->read() ← auPlayer 内部调用
▼
audevPlayOps_t.get_data() ← audevStartPlay 注册的回调
│
│ yopen_i2s_write() ← 播放线程写入硬件
▼
ES7111 DAC → PA → 喇叭
10.5 ES7111 在 audevStartPlay 中的特殊路径
audevStartPlay 在 [audio_device.c:567](file:///g:/x09/yopen/components/audio/src/audio_device.c#L567) 中根据 codecType 分支处理。当 codecType == AUDCONFIG_CODEC_I2S(即 ES7111)时:
// audio_device.c 中的伪代码
switch(device->codecCfg.codecType) {
case AUDCONFIG_CODEC_ES8311:
es8311Init(device->codecCfg.codecIface); // I2C 初始化
es8311Config(device->codecCfg.codecIface); // I2C 配置
break;
case AUDCONFIG_CODEC_GX8006:
gx8006Init(device->codecCfg.codecIface);
gx8006Config(device->codecCfg.codecIface);
break;
case AUDCONFIG_CODEC_PWMAUD:
audio_pwm_open(frame->sample_rate); // PWM 配置
break;
case AUDCONFIG_CODEC_I2S: // ⬅ ES7111 走这里
// ★ 跳过 codec 初始化!无 I2C 寄存器,无需配置
break;
}
ES7111 的优势:audevStartPlay 跳过所有 codec 初始化逻辑,直接配置 I2S 引脚和采样率,启动播放线程。这也是 AUDIO_CODEC_I2S 类型的设计意图——为"无寄存器的纯 I2S DAC"提供最简路径。
10.6 佐证:audevStartPlay 没有其他调用者
搜索整个项目(除 audio_device.c 自身定义外),audevStartPlay 没有任何源码调用者。这证明它的唯一调用者就是预编译的 audio_player.c.obj——也就是 auPlayerStart 的实现内部。
10.7 一句话总结
auPlayerStart是**“播放器”(管解码、状态、事件),audevStartPlay是"声卡驱动"**(管硬件、I2S、线程)。前者通过
audevPlayOps_t回调机制把后者"喂"数据,后者通过事件回调向前者报告状态。ES7111 作为纯 I2S DAC,在
audevStartPlay中享受最简路径——跳过 codec 初始化,直接开始 I2S 数据传输。

520

被折叠的 条评论
为什么被折叠?



