简介
今天这篇文章我们来着重分析一下这块开发板上的功放电路和烧录演示一下官方的麦克风回环和PCM的音频播放。

音频解码芯片这里使用了一颗ES8311,这款芯片在很多带有音频播放和解码的芯片上非常常见。支持ADC的音频采集和DAC的输出。主要特性如下所示
支持 I2S/PCM 数字音频接口
24-bit 分辨率,采样率 8–96 kHz
DAC 信噪比约 110 dB,ADC 约 100 dB
支持动态范围压缩、去噪、点击噪声抑制等
支持差分输出和耳机驱动
其中GPIO7 和 GPIO8 用于I2C的输入控制。 GPIO13 - GPIO 9 用于 I2S的音频输入。 其中MIC_P 和 MIC_N 则是用于麦克风的输入。还记得之前【ESP-IDF系列】【ESP32】使用ESP-IDF驱动INMP441和MAX98357结合I2S协议实现音频采集和播放 的这个帖子里我们使用了两个芯片,INMP441 作为麦克风采集数据输入到ESP32S3然后使用MAX98357作为功放输出的音频。 这个ES8311可以做到同样的效果。其中OUTN 和 OUTP 则可以接扬声器输出。但是由于这款芯片的单独驱动能力没有那么大。所以需要一个功放电路来对ES8311的DAC输出进行放大从而来直接驱动扬声器。

首先 GPIO53 控制总体的功放电路的开关,当整个功放电路关闭的时候,ES8311其实还是有音频数据输出的,只不过麦克风并不会再播放声音了。耳机也是有输出的。然后当耳机插入到耳机的座子的时候也会通过EarPhonedetect的引脚来控制SN74的这个芯片来关闭功放(通过Y的输出控制NS4150B),从而使其音频直接通过ES8311的输出在耳机内播放。
当功放芯片开启的时候ES8311的输出会经过滤波电路输入到D类功放芯片的IN + 和 IN - 中,然后经过NS4150B对音频进行放大然后经过PA out + 和 - 进行,阻抗匹配和滤波然后输出到坐子上(也就是扬声器的接口) 从而实现了音频的播放。
这里的这个SN74芯片用的非常巧妙实现了麦克风的检测以及功放电路的输出控制。 最终驱动8欧的麦克风进行输出。效果非常不错!
接下来我们将对程序进行测试,测试代码我们使用的是微雪提供的04_I2SCodeC的代码。 其中使用的是ES8311的组件库。
## IDF Component Manager Manifest File dependencies: idf: "^5.0" espressif/es8311: "^1.0.0" # After enabling Board Support Packages support in menuconfig, you can pick you specific BSP here espressif/esp-box: version: "^2.4.2" rules: - if: "target in [esp32s3]"
首先使能功放的输出: 即上述原理图中的GPIO53 控制功放的开关。
static void gpio_init(void)
{
// 配置GPIO48为输出模式
gpio_config_t io_conf = {
.pin_bit_mask = (1ULL << GPIO_OUTPUT_PA), // 选择GPIO48
.mode = GPIO_MODE_OUTPUT, // 配置为输出模式
.pull_down_en = GPIO_PULLDOWN_DISABLE, // 禁用下拉
.pull_up_en = GPIO_PULLUP_DISABLE, // 禁用上拉
.intr_type = GPIO_INTR_DISABLE // 禁用中断
};
gpio_config(&io_conf);
// 设置GPIO48为高电平
gpio_set_level(GPIO_OUTPUT_PA, 1);
}然后就是I2S的常规初始化
static esp_err_t i2s_driver_init(void)
{
i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM, I2S_ROLE_MASTER);
chan_cfg.auto_clear = true; // Auto clear the legacy data in the DMA buffer
ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, &tx_handle, &rx_handle));
i2s_std_config_t std_cfg = {
.clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(EXAMPLE_SAMPLE_RATE),
.slot_cfg = I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_STEREO),
.gpio_cfg = {
.mclk = I2S_MCK_IO,
.bclk = I2S_BCK_IO,
.ws = I2S_WS_IO,
.dout = I2S_DO_IO,
.din = I2S_DI_IO,
.invert_flags = {
.mclk_inv = false,
.bclk_inv = false,
.ws_inv = false,
},
},
};
std_cfg.clk_cfg.mclk_multiple = EXAMPLE_MCLK_MULTIPLE;
ESP_ERROR_CHECK(i2s_channel_init_std_mode(tx_handle, &std_cfg));
ESP_ERROR_CHECK(i2s_channel_init_std_mode(rx_handle, &std_cfg));
ESP_ERROR_CHECK(i2s_channel_enable(tx_handle));
ESP_ERROR_CHECK(i2s_channel_enable(rx_handle));
return ESP_OK;
}初始化I2C 和 ES8311
static esp_err_t es8311_codec_init(void)
{
/* Initialize I2C peripheral */
#if !defined(CONFIG_EXAMPLE_BSP)
const i2c_config_t es_i2c_cfg = {
.sda_io_num = I2C_SDA_IO,
.scl_io_num = I2C_SCL_IO,
.mode = I2C_MODE_MASTER,
.sda_pullup_en = GPIO_PULLUP_ENABLE,
.scl_pullup_en = GPIO_PULLUP_ENABLE,
.master.clk_speed = 100000,
};
ESP_RETURN_ON_ERROR(i2c_param_config(I2C_NUM, &es_i2c_cfg), TAG, "config i2c failed");
ESP_RETURN_ON_ERROR(i2c_driver_install(I2C_NUM, I2C_MODE_MASTER, 0, 0, 0), TAG, "install i2c driver failed");
#else
ESP_ERROR_CHECK(bsp_i2c_init());
#endif
/* Initialize es8311 codec */
es8311_handle_t es_handle = es8311_create(I2C_NUM, ES8311_ADDRRES_0);
ESP_RETURN_ON_FALSE(es_handle, ESP_FAIL, TAG, "es8311 create failed");
const es8311_clock_config_t es_clk = {
.mclk_inverted = false,
.sclk_inverted = false,
.mclk_from_mclk_pin = true,
.mclk_frequency = EXAMPLE_MCLK_FREQ_HZ,
.sample_frequency = EXAMPLE_SAMPLE_RATE};
ESP_ERROR_CHECK(es8311_init(es_handle, &es_clk, ES8311_RESOLUTION_16, ES8311_RESOLUTION_16));
ESP_RETURN_ON_ERROR(es8311_sample_frequency_config(es_handle, EXAMPLE_SAMPLE_RATE * EXAMPLE_MCLK_MULTIPLE, EXAMPLE_SAMPLE_RATE), TAG, "set es8311 sample frequency failed");
ESP_RETURN_ON_ERROR(es8311_voice_volume_set(es_handle, EXAMPLE_VOICE_VOLUME, NULL), TAG, "set es8311 volume failed");
ESP_RETURN_ON_ERROR(es8311_microphone_config(es_handle, false), TAG, "set es8311 microphone failed");
#if CONFIG_EXAMPLE_MODE_ECHO
ESP_RETURN_ON_ERROR(es8311_microphone_gain_set(es_handle, EXAMPLE_MIC_GAIN), TAG, "set es8311 microphone gain failed");
#endif
return ESP_OK;
}当完成这个配置之后,便可以使用I2C来控制ES8311 实现音频的播放了。
/* Import music file as buffer */
#if CONFIG_EXAMPLE_MODE_MUSIC
extern const uint8_t music_pcm_start[] asm("_binary_canon_pcm_start");
extern const uint8_t music_pcm_end[] asm("_binary_canon_pcm_end");
#endif首先定义一个数组来保存音频的起始和结束的地址。
static void i2s_music(void *args)
{
esp_err_t ret = ESP_OK;
size_t bytes_write = 0;
uint8_t *data_ptr = (uint8_t *)music_pcm_start;
/* (Optional) Disable TX channel and preload the data before enabling the TX channel,
* so that the valid data can be transmitted immediately */
ESP_ERROR_CHECK(i2s_channel_disable(tx_handle));
ESP_ERROR_CHECK(i2s_channel_preload_data(tx_handle, data_ptr, music_pcm_end - data_ptr, &bytes_write));
data_ptr += bytes_write; // Move forward the data pointer
/* Enable the TX channel */
ESP_ERROR_CHECK(i2s_channel_enable(tx_handle));
while (1)
{
/* Write music to earphone */
ret = i2s_channel_write(tx_handle, data_ptr, music_pcm_end - data_ptr, &bytes_write, portMAX_DELAY);
if (ret != ESP_OK)
{
/* Since we set timeout to 'portMAX_DELAY' in 'i2s_channel_write'
so you won't reach here unless you set other timeout value,
if timeout detected, it means write operation failed. */
ESP_LOGE(TAG, "[music] i2s write failed, %s", err_reason[ret == ESP_ERR_TIMEOUT]);
abort();
}
if (bytes_write > 0)
{
ESP_LOGI(TAG, "[music] i2s music played, %d bytes are written.", bytes_write);
}
else
{
ESP_LOGE(TAG, "[music] i2s music play failed.");
abort();
}
data_ptr = (uint8_t *)music_pcm_start;
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
vTaskDelete(NULL);
}在上述代码中,首先清空发送区。然后将上述文件数组中的数据写入到发送区,然后通过i2Swrite的方式对缓冲区的数据进行写入。便实现了音频的播放。
static void i2s_echo(void *args)
{
int *mic_data = malloc(EXAMPLE_RECV_BUF_SIZE);
if (!mic_data)
{
ESP_LOGE(TAG, "[echo] No memory for read data buffer");
abort();
}
esp_err_t ret = ESP_OK;
size_t bytes_read = 0;
size_t bytes_write = 0;
ESP_LOGI(TAG, "[echo] Echo start");
while (1)
{
memset(mic_data, 0, EXAMPLE_RECV_BUF_SIZE);
/* Read sample data from mic */
ret = i2s_channel_read(rx_handle, mic_data, EXAMPLE_RECV_BUF_SIZE, &bytes_read, 1000);
if (ret != ESP_OK)
{
ESP_LOGE(TAG, "[echo] i2s read failed, %s", err_reason[ret == ESP_ERR_TIMEOUT]);
abort();
}
/* Write sample data to earphone */
ret = i2s_channel_write(tx_handle, mic_data, EXAMPLE_RECV_BUF_SIZE, &bytes_write, 1000);
if (ret != ESP_OK)
{
ESP_LOGE(TAG, "[echo] i2s write failed, %s", err_reason[ret == ESP_ERR_TIMEOUT]);
abort();
}
if (bytes_read != bytes_write)
{
ESP_LOGW(TAG, "[echo] %d bytes read but only %d bytes are written", bytes_read, bytes_write);
}
}
vTaskDelete(NULL);
}回环的播放和之前我们那边INMP441和MAX98357一致,都是先读取然后再写入到I2S即可。

注意:音量不要开到最大,否则可能会出现扬声器声音无法播放全是噪音的情况
我要赚赏金
