diff --git a/docs/en/api-reference/peripherals/i2s.rst b/docs/en/api-reference/peripherals/i2s.rst index 2b40e28c59..8d16151afe 100644 --- a/docs/en/api-reference/peripherals/i2s.rst +++ b/docs/en/api-reference/peripherals/i2s.rst @@ -323,10 +323,10 @@ Users can initialize a channel by calling corresponding functions (i.e., :func:` Advanced API ^^^^^^^^^^^^ -To satisfy the high quality audio requiment, following advanced APIs are provided: +To satisfy the high quality audio requirement, following advanced APIs are provided: - :cpp:func:`i2s_channel_preload_data`: Preloading audio data into the I2S internal cache, enabling the TX channel to immediately send data upon activation, thereby reducing the initial audio output delay. -- :cpp:func:`i2s_channel_tune_rate`: Dynamically fine-tune the audio rate at runtime to match the speed of the audio data producer and consumer, thereby preventing the accumulation or shortage of intermediate buffered data caused by rate mismatches. +- :cpp:func:`i2s_channel_tune_rate`: Dynamically fine-tuning the audio rate at runtime to match the speed of the audio data producer and consumer, thereby preventing the accumulation or shortage of intermediate buffered data that caused by rate mismatches. IRAM Safe ^^^^^^^^^ diff --git a/examples/peripherals/.build-test-rules.yml b/examples/peripherals/.build-test-rules.yml index 6e87a80ff4..7fc3a4a152 100644 --- a/examples/peripherals/.build-test-rules.yml +++ b/examples/peripherals/.build-test-rules.yml @@ -110,6 +110,15 @@ examples/peripherals/i2c/i2c_tools: depends_filepatterns: - examples/system/console/advanced/components/**/* +examples/peripherals/i2s/i2s_advance/i2s_usb: + disable: + - if: SOC_I2S_SUPPORTED != 1 + - if: SOC_I2C_SUPPORTED != 1 + - if: SOC_USB_OTG_SUPPORTED != 1 + depends_components: + - esp_driver_i2s + - esp_driver_i2c + examples/peripherals/i2s/i2s_basic/i2s_pdm: disable: - if: SOC_I2S_SUPPORTS_PDM != 1 diff --git a/examples/peripherals/i2s/i2s_advance/i2s_usb/CMakeLists.txt b/examples/peripherals/i2s/i2s_advance/i2s_usb/CMakeLists.txt new file mode 100644 index 0000000000..ef797cf4a0 --- /dev/null +++ b/examples/peripherals/i2s/i2s_advance/i2s_usb/CMakeLists.txt @@ -0,0 +1,6 @@ +# The following five lines of boilerplate have to be in your project's +# CMakeLists in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.16) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(i2s_usb) diff --git a/examples/peripherals/i2s/i2s_advance/i2s_usb/README.md b/examples/peripherals/i2s/i2s_advance/i2s_usb/README.md new file mode 100644 index 0000000000..888aa0a867 --- /dev/null +++ b/examples/peripherals/i2s/i2s_advance/i2s_usb/README.md @@ -0,0 +1,139 @@ +| Supported Targets | ESP32-P4 | ESP32-S2 | ESP32-S3 | +| ----------------- | -------- | -------- | -------- | + +# I2S USB Example + +(See the README.md file in the upper level 'examples' directory for more information about examples.) + +This example demonstrates how to use I2S and USB to make ESP chip as a USB speaker and microphone. The USB on ESP chip will be initialized as a UAC(USB Audio Class) device to receive and transmit the audio data. + +This example also demonstrates how to use I2S rate tuning feature to adjust the audio rate dynamically to match the UAC speed. The example only shows a possible approach to match UAC speed, you can specify your own strategy in you actual project. + +NOTE: Please take care that the dynamic rate tuning might cause plosive sounds while changing the rate. Suggest to stop the audio first before tuning the rate if possible. + +## How to Use Example + +### Hardware Required + +* ESP chip that has USB support +* A USB cable for connection +* A board with ES8311 codec and power amplifier +* A speaker + +### Connection + +The pin assignment can be specified in the menuconfig, or the target specific default sdkconfig in `sdkconfig.defaults.`. + +The example hardware connection can be referred as follow: + +``` + ┌──────────────┐ ┌─────────────────┐ ┌───────────┐ +┌──────────────┐ │ ESP │ │ ES8311 │ │ MIC │ +│ │ USB Cable│ │ I2C Bus │ │ │ │ +│ PC ├──────────┤ USB_D+ SCL ├────────►│ CCLK MIC_1P │◄────┤ OUT+ │ +│ ├──────────┤ USB_D- SDA │◄───────►│ CDATA MIC_1N │◄────┤ OUT- │ +│ │ │ │ │ │ │ │ +└──────────────┘ │ │ │ │ └───────────┘ + │ │ I2S Bus │ │ ┌───────────┐ ┌─────────┐ + │ MCLK ├────────►│ MCLK │ │ PA │ │ SPEAKER │ + │ BCLK ├────────►│ SCLK │ │ │ │ │ + │ WS ├────────►│ LRCK OUT_P ├────►│ IN+ OUT+ ├────►│ │ + │ SDIN │◄────────┤ ASDOUT OUT_N ├────►│ IN- OUT- ├────►│ │ + │ SDOUT ├────────►│ DSDIN │ │ │ │ │ + │ │ └─────────────────┘ │ │ │ │ + │ │ PA Enable IO │ │ └─────────┘ + │ PA_CTRL ├────────────────────────────────►│ PA_EN │ + └──────────────┘ └───────────┘ +``` + +### Dependency + +This example is based on [es8311 component](https://components.espressif.com/component/espressif/es8311) and [usb_device_uac component](https://components.espressif.com/component/espressif/usb_device_uac). + +The component can be installed by esp component manager. Since this example already installed it, no need to re-installed it again, but if you want to install this component in your own project, you can input the following command: +``` +idf.py add-dependency espressif/esp_codec_dev: ^1.3.4 espressif/usb_device_uac^1.1.0 +``` + +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 + +1. Set the target of the build by following command, where TARGET can be the supported targets listed at the top of this file. +``` +idf.py set-target TARGET +``` +2. Following configurations can be specified in menuconfig: +``` +idf.py menuconfig +``` +* `Example Configuration` menu: + - I2C, I2S and PA_CTRL GPIOs + - Audio data bit width + - Microphone gain + - Initial voice volume + - Whether to enable the dynamic rate tuning +* `Component config > USB Device UAC Configuration > USB Device UAC` menu: + - UAC sample rate + - Speaker and microphone channel number + - Speaker and microphone intervals + - Speaker and microphone tasks + +### Build and Flash + +Run `idf.py -p PORT flash monitor` to build, flash and monitor the project. + +(To exit the serial monitor, type ``Ctrl-]``.) + +See the [Getting Started Guide](https://docs.espressif.com/projects/esp-idf/en/latest/get-started/index.html) for full steps to configure and use ESP-IDF to build projects. + +## Console Output + +If you enabled the dynamic rate tuning, the example will force to deviate the MCLK initially, so that to show what will happen if the I2S speed is slower than UAC. + +You might see the following output, the buffer water mark increases (the data pending to be sent was accumulated), then the MCLK will be increased if the water mark keeps above the threshold for a while: + +``` +initial MCLK: 4096000 Hz +I (399) ES8311: ES8311 in Slave mode and I2S format +I (407) gpio: GPIO[6]| InputEn: 0| OutputEn: 1| OpenDrain: 0| Pullup: 0| Pulldown: 0| Intr:0 +I (408) usbd_uac: UAC Device Start, Version: 1.1.0 +I (409) i2s_usb: MCLK: 4095200, delta: -800, buffer water mark: 0% +I (915) i2s_usb: MCLK: 4095200, delta: -800, buffer water mark: 0% +... +I (6915) i2s_usb: MCLK: 4095200, delta: -800, buffer water mark: 20% +... +I (15415) i2s_usb: MCLK: 4095200, delta: -800, buffer water mark: 32% +... +I (143415) i2s_usb: MCLK: 4095200, delta: -800, buffer water mark: 95% +I (143915) i2s_usb: MCLK: 4095200, delta: -800, buffer water mark: 95% +I (144415) i2s_usb: MCLK: 4095200, delta: -800, buffer water mark: 95% +D (144730) i2s_common: i2s tx channel disabled +D (144730) i2s_common: i2s tx channel enabled +overflow detected, current MCLK: 4095238 Hz, -762 Hz changed +E (144733) i2s_usb: i2s write failed +I (144915) i2s_usb: MCLK: 4095238, delta: -762, buffer water mark: 97% +I (145415) i2s_usb: MCLK: 4095238, delta: -762, buffer water mark: 97% +I (145915) i2s_usb: MCLK: 4095238, delta: -762, buffer water mark: 97% +I (146415) i2s_usb: MCLK: 4095238, delta: -762, buffer water mark: 97% +D (146773) i2s_common: i2s tx channel disabled +D (146773) i2s_common: i2s tx channel enabled +overflow detected, current MCLK: 4095277 Hz, -723 Hz changed +E (146776) i2s_usb: i2s write failed +I (146915) i2s_usb: MCLK: 4095277, delta: -723, buffer water mark: 90% +I (147415) i2s_usb: MCLK: 4095277, delta: -723, buffer water mark: 90% +I (147915) i2s_usb: MCLK: 4095277, delta: -723, buffer water mark: 90% +I (148415) i2s_usb: MCLK: 4095277, delta: -723, buffer water mark: 90% +D (148912) i2s_common: i2s tx channel disabled +D (148912) i2s_common: i2s tx channel enabled +overflow detected, current MCLK: 4095322 Hz, -678 Hz changed +I (148915) i2s_usb: MCLK: 4095322, delta: -678, buffer water mark: 87% +E (148915) i2s_usb: i2s write failed +I (149424) i2s_usb: MCLK: 4095322, delta: -678, buffer water mark: 72% +``` + +(Once failed to write, it means the buffer is overflowed, you will see the overflow error log about the write failure.) + +## Troubleshooting + +For any technical queries, please open an [issue](https://github.com/espressif/esp-idf/issues) on GitHub. We will get back to you soon. diff --git a/examples/peripherals/i2s/i2s_advance/i2s_usb/main/CMakeLists.txt b/examples/peripherals/i2s/i2s_advance/i2s_usb/main/CMakeLists.txt new file mode 100644 index 0000000000..22a078100a --- /dev/null +++ b/examples/peripherals/i2s/i2s_advance/i2s_usb/main/CMakeLists.txt @@ -0,0 +1,3 @@ +idf_component_register(SRCS "i2s_usb_example_main.c" + PRIV_REQUIRES esp_driver_i2s esp_driver_i2c + INCLUDE_DIRS ".") diff --git a/examples/peripherals/i2s/i2s_advance/i2s_usb/main/Kconfig.projbuild b/examples/peripherals/i2s/i2s_advance/i2s_usb/main/Kconfig.projbuild new file mode 100644 index 0000000000..2362b38d77 --- /dev/null +++ b/examples/peripherals/i2s/i2s_advance/i2s_usb/main/Kconfig.projbuild @@ -0,0 +1,113 @@ +menu "Example Configuration" + + menu "I2C GPIOs config" + config EXAMPLE_I2C_SCL_IO + int "SCL pin" + default 8 if IDF_TARGET_ESP32P4 + default 4 + config EXAMPLE_I2C_SDA_IO + int "SDA pin" + default 7 if IDF_TARGET_ESP32P4 + default 5 + endmenu + + menu "I2S GPIOs config" + config EXAMPLE_I2S_MCK_IO + int "MCK pin" + default 13 if IDF_TARGET_ESP32P4 + default 10 + config EXAMPLE_I2S_BCK_IO + int "BCK pin" + default 12 if IDF_TARGET_ESP32P4 + default 11 + config EXAMPLE_I2S_WS_IO + int "WS pin" + default 10 if IDF_TARGET_ESP32P4 + default 12 + config EXAMPLE_I2S_DO_IO + int "DOUT pin" + default 9 if IDF_TARGET_ESP32P4 + default 13 + config EXAMPLE_I2S_DI_IO + int "DIN pin" + default 11 if IDF_TARGET_ESP32P4 + default 14 + endmenu + + config EXAMPLE_DYNAMIC_TUNING_EN + bool "Enable dynamic tuning" + default n + help + Enable to tune the audio rate dynamically to match the I2S and UAC speed. + + choice EXAMPLE_BIT_WIDTH + prompt "Audio data bit width" + default BIT_WIDTH_16BIT + + config BIT_WIDTH_8BIT + bool "8-bit" + config BIT_WIDTH_16BIT + bool "16-bit" + config BIT_WIDTH_24BIT + bool "24-bit" + config BIT_WIDTH_32BIT + bool "32-bit" + endchoice + + config EXAMPLE_AUDIO_BIT_WIDTH + int + default 8 if BIT_WIDTH_8BIT + default 16 if BIT_WIDTH_16BIT + default 24 if BIT_WIDTH_24BIT + default 32 if BIT_WIDTH_32BIT + + choice EXAMPLE_SELECT_MIC_GAIN + prompt "Set MIC gain" + default MIC_GAIN_18DB + help + Select the default microphone gain + + config MIC_GAIN_0DB + bool "0dB" + config MIC_GAIN_6DB + bool "6dB" + config MIC_GAIN_12DB + bool "12dB" + config MIC_GAIN_18DB + bool "18dB" + config MIC_GAIN_24DB + bool "24dB" + config MIC_GAIN_30DB + bool "30dB" + config MIC_GAIN_36DB + bool "36dB" + config MIC_GAIN_42DB + bool "42dB" + endchoice + + config EXAMPLE_CODEC_MIC_GAIN + int + default 0 if MIC_GAIN_0DB + default 1 if MIC_GAIN_6DB + default 2 if MIC_GAIN_12DB + default 3 if MIC_GAIN_18DB + default 4 if MIC_GAIN_24DB + default 5 if MIC_GAIN_30DB + default 6 if MIC_GAIN_36DB + default 7 if MIC_GAIN_42DB + + config EXAMPLE_CODEC_VOLUME + int "Voice volume" + range 0 100 + default 60 + help + Set voice volume + + config EXAMPLE_PA_CTRL_IO + int "Power Amplifier control IO" + default 53 if IDF_TARGET_ESP32P4 + default 6 + help + Set GPIO number for PA control. Set -1 to disable PA control. + +endmenu diff --git a/examples/peripherals/i2s/i2s_advance/i2s_usb/main/i2s_usb_example.h b/examples/peripherals/i2s/i2s_advance/i2s_usb/main/i2s_usb_example.h new file mode 100644 index 0000000000..0c146a6a34 --- /dev/null +++ b/examples/peripherals/i2s/i2s_advance/i2s_usb/main/i2s_usb_example.h @@ -0,0 +1,56 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: CC0-1.0 + */ + +#pragma once + +#include "sdkconfig.h" + +/* I2S DMA configuration */ +#define EXAMPLE_I2S_DMA_DESC_NUM 8 +#define EXAMPLE_I2S_DMA_FRAME_NUM 96 +#define EXAMPLE_I2S_DMA_TOTAL_BUFFER_SIZE (EXAMPLE_I2S_DMA_DESC_NUM * (EXAMPLE_I2S_DMA_FRAME_NUM * EXAMPLE_I2S_DMA_DESC_NUM * EXAMPLE_AUDIO_BIT_WIDTH / 8)) + +/* I2S frequency tuning configuration */ +#define EXAMPLE_DYNAMIC_TUNING_EN CONFIG_EXAMPLE_DYNAMIC_TUNING_EN +#define EXAMPLE_I2S_MAX_DEVIATED_PPM (200) /* Maximum deviation compare to initial freuqency, in PPM (Parts Per Million) */ +#define EXAMPLE_I2S_MIN_DEVIATED_PPM (-200) /* Minimum deviation compare to initial freuqency, in PPM (Parts Per Million) */ +#define EXAMPLE_I2S_TUNING_STEP_PPM (10) /* Step of frequency deviation, in PPM (Parts Per Million). + * The tuning might take no effect when the step is too small, + * especially when the I2S clock source is lower than 100MHz */ +#define EXAMPLE_PPM_TO_MCLK(curr_mclk_hz, ppm) (ppm * (curr_mclk_hz / 1000000)) /* Convert PPM into MCLK ticks */ +#define EXAMPLE_I2S_DEFAULT_TUNING_CFG(curr_mclk_hz, is_increase) { \ + .tune_mode = I2S_TUNING_MODE_ADDSUB, \ + .tune_mclk_val = is_increase ? EXAMPLE_PPM_TO_MCLK(curr_mclk_hz, EXAMPLE_I2S_TUNING_STEP_PPM) : \ + -EXAMPLE_PPM_TO_MCLK(curr_mclk_hz, EXAMPLE_I2S_TUNING_STEP_PPM), \ + .max_delta_mclk = EXAMPLE_PPM_TO_MCLK(curr_mclk_hz, EXAMPLE_I2S_MAX_DEVIATED_PPM), \ + .min_delta_mclk = EXAMPLE_PPM_TO_MCLK(curr_mclk_hz, EXAMPLE_I2S_MIN_DEVIATED_PPM), \ +} + +/* I2C port and GPIOs */ +#define EXAMPLE_I2C_NUM (0) +#define EXAMPLE_I2C_SCL_IO CONFIG_EXAMPLE_I2C_SCL_IO +#define EXAMPLE_I2C_SDA_IO CONFIG_EXAMPLE_I2C_SDA_IO + +/* I2S port and GPIOs */ +#define EXAMPLE_I2S_NUM (0) +#define EXAMPLE_I2S_MCK_IO CONFIG_EXAMPLE_I2S_MCK_IO +#define EXAMPLE_I2S_BCK_IO CONFIG_EXAMPLE_I2S_BCK_IO +#define EXAMPLE_I2S_WS_IO CONFIG_EXAMPLE_I2S_WS_IO +#define EXAMPLE_I2S_DO_IO CONFIG_EXAMPLE_I2S_DO_IO +#define EXAMPLE_I2S_DI_IO CONFIG_EXAMPLE_I2S_DI_IO + +/* Audio configuration */ +#define EXAMPLE_AUDIO_SAMPLE_RATE CONFIG_UAC_SAMPLE_RATE +#define EXAMPLE_AUDIO_BIT_WIDTH CONFIG_EXAMPLE_AUDIO_BIT_WIDTH + +/* ES8311 configuration */ +#define EXAMPLE_CODEC_MCLK_MULTIPLE (EXAMPLE_AUDIO_BIT_WIDTH == 24 ? 384 : 256) +#define EXAMPLE_CODEC_MCLK_FREQ_HZ (EXAMPLE_CODEC_MCLK_MULTIPLE * EXAMPLE_AUDIO_SAMPLE_RATE) +#define EXAMPLE_CODEC_VOLUME CONFIG_EXAMPLE_CODEC_VOLUME +#define EXAMPLE_CODEC_MIC_GAIN CONFIG_EXAMPLE_CODEC_MIC_GAIN + +/* PA control GPIO */ +#define EXAMPLE_PA_CTRL_IO CONFIG_EXAMPLE_PA_CTRL_IO diff --git a/examples/peripherals/i2s/i2s_advance/i2s_usb/main/i2s_usb_example_main.c b/examples/peripherals/i2s/i2s_advance/i2s_usb/main/i2s_usb_example_main.c new file mode 100644 index 0000000000..bf66f0d52b --- /dev/null +++ b/examples/peripherals/i2s/i2s_advance/i2s_usb/main/i2s_usb_example_main.c @@ -0,0 +1,286 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: CC0-1.0 + */ + +#include +#include +#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 "usb_device_uac.h" +#include "esp_codec_dev_defaults.h" +#include "esp_codec_dev.h" +#include "esp_codec_dev_vol.h" +#include "i2s_usb_example.h" +#include "esp_log.h" + +static const char *TAG = "i2s_usb"; + +static i2s_chan_handle_t i2s_tx_handle = NULL; +static i2s_chan_handle_t i2s_rx_handle = NULL; +static i2c_master_bus_handle_t i2c_bus_handle = NULL; +static esp_codec_dev_handle_t codec_handle = NULL; +static uint32_t init_mclk_freq_hz = 0; + +static void i2s_driver_init(void) +{ + i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(EXAMPLE_I2S_NUM, I2S_ROLE_MASTER); + chan_cfg.auto_clear = true; // Auto clear the legacy data in the DMA buffer + chan_cfg.dma_desc_num = EXAMPLE_I2S_DMA_DESC_NUM; + chan_cfg.dma_frame_num = EXAMPLE_I2S_DMA_FRAME_NUM; + ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, &i2s_tx_handle, &i2s_rx_handle)); + i2s_std_config_t std_cfg = { + .clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(EXAMPLE_AUDIO_SAMPLE_RATE), + .slot_cfg = I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG(EXAMPLE_AUDIO_BIT_WIDTH, I2S_SLOT_MODE_MONO), + .gpio_cfg = { + .mclk = EXAMPLE_I2S_MCK_IO, + .bclk = EXAMPLE_I2S_BCK_IO, + .ws = EXAMPLE_I2S_WS_IO, + .dout = EXAMPLE_I2S_DO_IO, + .din = EXAMPLE_I2S_DI_IO, + .invert_flags = { + .mclk_inv = false, + .bclk_inv = false, + .ws_inv = false, + }, + }, + }; + std_cfg.clk_cfg.mclk_multiple = EXAMPLE_CODEC_MCLK_MULTIPLE; + + ESP_ERROR_CHECK(i2s_channel_init_std_mode(i2s_rx_handle, &std_cfg)); + ESP_ERROR_CHECK(i2s_channel_init_std_mode(i2s_tx_handle, &std_cfg)); + + ESP_ERROR_CHECK(i2s_channel_enable(i2s_rx_handle)); + ESP_ERROR_CHECK(i2s_channel_enable(i2s_tx_handle)); + + /* Get the initial MCLK */ + i2s_tuning_info_t tune_info = {}; + ESP_ERROR_CHECK(i2s_channel_tune_rate(i2s_tx_handle, NULL, &tune_info)); + init_mclk_freq_hz = tune_info.curr_mclk_hz; + printf("initial MCLK: %"PRIu32" Hz\n", init_mclk_freq_hz); + +#if EXAMPLE_DYNAMIC_TUNING_EN + /* Force to deviate the MCLK to demonstrate how the frequency tuning works. + * Please REMOVE the following code in your actual project!! */ + i2s_tuning_config_t tune_cfg = { + .tune_mode = I2S_TUNING_MODE_ADDSUB, + .tune_mclk_val = -800, + .max_delta_mclk = 800, + .min_delta_mclk = -800, + }; + ESP_ERROR_CHECK(i2s_channel_tune_rate(i2s_tx_handle, &tune_cfg, &tune_info)); +#endif // EXAMPLE_DYNAMIC_TUNING_EN +} + +static void i2c_driver_init(void) +{ + /* Initialize I2C peripheral */ + i2c_master_bus_config_t i2c_mst_cfg = { + .i2c_port = EXAMPLE_I2C_NUM, + .sda_io_num = EXAMPLE_I2C_SDA_IO, + .scl_io_num = EXAMPLE_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)); +} + +static void esp_codec_es8311_init(void) +{ + /* Create control interface with I2C bus handle */ + audio_codec_i2c_cfg_t i2c_cfg = { + .port = EXAMPLE_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 = EXAMPLE_I2S_NUM, + .rx_handle = i2s_rx_handle, + .tx_handle = i2s_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 = true, + .pa_pin = EXAMPLE_PA_CTRL_IO, + .pa_reverted = false, + .hw_gain = { + .pa_voltage = 5.0, + .codec_dac_voltage = 3.3, + }, + .mclk_div = EXAMPLE_CODEC_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, + }; + 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 = EXAMPLE_AUDIO_BIT_WIDTH, + .channel = 1, + .channel_mask = 0x01, + .sample_rate = EXAMPLE_AUDIO_SAMPLE_RATE, + }; + esp_codec_dev_open(codec_handle, &sample_cfg); + + /* Set the initial volume and gain */ + esp_codec_dev_set_out_vol(codec_handle, EXAMPLE_CODEC_VOLUME); + esp_codec_dev_set_in_gain(codec_handle, EXAMPLE_CODEC_MIC_GAIN); +} + +static esp_err_t usb_uac_device_output_cb(uint8_t *buf, size_t len, void *arg) +{ + int ret = esp_codec_dev_write(codec_handle, buf, len); + +#if EXAMPLE_DYNAMIC_TUNING_EN + static uint32_t overflow_cnt = 0; + static uint32_t underflow_cnt = 0; + static uint32_t normal_cnt = 0; + /* Get the current water mark */ + i2s_tuning_info_t tune_info = {}; + ESP_ERROR_CHECK(i2s_channel_tune_rate(i2s_tx_handle, NULL, &tune_info)); + /* If the water mark exceed the threshould for several times, + * it indicates I2S is slower than UAC, increase MCLK to match the UAC speed */ + if (tune_info.water_mark >= 90) { + overflow_cnt++; + /* In case of tuning the MCLK frequently, we need to monitor the long term trend, + * so we'd better to tune the frequency after several seconds since the first overflow happen. + * Take 5 sec for example, then the count can be calculated as follow: + * count = slot_bytes(2) * slot_num(1) * sample_rate(16000) * seconds(5) / bytes_per_written(32) = 5000 */ + if (overflow_cnt >= 5000) { + // No spare DMA buffer, i.e., too many data pending to be sent, increase MCLK to match the UAC speed + i2s_tuning_config_t tune_cfg = EXAMPLE_I2S_DEFAULT_TUNING_CFG(init_mclk_freq_hz, true); + /* Suggest to disable the I2S first before tuning the rate, + * otherwise plosive sounds might break out if we change the rate while the audio playing */ + ESP_ERROR_CHECK(i2s_channel_disable(i2s_tx_handle)); + ESP_ERROR_CHECK(i2s_channel_tune_rate(i2s_tx_handle, &tune_cfg, &tune_info)); + ESP_ERROR_CHECK(i2s_channel_enable(i2s_tx_handle)); + printf("overflow detected, current MCLK: %"PRIu32" Hz, %"PRId32" Hz changed\n", tune_info.curr_mclk_hz, tune_info.delta_mclk_hz); + overflow_cnt = 0; + normal_cnt = 0; + } + } + /* If the water mark is below the threshould for several times, + * it indicates I2S is faster than UAC, decrease MCLK to match the UAC speed */ + else if (tune_info.water_mark < 10) { + underflow_cnt++; + if (underflow_cnt >= 5000) { + // Almost all data were sent, decrease MCLK to match the UAC speed + i2s_tuning_config_t tune_cfg = EXAMPLE_I2S_DEFAULT_TUNING_CFG(init_mclk_freq_hz, false); + ESP_ERROR_CHECK(i2s_channel_disable(i2s_tx_handle)); + ESP_ERROR_CHECK(i2s_channel_tune_rate(i2s_tx_handle, &tune_cfg, &tune_info)); + ESP_ERROR_CHECK(i2s_channel_enable(i2s_tx_handle)); + printf("underflow detected, current MCLK: %"PRIu32" Hz, %"PRId32" Hz changed\n", tune_info.curr_mclk_hz, tune_info.delta_mclk_hz); + underflow_cnt = 0; + normal_cnt = 0; + } + } + /* If the water mark is within the normal range for a while, clear the counter */ + else { + normal_cnt %= 1000; + normal_cnt++; + if (normal_cnt >= 1000) { + overflow_cnt = 0; + underflow_cnt = 0; + } + } +#endif // EXAMPLE_DYNAMIC_TUNING_EN + if (ret != ESP_CODEC_DEV_OK) { + ESP_LOGE(TAG, "i2s write failed"); + return ESP_FAIL; + } + + return ESP_OK; +} + +static esp_err_t usb_uac_device_input_cb(uint8_t *buf, size_t len, size_t *bytes_read, void *arg) +{ + int ret = esp_codec_dev_read(codec_handle, buf, len); + /* Since TX and RX are duplex, we only need to tune one of them. + * And the MCLK is generated by the latter initialized channel, + * in this example, TX is initialized after RX, so the MCLK comes from the TX channel. + * Therefore, we only need to tune the TX channel. + * You can also tune both channels if you can't tell which one is initialized first, + * but only the channel that generates MCLK can be actually tuned */ + if (ret != ESP_CODEC_DEV_OK) { + ESP_LOGE(TAG, "i2s read failed"); + return ESP_FAIL; + } + + return ESP_OK; +} + +static void usb_uac_device_set_mute_cb(uint32_t mute, void *arg) +{ + esp_codec_dev_set_out_mute(codec_handle, !!mute); + ESP_LOGI(TAG, "uac-device %s muted", mute ? "is" : "cancel"); +} + +static void usb_uac_device_set_volume_cb(uint32_t volume, void *arg) +{ + esp_codec_dev_set_out_vol(codec_handle, volume); + ESP_LOGI(TAG, "set uac-device volume to: %"PRIu32, volume); +} + +static void usb_uac_device_init(void) +{ + uac_device_config_t config = { + .output_cb = usb_uac_device_output_cb, + .input_cb = usb_uac_device_input_cb, + .set_mute_cb = usb_uac_device_set_mute_cb, + .set_volume_cb = usb_uac_device_set_volume_cb, + .cb_ctx = NULL, + }; + /* Init UAC device, UAC related configurations can be set by the menuconfig */ + ESP_ERROR_CHECK(uac_device_init(&config)); +} + +void app_main(void) +{ + /* Initialize I2S peripheral */ + i2s_driver_init(); + /* Initialize I2C peripheral */ + i2c_driver_init(); + /* Initialize ES8311 codec */ + esp_codec_es8311_init(); + /* Initialize the USB as UAC device */ + usb_uac_device_init(); + + /* Print the MCLK and buffer info */ + i2s_tuning_info_t tune_info = {}; + while (1) { + ESP_ERROR_CHECK(i2s_channel_tune_rate(i2s_tx_handle, NULL, &tune_info)); + ESP_LOGI(TAG, "MCLK: %"PRId32", delta: %"PRId32", buffer water mark: %"PRIu32"%%", + tune_info.curr_mclk_hz, tune_info.delta_mclk_hz, tune_info.water_mark); + vTaskDelay(pdMS_TO_TICKS(500)); + } +} diff --git a/examples/peripherals/i2s/i2s_advance/i2s_usb/main/idf_component.yml b/examples/peripherals/i2s/i2s_advance/i2s_usb/main/idf_component.yml new file mode 100644 index 0000000000..810e60b5cd --- /dev/null +++ b/examples/peripherals/i2s/i2s_advance/i2s_usb/main/idf_component.yml @@ -0,0 +1,3 @@ +dependencies: + espressif/usb_device_uac: ^1.1.0 + espressif/esp_codec_dev: ^1.3.4 diff --git a/examples/peripherals/i2s/i2s_advance/i2s_usb/pytest_i2s_usb.py b/examples/peripherals/i2s/i2s_advance/i2s_usb/pytest_i2s_usb.py new file mode 100644 index 0000000000..4a673b6833 --- /dev/null +++ b/examples/peripherals/i2s/i2s_advance/i2s_usb/pytest_i2s_usb.py @@ -0,0 +1,12 @@ +# SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: Unlicense OR CC0-1.0 +import pytest +from pytest_embedded import Dut + + +@pytest.mark.esp32s2 +@pytest.mark.esp32s3 +@pytest.mark.esp32p4 +@pytest.mark.generic +def test_i2s_usb_example(dut: Dut) -> None: + dut.expect(r'initial MCLK: [0-9]+ Hz') diff --git a/examples/peripherals/i2s/i2s_advance/i2s_usb/sdkconfi.ci.defaults b/examples/peripherals/i2s/i2s_advance/i2s_usb/sdkconfi.ci.defaults new file mode 100644 index 0000000000..4a0af84b44 --- /dev/null +++ b/examples/peripherals/i2s/i2s_advance/i2s_usb/sdkconfi.ci.defaults @@ -0,0 +1 @@ +CONFIG_EXAMPLE_DYNAMIC_TUNING_EN=y diff --git a/examples/peripherals/i2s/i2s_advance/i2s_usb/sdkconfig.defaults b/examples/peripherals/i2s/i2s_advance/i2s_usb/sdkconfig.defaults new file mode 100644 index 0000000000..326ce84741 --- /dev/null +++ b/examples/peripherals/i2s/i2s_advance/i2s_usb/sdkconfig.defaults @@ -0,0 +1,4 @@ +CONFIG_I2S_ENABLE_DEBUG_LOG=y +CONFIG_FREERTOS_HZ=1000 +CONFIG_UAC_SAMPLE_RATE=16000 +CONFIG_CODEC_I2C_BACKWARD_COMPATIBLE=n diff --git a/examples/peripherals/i2s/i2s_advance/i2s_usb/sdkconfig.defaults.esp32p4 b/examples/peripherals/i2s/i2s_advance/i2s_usb/sdkconfig.defaults.esp32p4 new file mode 100644 index 0000000000..a8c34c1709 --- /dev/null +++ b/examples/peripherals/i2s/i2s_advance/i2s_usb/sdkconfig.defaults.esp32p4 @@ -0,0 +1,14 @@ +# ESP32-P4 EV board pin assignment +# I2C GPIOs +CONFIG_EXAMPLE_I2C_SCL_IO=8 +CONFIG_EXAMPLE_I2C_SDA_IO=7 + +# I2S GPIOs +CONFIG_EXAMPLE_I2S_MCK_IO=13 +CONFIG_EXAMPLE_I2S_BCK_IO=12 +CONFIG_EXAMPLE_I2S_WS_IO=10 +CONFIG_EXAMPLE_I2S_DO_IO=9 +CONFIG_EXAMPLE_I2S_DI_IO=11 + +# PA GPIO +CONFIG_EXAMPLE_PA_CTRL_IO=53 diff --git a/examples/peripherals/i2s/i2s_advance/i2s_usb/sdkconfig.defaults.esp32s2 b/examples/peripherals/i2s/i2s_advance/i2s_usb/sdkconfig.defaults.esp32s2 new file mode 100644 index 0000000000..933ddded07 --- /dev/null +++ b/examples/peripherals/i2s/i2s_advance/i2s_usb/sdkconfig.defaults.esp32s2 @@ -0,0 +1,13 @@ +# I2C GPIOs +CONFIG_EXAMPLE_I2C_SCL_IO=4 +CONFIG_EXAMPLE_I2C_SDA_IO=5 + +# I2S GPIOs +CONFIG_EXAMPLE_I2S_MCK_IO=10 +CONFIG_EXAMPLE_I2S_BCK_IO=11 +CONFIG_EXAMPLE_I2S_WS_IO=12 +CONFIG_EXAMPLE_I2S_DO_IO=13 +CONFIG_EXAMPLE_I2S_DI_IO=14 + +# PA GPIO +CONFIG_EXAMPLE_PA_CTRL_IO=6 diff --git a/examples/peripherals/i2s/i2s_advance/i2s_usb/sdkconfig.defaults.esp32s3 b/examples/peripherals/i2s/i2s_advance/i2s_usb/sdkconfig.defaults.esp32s3 new file mode 100644 index 0000000000..933ddded07 --- /dev/null +++ b/examples/peripherals/i2s/i2s_advance/i2s_usb/sdkconfig.defaults.esp32s3 @@ -0,0 +1,13 @@ +# I2C GPIOs +CONFIG_EXAMPLE_I2C_SCL_IO=4 +CONFIG_EXAMPLE_I2C_SDA_IO=5 + +# I2S GPIOs +CONFIG_EXAMPLE_I2S_MCK_IO=10 +CONFIG_EXAMPLE_I2S_BCK_IO=11 +CONFIG_EXAMPLE_I2S_WS_IO=12 +CONFIG_EXAMPLE_I2S_DO_IO=13 +CONFIG_EXAMPLE_I2S_DI_IO=14 + +# PA GPIO +CONFIG_EXAMPLE_PA_CTRL_IO=6