ESP32-S3 ADF音频播放器player应用开发学习记录
使用的开发板是 ESP32-S3-Korvo-2 V3.0,项目主要应用 播放SD卡音频 功能,所以开发板上其他组件功能未使用,主要学习开发创建播放器player播放音频功能。
开发板上使用的音频解码芯片是 ES8311和 音频功率放大器 NS4150。
组件 | 介绍 |
音频编解码芯片 (Audio Codec Chip) | 音频编解码器芯片 ES8311 是一种低功耗单声道音频编解码器,包含单通道 ADC、单通道 DAC、低噪声前置放大器、耳机驱动器、数字音效、模拟混音和增益功能。它通过 I2S 和 I2C 总线与 ESP32-S3-WROOM-1 模组连接,以提供独立于音频应用程序的硬件音频处理。 |
音频功率放大器 (Audio PA Chip) | NS4150 是一款低 EMI、3 W 单声道 D 类音频功率放大器,用于放大来自音频编解码芯片的音频信号,以驱动扬声器。 |
硬件部分
开发板使用引脚如下
管脚名称 | ES8311 | NS4150 |
IO16 | I2S0_MCLK | |
IO17 | I2C_SDA | |
IO18 | I2C_CLK | |
IO8 | I2S0_DSDIN | |
IO9 | I2S0_SCLK | |
IO45 | I2S0_LRCK | |
IO48 | PA_CTRL |
开发板硬件原理图如下
程序部分
ESP32提供了几个简单的高级 API。它旨在基于标准化音频元素的典型互连快速实现音频应用。所以此次开发使用了高等级的接口 API (esp_audio_
) 来构建 player。
可以参考例程: /examples/advanced_examples/esp_dispatcher_dueros/main/audio_setup.c
创建播放器
创建播放器具体分为六步:
Ⅰ 初始化音频解码芯片
Ⅱ 创建esp_audio实例
Ⅲ 启动编解码驱动程序
Ⅳ 添加音频输入流到特定的esp_audio实例
Ⅴ 添加解码器和编码器到esp_audio实例
Ⅵ 添加音频输出流到特定的esp_audio实例
void Setup\_player(void)
{
esp\_audio\_cfg\_t cfg = DEFAULT\_ESP\_AUDIO\_CONFIG();
//硬件编解码器初始化
board_handle = audio\_board\_init();
cfg.vol_handle = board_handle->audio_hal;
cfg.vol_set = (audio_volume_set)audio_hal_set_volume;
cfg.vol_get = (audio_volume_get)audio_hal_get_volume;
//esp\_audio适用于sepcific类型
//ESP\_AUDIO\_PREFER\_MEM 在新管道启动之前停止了先前链接的元素,但out流元素除外
cfg.prefer_type = ESP_AUDIO_PREFER_MEM;
//目标采样率
cfg.resample_rate = 48000;
//根据 cfg 参数创建 esp\_audio实例
player = esp\_audio\_create(&cfg);
audio\_hal\_ctrl\_codec(board_handle->audio_hal, AUDIO_HAL_CODEC_MODE_DECODE, AUDIO_HAL_CTRL_START);//启动/停止编解码驱动程序
//添加音频输入流到特定的esp\_audio实例
fatfs\_stream\_cfg\_t fatfs_cfg = FATFS\_STREAM\_CFG\_DEFAULT();
fatfs_cfg.type = AUDIO_STREAM_READER;
audio\_element\_handle\_t fatfs_stream_reader = fatfs\_stream\_init(&fatfs_cfg);
esp\_audio\_input\_stream\_add(player, fatfs_stream_reader);
//添加一个新的编解码器库,可以解码或编码音乐
mp3\_decoder\_cfg\_t mp3_cfg = DEFAULT\_MP3\_DECODER\_CONFIG();
audio\_element\_handle\_t mp3_decoder = mp3\_decoder\_init(&mp3_cfg);
esp\_audio\_codec\_lib\_add(player, AUDIO_CODEC_TYPE_DECODER, mp3_decoder);
//添加音频输出流到特定的esp\_audio实例
i2s\_stream\_cfg\_t i2s_cfg = I2S\_STREAM\_CFG\_DEFAULT();
i2s_cfg.i2s_config.sample_rate = 48000;//和编解码的采样率保持一致
i2s_cfg.type = AUDIO_STREAM_WRITER;
audio\_element\_handle\_t i2s_stream_writer = i2s\_stream\_init(&i2s_cfg);
esp\_audio\_output\_stream\_add(player, i2s_stream_writer);
}
Ⅰ.初始化音频解码芯片
audio\_board\_handle\_t board_handle = audio\_board\_init();
具体初始化流程
\esp-adf\components\audio_board\esp32_s3_korvo2_v3\board.c
audio_board_init --> audio_board_codec_init(音频芯片初始化) --> es8311_codec_init
--> audio_board_adc_init(音频ADC初始化)
audio\_board\_handle\_t audio\_board\_init(void)
{
if (board_handle) {
ESP\_LOGW(TAG, "The board has already been initialized!");
return board_handle;
}
board_handle = (audio\_board\_handle\_t) audio\_calloc(1, sizeof(struct audio\_board\_handle));
AUDIO\_MEM\_CHECK(TAG, board_handle, return NULL);
board_handle->audio_hal = audio\_board\_codec\_init();
board_handle->adc_hal = audio\_board\_adc\_init();
return board_handle;
}
audio\_hal\_handle\_t audio\_board\_codec\_init(void)
{
audio\_hal\_codec\_config\_t audio_codec_cfg = AUDIO\_CODEC\_DEFAULT\_CONFIG();
audio\_hal\_handle\_t codec_hal = audio\_hal\_init(&audio_codec_cfg, &AUDIO_CODEC_ES8311_DEFAULT_HANDLE);
AUDIO\_NULL\_CHECK(TAG, codec_hal, return NULL);
return codec_hal;
}
//初始化音频解码芯片的结构体
struct {
audio\_hal\_adc\_input\_t adc_input;/ \* !<设置adc通道\*/
audio\_hal\_dac\_output\_t dac_output;/ \* !<设置dac通道\*/
audio\_hal\_codec\_mode\_t codec_mode;/ \* !<选择编解码器模式:adc, dac或两者\*/
audio\_hal\_codec\_i2s\_iface\_t i2s_iface;/ \* !< set I2S接口配置\*/
} audio\_hal\_codec\_config\_t;
//编解码器E8311操作功能
AUDIO_CODEC_ES8311_DEFAULT_HANDLE = {
.audio_codec_initialize = es8311_codec_init //ES8311初始化
.audio_codec_deinitialize = es8311_codec_deinit,//去初始化
.audio_codec_ctrl = es8311_codec_ctrl_state,//控制状态
.audio_codec_config_iface = es8311_codec_config_i2s,//i2s配置
.audio_codec_set_mute = es8311_set_voice_mute,//设置静音
.audio_codec_set_volume = es8311_codec_set_voice_volume,//设置音量大小
.audio_codec_get_volume = es8311_codec_get_voice_volume,//获取音量大小
.audio_hal_lock = NULL
.handle = NULL;
};
ES8311初始化
\esp-adf\components\audio_hal\driver\es8311\es8311.c
esp\_err\_t es8311\_codec\_init(audio\_hal\_codec\_config\_t \*codec_cfg)
{
uint8\_t datmp, regv;
int coeff;
esp\_err\_t ret = ESP_OK;
i2c\_init(); // ESP32 in master mode ESP32主模式
ret |= es8311\_write\_reg(ES8311_CLK_MANAGER_REG01, 0x30);
ret |= es8311\_write\_reg(ES8311_CLK_MANAGER_REG02, 0x00);
ret |= es8311\_write\_reg(ES8311_CLK_MANAGER_REG03, 0x10);
ret |= es8311\_write\_reg(ES8311_ADC_REG16, 0x24);
ret |= es8311\_write\_reg(ES8311_CLK_MANAGER_REG04, 0x10);
ret |= es8311\_write\_reg(ES8311_CLK_MANAGER_REG05, 0x00);
ret |= es8311\_write\_reg(ES8311_SYSTEM_REG0B, 0x00);
ret |= es8311\_write\_reg(ES8311_SYSTEM_REG0C, 0x00);
ret |= es8311\_write\_reg(ES8311_SYSTEM_REG10, 0x1F);
ret |= es8311\_write\_reg(ES8311_SYSTEM_REG11, 0x7F);
ret |= es8311\_write\_reg(ES8311_RESET_REG00, 0x80);
/\*
\* Set Codec into Master or Slave mode 设置编解码器为主或从模式
\*/
regv = es8311\_read\_reg(ES8311_RESET_REG00);
/\*
\* Set master/slave audio interface 设置主\从音频接口
\*/
audio\_hal\_codec\_i2s\_iface\_t \*i2s_cfg = &(codec_cfg->i2s_iface);
switch (i2s_cfg->mode) {
case AUDIO_HAL_MODE_MASTER: /\* MASTER MODE 主模式\*/
ESP\_LOGI(TAG, "ES8311 in Master mode");
regv |= 0x40;
break;
case AUDIO_HAL_MODE_SLAVE: /\* SLAVE MODE 从模式\*/
ESP\_LOGI(TAG, "ES8311 in Slave mode");
regv &= 0xBF;
break;
default:
regv &= 0xBF;
}
ret |= es8311\_write\_reg(ES8311_RESET_REG00, regv);
ret |= es8311\_write\_reg(ES8311_CLK_MANAGER_REG01, 0x3F);
/\*
\* Select clock source for internal mclk 为内部时钟选择时钟源
\*/
switch (get\_es8311\_mclk\_src()) {
case FROM_MCLK_PIN:
regv = es8311\_read\_reg(ES8311_CLK_MANAGER_REG01);
regv &= 0x7F;
ret |= es8311\_write\_reg(ES8311_CLK_MANAGER_REG01, regv);
break;
case FROM_SCLK_PIN:
regv = es8311\_read\_reg(ES8311_CLK_MANAGER_REG01);
regv |= 0x80;
ret |= es8311\_write\_reg(ES8311_CLK_MANAGER_REG01, regv);
break;
default:
regv = es8311\_read\_reg(ES8311_CLK_MANAGER_REG01);
regv &= 0x7F;
ret |= es8311\_write\_reg(ES8311_CLK_MANAGER_REG01, regv);
break;
}
int sample_fre = 0;
int mclk_fre = 0;
switch (i2s_cfg->samples) {
case AUDIO_HAL_08K_SAMPLES:
sample_fre = 8000;
break;
case AUDIO_HAL_11K_SAMPLES:
sample_fre = 11025;
break;
case AUDIO_HAL_16K_SAMPLES:
sample_fre = 16000;
break;
case AUDIO_HAL_22K_SAMPLES:
sample_fre = 22050;
break;
case AUDIO_HAL_24K_SAMPLES:
sample_fre = 24000;
break;
case AUDIO_HAL_32K_SAMPLES:
sample_fre = 32000;
break;
case AUDIO_HAL_44K_SAMPLES:
sample_fre = 44100;
break;
case AUDIO_HAL_48K_SAMPLES:
sample_fre = 48000;
break;
default:
ESP\_LOGE(TAG, "Unable to configure sample rate %dHz", sample_fre);
break;
}
mclk_fre = sample_fre \* MCLK_DIV_FRE;
coeff = get\_coeff(mclk_fre, sample_fre);
if (coeff < 0) {
ESP\_LOGE(TAG, "Unable to configure sample rate %dHz with %dHz MCLK", sample_fre, mclk_fre);
return ESP_FAIL;
}
/\*
\* Set clock parammeters 设置时钟参数
\*/
if (coeff >= 0) {
regv = es8311\_read\_reg(ES8311_CLK_MANAGER_REG02) & 0x07;
regv |= (coeff_div[coeff].pre_div - 1) << 5;
datmp = 0;
switch (coeff_div[coeff].pre_multi) {
case 1:
datmp = 0;
break;
case 2:
datmp = 1;
break;
case 4:
datmp = 2;
break;
case 8:
datmp = 3;
break;
default:
break;
}
if (get\_es8311\_mclk\_src() == FROM_SCLK_PIN) {
datmp = 3; /\* DIG\_MCLK = LRCK \* 256 = BCLK \* 8 \*/
}
regv |= (datmp) << 3;
ret |= es8311\_write\_reg(ES8311_CLK_MANAGER_REG02, regv);
regv = es8311\_read\_reg(ES8311_CLK_MANAGER_REG05) & 0x00;
regv |= (coeff_div[coeff].adc_div - 1) << 4;
regv |= (coeff_div[coeff].dac_div - 1) << 0;
ret |= es8311\_write\_reg(ES8311_CLK_MANAGER_REG05, regv);
regv = es8311\_read\_reg(ES8311_CLK_MANAGER_REG03) & 0x80;
regv |= coeff_div[coeff].fs_mode << 6;
regv |= coeff_div[coeff].adc_osr << 0;
ret |= es8311\_write\_reg(ES8311_CLK_MANAGER_REG03, regv);
regv = es8311\_read\_reg(ES8311_CLK_MANAGER_REG04) & 0x80;
regv |= coeff_div[coeff].dac_osr << 0;
ret |= es8311\_write\_reg(ES8311_CLK_MANAGER_REG04, regv);
regv = es8311\_read\_reg(ES8311_CLK_MANAGER_REG07) & 0xC0;
regv |= coeff_div[coeff].lrck_h << 0;
ret |= es8311\_write\_reg(ES8311_CLK_MANAGER_REG07, regv);
regv = es8311\_read\_reg(ES8311_CLK_MANAGER_REG08) & 0x00;
regv |= coeff_div[coeff].lrck_l << 0;
ret |= es8311\_write\_reg(ES8311_CLK_MANAGER_REG08, regv);
regv = es8311\_read\_reg(ES8311_CLK_MANAGER_REG06) & 0xE0;
if (coeff_div[coeff].bclk_div < 19) {
regv |= (coeff_div[coeff].bclk_div - 1) << 0;
} else {
regv |= (coeff_div[coeff].bclk_div) << 0;
}
ret |= es8311\_write\_reg(ES8311_CLK_MANAGER_REG06, regv);
}
/\*
\* mclk inverted or not MCLK是否倒置
\*/
if (INVERT_MCLK) {
regv = es8311\_read\_reg(ES8311_CLK_MANAGER_REG01);
regv |= 0x40;
ret |= es8311\_write\_reg(ES8311_CLK_MANAGER_REG01, regv);
} else {
regv = es8311\_read\_reg(ES8311_CLK_MANAGER_REG01);
regv &= ~(0x40);
ret |= es8311\_write\_reg(ES8311_CLK_MANAGER_REG01, regv);
}
/\*
\* sclk inverted or not SCLK是否倒置
\*/
if (INVERT_SCLK) {
regv = es8311\_read\_reg(ES8311_CLK_MANAGER_REG06);
regv |= 0x20;
ret |= es8311\_write\_reg(ES8311_CLK_MANAGER_REG06, regv);
} else {
regv = es8311\_read\_reg(ES8311_CLK_MANAGER_REG06);
regv &= ~(0x20);
ret |= es8311\_write\_reg(ES8311_CLK_MANAGER_REG06, regv);
}
ret |= es8311\_write\_reg(ES8311_SYSTEM_REG13, 0x10);
ret |= es8311\_write\_reg(ES8311_ADC_REG1B, 0x0A);
ret |= es8311\_write\_reg(ES8311_ADC_REG1C, 0x6A);
/\* pa power gpio init 初始化功放引脚\*/
gpio\_config\_t io_conf;
memset(&io_conf, 0, sizeof(io_conf));
io_conf.mode = GPIO_MODE_OUTPUT;
io_conf.pin_bit_mask = BIT64(get\_pa\_enable\_gpio());
io_conf.pull_down_en = 0;
io_conf.pull_up_en = 0;
gpio\_config(&io_conf);
/\* enable pa power 使能功放电源\*/
es8311\_pa\_power(true);
codec\_dac\_volume\_config\_t vol_cfg = ES8311\_DAC\_VOL\_CFG\_DEFAULT();
dac_vol_handle = audio\_codec\_volume\_init(&vol_cfg);
return ESP_OK;
}
#define PA\_ENABLE\_GPIO GPIO\_NUM\_48
int8\_t get\_pa\_enable\_gpio(void)
{
return PA_ENABLE_GPIO;
}
void es8311\_pa\_power(bool enable)
{
if (enable) {
gpio\_set\_level(get\_pa\_enable\_gpio(), 1);
} else {
gpio\_set\_level(get\_pa\_enable\_gpio(), 0);
}
}