mirror of
https://github.com/espressif/esp-idf
synced 2025-03-09 09:09:10 -04:00
Merge branch 'feature/i2s_support_change_freq_in_runtime' into 'master'
feat(i2s): support tuning rate dynamically Closes IDF-11679 See merge request espressif/esp-idf!36537
This commit is contained in:
commit
1bd8fabf5e
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD
|
||||
* SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
@ -1096,10 +1096,10 @@ static void i2s_set_clock_legacy(i2s_port_t i2s_num)
|
||||
i2s_calculate_clock(i2s_num, &clk_info);
|
||||
I2S_CLOCK_SRC_ATOMIC() {
|
||||
if (p_i2s[i2s_num]->dir & I2S_DIR_TX) {
|
||||
i2s_hal_set_tx_clock(&(p_i2s[i2s_num]->hal), &clk_info, clk_cfg->clk_src);
|
||||
i2s_hal_set_tx_clock(&(p_i2s[i2s_num]->hal), &clk_info, clk_cfg->clk_src, NULL);
|
||||
}
|
||||
if (p_i2s[i2s_num]->dir & I2S_DIR_RX) {
|
||||
i2s_hal_set_rx_clock(&(p_i2s[i2s_num]->hal), &clk_info, clk_cfg->clk_src);
|
||||
i2s_hal_set_rx_clock(&(p_i2s[i2s_num]->hal), &clk_info, clk_cfg->clk_src, NULL);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1634,10 +1634,10 @@ esp_err_t i2s_driver_uninstall(i2s_port_t i2s_num)
|
||||
I2S_CLOCK_SRC_ATOMIC() {
|
||||
// switch back to PLL clock source
|
||||
if (obj->dir & I2S_DIR_TX) {
|
||||
i2s_hal_set_tx_clock(&obj->hal, NULL, I2S_CLK_SRC_DEFAULT);
|
||||
i2s_hal_set_tx_clock(&obj->hal, NULL, I2S_CLK_SRC_DEFAULT, NULL);
|
||||
}
|
||||
if (obj->dir & I2S_DIR_RX) {
|
||||
i2s_hal_set_rx_clock(&obj->hal, NULL, I2S_CLK_SRC_DEFAULT);
|
||||
i2s_hal_set_rx_clock(&obj->hal, NULL, I2S_CLK_SRC_DEFAULT, NULL);
|
||||
}
|
||||
}
|
||||
periph_rtc_apll_release();
|
||||
|
@ -25,6 +25,7 @@
|
||||
#include "soc/soc_caps.h"
|
||||
#include "hal/gpio_hal.h"
|
||||
#include "hal/i2s_hal.h"
|
||||
#include "hal/hal_utils.h"
|
||||
#include "hal/dma_types.h"
|
||||
#if SOC_CACHE_INTERNAL_MEM_VIA_L1CACHE
|
||||
#include "hal/cache_hal.h"
|
||||
@ -1374,6 +1375,95 @@ esp_err_t i2s_channel_read(i2s_chan_handle_t handle, void *dest, size_t size, si
|
||||
return ret;
|
||||
}
|
||||
|
||||
esp_err_t i2s_channel_tune_rate(i2s_chan_handle_t handle, const i2s_tuning_config_t *tune_cfg, i2s_tuning_info_t *tune_info)
|
||||
{
|
||||
/** We tune the sample rate via the MCLK clock.
|
||||
* Because the sample rate is decided by MCLK eventually,
|
||||
* and MCLK has a higher resolution which can be tuned more precisely.
|
||||
*/
|
||||
|
||||
ESP_RETURN_ON_FALSE(handle, ESP_ERR_INVALID_ARG, TAG, "NULL pointer");
|
||||
ESP_RETURN_ON_FALSE(!handle->is_external, ESP_ERR_NOT_SUPPORTED, TAG, "Not support to tune rate for external mclk");
|
||||
|
||||
/* If no tuning configuration given, just return the current information */
|
||||
if (tune_cfg == NULL) {
|
||||
xSemaphoreTake(handle->mutex, portMAX_DELAY);
|
||||
goto result;
|
||||
}
|
||||
ESP_RETURN_ON_FALSE(tune_cfg->max_delta_mclk >= tune_cfg->min_delta_mclk, ESP_ERR_INVALID_ARG, TAG, "invalid range");
|
||||
|
||||
uint32_t new_mclk = 0;
|
||||
|
||||
/* Get the new MCLK according to the tuning operation */
|
||||
switch (tune_cfg->tune_mode) {
|
||||
case I2S_TUNING_MODE_ADDSUB:
|
||||
new_mclk = handle->curr_mclk_hz + tune_cfg->tune_mclk_val;
|
||||
break;
|
||||
case I2S_TUNING_MODE_SET:
|
||||
new_mclk = tune_cfg->tune_mclk_val;
|
||||
break;
|
||||
case I2S_TUNING_MODE_RESET:
|
||||
new_mclk = handle->origin_mclk_hz;
|
||||
break;
|
||||
default:
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
/* Check if the tuned mclk is within the supposed range */
|
||||
if ((int32_t)new_mclk - (int32_t)handle->origin_mclk_hz > tune_cfg->max_delta_mclk) {
|
||||
new_mclk = handle->origin_mclk_hz + tune_cfg->max_delta_mclk;
|
||||
} else if ((int32_t)new_mclk - (int32_t)handle->origin_mclk_hz < tune_cfg->min_delta_mclk) {
|
||||
new_mclk = handle->origin_mclk_hz + tune_cfg->min_delta_mclk;
|
||||
}
|
||||
xSemaphoreTake(handle->mutex, portMAX_DELAY);
|
||||
#if SOC_CLK_APLL_SUPPORTED
|
||||
if (handle->clk_src == I2S_CLK_SRC_APLL) {
|
||||
periph_rtc_apll_release();
|
||||
handle->sclk_hz = i2s_set_get_apll_freq(new_mclk);
|
||||
periph_rtc_apll_acquire();
|
||||
}
|
||||
#endif
|
||||
/* Calculate the new divider */
|
||||
hal_utils_clk_div_t mclk_div = {};
|
||||
i2s_hal_calc_mclk_precise_division(handle->sclk_hz, new_mclk, &mclk_div);
|
||||
/* mclk_div = sclk / mclk >= 2 */
|
||||
if (mclk_div.integer < 2) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
/* Set the new divider for MCLK */
|
||||
I2S_CLOCK_SRC_ATOMIC() {
|
||||
if (handle->dir == I2S_DIR_TX) {
|
||||
i2s_ll_tx_set_mclk(handle->controller->hal.dev, &mclk_div);
|
||||
#if SOC_I2S_HW_VERSION_2
|
||||
i2s_ll_tx_update(handle->controller->hal.dev);
|
||||
#endif
|
||||
} else {
|
||||
i2s_ll_rx_set_mclk(handle->controller->hal.dev, &mclk_div);
|
||||
#if SOC_I2S_HW_VERSION_2
|
||||
i2s_ll_rx_update(handle->controller->hal.dev);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
/* Save the current mclk frequency */
|
||||
handle->curr_mclk_hz = (uint32_t)(((uint64_t)handle->sclk_hz * mclk_div.denominator) /
|
||||
(mclk_div.integer * mclk_div.denominator + mclk_div.numerator));
|
||||
result:
|
||||
/* Assign the information if needed */
|
||||
if (tune_info) {
|
||||
tune_info->curr_mclk_hz = handle->curr_mclk_hz;
|
||||
tune_info->delta_mclk_hz = (int32_t)handle->curr_mclk_hz - (int32_t)handle->origin_mclk_hz;
|
||||
uint32_t tot_size = handle->dma.buf_size * handle->dma.desc_num;
|
||||
uint32_t used_size = 0;
|
||||
if (handle->dir == I2S_DIR_TX) {
|
||||
used_size = uxQueueSpacesAvailable(handle->msg_queue) * handle->dma.buf_size + handle->dma.rw_pos;
|
||||
} else {
|
||||
used_size = uxQueueMessagesWaiting(handle->msg_queue) * handle->dma.buf_size + handle->dma.buf_size - handle->dma.rw_pos;
|
||||
}
|
||||
tune_info->water_mark = used_size * 100 / tot_size;
|
||||
}
|
||||
xSemaphoreGive(handle->mutex);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
#if SOC_I2S_SUPPORTS_TX_SYNC_CNT
|
||||
uint32_t i2s_sync_get_bclk_count(i2s_chan_handle_t tx_handle)
|
||||
{
|
||||
|
@ -81,13 +81,12 @@ static esp_err_t i2s_pdm_tx_set_clock(i2s_chan_handle_t handle, const i2s_pdm_tx
|
||||
i2s_hal_clock_info_t clk_info;
|
||||
/* Calculate clock parameters */
|
||||
ESP_RETURN_ON_ERROR(i2s_pdm_tx_calculate_clock(handle, clk_cfg, &clk_info), TAG, "clock calculate failed");
|
||||
ESP_LOGD(TAG, "Clock division info: [sclk] %"PRIu32" Hz [mdiv] %d [mclk] %"PRIu32" Hz [bdiv] %d [bclk] %"PRIu32" Hz",
|
||||
clk_info.sclk, clk_info.mclk_div, clk_info.mclk, clk_info.bclk_div, clk_info.bclk);
|
||||
|
||||
hal_utils_clk_div_t ret_mclk_div = {};
|
||||
portENTER_CRITICAL(&g_i2s.spinlock);
|
||||
/* Set clock configurations in HAL*/
|
||||
I2S_CLOCK_SRC_ATOMIC() {
|
||||
i2s_hal_set_tx_clock(&handle->controller->hal, &clk_info, clk_cfg->clk_src);
|
||||
i2s_hal_set_tx_clock(&handle->controller->hal, &clk_info, clk_cfg->clk_src, &ret_mclk_div);
|
||||
}
|
||||
#if SOC_I2S_HW_VERSION_2
|
||||
/* Work around for PDM TX clock, overwrite the raw division directly to reduce the noise
|
||||
@ -98,6 +97,13 @@ static esp_err_t i2s_pdm_tx_set_clock(i2s_chan_handle_t handle, const i2s_pdm_tx
|
||||
|
||||
/* Update the mode info: clock configuration */
|
||||
memcpy(&(pdm_tx_cfg->clk_cfg), clk_cfg, sizeof(i2s_pdm_tx_clk_config_t));
|
||||
handle->clk_src = clk_cfg->clk_src;
|
||||
handle->sclk_hz = clk_info.sclk;
|
||||
handle->origin_mclk_hz = ((uint64_t)clk_info.sclk * ret_mclk_div.denominator) / (ret_mclk_div.integer * ret_mclk_div.denominator + ret_mclk_div.numerator);
|
||||
handle->curr_mclk_hz = handle->origin_mclk_hz;
|
||||
|
||||
ESP_LOGD(TAG, "Clock division info: [sclk] %"PRIu32" Hz [mdiv] %"PRIu32" %"PRIu32"/%"PRIu32" [mclk] %"PRIu32" Hz [bdiv] %d [bclk] %"PRIu32" Hz",
|
||||
clk_info.sclk, ret_mclk_div.integer, ret_mclk_div.numerator, ret_mclk_div.denominator, handle->origin_mclk_hz, clk_info.bclk_div, clk_info.bclk);
|
||||
|
||||
return ret;
|
||||
}
|
||||
@ -401,18 +407,23 @@ static esp_err_t i2s_pdm_rx_set_clock(i2s_chan_handle_t handle, const i2s_pdm_rx
|
||||
i2s_hal_clock_info_t clk_info;
|
||||
/* Calculate clock parameters */
|
||||
ESP_RETURN_ON_ERROR(i2s_pdm_rx_calculate_clock(handle, clk_cfg, &clk_info), TAG, "clock calculate failed");
|
||||
ESP_LOGD(TAG, "Clock division info: [sclk] %"PRIu32" Hz [mdiv] %d [mclk] %"PRIu32" Hz [bdiv] %d [bclk] %"PRIu32" Hz",
|
||||
clk_info.sclk, clk_info.mclk_div, clk_info.mclk, clk_info.bclk_div, clk_info.bclk);
|
||||
|
||||
hal_utils_clk_div_t ret_mclk_div = {};
|
||||
portENTER_CRITICAL(&g_i2s.spinlock);
|
||||
/* Set clock configurations in HAL*/
|
||||
I2S_CLOCK_SRC_ATOMIC() {
|
||||
i2s_hal_set_rx_clock(&handle->controller->hal, &clk_info, clk_cfg->clk_src);
|
||||
i2s_hal_set_rx_clock(&handle->controller->hal, &clk_info, clk_cfg->clk_src, &ret_mclk_div);
|
||||
}
|
||||
portEXIT_CRITICAL(&g_i2s.spinlock);
|
||||
|
||||
/* Update the mode info: clock configuration */
|
||||
memcpy(&(pdm_rx_cfg->clk_cfg), clk_cfg, sizeof(i2s_pdm_rx_clk_config_t));
|
||||
handle->clk_src = clk_cfg->clk_src;
|
||||
handle->sclk_hz = clk_info.sclk;
|
||||
handle->origin_mclk_hz = ((uint64_t)clk_info.sclk * ret_mclk_div.denominator) / (ret_mclk_div.integer * ret_mclk_div.denominator + ret_mclk_div.numerator);
|
||||
handle->curr_mclk_hz = handle->origin_mclk_hz;
|
||||
ESP_LOGD(TAG, "Clock division info: [sclk] %"PRIu32" Hz [mdiv] %"PRIu32" %"PRIu32"/%"PRIu32" [mclk] %"PRIu32" Hz [bdiv] %d [bclk] %"PRIu32" Hz",
|
||||
clk_info.sclk, ret_mclk_div.integer, ret_mclk_div.numerator, ret_mclk_div.denominator, handle->origin_mclk_hz, clk_info.bclk_div, clk_info.bclk);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD
|
||||
* SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
@ -159,14 +159,21 @@ struct i2s_channel_obj_t {
|
||||
/* Stored configurations */
|
||||
int intr_prio_flags;/*!< i2s interrupt priority flags */
|
||||
void *mode_info; /*!< Slot, clock and gpio information of each mode */
|
||||
bool is_etm_start; /*!< Whether start by etm tasks */
|
||||
bool is_etm_stop; /*!< Whether stop by etm tasks */
|
||||
struct {
|
||||
bool is_etm_start: 1; /*!< Whether start by etm tasks */
|
||||
bool is_etm_stop: 1; /*!< Whether stop by etm tasks */
|
||||
bool is_raw_pdm: 1; /*!< Flag of whether send/receive PDM in raw data, i.e., no PCM2PDM/PDM2PCM filter enabled */
|
||||
bool is_external: 1; /*!< Whether use external clock */
|
||||
#if SOC_I2S_SUPPORTS_APLL
|
||||
bool apll_en; /*!< Flag of whether APLL enabled */
|
||||
bool apll_en: 1; /*!< Flag of whether APLL enabled */
|
||||
#endif
|
||||
bool is_raw_pdm; /*!< Flag of whether send/receive PDM in raw data, i.e., no PCM2PDM/PDM2PCM filter enabled */
|
||||
};
|
||||
uint32_t active_slot; /*!< Active slot number */
|
||||
uint32_t total_slot; /*!< Total slot number */
|
||||
i2s_clock_src_t clk_src; /*!< Clock source */
|
||||
uint32_t sclk_hz; /*!< Source clock frequency */
|
||||
uint32_t origin_mclk_hz; /*!< Original mclk frequency */
|
||||
uint32_t curr_mclk_hz; /*!< Current mclk frequency */
|
||||
/* Locks and queues */
|
||||
SemaphoreHandle_t mutex; /*!< Mutex semaphore for the channel operations */
|
||||
SemaphoreHandle_t binary; /*!< Binary semaphore for writing / reading / enabling / disabling */
|
||||
|
@ -49,9 +49,10 @@ static esp_err_t i2s_std_calculate_clock(i2s_chan_handle_t handle, const i2s_std
|
||||
clk_info->mclk = clk_info->bclk * clk_info->bclk_div;
|
||||
}
|
||||
#if SOC_I2S_HW_VERSION_2
|
||||
clk_info->sclk = clk_cfg->clk_src == I2S_CLK_SRC_EXTERNAL ?
|
||||
clk_cfg->ext_clk_freq_hz : i2s_get_source_clk_freq(clk_cfg->clk_src, clk_info->mclk);
|
||||
float min_mclk_div = clk_cfg->clk_src == I2S_CLK_SRC_EXTERNAL ? 0.99 : 1.99;
|
||||
handle->is_external = clk_cfg->clk_src == I2S_CLK_SRC_EXTERNAL;
|
||||
clk_info->sclk = handle->is_external ? clk_cfg->ext_clk_freq_hz :
|
||||
i2s_get_source_clk_freq(clk_cfg->clk_src, clk_info->mclk);
|
||||
float min_mclk_div = handle->is_external ? 0.99 : 1.99;
|
||||
#else
|
||||
clk_info->sclk = i2s_get_source_clk_freq(clk_cfg->clk_src, clk_info->mclk);
|
||||
float min_mclk_div = 1.99;
|
||||
@ -77,22 +78,28 @@ static esp_err_t i2s_std_set_clock(i2s_chan_handle_t handle, const i2s_std_clk_c
|
||||
i2s_hal_clock_info_t clk_info;
|
||||
/* Calculate clock parameters */
|
||||
ESP_RETURN_ON_ERROR(i2s_std_calculate_clock(handle, clk_cfg, &clk_info), TAG, "clock calculate failed");
|
||||
ESP_LOGD(TAG, "Clock division info: [sclk] %"PRIu32" Hz [mdiv] %d [mclk] %"PRIu32" Hz [bdiv] %d [bclk] %"PRIu32" Hz",
|
||||
clk_info.sclk, clk_info.mclk_div, clk_info.mclk, clk_info.bclk_div, clk_info.bclk);
|
||||
|
||||
hal_utils_clk_div_t ret_mclk_div = {};
|
||||
portENTER_CRITICAL(&g_i2s.spinlock);
|
||||
/* Set clock configurations in HAL*/
|
||||
I2S_CLOCK_SRC_ATOMIC() {
|
||||
if (handle->dir == I2S_DIR_TX) {
|
||||
i2s_hal_set_tx_clock(&handle->controller->hal, &clk_info, clk_cfg->clk_src);
|
||||
i2s_hal_set_tx_clock(&handle->controller->hal, &clk_info, clk_cfg->clk_src, &ret_mclk_div);
|
||||
} else {
|
||||
i2s_hal_set_rx_clock(&handle->controller->hal, &clk_info, clk_cfg->clk_src);
|
||||
i2s_hal_set_rx_clock(&handle->controller->hal, &clk_info, clk_cfg->clk_src, &ret_mclk_div);
|
||||
}
|
||||
}
|
||||
portEXIT_CRITICAL(&g_i2s.spinlock);
|
||||
|
||||
/* Update the mode info: clock configuration */
|
||||
memcpy(&(std_cfg->clk_cfg), clk_cfg, sizeof(i2s_std_clk_config_t));
|
||||
handle->clk_src = clk_cfg->clk_src;
|
||||
handle->sclk_hz = clk_info.sclk;
|
||||
handle->origin_mclk_hz = ((uint64_t)clk_info.sclk * ret_mclk_div.denominator) / (ret_mclk_div.integer * ret_mclk_div.denominator + ret_mclk_div.numerator);
|
||||
handle->curr_mclk_hz = handle->origin_mclk_hz;
|
||||
|
||||
ESP_LOGD(TAG, "Clock division info: [sclk] %"PRIu32" Hz [mdiv] %"PRIu32" %"PRIu32"/%"PRIu32" [mclk] %"PRIu32" Hz [bdiv] %d [bclk] %"PRIu32" Hz",
|
||||
clk_info.sclk, ret_mclk_div.integer, ret_mclk_div.numerator, ret_mclk_div.denominator, handle->origin_mclk_hz, clk_info.bclk_div, clk_info.bclk);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
@ -59,9 +59,10 @@ static esp_err_t i2s_tdm_calculate_clock(i2s_chan_handle_t handle, const i2s_tdm
|
||||
clk_info->bclk = rate * handle->total_slot * slot_bits;
|
||||
clk_info->mclk = clk_info->bclk * clk_info->bclk_div;
|
||||
}
|
||||
clk_info->sclk = clk_cfg->clk_src == I2S_CLK_SRC_EXTERNAL ?
|
||||
clk_cfg->ext_clk_freq_hz : i2s_get_source_clk_freq(clk_cfg->clk_src, clk_info->mclk);
|
||||
float min_mclk_div = clk_cfg->clk_src == I2S_CLK_SRC_EXTERNAL ? 0.99 : 1.99;
|
||||
handle->is_external = clk_cfg->clk_src == I2S_CLK_SRC_EXTERNAL;
|
||||
clk_info->sclk = handle->is_external ? clk_cfg->ext_clk_freq_hz :
|
||||
i2s_get_source_clk_freq(clk_cfg->clk_src, clk_info->mclk);
|
||||
float min_mclk_div = handle->is_external ? 0.99 : 1.99;
|
||||
clk_info->mclk_div = clk_info->sclk / clk_info->mclk;
|
||||
|
||||
/* Check if the configuration is correct. Use float for check in case the mclk division might be carried up in the fine division calculation */
|
||||
@ -78,25 +79,28 @@ static esp_err_t i2s_tdm_set_clock(i2s_chan_handle_t handle, const i2s_tdm_clk_c
|
||||
i2s_hal_clock_info_t clk_info;
|
||||
/* Calculate clock parameters */
|
||||
ESP_RETURN_ON_ERROR(i2s_tdm_calculate_clock(handle, clk_cfg, &clk_info), TAG, "clock calculate failed");
|
||||
ESP_LOGD(TAG, "Clock division info: [sclk] %"PRIu32" Hz [mdiv] %d [mclk] %"PRIu32" Hz [bdiv] %d [bclk] %"PRIu32" Hz",
|
||||
clk_info.sclk, clk_info.mclk_div, clk_info.mclk, clk_info.bclk_div, clk_info.bclk);
|
||||
|
||||
hal_utils_clk_div_t ret_mclk_div = {};
|
||||
portENTER_CRITICAL(&g_i2s.spinlock);
|
||||
/* Set clock configurations in HAL*/
|
||||
I2S_CLOCK_SRC_ATOMIC() {
|
||||
if (handle->dir == I2S_DIR_TX) {
|
||||
i2s_hal_set_tx_clock(&handle->controller->hal, &clk_info, clk_cfg->clk_src);
|
||||
i2s_hal_set_tx_clock(&handle->controller->hal, &clk_info, clk_cfg->clk_src, &ret_mclk_div);
|
||||
} else {
|
||||
i2s_hal_set_rx_clock(&handle->controller->hal, &clk_info, clk_cfg->clk_src);
|
||||
i2s_hal_set_rx_clock(&handle->controller->hal, &clk_info, clk_cfg->clk_src, &ret_mclk_div);
|
||||
}
|
||||
}
|
||||
portEXIT_CRITICAL(&g_i2s.spinlock);
|
||||
|
||||
/* Update the mode info: clock configuration */
|
||||
memcpy(&(tdm_cfg->clk_cfg), clk_cfg, sizeof(i2s_tdm_clk_config_t));
|
||||
/* Update the slot bit width to the actual slot bit width */
|
||||
tdm_cfg->slot_cfg.slot_bit_width = (int)tdm_cfg->slot_cfg.slot_bit_width < (int)tdm_cfg->slot_cfg.data_bit_width ?
|
||||
tdm_cfg->slot_cfg.data_bit_width : tdm_cfg->slot_cfg.slot_bit_width;
|
||||
handle->clk_src = clk_cfg->clk_src;
|
||||
handle->sclk_hz = clk_info.sclk;
|
||||
handle->origin_mclk_hz = ((uint64_t)clk_info.sclk * ret_mclk_div.denominator) / (ret_mclk_div.integer * ret_mclk_div.denominator + ret_mclk_div.numerator);
|
||||
handle->curr_mclk_hz = handle->origin_mclk_hz;
|
||||
|
||||
ESP_LOGD(TAG, "Clock division info: [sclk] %"PRIu32" Hz [mdiv] %"PRIu32" %"PRIu32"/%"PRIu32" [mclk] %"PRIu32" Hz [bdiv] %d [bclk] %"PRIu32" Hz",
|
||||
clk_info.sclk, ret_mclk_div.integer, ret_mclk_div.numerator, ret_mclk_div.denominator, handle->origin_mclk_hz, clk_info.bclk_div, clk_info.bclk);
|
||||
|
||||
return ret;
|
||||
}
|
||||
@ -147,6 +151,9 @@ static esp_err_t i2s_tdm_set_slot(i2s_chan_handle_t handle, const i2s_tdm_slot_c
|
||||
/* Update the mode info: slot configuration */
|
||||
i2s_tdm_config_t *tdm_cfg = (i2s_tdm_config_t *)(handle->mode_info);
|
||||
memcpy(&(tdm_cfg->slot_cfg), slot_cfg, sizeof(i2s_tdm_slot_config_t));
|
||||
/* Update the slot bit width to the actual slot bit width */
|
||||
tdm_cfg->slot_cfg.slot_bit_width = (int)tdm_cfg->slot_cfg.slot_bit_width < (int)tdm_cfg->slot_cfg.data_bit_width ?
|
||||
tdm_cfg->slot_cfg.data_bit_width : tdm_cfg->slot_cfg.slot_bit_width;
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD
|
||||
* SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
@ -96,6 +96,7 @@ typedef struct {
|
||||
*/
|
||||
} i2s_chan_info_t;
|
||||
|
||||
/************************************************** Basic APIs ********************************************************/
|
||||
/**
|
||||
* @brief Allocate new I2S channel(s)
|
||||
* @note The new created I2S channel handle will be REGISTERED state after it is allocated successfully.
|
||||
@ -174,28 +175,6 @@ esp_err_t i2s_channel_enable(i2s_chan_handle_t handle);
|
||||
*/
|
||||
esp_err_t i2s_channel_disable(i2s_chan_handle_t handle);
|
||||
|
||||
/**
|
||||
* @brief Preload the data into TX DMA buffer
|
||||
* @note Only allowed to be called when the channel state is READY, (i.e., channel has been initialized, but not started)
|
||||
* @note As the initial DMA buffer has no data inside, it will transmit the empty buffer after enabled the channel,
|
||||
* this function is used to preload the data into the DMA buffer, so that the valid data can be transmitted immediately
|
||||
* after the channel is enabled.
|
||||
* @note This function can be called multiple times before enabling the channel, the buffer that loaded later will be concatenated
|
||||
* behind the former loaded buffer. But when all the DMA buffers have been loaded, no more data can be preload then, please
|
||||
* check the `bytes_loaded` parameter to see how many bytes are loaded successfully, when the `bytes_loaded` is smaller than
|
||||
* the `size`, it means the DMA buffers are full.
|
||||
*
|
||||
* @param[in] tx_handle I2S TX channel handler
|
||||
* @param[in] src The pointer of the source buffer to be loaded
|
||||
* @param[in] size The source buffer size
|
||||
* @param[out] bytes_loaded The bytes that successfully been loaded into the TX DMA buffer
|
||||
* @return
|
||||
* - ESP_OK Load data successful
|
||||
* - ESP_ERR_INVALID_ARG NULL pointer or not TX direction
|
||||
* - ESP_ERR_INVALID_STATE This channel has not stated
|
||||
*/
|
||||
esp_err_t i2s_channel_preload_data(i2s_chan_handle_t tx_handle, const void *src, size_t size, size_t *bytes_loaded);
|
||||
|
||||
/**
|
||||
* @brief I2S write data
|
||||
* @note Only allowed to be called when the channel state is RUNNING, (i.e., TX channel has been started and is not writing now)
|
||||
@ -250,6 +229,47 @@ esp_err_t i2s_channel_read(i2s_chan_handle_t handle, void *dest, size_t size, si
|
||||
*/
|
||||
esp_err_t i2s_channel_register_event_callback(i2s_chan_handle_t handle, const i2s_event_callbacks_t *callbacks, void *user_data);
|
||||
|
||||
/************************************************ Advanced APIs *******************************************************/
|
||||
/**
|
||||
* @brief Preload the data into TX DMA buffer
|
||||
* @note Only allowed to be called when the channel state is READY, (i.e., channel has been initialized, but not started)
|
||||
* @note As the initial DMA buffer has no data inside, it will transmit the empty buffer after enabled the channel,
|
||||
* this function is used to preload the data into the DMA buffer, so that the valid data can be transmitted immediately
|
||||
* after the channel is enabled.
|
||||
* @note This function can be called multiple times before enabling the channel, the buffer that loaded later will be concatenated
|
||||
* behind the former loaded buffer. But when all the DMA buffers have been loaded, no more data can be preload then, please
|
||||
* check the `bytes_loaded` parameter to see how many bytes are loaded successfully, when the `bytes_loaded` is smaller than
|
||||
* the `size`, it means the DMA buffers are full.
|
||||
*
|
||||
* @param[in] tx_handle I2S TX channel handler
|
||||
* @param[in] src The pointer of the source buffer to be loaded
|
||||
* @param[in] size The source buffer size
|
||||
* @param[out] bytes_loaded The bytes that successfully been loaded into the TX DMA buffer
|
||||
* @return
|
||||
* - ESP_OK Load data successful
|
||||
* - ESP_ERR_INVALID_ARG NULL pointer or not TX direction
|
||||
* - ESP_ERR_INVALID_STATE This channel has not stated
|
||||
*/
|
||||
esp_err_t i2s_channel_preload_data(i2s_chan_handle_t tx_handle, const void *src, size_t size, size_t *bytes_loaded);
|
||||
|
||||
/**
|
||||
* @brief Tune the I2S clock rate
|
||||
* @note Only allowed to be called when the channel state is READY, (i.e., channel has been initialized, but not started)
|
||||
* @note This function is mainly to fine-tuning the mclk to match the speed of producer and consumer.
|
||||
* So that to avoid exsaust of the memory to store the data from producer.
|
||||
* Please take care the how different the frequency error can be tolerant by your codec,
|
||||
* otherwise the codec might stop working if the frequency changes a lot.
|
||||
*
|
||||
* @param[in] handle I2S channel handler
|
||||
* @param[in] tune_cfg The clock tuning configuration, can be NULL if only need the current clock result
|
||||
* @param[out] tune_info The clock tuning information, can be NULL if not needed
|
||||
* @return
|
||||
* - ESP_OK Tune the clock successfully
|
||||
* - ESP_ERR_INVALID_ARG Tune the clock failed because of the invalid argument like NULL pointer or out of range
|
||||
* - ESP_ERR_NOT_SUPPORTED Tune the clock failed because this function does not support to tune the external clock source
|
||||
*/
|
||||
esp_err_t i2s_channel_tune_rate(i2s_chan_handle_t handle, const i2s_tuning_config_t *tune_cfg, i2s_tuning_info_t *tune_info);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020-2024 Espressif Systems (Shanghai) CO LTD
|
||||
* SPDX-FileCopyrightText: 2020-2025 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
@ -87,6 +87,37 @@ typedef struct {
|
||||
*/
|
||||
} i2s_event_data_t;
|
||||
|
||||
/**
|
||||
* @brief I2S clock tuning operation
|
||||
*
|
||||
*/
|
||||
typedef enum {
|
||||
I2S_TUNING_MODE_ADDSUB, /*!< Add or subtract the tuning value based on the current clock */
|
||||
I2S_TUNING_MODE_SET, /*!< Set the tuning value to overwrite the current clock */
|
||||
I2S_TUNING_MODE_RESET, /*!< Set the clock to the initial value */
|
||||
} i2s_tuning_mode_t;
|
||||
|
||||
/**
|
||||
* @brief I2S clock tuning configurations
|
||||
*
|
||||
*/
|
||||
typedef struct {
|
||||
i2s_tuning_mode_t tune_mode; /*!< Tuning mode, which decides how to tune the MCLK with the tuning value */
|
||||
int32_t tune_mclk_val; /*!< Tuning value */
|
||||
int32_t max_delta_mclk; /*!< The maximum frequency that can be increased comparing to the initial MCLK freuqnecy */
|
||||
int32_t min_delta_mclk; /*!< The minimum frequency that can be decreased comparing to the initial MCLK freuqnecy */
|
||||
} i2s_tuning_config_t;
|
||||
|
||||
/**
|
||||
* @brief I2S clock tuning result
|
||||
*
|
||||
*/
|
||||
typedef struct {
|
||||
int32_t curr_mclk_hz; /*!< The current MCLK frequency after tuned */
|
||||
int32_t delta_mclk_hz; /*!< The current changed MCLK frequency comparing to the initial MCLK frequency */
|
||||
uint32_t water_mark; /*!< The water mark of the internal buffer, in percent */
|
||||
} i2s_tuning_info_t;
|
||||
|
||||
/**
|
||||
* @brief Event data structure for LP I2S
|
||||
*/
|
||||
|
@ -1016,3 +1016,78 @@ TEST_CASE("I2S_PDM2PCM_existence_test", "[i2s]")
|
||||
TEST_ESP_OK(i2s_del_channel(rx_handle));
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST_CASE("I2S_rate_tunning", "[i2s]")
|
||||
{
|
||||
i2s_chan_handle_t tx_handle;
|
||||
i2s_chan_handle_t rx_handle;
|
||||
|
||||
i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_0, I2S_ROLE_MASTER);
|
||||
i2s_std_config_t std_cfg = {
|
||||
.clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(SAMPLE_RATE),
|
||||
.slot_cfg = I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG(SAMPLE_BITS, I2S_SLOT_MODE_STEREO),
|
||||
.gpio_cfg = I2S_TEST_MASTER_DEFAULT_PIN,
|
||||
};
|
||||
std_cfg.gpio_cfg.din = std_cfg.gpio_cfg.dout; // GPIO loopback
|
||||
#if SOC_CLK_APLL_SUPPORTED
|
||||
std_cfg.clk_cfg.clk_src = I2S_CLK_SRC_APLL;
|
||||
#endif
|
||||
|
||||
TEST_ESP_OK(i2s_new_channel(&chan_cfg, &tx_handle, &rx_handle));
|
||||
TEST_ESP_OK(i2s_channel_init_std_mode(tx_handle, &std_cfg));
|
||||
TEST_ESP_OK(i2s_channel_init_std_mode(rx_handle, &std_cfg));
|
||||
|
||||
TEST_ESP_OK(i2s_channel_enable(rx_handle));
|
||||
TEST_ESP_OK(i2s_channel_enable(tx_handle));
|
||||
|
||||
i2s_tuning_info_t tune_init_res = {};
|
||||
TEST_ESP_OK(i2s_channel_tune_rate(tx_handle, NULL, &tune_init_res));
|
||||
i2s_tuning_info_t tune_info = {};
|
||||
|
||||
// Add/subtract case
|
||||
int32_t tune_val = 400;
|
||||
i2s_tuning_config_t tune_addsub_cfg = {
|
||||
.tune_mode = I2S_TUNING_MODE_ADDSUB,
|
||||
.tune_mclk_val = tune_val,
|
||||
.max_delta_mclk = 1000,
|
||||
.min_delta_mclk = -1000,
|
||||
};
|
||||
TEST_ESP_OK(i2s_channel_tune_rate(tx_handle, &tune_addsub_cfg, &tune_info));
|
||||
printf("# Addsub case: init mclk: %"PRIu32" tune val: %"PRId32" curr mclk: %"PRId32" delta mclk: %"PRId32"\n",
|
||||
tune_init_res.curr_mclk_hz, tune_val, tune_info.curr_mclk_hz, tune_info.delta_mclk_hz);
|
||||
TEST_ASSERT_UINT32_WITHIN(50, tune_init_res.curr_mclk_hz + tune_val, tune_info.curr_mclk_hz);
|
||||
|
||||
// Set case
|
||||
tune_val = tune_init_res.curr_mclk_hz - 500;
|
||||
i2s_tuning_config_t tune_set_cfg = {
|
||||
.tune_mode = I2S_TUNING_MODE_SET,
|
||||
.tune_mclk_val = tune_val,
|
||||
.max_delta_mclk = 1000,
|
||||
.min_delta_mclk = -1000,
|
||||
};
|
||||
TEST_ESP_OK(i2s_channel_tune_rate(tx_handle, &tune_set_cfg, &tune_info));
|
||||
printf("# Set case: init mclk: %"PRIu32" tune val: %"PRId32" curr mclk: %"PRId32" delta mclk: %"PRId32"\n",
|
||||
tune_init_res.curr_mclk_hz, tune_val, tune_info.curr_mclk_hz, tune_info.delta_mclk_hz);
|
||||
TEST_ASSERT_UINT32_WITHIN(50, tune_val, tune_info.curr_mclk_hz);
|
||||
|
||||
// Out of range case
|
||||
tune_set_cfg.min_delta_mclk = -400;
|
||||
TEST_ESP_OK(i2s_channel_tune_rate(tx_handle, &tune_set_cfg, &tune_info));
|
||||
printf("# Out of range case: init mclk: %"PRIu32" tune val: %"PRId32" curr mclk: %"PRId32" delta mclk: %"PRId32"\n",
|
||||
tune_init_res.curr_mclk_hz, tune_val, tune_info.curr_mclk_hz, tune_info.delta_mclk_hz);
|
||||
TEST_ASSERT_UINT32_WITHIN(50, tune_init_res.curr_mclk_hz + tune_set_cfg.min_delta_mclk, tune_info.curr_mclk_hz);
|
||||
|
||||
// Reset case
|
||||
i2s_tuning_config_t tune_reset_cfg = {
|
||||
.tune_mode = I2S_TUNING_MODE_RESET,
|
||||
};
|
||||
TEST_ESP_OK(i2s_channel_tune_rate(tx_handle, &tune_reset_cfg, &tune_info));
|
||||
printf("# Reset case: init mclk: %"PRIu32" tune val: %"PRId32" curr mclk: %"PRId32" delta mclk: %"PRId32"\n",
|
||||
tune_init_res.curr_mclk_hz, tune_val, tune_info.curr_mclk_hz, tune_info.delta_mclk_hz);
|
||||
TEST_ASSERT_EQUAL_UINT32(tune_init_res.curr_mclk_hz, tune_info.curr_mclk_hz);
|
||||
|
||||
TEST_ESP_OK(i2s_channel_disable(tx_handle));
|
||||
TEST_ESP_OK(i2s_channel_disable(rx_handle));
|
||||
TEST_ESP_OK(i2s_del_channel(tx_handle));
|
||||
TEST_ESP_OK(i2s_del_channel(rx_handle));
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD
|
||||
* SPDX-FileCopyrightText: 2021-2025 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
@ -388,6 +388,28 @@ static inline void i2s_ll_rx_set_mclk(i2s_dev_t *hw, const hal_utils_clk_div_t *
|
||||
i2s_ll_rx_set_raw_clk_div(hw, mclk_div->integer, div_x, div_y, div_z, div_yn1);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Update the TX configuration
|
||||
*
|
||||
* @param hw Peripheral I2S hardware instance address.
|
||||
*/
|
||||
static inline void i2s_ll_tx_update(i2s_dev_t *hw)
|
||||
{
|
||||
hw->tx_conf.tx_update = 1;
|
||||
while (hw->tx_conf.tx_update);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Update the RX configuration
|
||||
*
|
||||
* @param hw Peripheral I2S hardware instance address.
|
||||
*/
|
||||
static inline void i2s_ll_rx_update(i2s_dev_t *hw)
|
||||
{
|
||||
hw->rx_conf.rx_update = 1;
|
||||
while (hw->rx_conf.rx_update);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Start I2S TX
|
||||
*
|
||||
@ -396,8 +418,7 @@ static inline void i2s_ll_rx_set_mclk(i2s_dev_t *hw, const hal_utils_clk_div_t *
|
||||
static inline void i2s_ll_tx_start(i2s_dev_t *hw)
|
||||
{
|
||||
// Have to update registers before start
|
||||
hw->tx_conf.tx_update = 1;
|
||||
while (hw->tx_conf.tx_update);
|
||||
i2s_ll_tx_update(hw);
|
||||
hw->tx_conf.tx_start = 1;
|
||||
}
|
||||
|
||||
@ -409,8 +430,7 @@ static inline void i2s_ll_tx_start(i2s_dev_t *hw)
|
||||
static inline void i2s_ll_rx_start(i2s_dev_t *hw)
|
||||
{
|
||||
// Have to update registers before start
|
||||
hw->rx_conf.rx_update = 1;
|
||||
while (hw->rx_conf.rx_update);
|
||||
i2s_ll_rx_update(hw);
|
||||
hw->rx_conf.rx_start = 1;
|
||||
}
|
||||
|
||||
|
@ -420,6 +420,28 @@ static inline void i2s_ll_rx_set_mclk(i2s_dev_t *hw, const hal_utils_clk_div_t *
|
||||
i2s_ll_rx_set_raw_clk_div(hw, mclk_div->integer, div_x, div_y, div_z, div_yn1);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Update the TX configuration
|
||||
*
|
||||
* @param hw Peripheral I2S hardware instance address.
|
||||
*/
|
||||
static inline void i2s_ll_tx_update(i2s_dev_t *hw)
|
||||
{
|
||||
hw->tx_conf.tx_update = 1;
|
||||
while (hw->tx_conf.tx_update);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Update the RX configuration
|
||||
*
|
||||
* @param hw Peripheral I2S hardware instance address.
|
||||
*/
|
||||
static inline void i2s_ll_rx_update(i2s_dev_t *hw)
|
||||
{
|
||||
hw->rx_conf.rx_update = 1;
|
||||
while (hw->rx_conf.rx_update);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Start I2S TX
|
||||
*
|
||||
@ -428,8 +450,7 @@ static inline void i2s_ll_rx_set_mclk(i2s_dev_t *hw, const hal_utils_clk_div_t *
|
||||
static inline void i2s_ll_tx_start(i2s_dev_t *hw)
|
||||
{
|
||||
// Have to update registers before start
|
||||
hw->tx_conf.tx_update = 1;
|
||||
while (hw->tx_conf.tx_update);
|
||||
i2s_ll_tx_update(hw);
|
||||
hw->tx_conf.tx_start = 1;
|
||||
}
|
||||
|
||||
@ -441,8 +462,7 @@ static inline void i2s_ll_tx_start(i2s_dev_t *hw)
|
||||
static inline void i2s_ll_rx_start(i2s_dev_t *hw)
|
||||
{
|
||||
// Have to update registers before start
|
||||
hw->rx_conf.rx_update = 1;
|
||||
while (hw->rx_conf.rx_update);
|
||||
i2s_ll_rx_update(hw);
|
||||
hw->rx_conf.rx_start = 1;
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD
|
||||
* SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
@ -410,6 +410,28 @@ static inline void i2s_ll_rx_set_mclk(i2s_dev_t *hw, const hal_utils_clk_div_t *
|
||||
i2s_ll_rx_set_raw_clk_div(hw, mclk_div->integer, div_x, div_y, div_z, div_yn1);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Update the TX configuration
|
||||
*
|
||||
* @param hw Peripheral I2S hardware instance address.
|
||||
*/
|
||||
static inline void i2s_ll_tx_update(i2s_dev_t *hw)
|
||||
{
|
||||
hw->tx_conf.tx_update = 1;
|
||||
while (hw->tx_conf.tx_update);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Update the RX configuration
|
||||
*
|
||||
* @param hw Peripheral I2S hardware instance address.
|
||||
*/
|
||||
static inline void i2s_ll_rx_update(i2s_dev_t *hw)
|
||||
{
|
||||
hw->rx_conf.rx_update = 1;
|
||||
while (hw->rx_conf.rx_update);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Start I2S TX
|
||||
*
|
||||
@ -418,8 +440,7 @@ static inline void i2s_ll_rx_set_mclk(i2s_dev_t *hw, const hal_utils_clk_div_t *
|
||||
static inline void i2s_ll_tx_start(i2s_dev_t *hw)
|
||||
{
|
||||
// Have to update registers before start
|
||||
hw->tx_conf.tx_update = 1;
|
||||
while (hw->tx_conf.tx_update);
|
||||
i2s_ll_tx_update(hw);
|
||||
hw->tx_conf.tx_start = 1;
|
||||
}
|
||||
|
||||
@ -431,8 +452,7 @@ static inline void i2s_ll_tx_start(i2s_dev_t *hw)
|
||||
static inline void i2s_ll_rx_start(i2s_dev_t *hw)
|
||||
{
|
||||
// Have to update registers before start
|
||||
hw->rx_conf.rx_update = 1;
|
||||
while (hw->rx_conf.rx_update);
|
||||
i2s_ll_rx_update(hw);
|
||||
hw->rx_conf.rx_start = 1;
|
||||
}
|
||||
|
||||
|
@ -420,6 +420,28 @@ static inline void i2s_ll_rx_set_mclk(i2s_dev_t *hw, const hal_utils_clk_div_t *
|
||||
i2s_ll_rx_set_raw_clk_div(hw, mclk_div->integer, div_x, div_y, div_z, div_yn1);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Update the TX configuration
|
||||
*
|
||||
* @param hw Peripheral I2S hardware instance address.
|
||||
*/
|
||||
static inline void i2s_ll_tx_update(i2s_dev_t *hw)
|
||||
{
|
||||
hw->tx_conf.tx_update = 1;
|
||||
while (hw->tx_conf.tx_update);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Update the RX configuration
|
||||
*
|
||||
* @param hw Peripheral I2S hardware instance address.
|
||||
*/
|
||||
static inline void i2s_ll_rx_update(i2s_dev_t *hw)
|
||||
{
|
||||
hw->rx_conf.rx_update = 1;
|
||||
while (hw->rx_conf.rx_update);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Start I2S TX
|
||||
*
|
||||
@ -428,8 +450,7 @@ static inline void i2s_ll_rx_set_mclk(i2s_dev_t *hw, const hal_utils_clk_div_t *
|
||||
static inline void i2s_ll_tx_start(i2s_dev_t *hw)
|
||||
{
|
||||
// Have to update registers before start
|
||||
hw->tx_conf.tx_update = 1;
|
||||
while (hw->tx_conf.tx_update);
|
||||
i2s_ll_tx_update(hw);
|
||||
hw->tx_conf.tx_start = 1;
|
||||
}
|
||||
|
||||
@ -441,8 +462,7 @@ static inline void i2s_ll_tx_start(i2s_dev_t *hw)
|
||||
static inline void i2s_ll_rx_start(i2s_dev_t *hw)
|
||||
{
|
||||
// Have to update registers before start
|
||||
hw->rx_conf.rx_update = 1;
|
||||
while (hw->rx_conf.rx_update);
|
||||
i2s_ll_rx_update(hw);
|
||||
hw->rx_conf.rx_start = 1;
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD
|
||||
* SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
@ -418,6 +418,28 @@ static inline void i2s_ll_rx_set_mclk(i2s_dev_t *hw, const hal_utils_clk_div_t *
|
||||
i2s_ll_rx_set_raw_clk_div(hw, mclk_div->integer, div_x, div_y, div_z, div_yn1);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Update the TX configuration
|
||||
*
|
||||
* @param hw Peripheral I2S hardware instance address.
|
||||
*/
|
||||
static inline void i2s_ll_tx_update(i2s_dev_t *hw)
|
||||
{
|
||||
hw->tx_conf.tx_update = 1;
|
||||
while (hw->tx_conf.tx_update);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Update the RX configuration
|
||||
*
|
||||
* @param hw Peripheral I2S hardware instance address.
|
||||
*/
|
||||
static inline void i2s_ll_rx_update(i2s_dev_t *hw)
|
||||
{
|
||||
hw->rx_conf.rx_update = 1;
|
||||
while (hw->rx_conf.rx_update);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Start I2S TX
|
||||
*
|
||||
@ -426,8 +448,7 @@ static inline void i2s_ll_rx_set_mclk(i2s_dev_t *hw, const hal_utils_clk_div_t *
|
||||
static inline void i2s_ll_tx_start(i2s_dev_t *hw)
|
||||
{
|
||||
// Have to update registers before start
|
||||
hw->tx_conf.tx_update = 1;
|
||||
while (hw->tx_conf.tx_update);
|
||||
i2s_ll_tx_update(hw);
|
||||
hw->tx_conf.tx_start = 1;
|
||||
}
|
||||
|
||||
@ -439,8 +460,7 @@ static inline void i2s_ll_tx_start(i2s_dev_t *hw)
|
||||
static inline void i2s_ll_rx_start(i2s_dev_t *hw)
|
||||
{
|
||||
// Have to update registers before start
|
||||
hw->rx_conf.rx_update = 1;
|
||||
while (hw->rx_conf.rx_update);
|
||||
i2s_ll_rx_update(hw);
|
||||
hw->rx_conf.rx_start = 1;
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD
|
||||
* SPDX-FileCopyrightText: 2023-2025 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
@ -679,6 +679,28 @@ static inline void _i2s_ll_rx_set_mclk(i2s_dev_t *hw, const hal_utils_clk_div_t
|
||||
/// the critical section needs to declare the __DECLARE_RCC_ATOMIC_ENV variable in advance
|
||||
#define i2s_ll_rx_set_mclk(...) (void)__DECLARE_RCC_ATOMIC_ENV; _i2s_ll_rx_set_mclk(__VA_ARGS__)
|
||||
|
||||
/**
|
||||
* @brief Update the TX configuration
|
||||
*
|
||||
* @param hw Peripheral I2S hardware instance address.
|
||||
*/
|
||||
static inline void i2s_ll_tx_update(i2s_dev_t *hw)
|
||||
{
|
||||
hw->tx_conf.tx_update = 1;
|
||||
while (hw->tx_conf.tx_update);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Update the RX configuration
|
||||
*
|
||||
* @param hw Peripheral I2S hardware instance address.
|
||||
*/
|
||||
static inline void i2s_ll_rx_update(i2s_dev_t *hw)
|
||||
{
|
||||
hw->rx_conf.rx_update = 1;
|
||||
while (hw->rx_conf.rx_update);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Start I2S TX
|
||||
*
|
||||
@ -687,8 +709,7 @@ static inline void _i2s_ll_rx_set_mclk(i2s_dev_t *hw, const hal_utils_clk_div_t
|
||||
static inline void i2s_ll_tx_start(i2s_dev_t *hw)
|
||||
{
|
||||
// Have to update registers before start
|
||||
hw->tx_conf.tx_update = 1;
|
||||
while (hw->tx_conf.tx_update);
|
||||
i2s_ll_tx_update(hw);
|
||||
hw->tx_conf.tx_start = 1;
|
||||
}
|
||||
|
||||
@ -700,8 +721,7 @@ static inline void i2s_ll_tx_start(i2s_dev_t *hw)
|
||||
static inline void i2s_ll_rx_start(i2s_dev_t *hw)
|
||||
{
|
||||
// Have to update registers before start
|
||||
hw->rx_conf.rx_update = 1;
|
||||
while (hw->rx_conf.rx_update);
|
||||
i2s_ll_rx_update(hw);
|
||||
hw->rx_conf.rx_start = 1;
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD
|
||||
* SPDX-FileCopyrightText: 2021-2025 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
@ -394,6 +394,27 @@ static inline void i2s_ll_rx_set_mclk(i2s_dev_t *hw, const hal_utils_clk_div_t *
|
||||
i2s_ll_rx_set_raw_clk_div(hw, mclk_div->integer, div_x, div_y, div_z, div_yn1);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Update the TX configuration
|
||||
*
|
||||
* @param hw Peripheral I2S hardware instance address.
|
||||
*/
|
||||
static inline void i2s_ll_tx_update(i2s_dev_t *hw)
|
||||
{
|
||||
hw->tx_conf.tx_update = 1;
|
||||
while (hw->tx_conf.tx_update);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Update the RX configuration
|
||||
*
|
||||
* @param hw Peripheral I2S hardware instance address.
|
||||
*/
|
||||
static inline void i2s_ll_rx_update(i2s_dev_t *hw)
|
||||
{
|
||||
hw->rx_conf.rx_update = 1;
|
||||
while (hw->rx_conf.rx_update);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Start I2S TX
|
||||
@ -403,8 +424,7 @@ static inline void i2s_ll_rx_set_mclk(i2s_dev_t *hw, const hal_utils_clk_div_t *
|
||||
static inline void i2s_ll_tx_start(i2s_dev_t *hw)
|
||||
{
|
||||
// Have to update registers before start
|
||||
hw->tx_conf.tx_update = 1;
|
||||
while (hw->tx_conf.tx_update);
|
||||
i2s_ll_tx_update(hw);
|
||||
hw->tx_conf.tx_start = 1;
|
||||
}
|
||||
|
||||
@ -416,8 +436,7 @@ static inline void i2s_ll_tx_start(i2s_dev_t *hw)
|
||||
static inline void i2s_ll_rx_start(i2s_dev_t *hw)
|
||||
{
|
||||
// Have to update registers before start
|
||||
hw->rx_conf.rx_update = 1;
|
||||
while (hw->rx_conf.rx_update);
|
||||
i2s_ll_rx_update(hw);
|
||||
hw->rx_conf.rx_start = 1;
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020-2024 Espressif Systems (Shanghai) CO LTD
|
||||
* SPDX-FileCopyrightText: 2020-2025 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
@ -68,68 +68,72 @@ void i2s_hal_init(i2s_hal_context_t *hal, int port_id)
|
||||
}
|
||||
|
||||
#if SOC_PERIPH_CLK_CTRL_SHARED
|
||||
void _i2s_hal_set_tx_clock(i2s_hal_context_t *hal, const i2s_hal_clock_info_t *clk_info, i2s_clock_src_t clk_src)
|
||||
void _i2s_hal_set_tx_clock(i2s_hal_context_t *hal, const i2s_hal_clock_info_t *clk_info, i2s_clock_src_t clk_src, hal_utils_clk_div_t *ret_mclk_div)
|
||||
{
|
||||
if (clk_info) {
|
||||
hal_utils_clk_div_t mclk_div = {};
|
||||
hal_utils_clk_div_t *mclk_div_ptr = ret_mclk_div ? ret_mclk_div : &mclk_div;
|
||||
#if SOC_I2S_HW_VERSION_2
|
||||
_i2s_ll_tx_enable_clock(hal->dev);
|
||||
_i2s_ll_mclk_bind_to_tx_clk(hal->dev);
|
||||
#endif
|
||||
_i2s_ll_tx_clk_set_src(hal->dev, clk_src);
|
||||
i2s_hal_calc_mclk_precise_division(clk_info->sclk, clk_info->mclk, &mclk_div);
|
||||
_i2s_ll_tx_set_mclk(hal->dev, &mclk_div);
|
||||
i2s_hal_calc_mclk_precise_division(clk_info->sclk, clk_info->mclk, mclk_div_ptr);
|
||||
_i2s_ll_tx_set_mclk(hal->dev, mclk_div_ptr);
|
||||
i2s_ll_tx_set_bck_div_num(hal->dev, clk_info->bclk_div);
|
||||
} else {
|
||||
_i2s_ll_tx_clk_set_src(hal->dev, clk_src);
|
||||
}
|
||||
}
|
||||
|
||||
void _i2s_hal_set_rx_clock(i2s_hal_context_t *hal, const i2s_hal_clock_info_t *clk_info, i2s_clock_src_t clk_src)
|
||||
void _i2s_hal_set_rx_clock(i2s_hal_context_t *hal, const i2s_hal_clock_info_t *clk_info, i2s_clock_src_t clk_src, hal_utils_clk_div_t *ret_mclk_div)
|
||||
{
|
||||
if (clk_info) {
|
||||
hal_utils_clk_div_t mclk_div = {};
|
||||
hal_utils_clk_div_t *mclk_div_ptr = ret_mclk_div ? ret_mclk_div : &mclk_div;
|
||||
#if SOC_I2S_HW_VERSION_2
|
||||
_i2s_ll_rx_enable_clock(hal->dev);
|
||||
_i2s_ll_mclk_bind_to_rx_clk(hal->dev);
|
||||
#endif
|
||||
_i2s_ll_rx_clk_set_src(hal->dev, clk_src);
|
||||
i2s_hal_calc_mclk_precise_division(clk_info->sclk, clk_info->mclk, &mclk_div);
|
||||
_i2s_ll_rx_set_mclk(hal->dev, &mclk_div);
|
||||
i2s_hal_calc_mclk_precise_division(clk_info->sclk, clk_info->mclk, mclk_div_ptr);
|
||||
_i2s_ll_rx_set_mclk(hal->dev, mclk_div_ptr);
|
||||
i2s_ll_rx_set_bck_div_num(hal->dev, clk_info->bclk_div);
|
||||
} else {
|
||||
_i2s_ll_rx_clk_set_src(hal->dev, clk_src);
|
||||
}
|
||||
}
|
||||
#else
|
||||
void i2s_hal_set_tx_clock(i2s_hal_context_t *hal, const i2s_hal_clock_info_t *clk_info, i2s_clock_src_t clk_src)
|
||||
void i2s_hal_set_tx_clock(i2s_hal_context_t *hal, const i2s_hal_clock_info_t *clk_info, i2s_clock_src_t clk_src, hal_utils_clk_div_t *ret_mclk_div)
|
||||
{
|
||||
if (clk_info) {
|
||||
hal_utils_clk_div_t mclk_div = {};
|
||||
hal_utils_clk_div_t *mclk_div_ptr = ret_mclk_div ? ret_mclk_div : &mclk_div;
|
||||
#if SOC_I2S_HW_VERSION_2
|
||||
i2s_ll_tx_enable_clock(hal->dev);
|
||||
i2s_ll_mclk_bind_to_tx_clk(hal->dev);
|
||||
#endif
|
||||
i2s_ll_tx_clk_set_src(hal->dev, clk_src);
|
||||
i2s_hal_calc_mclk_precise_division(clk_info->sclk, clk_info->mclk, &mclk_div);
|
||||
i2s_ll_tx_set_mclk(hal->dev, &mclk_div);
|
||||
i2s_hal_calc_mclk_precise_division(clk_info->sclk, clk_info->mclk, mclk_div_ptr);
|
||||
i2s_ll_tx_set_mclk(hal->dev, mclk_div_ptr);
|
||||
i2s_ll_tx_set_bck_div_num(hal->dev, clk_info->bclk_div);
|
||||
} else {
|
||||
i2s_ll_tx_clk_set_src(hal->dev, clk_src);
|
||||
}
|
||||
}
|
||||
|
||||
void i2s_hal_set_rx_clock(i2s_hal_context_t *hal, const i2s_hal_clock_info_t *clk_info, i2s_clock_src_t clk_src)
|
||||
void i2s_hal_set_rx_clock(i2s_hal_context_t *hal, const i2s_hal_clock_info_t *clk_info, i2s_clock_src_t clk_src, hal_utils_clk_div_t *ret_mclk_div)
|
||||
{
|
||||
if (clk_info) {
|
||||
hal_utils_clk_div_t mclk_div = {};
|
||||
hal_utils_clk_div_t *mclk_div_ptr = ret_mclk_div ? ret_mclk_div : &mclk_div;
|
||||
#if SOC_I2S_HW_VERSION_2
|
||||
i2s_ll_rx_enable_clock(hal->dev);
|
||||
i2s_ll_mclk_bind_to_rx_clk(hal->dev);
|
||||
#endif
|
||||
i2s_ll_rx_clk_set_src(hal->dev, clk_src);
|
||||
i2s_hal_calc_mclk_precise_division(clk_info->sclk, clk_info->mclk, &mclk_div);
|
||||
i2s_ll_rx_set_mclk(hal->dev, &mclk_div);
|
||||
i2s_hal_calc_mclk_precise_division(clk_info->sclk, clk_info->mclk, mclk_div_ptr);
|
||||
i2s_ll_rx_set_mclk(hal->dev, mclk_div_ptr);
|
||||
i2s_ll_rx_set_bck_div_num(hal->dev, clk_info->bclk_div);
|
||||
} else {
|
||||
i2s_ll_rx_clk_set_src(hal->dev, clk_src);
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2020-2024 Espressif Systems (Shanghai) CO LTD
|
||||
* SPDX-FileCopyrightText: 2020-2025 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
@ -159,7 +159,7 @@ void i2s_hal_calc_mclk_precise_division(uint32_t sclk, uint32_t mclk, hal_utils_
|
||||
* @param clk_info clock information, if it is NULL, only set the clock source
|
||||
* @param clk_src clock source
|
||||
*/
|
||||
void _i2s_hal_set_tx_clock(i2s_hal_context_t *hal, const i2s_hal_clock_info_t *clk_info, i2s_clock_src_t clk_src);
|
||||
void _i2s_hal_set_tx_clock(i2s_hal_context_t *hal, const i2s_hal_clock_info_t *clk_info, i2s_clock_src_t clk_src, hal_utils_clk_div_t *ret_mclk_div);
|
||||
/// use a macro to wrap the function, force the caller to use it in a critical section
|
||||
/// the critical section needs to declare the __DECLARE_RCC_ATOMIC_ENV variable in advance
|
||||
#define i2s_hal_set_tx_clock(...) (void)__DECLARE_RCC_ATOMIC_ENV; _i2s_hal_set_tx_clock(__VA_ARGS__)
|
||||
@ -170,8 +170,9 @@ void _i2s_hal_set_tx_clock(i2s_hal_context_t *hal, const i2s_hal_clock_info_t *c
|
||||
* @param hal Context of the HAL layer
|
||||
* @param clk_info clock information, if it is NULL, only set the clock source
|
||||
* @param clk_src clock source
|
||||
* @param ret_mclk_div return mclk division coefficients, including integer part and decimal part
|
||||
*/
|
||||
void i2s_hal_set_tx_clock(i2s_hal_context_t *hal, const i2s_hal_clock_info_t *clk_info, i2s_clock_src_t clk_src);
|
||||
void i2s_hal_set_tx_clock(i2s_hal_context_t *hal, const i2s_hal_clock_info_t *clk_info, i2s_clock_src_t clk_src, hal_utils_clk_div_t *ret_mclk_div);
|
||||
#endif // SOC_PERIPH_CLK_CTRL_SHARED
|
||||
|
||||
/**
|
||||
@ -180,8 +181,9 @@ void i2s_hal_set_tx_clock(i2s_hal_context_t *hal, const i2s_hal_clock_info_t *cl
|
||||
* @param hal Context of the HAL layer
|
||||
* @param clk_info clock information, if it is NULL, only set the clock source
|
||||
* @param clk_src clock source
|
||||
* @param ret_mclk_div return mclk division coefficients, including integer part and decimal part
|
||||
*/
|
||||
void _i2s_hal_set_rx_clock(i2s_hal_context_t *hal, const i2s_hal_clock_info_t *clk_info, i2s_clock_src_t clk_src);
|
||||
void _i2s_hal_set_rx_clock(i2s_hal_context_t *hal, const i2s_hal_clock_info_t *clk_info, i2s_clock_src_t clk_src, hal_utils_clk_div_t *ret_mclk_div);
|
||||
|
||||
#if SOC_PERIPH_CLK_CTRL_SHARED
|
||||
/// use a macro to wrap the function, force the caller to use it in a critical section
|
||||
|
@ -320,6 +320,14 @@ Configuration
|
||||
|
||||
Users can initialize a channel by calling corresponding functions (i.e., :func:`i2s_channel_init_std_mode`, :func:`i2s_channel_init_pdm_rx_mode`, :func:`i2s_channel_init_pdm_tx_mode`, or :func:`i2s_channel_init_tdm_mode`) to a specific mode. If the configurations need to be updated after initialization, users have to first call :cpp:func:`i2s_channel_disable` to ensure that the channel has stopped, and then call corresponding ``reconfig`` functions, like :cpp:func:`i2s_channel_reconfig_std_slot`, :cpp:func:`i2s_channel_reconfig_std_clock`, and :cpp:func:`i2s_channel_reconfig_std_gpio`.
|
||||
|
||||
Advanced API
|
||||
^^^^^^^^^^^^
|
||||
|
||||
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-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
|
||||
^^^^^^^^^
|
||||
|
||||
|
@ -320,6 +320,14 @@ I2S 的数据传输(包括数据发送和接收)由 DMA 实现。在传输
|
||||
|
||||
用户可以通过调用相应函数(即 :func:`i2s_channel_init_std_mode`、 :func:`i2s_channel_init_pdm_rx_mode`、 :func:`i2s_channel_init_pdm_tx_mode` 或 :func:`i2s_channel_init_tdm_mode`)将通道初始化为特定模式。如果初始化后需要更新配置,必须先调用 :cpp:func:`i2s_channel_disable` 以确保通道已经停止运行,然后再调用相应的 'reconfig' 函数,例如 :cpp:func:`i2s_channel_reconfig_std_slot`、 :cpp:func:`i2s_channel_reconfig_std_clock` 和 :cpp:func:`i2s_channel_reconfig_std_gpio`。
|
||||
|
||||
进阶 API
|
||||
^^^^^^^^^^^^^^
|
||||
|
||||
为满足高质量音频需求,驱动提供了以下进阶 API:
|
||||
|
||||
- :cpp:func:`i2s_channel_preload_data`: 用于预加载音频数据到 I2S 内部缓存,使得 TX 通道使能后能够立即发送数据,以此降低音频初始输出延迟。
|
||||
- :cpp:func:`i2s_channel_tune_rate`: 用于在运行时动态微调音频速率,以匹配音频数据生产者和消费者的速度,从而防止因速率不匹配导致的中间缓存数据累积或不足。
|
||||
|
||||
IRAM 安全
|
||||
^^^^^^^^^
|
||||
|
||||
|
@ -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
|
||||
|
@ -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)
|
139
examples/peripherals/i2s/i2s_advance/i2s_usb/README.md
Normal file
139
examples/peripherals/i2s/i2s_advance/i2s_usb/README.md
Normal file
@ -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.<target>`.
|
||||
|
||||
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.
|
@ -0,0 +1,3 @@
|
||||
idf_component_register(SRCS "i2s_usb_example_main.c"
|
||||
PRIV_REQUIRES esp_driver_i2s esp_driver_i2c
|
||||
INCLUDE_DIRS ".")
|
@ -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
|
@ -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
|
@ -0,0 +1,286 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 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 "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));
|
||||
}
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
dependencies:
|
||||
espressif/usb_device_uac: ^1.1.0
|
||||
espressif/esp_codec_dev: ^1.3.4
|
@ -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')
|
@ -0,0 +1 @@
|
||||
CONFIG_EXAMPLE_DYNAMIC_TUNING_EN=y
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -25,7 +25,7 @@ def test_i2s_pdm_tx_example(dut: Dut) -> None:
|
||||
dut.expect(r'i2s_common: DMA malloc info: dma_desc_num = ([0-9]+), '
|
||||
r'dma_desc_buf_size = dma_frame_num \* slot_num \* data_bit_width = ([0-9]+)', timeout=5)
|
||||
dut.expect(r'i2s_pdm: Clock division info: \[sclk\] ([0-9]+) Hz '
|
||||
r'\[mdiv\] ([0-9]+) \[mclk\] ([0-9]+) Hz \[bdiv\] ([0-9]+) \[bclk\] ([0-9]+) Hz', timeout=5)
|
||||
r'\[mdiv\] ([0-9]+) ([0-9]+)/([0-9]+) \[mclk\] ([0-9]+) Hz \[bdiv\] ([0-9]+) \[bclk\] ([0-9]+) Hz', timeout=5)
|
||||
dut.expect(r'i2s_pdm: The tx channel on I2S0 has been initialized to PDM TX mode successfully', timeout=5)
|
||||
dut.expect(r'i2s_common: i2s tx channel enabled', timeout=5)
|
||||
dut.expect(r'Playing bass `twinkle twinkle little star`', timeout=5)
|
||||
|
@ -21,12 +21,12 @@ def test_i2s_basic_example(dut: Dut) -> None:
|
||||
dut.expect(r'i2s_common: DMA malloc info: dma_desc_num = ([0-9]+), '
|
||||
r'dma_desc_buf_size = dma_frame_num \* slot_num \* data_bit_width = ([0-9]+)', timeout=5)
|
||||
dut.expect(r'i2s_std: Clock division info: \[sclk\] ([0-9]+) Hz '
|
||||
r'\[mdiv\] ([0-9]+) \[mclk\] ([0-9]+) Hz \[bdiv\] ([0-9]+) \[bclk\] ([0-9]+) Hz', timeout=5)
|
||||
r'\[mdiv\] ([0-9]+) ([0-9]+)/([0-9]+) \[mclk\] ([0-9]+) Hz \[bdiv\] ([0-9]+) \[bclk\] ([0-9]+) Hz', timeout=5)
|
||||
dut.expect(r'i2s_std: The tx channel on I2S0 has been initialized to STD mode successfully', timeout=5)
|
||||
dut.expect(r'i2s_common: DMA malloc info: dma_desc_num = ([0-9]+), '
|
||||
r'dma_desc_buf_size = dma_frame_num \* slot_num \* data_bit_width = ([0-9]+)', timeout=5)
|
||||
dut.expect(r'i2s_std: Clock division info: \[sclk\] ([0-9]+) Hz '
|
||||
r'\[mdiv\] ([0-9]+) \[mclk\] ([0-9]+) Hz \[bdiv\] ([0-9]+) \[bclk\] ([0-9]+) Hz', timeout=5)
|
||||
r'\[mdiv\] ([0-9]+) ([0-9]+)/([0-9]+) \[mclk\] ([0-9]+) Hz \[bdiv\] ([0-9]+) \[bclk\] ([0-9]+) Hz', timeout=5)
|
||||
dut.expect(r'i2s_std: The rx channel on I2S0 has been initialized to STD mode successfully', timeout=5)
|
||||
chan_enable_pattern = [
|
||||
r'i2s_common: i2s tx channel enabled',
|
||||
|
@ -19,12 +19,12 @@ def test_i2s_tdm_example(dut: Dut) -> None:
|
||||
dut.expect(r'i2s_common: DMA malloc info: dma_desc_num = ([0-9]+), '
|
||||
r'dma_desc_buf_size = dma_frame_num \* slot_num \* data_bit_width = ([0-9]+)', timeout=5)
|
||||
dut.expect(r'i2s_tdm: Clock division info: \[sclk\] ([0-9]+) Hz '
|
||||
r'\[mdiv\] ([0-9]+) \[mclk\] ([0-9]+) Hz \[bdiv\] ([0-9]+) \[bclk\] ([0-9]+) Hz', timeout=5)
|
||||
r'\[mdiv\] ([0-9]+) ([0-9]+)/([0-9]+) \[mclk\] ([0-9]+) Hz \[bdiv\] ([0-9]+) \[bclk\] ([0-9]+) Hz', timeout=5)
|
||||
dut.expect(r'i2s_tdm: The tx channel on I2S0 has been initialized to TDM mode successfully', timeout=5)
|
||||
dut.expect(r'i2s_common: DMA malloc info: dma_desc_num = ([0-9]+), '
|
||||
r'dma_desc_buf_size = dma_frame_num \* slot_num \* data_bit_width = ([0-9]+)', timeout=5)
|
||||
dut.expect(r'i2s_tdm: Clock division info: \[sclk\] ([0-9]+) Hz '
|
||||
r'\[mdiv\] ([0-9]+) \[mclk\] ([0-9]+) Hz \[bdiv\] ([0-9]+) \[bclk\] ([0-9]+) Hz', timeout=5)
|
||||
r'\[mdiv\] ([0-9]+) ([0-9]+)/([0-9]+) \[mclk\] ([0-9]+) Hz \[bdiv\] ([0-9]+) \[bclk\] ([0-9]+) Hz', timeout=5)
|
||||
dut.expect(r'i2s_tdm: The rx channel on I2S0 has been initialized to TDM mode successfully', timeout=5)
|
||||
chan_enable_pattern = [
|
||||
r'i2s_common: i2s tx channel enabled',
|
||||
|
Loading…
x
Reference in New Issue
Block a user