refactor(i2s): combine separate callback registers into single one

This commit is contained in:
Chen Chen
2026-06-09 17:18:01 +08:00
parent c27bd91874
commit b8218f4fe1
11 changed files with 263 additions and 197 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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