change(i2s): allow config tx sync params while tx channel is running

This commit is contained in:
Chen Chen
2026-06-24 15:09:18 +08:00
parent b8218f4fe1
commit a907115eba
8 changed files with 138 additions and 54 deletions

View File

@@ -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;
}

View File

@@ -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 */

View File

@@ -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);

View File

@@ -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;

View File

@@ -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;

View File

@@ -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);
}

View File

@@ -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:

View File

@@ -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: