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: