ES7111 Codec 集成完整流程总结

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)301串行位时钟(也是 ES7111 的内部 MCLK)
LRCK311左右声道帧时钟
SDIN (DOUT)331MCU→DAC 数据输出
PA (功放)224 (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.cMP3 音频数据数组(player_buf[]),以 C 数组形式内置现有 demo,可直接复用
components/libmadMP3 解码器(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_sizeWordSize(数据位深)SlotSize(槽位时钟)BCLK/LRCK(单声道)
YOPEN_FRAME_SIZE_16_1616 bit16 bit16
YOPEN_FRAME_SIZE_16_3216 bit32 bit32
YOPEN_FRAME_SIZE_24_3224 bit32 bit32
YOPEN_FRAME_SIZE_32_3232 bit32 bit32
  • WordSize = 实际数据位宽,必须匹配 PCM 数据
  • SlotSize = 每个槽位的 BCLK 周期数,可以大于 WordSize

6.3 ES7111 对 MCLK/LRCK 的要求

ES7111 数据手册 Table 1 规定:

采样率支持的 MCLK/LRCK 比率
8k–32k32, 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–48k32, 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 的值说明
WordSize16bit✅ 匹配 PCM 数据位深
SlotSize32bit✅ 使 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 修改的原因

  1. ES8311/GX8006 有独立 MCLK,16_16 工作正常,不需要改
  2. 改宏会改变所有 codec 的 BCLK 频率,可能引入隐患
  3. 源码清晰,一眼可见这是 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 的核心区别

方面ES8311ES7111
类型Codec(ADC+DAC)纯 DAC
控制I2C 配置寄存器无寄存器(上电即工作)
MCLK独立引脚(可配分频)复用 SCLK
录音✅ 支持❌ 不支持
Codec 类型AUDIO_CODEC_ES8311AUDIO_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 表,因为:

  1. MCLK 引脚被禁用YOPEN_PIN_I2S_MCLK = -1,主控不输出 MCLK
  2. ES7111 用 BCLK 作内部 MCLK(数据手册:“SCLK is used as MCLK”)
  3. 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_sizeWordSizeSlotSizeBCLK/LRCK(单声道)
16_1616 bit16 bit16
16_3216 bit32 bit32
32_3232 bit32 bit32
  • 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 的值说明
WordSize16bit✅ 匹配 16bit PCM 数据
SlotSize32bit✅ 使 MCLK/LRCK = 32,满足 ES7111

16bit 数据放在 32bit 槽的低 16bit,高 16bit 由 I2S 控制器自动填 0(I2S 标准行为,对 ES7111 透明)。

Q6:frame_size 的两处修改方式有什么区别?

SET_I2S_PARAM 宏被 4 个 codec case 共用:

case调用位置传入的参数
ES8311L75es8311_param.i2s_param
TM8211L92tm8211_param.i2s_param
GX8006L120gx8006_param.i2s_param
I2S(ES7111)L135es8311_param.i2s_param

两种修改方式

方式改哪里影响范围
改宏 (i2s).i2s_config.frame_size = 16_32宏定义 L29所有 codec 都变 16_32
改 case s_audConfigParam.codec.es8311_param...frame_size = 16_32L135 之后仅 ES7111 变 16_32

推荐改 case 的原因

  1. 精确隔离:ES8311/GX8006 有独立 MCLK,16_16 工作正常,没必要让它们也变 16_32
  2. 避免隐患:改 BCLK 频率可能影响时序,ES8311/GX8006 可能已针对 16_16 调试过
  3. 源码清晰:改 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 通信

十、auPlayerStartaudevStartPlay 的关联深度解析

这两个函数的关系是理解音频播放链路的关键,也是集成过程中最容易混淆的部分。

10.1 两个函数的定位

对比项auPlayerStartaudevStartPlay
声明位置[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上层播放器 APIaudevStartPlay底层设备驱动 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 数据流向:从 auPlayerStartaudevStartPlay

                    数据流方向
            ◄───────────────────────
                                    
  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 数据传输。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值