mirror of
https://github.com/espressif/esp-idf.git
synced 2026-09-22 13:01:16 +03:00
refactor(i2s): combine separate callback registers into single one
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
*
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
/**
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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));
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user