refactor(i2s): replace es8311 example with pcm_player

Drop echo/recording now that mic_recorder covers capture. The new
example is TX-only and plays embedded PCM on ES8311 or ES8389.
This commit is contained in:
morris
2026-09-06 15:30:59 +08:00
parent 8961d6b136
commit 85680029d2
31 changed files with 617 additions and 625 deletions

View File

@@ -449,7 +449,7 @@ The examples of the I2S driver can be found in the directory :example:`periphera
Standard TX/RX Usage
^^^^^^^^^^^^^^^^^^^^
- :example:`peripherals/i2s/i2s_codec/i2s_es8311` demonstrates how to use the I2S ES8311 audio codec with {IDF_TARGET_NAME} to play music or echo sounds, featuring high performance and low power multi-bit delta-sigma audio ADC and DAC, with options to customize music and adjust mic gain and volume.
- :example:`peripherals/i2s/wav_player` demonstrates how to play an embedded WAV file through I2S STD TX to an ES8311 or ES8389 codec DAC on {IDF_TARGET_NAME}.
- :example:`peripherals/i2s/i2s_basic/i2s_std` demonstrates how to use the I2S standard mode in either simplex or full-duplex mode on {IDF_TARGET_NAME}.
- :example:`peripherals/i2s/mic_recorder` demonstrates how to record audio from an analog microphone connected to an ES8389 codec through the I2S STD interface.

View File

@@ -449,7 +449,7 @@ I2S 驱动例程请参考 :example:`peripherals/i2s` 目录。以下为每种模
标准 TX/RX 模式的应用
^^^^^^^^^^^^^^^^^^^^^
- :example:`peripherals/i2s/i2s_codec/i2s_es8311` 演示了如何在 {IDF_TARGET_NAME} 上使用 I2S ES8311 音频编解码器播放音乐或回声,具有高性能和低功耗的多位 delta-sigma 音频 ADC 和 DAC提供自定义音乐、调整麦克风增益和音量的选项
- :example:`peripherals/i2s/wav_player` 演示了如何在 {IDF_TARGET_NAME} 上使用 I2S STD TX将嵌入的 WAV 音频通过 ES8311 或 ES8389 音频编解码器播放。
- :example:`peripherals/i2s/i2s_basic/i2s_std` 演示了如何在 {IDF_TARGET_NAME} 上以单工或全双工模式使用 I2S 标准模式。
- :example:`peripherals/i2s/mic_recorder` 演示了如何通过 I2S STD 接口,使用 ES8389 音频编解码器录制模拟麦克风的音频。

View File

@@ -241,17 +241,6 @@ examples/peripherals/i2s/i2s_codec/i2s_es7210_tdm:
- esp_hal_i2s
- soc
examples/peripherals/i2s/i2s_codec/i2s_es8311:
disable:
- if: (SOC_I2S_SUPPORTED != 1 or SOC_I2C_SUPPORTED != 1)
reason: rely on I2S STD mode and I2C to config es7210
depends_components:
- esp_driver_dma
- esp_driver_i2s
- esp_driver_i2c
- esp_hal_i2s
- soc
examples/peripherals/i2s/mic_recorder:
disable:
- if: SOC_I2S_SUPPORTED != 1
@@ -260,6 +249,17 @@ examples/peripherals/i2s/mic_recorder:
- esp_hal_i2s
- soc
examples/peripherals/i2s/wav_player:
disable:
- if: (SOC_I2S_SUPPORTED != 1 or SOC_I2C_SUPPORTED != 1)
reason: rely on I2S STD TX and I2C to configure the DAC codec
depends_components:
- esp_driver_dma
- esp_driver_i2s
- esp_driver_i2c
- esp_hal_i2s
- soc
examples/peripherals/i3c/i3c_i2c_basic:
disable:
- if: SOC_I3C_MASTER_SUPPORTED != 1

View File

@@ -1,163 +0,0 @@
| Supported Targets | ESP32 | ESP32-C3 | ESP32-C5 | ESP32-C6 | ESP32-C61 | ESP32-H2 | ESP32-H21 | ESP32-H4 | ESP32-P4 | ESP32-S2 | ESP32-S3 | ESP32-S31 |
| ----------------- | ----- | -------- | -------- | -------- | --------- | -------- | --------- | -------- | -------- | -------- | -------- | --------- |
# I2S ES8311 Example
(See the README.md file in the upper level 'examples' directory for more information about examples.)
In this example, you can choose music mode or echo mode in menuconfig. In music mode, the example will play a piece of music in 'canon.pcm', and in echo mode, you can hear what you said in earphone.
## ES8311 brief
ES8311 low power mono audio codec features:
- High performance and low power multi-bit delta-sigma audio ADC and DAC
- I2S/PCM master or slave serial data port
- I2C interface for configuration
- ADC: 24-bit, 8 to 96 kHz sampling frequency
- ADC: 100 dB signal to noise ratio, -93 dB THD+N
- DAC: 24-bit, 8 to 96 kHz sampling frequency
- DAC: 110 dB signal to noise ratio, -80 dB THD+N
For more details, see [ES8311 datasheet](http://www.everest-semi.com/pdf/ES8311%20PB.pdf)
## How to Use Example
### Hardware Required
* A development board with any supported Espressif SOC chip (see `Supported Targets` table above)
* The example can be preconfigured for [ESP-BOX](https://components.espressif.com/components/espressif/esp-box), [ESP32-S2-Kaluga-kit](https://components.espressif.com/components/espressif/esp32_s2_kaluga_kit) and [ESP32-S3-LCD-EV-board](https://components.espressif.com/components/espressif/esp32_s3_lcd_ev_board). More information is in 'Configure the Project' section.
* A USB cable for power supply and programming.
* A board with ES8311 codec, mic and earphone interface(e.g. ESP-LyraT-8311A extension board).
### Connection
```
┌─────────────────┐ ┌──────────────────────────┐
│ ESP │ │ ES8311 │
│ │ │ │
│ I2S_MCK_IO├──────────►│PIN2-MCLK │
│ │ │ │ ┌─────────┐
│ I2S_BCK_IO├──────────►│PIN6-BCLK PIN12-OUTP├───────────┤ │
│ │ │ │ │ EARPHONE│
│ I2S_WS_IO├──────────►│PIN8-LRCK PIN13-OUTN├───────────┤ │
│ │ │ │ └─────────┘
│ I2S_DO_IO├──────────►│PIN9-SDIN │
│ │ │ │
│ I2S_DI_IO│◄──────────┤PIN7-SDOUT │
│ │ │ │ ┌─────────┐
│ │ │ PIN18-MIC1P├───────────┤ │
│ I2C_SCL_IO├──────────►│PIN1 -CCLK │ │ MIC │
│ │ │ PIN17-MIC1N├───────────┤ │
│ I2C_SDA_IO│◄─────────►│PIN19-CDATA │ └─────────┘
│ │ │ │
│ VCC 3.3├───────────┤VCC │
│ │ │ │
│ GND├───────────┤GND │
└─────────────────┘ └──────────────────────────┘
```
Note: Since ESP32-C3 & ESP32-H2 board does not have GPIO 16/17, you can use other available GPIOs instead. In this example, we set GPIO 6/7 as I2C pins for ESP32-C3 and GPIO 8/9 ESP32-H2 and GPIO 16/17 for other chips, same as GPIO 18/19, we use GPIO 2/3 instead.
### Dependency
This example is based on [esp_codec_dev component](https://components.espressif.com/components/espressif/esp_codec_dev)
The component can be installed by [IDF Component Manager](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/tools/idf-component-manager.html). This example already includes it. If you want to install [esp_codec_dev component](https://components.espressif.com/components/espressif/esp_codec_dev) separately in your project, you can input the following command:
```
idf.py add-dependency "espressif/esp_codec_dev^1.3.4"
```
If the dependency is added, you can check `idf_component.yml` for more detail. When building this example or other projects with managed components, the component manager will search for the required components online and download them into the `managed_components` folder.
### Configure the Project
```
idf.py menuconfig
```
You can find configurations for this example in 'Example Configuration' tag.
* In 'Example mode' subtag, you can set the example mode to 'music' or 'echo'. You can hear a piece of music in 'music' mode and echo the sound sampled by mic in 'echo' mode. You can also customize you own music to play as shown below.
* In 'Set MIC gain' subtag, you can set the mic gain for echo mode.
* In 'Voice volume', you can set the volume between 0 to 100.
* In 'Enable Board Support Package (BSP) support' you can enable support for BSP. You can pick specific BSP in [idf_component.yml](main/idf_component.yml).
### Build and Flash
Build the project and flash it to the board, then run monitor tool to view serial output:
```
idf.py -p PORT flash monitor
```
(To exit the serial monitor, type ``Ctrl-]``.)
See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects.
## Example Output
Running this example in music mode, you can hear a piece of music (canon), the log is shown as follow:
```
I (348) I2S: DMA Malloc info, datalen=blocksize=1200, dma_desc_num=6
I (348) I2S: DMA Malloc info, datalen=blocksize=1200, dma_desc_num=6
I (358) I2S: I2S0, MCLK output by GPIO0
I (368) DRV8311: ES8311 in Slave mode
I (378) gpio: GPIO[10]| InputEn: 0| OutputEn: 1| OpenDrain: 0| Pullup: 0| Pulldown: 0| Intr:0
I (3718) i2s_es8311: I2S music played, 213996 bytes are written.
I (7948) i2s_es8311: I2S music played, 213996 bytes are written.
......
```
Running this example in echo mode, you can hear the sound in earphone that collected by mic.
```
I (312) I2S: DMA Malloc info, datalen=blocksize=1200, dma_desc_num=6
I (312) I2S: DMA Malloc info, datalen=blocksize=1200, dma_desc_num=6
I (322) I2S: I2S0, MCLK output by GPIO0
I (332) DRV8311: ES8311 in Slave mode
I (342) gpio: GPIO[10]| InputEn: 0| OutputEn: 1| OpenDrain: 0| Pullup: 0| Pulldown: 0| Intr:0
```
If you have a logic analyzer, you can use a logic analyzer to grab GPIO signal directly. The following table describes the pins we use by default (Note that you can also use other pins for the same purpose).
| pin name| function | gpio_num |
|:---:|:---:|:---:|
| MCLK |module clock | GPIO_NUM_0|
| BCLK |bit clock | GPIO_NUM_4 |
| WS |word select | GPIO_NUM_5 |
| SDOUT |serial data out| GPIO_NUM_18/2 |
| SDIN |serial data in | GPIO_NUM_19/3 |
Other pins like I2C please refer to `example_config.h`.
Please note that the power amplifier on some development boards (like P4 EV board) are disabled by default, you might need to set the PA_CTRL pin to high to play the music via a speaker.
The PA_CTRL pin can be configured by `idf.py menuconfig`, please check if the PA_CTRL pin is correct on your board if the audio can only be played from the earphones but not the speaker.
### Customize your own music
The example have contained a piece of music in canon.pcm, if you want to play your own music, you can follow these steps:
1. Choose the music in any format you want to play (e.g. a.mp3)
2. Install 'ffmpeg' tool
3. Check your music format using ```ffprobe a.mp3```, you can get the stream format (e.g. Stream #0.0: Audio: mp3, 44100Hz, stereo, s16p, 64kb/s)
4. Cut your music since there is no enough space for the whole piece of music. ```ffmpeg -i a.mp3 -ss 00:00:00 -t 00:00:20 a_cut.mp3```
5. Transfer the music format into .pcm. ```ffmpeg -i a_cut.mp3 -f s16le -ar 16000 -ac 2 -acodec pcm_s16le a.pcm```
6. Move 'a.pcm' under 'main' directory
7. Replace 'canon.pcm' with 'a.pcm' in 'CMakeLists.txt' under 'main' directory
8. Replace '_binary_canon_pcm_start' and '_binary_canon_pcm_end' with '_binary_a_pcm_start' and '_binary_a_pcm_end' in `i2s_es8311_example.c`
9. Download the example and enjoy your own music
## Troubleshooting
* Program upload failure
* Hardware connection is not correct: run `idf.py -p PORT monitor`, and reboot your board to see if there are any output logs.
* The baud rate for downloading is too high: lower your baud rate in the `menuconfig` menu, and try again.
* Failed to get audio from specker
* The PA (Power Amplifier) on some dev-kits might be disabled by default, please check the schematic to see if PA_CTRL is connected to any GPIO or something.
* Pull-up the PA_CTRL pin either by setting that GPIO to high or by connecting it to 3.3V with a jump wire should help.
For any technical queries, please open an [issue](https://github.com/espressif/esp-idf/issues) on GitHub. We will get back to you soon.

View File

@@ -1,3 +0,0 @@
idf_component_register(SRCS "i2s_es8311_example.c"
PRIV_REQUIRES esp_driver_i2s esp_driver_i2c esp_driver_gpio
EMBED_FILES "canon.pcm")

View File

@@ -1,78 +0,0 @@
menu "Example Configuration"
menu "Example IO Configuration"
config EXAMPLE_PA_CTRL_IO
int "Power Amplifier control IO"
default 53 if IDF_TARGET_ESP32P4
default -1
help
Set GPIO number for PA control. Set -1 to disable PA control.
config EXAMPLE_I2C_SDA_IO
int "I2C SDA IO"
default 17 if IDF_TARGET_ESP32 || IDF_TARGET_ESP32S2 || IDF_TARGET_ESP32S3 || IDF_TARGET_ESP32H4
default 9 if IDF_TARGET_ESP32H2
default 7
config EXAMPLE_I2C_SCL_IO
int "I2C SCL IO"
default 16 if IDF_TARGET_ESP32 || IDF_TARGET_ESP32S2 || IDF_TARGET_ESP32S3 || IDF_TARGET_ESP32H4
default 8 if IDF_TARGET_ESP32H2 || IDF_TARGET_ESP32P4
default 6
config EXAMPLE_I2S_MCLK_IO
int "I2S MCLK IO"
default 13 if IDF_TARGET_ESP32P4
default 0
config EXAMPLE_I2S_BCLK_IO
int "I2S BCLK IO"
default 12 if IDF_TARGET_ESP32P4
default 4
config EXAMPLE_I2S_WS_IO
int "I2S WS IO"
default 10 if IDF_TARGET_ESP32P4
default 5
config EXAMPLE_I2S_DIN_IO
int "I2S DIN IO"
default 19 if IDF_TARGET_ESP32 || IDF_TARGET_ESP32S2 || IDF_TARGET_ESP32S3
default 11 if IDF_TARGET_ESP32P4
default 3
config EXAMPLE_I2S_DOUT_IO
int "I2S DOUT IO"
default 18 if IDF_TARGET_ESP32 || IDF_TARGET_ESP32S2 || IDF_TARGET_ESP32S3
default 9 if IDF_TARGET_ESP32P4
default 2
endmenu
choice EXAMPLE_MODE
prompt "Example mode"
default EXAMPLE_MODE_MUSIC
help
Select example work mode
config EXAMPLE_MODE_MUSIC
bool "music"
config EXAMPLE_MODE_ECHO
depends on !EXAMPLE_BSP
bool "echo"
endchoice
config EXAMPLE_MIC_GAIN
int "Set MIC gain (unit: dB)"
depends on EXAMPLE_MODE_ECHO
default 18
help
Select mic gain for echo mode
config EXAMPLE_VOICE_VOLUME
int "Voice volume"
range 0 100
default 60
help
Set voice volume
endmenu

View File

@@ -1,33 +0,0 @@
/*
* SPDX-FileCopyrightText: 2021-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: CC0-1.0
*/
#pragma once
#include "sdkconfig.h"
/* Example configurations */
#define EXAMPLE_RECV_BUF_SIZE (2400)
#define EXAMPLE_SAMPLE_RATE (16000)
#define EXAMPLE_MCLK_MULTIPLE (384) // If not using 24-bit data width, 256 should be enough
#define EXAMPLE_MCLK_FREQ_HZ (EXAMPLE_SAMPLE_RATE * EXAMPLE_MCLK_MULTIPLE)
#define EXAMPLE_VOICE_VOLUME CONFIG_EXAMPLE_VOICE_VOLUME
#define EXAMPLE_PA_CTRL_IO CONFIG_EXAMPLE_PA_CTRL_IO
#if CONFIG_EXAMPLE_MODE_ECHO
#define EXAMPLE_MIC_GAIN CONFIG_EXAMPLE_MIC_GAIN
#endif
/* I2C port and GPIOs */
#define I2C_NUM (0)
#define I2C_SCL_IO CONFIG_EXAMPLE_I2C_SCL_IO
#define I2C_SDA_IO CONFIG_EXAMPLE_I2C_SDA_IO
/* I2S port and GPIOs */
#define I2S_NUM (0)
#define I2S_MCK_IO CONFIG_EXAMPLE_I2S_MCLK_IO
#define I2S_BCK_IO CONFIG_EXAMPLE_I2S_BCLK_IO
#define I2S_WS_IO CONFIG_EXAMPLE_I2S_WS_IO
#define I2S_DO_IO CONFIG_EXAMPLE_I2S_DOUT_IO
#define I2S_DI_IO CONFIG_EXAMPLE_I2S_DIN_IO

View File

@@ -1,251 +0,0 @@
/*
* SPDX-FileCopyrightText: 2021-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: CC0-1.0
*/
#include <stdio.h>
#include <string.h>
#include "sdkconfig.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/i2s_std.h"
#include "driver/i2c_master.h"
#include "driver/gpio.h"
#include "esp_system.h"
#include "esp_codec_dev_defaults.h"
#include "esp_codec_dev.h"
#include "esp_codec_dev_vol.h"
#include "esp_check.h"
#include "example_config.h"
static const char *TAG = "i2s_es8311";
static const char err_reason[][30] = {"input param is invalid",
"operation timeout"
};
static i2s_chan_handle_t tx_handle = NULL;
static i2s_chan_handle_t rx_handle = NULL;
/* 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 esp_err_t es8311_codec_init(void)
{
/* Initialize I2C peripheral */
i2c_master_bus_handle_t i2c_bus_handle = NULL;
i2c_master_bus_config_t i2c_mst_cfg = {
.i2c_port = I2C_NUM,
.sda_io_num = I2C_SDA_IO,
.scl_io_num = I2C_SCL_IO,
.clk_source = I2C_CLK_SRC_DEFAULT,
.glitch_ignore_cnt = 7,
/* Pull-up internally for no external pull-up case.
Suggest to use external pull-up to ensure a strong enough pull-up. */
.flags.enable_internal_pullup = true,
};
ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_mst_cfg, &i2c_bus_handle));
/* Create control interface with I2C bus handle */
audio_codec_i2c_cfg_t i2c_cfg = {
.port = I2C_NUM,
.addr = ES8311_CODEC_DEFAULT_ADDR,
.bus_handle = i2c_bus_handle,
};
const audio_codec_ctrl_if_t *ctrl_if = audio_codec_new_i2c_ctrl(&i2c_cfg);
assert(ctrl_if);
/* Create data interface with I2S bus handle */
audio_codec_i2s_cfg_t i2s_cfg = {
.port = I2S_NUM,
.rx_handle = rx_handle,
.tx_handle = tx_handle,
};
const audio_codec_data_if_t *data_if = audio_codec_new_i2s_data(&i2s_cfg);
assert(data_if);
/* Create ES8311 interface handle */
const audio_codec_gpio_if_t *gpio_if = audio_codec_new_gpio();
assert(gpio_if);
es8311_codec_cfg_t es8311_cfg = {
.ctrl_if = ctrl_if,
.gpio_if = gpio_if,
.codec_mode = ESP_CODEC_DEV_WORK_MODE_BOTH,
.master_mode = false,
.use_mclk = I2S_MCK_IO >= 0,
.pa_pin = EXAMPLE_PA_CTRL_IO,
.pa_reverted = false,
.hw_gain = {
.pa_voltage = 5.0,
.codec_dac_voltage = 3.3,
},
.mclk_div = EXAMPLE_MCLK_MULTIPLE,
};
const audio_codec_if_t *es8311_if = es8311_codec_new(&es8311_cfg);
assert(es8311_if);
/* Create the top codec handle with ES8311 interface handle and data interface */
esp_codec_dev_cfg_t dev_cfg = {
.dev_type = ESP_CODEC_DEV_TYPE_IN_OUT,
.codec_if = es8311_if,
.data_if = data_if,
};
esp_codec_dev_handle_t codec_handle = esp_codec_dev_new(&dev_cfg);
assert(codec_handle);
/* Specify the sample configurations and open the device */
esp_codec_dev_sample_info_t sample_cfg = {
.bits_per_sample = I2S_DATA_BIT_WIDTH_16BIT,
.channel = 2,
.channel_mask = 0x03,
.sample_rate = EXAMPLE_SAMPLE_RATE,
.mclk_multiple = EXAMPLE_MCLK_MULTIPLE,
};
if (esp_codec_dev_open(codec_handle, &sample_cfg) != ESP_CODEC_DEV_OK) {
ESP_LOGE(TAG, "Open codec device failed");
return ESP_FAIL;
}
/* Set the initial volume and gain */
if (esp_codec_dev_set_out_vol(codec_handle, EXAMPLE_VOICE_VOLUME) != ESP_CODEC_DEV_OK) {
ESP_LOGE(TAG, "set output volume failed");
return ESP_FAIL;
}
#if CONFIG_EXAMPLE_MODE_ECHO
if (esp_codec_dev_set_in_gain(codec_handle, EXAMPLE_MIC_GAIN) != ESP_CODEC_DEV_OK) {
ESP_LOGE(TAG, "set input gain failed");
return ESP_FAIL;
}
#endif
return ESP_OK;
}
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,
},
},
};
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;
}
#if CONFIG_EXAMPLE_MODE_MUSIC
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);
}
#else
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);
}
#endif
void app_main(void)
{
printf("i2s es8311 codec example start\n-----------------------------\n");
/* Initialize i2s peripheral */
if (i2s_driver_init() != ESP_OK) {
ESP_LOGE(TAG, "i2s driver init failed");
abort();
} else {
ESP_LOGI(TAG, "i2s driver init success");
}
/* Initialize i2c peripheral and config es8311 codec by i2c */
if (es8311_codec_init() != ESP_OK) {
ESP_LOGE(TAG, "es8311 codec init failed");
abort();
} else {
ESP_LOGI(TAG, "es8311 codec init success");
}
#if CONFIG_EXAMPLE_MODE_MUSIC
/* Play a piece of music in music mode */
xTaskCreate(i2s_music, "i2s_music", 4096, NULL, 5, NULL);
#else
/* Echo the sound from MIC in echo mode */
xTaskCreate(i2s_echo, "i2s_echo", 8192, NULL, 5, NULL);
#endif
}

View File

@@ -1,5 +0,0 @@
## IDF Component Manager Manifest File
dependencies:
espressif/esp_codec_dev: ^1.6.0
i2s_examples_common:
path: ${IDF_PATH}/examples/peripherals/i2s/i2s_examples_common

View File

@@ -1,36 +0,0 @@
# SPDX-FileCopyrightText: 2021-2025 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: CC0-1.0
import pytest
from pytest_embedded import Dut
from pytest_embedded_idf.utils import idf_parametrize
from pytest_embedded_idf.utils import soc_filtered_targets
@pytest.mark.generic
@idf_parametrize(
'target',
soc_filtered_targets('SOC_I2S_SUPPORTED == 1'),
indirect=['target'],
)
@pytest.mark.temp_skip_ci(targets=['esp32h21'], reason='lack of runners')
def test_i2s_es8311_example_generic(dut: Dut) -> None:
dut.expect('i2s es8311 codec example start')
dut.expect('-----------------------------')
dut.expect('i2s_es8311: i2s driver init success')
@pytest.mark.es8311
@pytest.mark.parametrize(
'config',
[
's3_korvo2_v3',
],
indirect=True,
)
@idf_parametrize('target', ['esp32s3'], indirect=['target'])
def test_i2s_es8311_example_korvo2_v3(dut: Dut) -> None:
dut.expect(r'i2s es8311 codec example start')
dut.expect(r'-----------------------------')
dut.expect(r'i2s_es8311: i2s driver init success')
dut.expect(r'i2s_es8311: es8311 codec init success')
dut.expect(r'Returned from app_main\(\)')

View File

@@ -1,8 +0,0 @@
CONFIG_EXAMPLE_PA_CTRL_IO=48
CONFIG_EXAMPLE_I2C_SDA_IO=17
CONFIG_EXAMPLE_I2C_SCL_IO=18
CONFIG_EXAMPLE_I2S_MCLK_IO=16
CONFIG_EXAMPLE_I2S_BCLK_IO=9
CONFIG_EXAMPLE_I2S_WS_IO=45
CONFIG_EXAMPLE_I2S_DIN_IO=10
CONFIG_EXAMPLE_I2S_DOUT_IO=8

View File

@@ -1 +0,0 @@
CONFIG_CODEC_I2C_BACKWARD_COMPATIBLE=n

View File

@@ -72,7 +72,7 @@ menu "Example Configuration"
config EXAMPLE_I2S_MCLK_IO
int "I2S MCLK GPIO (set -1 if the codec does not use an external MCLK)"
default 2
default -1
help
Set the GPIO number used for the I2S MCLK signal. When set to -1, no MCLK
is generated and the ES8389 derives its clock from BCLK.

View File

@@ -132,10 +132,36 @@ static void record_from_pdm_microphone(uint8_t *pcm_data, size_t pcm_size)
#endif
#if CONFIG_EXAMPLE_MIC_TYPE_AMIC
static i2s_chan_handle_t i2s_rx_init(void)
{
/* Simplex RX. I2S_ROLE_MASTER: the SoC drives BCLK/WS (and optional MCLK)
* so the ES8389 can run as I2S slave. */
i2s_chan_handle_t rx_handle = NULL;
i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_AUTO, I2S_ROLE_MASTER);
ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, NULL, &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(EXAMPLE_BITS_PER_SAMPLE, EXAMPLE_CHANNEL_COUNT),
.gpio_cfg = {
.mclk = CONFIG_EXAMPLE_I2S_MCLK_IO,
.bclk = CONFIG_EXAMPLE_I2S_BCLK_IO,
.ws = CONFIG_EXAMPLE_I2S_WS_IO,
.din = CONFIG_EXAMPLE_I2S_DIN_IO,
.dout = GPIO_NUM_NC,
},
};
ESP_ERROR_CHECK(i2s_channel_init_std_mode(rx_handle, &std_cfg));
/* Clocks must be running before the codec PLL is started over I2C. */
ESP_ERROR_CHECK(i2s_channel_enable(rx_handle));
return rx_handle;
}
static void record_from_es8389_microphone(uint8_t *pcm_data, size_t pcm_size)
{
/* The analog signal is sampled by the ES8389 ADC. I2S RX receives its digital samples
* and, as bus master, generates the BCLK/WS (and optional MCLK) required by the codec. */
i2s_chan_handle_t rx_handle = i2s_rx_init();
i2c_master_bus_handle_t i2c_bus_handle = NULL;
i2c_master_bus_config_t i2c_bus_cfg = {
.clk_source = I2C_CLK_SRC_DEFAULT,
@@ -149,7 +175,6 @@ static void record_from_es8389_microphone(uint8_t *pcm_data, size_t pcm_size)
};
ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_handle));
/* I2C configures codec registers; PCM samples use I2S. */
audio_codec_i2c_cfg_t i2c_cfg = {
.bus_handle = i2c_bus_handle,
.addr = ES8389_CODEC_DEFAULT_ADDR,
@@ -162,33 +187,12 @@ static void record_from_es8389_microphone(uint8_t *pcm_data, size_t pcm_size)
.ctrl_if = ctrl_if,
.codec_mode = ESP_CODEC_DEV_WORK_MODE_ADC,
.pa_pin = GPIO_NUM_NC,
/* When MCLK is unused, the ES8389 derives its clock from BCLK. */
.use_mclk = CONFIG_EXAMPLE_I2S_MCLK_IO >= 0,
.mclk_div = EXAMPLE_MCLK_MULTIPLE,
};
const audio_codec_if_t *codec_if = es8389_codec_new(&es8389_cfg);
ESP_ERROR_CHECK(codec_if ? ESP_OK : ESP_FAIL);
/* Recording needs only an RX channel. In master mode, RX also drives BCLK/WS. */
i2s_chan_handle_t rx_handle = NULL;
i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_AUTO, I2S_ROLE_MASTER);
ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, NULL, &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(EXAMPLE_BITS_PER_SAMPLE, EXAMPLE_CHANNEL_COUNT),
.gpio_cfg = {
.mclk = CONFIG_EXAMPLE_I2S_MCLK_IO,
.bclk = CONFIG_EXAMPLE_I2S_BCLK_IO,
.ws = CONFIG_EXAMPLE_I2S_WS_IO,
.dout = GPIO_NUM_NC,
.din = CONFIG_EXAMPLE_I2S_DIN_IO,
},
};
ESP_ERROR_CHECK(i2s_channel_init_std_mode(rx_handle, &std_cfg));
ESP_ERROR_CHECK(i2s_channel_enable(rx_handle));
/* Pass the already-configured RX channel to the codec data interface. */
audio_codec_i2s_cfg_t i2s_cfg = {
.rx_handle = rx_handle,
};
@@ -200,8 +204,8 @@ static void record_from_es8389_microphone(uint8_t *pcm_data, size_t pcm_size)
.codec_if = codec_if,
.data_if = data_if,
};
esp_codec_dev_handle_t codec_handle = esp_codec_dev_new(&dev_cfg);
ESP_ERROR_CHECK(codec_handle ? ESP_OK : ESP_FAIL);
esp_codec_dev_handle_t codec_dev = esp_codec_dev_new(&dev_cfg);
ESP_ERROR_CHECK(codec_dev ? ESP_OK : ESP_FAIL);
esp_codec_dev_sample_info_t sample_cfg = {
.bits_per_sample = EXAMPLE_BITS_PER_SAMPLE,
@@ -210,8 +214,8 @@ static void record_from_es8389_microphone(uint8_t *pcm_data, size_t pcm_size)
.sample_rate = EXAMPLE_SAMPLE_RATE,
.mclk_multiple = EXAMPLE_MCLK_MULTIPLE,
};
ESP_ERROR_CHECK(esp_codec_dev_open(codec_handle, &sample_cfg));
ESP_ERROR_CHECK(esp_codec_dev_set_in_gain(codec_handle, CONFIG_EXAMPLE_MIC_GAIN));
ESP_ERROR_CHECK(esp_codec_dev_open(codec_dev, &sample_cfg));
ESP_ERROR_CHECK(esp_codec_dev_set_in_gain(codec_dev, CONFIG_EXAMPLE_MIC_GAIN));
ESP_LOGI(TAG, "ES8389 codec initialized");
/* Discard the startup PCM to skip the microphone or codec startup transient. */
@@ -219,9 +223,8 @@ static void record_from_es8389_microphone(uint8_t *pcm_data, size_t pcm_size)
ESP_LOGI(TAG, "Starting ES8389 recording for %d seconds!", EXAMPLE_RECORD_TIME_SECONDS);
capture_pcm(rx_handle, pcm_data, pcm_size);
/* Closing the input device disables RX before its channel is deleted. */
ESP_ERROR_CHECK(esp_codec_dev_close(codec_handle));
esp_codec_dev_delete(codec_handle);
ESP_ERROR_CHECK(esp_codec_dev_close(codec_dev));
esp_codec_dev_delete(codec_dev);
ESP_ERROR_CHECK(audio_codec_delete_codec_if(codec_if));
ESP_ERROR_CHECK(audio_codec_delete_data_if(data_if));
ESP_ERROR_CHECK(audio_codec_delete_ctrl_if(ctrl_if));

View File

@@ -6,4 +6,4 @@ include($ENV{IDF_PATH}/tools/cmake/project.cmake)
# "Trim" the build. Include the minimal set of components, main, and anything it depends on.
idf_build_set_property(MINIMAL_BUILD ON)
project(i2s-es8311-example)
project(wav_player_example)

View File

@@ -0,0 +1,122 @@
| Supported Targets | ESP32 | ESP32-C3 | ESP32-C5 | ESP32-C6 | ESP32-C61 | ESP32-H2 | ESP32-H21 | ESP32-H4 | ESP32-P4 | ESP32-S2 | ESP32-S3 | ESP32-S31 |
| ----------------- | ----- | -------- | -------- | -------- | --------- | -------- | --------- | -------- | -------- | -------- | -------- | --------- |
# I2S WAV Player Example
(See the README.md file in the upper level 'examples' directory for more information about examples.)
This example plays an embedded WAV file through a codec DAC over I2S STD TX. It supports two codecs, selectable in menuconfig:
* **ES8311** — low-power mono ADC/DAC codec (for example ESP-BOX and ESP32-S3-Korvo-2).
* **ES8389** — stereo ADC/DAC codec (for example ESP32-S31-Korvo-1).
The firmware embeds `assets/music.wav` in flash and loops it on the TX channel. A standard PCM WAV is a short RIFF header plus little-endian signed PCM; the example finds the `data` chunk and hands that payload to I2S. The clip stays mapped in flash and is copied in DMA-sized pieces, not into a RAM buffer. There is no microphone path. For recording, see [mic_recorder](../mic_recorder).
| Audio Setting | Value |
|:---:|:---:|
| Sample Rate |16000 Hz|
| Bits per Sample |16 bits|
| Channels |Stereo|
| Source |Embedded `assets/music.wav`|
These settings are defined as macros at the top of `main/wav_player_example_main.c` (see `EXAMPLE_SAMPLE_RATE`, `EXAMPLE_BITS_PER_SAMPLE`, etc.). **A replacement WAV must be uncompressed signed 16-bit PCM with the same rate, width, and channel count.**
`music.wav` is an excerpt of *Canon in D Major (piano only)* by [Ava](https://freemusicarchive.org/music/ava-drumm/single/canon-in-d-major-piano-only), released on the Free Music Archive under [CC0 1.0 Universal](https://creativecommons.org/publicdomain/zero/1.0/). The clip was resampled for this example. See [assets/README.md](assets/README.md) for the full provenance.
## How to Use Example
### Hardware Required
* A development board with a SoC that supports I2S STD and I2C (see `Supported Targets` table above).
* A USB cable for power supply and programming.
* A board or module with an ES8311 or ES8389 codec and a headphone or speaker output.
### Connection
```
┌─────────────────┐ ┌──────────────────────────┐
│ ESP │ │ ES8311 / ES8389 │
│ │ │ │
│ I2S_MCK_IO├──────────►│MCLK │
│ │ │ │ ┌─────────┐
│ I2S_BCK_IO├──────────►│BCLK analog out├───────────┤ │
│ │ │ │ │ EARPHONE│
│ I2S_WS_IO├──────────►│LRCK / WS │ │ / SPK │
│ │ │ │ └─────────┘
│ I2S_DOUT_IO├──────────►│SDIN │
│ │ │ │
│ I2C_SCL_IO├──────────►│SCL / CCLK │
│ │ │ │
│ I2C_SDA_IO│◄─────────►│SDA / CDATA │
│ │ │ │
│ PA_CTRL ├──────────►│amp EN / CTRL (optional) │
│ │ │ │
│ VCC 3.3├───────────┤VCC │
│ GND├───────────┤GND │
└─────────────────┘ └──────────────────────────┘
```
The codec chip has no amplifier pin. `PA_CTRL` is the MCU GPIO that enables the board's speaker amplifier (HT8691, NS4150, and similar). Check the schematic: the enable is often labeled `EN`, `CTRL`, `PA`, or even `INT`. Set `Power amplifier enable IO` in menuconfig to that GPIO so the codec driver raises it when the DAC starts. Set it to `-1` if the enable is already strapped high or the board has no amplifier. A disabled amplifier is a common reason for silent speakers while headphones still work.
Assign every signal in the diagram to your board's GPIOs under `Example Configuration``Example IO Configuration` in menuconfig. Some targets also ship extra defaults in `sdkconfig.defaults.<target>`; `idf.py set-target` applies those automatically.
### Dependency
This example is based on [esp_codec_dev](https://components.espressif.com/components/espressif/esp_codec_dev). The component is already listed in `main/idf_component.yml` and is fetched by the [IDF Component Manager](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/tools/idf-component-manager.html) on the first build.
To add the same dependency to another project:
```
idf.py add-dependency "espressif/esp_codec_dev^1.6.0"
```
### Configure the Project
```
idf.py menuconfig
```
In the `Example Configuration` menu:
* Use `DAC / codec type` to select ES8311 or ES8389.
* Use `Example IO Configuration` to assign the shared I2C, I2S, and amplifier-enable GPIOs.
* Use `Voice volume` to set the output volume (0 to 100).
### Build and Flash
Build the project and flash it to the board, then run monitor tool to view serial output:
```
idf.py -p PORT flash monitor
```
(To exit the serial monitor, type ``Ctrl-]``.)
See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects.
* [ESP-IDF Getting Started Guide on ESP32](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/get-started/index.html)
## Example Output
```
wav player example start
--------------------------
I (...) wav_player: i2s driver init success
I (...) wav_player: ES8311 codec init success
I (...) wav_player: i2s music played, ... bytes are written.
I (...) wav_player: i2s music played, ... bytes are written.
......
```
An ES8389 run looks the same, except the codec init line names ES8389.
If you have a logic analyzer, probe the I2S signals on the GPIOs you set in `Example IO Configuration`.
## Troubleshooting
* No sound from the speaker or headphone
* Confirm `DAC / codec type` and the I2C/I2S GPIOs match the board. Playback data goes to the codec **SDI** (SDIN) pin, not SDO.
* If the board has no MCLK pin (ESP32-S31-Korvo-1 does not), set `I2S MCLK IO` to -1 so the codec derives its clock from BCLK. Driving an unconnected MCLK GPIO while the codec waits for external MCLK produces silence.
* Check the schematic and set `Power amplifier enable IO` to the speaker-amplifier enable (`EN` / `CTRL` / `PA` / `INT`). Set the option to -1 if that pin is already strapped high.
For any technical queries, please open an [issue](https://github.com/espressif/esp-idf/issues) on GitHub. We will get back to you soon.

View File

@@ -0,0 +1,16 @@
# Audio clip provenance
`music.wav` is a short excerpt of the following recording, converted for this example to 16 kHz, signed 16-bit, stereo PCM:
| Field | Value |
| --- | --- |
| Title | Canon in D Major (piano only) |
| Artist | Ava |
| Source | https://freemusicarchive.org/music/ava-drumm/single/canon-in-d-major-piano-only |
| Released | 12 August 2021 |
| License | [CC0 1.0 Universal](https://creativecommons.org/publicdomain/zero/1.0/) |
The recording is a piano performance of Johann Pachelbel's *Canon in D*.
Pachelbel's composition is in the public domain. Ava released this performance under CC0 1.0, dedicating it to the public domain.
The example firmware only embeds this file; it does not change the license of the recording.

Binary file not shown.

View File

@@ -0,0 +1,8 @@
idf_component_register(SRCS "wav_player_example_main.c"
PRIV_REQUIRES esp_driver_i2s esp_driver_i2c esp_driver_gpio
INCLUDE_DIRS .)
# Embed the WAV as-is. The firmware skips the RIFF header and plays the signed
# 16-bit PCM payload from flash; no build-time conversion and no RAM copy of the clip.
target_add_binary_data(${COMPONENT_LIB} "${CMAKE_CURRENT_LIST_DIR}/../assets/music.wav"
BINARY RENAME_TO "music_wav")

View File

@@ -0,0 +1,79 @@
menu "Example Configuration"
choice EXAMPLE_DAC_TYPE
prompt "DAC / codec type"
default EXAMPLE_DAC_TYPE_ES8311
help
Select the DAC codec connected to the board. Both options use I2S STD TX
to send PCM and I2C to configure the codec:
- ES8311: low-power mono ADC/DAC codec used on boards such as ESP-BOX
and ESP32-S3-Korvo-2.
- ES8389: stereo ADC/DAC codec used on boards such as ESP32-S31-Korvo-1.
config EXAMPLE_DAC_TYPE_ES8311
bool "ES8311 codec"
select CODEC_ES8311_SUPPORT
config EXAMPLE_DAC_TYPE_ES8389
bool "ES8389 codec"
select CODEC_ES8389_SUPPORT
endchoice
menu "Example IO Configuration"
config EXAMPLE_PA_CTRL_IO
int "Power amplifier enable IO"
default -1
help
GPIO connected to the board's speaker-amplifier enable
(often labeled EN, CTRL, PA, or INT). This is not a pin
on the ES8311 or ES8389. The codec driver raises it when
the DAC starts. Set -1 if the board has no amplifier
enable, or if that pin is already strapped high.
config EXAMPLE_I2C_SDA_IO
int "I2C SDA IO"
default 0
help
Set the GPIO number used for the I2C SDA signal towards the codec.
config EXAMPLE_I2C_SCL_IO
int "I2C SCL IO"
default 1
help
Set the GPIO number used for the I2C SCL signal towards the codec.
config EXAMPLE_I2S_MCLK_IO
int "I2S MCLK IO (set -1 if the codec does not use an external MCLK)"
default -1
help
Set the GPIO number used for the I2S MCLK signal. When set to -1, no MCLK
is generated and the codec derives its clock from BCLK.
config EXAMPLE_I2S_BCLK_IO
int "I2S BCLK IO"
default 3
help
Set the GPIO number used for the I2S BCLK signal towards the codec.
config EXAMPLE_I2S_WS_IO
int "I2S WS/LRCK IO"
default 4
help
Set the GPIO number used for the I2S WS/LRCK signal towards the codec.
config EXAMPLE_I2S_DOUT_IO
int "I2S DOUT IO"
default 7
help
Set the GPIO number used for sending PCM data to the codec.
endmenu
config EXAMPLE_VOICE_VOLUME
int "Voice volume"
range 0 100
default 60
help
Set the codec output volume.
endmenu

View File

@@ -0,0 +1,3 @@
## IDF Component Manager Manifest File
dependencies:
espressif/esp_codec_dev: "^1.6.0"

View File

@@ -0,0 +1,289 @@
/*
* SPDX-FileCopyrightText: 2021-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#include <stdio.h>
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
#include "sdkconfig.h"
#include "esp_err.h"
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/i2s_std.h"
#include "driver/i2c_master.h"
#include "driver/gpio.h"
#include "esp_codec_dev.h"
#include "esp_codec_dev_defaults.h"
static const char *TAG = "wav_player";
/* These four values are the I2S slot format AND the WAV / codec format. They must match. */
#define EXAMPLE_SAMPLE_RATE 16000
#define EXAMPLE_BITS_PER_SAMPLE 16
#define EXAMPLE_CHANNEL_COUNT 2
#define EXAMPLE_STEREO_CHANNEL_MASK 0x03 /* Bit0 = left, bit1 = right. */
/* Most codecs expect MCLK = 256 * LRCK (sample rate) when an external MCLK pin is wired. */
#define EXAMPLE_MCLK_MULTIPLE 256
#define EXAMPLE_I2C_CLOCK_HZ 100000
#if CONFIG_EXAMPLE_DAC_TYPE_ES8311
#define EXAMPLE_CODEC_I2C_ADDR ES8311_CODEC_DEFAULT_ADDR
#define EXAMPLE_CODEC_NAME "ES8311"
#else
#define EXAMPLE_CODEC_I2C_ADDR ES8389_CODEC_DEFAULT_ADDR
#define EXAMPLE_CODEC_NAME "ES8389"
#endif
/* CMake embeds the WAV in flash as a binary blob (target_add_binary_data). */
extern const uint8_t music_wav_start[] asm("_binary_music_wav_start");
extern const uint8_t music_wav_end[] asm("_binary_music_wav_end");
static bool fourcc_eq(const uint8_t *p, const char *id)
{
return memcmp(p, id, 4) == 0;
}
static uint16_t read_le16(const uint8_t *p)
{
return (uint16_t)p[0] | ((uint16_t)p[1] << 8);
}
static uint32_t read_le32(const uint8_t *p)
{
return (uint32_t)p[0] | ((uint32_t)p[1] << 8) | ((uint32_t)p[2] << 16) | ((uint32_t)p[3] << 24);
}
/* I2S transmits raw PCM only. A WAV file is a RIFF container: skip "fmt " / "data"
* chunks until the little-endian PCM payload, and reject compressed encodings. */
static esp_err_t wav_get_pcm(const uint8_t *wav, size_t wav_len, const uint8_t **pcm, size_t *pcm_len)
{
if (wav_len < 12 || !fourcc_eq(wav, "RIFF") || !fourcc_eq(wav + 8, "WAVE")) {
ESP_LOGE(TAG, "not a RIFF/WAVE file");
return ESP_ERR_INVALID_ARG;
}
const uint8_t *fmt = NULL;
uint32_t fmt_size = 0;
const uint8_t *data = NULL;
uint32_t data_size = 0;
for (size_t off = 12; off + 8 <= wav_len;) {
uint32_t size = read_le32(wav + off + 4);
size_t payload = off + 8;
if (size > wav_len - payload) {
ESP_LOGE(TAG, "WAV chunk overruns file");
return ESP_ERR_INVALID_SIZE;
}
if (fourcc_eq(wav + off, "fmt ")) {
fmt = wav + payload;
fmt_size = size;
} else if (fourcc_eq(wav + off, "data")) {
data = wav + payload;
data_size = size;
break;
}
off = payload + size + (size & 1U);
}
if (fmt == NULL || fmt_size < 16 || data == NULL || data_size == 0) {
ESP_LOGE(TAG, "WAV missing fmt or data chunk");
return ESP_ERR_NOT_FOUND;
}
uint16_t audio_format = read_le16(fmt);
uint16_t channels = read_le16(fmt + 2);
uint32_t sample_rate = read_le32(fmt + 4);
uint16_t bits = read_le16(fmt + 14);
if (audio_format != 1) {
ESP_LOGE(TAG, "WAV is not uncompressed PCM (format %u)", audio_format);
return ESP_ERR_NOT_SUPPORTED;
}
if (channels != EXAMPLE_CHANNEL_COUNT ||
sample_rate != EXAMPLE_SAMPLE_RATE ||
bits != EXAMPLE_BITS_PER_SAMPLE) {
ESP_LOGE(TAG, "WAV is %u Hz / %u-bit / %u ch, expected %u / %u / %u",
(unsigned)sample_rate, bits, channels,
EXAMPLE_SAMPLE_RATE, EXAMPLE_BITS_PER_SAMPLE, EXAMPLE_CHANNEL_COUNT);
return ESP_ERR_INVALID_ARG;
}
*pcm = data;
*pcm_len = data_size;
ESP_LOGI(TAG, "WAV PCM payload %u bytes", (unsigned)data_size);
return ESP_OK;
}
static const audio_codec_if_t *create_dac_codec_if(const audio_codec_ctrl_if_t *ctrl_if,
const audio_codec_gpio_if_t *gpio_if)
{
/* The codec is the I2S slave: it receives BCLK/WS (and optional MCLK) from the SoC.
* pa_pin is the board amplifier enable, not a pin on the codec die. */
#if CONFIG_EXAMPLE_DAC_TYPE_ES8311
es8311_codec_cfg_t dac_cfg = {
.ctrl_if = ctrl_if,
.gpio_if = gpio_if,
.codec_mode = ESP_CODEC_DEV_WORK_MODE_DAC,
.master_mode = false,
.use_mclk = CONFIG_EXAMPLE_I2S_MCLK_IO >= 0,
.pa_pin = CONFIG_EXAMPLE_PA_CTRL_IO,
.hw_gain = {
.pa_voltage = 5.0,
.codec_dac_voltage = 3.3,
},
.mclk_div = EXAMPLE_MCLK_MULTIPLE,
};
return es8311_codec_new(&dac_cfg);
#else
es8389_codec_cfg_t dac_cfg = {
.ctrl_if = ctrl_if,
.gpio_if = gpio_if,
.codec_mode = ESP_CODEC_DEV_WORK_MODE_DAC,
.master_mode = false,
.use_mclk = CONFIG_EXAMPLE_I2S_MCLK_IO >= 0,
.pa_pin = CONFIG_EXAMPLE_PA_CTRL_IO,
.pa_reverted = false,
.hw_gain = {
.pa_voltage = 5.0,
.codec_dac_voltage = 3.3,
},
.mclk_div = EXAMPLE_MCLK_MULTIPLE,
};
return es8389_codec_new(&dac_cfg);
#endif
}
static i2s_chan_handle_t i2s_driver_init(void)
{
/* Simplex TX: only a transmit channel. I2S_ROLE_MASTER means the SoC generates
* BCLK and WS. The codec must be configured as I2S slave (see create_dac_codec_if).
* auto_clear writes zeros if the DMA ring underruns, which avoids a stuck last sample. */
i2s_chan_handle_t tx_handle = NULL;
i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_AUTO, I2S_ROLE_MASTER);
chan_cfg.auto_clear = true;
ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, &tx_handle, NULL));
/* Standard (Philips) I2S: WS low = left slot, WS high = right slot.
* Each slot is EXAMPLE_BITS_PER_SAMPLE bits. Stereo 16-bit uses
* BCLK = sample_rate * 32. MCLK, if used, is sample_rate * EXAMPLE_MCLK_MULTIPLE. */
i2s_std_config_t std_cfg = {
.clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(EXAMPLE_SAMPLE_RATE),
.slot_cfg = I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG(EXAMPLE_BITS_PER_SAMPLE, EXAMPLE_CHANNEL_COUNT),
.gpio_cfg = {
.mclk = CONFIG_EXAMPLE_I2S_MCLK_IO,
.bclk = CONFIG_EXAMPLE_I2S_BCLK_IO,
.ws = CONFIG_EXAMPLE_I2S_WS_IO,
.dout = CONFIG_EXAMPLE_I2S_DOUT_IO,
.din = GPIO_NUM_NC,
},
};
ESP_ERROR_CHECK(i2s_channel_init_std_mode(tx_handle, &std_cfg));
/* Enable now so BCLK/WS (and MCLK) are already running when the codec PLL starts. */
ESP_ERROR_CHECK(i2s_channel_enable(tx_handle));
return tx_handle;
}
static void dac_codec_init(i2s_chan_handle_t tx_handle)
{
/* I2C programs codec registers (volume, mute, clock source, PA). PCM is still
* written with i2s_channel_write(); the handle is only handed to the codec
* so esp_codec_dev_open() can match the slot format already set on I2S. */
i2c_master_bus_handle_t i2c_bus_handle = NULL;
i2c_master_bus_config_t i2c_bus_cfg = {
.clk_source = I2C_CLK_SRC_DEFAULT,
.i2c_port = -1, /* Let the driver select a free I2C port */
.sda_io_num = CONFIG_EXAMPLE_I2C_SDA_IO,
.scl_io_num = CONFIG_EXAMPLE_I2C_SCL_IO,
.glitch_ignore_cnt = 7,
/* Enable the internal pull-ups in case there is no external pull-up on the board.
* External pull-ups are recommended to make the bus more robust. */
.flags.enable_internal_pullup = true,
};
ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_handle));
audio_codec_i2c_cfg_t i2c_cfg = {
.bus_handle = i2c_bus_handle,
.addr = EXAMPLE_CODEC_I2C_ADDR,
.clock_speed_hz = EXAMPLE_I2C_CLOCK_HZ,
};
const audio_codec_ctrl_if_t *ctrl_if = audio_codec_new_i2c_ctrl(&i2c_cfg);
ESP_ERROR_CHECK(ctrl_if ? ESP_OK : ESP_FAIL);
const audio_codec_gpio_if_t *gpio_if = audio_codec_new_gpio();
ESP_ERROR_CHECK(gpio_if ? ESP_OK : ESP_FAIL);
const audio_codec_if_t *codec_if = create_dac_codec_if(ctrl_if, gpio_if);
ESP_ERROR_CHECK(codec_if ? ESP_OK : ESP_FAIL);
audio_codec_i2s_cfg_t i2s_cfg = {
.tx_handle = tx_handle,
};
const audio_codec_data_if_t *data_if = audio_codec_new_i2s_data(&i2s_cfg);
ESP_ERROR_CHECK(data_if ? ESP_OK : ESP_FAIL);
esp_codec_dev_cfg_t dev_cfg = {
.dev_type = ESP_CODEC_DEV_TYPE_OUT,
.codec_if = codec_if,
.data_if = data_if,
};
esp_codec_dev_handle_t codec_dev = esp_codec_dev_new(&dev_cfg);
ESP_ERROR_CHECK(codec_dev ? ESP_OK : ESP_FAIL);
/* open() applies the same sample format to the codec (and keeps the I2S
* channel in sync), then enables the DAC and the board PA. */
esp_codec_dev_sample_info_t sample_cfg = {
.bits_per_sample = EXAMPLE_BITS_PER_SAMPLE,
.channel = EXAMPLE_CHANNEL_COUNT,
.channel_mask = EXAMPLE_STEREO_CHANNEL_MASK,
.sample_rate = EXAMPLE_SAMPLE_RATE,
.mclk_multiple = EXAMPLE_MCLK_MULTIPLE,
};
ESP_ERROR_CHECK(esp_codec_dev_open(codec_dev, &sample_cfg));
ESP_ERROR_CHECK(esp_codec_dev_set_out_vol(codec_dev, CONFIG_EXAMPLE_VOICE_VOLUME));
ESP_LOGI(TAG, "%s codec init success", EXAMPLE_CODEC_NAME);
}
void app_main(void)
{
printf("wav player example start\n--------------------------\n");
const uint8_t *pcm = NULL;
size_t pcm_len = 0;
ESP_ERROR_CHECK(wav_get_pcm(music_wav_start, music_wav_end - music_wav_start, &pcm, &pcm_len));
i2s_chan_handle_t tx_handle = i2s_driver_init();
ESP_LOGI(TAG, "i2s driver init success");
dac_codec_init(tx_handle);
/* Disable TX, preload the DMA descriptors, then enable. Otherwise the first
* frames on the wire are zeros or stale SRAM (a click at the start of playback). */
size_t bytes_write = 0;
const uint8_t *data_ptr = pcm;
ESP_ERROR_CHECK(i2s_channel_disable(tx_handle));
ESP_ERROR_CHECK(i2s_channel_preload_data(tx_handle, data_ptr, pcm_len, &bytes_write));
data_ptr += bytes_write;
ESP_ERROR_CHECK(i2s_channel_enable(tx_handle));
while (1) {
size_t remain = (size_t)(pcm + pcm_len - data_ptr);
while (remain > 0) {
ESP_ERROR_CHECK(i2s_channel_write(tx_handle, data_ptr, remain, &bytes_write, portMAX_DELAY));
if (bytes_write == 0) {
ESP_LOGE(TAG, "i2s music play failed.");
abort();
}
data_ptr += bytes_write;
remain -= bytes_write;
}
ESP_LOGI(TAG, "i2s music played, %d bytes are written.", (int)pcm_len);
data_ptr = pcm;
vTaskDelay(pdMS_TO_TICKS(1000));
}
}

View File

@@ -0,0 +1,35 @@
# SPDX-FileCopyrightText: 2021-2026 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: CC0-1.0
import os
import pytest
from pytest_embedded import Dut
from pytest_embedded_idf.utils import idf_parametrize
from pytest_embedded_idf.utils import soc_filtered_targets
I2S_TARGETS = soc_filtered_targets('SOC_I2S_SUPPORTED == 1')
@pytest.mark.generic
@pytest.mark.parametrize(
'config',
[
'es8311',
'es8389',
],
indirect=True,
)
@idf_parametrize(
'target',
I2S_TARGETS,
indirect=['target'],
)
def test_wav_player_example(dut: Dut, config: str) -> None:
dut.expect_exact('wav player example start')
dut.expect_exact('wav_player: i2s driver init success')
# Generic CI runners have no codec; only check the rest when running locally.
if os.getenv('CI_JOB_ID'):
return
dut.expect_exact(f'wav_player: {config.upper()} codec init success', timeout=10)

View File

@@ -0,0 +1 @@
CONFIG_EXAMPLE_DAC_TYPE_ES8311=y

View File

@@ -0,0 +1 @@
CONFIG_EXAMPLE_DAC_TYPE_ES8389=y

View File

@@ -0,0 +1,3 @@
# This file must exist. CMake only loads sdkconfig.defaults.<target>
# as a companion of each file listed in SDKCONFIG_DEFAULTS, which
# defaults to this path.

View File

@@ -0,0 +1,5 @@
CONFIG_EXAMPLE_I2C_SDA_IO=21
CONFIG_EXAMPLE_I2C_SCL_IO=22
CONFIG_EXAMPLE_I2S_BCLK_IO=4
CONFIG_EXAMPLE_I2S_WS_IO=5
CONFIG_EXAMPLE_I2S_DOUT_IO=18

View File

@@ -0,0 +1,6 @@
CONFIG_EXAMPLE_I2C_SDA_IO=0
CONFIG_EXAMPLE_I2C_SCL_IO=1
CONFIG_EXAMPLE_I2S_BCLK_IO=3
CONFIG_EXAMPLE_I2S_WS_IO=4
CONFIG_EXAMPLE_I2S_DOUT_IO=5
CONFIG_EXAMPLE_PA_CTRL_IO=7

View File

@@ -106,7 +106,6 @@ env_markers =
httpbin: runner for tests that need to access the httpbin service
flash_4mb: C2 runners with 4 MB flash
jtag_re_enable: Runner to re-enable jtag which is softly disabled by burning bit SOFT_DIS_JTAG on eFuse
es8311: Development board that carries es8311 codec
camera: Runner with camera
ov5647: Runner with camera ov5647
esp32s3_eye: Runner for esp32s3_eye and camera ov2640