From f44f4a82e69beccec990e5a1be4699b577f6cbf1 Mon Sep 17 00:00:00 2001 From: Chen Chen Date: Fri, 22 May 2026 15:53:00 +0800 Subject: [PATCH 1/4] feat(i2s): support TX FIFO sync on esp32s31 --- components/esp_driver_i2s/i2s_common.c | 2 + components/esp_driver_i2s/i2s_etm.c | 2 +- .../esp_hal_i2s/esp32s31/include/hal/i2s_ll.h | 88 ++++++++++++++----- .../esp32s31/include/soc/Kconfig.soc_caps.in | 4 + .../soc/esp32s31/include/soc/soc_caps.h | 1 + 5 files changed, 74 insertions(+), 23 deletions(-) diff --git a/components/esp_driver_i2s/i2s_common.c b/components/esp_driver_i2s/i2s_common.c index a1bd56fa1aa..2ece6490502 100644 --- a/components/esp_driver_i2s/i2s_common.c +++ b/components/esp_driver_i2s/i2s_common.c @@ -1673,6 +1673,7 @@ esp_err_t i2s_sync_enable_hw_fifo_sync(i2s_chan_handle_t tx_handle, bool enable) return ESP_ERR_NOT_SUPPORTED; } i2s_ll_tx_enable_hw_fifo_sync(tx_handle->controller->hal.dev, enable); + i2s_ll_tx_update(tx_handle->controller->hal.dev); return ESP_OK; } @@ -1695,6 +1696,7 @@ esp_err_t i2s_sync_config_hw_fifo_sync(i2s_chan_handle_t tx_handle, const i2s_sy if (config->suppl_mode == I2S_SYNC_SUPPL_MODE_STATIC_DATA) { i2s_ll_tx_set_hw_fifo_sync_static_suppl_data(tx_handle->controller->hal.dev, config->suppl_data); } + i2s_ll_tx_update(tx_handle->controller->hal.dev); return ESP_OK; } #endif diff --git a/components/esp_driver_i2s/i2s_etm.c b/components/esp_driver_i2s/i2s_etm.c index cdecd3e2b0a..6c06565ab7a 100644 --- a/components/esp_driver_i2s/i2s_etm.c +++ b/components/esp_driver_i2s/i2s_etm.c @@ -104,7 +104,7 @@ esp_err_t i2s_new_etm_task(i2s_chan_handle_t handle, const i2s_etm_task_config_t if (config->task_type == I2S_ETM_TASK_START) { // The i2s start will be controlled by etm handle->is_etm_start = true; - } else { + } else if (config->task_type == I2S_ETM_TASK_STOP) { // The i2s stop will be controlled by etm handle->is_etm_stop = true; } diff --git a/components/esp_hal_i2s/esp32s31/include/hal/i2s_ll.h b/components/esp_hal_i2s/esp32s31/include/hal/i2s_ll.h index cfcbcbaeab7..20abf78f950 100644 --- a/components/esp_hal_i2s/esp32s31/include/hal/i2s_ll.h +++ b/components/esp_hal_i2s/esp32s31/include/hal/i2s_ll.h @@ -82,20 +82,24 @@ extern "C" { [I2S_DIR_RX - 1] = { \ [I2S_ETM_TASK_START] = I2S0_TASK_START_RX, \ [I2S_ETM_TASK_STOP] = I2S0_TASK_STOP_RX, \ + [I2S_ETM_TASK_SYNC_FIFO] = -1, \ }, \ [I2S_DIR_TX - 1] = { \ [I2S_ETM_TASK_START] = I2S0_TASK_START_TX, \ [I2S_ETM_TASK_STOP] = I2S0_TASK_STOP_TX, \ + [I2S_ETM_TASK_SYNC_FIFO] = I2S0_TASK_SYNC_CHECK, \ }, \ }, \ [1] = { \ [I2S_DIR_RX - 1] = { \ [I2S_ETM_TASK_START] = I2S1_TASK_START_RX, \ [I2S_ETM_TASK_STOP] = I2S1_TASK_STOP_RX, \ + [I2S_ETM_TASK_SYNC_FIFO] = -1, \ }, \ [I2S_DIR_TX - 1] = { \ [I2S_ETM_TASK_START] = I2S1_TASK_START_TX, \ [I2S_ETM_TASK_STOP] = I2S1_TASK_STOP_TX, \ + [I2S_ETM_TASK_SYNC_FIFO] = I2S1_TASK_SYNC_CHECK, \ }, \ }, \ }[i2s_port][(chan_dir) - 1][task] @@ -1803,56 +1807,96 @@ static inline uint32_t i2s_ll_tx_get_ideal_cnt(i2s_dev_t *hw) } /** - * @brief Set TX sync software threshold + * @brief Get TX FIFO synchronization difference count value * - * @param hw Peripheral I2S hardware instance address. - * @param thres Software threshold value + * @param hw Peripheral I2S hardware instance address. + * @return + * fifo count value */ -static inline void i2s_ll_tx_set_sync_sw_thres(i2s_dev_t *hw, uint32_t thres) +__attribute__((always_inline)) +static inline uint32_t i2s_ll_tx_get_fifo_sync_diff_count(i2s_dev_t *hw) { - hw->sync_sw_thres.tx_cnt_diff_sw_thres = thres; + return hw->cnt_diff.tx_cnt_diff; } /** - * @brief Set TX sync hardware threshold + * @brief Reset TX FIFO synchronization difference counter * - * @param hw Peripheral I2S hardware instance address. - * @param thres Hardware threshold value + * @param hw Peripheral I2S hardware instance address. */ -static inline void i2s_ll_tx_set_sync_hw_thres(i2s_dev_t *hw, uint32_t thres) +__attribute__((always_inline)) +static inline void i2s_ll_tx_reset_fifo_sync_diff_counter(i2s_dev_t *hw) { - hw->sync_hw_thres.tx_cnt_diff_hw_thres = thres; + hw->cnt_diff.tx_cnt_diff_rst = 1; + hw->cnt_diff.tx_cnt_diff_rst = 0; } /** - * @brief Enable TX hardware sync + * @brief Set TX FIFO synchronization difference counter software threshold + * @note It determines the up threshold that the hardware synchronize the data automatically. + * - If diff_count <= sw_threshold, the hardware will synchronize the data automatically. + * - If diff_count > sw_threshold, the automatic synchronization is not proper for this case, + * interrupt will be triggered to let the software decide how to handle this case. * - * @param hw Peripheral I2S hardware instance address. - * @param enable Set true to enable hardware sync + * @param hw Peripheral I2S hardware instance address. + * @param thresh The threshold that send */ -static inline void i2s_ll_tx_enable_hw_sync(i2s_dev_t *hw, bool enable) +__attribute__((always_inline)) +static inline void i2s_ll_tx_set_fifo_sync_diff_conter_sw_threshold(i2s_dev_t *hw, uint32_t thresh) +{ + hw->sync_sw_thres.tx_cnt_diff_sw_thres = thresh; +} + +/** + * @brief Set TX FIFO synchronization difference counter hardware threshold + * @note It determines the down threshold that the hardware synchronize the data automatically. + * - If diff_count < hw_threshold, synchronization check pass, do nothing + * - If diff_count >= hw_threshold, the hardware will synchronize the data automatically. + * + * @param hw Peripheral I2S hardware instance address. + * @param thresh The threshold that send + */ +__attribute__((always_inline)) +static inline void i2s_ll_tx_set_fifo_sync_diff_conter_hw_threshold(i2s_dev_t *hw, uint32_t thresh) +{ + hw->sync_hw_thres.tx_cnt_diff_hw_thres = thresh; +} + +/** + * @brief Enable TX FIFO synchronization hardware mode + * + * @param hw Peripheral I2S hardware instance address. + * @param enable Set true to enable hardware mode + */ +__attribute__((always_inline)) +static inline void i2s_ll_tx_enable_hw_fifo_sync(i2s_dev_t *hw, bool enable) { hw->hw_sync_conf.tx_hw_sync_en = enable; } /** - * @brief Set TX hardware sync supplement mode + * @brief Set TX FIFO synchronization hardware data supplementation mode + * @note It determines the supplementation data when the actual sent data is less than the `diff_count - threshold` * - * @param hw Peripheral I2S hardware instance address. - * @param mode Supplement mode: 0 = last data, 1 = configured data + * @param hw Peripheral I2S hardware instance address. + * @param mode Data supplementation mode + * - 0: Supplement the last data + * - 1: Supplement the data configured in `hw_sync_data` reg */ -static inline void i2s_ll_tx_set_hw_sync_suppl_mode(i2s_dev_t *hw, bool mode) +__attribute__((always_inline)) +static inline void i2s_ll_tx_set_hw_fifo_sync_suppl_mode(i2s_dev_t *hw, uint32_t mode) { hw->hw_sync_conf.tx_hw_sync_suppl_mode = mode; } /** - * @brief Set TX hardware sync supplement data + * @brief Set TX FIFO synchronization hardware supplementation data when `tx_hw_sync_suppl_mode` is 1 * - * @param hw Peripheral I2S hardware instance address. - * @param data Supplement data value + * @param hw Peripheral I2S hardware instance address. + * @param data Data to be supplemented when `tx_hw_sync_suppl_mode` is 1 */ -static inline void i2s_ll_tx_set_hw_sync_suppl_data(i2s_dev_t *hw, uint32_t data) +__attribute__((always_inline)) +static inline void i2s_ll_tx_set_hw_fifo_sync_static_suppl_data(i2s_dev_t *hw, uint32_t data) { hw->hw_sync_data.tx_hw_sync_suppl_data = data; } diff --git a/components/soc/esp32s31/include/soc/Kconfig.soc_caps.in b/components/soc/esp32s31/include/soc/Kconfig.soc_caps.in index 023070cf68a..f16d6384364 100644 --- a/components/soc/esp32s31/include/soc/Kconfig.soc_caps.in +++ b/components/soc/esp32s31/include/soc/Kconfig.soc_caps.in @@ -1795,6 +1795,10 @@ config SOC_I2S_SUPPORTS_TX_SYNC_CNT bool default y +config SOC_I2S_SUPPORTS_TX_FIFO_SYNC + bool + default y + config SOC_I2S_SUPPORTS_RX_RECOMB bool default y diff --git a/components/soc/esp32s31/include/soc/soc_caps.h b/components/soc/esp32s31/include/soc/soc_caps.h index f0549b97bc7..0c5472ad768 100644 --- a/components/soc/esp32s31/include/soc/soc_caps.h +++ b/components/soc/esp32s31/include/soc/soc_caps.h @@ -666,6 +666,7 @@ #define SOC_I2S_SUPPORTS_PDM2PCM (1) // Support to input PDM format but read PCM format data with the help of PDM to PCM filter (only on I2S0) #define SOC_I2S_SUPPORTS_PDM_RX_HP_FILTER (1) #define SOC_I2S_SUPPORTS_TX_SYNC_CNT (1) // Support TX synchronization count (ideal_cnt) +#define SOC_I2S_SUPPORTS_TX_FIFO_SYNC (1) #define SOC_I2S_SUPPORTS_RX_RECOMB (1) // Support RX recomb for DMA data format reorganization #define SOC_I2S_SUPPORTS_TDM (1) #define SOC_I2S_SUPPORTS_BT_DEST (1) // Support routing I2S TX/RX data to Bluetooth (Classic sync link / voice path), not via DMA From c27bd918743b2d3e7081293401e6035188a22b2a Mon Sep 17 00:00:00 2001 From: Chen Chen Date: Thu, 28 May 2026 11:49:27 +0800 Subject: [PATCH 2/4] feat(i2s): release i2s tx sync APIs --- components/esp_driver_i2s/i2s_common.c | 206 +++++++++++++----- components/esp_driver_i2s/i2s_etm.c | 2 +- components/esp_driver_i2s/i2s_private.h | 5 + .../include/driver/i2s_common.h | 99 +++++++++ .../esp_driver_i2s/include/driver/i2s_types.h | 26 ++- .../include/esp_private/i2s_sync.h | 146 ------------- components/esp_driver_i2s/linker.lf | 10 +- .../test_apps/i2s/main/CMakeLists.txt | 2 +- .../test_apps/i2s/main/test_i2s.c | 143 ++++++++++++ .../esp_hal_i2s/esp32h4/include/hal/i2s_ll.h | 81 +++++-- .../esp_hal_i2s/esp32s31/include/hal/i2s_ll.h | 81 +++++-- .../esp_hal_i2s/include/hal/i2s_types.h | 10 + docs/en/api-reference/peripherals/i2s.rst | 94 ++++++++ docs/zh_CN/api-reference/peripherals/i2s.rst | 87 ++++++++ 14 files changed, 746 insertions(+), 246 deletions(-) delete mode 100644 components/esp_driver_i2s/include/esp_private/i2s_sync.h diff --git a/components/esp_driver_i2s/i2s_common.c b/components/esp_driver_i2s/i2s_common.c index 2ece6490502..7f6407f1be9 100644 --- a/components/esp_driver_i2s/i2s_common.c +++ b/components/esp_driver_i2s/i2s_common.c @@ -47,7 +47,6 @@ #include "driver/gpio.h" #include "esp_private/gpio.h" -#include "esp_private/i2s_sync.h" #include "driver/i2s_common.h" #include "i2s_private.h" @@ -1222,6 +1221,14 @@ esp_err_t i2s_del_channel(i2s_chan_handle_t handle) if (handle->binary) { vSemaphoreDeleteWithCaps(handle->binary); } +#if SOC_I2S_SUPPORTS_TX_FIFO_SYNC + if (handle->sync_intr) { + i2s_ll_enable_interrupt(handle->controller->hal.dev, I2S_LL_TX_SYNC_INT_EVENT, false); + esp_intr_disable(handle->sync_intr); + esp_intr_free(handle->sync_intr); + handle->sync_intr = NULL; + } +#endif #if SOC_I2S_HW_VERSION_1 i2s_obj->chan_occupancy = 0; #else @@ -1635,68 +1642,157 @@ err: } #if SOC_I2S_SUPPORTS_TX_SYNC_CNT -uint32_t i2s_sync_get_bclk_count(i2s_chan_handle_t tx_handle) +__attribute__((always_inline)) +static inline esp_err_t i2s_check_tx_handle(i2s_chan_handle_t tx_handle) { - return i2s_ll_tx_get_bclk_sync_count(tx_handle->controller->hal.dev); + I2S_NULL_POINTER_CHECK(TAG, tx_handle); + ESP_RETURN_ON_FALSE(tx_handle->dir == I2S_DIR_TX, ESP_ERR_INVALID_ARG, TAG, "channel is not TX"); + return ESP_OK; } -uint32_t i2s_sync_get_fifo_count(i2s_chan_handle_t tx_handle) +esp_err_t i2s_channel_get_sync_count(i2s_chan_handle_t tx_handle, uint32_t *bclk_count, uint32_t *fifo_count, + bool reset) { - return i2s_ll_tx_get_fifo_sync_count(tx_handle->controller->hal.dev); + ESP_RETURN_ON_ERROR(i2s_check_tx_handle(tx_handle), TAG, "invalid TX handle"); + i2s_dev_t *hw = tx_handle->controller->hal.dev; + if (bclk_count) { + *bclk_count = i2s_ll_tx_get_bclk_sync_count(hw); + } + if (fifo_count) { + *fifo_count = i2s_ll_tx_get_fifo_sync_count(hw); + } + if (reset) { + i2s_ll_tx_reset_bclk_sync_counter(hw); + i2s_ll_tx_reset_fifo_sync_counter(hw); + } + return ESP_OK; } -void i2s_sync_reset_bclk_count(i2s_chan_handle_t tx_handle) -{ - i2s_ll_tx_reset_bclk_sync_counter(tx_handle->controller->hal.dev); -} - -void i2s_sync_reset_fifo_count(i2s_chan_handle_t tx_handle) -{ - i2s_ll_tx_reset_fifo_sync_counter(tx_handle->controller->hal.dev); -} -#endif // SOC_I2S_SUPPORTS_TX_SYNC_CNT - #if SOC_I2S_SUPPORTS_TX_FIFO_SYNC -uint32_t i2s_sync_get_fifo_sync_diff_count(i2s_chan_handle_t tx_handle) +__attribute__((always_inline)) +static inline int32_t i2s_sign_extend_sync_diff(uint32_t diff) { - return i2s_ll_tx_get_fifo_sync_diff_count(tx_handle->controller->hal.dev); + return (diff & BIT(30)) ? (int32_t)(diff | BIT(31)) : (int32_t)diff; } -void i2s_sync_reset_fifo_sync_diff_count(i2s_chan_handle_t tx_handle) -{ - i2s_ll_tx_reset_fifo_sync_diff_counter(tx_handle->controller->hal.dev); -} - -esp_err_t i2s_sync_enable_hw_fifo_sync(i2s_chan_handle_t tx_handle, bool enable) -{ - if (tx_handle->dir == I2S_DIR_RX) { - return ESP_ERR_NOT_SUPPORTED; - } - i2s_ll_tx_enable_hw_fifo_sync(tx_handle->controller->hal.dev, enable); - i2s_ll_tx_update(tx_handle->controller->hal.dev); - return ESP_OK; -} - -esp_err_t i2s_sync_config_hw_fifo_sync(i2s_chan_handle_t tx_handle, const i2s_sync_fifo_sync_config_t *config) -{ - if (!(tx_handle && config)) { - return ESP_ERR_INVALID_ARG; - } - if (tx_handle->dir == I2S_DIR_RX) { - return ESP_ERR_NOT_SUPPORTED; - } - if (config->sw_high_thresh < config->hw_low_thresh) { - return ESP_ERR_INVALID_ARG; - } - - i2s_ll_tx_set_etm_sync_ideal_cnt(tx_handle->controller->hal.dev, config->ideal_cnt); - i2s_ll_tx_set_fifo_sync_diff_conter_sw_threshold(tx_handle->controller->hal.dev, config->sw_high_thresh); - i2s_ll_tx_set_fifo_sync_diff_conter_hw_threshold(tx_handle->controller->hal.dev, config->hw_low_thresh); - i2s_ll_tx_set_hw_fifo_sync_suppl_mode(tx_handle->controller->hal.dev, (uint32_t)config->suppl_mode); - if (config->suppl_mode == I2S_SYNC_SUPPL_MODE_STATIC_DATA) { - i2s_ll_tx_set_hw_fifo_sync_static_suppl_data(tx_handle->controller->hal.dev, config->suppl_data); - } - i2s_ll_tx_update(tx_handle->controller->hal.dev); - return ESP_OK; -} +#if CONFIG_I2S_ISR_IRAM_SAFE +#define I2S_ISR_HANDLER_ATTR IRAM_ATTR +#else +#define I2S_ISR_HANDLER_ATTR #endif + +static I2S_ISR_HANDLER_ATTR void i2s_isr_handler(void *args) +{ + i2s_chan_handle_t handle = (i2s_chan_handle_t)args; + i2s_dev_t *hw = handle->controller->hal.dev; + + if (i2s_ll_get_interrupt_status(hw, I2S_LL_TX_SYNC_INT_EVENT)) { + i2s_sync_event_data_t evt = { + .diff_count = i2s_sign_extend_sync_diff(i2s_ll_tx_get_fifo_sync_diff_count(hw)), + }; + i2s_ll_tx_reset_fifo_sync_diff_counter(hw); + i2s_ll_clear_interrupt_status(hw, I2S_LL_TX_SYNC_INT_EVENT); + + if (handle->on_sync && handle->on_sync(handle, &evt, handle->sync_user_data)) { + portYIELD_FROM_ISR(); + } + } +} + +esp_err_t i2s_channel_get_sync_diff_count(i2s_chan_handle_t tx_handle, int32_t *diff_count, bool reset) +{ + ESP_RETURN_ON_ERROR(i2s_check_tx_handle(tx_handle), TAG, "invalid TX handle"); + i2s_dev_t *hw = tx_handle->controller->hal.dev; + if (diff_count) { + *diff_count = i2s_sign_extend_sync_diff(i2s_ll_tx_get_fifo_sync_diff_count(hw)); + } + if (reset) { + i2s_ll_tx_reset_fifo_sync_diff_counter(hw); + } + return ESP_OK; +} + +esp_err_t i2s_channel_config_tx_fifo_sync(i2s_chan_handle_t tx_handle, const i2s_tx_fifo_sync_config_t *config) +{ + esp_err_t ret = ESP_OK; + ESP_RETURN_ON_ERROR(i2s_check_tx_handle(tx_handle), TAG, "invalid TX handle"); + I2S_NULL_POINTER_CHECK(TAG, config); + ESP_RETURN_ON_FALSE(config->auto_suppl_thresh == 0 || + config->auto_suppl_thresh < config->manual_suppl_thresh, + ESP_ERR_INVALID_ARG, TAG, + "auto_suppl_thresh must be 0 or smaller than manual_suppl_thresh"); + + i2s_dev_t *hw = tx_handle->controller->hal.dev; + xSemaphoreTake(tx_handle->mutex, portMAX_DELAY); + ESP_GOTO_ON_FALSE(tx_handle->state < I2S_CHAN_STATE_RUNNING, ESP_ERR_INVALID_STATE, err, TAG, + "invalid state, I2S has enabled"); + i2s_ll_tx_set_etm_sync_ideal_cnt(hw, config->ideal_cnt); + i2s_ll_tx_set_fifo_sync_diff_counter_manual_threshold(hw, config->manual_suppl_thresh); + i2s_ll_tx_set_fifo_sync_diff_counter_auto_threshold(hw, config->auto_suppl_thresh); + i2s_ll_tx_set_hw_fifo_sync_suppl_mode(hw, config->suppl_mode); + if (config->suppl_mode == I2S_TX_FIFO_SYNC_SUPPL_MODE_STATIC_DATA) { + i2s_ll_tx_set_hw_fifo_sync_static_suppl_data(hw, config->suppl_data); + } + i2s_ll_tx_enable_hw_fifo_sync(hw, config->auto_suppl_thresh > 0); + i2s_ll_tx_update(hw); +err: + xSemaphoreGive(tx_handle->mutex); + return ret; +} + +esp_err_t i2s_channel_register_intr_event_callback(i2s_chan_handle_t tx_handle, + const i2s_intr_event_callbacks_t *callbacks, void *user_data) +{ + esp_err_t ret = ESP_OK; + ESP_RETURN_ON_ERROR(i2s_check_tx_handle(tx_handle), TAG, "invalid TX handle"); + I2S_NULL_POINTER_CHECK(TAG, callbacks); + i2s_sync_callback_t callback = callbacks->on_tx_sync; +#if CONFIG_I2S_ISR_IRAM_SAFE + if (callback) { + ESP_RETURN_ON_FALSE(esp_ptr_in_iram(callback), ESP_ERR_INVALID_ARG, TAG, "sync callback not in IRAM"); + } + if (user_data) { + ESP_RETURN_ON_FALSE(esp_ptr_internal(user_data), ESP_ERR_INVALID_ARG, TAG, "user context not in internal RAM"); + } +#endif + + i2s_dev_t *hw = tx_handle->controller->hal.dev; + xSemaphoreTake(tx_handle->mutex, portMAX_DELAY); + ESP_GOTO_ON_FALSE(tx_handle->state < I2S_CHAN_STATE_RUNNING, ESP_ERR_INVALID_STATE, err, TAG, + "invalid state, I2S has enabled"); + + if (callback) { + if (!tx_handle->sync_intr) { + int port_id = tx_handle->controller->id; + int intr_flag = ESP_INTR_FLAG_INTRDISABLED | tx_handle->intr_prio_flags; +#if CONFIG_I2S_ISR_IRAM_SAFE + intr_flag |= ESP_INTR_FLAG_IRAM; +#endif + ret = esp_intr_alloc_intrstatus(i2s_periph_signal[port_id].irq, intr_flag, + (uint32_t)i2s_ll_get_interrupt_status_reg(hw), + I2S_LL_TX_SYNC_INT_EVENT, i2s_isr_handler, tx_handle, + &tx_handle->sync_intr); + ESP_GOTO_ON_ERROR(ret, err, TAG, "allocate TX sync interrupt failed"); + ESP_GOTO_ON_ERROR(esp_intr_enable(tx_handle->sync_intr), err, TAG, "enable TX sync interrupt failed"); + } + tx_handle->on_sync = callback; + tx_handle->sync_user_data = user_data; + i2s_ll_clear_interrupt_status(hw, I2S_LL_TX_SYNC_INT_EVENT); + i2s_ll_enable_interrupt(hw, I2S_LL_TX_SYNC_INT_EVENT, true); + } else { + i2s_ll_enable_interrupt(hw, I2S_LL_TX_SYNC_INT_EVENT, false); + if (tx_handle->sync_intr) { + esp_intr_disable(tx_handle->sync_intr); + esp_intr_free(tx_handle->sync_intr); + tx_handle->sync_intr = NULL; + } + tx_handle->on_sync = NULL; + tx_handle->sync_user_data = NULL; + } + +err: + xSemaphoreGive(tx_handle->mutex); + return ret; +} +#endif // SOC_I2S_SUPPORTS_TX_FIFO_SYNC +#endif // SOC_I2S_SUPPORTS_TX_SYNC_CNT diff --git a/components/esp_driver_i2s/i2s_etm.c b/components/esp_driver_i2s/i2s_etm.c index 6c06565ab7a..62b5c438663 100644 --- a/components/esp_driver_i2s/i2s_etm.c +++ b/components/esp_driver_i2s/i2s_etm.c @@ -39,7 +39,7 @@ static esp_err_t s_i2s_del_etm_task(esp_etm_task_t *task) if (i2s_task->task_type == I2S_ETM_TASK_START) { // The i2s start no longer be controlled by etm i2s_task->handle->is_etm_start = false; - } else { + } else if (i2s_task->task_type == I2S_ETM_TASK_STOP) { // The i2s stop no longer be controlled by etm i2s_task->handle->is_etm_stop = false; } diff --git a/components/esp_driver_i2s/i2s_private.h b/components/esp_driver_i2s/i2s_private.h index d8cfb331ecd..5dbdf601da6 100644 --- a/components/esp_driver_i2s/i2s_private.h +++ b/components/esp_driver_i2s/i2s_private.h @@ -180,6 +180,11 @@ struct i2s_channel_obj_t { uint64_t reserve_gpio_mask; /*!< The gpio mask that has been reserved by I2S */ i2s_event_callbacks_internal_t callbacks; /*!< Callback functions */ void *user_data; /*!< User data for callback functions */ +#if SOC_I2S_SUPPORTS_TX_FIFO_SYNC + i2s_sync_callback_t on_sync; /*!< TX FIFO sync manual supplement threshold callback */ + void *sync_user_data; /*!< User data for TX FIFO sync callback */ + intr_handle_t sync_intr; /*!< I2S peripheral interrupt for TX FIFO sync */ +#endif void (*start)(i2s_chan_handle_t); /*!< start tx/rx channel */ void (*stop)(i2s_chan_handle_t); /*!< stop tx/rx channel */ }; diff --git a/components/esp_driver_i2s/include/driver/i2s_common.h b/components/esp_driver_i2s/include/driver/i2s_common.h index 3d64b77b39d..f78dacc7792 100644 --- a/components/esp_driver_i2s/include/driver/i2s_common.h +++ b/components/esp_driver_i2s/include/driver/i2s_common.h @@ -290,6 +290,105 @@ esp_err_t i2s_channel_preload_data(i2s_chan_handle_t tx_handle, const void *src, */ 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); +#if SOC_I2S_SUPPORTS_TX_SYNC_CNT +/** + * @brief Get TX synchronization counters + * + * @note `fifo_count` reflects how many data have been read from TX FIFO. + * Normally, `bclk_count = fifo_count * slot_bit_width`. + * Both counters are reset automatically when `I2S_ETM_TASK_SYNC_FIFO` is triggered. + * + * @param[in] tx_handle I2S TX channel handle + * @param[out] bclk_count Pointer to receive BCLK sync counter, set NULL to ignore + * @param[out] fifo_count Pointer to receive FIFO sync counter, set NULL to ignore + * @param[in] reset Whether to reset both counters after reading + * @return + * - ESP_OK Success + * - ESP_ERR_INVALID_ARG Invalid handle or channel is not TX + */ +esp_err_t i2s_channel_get_sync_count(i2s_chan_handle_t tx_handle, uint32_t *bclk_count, uint32_t *fifo_count, + bool reset); + +#if SOC_I2S_SUPPORTS_TX_FIFO_SYNC +/** + * @brief Get TX FIFO synchronization difference counter + * + * @note `diff_count` is a signed 31-bit value equal to `I2S_TX_FIFO_CNT - I2S_TX_FIFO_IDEAL_CNT`. + * + * @param[in] tx_handle I2S TX channel handle + * @param[out] diff_count Pointer to receive signed difference counter, set NULL to ignore + * @param[in] reset Whether to reset the difference counter after reading + * @return + * - ESP_OK Success + * - ESP_ERR_INVALID_ARG Invalid handle or channel is not TX + */ +esp_err_t i2s_channel_get_sync_diff_count(i2s_chan_handle_t tx_handle, int32_t *diff_count, bool reset); + +/** + * @brief TX FIFO synchronization configuration + */ +typedef struct { + uint32_t auto_suppl_thresh; /*!< Threshold to enable automatic FIFO data supplement; + * 0 disables automatic supplement + */ + uint32_t manual_suppl_thresh; /*!< Threshold to trigger the callback for manual FIFO data supplement */ + uint32_t ideal_cnt; /*!< Ideal FIFO count when ETM sync task is triggered */ + i2s_tx_fifo_sync_suppl_mode_t suppl_mode; /*!< Data supplement mode for automatic supplement */ + uint32_t suppl_data; /*!< Static supplement data, valid when suppl_mode is + * I2S_TX_FIFO_SYNC_SUPPL_MODE_STATIC_DATA + */ +} i2s_tx_fifo_sync_config_t; + +/** + * @brief Group of I2S peripheral interrupt event callbacks + * + * @note The callbacks are all running under ISR environment. + */ +typedef struct { + i2s_sync_callback_t on_tx_sync; /*!< Callback when the sync difference count exceeds + * `manual_suppl_thresh` + */ +} i2s_intr_event_callbacks_t; + +/** + * @brief Configure TX FIFO synchronization + * + * @note Set `auto_suppl_thresh` to 0 to disable automatic hardware supplementation. + * @note When automatic hardware supplementation is enabled, `auto_suppl_thresh` must be smaller than + * `manual_suppl_thresh`. + * @note Only allowed when channel state is REGISTERED or READY (before channel starts). + * + * @param[in] tx_handle I2S TX channel handle + * @param[in] config TX FIFO synchronization configuration + * @return + * - ESP_OK Success + * - ESP_ERR_INVALID_ARG Invalid handle, channel is not TX, or invalid configuration + * - ESP_ERR_INVALID_STATE Channel is already running + */ +esp_err_t i2s_channel_config_tx_fifo_sync(i2s_chan_handle_t tx_handle, const i2s_tx_fifo_sync_config_t *config); + +/** + * @brief Register I2S peripheral interrupt event callbacks + * + * @note `on_tx_sync` is invoked when `tx_cnt_diff` exceeds `manual_suppl_thresh`. + * @note Only allowed when channel state is REGISTERED or READY (before channel starts). + * @note When CONFIG_I2S_ISR_IRAM_SAFE is enabled, the callback and user_data must reside in internal RAM. + * @note Set `callbacks->on_tx_sync` to NULL to deregister the callback and uninstall the TX sync interrupt. + * + * @param[in] tx_handle I2S TX channel handle + * @param[in] callbacks Group of I2S peripheral interrupt callbacks + * @param[in] user_data User context passed to callback + * @return + * - ESP_OK Success + * - ESP_ERR_INVALID_ARG Invalid handle or channel is not TX + * - ESP_ERR_INVALID_STATE Channel is already running + * - ESP_ERR_NO_MEM Failed to allocate interrupt + */ +esp_err_t i2s_channel_register_intr_event_callback(i2s_chan_handle_t tx_handle, + const i2s_intr_event_callbacks_t *callbacks, void *user_data); +#endif // SOC_I2S_SUPPORTS_TX_FIFO_SYNC +#endif // SOC_I2S_SUPPORTS_TX_SYNC_CNT + #ifdef __cplusplus } #endif diff --git a/components/esp_driver_i2s/include/driver/i2s_types.h b/components/esp_driver_i2s/include/driver/i2s_types.h index d3591b1d6b9..663a160d727 100644 --- a/components/esp_driver_i2s/include/driver/i2s_types.h +++ b/components/esp_driver_i2s/include/driver/i2s_types.h @@ -106,6 +106,29 @@ typedef struct { uint32_t water_mark; /*!< The water mark of the internal buffer, in percent */ } i2s_tuning_info_t; +typedef struct i2s_channel_obj_t *i2s_chan_handle_t; /*!< I2S channel object handle, the control unit of the I2S driver*/ +typedef struct lp_i2s_channel_obj_t *lp_i2s_chan_handle_t; /*!< I2S channel object handle, the control unit of the I2S driver*/ + +#if SOC_I2S_SUPPORTS_TX_FIFO_SYNC +/** + * @brief TX synchronization event data passed to sync callback + */ +typedef struct { + int32_t diff_count; /*!< Signed difference: I2S_TX_FIFO_CNT - I2S_TX_FIFO_IDEAL_CNT */ +} i2s_sync_event_data_t; + +/** + * @brief TX synchronization event callback + * + * @param[in] handle I2S TX channel handle + * @param[in] event TX synchronization event data + * @param[in] user_ctx User context registered via `i2s_channel_register_intr_event_callback()` + * + * @return Whether a high priority task has been waken up by this callback function + */ +typedef bool (*i2s_sync_callback_t)(i2s_chan_handle_t handle, const i2s_sync_event_data_t *event, void *user_ctx); +#endif // SOC_I2S_SUPPORTS_TX_FIFO_SYNC + /** * @brief Event data structure for LP I2S */ @@ -113,9 +136,6 @@ typedef struct { lp_i2s_trans_t trans; ///< LP I2S transaction } lp_i2s_evt_data_t; -typedef struct i2s_channel_obj_t *i2s_chan_handle_t; /*!< I2S channel object handle, the control unit of the I2S driver*/ -typedef struct lp_i2s_channel_obj_t *lp_i2s_chan_handle_t; /*!< I2S channel object handle, the control unit of the I2S driver*/ - /** * @brief I2S event callback * @param[in] handle I2S channel handle, created from `i2s_new_channel()` diff --git a/components/esp_driver_i2s/include/esp_private/i2s_sync.h b/components/esp_driver_i2s/include/esp_private/i2s_sync.h deleted file mode 100644 index f066bf0ea8d..00000000000 --- a/components/esp_driver_i2s/include/esp_private/i2s_sync.h +++ /dev/null @@ -1,146 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2023-2025 Espressif Systems (Shanghai) CO LTD - * - * SPDX-License-Identifier: Apache-2.0 - */ - -// DO NOT USE THESE APIS IN YOUR APPLICATIONS -// The following APIs are for internal use, public to other IDF components, but not for users' applications. - -/** - * This file is used for getting the bclk and fifo sending count - * for the synchronization among different I2S ports. - * - * The APIs in this file might be called frequently, so they are made light-weight and flexible to be called - * - * NOTE: These APIs are private for ESP internal usages. - * Please be aware of the risk that APIs might be changed regarding the use case. - */ - -#pragma once - -#include -#include "driver/i2s_types.h" -#include "esp_err.h" - -#ifdef __cplusplus -extern "C" { -#endif - -#if SOC_I2S_SUPPORTS_TX_SYNC_CNT - -/** - * @brief Get the counter number of BCLK ticks - * @note The BCLK tick count reflects the real data that have sent on line - * @note It will be reset automatically when `I2S_ETM_TASK_SYNC_FIFO` is triggered - * - * @param[in] tx_handle The I2S tx channel handle - * @return - * - BCLK tick count - */ -uint32_t i2s_sync_get_bclk_count(i2s_chan_handle_t tx_handle); - -/** - * @brief Get the counter number of fifo - * @note The FIFO count reflects how many slots have processed - * Normally, fifo_cnt = slot_bit_width * bclk_cnt - * If fifo_cnt < slot_bit_width * bclk_cnt, that means some data are still stuck in the I2S controller - * @note It will be reset automatically when `I2S_ETM_TASK_SYNC_FIFO` is triggered - * - * @param[in] tx_handle The I2S tx channel handle - * @return - * - FIFO slot count - */ -uint32_t i2s_sync_get_fifo_count(i2s_chan_handle_t tx_handle); - -/** - * @brief Reset the bclk counter - * - * @param[in] tx_handle The I2S tx channel handle - */ -void i2s_sync_reset_bclk_count(i2s_chan_handle_t tx_handle); - -/** - * @brief Reset the fifo counter - * - * @param[in] tx_handle The I2S tx channel handle - */ -void i2s_sync_reset_fifo_count(i2s_chan_handle_t tx_handle); - -#endif // SOC_I2S_SUPPORTS_TX_SYNC_CNT - -#if SOC_I2S_SUPPORTS_TX_FIFO_SYNC -/** - * @brief I2S hardware FIFO synchronization supplement mode - * @note When the FIFO sync difference count is out of threshold, the hardware will supplement data automatically - * This type is to specify which data will be supplemented - */ -typedef enum { - I2S_SYNC_SUPPL_MODE_LAST_DATA = 0, /*!< Supplement with the last transmitted data */ - I2S_SYNC_SUPPL_MODE_STATIC_DATA = 1, /*!< Supplement with static data specified in config */ -} i2s_sync_suppl_mode_t; - -/** - * @brief I2S hardware FIFO synchronization configuration - * @note This configuration is used for multi I2S port synchronization via ETM - */ -typedef struct { - uint32_t hw_low_thresh; /*!< Lower threshold for FIFO sync difference counter - - If difference count < hw_low_thresh, do nothing - - If difference count >= hw_low_thresh, the hardware will supplement data automatically */ - uint32_t sw_high_thresh; /*!< Upper threshold for FIFO sync difference counter - - If difference count <= sw_high_thresh, the hardware supplement data automatically - - If difference count > sw_high_thresh, sync interrupt triggered and - the software is responsible to decide how to handle this severe asynchronization */ - uint32_t ideal_cnt; /*!< Ideal count for FIFO sync difference counter, it depends on the ETM sync task interval and the data rate */ - i2s_sync_suppl_mode_t suppl_mode; /*!< Data supplement mode when FIFO sync difference is out of threshold */ - uint32_t suppl_data; /*!< Static supplement data, only valid when suppl_mode is I2S_SYNC_SUPPL_MODE_STATIC_DATA */ -} i2s_sync_fifo_sync_config_t; - -/** - * @brief Get the counter number of FIFO sync difference - * @note The FIFO sync difference count reflects the difference between current FIFO count and ideal count - * - * @param[in] tx_handle The I2S tx channel handle - * @return - * - FIFO sync difference count - */ -uint32_t i2s_sync_get_fifo_sync_diff_count(i2s_chan_handle_t tx_handle); - -/** - * @brief Reset the FIFO sync difference counter - * - * @param[in] tx_handle The I2S tx channel handle - */ -void i2s_sync_reset_fifo_sync_diff_count(i2s_chan_handle_t tx_handle); - -/** - * @brief Enable or disable hardware FIFO synchronization - * @note When enabled, hardware will automatically supplement data when FIFO sync difference is greater than hw_low_thresh - * - * @param[in] tx_handle The I2S tx channel handle - * @param[in] enable true to enable, false to disable - * @return - * - ESP_OK on success - * - ESP_ERR_NOT_SUPPORTED if called on RX channel - */ -esp_err_t i2s_sync_enable_hw_fifo_sync(i2s_chan_handle_t tx_handle, bool enable); - -/** - * @brief Configure hardware FIFO synchronization parameters - * @note This function configures the thresholds and supplement mode for hardware FIFO sync - * - * @param[in] tx_handle The I2S tx channel handle - * @param[in] config Configuration for hardware FIFO synchronization - * @return - * - ESP_OK on success - * - ESP_ERR_INVALID_ARG if invalid arguments - * - ESP_ERR_NOT_SUPPORTED if called on RX channel - */ -esp_err_t i2s_sync_config_hw_fifo_sync(i2s_chan_handle_t tx_handle, const i2s_sync_fifo_sync_config_t *config); - -#endif - -#ifdef __cplusplus -} -#endif diff --git a/components/esp_driver_i2s/linker.lf b/components/esp_driver_i2s/linker.lf index ed2ae42edfd..f241c777540 100644 --- a/components/esp_driver_i2s/linker.lf +++ b/components/esp_driver_i2s/linker.lf @@ -6,12 +6,6 @@ entries: i2s_common: i2s_dma_tx_callback (noflash) if I2S_CTRL_FUNC_IN_IRAM = y: if SOC_I2S_SUPPORTS_TX_SYNC_CNT = y: - i2s_common: i2s_sync_get_bclk_count (noflash) - i2s_common: i2s_sync_get_fifo_count (noflash) - i2s_common: i2s_sync_reset_bclk_count (noflash) - i2s_common: i2s_sync_reset_fifo_count (noflash) + i2s_common: i2s_channel_get_sync_count (noflash) if SOC_I2S_SUPPORTS_TX_FIFO_SYNC = y: - i2s_common: i2s_sync_get_fifo_sync_diff_count (noflash) - i2s_common: i2s_sync_reset_fifo_sync_diff_count (noflash) - i2s_common: i2s_sync_enable_hw_fifo_sync (noflash) - i2s_common: i2s_sync_config_hw_fifo_sync (noflash) + i2s_common: i2s_channel_get_sync_diff_count (noflash) diff --git a/components/esp_driver_i2s/test_apps/i2s/main/CMakeLists.txt b/components/esp_driver_i2s/test_apps/i2s/main/CMakeLists.txt index a1770e65766..69eef6dfa2e 100644 --- a/components/esp_driver_i2s/test_apps/i2s/main/CMakeLists.txt +++ b/components/esp_driver_i2s/test_apps/i2s/main/CMakeLists.txt @@ -12,5 +12,5 @@ endif() idf_component_register(SRCS ${srcs} PRIV_REQUIRES unity esp_driver_pcnt spi_flash - esp_driver_gpio esp_driver_i2s esp_driver_uart esp_psram + esp_driver_gpio esp_driver_i2s esp_driver_uart esp_psram esp_driver_gptimer WHOLE_ARCHIVE) diff --git a/components/esp_driver_i2s/test_apps/i2s/main/test_i2s.c b/components/esp_driver_i2s/test_apps/i2s/main/test_i2s.c index a0086864a96..356aeace682 100644 --- a/components/esp_driver_i2s/test_apps/i2s/main/test_i2s.c +++ b/components/esp_driver_i2s/test_apps/i2s/main/test_i2s.c @@ -25,6 +25,11 @@ #include "driver/i2s_std.h" #include "driver/i2s_common.h" #include "soc/soc_caps.h" +#if SOC_I2S_SUPPORTS_TX_FIFO_SYNC +#include "driver/i2s_etm.h" +#include "driver/gptimer.h" +#include "esp_etm.h" +#endif #if SOC_I2S_SUPPORTS_PDM #include "driver/i2s_pdm.h" #endif @@ -1278,6 +1283,144 @@ TEST_CASE("I2S_rate_tunning", "[i2s]") TEST_ESP_OK(i2s_del_channel(rx_handle)); } +#if SOC_I2S_SUPPORTS_TX_FIFO_SYNC +#define I2S_TX_SYNC_TEST_TIMER_RES_HZ (1000 * 1000) +#define I2S_TX_SYNC_TEST_ALARM_COUNT (5000) +#define I2S_TX_SYNC_TEST_IDEAL_CNT (1000) +#define I2S_TX_SYNC_TEST_MANUAL_THRESH (64) +#define I2S_TX_SYNC_TEST_BUF_SIZE (4096) + +typedef struct { + SemaphoreHandle_t sem; + int32_t diff_count; +} i2s_tx_sync_test_ctx_t; + +static IRAM_ATTR bool i2s_tx_sync_test_callback(i2s_chan_handle_t handle, const i2s_sync_event_data_t *event, void *user_ctx) +{ + (void)handle; + i2s_tx_sync_test_ctx_t *ctx = (i2s_tx_sync_test_ctx_t *)user_ctx; + ctx->diff_count = event->diff_count; + + BaseType_t need_yield = pdFALSE; + xSemaphoreGiveFromISR(ctx->sem, &need_yield); + return need_yield == pdTRUE; +} + +TEST_CASE("I2S TX sync callback is triggered by GPTimer ETM alarm", "[i2s][etm]") +{ + i2s_chan_handle_t tx_handle = NULL; + gptimer_handle_t timer = NULL; + esp_etm_event_handle_t timer_event = NULL; + esp_etm_task_handle_t i2s_sync_task = NULL; + esp_etm_channel_handle_t etm_channel = NULL; + uint8_t *buf = NULL; + + i2s_tx_sync_test_ctx_t cb_ctx = { + .sem = xSemaphoreCreateBinary(), + }; + TEST_ASSERT_NOT_NULL(cb_ctx.sem); + + i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_0, I2S_ROLE_MASTER); + chan_cfg.dma_desc_num = 4; + chan_cfg.dma_frame_num = 256; + i2s_std_config_t std_cfg = { + .clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(48000), + .slot_cfg = I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_STEREO), + .gpio_cfg = I2S_TEST_MASTER_DEFAULT_PIN, + }; + std_cfg.gpio_cfg.mclk = -1; +#if CONFIG_IDF_TARGET_ESP32S31 + std_cfg.clk_cfg.clk_src = I2S_CLK_SRC_APLL; +#endif + + TEST_ESP_OK(i2s_new_channel(&chan_cfg, &tx_handle, NULL)); + TEST_ESP_OK(i2s_channel_init_std_mode(tx_handle, &std_cfg)); + + buf = (uint8_t *)calloc(1, I2S_TX_SYNC_TEST_BUF_SIZE); + TEST_ASSERT_NOT_NULL(buf); + size_t bytes_loaded = 0; + TEST_ESP_OK(i2s_channel_preload_data(tx_handle, buf, I2S_TX_SYNC_TEST_BUF_SIZE, &bytes_loaded)); + TEST_ASSERT_GREATER_THAN(0, bytes_loaded); + + i2s_tx_fifo_sync_config_t sync_cfg = { + .auto_suppl_thresh = 0, + .manual_suppl_thresh = I2S_TX_SYNC_TEST_MANUAL_THRESH, + .ideal_cnt = I2S_TX_SYNC_TEST_IDEAL_CNT, + .suppl_mode = I2S_TX_FIFO_SYNC_SUPPL_MODE_LAST_DATA, + }; + i2s_intr_event_callbacks_t intr_cbs = { + .on_tx_sync = i2s_tx_sync_test_callback, + }; + TEST_ESP_OK(i2s_channel_config_tx_fifo_sync(tx_handle, &sync_cfg)); + TEST_ESP_OK(i2s_channel_register_intr_event_callback(tx_handle, &intr_cbs, &cb_ctx)); + + uint32_t bclk_count = 0; + uint32_t fifo_count = 0; + int32_t diff_count = 0; + TEST_ESP_OK(i2s_channel_get_sync_count(tx_handle, &bclk_count, &fifo_count, true)); + TEST_ESP_OK(i2s_channel_get_sync_count(tx_handle, NULL, NULL, false)); + TEST_ESP_OK(i2s_channel_get_sync_diff_count(tx_handle, &diff_count, true)); + TEST_ESP_OK(i2s_channel_get_sync_diff_count(tx_handle, NULL, false)); + printf("TX sync API before start: diff=%"PRId32", fifo=%"PRIu32", bclk=%"PRIu32"\n", + diff_count, fifo_count, bclk_count); + + i2s_etm_task_config_t i2s_task_cfg = { + .task_type = I2S_ETM_TASK_SYNC_FIFO, + }; + TEST_ESP_OK(i2s_new_etm_task(tx_handle, &i2s_task_cfg, &i2s_sync_task)); + + gptimer_config_t timer_cfg = { + .clk_src = GPTIMER_CLK_SRC_DEFAULT, + .direction = GPTIMER_COUNT_UP, + .resolution_hz = I2S_TX_SYNC_TEST_TIMER_RES_HZ, + }; + TEST_ESP_OK(gptimer_new_timer(&timer_cfg, &timer)); + gptimer_alarm_config_t alarm_cfg = { + .alarm_count = I2S_TX_SYNC_TEST_ALARM_COUNT, + }; + TEST_ESP_OK(gptimer_set_alarm_action(timer, &alarm_cfg)); + + gptimer_etm_event_config_t timer_event_cfg = { + .event_type = GPTIMER_ETM_EVENT_ALARM_MATCH, + }; + TEST_ESP_OK(gptimer_new_etm_event(timer, &timer_event_cfg, &timer_event)); + + esp_etm_channel_config_t etm_cfg = {}; + TEST_ESP_OK(esp_etm_new_channel(&etm_cfg, &etm_channel)); + TEST_ESP_OK(esp_etm_channel_connect(etm_channel, timer_event, i2s_sync_task)); + TEST_ESP_OK(esp_etm_channel_enable(etm_channel)); + + TEST_ESP_OK(gptimer_enable(timer)); + TEST_ESP_OK(gptimer_start(timer)); + TEST_ESP_OK(i2s_channel_enable(tx_handle)); + + bool callback_triggered = xSemaphoreTake(cb_ctx.sem, pdMS_TO_TICKS(100)) == pdTRUE; + printf("TX sync callback: triggered=%d, diff=%"PRId32"\n", callback_triggered, cb_ctx.diff_count); + + TEST_ESP_OK(gptimer_stop(timer)); + TEST_ESP_OK(i2s_channel_disable(tx_handle)); + intr_cbs.on_tx_sync = NULL; + TEST_ESP_OK(i2s_channel_register_intr_event_callback(tx_handle, &intr_cbs, NULL)); + TEST_ESP_OK(gptimer_disable(timer)); + TEST_ESP_OK(esp_etm_channel_disable(etm_channel)); + + TEST_ESP_OK(esp_etm_del_event(timer_event)); + TEST_ESP_OK(esp_etm_del_task(i2s_sync_task)); + TEST_ESP_OK(esp_etm_del_channel(etm_channel)); + TEST_ESP_OK(gptimer_del_timer(timer)); + TEST_ESP_OK(i2s_del_channel(tx_handle)); + vSemaphoreDelete(cb_ctx.sem); + free(buf); + + TEST_ASSERT_TRUE(callback_triggered); + + /* SYNC_CHECK fires at 0.005 s. About 48000 * 2 * 0.005 = 480 samples + * are sent, so ideal_cnt 1000 should produce diff_count around -520. + */ + TEST_ASSERT_INT32_WITHIN(52, -520, cb_ctx.diff_count); +} +#endif + TEST_CASE("i2s_destination_test", "[i2s]") { i2s_chan_handle_t tx = NULL; diff --git a/components/esp_hal_i2s/esp32h4/include/hal/i2s_ll.h b/components/esp_hal_i2s/esp32h4/include/hal/i2s_ll.h index 20a28716ee1..0f9659d505d 100644 --- a/components/esp_hal_i2s/esp32h4/include/hal/i2s_ll.h +++ b/components/esp_hal_i2s/esp32h4/include/hal/i2s_ll.h @@ -68,6 +68,7 @@ extern "C" { [I2S_ETM_TASK_SYNC_FIFO] = I2S0_TASK_SYNC_CHECK, \ }}}[i2s_port][(chan_dir) - 1][task] #define I2S_LL_ETM_MAX_THRESH_NUM (0x3FFFUL) +#define I2S_LL_TX_SYNC_INT_EVENT (BIT(4)) /** * @@ -459,6 +460,7 @@ static inline void i2s_ll_rx_set_mclk(i2s_dev_t *hw, const hal_utils_clk_div_t * * * @param hw Peripheral I2S hardware instance address. */ +__attribute__((always_inline)) static inline void i2s_ll_tx_update(i2s_dev_t *hw) { hw->tx_conf.tx_update = 1; @@ -1311,32 +1313,30 @@ static inline void i2s_ll_tx_reset_fifo_sync_diff_counter(i2s_dev_t *hw) } /** - * @brief Set TX FIFO synchronization difference counter software threshold - * @note It determines the up threshold that the hardware synchronize the data automatically. - * - If diff_count <= sw_threshold, the hardware will synchronize the data automatically. - * - If diff_count > sw_threshold, the automatic synchronization is not proper for this case, - * interrupt will be triggered to let the software decide how to handle this case. + * @brief Set TX FIFO synchronization difference threshold for manual handling + * @note If diff_count is greater than manual_threshold, a TX sync interrupt is triggered for software handling. * * @param hw Peripheral I2S hardware instance address. - * @param thresh The threshold that send + * @param thresh Difference counter threshold. When the difference count exceeds this value, + * the TX sync interrupt is triggered. */ __attribute__((always_inline)) -static inline void i2s_ll_tx_set_fifo_sync_diff_conter_sw_threshold(i2s_dev_t *hw, uint32_t thresh) +static inline void i2s_ll_tx_set_fifo_sync_diff_counter_manual_threshold(i2s_dev_t *hw, uint32_t thresh) { hw->sync_sw_thres.tx_cnt_diff_sw_thres = thresh; } /** - * @brief Set TX FIFO synchronization difference counter hardware threshold - * @note It determines the down threshold that the hardware synchronize the data automatically. - * - If diff_count < hw_threshold, synchronization check pass, do nothing - * - If diff_count >= hw_threshold, the hardware will synchronize the data automatically. + * @brief Set TX FIFO synchronization difference threshold for automatic hardware supplementation + * @note If diff_count is greater than or equal to auto_threshold and less than manual_threshold, + * hardware will synchronize the data automatically. * * @param hw Peripheral I2S hardware instance address. - * @param thresh The threshold that send + * @param thresh Difference counter threshold. When the difference count reaches this value, + * hardware auto-synchronization is triggered. */ __attribute__((always_inline)) -static inline void i2s_ll_tx_set_fifo_sync_diff_conter_hw_threshold(i2s_dev_t *hw, uint32_t thresh) +static inline void i2s_ll_tx_set_fifo_sync_diff_counter_auto_threshold(i2s_dev_t *hw, uint32_t thresh) { hw->sync_hw_thres.tx_cnt_diff_hw_thres = thresh; } @@ -1347,11 +1347,9 @@ static inline void i2s_ll_tx_set_fifo_sync_diff_conter_hw_threshold(i2s_dev_t *h * * @param hw Peripheral I2S hardware instance address. * @param mode Data supplementation mode - * - 0: Supplement the last data - * - 1: Supplement the data configured in `hw_sync_data` reg */ __attribute__((always_inline)) -static inline void i2s_ll_tx_set_hw_fifo_sync_suppl_mode(i2s_dev_t *hw, uint32_t mode) +static inline void i2s_ll_tx_set_hw_fifo_sync_suppl_mode(i2s_dev_t *hw, i2s_tx_fifo_sync_suppl_mode_t mode) { hw->hw_sync_conf.tx_hw_sync_suppl_mode = mode; } @@ -1368,6 +1366,57 @@ static inline void i2s_ll_tx_set_hw_fifo_sync_static_suppl_data(i2s_dev_t *hw, u hw->hw_sync_data.tx_hw_sync_suppl_data = data; } +/** + * @brief Get I2S interrupt status register address + */ +__attribute__((always_inline)) +static inline volatile void *i2s_ll_get_interrupt_status_reg(i2s_dev_t *hw) +{ + return (volatile void *)&hw->int_st; +} + +/** + * @brief Get I2S interrupt status by mask + * + * @param hw Peripheral I2S hardware instance address. + * @param mask Interrupt status mask + * @return Interrupt status + */ +__attribute__((always_inline)) +static inline uint32_t i2s_ll_get_interrupt_status(i2s_dev_t *hw, uint32_t mask) +{ + return hw->int_st.val & mask; +} + +/** + * @brief Clear I2S interrupt status by mask + * + * @param hw Peripheral I2S hardware instance address. + * @param mask Interrupt status mask + */ +__attribute__((always_inline)) +static inline void i2s_ll_clear_interrupt_status(i2s_dev_t *hw, uint32_t mask) +{ + hw->int_clr.val = mask; +} + +/** + * @brief Enable I2S interrupt for specific event mask + * + * @param hw Peripheral I2S hardware instance address. + * @param mask Event mask + * @param enable True to enable, false to disable + */ +__attribute__((always_inline)) +static inline void i2s_ll_enable_interrupt(i2s_dev_t *hw, uint32_t mask, bool enable) +{ + if (enable) { + hw->int_ena.val |= mask; + } else { + hw->int_ena.val &= ~mask; + } +} + /** * @brief Set the TX ETM synchronization ideal count * diff --git a/components/esp_hal_i2s/esp32s31/include/hal/i2s_ll.h b/components/esp_hal_i2s/esp32s31/include/hal/i2s_ll.h index 20abf78f950..608ab5681ac 100644 --- a/components/esp_hal_i2s/esp32s31/include/hal/i2s_ll.h +++ b/components/esp_hal_i2s/esp32s31/include/hal/i2s_ll.h @@ -105,6 +105,7 @@ extern "C" { }[i2s_port][(chan_dir) - 1][task] #define I2S_LL_ETM_MAX_THRESH_NUM (0x3FFFUL) +#define I2S_LL_TX_SYNC_INT_EVENT (BIT(4)) /** * @brief Enable the bus clock for I2S module @@ -672,6 +673,7 @@ static inline void i2s_ll_rx_set_mclk(i2s_dev_t *hw, const hal_utils_clk_div_t * * * @param hw Peripheral I2S hardware instance address. */ +__attribute__((always_inline)) static inline void i2s_ll_tx_update(i2s_dev_t *hw) { hw->tx_conf.tx_update = 1; @@ -1832,32 +1834,30 @@ static inline void i2s_ll_tx_reset_fifo_sync_diff_counter(i2s_dev_t *hw) } /** - * @brief Set TX FIFO synchronization difference counter software threshold - * @note It determines the up threshold that the hardware synchronize the data automatically. - * - If diff_count <= sw_threshold, the hardware will synchronize the data automatically. - * - If diff_count > sw_threshold, the automatic synchronization is not proper for this case, - * interrupt will be triggered to let the software decide how to handle this case. + * @brief Set TX FIFO synchronization difference threshold for manual handling + * @note If diff_count is greater than manual_threshold, a TX sync interrupt is triggered for software handling. * * @param hw Peripheral I2S hardware instance address. - * @param thresh The threshold that send + * @param thresh Difference counter threshold. When the difference count exceeds this value, + * the TX sync interrupt is triggered. */ __attribute__((always_inline)) -static inline void i2s_ll_tx_set_fifo_sync_diff_conter_sw_threshold(i2s_dev_t *hw, uint32_t thresh) +static inline void i2s_ll_tx_set_fifo_sync_diff_counter_manual_threshold(i2s_dev_t *hw, uint32_t thresh) { hw->sync_sw_thres.tx_cnt_diff_sw_thres = thresh; } /** - * @brief Set TX FIFO synchronization difference counter hardware threshold - * @note It determines the down threshold that the hardware synchronize the data automatically. - * - If diff_count < hw_threshold, synchronization check pass, do nothing - * - If diff_count >= hw_threshold, the hardware will synchronize the data automatically. + * @brief Set TX FIFO synchronization difference threshold for automatic hardware supplementation + * @note If diff_count is greater than or equal to auto_threshold and less than manual_threshold, + * hardware will synchronize the data automatically. * * @param hw Peripheral I2S hardware instance address. - * @param thresh The threshold that send + * @param thresh Difference counter threshold. When the difference count reaches this value, + * hardware auto-synchronization is triggered. */ __attribute__((always_inline)) -static inline void i2s_ll_tx_set_fifo_sync_diff_conter_hw_threshold(i2s_dev_t *hw, uint32_t thresh) +static inline void i2s_ll_tx_set_fifo_sync_diff_counter_auto_threshold(i2s_dev_t *hw, uint32_t thresh) { hw->sync_hw_thres.tx_cnt_diff_hw_thres = thresh; } @@ -1880,11 +1880,9 @@ static inline void i2s_ll_tx_enable_hw_fifo_sync(i2s_dev_t *hw, bool enable) * * @param hw Peripheral I2S hardware instance address. * @param mode Data supplementation mode - * - 0: Supplement the last data - * - 1: Supplement the data configured in `hw_sync_data` reg */ __attribute__((always_inline)) -static inline void i2s_ll_tx_set_hw_fifo_sync_suppl_mode(i2s_dev_t *hw, uint32_t mode) +static inline void i2s_ll_tx_set_hw_fifo_sync_suppl_mode(i2s_dev_t *hw, i2s_tx_fifo_sync_suppl_mode_t mode) { hw->hw_sync_conf.tx_hw_sync_suppl_mode = mode; } @@ -1901,6 +1899,57 @@ static inline void i2s_ll_tx_set_hw_fifo_sync_static_suppl_data(i2s_dev_t *hw, u hw->hw_sync_data.tx_hw_sync_suppl_data = data; } +/** + * @brief Get I2S interrupt status register address + */ +__attribute__((always_inline)) +static inline volatile void *i2s_ll_get_interrupt_status_reg(i2s_dev_t *hw) +{ + return (volatile void *)&hw->int_st; +} + +/** + * @brief Get I2S interrupt status by mask + * + * @param hw Peripheral I2S hardware instance address. + * @param mask Interrupt status mask + * @return Interrupt status + */ +__attribute__((always_inline)) +static inline uint32_t i2s_ll_get_interrupt_status(i2s_dev_t *hw, uint32_t mask) +{ + return hw->int_st.val & mask; +} + +/** + * @brief Clear I2S interrupt status by mask + * + * @param hw Peripheral I2S hardware instance address. + * @param mask Interrupt status mask + */ +__attribute__((always_inline)) +static inline void i2s_ll_clear_interrupt_status(i2s_dev_t *hw, uint32_t mask) +{ + hw->int_clr.val = mask; +} + +/** + * @brief Enable I2S interrupt for specific event mask + * + * @param hw Peripheral I2S hardware instance address. + * @param mask Event mask + * @param enable True to enable, false to disable + */ +__attribute__((always_inline)) +static inline void i2s_ll_enable_interrupt(i2s_dev_t *hw, uint32_t mask, bool enable) +{ + if (enable) { + hw->int_ena.val |= mask; + } else { + hw->int_ena.val &= ~mask; + } +} + /** * @brief Check whether an I2S data destination is supported on the specified port */ diff --git a/components/esp_hal_i2s/include/hal/i2s_types.h b/components/esp_hal_i2s/include/hal/i2s_types.h index d913f6a8e1f..7a711587f07 100644 --- a/components/esp_hal_i2s/include/hal/i2s_types.h +++ b/components/esp_hal_i2s/include/hal/i2s_types.h @@ -78,6 +78,16 @@ typedef enum { I2S_DESTINATION_BT = 1, /*!< Route I2S data to Bluetooth (I2S0 only; see `SOC_I2S_SUPPORTS_BT_DEST`) */ } i2s_destination_t; +#if SOC_I2S_SUPPORTS_TX_FIFO_SYNC +/** + * @brief TX FIFO synchronization hardware data supplement mode + */ +typedef enum { + I2S_TX_FIFO_SYNC_SUPPL_MODE_LAST_DATA = 0, /*!< Supplement with the last transmitted data */ + I2S_TX_FIFO_SYNC_SUPPL_MODE_STATIC_DATA = 1, /*!< Supplement with static data specified in config */ +} i2s_tx_fifo_sync_suppl_mode_t; +#endif // SOC_I2S_SUPPORTS_TX_FIFO_SYNC + #if SOC_I2S_SUPPORTS_PCM /** * @brief A/U-law decompress or compress configuration. diff --git a/docs/en/api-reference/peripherals/i2s.rst b/docs/en/api-reference/peripherals/i2s.rst index df41eafe13b..8e5733c4752 100644 --- a/docs/en/api-reference/peripherals/i2s.rst +++ b/docs/en/api-reference/peripherals/i2s.rst @@ -313,6 +313,100 @@ To satisfy the high quality audio requirement, following advanced APIs are provi - :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. +.. only:: SOC_I2S_SUPPORTS_TX_SYNC_CNT + + - :cpp:func:`i2s_channel_get_sync_count`: Read the TX BCLK/FIFO synchronization counters. This API can also actively clear them through the ``reset`` argument. + +.. only:: SOC_I2S_SUPPORTS_TX_FIFO_SYNC + + TX FIFO Synchronization + """"""""""""""""""""""" + + {IDF_TARGET_NAME} supports I2S TX FIFO synchronization. It can periodically trigger ``I2S_ETM_TASK_SYNC_FIFO`` task through ETM to check the difference between the actual TX FIFO data count and the expected count. This feature is useful when multiple I2S TX ports or an external timing source need to stay synchronized. + + TX FIFO synchronization related APIs include: + + - :cpp:func:`i2s_channel_get_sync_diff_count`: Read the TX FIFO synchronization difference counter. The value is signed and means ``I2S_TX_FIFO_CNT - I2S_TX_FIFO_IDEAL_CNT``. + - :cpp:func:`i2s_channel_config_tx_fifo_sync`: Configure the expected count, automatic supplement threshold, + manual supplement threshold, and hardware supplement mode. After automatic hardware supplementation is + enabled, hardware automatically supplements or deletes data according to ``diff_count`` so that the actual + count approaches ``ideal_cnt``. + - :cpp:func:`i2s_channel_register_intr_event_callback`: Register the manual supplement threshold interrupt callback. When + ``diff_count`` exceeds the manual supplement threshold, the driver calls this callback in the ISR and provides + ``diff_count`` through :cpp:type:`i2s_sync_event_data_t`. + + The typical usage steps are: + + 1. Create and initialize an I2S TX channel. + 2. Call :cpp:func:`i2s_channel_config_tx_fifo_sync` to configure :cpp:type:`i2s_tx_fifo_sync_config_t`. ``ideal_cnt`` + is the expected number of transmitted data units at each ETM synchronization check. ``auto_suppl_thresh`` is + the automatic hardware supplement threshold: set it to ``0`` to disable automatic hardware supplementation, or + set it to a value greater than ``0`` and smaller than ``manual_suppl_thresh`` to enable automatic hardware + supplementation. ``manual_suppl_thresh`` is the threshold for triggering the callback for manual handling. Once + enabled, if the difference exceeds the automatic supplement threshold but has not reached the manual supplement + threshold, hardware automatically supplements or deletes the corresponding amount of data to synchronize with + ``ideal_cnt``. + 3. To handle severe out-of-sync conditions, call :cpp:func:`i2s_channel_register_intr_event_callback` to register a callback. + After the callback is registered, the driver enables the TX synchronization interrupt. If the difference exceeds + the manual supplement threshold, the driver calls this callback in the ISR and provides ``diff_count`` through + :cpp:type:`i2s_sync_event_data_t`. + 4. Call :cpp:func:`i2s_new_etm_task` to create the ``I2S_ETM_TASK_SYNC_FIFO`` task, and connect an external ETM event to this task. + 5. Enable the ETM channel and I2S TX channel, so that ETM events periodically trigger synchronization checks. + + The following example shows how to use a GPTimer alarm event to trigger the I2S TX FIFO synchronization check, and + get ``diff_count`` in the manual supplement threshold interrupt: + + .. code-block:: c + + #include "driver/i2s_common.h" + #include "driver/i2s_etm.h" + #include "driver/gptimer.h" + #include "esp_etm.h" + + /* Assume the I2S TX channel, GPTimer, and ETM channel have been created and initialized */ + i2s_chan_handle_t tx_handle; + gptimer_handle_t timer; + esp_etm_channel_handle_t etm_channel; + + static bool IRAM_ATTR i2s_tx_sync_callback(i2s_chan_handle_t handle, + const i2s_sync_event_data_t *event, + void *user_ctx) + { + // event->diff_count = I2S_TX_FIFO_CNT - I2S_TX_FIFO_IDEAL_CNT + return false; + } + + i2s_tx_fifo_sync_config_t sync_cfg = { + .ideal_cnt = 1000, + .manual_suppl_thresh = 64, + .auto_suppl_thresh = 32, + .suppl_mode = I2S_TX_FIFO_SYNC_SUPPL_MODE_LAST_DATA, + }; + i2s_intr_event_callbacks_t intr_cbs = { + .on_tx_sync = i2s_tx_sync_callback, + }; + i2s_channel_config_tx_fifo_sync(tx_handle, &sync_cfg); + i2s_channel_register_intr_event_callback(tx_handle, &intr_cbs, NULL); + + i2s_etm_task_config_t i2s_task_cfg = { + .task_type = I2S_ETM_TASK_SYNC_FIFO, + }; + esp_etm_task_handle_t i2s_sync_task = NULL; + i2s_new_etm_task(tx_handle, &i2s_task_cfg, &i2s_sync_task); + + gptimer_etm_event_config_t timer_event_cfg = { + .event_type = GPTIMER_ETM_EVENT_ALARM_MATCH, + }; + esp_etm_event_handle_t timer_event = NULL; + gptimer_new_etm_event(timer, &timer_event_cfg, &timer_event); + + esp_etm_channel_connect(etm_channel, timer_event, i2s_sync_task); + esp_etm_channel_enable(etm_channel); + + .. note:: + + After ``I2S_ETM_TASK_SYNC_FIFO`` is triggered, hardware automatically clears the TX FIFO/BCLK synchronization counters. + .. _i2s-iram-safe: IRAM Safe diff --git a/docs/zh_CN/api-reference/peripherals/i2s.rst b/docs/zh_CN/api-reference/peripherals/i2s.rst index 1fe7ad7e78e..b479acdb345 100644 --- a/docs/zh_CN/api-reference/peripherals/i2s.rst +++ b/docs/zh_CN/api-reference/peripherals/i2s.rst @@ -313,6 +313,93 @@ I2S 的数据传输(包括数据发送和接收)由 DMA 实现。在传输 - :cpp:func:`i2s_channel_preload_data`: 用于预加载音频数据到 I2S 内部缓存,使得 TX 通道使能后能够立即发送数据,以此降低音频初始输出延迟。 - :cpp:func:`i2s_channel_tune_rate`: 用于在运行时动态微调音频速率,以匹配音频数据生产者和消费者的速度,从而防止因速率不匹配导致的中间缓存数据累积或不足。 +.. only:: SOC_I2S_SUPPORTS_TX_SYNC_CNT + + - :cpp:func:`i2s_channel_get_sync_count`:用于读取 TX BCLK/FIFO 同步计数器。该 API 也可通过 ``reset`` 参数主动清零计数器。 + +.. only:: SOC_I2S_SUPPORTS_TX_FIFO_SYNC + + TX FIFO 同步 + """""""""""" + + {IDF_TARGET_NAME} 支持 I2S TX FIFO 同步功能,可用于通过 ETM 周期性触发 ``I2S_ETM_TASK_SYNC_FIFO`` 任务,检查 TX FIFO 实际发送的数据计数与期望计数之间的偏差。该功能适用于需要多个 I2S TX 端口或外部时序源保持同步的场景。 + + TX FIFO 同步相关 API 包括: + + - :cpp:func:`i2s_channel_get_sync_diff_count`:读取 TX FIFO 同步差值计数器。该值为有符号数,含义为 ``I2S_TX_FIFO_CNT - I2S_TX_FIFO_IDEAL_CNT``。 + - :cpp:func:`i2s_channel_config_tx_fifo_sync`:配置期望计数、自动补偿阈值、手动补偿阈值以及硬件补偿方式。 + 启用硬件自动补偿后,硬件会根据 ``diff_count`` 自动补充或删除数据,使实际计数靠近 ``ideal_cnt``。 + - :cpp:func:`i2s_channel_register_intr_event_callback`:注册手动补偿阈值中断回调。当 ``diff_count`` 超过手动补偿阈值时, + 驱动会在 ISR 中调用该回调,并通过 :cpp:type:`i2s_sync_event_data_t` 提供 ``diff_count``。 + + 使用该功能的一般步骤如下: + + 1. 创建并初始化 I2S TX 通道。 + 2. 调用 :cpp:func:`i2s_channel_config_tx_fifo_sync` 配置 :cpp:type:`i2s_tx_fifo_sync_config_t`。其中 ``ideal_cnt`` + 为每次 ETM 同步检查时期望发送的数据个数;``auto_suppl_thresh`` 为硬件自动补偿阈值,设置为 ``0`` 表示 + 关闭硬件自动补偿,设置为大于 ``0`` 且小于 ``manual_suppl_thresh`` 表示开启硬件自动补偿; + ``manual_suppl_thresh`` 为触发回调并交由软件手动处理的阈值。开启后,如果偏差超过自动补偿阈值但尚未达到 + 手动补偿阈值,硬件会自动补充或删除相应数量的数据,以实现与 ``ideal_cnt`` 同步。 + 3. 如需处理严重不同步场景,调用 :cpp:func:`i2s_channel_register_intr_event_callback` 注册回调。注册回调后,驱动会使能 + TX 同步中断;如果偏差超过手动补偿阈值,驱动会在 ISR 中调用该回调,并通过 + :cpp:type:`i2s_sync_event_data_t` 提供 ``diff_count``。 + 4. 调用 :cpp:func:`i2s_new_etm_task` 创建 ``I2S_ETM_TASK_SYNC_FIFO`` 任务,并将外部 ETM 事件连接到该任务。 + 5. 使能 ETM 通道和 I2S TX 通道,由 ETM 事件周期性触发同步检查。 + + 以下示例展示了如何使用 GPTimer alarm event 触发 I2S TX FIFO 同步检查,并在手动补偿阈值中断中获取 + ``diff_count``: + + .. code-block:: c + + #include "driver/i2s_common.h" + #include "driver/i2s_etm.h" + #include "driver/gptimer.h" + #include "esp_etm.h" + + /* 假设已经创建并初始化 I2S TX 通道、GPTimer 和 ETM 通道 */ + i2s_chan_handle_t tx_handle; + gptimer_handle_t timer; + esp_etm_channel_handle_t etm_channel; + + static bool IRAM_ATTR i2s_tx_sync_callback(i2s_chan_handle_t handle, + const i2s_sync_event_data_t *event, + void *user_ctx) + { + // event->diff_count = I2S_TX_FIFO_CNT - I2S_TX_FIFO_IDEAL_CNT + return false; + } + + i2s_tx_fifo_sync_config_t sync_cfg = { + .ideal_cnt = 1000, + .manual_suppl_thresh = 64, + .auto_suppl_thresh = 32, + .suppl_mode = I2S_TX_FIFO_SYNC_SUPPL_MODE_LAST_DATA, + }; + i2s_intr_event_callbacks_t intr_cbs = { + .on_tx_sync = i2s_tx_sync_callback, + }; + i2s_channel_config_tx_fifo_sync(tx_handle, &sync_cfg); + i2s_channel_register_intr_event_callback(tx_handle, &intr_cbs, NULL); + + i2s_etm_task_config_t i2s_task_cfg = { + .task_type = I2S_ETM_TASK_SYNC_FIFO, + }; + esp_etm_task_handle_t i2s_sync_task = NULL; + i2s_new_etm_task(tx_handle, &i2s_task_cfg, &i2s_sync_task); + + gptimer_etm_event_config_t timer_event_cfg = { + .event_type = GPTIMER_ETM_EVENT_ALARM_MATCH, + }; + esp_etm_event_handle_t timer_event = NULL; + gptimer_new_etm_event(timer, &timer_event_cfg, &timer_event); + + esp_etm_channel_connect(etm_channel, timer_event, i2s_sync_task); + esp_etm_channel_enable(etm_channel); + + .. note:: + + ``I2S_ETM_TASK_SYNC_FIFO`` 触发后,硬件会自动清零 TX FIFO/BCLK 同步计数器。 + .. _i2s-iram-safe: IRAM 安全 From b8218f4fe126c2523fbda76610d65b62f70fac81 Mon Sep 17 00:00:00 2001 From: Chen Chen Date: Tue, 9 Jun 2026 17:18:01 +0800 Subject: [PATCH 3/4] refactor(i2s): combine separate callback registers into single one --- components/esp_driver_i2s/i2s_common.c | 216 +++++++++++------- components/esp_driver_i2s/i2s_pdm.c | 3 + components/esp_driver_i2s/i2s_private.h | 19 +- components/esp_driver_i2s/i2s_std.c | 5 + components/esp_driver_i2s/i2s_tdm.c | 5 + .../include/driver/i2s_common.h | 85 +++---- .../esp_driver_i2s/include/driver/i2s_types.h | 4 +- components/esp_driver_i2s/linker.lf | 2 +- .../test_apps/i2s/main/test_i2s.c | 24 +- docs/en/api-reference/peripherals/i2s.rst | 47 ++-- docs/zh_CN/api-reference/peripherals/i2s.rst | 50 ++-- 11 files changed, 263 insertions(+), 197 deletions(-) diff --git a/components/esp_driver_i2s/i2s_common.c b/components/esp_driver_i2s/i2s_common.c index 7f6407f1be9..9f67e48386c 100644 --- a/components/esp_driver_i2s/i2s_common.c +++ b/components/esp_driver_i2s/i2s_common.c @@ -76,6 +76,12 @@ static const char *TAG = "i2s_common"; +#if SOC_I2S_SUPPORTS_TX_FIFO_SYNC +static void s_i2s_channel_update_tx_sync_callback(i2s_chan_handle_t tx_handle, + i2s_tx_fifo_sync_callback_t cb, + void *user_data); +#endif + __attribute__((always_inline)) inline void *i2s_dma_calloc(i2s_chan_handle_t handle, size_t num, size_t size) { @@ -432,10 +438,20 @@ esp_err_t i2s_channel_register_event_callback(i2s_chan_handle_t handle, const i2 { I2S_NULL_POINTER_CHECK(TAG, handle); I2S_NULL_POINTER_CHECK(TAG, callbacks); - /* on_recv/on_sent are dispatched from the DMA ISR. If this channel does not use the DMA memory path, no such callback fires. */ - ESP_RETURN_ON_FALSE(I2S_CHANNEL_USES_DMA(handle), ESP_ERR_NOT_SUPPORTED, TAG, - "event callbacks require the DMA memory data path on this channel"); esp_err_t ret = ESP_OK; + bool has_dma_event_cb = callbacks->on_recv || callbacks->on_recv_q_ovf || + callbacks->on_sent || callbacks->on_send_q_ovf; + bool update_dma_cb = I2S_CHANNEL_USES_DMA(handle); +#if SOC_I2S_SUPPORTS_TX_FIFO_SYNC + i2s_tx_fifo_sync_callback_t sync_cb = callbacks->on_tx_sync_evt; + bool update_sync_cb = sync_cb || (handle->dir == I2S_DIR_TX && handle->on_tx_sync); +#endif + ESP_RETURN_ON_FALSE(!has_dma_event_cb || update_dma_cb, + ESP_ERR_NOT_SUPPORTED, TAG, + "DMA event callbacks require the DMA memory data path on this channel"); +#if SOC_I2S_SUPPORTS_TX_FIFO_SYNC + ESP_RETURN_ON_FALSE(!update_sync_cb || handle->dir == I2S_DIR_TX, ESP_ERR_INVALID_ARG, TAG, "channel is not TX"); +#endif #if CONFIG_I2S_ISR_IRAM_SAFE if (callbacks->on_recv) { ESP_RETURN_ON_FALSE(esp_ptr_in_iram(callbacks->on_recv), ESP_ERR_INVALID_ARG, TAG, "on_recv callback not in IRAM"); @@ -449,15 +465,31 @@ esp_err_t i2s_channel_register_event_callback(i2s_chan_handle_t handle, const i2 if (callbacks->on_send_q_ovf) { ESP_RETURN_ON_FALSE(esp_ptr_in_iram(callbacks->on_send_q_ovf), ESP_ERR_INVALID_ARG, TAG, "on_send_q_ovf callback not in IRAM"); } +#if SOC_I2S_SUPPORTS_TX_FIFO_SYNC + if (sync_cb) { + ESP_RETURN_ON_FALSE(esp_ptr_in_iram(sync_cb), ESP_ERR_INVALID_ARG, TAG, "sync callback not in IRAM"); + } +#endif if (user_data) { ESP_RETURN_ON_FALSE(esp_ptr_internal(user_data), ESP_ERR_INVALID_ARG, TAG, "user context not in internal RAM"); } + #endif xSemaphoreTake(handle->mutex, portMAX_DELAY); ESP_GOTO_ON_FALSE(handle->state < I2S_CHAN_STATE_RUNNING, ESP_ERR_INVALID_STATE, err, TAG, "invalid state, I2S has enabled"); - memcpy(&(handle->callbacks), callbacks, sizeof(i2s_event_callbacks_t)); - handle->user_data = user_data; + if (update_dma_cb) { + handle->callbacks.on_recv = callbacks->on_recv; + handle->callbacks.on_recv_q_ovf = callbacks->on_recv_q_ovf; + handle->callbacks.on_sent = callbacks->on_sent; + handle->callbacks.on_send_q_ovf = callbacks->on_send_q_ovf; + handle->user_data = user_data; + } +#if SOC_I2S_SUPPORTS_TX_FIFO_SYNC + if (update_sync_cb) { + s_i2s_channel_update_tx_sync_callback(handle, sync_cb, user_data); + } +#endif err: xSemaphoreGive(handle->mutex); return ret; @@ -1162,6 +1194,15 @@ esp_err_t i2s_del_channel(i2s_chan_handle_t handle) i2s_dir_t __attribute__((unused)) dir = handle->dir; bool is_bound = true; +#if SOC_I2S_SUPPORTS_TX_FIFO_SYNC + if (handle->i2s_intr) { + i2s_ll_enable_interrupt(handle->controller->hal.dev, I2S_LL_TX_SYNC_INT_EVENT, false); + esp_intr_disable(handle->i2s_intr); + esp_intr_free(handle->i2s_intr); + handle->i2s_intr = NULL; + } +#endif + #if SOC_I2S_SUPPORTS_APLL /* Must switch back to D2CLK on ESP32-S2, * because the clock of some registers are bound to APLL, @@ -1221,14 +1262,6 @@ esp_err_t i2s_del_channel(i2s_chan_handle_t handle) if (handle->binary) { vSemaphoreDeleteWithCaps(handle->binary); } -#if SOC_I2S_SUPPORTS_TX_FIFO_SYNC - if (handle->sync_intr) { - i2s_ll_enable_interrupt(handle->controller->hal.dev, I2S_LL_TX_SYNC_INT_EVENT, false); - esp_intr_disable(handle->sync_intr); - esp_intr_free(handle->sync_intr); - handle->sync_intr = NULL; - } -#endif #if SOC_I2S_HW_VERSION_1 i2s_obj->chan_occupancy = 0; #else @@ -1650,31 +1683,35 @@ static inline esp_err_t i2s_check_tx_handle(i2s_chan_handle_t tx_handle) return ESP_OK; } -esp_err_t i2s_channel_get_sync_count(i2s_chan_handle_t tx_handle, uint32_t *bclk_count, uint32_t *fifo_count, - bool reset) -{ - ESP_RETURN_ON_ERROR(i2s_check_tx_handle(tx_handle), TAG, "invalid TX handle"); - i2s_dev_t *hw = tx_handle->controller->hal.dev; - if (bclk_count) { - *bclk_count = i2s_ll_tx_get_bclk_sync_count(hw); - } - if (fifo_count) { - *fifo_count = i2s_ll_tx_get_fifo_sync_count(hw); - } - if (reset) { - i2s_ll_tx_reset_bclk_sync_counter(hw); - i2s_ll_tx_reset_fifo_sync_counter(hw); - } - return ESP_OK; -} - #if SOC_I2S_SUPPORTS_TX_FIFO_SYNC __attribute__((always_inline)) static inline int32_t i2s_sign_extend_sync_diff(uint32_t diff) { return (diff & BIT(30)) ? (int32_t)(diff | BIT(31)) : (int32_t)diff; } +#endif +esp_err_t i2s_channel_get_sync_count(i2s_chan_handle_t tx_handle, i2s_sync_count_t *count, bool reset) +{ + ESP_RETURN_ON_ERROR(i2s_check_tx_handle(tx_handle), TAG, "invalid TX handle"); + I2S_NULL_POINTER_CHECK(TAG, count); + i2s_dev_t *hw = tx_handle->controller->hal.dev; + count->bclk_count = i2s_ll_tx_get_bclk_sync_count(hw); + count->fifo_count = i2s_ll_tx_get_fifo_sync_count(hw); +#if SOC_I2S_SUPPORTS_TX_FIFO_SYNC + count->diff_count = i2s_sign_extend_sync_diff(i2s_ll_tx_get_fifo_sync_diff_count(hw)); +#endif + if (reset) { + i2s_ll_tx_reset_bclk_sync_counter(hw); + i2s_ll_tx_reset_fifo_sync_counter(hw); +#if SOC_I2S_SUPPORTS_TX_FIFO_SYNC + i2s_ll_tx_reset_fifo_sync_diff_counter(hw); +#endif + } + return ESP_OK; +} + +#if SOC_I2S_SUPPORTS_TX_FIFO_SYNC #if CONFIG_I2S_ISR_IRAM_SAFE #define I2S_ISR_HANDLER_ATTR IRAM_ATTR #else @@ -1693,22 +1730,65 @@ static I2S_ISR_HANDLER_ATTR void i2s_isr_handler(void *args) i2s_ll_tx_reset_fifo_sync_diff_counter(hw); i2s_ll_clear_interrupt_status(hw, I2S_LL_TX_SYNC_INT_EVENT); - if (handle->on_sync && handle->on_sync(handle, &evt, handle->sync_user_data)) { + if (handle->on_tx_sync && handle->on_tx_sync(handle, &evt, handle->sync_user_data)) { portYIELD_FROM_ISR(); } } } -esp_err_t i2s_channel_get_sync_diff_count(i2s_chan_handle_t tx_handle, int32_t *diff_count, bool reset) +static void s_i2s_channel_update_tx_sync_callback(i2s_chan_handle_t tx_handle, + i2s_tx_fifo_sync_callback_t cb, + void *user_data) { - ESP_RETURN_ON_ERROR(i2s_check_tx_handle(tx_handle), TAG, "invalid TX handle"); i2s_dev_t *hw = tx_handle->controller->hal.dev; - if (diff_count) { - *diff_count = i2s_sign_extend_sync_diff(i2s_ll_tx_get_fifo_sync_diff_count(hw)); + + portENTER_CRITICAL(&g_i2s.spinlock); + if (cb) { + tx_handle->on_tx_sync = cb; + tx_handle->sync_user_data = user_data; + i2s_ll_clear_interrupt_status(hw, I2S_LL_TX_SYNC_INT_EVENT); + i2s_ll_enable_interrupt(hw, I2S_LL_TX_SYNC_INT_EVENT, true); + } else { + i2s_ll_enable_interrupt(hw, I2S_LL_TX_SYNC_INT_EVENT, false); + tx_handle->on_tx_sync = NULL; + tx_handle->sync_user_data = NULL; } - if (reset) { - i2s_ll_tx_reset_fifo_sync_diff_counter(hw); + portEXIT_CRITICAL(&g_i2s.spinlock); +} + +esp_err_t i2s_init_i2s_intr(i2s_chan_handle_t handle) +{ + esp_err_t ret = ESP_OK; + ESP_RETURN_ON_ERROR(i2s_check_tx_handle(handle), TAG, "invalid TX handle"); + if (handle->i2s_intr) { + return ESP_OK; } + + i2s_dev_t *hw = handle->controller->hal.dev; + int port_id = handle->controller->id; + int intr_flag = handle->intr_prio_flags; +#if CONFIG_I2S_ISR_IRAM_SAFE + intr_flag |= ESP_INTR_FLAG_IRAM; +#endif + + i2s_ll_enable_interrupt(hw, I2S_LL_TX_SYNC_INT_EVENT, false); + i2s_ll_clear_interrupt_status(hw, I2S_LL_TX_SYNC_INT_EVENT); + ret = esp_intr_alloc_intrstatus(i2s_periph_signal[port_id].irq, intr_flag, + (uint32_t)i2s_ll_get_interrupt_status_reg(hw), + I2S_LL_TX_SYNC_INT_EVENT, i2s_isr_handler, handle, + &handle->i2s_intr); + ESP_RETURN_ON_ERROR(ret, TAG, "allocate I2S interrupt failed"); + ret = esp_intr_enable(handle->i2s_intr); + if (ret != ESP_OK) { + esp_intr_free(handle->i2s_intr); + handle->i2s_intr = NULL; + return ret; + } + if (handle->on_tx_sync) { + i2s_ll_clear_interrupt_status(hw, I2S_LL_TX_SYNC_INT_EVENT); + i2s_ll_enable_interrupt(hw, I2S_LL_TX_SYNC_INT_EVENT, true); + } + return ESP_OK; } @@ -1717,10 +1797,9 @@ esp_err_t i2s_channel_config_tx_fifo_sync(i2s_chan_handle_t tx_handle, const i2s esp_err_t ret = ESP_OK; ESP_RETURN_ON_ERROR(i2s_check_tx_handle(tx_handle), TAG, "invalid TX handle"); I2S_NULL_POINTER_CHECK(TAG, config); - ESP_RETURN_ON_FALSE(config->auto_suppl_thresh == 0 || - config->auto_suppl_thresh < config->manual_suppl_thresh, + ESP_RETURN_ON_FALSE(config->auto_suppl_thresh < config->manual_suppl_thresh, ESP_ERR_INVALID_ARG, TAG, - "auto_suppl_thresh must be 0 or smaller than manual_suppl_thresh"); + "auto_suppl_thresh must be smaller than manual_suppl_thresh"); i2s_dev_t *hw = tx_handle->controller->hal.dev; xSemaphoreTake(tx_handle->mutex, portMAX_DELAY); @@ -1733,66 +1812,31 @@ esp_err_t i2s_channel_config_tx_fifo_sync(i2s_chan_handle_t tx_handle, const i2s if (config->suppl_mode == I2S_TX_FIFO_SYNC_SUPPL_MODE_STATIC_DATA) { i2s_ll_tx_set_hw_fifo_sync_static_suppl_data(hw, config->suppl_data); } - i2s_ll_tx_enable_hw_fifo_sync(hw, config->auto_suppl_thresh > 0); + i2s_ll_tx_enable_hw_fifo_sync(hw, false); i2s_ll_tx_update(hw); err: xSemaphoreGive(tx_handle->mutex); return ret; } -esp_err_t i2s_channel_register_intr_event_callback(i2s_chan_handle_t tx_handle, - const i2s_intr_event_callbacks_t *callbacks, void *user_data) +esp_err_t i2s_channel_enable_tx_fifo_sync(i2s_chan_handle_t tx_handle, bool enable) { - esp_err_t ret = ESP_OK; ESP_RETURN_ON_ERROR(i2s_check_tx_handle(tx_handle), TAG, "invalid TX handle"); - I2S_NULL_POINTER_CHECK(TAG, callbacks); - i2s_sync_callback_t callback = callbacks->on_tx_sync; -#if CONFIG_I2S_ISR_IRAM_SAFE - if (callback) { - ESP_RETURN_ON_FALSE(esp_ptr_in_iram(callback), ESP_ERR_INVALID_ARG, TAG, "sync callback not in IRAM"); - } - if (user_data) { - ESP_RETURN_ON_FALSE(esp_ptr_internal(user_data), ESP_ERR_INVALID_ARG, TAG, "user context not in internal RAM"); - } -#endif + ESP_RETURN_ON_FALSE(tx_handle->i2s_intr, ESP_ERR_INVALID_STATE, TAG, "TX FIFO sync not configured"); i2s_dev_t *hw = tx_handle->controller->hal.dev; - xSemaphoreTake(tx_handle->mutex, portMAX_DELAY); - ESP_GOTO_ON_FALSE(tx_handle->state < I2S_CHAN_STATE_RUNNING, ESP_ERR_INVALID_STATE, err, TAG, - "invalid state, I2S has enabled"); - - if (callback) { - if (!tx_handle->sync_intr) { - int port_id = tx_handle->controller->id; - int intr_flag = ESP_INTR_FLAG_INTRDISABLED | tx_handle->intr_prio_flags; -#if CONFIG_I2S_ISR_IRAM_SAFE - intr_flag |= ESP_INTR_FLAG_IRAM; -#endif - ret = esp_intr_alloc_intrstatus(i2s_periph_signal[port_id].irq, intr_flag, - (uint32_t)i2s_ll_get_interrupt_status_reg(hw), - I2S_LL_TX_SYNC_INT_EVENT, i2s_isr_handler, tx_handle, - &tx_handle->sync_intr); - ESP_GOTO_ON_ERROR(ret, err, TAG, "allocate TX sync interrupt failed"); - ESP_GOTO_ON_ERROR(esp_intr_enable(tx_handle->sync_intr), err, TAG, "enable TX sync interrupt failed"); - } - tx_handle->on_sync = callback; - tx_handle->sync_user_data = user_data; + portENTER_CRITICAL(&g_i2s.spinlock); + i2s_ll_tx_enable_hw_fifo_sync(hw, enable); + if (enable && tx_handle->on_tx_sync) { i2s_ll_clear_interrupt_status(hw, I2S_LL_TX_SYNC_INT_EVENT); i2s_ll_enable_interrupt(hw, I2S_LL_TX_SYNC_INT_EVENT, true); } else { i2s_ll_enable_interrupt(hw, I2S_LL_TX_SYNC_INT_EVENT, false); - if (tx_handle->sync_intr) { - esp_intr_disable(tx_handle->sync_intr); - esp_intr_free(tx_handle->sync_intr); - tx_handle->sync_intr = NULL; - } - tx_handle->on_sync = NULL; - tx_handle->sync_user_data = NULL; } - -err: - xSemaphoreGive(tx_handle->mutex); - return ret; + i2s_ll_tx_update(hw); + portEXIT_CRITICAL(&g_i2s.spinlock); + return ESP_OK; } + #endif // SOC_I2S_SUPPORTS_TX_FIFO_SYNC #endif // SOC_I2S_SUPPORTS_TX_SYNC_CNT diff --git a/components/esp_driver_i2s/i2s_pdm.c b/components/esp_driver_i2s/i2s_pdm.c index 8a0751da416..19fe978c3ad 100644 --- a/components/esp_driver_i2s/i2s_pdm.c +++ b/components/esp_driver_i2s/i2s_pdm.c @@ -240,6 +240,9 @@ esp_err_t i2s_channel_init_pdm_tx_mode(i2s_chan_handle_t handle, const i2s_pdm_t if (I2S_CHANNEL_USES_DMA(handle)) { ESP_GOTO_ON_ERROR(i2s_init_dma_intr(handle, I2S_INTR_ALLOC_FLAGS), err, TAG, "initialize dma interrupt failed"); } +#if SOC_I2S_SUPPORTS_TX_FIFO_SYNC + ESP_GOTO_ON_ERROR(i2s_init_i2s_intr(handle), err, TAG, "initialize I2S interrupt failed"); +#endif i2s_ll_tx_enable_pdm(handle->controller->hal.dev, pdm_tx_cfg->slot_cfg.data_fmt == I2S_PDM_DATA_FMT_PCM); i2s_ll_set_destination(handle->controller->hal.dev, handle->dir, handle->destination); diff --git a/components/esp_driver_i2s/i2s_private.h b/components/esp_driver_i2s/i2s_private.h index 5dbdf601da6..115c2ebb06b 100644 --- a/components/esp_driver_i2s/i2s_private.h +++ b/components/esp_driver_i2s/i2s_private.h @@ -90,6 +90,9 @@ typedef struct { i2s_isr_callback_t on_send_q_ovf; /**< Callback of sending queue overflowed event, only for tx channel * The event data includes buffer size that has been overwritten */ +#if SOC_I2S_SUPPORTS_TX_FIFO_SYNC + i2s_tx_fifo_sync_callback_t on_tx_sync_evt; /**< Callback when the TX sync difference count exceeds the manual threshold */ +#endif } i2s_event_callbacks_internal_t; /** @@ -181,9 +184,9 @@ struct i2s_channel_obj_t { i2s_event_callbacks_internal_t callbacks; /*!< Callback functions */ void *user_data; /*!< User data for callback functions */ #if SOC_I2S_SUPPORTS_TX_FIFO_SYNC - i2s_sync_callback_t on_sync; /*!< TX FIFO sync manual supplement threshold callback */ + i2s_tx_fifo_sync_callback_t on_tx_sync; /*!< TX FIFO sync manual supplement threshold callback */ void *sync_user_data; /*!< User data for TX FIFO sync callback */ - intr_handle_t sync_intr; /*!< I2S peripheral interrupt for TX FIFO sync */ + intr_handle_t i2s_intr; /*!< I2S peripheral interrupt handle */ #endif void (*start)(i2s_chan_handle_t); /*!< start tx/rx channel */ void (*stop)(i2s_chan_handle_t); /*!< stop tx/rx channel */ @@ -244,6 +247,18 @@ extern i2s_platform_t g_i2s; /*!< Global i2s instance for driver internal use * */ esp_err_t i2s_init_dma_intr(i2s_chan_handle_t handle, int intr_flag); +#if SOC_I2S_SUPPORTS_TX_FIFO_SYNC +/** + * @brief Initialize I2S peripheral interrupt + * + * @param handle I2S channel handle + * @return + * - ESP_OK Initialize interrupt success + * - ESP_ERR_INVALID_ARG Wrong port id or NULL pointer + */ +esp_err_t i2s_init_i2s_intr(i2s_chan_handle_t handle); +#endif + /** * @brief Free I2S DMA descriptor and DMA buffer * diff --git a/components/esp_driver_i2s/i2s_std.c b/components/esp_driver_i2s/i2s_std.c index db434ff9b59..76e8ef28048 100644 --- a/components/esp_driver_i2s/i2s_std.c +++ b/components/esp_driver_i2s/i2s_std.c @@ -345,6 +345,11 @@ esp_err_t i2s_channel_init_std_mode(i2s_chan_handle_t handle, const i2s_std_conf if (I2S_CHANNEL_USES_DMA(handle)) { ESP_GOTO_ON_ERROR(i2s_init_dma_intr(handle, I2S_INTR_ALLOC_FLAGS), err, TAG, "initialize dma interrupt failed"); } +#if SOC_I2S_SUPPORTS_TX_FIFO_SYNC + if (handle->dir == I2S_DIR_TX) { + ESP_GOTO_ON_ERROR(i2s_init_i2s_intr(handle), err, TAG, "initialize I2S interrupt failed"); + } +#endif #if SOC_I2S_HW_VERSION_2 /* Enable clock to start outputting mclk signal. Some codecs will reset once mclk stop */ if (handle->dir == I2S_DIR_TX) { diff --git a/components/esp_driver_i2s/i2s_tdm.c b/components/esp_driver_i2s/i2s_tdm.c index d8f4f209997..96d07070184 100644 --- a/components/esp_driver_i2s/i2s_tdm.c +++ b/components/esp_driver_i2s/i2s_tdm.c @@ -327,6 +327,11 @@ esp_err_t i2s_channel_init_tdm_mode(i2s_chan_handle_t handle, const i2s_tdm_conf if (I2S_CHANNEL_USES_DMA(handle)) { ESP_GOTO_ON_ERROR(i2s_init_dma_intr(handle, I2S_INTR_ALLOC_FLAGS), err, TAG, "initialize dma interrupt failed"); } +#if SOC_I2S_SUPPORTS_TX_FIFO_SYNC + if (handle->dir == I2S_DIR_TX) { + ESP_GOTO_ON_ERROR(i2s_init_i2s_intr(handle), err, TAG, "initialize I2S interrupt failed"); + } +#endif #if SOC_I2S_HW_VERSION_2 /* Enable clock to start outputting mclk signal. Some codecs will reset once mclk stop */ diff --git a/components/esp_driver_i2s/include/driver/i2s_common.h b/components/esp_driver_i2s/include/driver/i2s_common.h index f78dacc7792..ef4ce0abc0f 100644 --- a/components/esp_driver_i2s/include/driver/i2s_common.h +++ b/components/esp_driver_i2s/include/driver/i2s_common.h @@ -53,6 +53,9 @@ typedef struct { i2s_isr_callback_t on_send_q_ovf; /**< Callback of sending queue overflowed event, only for TX channel * The event data includes buffer size that has been overwritten */ +#if SOC_I2S_SUPPORTS_TX_FIFO_SYNC + i2s_tx_fifo_sync_callback_t on_tx_sync_evt; /**< Callback when the TX sync difference count exceeds the manual threshold */ +#endif } i2s_event_callbacks_t; /** @@ -244,7 +247,7 @@ esp_err_t i2s_channel_read(i2s_chan_handle_t handle, void *dest, size_t size, si * - ESP_OK Set event callbacks successfully * - ESP_ERR_INVALID_ARG Set event callbacks failed because of invalid argument * - ESP_ERR_INVALID_STATE Set event callbacks failed because the current channel state is not REGISTERED or READY - * - ESP_ERR_NOT_SUPPORTED Set event callbacks failed because the channel does not use the DMA data path + * - ESP_ERR_NOT_SUPPORTED Set event callbacks failed because the requested event is not supported by this channel */ esp_err_t i2s_channel_register_event_callback(i2s_chan_handle_t handle, const i2s_event_callbacks_t *callbacks, void *user_data); @@ -291,46 +294,41 @@ esp_err_t i2s_channel_preload_data(i2s_chan_handle_t tx_handle, const void *src, 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); #if SOC_I2S_SUPPORTS_TX_SYNC_CNT +/** + * @brief TX synchronization counter values + */ +typedef struct { + uint32_t bclk_count; /*!< BCLK sync counter */ + uint32_t fifo_count; /*!< FIFO sync counter */ +#if SOC_I2S_SUPPORTS_TX_FIFO_SYNC + int32_t diff_count; /*!< Signed difference: I2S_TX_FIFO_CNT - I2S_TX_FIFO_IDEAL_CNT */ +#endif +} i2s_sync_count_t; + /** * @brief Get TX synchronization counters * * @note `fifo_count` reflects how many data have been read from TX FIFO. * Normally, `bclk_count = fifo_count * slot_bit_width`. - * Both counters are reset automatically when `I2S_ETM_TASK_SYNC_FIFO` is triggered. + * BCLK/FIFO counters are reset automatically when `I2S_ETM_TASK_SYNC_FIFO` is triggered. + * @note When `SOC_I2S_SUPPORTS_TX_FIFO_SYNC` is supported, `diff_count` is a signed 31-bit value + * equal to `I2S_TX_FIFO_CNT - I2S_TX_FIFO_IDEAL_CNT`. * * @param[in] tx_handle I2S TX channel handle - * @param[out] bclk_count Pointer to receive BCLK sync counter, set NULL to ignore - * @param[out] fifo_count Pointer to receive FIFO sync counter, set NULL to ignore - * @param[in] reset Whether to reset both counters after reading + * @param[out] count Pointer to receive TX synchronization counters + * @param[in] reset Whether to reset the counters after reading * @return * - ESP_OK Success - * - ESP_ERR_INVALID_ARG Invalid handle or channel is not TX + * - ESP_ERR_INVALID_ARG Invalid handle, channel is not TX, or NULL count pointer */ -esp_err_t i2s_channel_get_sync_count(i2s_chan_handle_t tx_handle, uint32_t *bclk_count, uint32_t *fifo_count, - bool reset); +esp_err_t i2s_channel_get_sync_count(i2s_chan_handle_t tx_handle, i2s_sync_count_t *count, bool reset); #if SOC_I2S_SUPPORTS_TX_FIFO_SYNC -/** - * @brief Get TX FIFO synchronization difference counter - * - * @note `diff_count` is a signed 31-bit value equal to `I2S_TX_FIFO_CNT - I2S_TX_FIFO_IDEAL_CNT`. - * - * @param[in] tx_handle I2S TX channel handle - * @param[out] diff_count Pointer to receive signed difference counter, set NULL to ignore - * @param[in] reset Whether to reset the difference counter after reading - * @return - * - ESP_OK Success - * - ESP_ERR_INVALID_ARG Invalid handle or channel is not TX - */ -esp_err_t i2s_channel_get_sync_diff_count(i2s_chan_handle_t tx_handle, int32_t *diff_count, bool reset); - /** * @brief TX FIFO synchronization configuration */ typedef struct { - uint32_t auto_suppl_thresh; /*!< Threshold to enable automatic FIFO data supplement; - * 0 disables automatic supplement - */ + uint32_t auto_suppl_thresh; /*!< Threshold for automatic FIFO data supplement */ uint32_t manual_suppl_thresh; /*!< Threshold to trigger the callback for manual FIFO data supplement */ uint32_t ideal_cnt; /*!< Ideal FIFO count when ETM sync task is triggered */ i2s_tx_fifo_sync_suppl_mode_t suppl_mode; /*!< Data supplement mode for automatic supplement */ @@ -339,23 +337,11 @@ typedef struct { */ } i2s_tx_fifo_sync_config_t; -/** - * @brief Group of I2S peripheral interrupt event callbacks - * - * @note The callbacks are all running under ISR environment. - */ -typedef struct { - i2s_sync_callback_t on_tx_sync; /*!< Callback when the sync difference count exceeds - * `manual_suppl_thresh` - */ -} i2s_intr_event_callbacks_t; - /** * @brief Configure TX FIFO synchronization * - * @note Set `auto_suppl_thresh` to 0 to disable automatic hardware supplementation. - * @note When automatic hardware supplementation is enabled, `auto_suppl_thresh` must be smaller than - * `manual_suppl_thresh`. + * @note `auto_suppl_thresh` must be smaller than `manual_suppl_thresh`. + * @note Use i2s_channel_enable_tx_fifo_sync() to activate/deactivate after configuration. * @note Only allowed when channel state is REGISTERED or READY (before channel starts). * * @param[in] tx_handle I2S TX channel handle @@ -368,24 +354,21 @@ typedef struct { esp_err_t i2s_channel_config_tx_fifo_sync(i2s_chan_handle_t tx_handle, const i2s_tx_fifo_sync_config_t *config); /** - * @brief Register I2S peripheral interrupt event callbacks + * @brief Enable or disable TX FIFO synchronization * - * @note `on_tx_sync` is invoked when `tx_cnt_diff` exceeds `manual_suppl_thresh`. - * @note Only allowed when channel state is REGISTERED or READY (before channel starts). - * @note When CONFIG_I2S_ISR_IRAM_SAFE is enabled, the callback and user_data must reside in internal RAM. - * @note Set `callbacks->on_tx_sync` to NULL to deregister the callback and uninstall the TX sync interrupt. + * @note When enabled, both automatic hardware data supplementation and manual interrupt + * are activated simultaneously. When disabled, both are deactivated. + * @note Must be called after i2s_channel_config_tx_fifo_sync(). * - * @param[in] tx_handle I2S TX channel handle - * @param[in] callbacks Group of I2S peripheral interrupt callbacks - * @param[in] user_data User context passed to callback + * @param[in] tx_handle I2S TX channel handle + * @param[in] enable true to enable, false to disable * @return * - ESP_OK Success * - ESP_ERR_INVALID_ARG Invalid handle or channel is not TX - * - ESP_ERR_INVALID_STATE Channel is already running - * - ESP_ERR_NO_MEM Failed to allocate interrupt + * - ESP_ERR_INVALID_STATE FIFO sync not configured */ -esp_err_t i2s_channel_register_intr_event_callback(i2s_chan_handle_t tx_handle, - const i2s_intr_event_callbacks_t *callbacks, void *user_data); +esp_err_t i2s_channel_enable_tx_fifo_sync(i2s_chan_handle_t tx_handle, bool enable); + #endif // SOC_I2S_SUPPORTS_TX_FIFO_SYNC #endif // SOC_I2S_SUPPORTS_TX_SYNC_CNT diff --git a/components/esp_driver_i2s/include/driver/i2s_types.h b/components/esp_driver_i2s/include/driver/i2s_types.h index 663a160d727..31893a1e51a 100644 --- a/components/esp_driver_i2s/include/driver/i2s_types.h +++ b/components/esp_driver_i2s/include/driver/i2s_types.h @@ -122,11 +122,11 @@ typedef struct { * * @param[in] handle I2S TX channel handle * @param[in] event TX synchronization event data - * @param[in] user_ctx User context registered via `i2s_channel_register_intr_event_callback()` + * @param[in] user_ctx User context registered via `i2s_channel_register_event_callback()` * * @return Whether a high priority task has been waken up by this callback function */ -typedef bool (*i2s_sync_callback_t)(i2s_chan_handle_t handle, const i2s_sync_event_data_t *event, void *user_ctx); +typedef bool (*i2s_tx_fifo_sync_callback_t)(i2s_chan_handle_t handle, const i2s_sync_event_data_t *event, void *user_ctx); #endif // SOC_I2S_SUPPORTS_TX_FIFO_SYNC /** diff --git a/components/esp_driver_i2s/linker.lf b/components/esp_driver_i2s/linker.lf index f241c777540..1db8b498859 100644 --- a/components/esp_driver_i2s/linker.lf +++ b/components/esp_driver_i2s/linker.lf @@ -8,4 +8,4 @@ entries: if SOC_I2S_SUPPORTS_TX_SYNC_CNT = y: i2s_common: i2s_channel_get_sync_count (noflash) if SOC_I2S_SUPPORTS_TX_FIFO_SYNC = y: - i2s_common: i2s_channel_get_sync_diff_count (noflash) + i2s_common: i2s_channel_enable_tx_fifo_sync (noflash) diff --git a/components/esp_driver_i2s/test_apps/i2s/main/test_i2s.c b/components/esp_driver_i2s/test_apps/i2s/main/test_i2s.c index 356aeace682..900500bf464 100644 --- a/components/esp_driver_i2s/test_apps/i2s/main/test_i2s.c +++ b/components/esp_driver_i2s/test_apps/i2s/main/test_i2s.c @@ -1348,21 +1348,18 @@ TEST_CASE("I2S TX sync callback is triggered by GPTimer ETM alarm", "[i2s][etm]" .ideal_cnt = I2S_TX_SYNC_TEST_IDEAL_CNT, .suppl_mode = I2S_TX_FIFO_SYNC_SUPPL_MODE_LAST_DATA, }; - i2s_intr_event_callbacks_t intr_cbs = { - .on_tx_sync = i2s_tx_sync_test_callback, + i2s_event_callbacks_t cbs = { + .on_tx_sync_evt = i2s_tx_sync_test_callback, }; TEST_ESP_OK(i2s_channel_config_tx_fifo_sync(tx_handle, &sync_cfg)); - TEST_ESP_OK(i2s_channel_register_intr_event_callback(tx_handle, &intr_cbs, &cb_ctx)); + TEST_ESP_OK(i2s_channel_register_event_callback(tx_handle, &cbs, &cb_ctx)); + TEST_ESP_OK(i2s_channel_enable_tx_fifo_sync(tx_handle, true)); - uint32_t bclk_count = 0; - uint32_t fifo_count = 0; - int32_t diff_count = 0; - TEST_ESP_OK(i2s_channel_get_sync_count(tx_handle, &bclk_count, &fifo_count, true)); - TEST_ESP_OK(i2s_channel_get_sync_count(tx_handle, NULL, NULL, false)); - TEST_ESP_OK(i2s_channel_get_sync_diff_count(tx_handle, &diff_count, true)); - TEST_ESP_OK(i2s_channel_get_sync_diff_count(tx_handle, NULL, false)); + i2s_sync_count_t sync_count = {}; + TEST_ESP_OK(i2s_channel_get_sync_count(tx_handle, &sync_count, true)); + TEST_ESP_OK(i2s_channel_get_sync_count(tx_handle, &sync_count, false)); printf("TX sync API before start: diff=%"PRId32", fifo=%"PRIu32", bclk=%"PRIu32"\n", - diff_count, fifo_count, bclk_count); + sync_count.diff_count, sync_count.fifo_count, sync_count.bclk_count); i2s_etm_task_config_t i2s_task_cfg = { .task_type = I2S_ETM_TASK_SYNC_FIFO, @@ -1399,8 +1396,9 @@ TEST_CASE("I2S TX sync callback is triggered by GPTimer ETM alarm", "[i2s][etm]" TEST_ESP_OK(gptimer_stop(timer)); TEST_ESP_OK(i2s_channel_disable(tx_handle)); - intr_cbs.on_tx_sync = NULL; - TEST_ESP_OK(i2s_channel_register_intr_event_callback(tx_handle, &intr_cbs, NULL)); + TEST_ESP_OK(i2s_channel_enable_tx_fifo_sync(tx_handle, false)); + cbs.on_tx_sync_evt = NULL; + TEST_ESP_OK(i2s_channel_register_event_callback(tx_handle, &cbs, NULL)); TEST_ESP_OK(gptimer_disable(timer)); TEST_ESP_OK(esp_etm_channel_disable(etm_channel)); diff --git a/docs/en/api-reference/peripherals/i2s.rst b/docs/en/api-reference/peripherals/i2s.rst index 8e5733c4752..4c4669643fd 100644 --- a/docs/en/api-reference/peripherals/i2s.rst +++ b/docs/en/api-reference/peripherals/i2s.rst @@ -315,7 +315,9 @@ To satisfy the high quality audio requirement, following advanced APIs are provi .. only:: SOC_I2S_SUPPORTS_TX_SYNC_CNT - - :cpp:func:`i2s_channel_get_sync_count`: Read the TX BCLK/FIFO synchronization counters. This API can also actively clear them through the ``reset`` argument. + - :cpp:func:`i2s_channel_get_sync_count`: Read the TX synchronization counters through + :cpp:type:`i2s_sync_count_t`. When TX FIFO synchronization is supported, ``diff_count`` is also returned. + This API can also actively clear the counters through the ``reset`` argument. .. only:: SOC_I2S_SUPPORTS_TX_FIFO_SYNC @@ -326,12 +328,15 @@ To satisfy the high quality audio requirement, following advanced APIs are provi TX FIFO synchronization related APIs include: - - :cpp:func:`i2s_channel_get_sync_diff_count`: Read the TX FIFO synchronization difference counter. The value is signed and means ``I2S_TX_FIFO_CNT - I2S_TX_FIFO_IDEAL_CNT``. + - :cpp:func:`i2s_channel_get_sync_count`: Read the TX synchronization counters through :cpp:type:`i2s_sync_count_t`. + When TX FIFO synchronization is supported, ``diff_count`` is also returned as ``I2S_TX_FIFO_CNT - I2S_TX_FIFO_IDEAL_CNT``. - :cpp:func:`i2s_channel_config_tx_fifo_sync`: Configure the expected count, automatic supplement threshold, - manual supplement threshold, and hardware supplement mode. After automatic hardware supplementation is - enabled, hardware automatically supplements or deletes data according to ``diff_count`` so that the actual - count approaches ``ideal_cnt``. - - :cpp:func:`i2s_channel_register_intr_event_callback`: Register the manual supplement threshold interrupt callback. When + manual supplement threshold, and hardware supplement mode. + - :cpp:func:`i2s_channel_enable_tx_fifo_sync`: Enable or disable TX FIFO synchronization. When enabled, + both automatic hardware data supplementation and manual interrupt are activated simultaneously. + When disabled, both are deactivated. This API must be called after + :cpp:func:`i2s_channel_config_tx_fifo_sync`. + - :cpp:func:`i2s_channel_register_event_callback`: Register the manual supplement threshold interrupt callback. When ``diff_count`` exceeds the manual supplement threshold, the driver calls this callback in the ISR and provides ``diff_count`` through :cpp:type:`i2s_sync_event_data_t`. @@ -340,18 +345,15 @@ To satisfy the high quality audio requirement, following advanced APIs are provi 1. Create and initialize an I2S TX channel. 2. Call :cpp:func:`i2s_channel_config_tx_fifo_sync` to configure :cpp:type:`i2s_tx_fifo_sync_config_t`. ``ideal_cnt`` is the expected number of transmitted data units at each ETM synchronization check. ``auto_suppl_thresh`` is - the automatic hardware supplement threshold: set it to ``0`` to disable automatic hardware supplementation, or - set it to a value greater than ``0`` and smaller than ``manual_suppl_thresh`` to enable automatic hardware - supplementation. ``manual_suppl_thresh`` is the threshold for triggering the callback for manual handling. Once - enabled, if the difference exceeds the automatic supplement threshold but has not reached the manual supplement - threshold, hardware automatically supplements or deletes the corresponding amount of data to synchronize with - ``ideal_cnt``. - 3. To handle severe out-of-sync conditions, call :cpp:func:`i2s_channel_register_intr_event_callback` to register a callback. - After the callback is registered, the driver enables the TX synchronization interrupt. If the difference exceeds - the manual supplement threshold, the driver calls this callback in the ISR and provides ``diff_count`` through - :cpp:type:`i2s_sync_event_data_t`. - 4. Call :cpp:func:`i2s_new_etm_task` to create the ``I2S_ETM_TASK_SYNC_FIFO`` task, and connect an external ETM event to this task. - 5. Enable the ETM channel and I2S TX channel, so that ETM events periodically trigger synchronization checks. + the automatic hardware supplement threshold and must be smaller than ``manual_suppl_thresh``. + ``manual_suppl_thresh`` is the threshold for triggering the callback for manual handling. If the difference + exceeds the automatic supplement threshold but has not reached the manual supplement threshold, hardware + automatically supplements or deletes the corresponding amount of data to synchronize with ``ideal_cnt``. + 3. To handle severe out-of-sync conditions, call :cpp:func:`i2s_channel_register_event_callback` to register a callback. + 4. Call :cpp:func:`i2s_channel_enable_tx_fifo_sync` with ``enable`` set to ``true`` to activate both automatic + hardware supplementation and manual interrupt simultaneously. + 5. Call :cpp:func:`i2s_new_etm_task` to create the ``I2S_ETM_TASK_SYNC_FIFO`` task, and connect an external ETM event to this task. + 6. Enable the ETM channel and I2S TX channel, so that ETM events periodically trigger synchronization checks. The following example shows how to use a GPTimer alarm event to trigger the I2S TX FIFO synchronization check, and get ``diff_count`` in the manual supplement threshold interrupt: @@ -372,7 +374,7 @@ To satisfy the high quality audio requirement, following advanced APIs are provi const i2s_sync_event_data_t *event, void *user_ctx) { - // event->diff_count = I2S_TX_FIFO_CNT - I2S_TX_FIFO_IDEAL_CNT + // Applications can use event->diff_count to adjust the data source, choose a compensation policy, or report it to upper layers. return false; } @@ -382,11 +384,12 @@ To satisfy the high quality audio requirement, following advanced APIs are provi .auto_suppl_thresh = 32, .suppl_mode = I2S_TX_FIFO_SYNC_SUPPL_MODE_LAST_DATA, }; - i2s_intr_event_callbacks_t intr_cbs = { - .on_tx_sync = i2s_tx_sync_callback, + i2s_event_callbacks_t cbs = { + .on_tx_sync_evt = i2s_tx_sync_callback, }; i2s_channel_config_tx_fifo_sync(tx_handle, &sync_cfg); - i2s_channel_register_intr_event_callback(tx_handle, &intr_cbs, NULL); + i2s_channel_register_event_callback(tx_handle, &cbs, NULL); + i2s_channel_enable_tx_fifo_sync(tx_handle, true); i2s_etm_task_config_t i2s_task_cfg = { .task_type = I2S_ETM_TASK_SYNC_FIFO, diff --git a/docs/zh_CN/api-reference/peripherals/i2s.rst b/docs/zh_CN/api-reference/peripherals/i2s.rst index b479acdb345..06534cbc665 100644 --- a/docs/zh_CN/api-reference/peripherals/i2s.rst +++ b/docs/zh_CN/api-reference/peripherals/i2s.rst @@ -315,7 +315,9 @@ I2S 的数据传输(包括数据发送和接收)由 DMA 实现。在传输 .. only:: SOC_I2S_SUPPORTS_TX_SYNC_CNT - - :cpp:func:`i2s_channel_get_sync_count`:用于读取 TX BCLK/FIFO 同步计数器。该 API 也可通过 ``reset`` 参数主动清零计数器。 + - :cpp:func:`i2s_channel_get_sync_count`:通过 :cpp:type:`i2s_sync_count_t` 读取 + TX 同步计数器。当支持 TX FIFO 同步时,也会返回 ``diff_count``。 + 该 API 也可通过 ``reset`` 参数主动清零计数器。 .. only:: SOC_I2S_SUPPORTS_TX_FIFO_SYNC @@ -326,25 +328,32 @@ I2S 的数据传输(包括数据发送和接收)由 DMA 实现。在传输 TX FIFO 同步相关 API 包括: - - :cpp:func:`i2s_channel_get_sync_diff_count`:读取 TX FIFO 同步差值计数器。该值为有符号数,含义为 ``I2S_TX_FIFO_CNT - I2S_TX_FIFO_IDEAL_CNT``。 - - :cpp:func:`i2s_channel_config_tx_fifo_sync`:配置期望计数、自动补偿阈值、手动补偿阈值以及硬件补偿方式。 - 启用硬件自动补偿后,硬件会根据 ``diff_count`` 自动补充或删除数据,使实际计数靠近 ``ideal_cnt``。 - - :cpp:func:`i2s_channel_register_intr_event_callback`:注册手动补偿阈值中断回调。当 ``diff_count`` 超过手动补偿阈值时, - 驱动会在 ISR 中调用该回调,并通过 :cpp:type:`i2s_sync_event_data_t` 提供 ``diff_count``。 + - :cpp:func:`i2s_channel_get_sync_count`:通过 :cpp:type:`i2s_sync_count_t` 读取 TX 同步计数器。 + 当支持 TX FIFO 同步时,也会返回 ``diff_count``,其含义为 ``I2S_TX_FIFO_CNT - I2S_TX_FIFO_IDEAL_CNT``。 + - :cpp:func:`i2s_channel_config_tx_fifo_sync`:配置期望计数、自动补偿阈值、 + 手动补偿阈值以及硬件补偿方式。 + - :cpp:func:`i2s_channel_enable_tx_fifo_sync`:使能或关闭 TX FIFO 同步功能。使能后, + 硬件自动补偿和手动补偿中断同时激活。 + 关闭后,两者同时停用。该 API 必须在 + :cpp:func:`i2s_channel_config_tx_fifo_sync` 之后调用。 + - :cpp:func:`i2s_channel_register_event_callback`:注册手动补偿阈值中断回调。当 + ``diff_count`` 超过手动补偿阈值时,驱动会在 ISR 中调用该回调,并通过 + :cpp:type:`i2s_sync_event_data_t` 提供 ``diff_count``。 使用该功能的一般步骤如下: 1. 创建并初始化 I2S TX 通道。 - 2. 调用 :cpp:func:`i2s_channel_config_tx_fifo_sync` 配置 :cpp:type:`i2s_tx_fifo_sync_config_t`。其中 ``ideal_cnt`` - 为每次 ETM 同步检查时期望发送的数据个数;``auto_suppl_thresh`` 为硬件自动补偿阈值,设置为 ``0`` 表示 - 关闭硬件自动补偿,设置为大于 ``0`` 且小于 ``manual_suppl_thresh`` 表示开启硬件自动补偿; - ``manual_suppl_thresh`` 为触发回调并交由软件手动处理的阈值。开启后,如果偏差超过自动补偿阈值但尚未达到 - 手动补偿阈值,硬件会自动补充或删除相应数量的数据,以实现与 ``ideal_cnt`` 同步。 - 3. 如需处理严重不同步场景,调用 :cpp:func:`i2s_channel_register_intr_event_callback` 注册回调。注册回调后,驱动会使能 - TX 同步中断;如果偏差超过手动补偿阈值,驱动会在 ISR 中调用该回调,并通过 - :cpp:type:`i2s_sync_event_data_t` 提供 ``diff_count``。 - 4. 调用 :cpp:func:`i2s_new_etm_task` 创建 ``I2S_ETM_TASK_SYNC_FIFO`` 任务,并将外部 ETM 事件连接到该任务。 - 5. 使能 ETM 通道和 I2S TX 通道,由 ETM 事件周期性触发同步检查。 + 2. 调用 :cpp:func:`i2s_channel_config_tx_fifo_sync` 配置 :cpp:type:`i2s_tx_fifo_sync_config_t`。``ideal_cnt`` + 为每次 ETM 同步检查时期望发送的数据个数;``auto_suppl_thresh`` 为 + 硬件自动补偿阈值,必须小于 ``manual_suppl_thresh``。 + ``manual_suppl_thresh`` 为触发回调并交由软件手动处理的阈值。如果偏差 + 超过自动补偿阈值但尚未达到手动补偿阈值,硬件会自动补充或删除相应数量的数据, + 以实现与 ``ideal_cnt`` 同步。 + 3. 如需处理严重不同步场景,调用 :cpp:func:`i2s_channel_register_event_callback` 注册回调。 + 4. 调用 :cpp:func:`i2s_channel_enable_tx_fifo_sync` 并将 ``enable`` 设为 ``true``,同时激活硬件自动补偿 + 和手动补偿中断。 + 5. 调用 :cpp:func:`i2s_new_etm_task` 创建 ``I2S_ETM_TASK_SYNC_FIFO`` 任务,并将外部 ETM 事件连接到该任务。 + 6. 使能 ETM 通道和 I2S TX 通道,由 ETM 事件周期性触发同步检查。 以下示例展示了如何使用 GPTimer alarm event 触发 I2S TX FIFO 同步检查,并在手动补偿阈值中断中获取 ``diff_count``: @@ -365,7 +374,7 @@ I2S 的数据传输(包括数据发送和接收)由 DMA 实现。在传输 const i2s_sync_event_data_t *event, void *user_ctx) { - // event->diff_count = I2S_TX_FIFO_CNT - I2S_TX_FIFO_IDEAL_CNT + // 应用可根据 event->diff_count 决定是否调整数据源、补偿策略或上报给上层处理 return false; } @@ -375,11 +384,12 @@ I2S 的数据传输(包括数据发送和接收)由 DMA 实现。在传输 .auto_suppl_thresh = 32, .suppl_mode = I2S_TX_FIFO_SYNC_SUPPL_MODE_LAST_DATA, }; - i2s_intr_event_callbacks_t intr_cbs = { - .on_tx_sync = i2s_tx_sync_callback, + i2s_event_callbacks_t cbs = { + .on_tx_sync_evt = i2s_tx_sync_callback, }; i2s_channel_config_tx_fifo_sync(tx_handle, &sync_cfg); - i2s_channel_register_intr_event_callback(tx_handle, &intr_cbs, NULL); + i2s_channel_register_event_callback(tx_handle, &cbs, NULL); + i2s_channel_enable_tx_fifo_sync(tx_handle, true); i2s_etm_task_config_t i2s_task_cfg = { .task_type = I2S_ETM_TASK_SYNC_FIFO, From a907115eba7141849aea92bd0731297eae906d59 Mon Sep 17 00:00:00 2001 From: Chen Chen Date: Wed, 24 Jun 2026 15:09:18 +0800 Subject: [PATCH 4/4] change(i2s): allow config tx sync params while tx channel is running --- components/esp_driver_i2s/i2s_common.c | 75 ++++++++++++------- components/esp_driver_i2s/i2s_private.h | 2 + .../include/driver/i2s_common.h | 13 ++-- .../test_apps/i2s/main/test_app_main.c | 2 +- .../test_apps/i2s/main/test_i2s.c | 57 ++++++++++++-- .../i2s_multi_dev/main/test_i2s_multi_dev.c | 6 ++ docs/en/api-reference/peripherals/i2s.rst | 18 +++-- docs/zh_CN/api-reference/peripherals/i2s.rst | 19 +++-- 8 files changed, 138 insertions(+), 54 deletions(-) diff --git a/components/esp_driver_i2s/i2s_common.c b/components/esp_driver_i2s/i2s_common.c index 9f67e48386c..ae1be9f3de9 100644 --- a/components/esp_driver_i2s/i2s_common.c +++ b/components/esp_driver_i2s/i2s_common.c @@ -438,20 +438,26 @@ esp_err_t i2s_channel_register_event_callback(i2s_chan_handle_t handle, const i2 { I2S_NULL_POINTER_CHECK(TAG, handle); I2S_NULL_POINTER_CHECK(TAG, callbacks); - esp_err_t ret = ESP_OK; + + /* DMA event callbacks are dispatched from the DMA ISR, only available on the DMA memory data path */ + bool dma_cb_supported = I2S_CHANNEL_USES_DMA(handle); bool has_dma_event_cb = callbacks->on_recv || callbacks->on_recv_q_ovf || callbacks->on_sent || callbacks->on_send_q_ovf; - bool update_dma_cb = I2S_CHANNEL_USES_DMA(handle); -#if SOC_I2S_SUPPORTS_TX_FIFO_SYNC - i2s_tx_fifo_sync_callback_t sync_cb = callbacks->on_tx_sync_evt; - bool update_sync_cb = sync_cb || (handle->dir == I2S_DIR_TX && handle->on_tx_sync); -#endif - ESP_RETURN_ON_FALSE(!has_dma_event_cb || update_dma_cb, - ESP_ERR_NOT_SUPPORTED, TAG, + ESP_RETURN_ON_FALSE(!has_dma_event_cb || dma_cb_supported, ESP_ERR_NOT_SUPPORTED, TAG, "DMA event callbacks require the DMA memory data path on this channel"); #if SOC_I2S_SUPPORTS_TX_FIFO_SYNC - ESP_RETURN_ON_FALSE(!update_sync_cb || handle->dir == I2S_DIR_TX, ESP_ERR_INVALID_ARG, TAG, "channel is not TX"); + /* TX FIFO sync callback is dispatched from the I2S peripheral ISR, available on any TX channel regardless of the data path */ + bool sync_cb_supported = (handle->dir == I2S_DIR_TX); + ESP_RETURN_ON_FALSE(!callbacks->on_tx_sync_evt || sync_cb_supported, ESP_ERR_NOT_SUPPORTED, TAG, + "TX FIFO sync callback requires a TX channel"); + bool sync_only_update = sync_cb_supported && !has_dma_event_cb; + bool cb_supported = dma_cb_supported || sync_cb_supported; +#else + bool sync_only_update = false; + bool cb_supported = dma_cb_supported; #endif + ESP_RETURN_ON_FALSE(cb_supported, ESP_ERR_NOT_SUPPORTED, TAG, + "event callbacks are not supported on this channel"); #if CONFIG_I2S_ISR_IRAM_SAFE if (callbacks->on_recv) { ESP_RETURN_ON_FALSE(esp_ptr_in_iram(callbacks->on_recv), ESP_ERR_INVALID_ARG, TAG, "on_recv callback not in IRAM"); @@ -466,8 +472,8 @@ esp_err_t i2s_channel_register_event_callback(i2s_chan_handle_t handle, const i2 ESP_RETURN_ON_FALSE(esp_ptr_in_iram(callbacks->on_send_q_ovf), ESP_ERR_INVALID_ARG, TAG, "on_send_q_ovf callback not in IRAM"); } #if SOC_I2S_SUPPORTS_TX_FIFO_SYNC - if (sync_cb) { - ESP_RETURN_ON_FALSE(esp_ptr_in_iram(sync_cb), ESP_ERR_INVALID_ARG, TAG, "sync callback not in IRAM"); + if (callbacks->on_tx_sync_evt) { + ESP_RETURN_ON_FALSE(esp_ptr_in_iram(callbacks->on_tx_sync_evt), ESP_ERR_INVALID_ARG, TAG, "sync callback not in IRAM"); } #endif if (user_data) { @@ -476,9 +482,13 @@ esp_err_t i2s_channel_register_event_callback(i2s_chan_handle_t handle, const i2 #endif + esp_err_t ret = ESP_OK; xSemaphoreTake(handle->mutex, portMAX_DELAY); - ESP_GOTO_ON_FALSE(handle->state < I2S_CHAN_STATE_RUNNING, ESP_ERR_INVALID_STATE, err, TAG, "invalid state, I2S has enabled"); - if (update_dma_cb) { + bool update_dma_cbs = dma_cb_supported && !(sync_only_update && handle->state == I2S_CHAN_STATE_RUNNING); + ESP_GOTO_ON_FALSE(!update_dma_cbs || handle->state < I2S_CHAN_STATE_RUNNING, + ESP_ERR_INVALID_STATE, err, TAG, + "DMA event callbacks can't be changed while the channel is running"); + if (update_dma_cbs) { handle->callbacks.on_recv = callbacks->on_recv; handle->callbacks.on_recv_q_ovf = callbacks->on_recv_q_ovf; handle->callbacks.on_sent = callbacks->on_sent; @@ -486,8 +496,8 @@ esp_err_t i2s_channel_register_event_callback(i2s_chan_handle_t handle, const i2 handle->user_data = user_data; } #if SOC_I2S_SUPPORTS_TX_FIFO_SYNC - if (update_sync_cb) { - s_i2s_channel_update_tx_sync_callback(handle, sync_cb, user_data); + if (sync_cb_supported) { + s_i2s_channel_update_tx_sync_callback(handle, callbacks->on_tx_sync_evt, user_data); } #endif err: @@ -1196,7 +1206,12 @@ esp_err_t i2s_del_channel(i2s_chan_handle_t handle) #if SOC_I2S_SUPPORTS_TX_FIFO_SYNC if (handle->i2s_intr) { + portENTER_CRITICAL(&g_i2s.spinlock); + i2s_ll_tx_enable_hw_fifo_sync(handle->controller->hal.dev, false); i2s_ll_enable_interrupt(handle->controller->hal.dev, I2S_LL_TX_SYNC_INT_EVENT, false); + i2s_ll_tx_update(handle->controller->hal.dev); + handle->tx_fifo_sync_enabled = false; + portEXIT_CRITICAL(&g_i2s.spinlock); esp_intr_disable(handle->i2s_intr); esp_intr_free(handle->i2s_intr); handle->i2s_intr = NULL; @@ -1740,16 +1755,11 @@ static void s_i2s_channel_update_tx_sync_callback(i2s_chan_handle_t tx_handle, i2s_tx_fifo_sync_callback_t cb, void *user_data) { - i2s_dev_t *hw = tx_handle->controller->hal.dev; - portENTER_CRITICAL(&g_i2s.spinlock); if (cb) { tx_handle->on_tx_sync = cb; tx_handle->sync_user_data = user_data; - i2s_ll_clear_interrupt_status(hw, I2S_LL_TX_SYNC_INT_EVENT); - i2s_ll_enable_interrupt(hw, I2S_LL_TX_SYNC_INT_EVENT, true); } else { - i2s_ll_enable_interrupt(hw, I2S_LL_TX_SYNC_INT_EVENT, false); tx_handle->on_tx_sync = NULL; tx_handle->sync_user_data = NULL; } @@ -1784,11 +1794,6 @@ esp_err_t i2s_init_i2s_intr(i2s_chan_handle_t handle) handle->i2s_intr = NULL; return ret; } - if (handle->on_tx_sync) { - i2s_ll_clear_interrupt_status(hw, I2S_LL_TX_SYNC_INT_EVENT); - i2s_ll_enable_interrupt(hw, I2S_LL_TX_SYNC_INT_EVENT, true); - } - return ESP_OK; } @@ -1803,8 +1808,13 @@ esp_err_t i2s_channel_config_tx_fifo_sync(i2s_chan_handle_t tx_handle, const i2s i2s_dev_t *hw = tx_handle->controller->hal.dev; xSemaphoreTake(tx_handle->mutex, portMAX_DELAY); - ESP_GOTO_ON_FALSE(tx_handle->state < I2S_CHAN_STATE_RUNNING, ESP_ERR_INVALID_STATE, err, TAG, - "invalid state, I2S has enabled"); + portENTER_CRITICAL(&g_i2s.spinlock); + if (tx_handle->tx_fifo_sync_enabled) { + portEXIT_CRITICAL(&g_i2s.spinlock); + ESP_LOGE(TAG, "TX FIFO sync is enabled"); + ret = ESP_ERR_INVALID_STATE; + goto err; + } i2s_ll_tx_set_etm_sync_ideal_cnt(hw, config->ideal_cnt); i2s_ll_tx_set_fifo_sync_diff_counter_manual_threshold(hw, config->manual_suppl_thresh); i2s_ll_tx_set_fifo_sync_diff_counter_auto_threshold(hw, config->auto_suppl_thresh); @@ -1814,6 +1824,8 @@ esp_err_t i2s_channel_config_tx_fifo_sync(i2s_chan_handle_t tx_handle, const i2s } i2s_ll_tx_enable_hw_fifo_sync(hw, false); i2s_ll_tx_update(hw); + tx_handle->tx_fifo_sync_configured = true; + portEXIT_CRITICAL(&g_i2s.spinlock); err: xSemaphoreGive(tx_handle->mutex); return ret; @@ -1822,18 +1834,23 @@ err: esp_err_t i2s_channel_enable_tx_fifo_sync(i2s_chan_handle_t tx_handle, bool enable) { ESP_RETURN_ON_ERROR(i2s_check_tx_handle(tx_handle), TAG, "invalid TX handle"); - ESP_RETURN_ON_FALSE(tx_handle->i2s_intr, ESP_ERR_INVALID_STATE, TAG, "TX FIFO sync not configured"); + ESP_RETURN_ON_FALSE(tx_handle->tx_fifo_sync_configured, ESP_ERR_INVALID_STATE, TAG, "TX FIFO sync not configured"); + ESP_RETURN_ON_FALSE(tx_handle->i2s_intr, ESP_ERR_INVALID_STATE, TAG, "TX FIFO sync interrupt not initialized"); i2s_dev_t *hw = tx_handle->controller->hal.dev; portENTER_CRITICAL(&g_i2s.spinlock); i2s_ll_tx_enable_hw_fifo_sync(hw, enable); - if (enable && tx_handle->on_tx_sync) { + if (enable) { + i2s_ll_tx_reset_fifo_sync_counter(hw); + i2s_ll_tx_reset_bclk_sync_counter(hw); + i2s_ll_tx_reset_fifo_sync_diff_counter(hw); i2s_ll_clear_interrupt_status(hw, I2S_LL_TX_SYNC_INT_EVENT); i2s_ll_enable_interrupt(hw, I2S_LL_TX_SYNC_INT_EVENT, true); } else { i2s_ll_enable_interrupt(hw, I2S_LL_TX_SYNC_INT_EVENT, false); } i2s_ll_tx_update(hw); + tx_handle->tx_fifo_sync_enabled = enable; portEXIT_CRITICAL(&g_i2s.spinlock); return ESP_OK; } diff --git a/components/esp_driver_i2s/i2s_private.h b/components/esp_driver_i2s/i2s_private.h index 115c2ebb06b..1bc39580460 100644 --- a/components/esp_driver_i2s/i2s_private.h +++ b/components/esp_driver_i2s/i2s_private.h @@ -187,6 +187,8 @@ struct i2s_channel_obj_t { i2s_tx_fifo_sync_callback_t on_tx_sync; /*!< TX FIFO sync manual supplement threshold callback */ void *sync_user_data; /*!< User data for TX FIFO sync callback */ intr_handle_t i2s_intr; /*!< I2S peripheral interrupt handle */ + bool tx_fifo_sync_configured; /*!< Whether TX FIFO sync has been configured */ + bool tx_fifo_sync_enabled; /*!< Whether TX FIFO sync is enabled */ #endif void (*start)(i2s_chan_handle_t); /*!< start tx/rx channel */ void (*stop)(i2s_chan_handle_t); /*!< stop tx/rx channel */ diff --git a/components/esp_driver_i2s/include/driver/i2s_common.h b/components/esp_driver_i2s/include/driver/i2s_common.h index ef4ce0abc0f..6523c3c2b77 100644 --- a/components/esp_driver_i2s/include/driver/i2s_common.h +++ b/components/esp_driver_i2s/include/driver/i2s_common.h @@ -235,7 +235,10 @@ esp_err_t i2s_channel_read(i2s_chan_handle_t handle, void *dest, size_t size, si /** * @brief Set event callbacks for I2S channel * - * @note Only allowed to be called when the channel state is REGISTERED / READY, (i.e., before channel starts) + * @note DMA event callbacks can only be registered or deregistered before the channel is enabled. + * @note The TX FIFO sync callback can be registered, updated, or deregistered while the channel is running. + * @note Registering the TX FIFO sync callback only updates the handler; the TX sync interrupt is controlled by + * i2s_channel_enable_tx_fifo_sync(). * @note User can deregister a previously registered callback by calling this function and setting the callback member in the `callbacks` structure to NULL. * @note When CONFIG_I2S_ISR_IRAM_SAFE is enabled, the callback itself and functions called by it should be placed in IRAM. * The variables used in the function should be in the SRAM as well. The `user_data` should also reside in SRAM or internal RAM as well. @@ -246,7 +249,7 @@ esp_err_t i2s_channel_read(i2s_chan_handle_t handle, void *dest, size_t size, si * @return * - ESP_OK Set event callbacks successfully * - ESP_ERR_INVALID_ARG Set event callbacks failed because of invalid argument - * - ESP_ERR_INVALID_STATE Set event callbacks failed because the current channel state is not REGISTERED or READY + * - ESP_ERR_INVALID_STATE Set event callbacks failed because DMA event callbacks are changed while the channel is running * - ESP_ERR_NOT_SUPPORTED Set event callbacks failed because the requested event is not supported by this channel */ esp_err_t i2s_channel_register_event_callback(i2s_chan_handle_t handle, const i2s_event_callbacks_t *callbacks, void *user_data); @@ -342,14 +345,14 @@ typedef struct { * * @note `auto_suppl_thresh` must be smaller than `manual_suppl_thresh`. * @note Use i2s_channel_enable_tx_fifo_sync() to activate/deactivate after configuration. - * @note Only allowed when channel state is REGISTERED or READY (before channel starts). + * @note Can be called while the channel is running, but only when TX FIFO synchronization is disabled. * * @param[in] tx_handle I2S TX channel handle * @param[in] config TX FIFO synchronization configuration * @return * - ESP_OK Success * - ESP_ERR_INVALID_ARG Invalid handle, channel is not TX, or invalid configuration - * - ESP_ERR_INVALID_STATE Channel is already running + * - ESP_ERR_INVALID_STATE TX FIFO synchronization is enabled */ esp_err_t i2s_channel_config_tx_fifo_sync(i2s_chan_handle_t tx_handle, const i2s_tx_fifo_sync_config_t *config); @@ -365,7 +368,7 @@ esp_err_t i2s_channel_config_tx_fifo_sync(i2s_chan_handle_t tx_handle, const i2s * @return * - ESP_OK Success * - ESP_ERR_INVALID_ARG Invalid handle or channel is not TX - * - ESP_ERR_INVALID_STATE FIFO sync not configured + * - ESP_ERR_INVALID_STATE FIFO sync not configured, or TX channel is not initialized */ esp_err_t i2s_channel_enable_tx_fifo_sync(i2s_chan_handle_t tx_handle, bool enable); diff --git a/components/esp_driver_i2s/test_apps/i2s/main/test_app_main.c b/components/esp_driver_i2s/test_apps/i2s/main/test_app_main.c index c05ad2830a4..a94c86b04ba 100644 --- a/components/esp_driver_i2s/test_apps/i2s/main/test_app_main.c +++ b/components/esp_driver_i2s/test_apps/i2s/main/test_app_main.c @@ -9,7 +9,7 @@ #include "esp_heap_caps.h" // Some resources are lazy allocated in I2S driver, the threshold is left for that case -#define TEST_MEMORY_LEAK_THRESHOLD (-360) +#define TEST_MEMORY_LEAK_THRESHOLD (-450) static size_t before_free_8bit; static size_t before_free_32bit; diff --git a/components/esp_driver_i2s/test_apps/i2s/main/test_i2s.c b/components/esp_driver_i2s/test_apps/i2s/main/test_i2s.c index 900500bf464..b2eab240d58 100644 --- a/components/esp_driver_i2s/test_apps/i2s/main/test_i2s.c +++ b/components/esp_driver_i2s/test_apps/i2s/main/test_i2s.c @@ -1306,6 +1306,41 @@ static IRAM_ATTR bool i2s_tx_sync_test_callback(i2s_chan_handle_t handle, const return need_yield == pdTRUE; } +TEST_CASE("I2S TX sync callback can be registered while running", "[i2s]") +{ + i2s_chan_handle_t tx_handle = NULL; + + 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(48000), + .slot_cfg = I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_STEREO), + .gpio_cfg = I2S_TEST_MASTER_DEFAULT_PIN, + }; + std_cfg.gpio_cfg.mclk = -1; +#if CONFIG_IDF_TARGET_ESP32S31 + std_cfg.clk_cfg.clk_src = I2S_CLK_SRC_APLL; +#endif + + TEST_ESP_OK(i2s_new_channel(&chan_cfg, &tx_handle, NULL)); + TEST_ESP_OK(i2s_channel_init_std_mode(tx_handle, &std_cfg)); + TEST_ESP_OK(i2s_channel_enable(tx_handle)); + + i2s_event_callbacks_t dma_cbs = { + .on_sent = i2s_tx_on_sent_callback, + }; + TEST_ASSERT_EQUAL(ESP_ERR_INVALID_STATE, i2s_channel_register_event_callback(tx_handle, &dma_cbs, NULL)); + + i2s_event_callbacks_t sync_cbs = { + .on_tx_sync_evt = i2s_tx_sync_test_callback, + }; + TEST_ESP_OK(i2s_channel_register_event_callback(tx_handle, &sync_cbs, NULL)); + sync_cbs.on_tx_sync_evt = NULL; + TEST_ESP_OK(i2s_channel_register_event_callback(tx_handle, &sync_cbs, NULL)); + + TEST_ESP_OK(i2s_channel_disable(tx_handle)); + TEST_ESP_OK(i2s_del_channel(tx_handle)); +} + TEST_CASE("I2S TX sync callback is triggered by GPTimer ETM alarm", "[i2s][etm]") { i2s_chan_handle_t tx_handle = NULL; @@ -1351,9 +1386,7 @@ TEST_CASE("I2S TX sync callback is triggered by GPTimer ETM alarm", "[i2s][etm]" i2s_event_callbacks_t cbs = { .on_tx_sync_evt = i2s_tx_sync_test_callback, }; - TEST_ESP_OK(i2s_channel_config_tx_fifo_sync(tx_handle, &sync_cfg)); TEST_ESP_OK(i2s_channel_register_event_callback(tx_handle, &cbs, &cb_ctx)); - TEST_ESP_OK(i2s_channel_enable_tx_fifo_sync(tx_handle, true)); i2s_sync_count_t sync_count = {}; TEST_ESP_OK(i2s_channel_get_sync_count(tx_handle, &sync_count, true)); @@ -1385,11 +1418,14 @@ TEST_CASE("I2S TX sync callback is triggered by GPTimer ETM alarm", "[i2s][etm]" esp_etm_channel_config_t etm_cfg = {}; TEST_ESP_OK(esp_etm_new_channel(&etm_cfg, &etm_channel)); TEST_ESP_OK(esp_etm_channel_connect(etm_channel, timer_event, i2s_sync_task)); - TEST_ESP_OK(esp_etm_channel_enable(etm_channel)); + TEST_ESP_OK(i2s_channel_enable(tx_handle)); + // TEST_ASSERT_EQUAL(ESP_ERR_INVALID_STATE, i2s_channel_enable_tx_fifo_sync(tx_handle, true)); + TEST_ESP_OK(i2s_channel_config_tx_fifo_sync(tx_handle, &sync_cfg)); + TEST_ESP_OK(i2s_channel_enable_tx_fifo_sync(tx_handle, true)); + TEST_ESP_OK(esp_etm_channel_enable(etm_channel)); TEST_ESP_OK(gptimer_enable(timer)); TEST_ESP_OK(gptimer_start(timer)); - TEST_ESP_OK(i2s_channel_enable(tx_handle)); bool callback_triggered = xSemaphoreTake(cb_ctx.sem, pdMS_TO_TICKS(100)) == pdTRUE; printf("TX sync callback: triggered=%d, diff=%"PRId32"\n", callback_triggered, cb_ctx.diff_count); @@ -1458,7 +1494,14 @@ TEST_CASE("i2s_destination_test", "[i2s]") chan_cfg.tx_destination = I2S_DESTINATION_BT; TEST_ESP_OK(i2s_new_channel(&chan_cfg, &tx, NULL)); TEST_ESP_OK(i2s_channel_init_std_mode(tx, &std_cfg)); - TEST_ASSERT_EQUAL(ESP_ERR_NOT_SUPPORTED, i2s_channel_register_event_callback(tx, &cbs, NULL)); + // DMA event callbacks rely on the DMA data path and are not available on the Bluetooth path. + i2s_event_callbacks_t tx_dma_cbs = { .on_sent = i2s_tx_on_sent_callback }; + TEST_ASSERT_EQUAL(ESP_ERR_NOT_SUPPORTED, i2s_channel_register_event_callback(tx, &tx_dma_cbs, NULL)); +#if SOC_I2S_SUPPORTS_TX_FIFO_SYNC + // TX FIFO sync callback is a peripheral interrupt event and is available regardless of the data path. + i2s_event_callbacks_t tx_sync_cbs = { .on_tx_sync_evt = i2s_tx_sync_test_callback }; + TEST_ESP_OK(i2s_channel_register_event_callback(tx, &tx_sync_cbs, NULL)); +#endif TEST_ASSERT_EQUAL(ESP_ERR_NOT_SUPPORTED, i2s_channel_preload_data(tx, buf, sizeof(buf), &loaded)); TEST_ASSERT_EQUAL(ESP_ERR_NOT_SUPPORTED, i2s_channel_write(tx, buf, sizeof(buf), &written, 0)); TEST_ESP_OK(i2s_del_channel(tx)); @@ -1469,7 +1512,9 @@ TEST_CASE("i2s_destination_test", "[i2s]") chan_cfg.rx_destination = I2S_DESTINATION_BT; TEST_ESP_OK(i2s_new_channel(&chan_cfg, NULL, &rx)); TEST_ESP_OK(i2s_channel_init_std_mode(rx, &std_cfg)); - TEST_ASSERT_EQUAL(ESP_ERR_NOT_SUPPORTED, i2s_channel_register_event_callback(rx, &cbs, NULL)); + // DMA event callbacks rely on the DMA data path and are not available on the Bluetooth path. + i2s_event_callbacks_t rx_dma_cbs = { .on_recv = i2s_rx_on_recv_callback }; + TEST_ASSERT_EQUAL(ESP_ERR_NOT_SUPPORTED, i2s_channel_register_event_callback(rx, &rx_dma_cbs, NULL)); TEST_ASSERT_EQUAL(ESP_ERR_NOT_SUPPORTED, i2s_channel_read(rx, buf, sizeof(buf), &read_bytes, 0)); TEST_ESP_OK(i2s_del_channel(rx)); rx = NULL; diff --git a/components/esp_driver_i2s/test_apps/i2s_multi_dev/main/test_i2s_multi_dev.c b/components/esp_driver_i2s/test_apps/i2s_multi_dev/main/test_i2s_multi_dev.c index b4e0afa5211..8434604870a 100644 --- a/components/esp_driver_i2s/test_apps/i2s_multi_dev/main/test_i2s_multi_dev.c +++ b/components/esp_driver_i2s/test_apps/i2s_multi_dev/main/test_i2s_multi_dev.c @@ -412,12 +412,18 @@ static void test_i2s_external_clk_src(bool is_master, bool is_external) } else { unity_wait_for_signal("Master Finished"); } + if (!is_external) { + unity_wait_for_signal("External Clock User Finished"); + } // Disable and free the resources TEST_ESP_OK(i2s_channel_disable(rx_handle)); TEST_ESP_OK(i2s_channel_disable(tx_handle)); free(recv_buff); TEST_ESP_OK(i2s_del_channel(rx_handle)); TEST_ESP_OK(i2s_del_channel(tx_handle)); + if (is_external) { + unity_send_signal("External Clock User Finished"); + } // Assert whether the test success TEST_ASSERT(is_success); } diff --git a/docs/en/api-reference/peripherals/i2s.rst b/docs/en/api-reference/peripherals/i2s.rst index 4c4669643fd..d68ebe64e63 100644 --- a/docs/en/api-reference/peripherals/i2s.rst +++ b/docs/en/api-reference/peripherals/i2s.rst @@ -331,27 +331,30 @@ To satisfy the high quality audio requirement, following advanced APIs are provi - :cpp:func:`i2s_channel_get_sync_count`: Read the TX synchronization counters through :cpp:type:`i2s_sync_count_t`. When TX FIFO synchronization is supported, ``diff_count`` is also returned as ``I2S_TX_FIFO_CNT - I2S_TX_FIFO_IDEAL_CNT``. - :cpp:func:`i2s_channel_config_tx_fifo_sync`: Configure the expected count, automatic supplement threshold, - manual supplement threshold, and hardware supplement mode. + manual supplement threshold, and hardware supplement mode. It can be called while the TX channel is running, + but TX FIFO synchronization must be disabled. - :cpp:func:`i2s_channel_enable_tx_fifo_sync`: Enable or disable TX FIFO synchronization. When enabled, both automatic hardware data supplementation and manual interrupt are activated simultaneously. - When disabled, both are deactivated. This API must be called after - :cpp:func:`i2s_channel_config_tx_fifo_sync`. + When disabled, both are deactivated. Enabling TX FIFO synchronization resets the TX FIFO/BCLK synchronization + counters. This API must be called after :cpp:func:`i2s_channel_config_tx_fifo_sync`. - :cpp:func:`i2s_channel_register_event_callback`: Register the manual supplement threshold interrupt callback. When ``diff_count`` exceeds the manual supplement threshold, the driver calls this callback in the ISR and provides - ``diff_count`` through :cpp:type:`i2s_sync_event_data_t`. + ``diff_count`` through :cpp:type:`i2s_sync_event_data_t`. Registering the callback only updates the handler; + the TX sync interrupt's enable/disable is controlled by :cpp:func:`i2s_channel_enable_tx_fifo_sync`. The typical usage steps are: 1. Create and initialize an I2S TX channel. 2. Call :cpp:func:`i2s_channel_config_tx_fifo_sync` to configure :cpp:type:`i2s_tx_fifo_sync_config_t`. ``ideal_cnt`` - is the expected number of transmitted data units at each ETM synchronization check. ``auto_suppl_thresh`` is + is the expected number of transmitted data units at each ETM synchronization check. This step can be performed while the TX channel is running, but TX FIFO synchronization must be disabled before reconfiguration. ``auto_suppl_thresh`` is the automatic hardware supplement threshold and must be smaller than ``manual_suppl_thresh``. ``manual_suppl_thresh`` is the threshold for triggering the callback for manual handling. If the difference exceeds the automatic supplement threshold but has not reached the manual supplement threshold, hardware automatically supplements or deletes the corresponding amount of data to synchronize with ``ideal_cnt``. 3. To handle severe out-of-sync conditions, call :cpp:func:`i2s_channel_register_event_callback` to register a callback. 4. Call :cpp:func:`i2s_channel_enable_tx_fifo_sync` with ``enable`` set to ``true`` to activate both automatic - hardware supplementation and manual interrupt simultaneously. + hardware supplementation and manual interrupt simultaneously. This call resets the TX FIFO/BCLK synchronization + counters, so the first ETM synchronization check uses a new count window. 5. Call :cpp:func:`i2s_new_etm_task` to create the ``I2S_ETM_TASK_SYNC_FIFO`` task, and connect an external ETM event to this task. 6. Enable the ETM channel and I2S TX channel, so that ETM events periodically trigger synchronization checks. @@ -409,6 +412,9 @@ To satisfy the high quality audio requirement, following advanced APIs are provi .. note:: After ``I2S_ETM_TASK_SYNC_FIFO`` is triggered, hardware automatically clears the TX FIFO/BCLK synchronization counters. + To avoid a synchronization check using partially updated configuration, call :cpp:func:`i2s_channel_enable_tx_fifo_sync` + with ``enable`` set to ``false`` before reconfiguring TX FIFO synchronization. If an ETM event source may still + trigger during reconfiguration, disable the ETM channel or pause the event source as needed. .. _i2s-iram-safe: diff --git a/docs/zh_CN/api-reference/peripherals/i2s.rst b/docs/zh_CN/api-reference/peripherals/i2s.rst index 06534cbc665..1fee14a1e0a 100644 --- a/docs/zh_CN/api-reference/peripherals/i2s.rst +++ b/docs/zh_CN/api-reference/peripherals/i2s.rst @@ -331,27 +331,29 @@ I2S 的数据传输(包括数据发送和接收)由 DMA 实现。在传输 - :cpp:func:`i2s_channel_get_sync_count`:通过 :cpp:type:`i2s_sync_count_t` 读取 TX 同步计数器。 当支持 TX FIFO 同步时,也会返回 ``diff_count``,其含义为 ``I2S_TX_FIFO_CNT - I2S_TX_FIFO_IDEAL_CNT``。 - :cpp:func:`i2s_channel_config_tx_fifo_sync`:配置期望计数、自动补偿阈值、 - 手动补偿阈值以及硬件补偿方式。 + 手动补偿阈值以及硬件补偿方式。该 API 可在 TX 通道运行时调用, + 但此时 TX FIFO 同步功能必须处于关闭状态。 - :cpp:func:`i2s_channel_enable_tx_fifo_sync`:使能或关闭 TX FIFO 同步功能。使能后, 硬件自动补偿和手动补偿中断同时激活。 - 关闭后,两者同时停用。该 API 必须在 - :cpp:func:`i2s_channel_config_tx_fifo_sync` 之后调用。 + 关闭后,两者同时停用。使能 TX FIFO 同步时会重置 TX FIFO/BCLK 同步计数器。 + 该 API 必须在 :cpp:func:`i2s_channel_config_tx_fifo_sync` 之后调用。 - :cpp:func:`i2s_channel_register_event_callback`:注册手动补偿阈值中断回调。当 ``diff_count`` 超过手动补偿阈值时,驱动会在 ISR 中调用该回调,并通过 - :cpp:type:`i2s_sync_event_data_t` 提供 ``diff_count``。 + :cpp:type:`i2s_sync_event_data_t` 提供 ``diff_count``。注册回调只更新 handler; + TX sync 中断的开关由 :cpp:func:`i2s_channel_enable_tx_fifo_sync` 控制。 使用该功能的一般步骤如下: 1. 创建并初始化 I2S TX 通道。 2. 调用 :cpp:func:`i2s_channel_config_tx_fifo_sync` 配置 :cpp:type:`i2s_tx_fifo_sync_config_t`。``ideal_cnt`` - 为每次 ETM 同步检查时期望发送的数据个数;``auto_suppl_thresh`` 为 - 硬件自动补偿阈值,必须小于 ``manual_suppl_thresh``。 + 为每次 ETM 同步检查时期望发送的数据个数。该步骤可在 TX 通道运行时执行,但重新配置前必须先关闭 TX FIFO 同步功能。``auto_suppl_thresh`` 为硬件自动补偿阈值, + 必须小于 ``manual_suppl_thresh``。 ``manual_suppl_thresh`` 为触发回调并交由软件手动处理的阈值。如果偏差 超过自动补偿阈值但尚未达到手动补偿阈值,硬件会自动补充或删除相应数量的数据, 以实现与 ``ideal_cnt`` 同步。 3. 如需处理严重不同步场景,调用 :cpp:func:`i2s_channel_register_event_callback` 注册回调。 4. 调用 :cpp:func:`i2s_channel_enable_tx_fifo_sync` 并将 ``enable`` 设为 ``true``,同时激活硬件自动补偿 - 和手动补偿中断。 + 和手动补偿中断。该调用会重置 TX FIFO/BCLK 同步计数器,因此第一次 ETM 同步检查会使用新的计数窗口。 5. 调用 :cpp:func:`i2s_new_etm_task` 创建 ``I2S_ETM_TASK_SYNC_FIFO`` 任务,并将外部 ETM 事件连接到该任务。 6. 使能 ETM 通道和 I2S TX 通道,由 ETM 事件周期性触发同步检查。 @@ -409,6 +411,9 @@ I2S 的数据传输(包括数据发送和接收)由 DMA 实现。在传输 .. note:: ``I2S_ETM_TASK_SYNC_FIFO`` 触发后,硬件会自动清零 TX FIFO/BCLK 同步计数器。 + 为避免同步检查使用到正在更新中的配置,重新配置 TX FIFO 同步前应调用 + :cpp:func:`i2s_channel_enable_tx_fifo_sync` 并将 ``enable`` 设为 ``false``。 + 如果重新配置期间 ETM 事件源仍可能触发,可根据需要关闭 ETM 通道或暂停事件源。 .. _i2s-iram-safe: