mirror of
https://github.com/espressif/esp-idf.git
synced 2026-09-22 13:01:16 +03:00
feat(i2s): release i2s tx sync APIs
This commit is contained in:
@@ -47,7 +47,6 @@
|
||||
|
||||
#include "driver/gpio.h"
|
||||
#include "esp_private/gpio.h"
|
||||
#include "esp_private/i2s_sync.h"
|
||||
#include "driver/i2s_common.h"
|
||||
#include "i2s_private.h"
|
||||
|
||||
@@ -1222,6 +1221,14 @@ esp_err_t i2s_del_channel(i2s_chan_handle_t handle)
|
||||
if (handle->binary) {
|
||||
vSemaphoreDeleteWithCaps(handle->binary);
|
||||
}
|
||||
#if SOC_I2S_SUPPORTS_TX_FIFO_SYNC
|
||||
if (handle->sync_intr) {
|
||||
i2s_ll_enable_interrupt(handle->controller->hal.dev, I2S_LL_TX_SYNC_INT_EVENT, false);
|
||||
esp_intr_disable(handle->sync_intr);
|
||||
esp_intr_free(handle->sync_intr);
|
||||
handle->sync_intr = NULL;
|
||||
}
|
||||
#endif
|
||||
#if SOC_I2S_HW_VERSION_1
|
||||
i2s_obj->chan_occupancy = 0;
|
||||
#else
|
||||
@@ -1635,68 +1642,157 @@ err:
|
||||
}
|
||||
|
||||
#if SOC_I2S_SUPPORTS_TX_SYNC_CNT
|
||||
uint32_t i2s_sync_get_bclk_count(i2s_chan_handle_t tx_handle)
|
||||
__attribute__((always_inline))
|
||||
static inline esp_err_t i2s_check_tx_handle(i2s_chan_handle_t tx_handle)
|
||||
{
|
||||
return i2s_ll_tx_get_bclk_sync_count(tx_handle->controller->hal.dev);
|
||||
I2S_NULL_POINTER_CHECK(TAG, tx_handle);
|
||||
ESP_RETURN_ON_FALSE(tx_handle->dir == I2S_DIR_TX, ESP_ERR_INVALID_ARG, TAG, "channel is not TX");
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
uint32_t i2s_sync_get_fifo_count(i2s_chan_handle_t tx_handle)
|
||||
esp_err_t i2s_channel_get_sync_count(i2s_chan_handle_t tx_handle, uint32_t *bclk_count, uint32_t *fifo_count,
|
||||
bool reset)
|
||||
{
|
||||
return i2s_ll_tx_get_fifo_sync_count(tx_handle->controller->hal.dev);
|
||||
ESP_RETURN_ON_ERROR(i2s_check_tx_handle(tx_handle), TAG, "invalid TX handle");
|
||||
i2s_dev_t *hw = tx_handle->controller->hal.dev;
|
||||
if (bclk_count) {
|
||||
*bclk_count = i2s_ll_tx_get_bclk_sync_count(hw);
|
||||
}
|
||||
if (fifo_count) {
|
||||
*fifo_count = i2s_ll_tx_get_fifo_sync_count(hw);
|
||||
}
|
||||
if (reset) {
|
||||
i2s_ll_tx_reset_bclk_sync_counter(hw);
|
||||
i2s_ll_tx_reset_fifo_sync_counter(hw);
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
void i2s_sync_reset_bclk_count(i2s_chan_handle_t tx_handle)
|
||||
{
|
||||
i2s_ll_tx_reset_bclk_sync_counter(tx_handle->controller->hal.dev);
|
||||
}
|
||||
|
||||
void i2s_sync_reset_fifo_count(i2s_chan_handle_t tx_handle)
|
||||
{
|
||||
i2s_ll_tx_reset_fifo_sync_counter(tx_handle->controller->hal.dev);
|
||||
}
|
||||
#endif // SOC_I2S_SUPPORTS_TX_SYNC_CNT
|
||||
|
||||
#if SOC_I2S_SUPPORTS_TX_FIFO_SYNC
|
||||
uint32_t i2s_sync_get_fifo_sync_diff_count(i2s_chan_handle_t tx_handle)
|
||||
__attribute__((always_inline))
|
||||
static inline int32_t i2s_sign_extend_sync_diff(uint32_t diff)
|
||||
{
|
||||
return i2s_ll_tx_get_fifo_sync_diff_count(tx_handle->controller->hal.dev);
|
||||
return (diff & BIT(30)) ? (int32_t)(diff | BIT(31)) : (int32_t)diff;
|
||||
}
|
||||
|
||||
void i2s_sync_reset_fifo_sync_diff_count(i2s_chan_handle_t tx_handle)
|
||||
{
|
||||
i2s_ll_tx_reset_fifo_sync_diff_counter(tx_handle->controller->hal.dev);
|
||||
}
|
||||
|
||||
esp_err_t i2s_sync_enable_hw_fifo_sync(i2s_chan_handle_t tx_handle, bool enable)
|
||||
{
|
||||
if (tx_handle->dir == I2S_DIR_RX) {
|
||||
return ESP_ERR_NOT_SUPPORTED;
|
||||
}
|
||||
i2s_ll_tx_enable_hw_fifo_sync(tx_handle->controller->hal.dev, enable);
|
||||
i2s_ll_tx_update(tx_handle->controller->hal.dev);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t i2s_sync_config_hw_fifo_sync(i2s_chan_handle_t tx_handle, const i2s_sync_fifo_sync_config_t *config)
|
||||
{
|
||||
if (!(tx_handle && config)) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
if (tx_handle->dir == I2S_DIR_RX) {
|
||||
return ESP_ERR_NOT_SUPPORTED;
|
||||
}
|
||||
if (config->sw_high_thresh < config->hw_low_thresh) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
i2s_ll_tx_set_etm_sync_ideal_cnt(tx_handle->controller->hal.dev, config->ideal_cnt);
|
||||
i2s_ll_tx_set_fifo_sync_diff_conter_sw_threshold(tx_handle->controller->hal.dev, config->sw_high_thresh);
|
||||
i2s_ll_tx_set_fifo_sync_diff_conter_hw_threshold(tx_handle->controller->hal.dev, config->hw_low_thresh);
|
||||
i2s_ll_tx_set_hw_fifo_sync_suppl_mode(tx_handle->controller->hal.dev, (uint32_t)config->suppl_mode);
|
||||
if (config->suppl_mode == I2S_SYNC_SUPPL_MODE_STATIC_DATA) {
|
||||
i2s_ll_tx_set_hw_fifo_sync_static_suppl_data(tx_handle->controller->hal.dev, config->suppl_data);
|
||||
}
|
||||
i2s_ll_tx_update(tx_handle->controller->hal.dev);
|
||||
return ESP_OK;
|
||||
}
|
||||
#if CONFIG_I2S_ISR_IRAM_SAFE
|
||||
#define I2S_ISR_HANDLER_ATTR IRAM_ATTR
|
||||
#else
|
||||
#define I2S_ISR_HANDLER_ATTR
|
||||
#endif
|
||||
|
||||
static I2S_ISR_HANDLER_ATTR void i2s_isr_handler(void *args)
|
||||
{
|
||||
i2s_chan_handle_t handle = (i2s_chan_handle_t)args;
|
||||
i2s_dev_t *hw = handle->controller->hal.dev;
|
||||
|
||||
if (i2s_ll_get_interrupt_status(hw, I2S_LL_TX_SYNC_INT_EVENT)) {
|
||||
i2s_sync_event_data_t evt = {
|
||||
.diff_count = i2s_sign_extend_sync_diff(i2s_ll_tx_get_fifo_sync_diff_count(hw)),
|
||||
};
|
||||
i2s_ll_tx_reset_fifo_sync_diff_counter(hw);
|
||||
i2s_ll_clear_interrupt_status(hw, I2S_LL_TX_SYNC_INT_EVENT);
|
||||
|
||||
if (handle->on_sync && handle->on_sync(handle, &evt, handle->sync_user_data)) {
|
||||
portYIELD_FROM_ISR();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
esp_err_t i2s_channel_get_sync_diff_count(i2s_chan_handle_t tx_handle, int32_t *diff_count, bool reset)
|
||||
{
|
||||
ESP_RETURN_ON_ERROR(i2s_check_tx_handle(tx_handle), TAG, "invalid TX handle");
|
||||
i2s_dev_t *hw = tx_handle->controller->hal.dev;
|
||||
if (diff_count) {
|
||||
*diff_count = i2s_sign_extend_sync_diff(i2s_ll_tx_get_fifo_sync_diff_count(hw));
|
||||
}
|
||||
if (reset) {
|
||||
i2s_ll_tx_reset_fifo_sync_diff_counter(hw);
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t i2s_channel_config_tx_fifo_sync(i2s_chan_handle_t tx_handle, const i2s_tx_fifo_sync_config_t *config)
|
||||
{
|
||||
esp_err_t ret = ESP_OK;
|
||||
ESP_RETURN_ON_ERROR(i2s_check_tx_handle(tx_handle), TAG, "invalid TX handle");
|
||||
I2S_NULL_POINTER_CHECK(TAG, config);
|
||||
ESP_RETURN_ON_FALSE(config->auto_suppl_thresh == 0 ||
|
||||
config->auto_suppl_thresh < config->manual_suppl_thresh,
|
||||
ESP_ERR_INVALID_ARG, TAG,
|
||||
"auto_suppl_thresh must be 0 or smaller than manual_suppl_thresh");
|
||||
|
||||
i2s_dev_t *hw = tx_handle->controller->hal.dev;
|
||||
xSemaphoreTake(tx_handle->mutex, portMAX_DELAY);
|
||||
ESP_GOTO_ON_FALSE(tx_handle->state < I2S_CHAN_STATE_RUNNING, ESP_ERR_INVALID_STATE, err, TAG,
|
||||
"invalid state, I2S has enabled");
|
||||
i2s_ll_tx_set_etm_sync_ideal_cnt(hw, config->ideal_cnt);
|
||||
i2s_ll_tx_set_fifo_sync_diff_counter_manual_threshold(hw, config->manual_suppl_thresh);
|
||||
i2s_ll_tx_set_fifo_sync_diff_counter_auto_threshold(hw, config->auto_suppl_thresh);
|
||||
i2s_ll_tx_set_hw_fifo_sync_suppl_mode(hw, config->suppl_mode);
|
||||
if (config->suppl_mode == I2S_TX_FIFO_SYNC_SUPPL_MODE_STATIC_DATA) {
|
||||
i2s_ll_tx_set_hw_fifo_sync_static_suppl_data(hw, config->suppl_data);
|
||||
}
|
||||
i2s_ll_tx_enable_hw_fifo_sync(hw, config->auto_suppl_thresh > 0);
|
||||
i2s_ll_tx_update(hw);
|
||||
err:
|
||||
xSemaphoreGive(tx_handle->mutex);
|
||||
return ret;
|
||||
}
|
||||
|
||||
esp_err_t i2s_channel_register_intr_event_callback(i2s_chan_handle_t tx_handle,
|
||||
const i2s_intr_event_callbacks_t *callbacks, void *user_data)
|
||||
{
|
||||
esp_err_t ret = ESP_OK;
|
||||
ESP_RETURN_ON_ERROR(i2s_check_tx_handle(tx_handle), TAG, "invalid TX handle");
|
||||
I2S_NULL_POINTER_CHECK(TAG, callbacks);
|
||||
i2s_sync_callback_t callback = callbacks->on_tx_sync;
|
||||
#if CONFIG_I2S_ISR_IRAM_SAFE
|
||||
if (callback) {
|
||||
ESP_RETURN_ON_FALSE(esp_ptr_in_iram(callback), ESP_ERR_INVALID_ARG, TAG, "sync callback not in IRAM");
|
||||
}
|
||||
if (user_data) {
|
||||
ESP_RETURN_ON_FALSE(esp_ptr_internal(user_data), ESP_ERR_INVALID_ARG, TAG, "user context not in internal RAM");
|
||||
}
|
||||
#endif
|
||||
|
||||
i2s_dev_t *hw = tx_handle->controller->hal.dev;
|
||||
xSemaphoreTake(tx_handle->mutex, portMAX_DELAY);
|
||||
ESP_GOTO_ON_FALSE(tx_handle->state < I2S_CHAN_STATE_RUNNING, ESP_ERR_INVALID_STATE, err, TAG,
|
||||
"invalid state, I2S has enabled");
|
||||
|
||||
if (callback) {
|
||||
if (!tx_handle->sync_intr) {
|
||||
int port_id = tx_handle->controller->id;
|
||||
int intr_flag = ESP_INTR_FLAG_INTRDISABLED | tx_handle->intr_prio_flags;
|
||||
#if CONFIG_I2S_ISR_IRAM_SAFE
|
||||
intr_flag |= ESP_INTR_FLAG_IRAM;
|
||||
#endif
|
||||
ret = esp_intr_alloc_intrstatus(i2s_periph_signal[port_id].irq, intr_flag,
|
||||
(uint32_t)i2s_ll_get_interrupt_status_reg(hw),
|
||||
I2S_LL_TX_SYNC_INT_EVENT, i2s_isr_handler, tx_handle,
|
||||
&tx_handle->sync_intr);
|
||||
ESP_GOTO_ON_ERROR(ret, err, TAG, "allocate TX sync interrupt failed");
|
||||
ESP_GOTO_ON_ERROR(esp_intr_enable(tx_handle->sync_intr), err, TAG, "enable TX sync interrupt failed");
|
||||
}
|
||||
tx_handle->on_sync = callback;
|
||||
tx_handle->sync_user_data = user_data;
|
||||
i2s_ll_clear_interrupt_status(hw, I2S_LL_TX_SYNC_INT_EVENT);
|
||||
i2s_ll_enable_interrupt(hw, I2S_LL_TX_SYNC_INT_EVENT, true);
|
||||
} else {
|
||||
i2s_ll_enable_interrupt(hw, I2S_LL_TX_SYNC_INT_EVENT, false);
|
||||
if (tx_handle->sync_intr) {
|
||||
esp_intr_disable(tx_handle->sync_intr);
|
||||
esp_intr_free(tx_handle->sync_intr);
|
||||
tx_handle->sync_intr = NULL;
|
||||
}
|
||||
tx_handle->on_sync = NULL;
|
||||
tx_handle->sync_user_data = NULL;
|
||||
}
|
||||
|
||||
err:
|
||||
xSemaphoreGive(tx_handle->mutex);
|
||||
return ret;
|
||||
}
|
||||
#endif // SOC_I2S_SUPPORTS_TX_FIFO_SYNC
|
||||
#endif // SOC_I2S_SUPPORTS_TX_SYNC_CNT
|
||||
|
||||
@@ -39,7 +39,7 @@ static esp_err_t s_i2s_del_etm_task(esp_etm_task_t *task)
|
||||
if (i2s_task->task_type == I2S_ETM_TASK_START) {
|
||||
// The i2s start no longer be controlled by etm
|
||||
i2s_task->handle->is_etm_start = false;
|
||||
} else {
|
||||
} else if (i2s_task->task_type == I2S_ETM_TASK_STOP) {
|
||||
// The i2s stop no longer be controlled by etm
|
||||
i2s_task->handle->is_etm_stop = false;
|
||||
}
|
||||
|
||||
@@ -180,6 +180,11 @@ struct i2s_channel_obj_t {
|
||||
uint64_t reserve_gpio_mask; /*!< The gpio mask that has been reserved by I2S */
|
||||
i2s_event_callbacks_internal_t callbacks; /*!< Callback functions */
|
||||
void *user_data; /*!< User data for callback functions */
|
||||
#if SOC_I2S_SUPPORTS_TX_FIFO_SYNC
|
||||
i2s_sync_callback_t on_sync; /*!< TX FIFO sync manual supplement threshold callback */
|
||||
void *sync_user_data; /*!< User data for TX FIFO sync callback */
|
||||
intr_handle_t sync_intr; /*!< I2S peripheral interrupt for TX FIFO sync */
|
||||
#endif
|
||||
void (*start)(i2s_chan_handle_t); /*!< start tx/rx channel */
|
||||
void (*stop)(i2s_chan_handle_t); /*!< stop tx/rx channel */
|
||||
};
|
||||
|
||||
@@ -290,6 +290,105 @@ esp_err_t i2s_channel_preload_data(i2s_chan_handle_t tx_handle, const void *src,
|
||||
*/
|
||||
esp_err_t i2s_channel_tune_rate(i2s_chan_handle_t handle, const i2s_tuning_config_t *tune_cfg, i2s_tuning_info_t *tune_info);
|
||||
|
||||
#if SOC_I2S_SUPPORTS_TX_SYNC_CNT
|
||||
/**
|
||||
* @brief Get TX synchronization counters
|
||||
*
|
||||
* @note `fifo_count` reflects how many data have been read from TX FIFO.
|
||||
* Normally, `bclk_count = fifo_count * slot_bit_width`.
|
||||
* Both counters are reset automatically when `I2S_ETM_TASK_SYNC_FIFO` is triggered.
|
||||
*
|
||||
* @param[in] tx_handle I2S TX channel handle
|
||||
* @param[out] bclk_count Pointer to receive BCLK sync counter, set NULL to ignore
|
||||
* @param[out] fifo_count Pointer to receive FIFO sync counter, set NULL to ignore
|
||||
* @param[in] reset Whether to reset both counters after reading
|
||||
* @return
|
||||
* - ESP_OK Success
|
||||
* - ESP_ERR_INVALID_ARG Invalid handle or channel is not TX
|
||||
*/
|
||||
esp_err_t i2s_channel_get_sync_count(i2s_chan_handle_t tx_handle, uint32_t *bclk_count, uint32_t *fifo_count,
|
||||
bool reset);
|
||||
|
||||
#if SOC_I2S_SUPPORTS_TX_FIFO_SYNC
|
||||
/**
|
||||
* @brief Get TX FIFO synchronization difference counter
|
||||
*
|
||||
* @note `diff_count` is a signed 31-bit value equal to `I2S_TX_FIFO_CNT - I2S_TX_FIFO_IDEAL_CNT`.
|
||||
*
|
||||
* @param[in] tx_handle I2S TX channel handle
|
||||
* @param[out] diff_count Pointer to receive signed difference counter, set NULL to ignore
|
||||
* @param[in] reset Whether to reset the difference counter after reading
|
||||
* @return
|
||||
* - ESP_OK Success
|
||||
* - ESP_ERR_INVALID_ARG Invalid handle or channel is not TX
|
||||
*/
|
||||
esp_err_t i2s_channel_get_sync_diff_count(i2s_chan_handle_t tx_handle, int32_t *diff_count, bool reset);
|
||||
|
||||
/**
|
||||
* @brief TX FIFO synchronization configuration
|
||||
*/
|
||||
typedef struct {
|
||||
uint32_t auto_suppl_thresh; /*!< Threshold to enable automatic FIFO data supplement;
|
||||
* 0 disables automatic supplement
|
||||
*/
|
||||
uint32_t manual_suppl_thresh; /*!< Threshold to trigger the callback for manual FIFO data supplement */
|
||||
uint32_t ideal_cnt; /*!< Ideal FIFO count when ETM sync task is triggered */
|
||||
i2s_tx_fifo_sync_suppl_mode_t suppl_mode; /*!< Data supplement mode for automatic supplement */
|
||||
uint32_t suppl_data; /*!< Static supplement data, valid when suppl_mode is
|
||||
* I2S_TX_FIFO_SYNC_SUPPL_MODE_STATIC_DATA
|
||||
*/
|
||||
} i2s_tx_fifo_sync_config_t;
|
||||
|
||||
/**
|
||||
* @brief Group of I2S peripheral interrupt event callbacks
|
||||
*
|
||||
* @note The callbacks are all running under ISR environment.
|
||||
*/
|
||||
typedef struct {
|
||||
i2s_sync_callback_t on_tx_sync; /*!< Callback when the sync difference count exceeds
|
||||
* `manual_suppl_thresh`
|
||||
*/
|
||||
} i2s_intr_event_callbacks_t;
|
||||
|
||||
/**
|
||||
* @brief Configure TX FIFO synchronization
|
||||
*
|
||||
* @note Set `auto_suppl_thresh` to 0 to disable automatic hardware supplementation.
|
||||
* @note When automatic hardware supplementation is enabled, `auto_suppl_thresh` must be smaller than
|
||||
* `manual_suppl_thresh`.
|
||||
* @note Only allowed when channel state is REGISTERED or READY (before channel starts).
|
||||
*
|
||||
* @param[in] tx_handle I2S TX channel handle
|
||||
* @param[in] config TX FIFO synchronization configuration
|
||||
* @return
|
||||
* - ESP_OK Success
|
||||
* - ESP_ERR_INVALID_ARG Invalid handle, channel is not TX, or invalid configuration
|
||||
* - ESP_ERR_INVALID_STATE Channel is already running
|
||||
*/
|
||||
esp_err_t i2s_channel_config_tx_fifo_sync(i2s_chan_handle_t tx_handle, const i2s_tx_fifo_sync_config_t *config);
|
||||
|
||||
/**
|
||||
* @brief Register I2S peripheral interrupt event callbacks
|
||||
*
|
||||
* @note `on_tx_sync` is invoked when `tx_cnt_diff` exceeds `manual_suppl_thresh`.
|
||||
* @note Only allowed when channel state is REGISTERED or READY (before channel starts).
|
||||
* @note When CONFIG_I2S_ISR_IRAM_SAFE is enabled, the callback and user_data must reside in internal RAM.
|
||||
* @note Set `callbacks->on_tx_sync` to NULL to deregister the callback and uninstall the TX sync interrupt.
|
||||
*
|
||||
* @param[in] tx_handle I2S TX channel handle
|
||||
* @param[in] callbacks Group of I2S peripheral interrupt callbacks
|
||||
* @param[in] user_data User context passed to callback
|
||||
* @return
|
||||
* - ESP_OK Success
|
||||
* - ESP_ERR_INVALID_ARG Invalid handle or channel is not TX
|
||||
* - ESP_ERR_INVALID_STATE Channel is already running
|
||||
* - ESP_ERR_NO_MEM Failed to allocate interrupt
|
||||
*/
|
||||
esp_err_t i2s_channel_register_intr_event_callback(i2s_chan_handle_t tx_handle,
|
||||
const i2s_intr_event_callbacks_t *callbacks, void *user_data);
|
||||
#endif // SOC_I2S_SUPPORTS_TX_FIFO_SYNC
|
||||
#endif // SOC_I2S_SUPPORTS_TX_SYNC_CNT
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -106,6 +106,29 @@ typedef struct {
|
||||
uint32_t water_mark; /*!< The water mark of the internal buffer, in percent */
|
||||
} i2s_tuning_info_t;
|
||||
|
||||
typedef struct i2s_channel_obj_t *i2s_chan_handle_t; /*!< I2S channel object handle, the control unit of the I2S driver*/
|
||||
typedef struct lp_i2s_channel_obj_t *lp_i2s_chan_handle_t; /*!< I2S channel object handle, the control unit of the I2S driver*/
|
||||
|
||||
#if SOC_I2S_SUPPORTS_TX_FIFO_SYNC
|
||||
/**
|
||||
* @brief TX synchronization event data passed to sync callback
|
||||
*/
|
||||
typedef struct {
|
||||
int32_t diff_count; /*!< Signed difference: I2S_TX_FIFO_CNT - I2S_TX_FIFO_IDEAL_CNT */
|
||||
} i2s_sync_event_data_t;
|
||||
|
||||
/**
|
||||
* @brief TX synchronization event callback
|
||||
*
|
||||
* @param[in] handle I2S TX channel handle
|
||||
* @param[in] event TX synchronization event data
|
||||
* @param[in] user_ctx User context registered via `i2s_channel_register_intr_event_callback()`
|
||||
*
|
||||
* @return Whether a high priority task has been waken up by this callback function
|
||||
*/
|
||||
typedef bool (*i2s_sync_callback_t)(i2s_chan_handle_t handle, const i2s_sync_event_data_t *event, void *user_ctx);
|
||||
#endif // SOC_I2S_SUPPORTS_TX_FIFO_SYNC
|
||||
|
||||
/**
|
||||
* @brief Event data structure for LP I2S
|
||||
*/
|
||||
@@ -113,9 +136,6 @@ typedef struct {
|
||||
lp_i2s_trans_t trans; ///< LP I2S transaction
|
||||
} lp_i2s_evt_data_t;
|
||||
|
||||
typedef struct i2s_channel_obj_t *i2s_chan_handle_t; /*!< I2S channel object handle, the control unit of the I2S driver*/
|
||||
typedef struct lp_i2s_channel_obj_t *lp_i2s_chan_handle_t; /*!< I2S channel object handle, the control unit of the I2S driver*/
|
||||
|
||||
/**
|
||||
* @brief I2S event callback
|
||||
* @param[in] handle I2S channel handle, created from `i2s_new_channel()`
|
||||
|
||||
@@ -1,146 +0,0 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2023-2025 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
// DO NOT USE THESE APIS IN YOUR APPLICATIONS
|
||||
// The following APIs are for internal use, public to other IDF components, but not for users' applications.
|
||||
|
||||
/**
|
||||
* This file is used for getting the bclk and fifo sending count
|
||||
* for the synchronization among different I2S ports.
|
||||
*
|
||||
* The APIs in this file might be called frequently, so they are made light-weight and flexible to be called
|
||||
*
|
||||
* NOTE: These APIs are private for ESP internal usages.
|
||||
* Please be aware of the risk that APIs might be changed regarding the use case.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include "driver/i2s_types.h"
|
||||
#include "esp_err.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#if SOC_I2S_SUPPORTS_TX_SYNC_CNT
|
||||
|
||||
/**
|
||||
* @brief Get the counter number of BCLK ticks
|
||||
* @note The BCLK tick count reflects the real data that have sent on line
|
||||
* @note It will be reset automatically when `I2S_ETM_TASK_SYNC_FIFO` is triggered
|
||||
*
|
||||
* @param[in] tx_handle The I2S tx channel handle
|
||||
* @return
|
||||
* - BCLK tick count
|
||||
*/
|
||||
uint32_t i2s_sync_get_bclk_count(i2s_chan_handle_t tx_handle);
|
||||
|
||||
/**
|
||||
* @brief Get the counter number of fifo
|
||||
* @note The FIFO count reflects how many slots have processed
|
||||
* Normally, fifo_cnt = slot_bit_width * bclk_cnt
|
||||
* If fifo_cnt < slot_bit_width * bclk_cnt, that means some data are still stuck in the I2S controller
|
||||
* @note It will be reset automatically when `I2S_ETM_TASK_SYNC_FIFO` is triggered
|
||||
*
|
||||
* @param[in] tx_handle The I2S tx channel handle
|
||||
* @return
|
||||
* - FIFO slot count
|
||||
*/
|
||||
uint32_t i2s_sync_get_fifo_count(i2s_chan_handle_t tx_handle);
|
||||
|
||||
/**
|
||||
* @brief Reset the bclk counter
|
||||
*
|
||||
* @param[in] tx_handle The I2S tx channel handle
|
||||
*/
|
||||
void i2s_sync_reset_bclk_count(i2s_chan_handle_t tx_handle);
|
||||
|
||||
/**
|
||||
* @brief Reset the fifo counter
|
||||
*
|
||||
* @param[in] tx_handle The I2S tx channel handle
|
||||
*/
|
||||
void i2s_sync_reset_fifo_count(i2s_chan_handle_t tx_handle);
|
||||
|
||||
#endif // SOC_I2S_SUPPORTS_TX_SYNC_CNT
|
||||
|
||||
#if SOC_I2S_SUPPORTS_TX_FIFO_SYNC
|
||||
/**
|
||||
* @brief I2S hardware FIFO synchronization supplement mode
|
||||
* @note When the FIFO sync difference count is out of threshold, the hardware will supplement data automatically
|
||||
* This type is to specify which data will be supplemented
|
||||
*/
|
||||
typedef enum {
|
||||
I2S_SYNC_SUPPL_MODE_LAST_DATA = 0, /*!< Supplement with the last transmitted data */
|
||||
I2S_SYNC_SUPPL_MODE_STATIC_DATA = 1, /*!< Supplement with static data specified in config */
|
||||
} i2s_sync_suppl_mode_t;
|
||||
|
||||
/**
|
||||
* @brief I2S hardware FIFO synchronization configuration
|
||||
* @note This configuration is used for multi I2S port synchronization via ETM
|
||||
*/
|
||||
typedef struct {
|
||||
uint32_t hw_low_thresh; /*!< Lower threshold for FIFO sync difference counter
|
||||
- If difference count < hw_low_thresh, do nothing
|
||||
- If difference count >= hw_low_thresh, the hardware will supplement data automatically */
|
||||
uint32_t sw_high_thresh; /*!< Upper threshold for FIFO sync difference counter
|
||||
- If difference count <= sw_high_thresh, the hardware supplement data automatically
|
||||
- If difference count > sw_high_thresh, sync interrupt triggered and
|
||||
the software is responsible to decide how to handle this severe asynchronization */
|
||||
uint32_t ideal_cnt; /*!< Ideal count for FIFO sync difference counter, it depends on the ETM sync task interval and the data rate */
|
||||
i2s_sync_suppl_mode_t suppl_mode; /*!< Data supplement mode when FIFO sync difference is out of threshold */
|
||||
uint32_t suppl_data; /*!< Static supplement data, only valid when suppl_mode is I2S_SYNC_SUPPL_MODE_STATIC_DATA */
|
||||
} i2s_sync_fifo_sync_config_t;
|
||||
|
||||
/**
|
||||
* @brief Get the counter number of FIFO sync difference
|
||||
* @note The FIFO sync difference count reflects the difference between current FIFO count and ideal count
|
||||
*
|
||||
* @param[in] tx_handle The I2S tx channel handle
|
||||
* @return
|
||||
* - FIFO sync difference count
|
||||
*/
|
||||
uint32_t i2s_sync_get_fifo_sync_diff_count(i2s_chan_handle_t tx_handle);
|
||||
|
||||
/**
|
||||
* @brief Reset the FIFO sync difference counter
|
||||
*
|
||||
* @param[in] tx_handle The I2S tx channel handle
|
||||
*/
|
||||
void i2s_sync_reset_fifo_sync_diff_count(i2s_chan_handle_t tx_handle);
|
||||
|
||||
/**
|
||||
* @brief Enable or disable hardware FIFO synchronization
|
||||
* @note When enabled, hardware will automatically supplement data when FIFO sync difference is greater than hw_low_thresh
|
||||
*
|
||||
* @param[in] tx_handle The I2S tx channel handle
|
||||
* @param[in] enable true to enable, false to disable
|
||||
* @return
|
||||
* - ESP_OK on success
|
||||
* - ESP_ERR_NOT_SUPPORTED if called on RX channel
|
||||
*/
|
||||
esp_err_t i2s_sync_enable_hw_fifo_sync(i2s_chan_handle_t tx_handle, bool enable);
|
||||
|
||||
/**
|
||||
* @brief Configure hardware FIFO synchronization parameters
|
||||
* @note This function configures the thresholds and supplement mode for hardware FIFO sync
|
||||
*
|
||||
* @param[in] tx_handle The I2S tx channel handle
|
||||
* @param[in] config Configuration for hardware FIFO synchronization
|
||||
* @return
|
||||
* - ESP_OK on success
|
||||
* - ESP_ERR_INVALID_ARG if invalid arguments
|
||||
* - ESP_ERR_NOT_SUPPORTED if called on RX channel
|
||||
*/
|
||||
esp_err_t i2s_sync_config_hw_fifo_sync(i2s_chan_handle_t tx_handle, const i2s_sync_fifo_sync_config_t *config);
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -6,12 +6,6 @@ entries:
|
||||
i2s_common: i2s_dma_tx_callback (noflash)
|
||||
if I2S_CTRL_FUNC_IN_IRAM = y:
|
||||
if SOC_I2S_SUPPORTS_TX_SYNC_CNT = y:
|
||||
i2s_common: i2s_sync_get_bclk_count (noflash)
|
||||
i2s_common: i2s_sync_get_fifo_count (noflash)
|
||||
i2s_common: i2s_sync_reset_bclk_count (noflash)
|
||||
i2s_common: i2s_sync_reset_fifo_count (noflash)
|
||||
i2s_common: i2s_channel_get_sync_count (noflash)
|
||||
if SOC_I2S_SUPPORTS_TX_FIFO_SYNC = y:
|
||||
i2s_common: i2s_sync_get_fifo_sync_diff_count (noflash)
|
||||
i2s_common: i2s_sync_reset_fifo_sync_diff_count (noflash)
|
||||
i2s_common: i2s_sync_enable_hw_fifo_sync (noflash)
|
||||
i2s_common: i2s_sync_config_hw_fifo_sync (noflash)
|
||||
i2s_common: i2s_channel_get_sync_diff_count (noflash)
|
||||
|
||||
@@ -12,5 +12,5 @@ endif()
|
||||
|
||||
idf_component_register(SRCS ${srcs}
|
||||
PRIV_REQUIRES unity esp_driver_pcnt spi_flash
|
||||
esp_driver_gpio esp_driver_i2s esp_driver_uart esp_psram
|
||||
esp_driver_gpio esp_driver_i2s esp_driver_uart esp_psram esp_driver_gptimer
|
||||
WHOLE_ARCHIVE)
|
||||
|
||||
@@ -25,6 +25,11 @@
|
||||
#include "driver/i2s_std.h"
|
||||
#include "driver/i2s_common.h"
|
||||
#include "soc/soc_caps.h"
|
||||
#if SOC_I2S_SUPPORTS_TX_FIFO_SYNC
|
||||
#include "driver/i2s_etm.h"
|
||||
#include "driver/gptimer.h"
|
||||
#include "esp_etm.h"
|
||||
#endif
|
||||
#if SOC_I2S_SUPPORTS_PDM
|
||||
#include "driver/i2s_pdm.h"
|
||||
#endif
|
||||
@@ -1278,6 +1283,144 @@ TEST_CASE("I2S_rate_tunning", "[i2s]")
|
||||
TEST_ESP_OK(i2s_del_channel(rx_handle));
|
||||
}
|
||||
|
||||
#if SOC_I2S_SUPPORTS_TX_FIFO_SYNC
|
||||
#define I2S_TX_SYNC_TEST_TIMER_RES_HZ (1000 * 1000)
|
||||
#define I2S_TX_SYNC_TEST_ALARM_COUNT (5000)
|
||||
#define I2S_TX_SYNC_TEST_IDEAL_CNT (1000)
|
||||
#define I2S_TX_SYNC_TEST_MANUAL_THRESH (64)
|
||||
#define I2S_TX_SYNC_TEST_BUF_SIZE (4096)
|
||||
|
||||
typedef struct {
|
||||
SemaphoreHandle_t sem;
|
||||
int32_t diff_count;
|
||||
} i2s_tx_sync_test_ctx_t;
|
||||
|
||||
static IRAM_ATTR bool i2s_tx_sync_test_callback(i2s_chan_handle_t handle, const i2s_sync_event_data_t *event, void *user_ctx)
|
||||
{
|
||||
(void)handle;
|
||||
i2s_tx_sync_test_ctx_t *ctx = (i2s_tx_sync_test_ctx_t *)user_ctx;
|
||||
ctx->diff_count = event->diff_count;
|
||||
|
||||
BaseType_t need_yield = pdFALSE;
|
||||
xSemaphoreGiveFromISR(ctx->sem, &need_yield);
|
||||
return need_yield == pdTRUE;
|
||||
}
|
||||
|
||||
TEST_CASE("I2S TX sync callback is triggered by GPTimer ETM alarm", "[i2s][etm]")
|
||||
{
|
||||
i2s_chan_handle_t tx_handle = NULL;
|
||||
gptimer_handle_t timer = NULL;
|
||||
esp_etm_event_handle_t timer_event = NULL;
|
||||
esp_etm_task_handle_t i2s_sync_task = NULL;
|
||||
esp_etm_channel_handle_t etm_channel = NULL;
|
||||
uint8_t *buf = NULL;
|
||||
|
||||
i2s_tx_sync_test_ctx_t cb_ctx = {
|
||||
.sem = xSemaphoreCreateBinary(),
|
||||
};
|
||||
TEST_ASSERT_NOT_NULL(cb_ctx.sem);
|
||||
|
||||
i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_0, I2S_ROLE_MASTER);
|
||||
chan_cfg.dma_desc_num = 4;
|
||||
chan_cfg.dma_frame_num = 256;
|
||||
i2s_std_config_t std_cfg = {
|
||||
.clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(48000),
|
||||
.slot_cfg = I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_STEREO),
|
||||
.gpio_cfg = I2S_TEST_MASTER_DEFAULT_PIN,
|
||||
};
|
||||
std_cfg.gpio_cfg.mclk = -1;
|
||||
#if CONFIG_IDF_TARGET_ESP32S31
|
||||
std_cfg.clk_cfg.clk_src = I2S_CLK_SRC_APLL;
|
||||
#endif
|
||||
|
||||
TEST_ESP_OK(i2s_new_channel(&chan_cfg, &tx_handle, NULL));
|
||||
TEST_ESP_OK(i2s_channel_init_std_mode(tx_handle, &std_cfg));
|
||||
|
||||
buf = (uint8_t *)calloc(1, I2S_TX_SYNC_TEST_BUF_SIZE);
|
||||
TEST_ASSERT_NOT_NULL(buf);
|
||||
size_t bytes_loaded = 0;
|
||||
TEST_ESP_OK(i2s_channel_preload_data(tx_handle, buf, I2S_TX_SYNC_TEST_BUF_SIZE, &bytes_loaded));
|
||||
TEST_ASSERT_GREATER_THAN(0, bytes_loaded);
|
||||
|
||||
i2s_tx_fifo_sync_config_t sync_cfg = {
|
||||
.auto_suppl_thresh = 0,
|
||||
.manual_suppl_thresh = I2S_TX_SYNC_TEST_MANUAL_THRESH,
|
||||
.ideal_cnt = I2S_TX_SYNC_TEST_IDEAL_CNT,
|
||||
.suppl_mode = I2S_TX_FIFO_SYNC_SUPPL_MODE_LAST_DATA,
|
||||
};
|
||||
i2s_intr_event_callbacks_t intr_cbs = {
|
||||
.on_tx_sync = i2s_tx_sync_test_callback,
|
||||
};
|
||||
TEST_ESP_OK(i2s_channel_config_tx_fifo_sync(tx_handle, &sync_cfg));
|
||||
TEST_ESP_OK(i2s_channel_register_intr_event_callback(tx_handle, &intr_cbs, &cb_ctx));
|
||||
|
||||
uint32_t bclk_count = 0;
|
||||
uint32_t fifo_count = 0;
|
||||
int32_t diff_count = 0;
|
||||
TEST_ESP_OK(i2s_channel_get_sync_count(tx_handle, &bclk_count, &fifo_count, true));
|
||||
TEST_ESP_OK(i2s_channel_get_sync_count(tx_handle, NULL, NULL, false));
|
||||
TEST_ESP_OK(i2s_channel_get_sync_diff_count(tx_handle, &diff_count, true));
|
||||
TEST_ESP_OK(i2s_channel_get_sync_diff_count(tx_handle, NULL, false));
|
||||
printf("TX sync API before start: diff=%"PRId32", fifo=%"PRIu32", bclk=%"PRIu32"\n",
|
||||
diff_count, fifo_count, bclk_count);
|
||||
|
||||
i2s_etm_task_config_t i2s_task_cfg = {
|
||||
.task_type = I2S_ETM_TASK_SYNC_FIFO,
|
||||
};
|
||||
TEST_ESP_OK(i2s_new_etm_task(tx_handle, &i2s_task_cfg, &i2s_sync_task));
|
||||
|
||||
gptimer_config_t timer_cfg = {
|
||||
.clk_src = GPTIMER_CLK_SRC_DEFAULT,
|
||||
.direction = GPTIMER_COUNT_UP,
|
||||
.resolution_hz = I2S_TX_SYNC_TEST_TIMER_RES_HZ,
|
||||
};
|
||||
TEST_ESP_OK(gptimer_new_timer(&timer_cfg, &timer));
|
||||
gptimer_alarm_config_t alarm_cfg = {
|
||||
.alarm_count = I2S_TX_SYNC_TEST_ALARM_COUNT,
|
||||
};
|
||||
TEST_ESP_OK(gptimer_set_alarm_action(timer, &alarm_cfg));
|
||||
|
||||
gptimer_etm_event_config_t timer_event_cfg = {
|
||||
.event_type = GPTIMER_ETM_EVENT_ALARM_MATCH,
|
||||
};
|
||||
TEST_ESP_OK(gptimer_new_etm_event(timer, &timer_event_cfg, &timer_event));
|
||||
|
||||
esp_etm_channel_config_t etm_cfg = {};
|
||||
TEST_ESP_OK(esp_etm_new_channel(&etm_cfg, &etm_channel));
|
||||
TEST_ESP_OK(esp_etm_channel_connect(etm_channel, timer_event, i2s_sync_task));
|
||||
TEST_ESP_OK(esp_etm_channel_enable(etm_channel));
|
||||
|
||||
TEST_ESP_OK(gptimer_enable(timer));
|
||||
TEST_ESP_OK(gptimer_start(timer));
|
||||
TEST_ESP_OK(i2s_channel_enable(tx_handle));
|
||||
|
||||
bool callback_triggered = xSemaphoreTake(cb_ctx.sem, pdMS_TO_TICKS(100)) == pdTRUE;
|
||||
printf("TX sync callback: triggered=%d, diff=%"PRId32"\n", callback_triggered, cb_ctx.diff_count);
|
||||
|
||||
TEST_ESP_OK(gptimer_stop(timer));
|
||||
TEST_ESP_OK(i2s_channel_disable(tx_handle));
|
||||
intr_cbs.on_tx_sync = NULL;
|
||||
TEST_ESP_OK(i2s_channel_register_intr_event_callback(tx_handle, &intr_cbs, NULL));
|
||||
TEST_ESP_OK(gptimer_disable(timer));
|
||||
TEST_ESP_OK(esp_etm_channel_disable(etm_channel));
|
||||
|
||||
TEST_ESP_OK(esp_etm_del_event(timer_event));
|
||||
TEST_ESP_OK(esp_etm_del_task(i2s_sync_task));
|
||||
TEST_ESP_OK(esp_etm_del_channel(etm_channel));
|
||||
TEST_ESP_OK(gptimer_del_timer(timer));
|
||||
TEST_ESP_OK(i2s_del_channel(tx_handle));
|
||||
vSemaphoreDelete(cb_ctx.sem);
|
||||
free(buf);
|
||||
|
||||
TEST_ASSERT_TRUE(callback_triggered);
|
||||
|
||||
/* SYNC_CHECK fires at 0.005 s. About 48000 * 2 * 0.005 = 480 samples
|
||||
* are sent, so ideal_cnt 1000 should produce diff_count around -520.
|
||||
*/
|
||||
TEST_ASSERT_INT32_WITHIN(52, -520, cb_ctx.diff_count);
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST_CASE("i2s_destination_test", "[i2s]")
|
||||
{
|
||||
i2s_chan_handle_t tx = NULL;
|
||||
|
||||
@@ -68,6 +68,7 @@ extern "C" {
|
||||
[I2S_ETM_TASK_SYNC_FIFO] = I2S0_TASK_SYNC_CHECK, \
|
||||
}}}[i2s_port][(chan_dir) - 1][task]
|
||||
#define I2S_LL_ETM_MAX_THRESH_NUM (0x3FFFUL)
|
||||
#define I2S_LL_TX_SYNC_INT_EVENT (BIT(4))
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -459,6 +460,7 @@ static inline void i2s_ll_rx_set_mclk(i2s_dev_t *hw, const hal_utils_clk_div_t *
|
||||
*
|
||||
* @param hw Peripheral I2S hardware instance address.
|
||||
*/
|
||||
__attribute__((always_inline))
|
||||
static inline void i2s_ll_tx_update(i2s_dev_t *hw)
|
||||
{
|
||||
hw->tx_conf.tx_update = 1;
|
||||
@@ -1311,32 +1313,30 @@ static inline void i2s_ll_tx_reset_fifo_sync_diff_counter(i2s_dev_t *hw)
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set TX FIFO synchronization difference counter software threshold
|
||||
* @note It determines the up threshold that the hardware synchronize the data automatically.
|
||||
* - If diff_count <= sw_threshold, the hardware will synchronize the data automatically.
|
||||
* - If diff_count > sw_threshold, the automatic synchronization is not proper for this case,
|
||||
* interrupt will be triggered to let the software decide how to handle this case.
|
||||
* @brief Set TX FIFO synchronization difference threshold for manual handling
|
||||
* @note If diff_count is greater than manual_threshold, a TX sync interrupt is triggered for software handling.
|
||||
*
|
||||
* @param hw Peripheral I2S hardware instance address.
|
||||
* @param thresh The threshold that send
|
||||
* @param thresh Difference counter threshold. When the difference count exceeds this value,
|
||||
* the TX sync interrupt is triggered.
|
||||
*/
|
||||
__attribute__((always_inline))
|
||||
static inline void i2s_ll_tx_set_fifo_sync_diff_conter_sw_threshold(i2s_dev_t *hw, uint32_t thresh)
|
||||
static inline void i2s_ll_tx_set_fifo_sync_diff_counter_manual_threshold(i2s_dev_t *hw, uint32_t thresh)
|
||||
{
|
||||
hw->sync_sw_thres.tx_cnt_diff_sw_thres = thresh;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set TX FIFO synchronization difference counter hardware threshold
|
||||
* @note It determines the down threshold that the hardware synchronize the data automatically.
|
||||
* - If diff_count < hw_threshold, synchronization check pass, do nothing
|
||||
* - If diff_count >= hw_threshold, the hardware will synchronize the data automatically.
|
||||
* @brief Set TX FIFO synchronization difference threshold for automatic hardware supplementation
|
||||
* @note If diff_count is greater than or equal to auto_threshold and less than manual_threshold,
|
||||
* hardware will synchronize the data automatically.
|
||||
*
|
||||
* @param hw Peripheral I2S hardware instance address.
|
||||
* @param thresh The threshold that send
|
||||
* @param thresh Difference counter threshold. When the difference count reaches this value,
|
||||
* hardware auto-synchronization is triggered.
|
||||
*/
|
||||
__attribute__((always_inline))
|
||||
static inline void i2s_ll_tx_set_fifo_sync_diff_conter_hw_threshold(i2s_dev_t *hw, uint32_t thresh)
|
||||
static inline void i2s_ll_tx_set_fifo_sync_diff_counter_auto_threshold(i2s_dev_t *hw, uint32_t thresh)
|
||||
{
|
||||
hw->sync_hw_thres.tx_cnt_diff_hw_thres = thresh;
|
||||
}
|
||||
@@ -1347,11 +1347,9 @@ static inline void i2s_ll_tx_set_fifo_sync_diff_conter_hw_threshold(i2s_dev_t *h
|
||||
*
|
||||
* @param hw Peripheral I2S hardware instance address.
|
||||
* @param mode Data supplementation mode
|
||||
* - 0: Supplement the last data
|
||||
* - 1: Supplement the data configured in `hw_sync_data` reg
|
||||
*/
|
||||
__attribute__((always_inline))
|
||||
static inline void i2s_ll_tx_set_hw_fifo_sync_suppl_mode(i2s_dev_t *hw, uint32_t mode)
|
||||
static inline void i2s_ll_tx_set_hw_fifo_sync_suppl_mode(i2s_dev_t *hw, i2s_tx_fifo_sync_suppl_mode_t mode)
|
||||
{
|
||||
hw->hw_sync_conf.tx_hw_sync_suppl_mode = mode;
|
||||
}
|
||||
@@ -1368,6 +1366,57 @@ static inline void i2s_ll_tx_set_hw_fifo_sync_static_suppl_data(i2s_dev_t *hw, u
|
||||
hw->hw_sync_data.tx_hw_sync_suppl_data = data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get I2S interrupt status register address
|
||||
*/
|
||||
__attribute__((always_inline))
|
||||
static inline volatile void *i2s_ll_get_interrupt_status_reg(i2s_dev_t *hw)
|
||||
{
|
||||
return (volatile void *)&hw->int_st;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get I2S interrupt status by mask
|
||||
*
|
||||
* @param hw Peripheral I2S hardware instance address.
|
||||
* @param mask Interrupt status mask
|
||||
* @return Interrupt status
|
||||
*/
|
||||
__attribute__((always_inline))
|
||||
static inline uint32_t i2s_ll_get_interrupt_status(i2s_dev_t *hw, uint32_t mask)
|
||||
{
|
||||
return hw->int_st.val & mask;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Clear I2S interrupt status by mask
|
||||
*
|
||||
* @param hw Peripheral I2S hardware instance address.
|
||||
* @param mask Interrupt status mask
|
||||
*/
|
||||
__attribute__((always_inline))
|
||||
static inline void i2s_ll_clear_interrupt_status(i2s_dev_t *hw, uint32_t mask)
|
||||
{
|
||||
hw->int_clr.val = mask;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Enable I2S interrupt for specific event mask
|
||||
*
|
||||
* @param hw Peripheral I2S hardware instance address.
|
||||
* @param mask Event mask
|
||||
* @param enable True to enable, false to disable
|
||||
*/
|
||||
__attribute__((always_inline))
|
||||
static inline void i2s_ll_enable_interrupt(i2s_dev_t *hw, uint32_t mask, bool enable)
|
||||
{
|
||||
if (enable) {
|
||||
hw->int_ena.val |= mask;
|
||||
} else {
|
||||
hw->int_ena.val &= ~mask;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set the TX ETM synchronization ideal count
|
||||
*
|
||||
|
||||
@@ -105,6 +105,7 @@ extern "C" {
|
||||
}[i2s_port][(chan_dir) - 1][task]
|
||||
|
||||
#define I2S_LL_ETM_MAX_THRESH_NUM (0x3FFFUL)
|
||||
#define I2S_LL_TX_SYNC_INT_EVENT (BIT(4))
|
||||
|
||||
/**
|
||||
* @brief Enable the bus clock for I2S module
|
||||
@@ -672,6 +673,7 @@ static inline void i2s_ll_rx_set_mclk(i2s_dev_t *hw, const hal_utils_clk_div_t *
|
||||
*
|
||||
* @param hw Peripheral I2S hardware instance address.
|
||||
*/
|
||||
__attribute__((always_inline))
|
||||
static inline void i2s_ll_tx_update(i2s_dev_t *hw)
|
||||
{
|
||||
hw->tx_conf.tx_update = 1;
|
||||
@@ -1832,32 +1834,30 @@ static inline void i2s_ll_tx_reset_fifo_sync_diff_counter(i2s_dev_t *hw)
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set TX FIFO synchronization difference counter software threshold
|
||||
* @note It determines the up threshold that the hardware synchronize the data automatically.
|
||||
* - If diff_count <= sw_threshold, the hardware will synchronize the data automatically.
|
||||
* - If diff_count > sw_threshold, the automatic synchronization is not proper for this case,
|
||||
* interrupt will be triggered to let the software decide how to handle this case.
|
||||
* @brief Set TX FIFO synchronization difference threshold for manual handling
|
||||
* @note If diff_count is greater than manual_threshold, a TX sync interrupt is triggered for software handling.
|
||||
*
|
||||
* @param hw Peripheral I2S hardware instance address.
|
||||
* @param thresh The threshold that send
|
||||
* @param thresh Difference counter threshold. When the difference count exceeds this value,
|
||||
* the TX sync interrupt is triggered.
|
||||
*/
|
||||
__attribute__((always_inline))
|
||||
static inline void i2s_ll_tx_set_fifo_sync_diff_conter_sw_threshold(i2s_dev_t *hw, uint32_t thresh)
|
||||
static inline void i2s_ll_tx_set_fifo_sync_diff_counter_manual_threshold(i2s_dev_t *hw, uint32_t thresh)
|
||||
{
|
||||
hw->sync_sw_thres.tx_cnt_diff_sw_thres = thresh;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set TX FIFO synchronization difference counter hardware threshold
|
||||
* @note It determines the down threshold that the hardware synchronize the data automatically.
|
||||
* - If diff_count < hw_threshold, synchronization check pass, do nothing
|
||||
* - If diff_count >= hw_threshold, the hardware will synchronize the data automatically.
|
||||
* @brief Set TX FIFO synchronization difference threshold for automatic hardware supplementation
|
||||
* @note If diff_count is greater than or equal to auto_threshold and less than manual_threshold,
|
||||
* hardware will synchronize the data automatically.
|
||||
*
|
||||
* @param hw Peripheral I2S hardware instance address.
|
||||
* @param thresh The threshold that send
|
||||
* @param thresh Difference counter threshold. When the difference count reaches this value,
|
||||
* hardware auto-synchronization is triggered.
|
||||
*/
|
||||
__attribute__((always_inline))
|
||||
static inline void i2s_ll_tx_set_fifo_sync_diff_conter_hw_threshold(i2s_dev_t *hw, uint32_t thresh)
|
||||
static inline void i2s_ll_tx_set_fifo_sync_diff_counter_auto_threshold(i2s_dev_t *hw, uint32_t thresh)
|
||||
{
|
||||
hw->sync_hw_thres.tx_cnt_diff_hw_thres = thresh;
|
||||
}
|
||||
@@ -1880,11 +1880,9 @@ static inline void i2s_ll_tx_enable_hw_fifo_sync(i2s_dev_t *hw, bool enable)
|
||||
*
|
||||
* @param hw Peripheral I2S hardware instance address.
|
||||
* @param mode Data supplementation mode
|
||||
* - 0: Supplement the last data
|
||||
* - 1: Supplement the data configured in `hw_sync_data` reg
|
||||
*/
|
||||
__attribute__((always_inline))
|
||||
static inline void i2s_ll_tx_set_hw_fifo_sync_suppl_mode(i2s_dev_t *hw, uint32_t mode)
|
||||
static inline void i2s_ll_tx_set_hw_fifo_sync_suppl_mode(i2s_dev_t *hw, i2s_tx_fifo_sync_suppl_mode_t mode)
|
||||
{
|
||||
hw->hw_sync_conf.tx_hw_sync_suppl_mode = mode;
|
||||
}
|
||||
@@ -1901,6 +1899,57 @@ static inline void i2s_ll_tx_set_hw_fifo_sync_static_suppl_data(i2s_dev_t *hw, u
|
||||
hw->hw_sync_data.tx_hw_sync_suppl_data = data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get I2S interrupt status register address
|
||||
*/
|
||||
__attribute__((always_inline))
|
||||
static inline volatile void *i2s_ll_get_interrupt_status_reg(i2s_dev_t *hw)
|
||||
{
|
||||
return (volatile void *)&hw->int_st;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get I2S interrupt status by mask
|
||||
*
|
||||
* @param hw Peripheral I2S hardware instance address.
|
||||
* @param mask Interrupt status mask
|
||||
* @return Interrupt status
|
||||
*/
|
||||
__attribute__((always_inline))
|
||||
static inline uint32_t i2s_ll_get_interrupt_status(i2s_dev_t *hw, uint32_t mask)
|
||||
{
|
||||
return hw->int_st.val & mask;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Clear I2S interrupt status by mask
|
||||
*
|
||||
* @param hw Peripheral I2S hardware instance address.
|
||||
* @param mask Interrupt status mask
|
||||
*/
|
||||
__attribute__((always_inline))
|
||||
static inline void i2s_ll_clear_interrupt_status(i2s_dev_t *hw, uint32_t mask)
|
||||
{
|
||||
hw->int_clr.val = mask;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Enable I2S interrupt for specific event mask
|
||||
*
|
||||
* @param hw Peripheral I2S hardware instance address.
|
||||
* @param mask Event mask
|
||||
* @param enable True to enable, false to disable
|
||||
*/
|
||||
__attribute__((always_inline))
|
||||
static inline void i2s_ll_enable_interrupt(i2s_dev_t *hw, uint32_t mask, bool enable)
|
||||
{
|
||||
if (enable) {
|
||||
hw->int_ena.val |= mask;
|
||||
} else {
|
||||
hw->int_ena.val &= ~mask;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check whether an I2S data destination is supported on the specified port
|
||||
*/
|
||||
|
||||
@@ -78,6 +78,16 @@ typedef enum {
|
||||
I2S_DESTINATION_BT = 1, /*!< Route I2S data to Bluetooth (I2S0 only; see `SOC_I2S_SUPPORTS_BT_DEST`) */
|
||||
} i2s_destination_t;
|
||||
|
||||
#if SOC_I2S_SUPPORTS_TX_FIFO_SYNC
|
||||
/**
|
||||
* @brief TX FIFO synchronization hardware data supplement mode
|
||||
*/
|
||||
typedef enum {
|
||||
I2S_TX_FIFO_SYNC_SUPPL_MODE_LAST_DATA = 0, /*!< Supplement with the last transmitted data */
|
||||
I2S_TX_FIFO_SYNC_SUPPL_MODE_STATIC_DATA = 1, /*!< Supplement with static data specified in config */
|
||||
} i2s_tx_fifo_sync_suppl_mode_t;
|
||||
#endif // SOC_I2S_SUPPORTS_TX_FIFO_SYNC
|
||||
|
||||
#if SOC_I2S_SUPPORTS_PCM
|
||||
/**
|
||||
* @brief A/U-law decompress or compress configuration.
|
||||
|
||||
@@ -313,6 +313,100 @@ To satisfy the high quality audio requirement, following advanced APIs are provi
|
||||
- :cpp:func:`i2s_channel_preload_data`: Preloading audio data into the I2S internal cache, enabling the TX channel to immediately send data upon activation, thereby reducing the initial audio output delay.
|
||||
- :cpp:func:`i2s_channel_tune_rate`: Dynamically fine-tuning the audio rate at runtime to match the speed of the audio data producer and consumer, thereby preventing the accumulation or shortage of intermediate buffered data that caused by rate mismatches.
|
||||
|
||||
.. only:: SOC_I2S_SUPPORTS_TX_SYNC_CNT
|
||||
|
||||
- :cpp:func:`i2s_channel_get_sync_count`: Read the TX BCLK/FIFO synchronization counters. This API can also actively clear them through the ``reset`` argument.
|
||||
|
||||
.. only:: SOC_I2S_SUPPORTS_TX_FIFO_SYNC
|
||||
|
||||
TX FIFO Synchronization
|
||||
"""""""""""""""""""""""
|
||||
|
||||
{IDF_TARGET_NAME} supports I2S TX FIFO synchronization. It can periodically trigger ``I2S_ETM_TASK_SYNC_FIFO`` task through ETM to check the difference between the actual TX FIFO data count and the expected count. This feature is useful when multiple I2S TX ports or an external timing source need to stay synchronized.
|
||||
|
||||
TX FIFO synchronization related APIs include:
|
||||
|
||||
- :cpp:func:`i2s_channel_get_sync_diff_count`: Read the TX FIFO synchronization difference counter. The value is signed and means ``I2S_TX_FIFO_CNT - I2S_TX_FIFO_IDEAL_CNT``.
|
||||
- :cpp:func:`i2s_channel_config_tx_fifo_sync`: Configure the expected count, automatic supplement threshold,
|
||||
manual supplement threshold, and hardware supplement mode. After automatic hardware supplementation is
|
||||
enabled, hardware automatically supplements or deletes data according to ``diff_count`` so that the actual
|
||||
count approaches ``ideal_cnt``.
|
||||
- :cpp:func:`i2s_channel_register_intr_event_callback`: Register the manual supplement threshold interrupt callback. When
|
||||
``diff_count`` exceeds the manual supplement threshold, the driver calls this callback in the ISR and provides
|
||||
``diff_count`` through :cpp:type:`i2s_sync_event_data_t`.
|
||||
|
||||
The typical usage steps are:
|
||||
|
||||
1. Create and initialize an I2S TX channel.
|
||||
2. Call :cpp:func:`i2s_channel_config_tx_fifo_sync` to configure :cpp:type:`i2s_tx_fifo_sync_config_t`. ``ideal_cnt``
|
||||
is the expected number of transmitted data units at each ETM synchronization check. ``auto_suppl_thresh`` is
|
||||
the automatic hardware supplement threshold: set it to ``0`` to disable automatic hardware supplementation, or
|
||||
set it to a value greater than ``0`` and smaller than ``manual_suppl_thresh`` to enable automatic hardware
|
||||
supplementation. ``manual_suppl_thresh`` is the threshold for triggering the callback for manual handling. Once
|
||||
enabled, if the difference exceeds the automatic supplement threshold but has not reached the manual supplement
|
||||
threshold, hardware automatically supplements or deletes the corresponding amount of data to synchronize with
|
||||
``ideal_cnt``.
|
||||
3. To handle severe out-of-sync conditions, call :cpp:func:`i2s_channel_register_intr_event_callback` to register a callback.
|
||||
After the callback is registered, the driver enables the TX synchronization interrupt. If the difference exceeds
|
||||
the manual supplement threshold, the driver calls this callback in the ISR and provides ``diff_count`` through
|
||||
:cpp:type:`i2s_sync_event_data_t`.
|
||||
4. Call :cpp:func:`i2s_new_etm_task` to create the ``I2S_ETM_TASK_SYNC_FIFO`` task, and connect an external ETM event to this task.
|
||||
5. Enable the ETM channel and I2S TX channel, so that ETM events periodically trigger synchronization checks.
|
||||
|
||||
The following example shows how to use a GPTimer alarm event to trigger the I2S TX FIFO synchronization check, and
|
||||
get ``diff_count`` in the manual supplement threshold interrupt:
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
#include "driver/i2s_common.h"
|
||||
#include "driver/i2s_etm.h"
|
||||
#include "driver/gptimer.h"
|
||||
#include "esp_etm.h"
|
||||
|
||||
/* Assume the I2S TX channel, GPTimer, and ETM channel have been created and initialized */
|
||||
i2s_chan_handle_t tx_handle;
|
||||
gptimer_handle_t timer;
|
||||
esp_etm_channel_handle_t etm_channel;
|
||||
|
||||
static bool IRAM_ATTR i2s_tx_sync_callback(i2s_chan_handle_t handle,
|
||||
const i2s_sync_event_data_t *event,
|
||||
void *user_ctx)
|
||||
{
|
||||
// event->diff_count = I2S_TX_FIFO_CNT - I2S_TX_FIFO_IDEAL_CNT
|
||||
return false;
|
||||
}
|
||||
|
||||
i2s_tx_fifo_sync_config_t sync_cfg = {
|
||||
.ideal_cnt = 1000,
|
||||
.manual_suppl_thresh = 64,
|
||||
.auto_suppl_thresh = 32,
|
||||
.suppl_mode = I2S_TX_FIFO_SYNC_SUPPL_MODE_LAST_DATA,
|
||||
};
|
||||
i2s_intr_event_callbacks_t intr_cbs = {
|
||||
.on_tx_sync = i2s_tx_sync_callback,
|
||||
};
|
||||
i2s_channel_config_tx_fifo_sync(tx_handle, &sync_cfg);
|
||||
i2s_channel_register_intr_event_callback(tx_handle, &intr_cbs, NULL);
|
||||
|
||||
i2s_etm_task_config_t i2s_task_cfg = {
|
||||
.task_type = I2S_ETM_TASK_SYNC_FIFO,
|
||||
};
|
||||
esp_etm_task_handle_t i2s_sync_task = NULL;
|
||||
i2s_new_etm_task(tx_handle, &i2s_task_cfg, &i2s_sync_task);
|
||||
|
||||
gptimer_etm_event_config_t timer_event_cfg = {
|
||||
.event_type = GPTIMER_ETM_EVENT_ALARM_MATCH,
|
||||
};
|
||||
esp_etm_event_handle_t timer_event = NULL;
|
||||
gptimer_new_etm_event(timer, &timer_event_cfg, &timer_event);
|
||||
|
||||
esp_etm_channel_connect(etm_channel, timer_event, i2s_sync_task);
|
||||
esp_etm_channel_enable(etm_channel);
|
||||
|
||||
.. note::
|
||||
|
||||
After ``I2S_ETM_TASK_SYNC_FIFO`` is triggered, hardware automatically clears the TX FIFO/BCLK synchronization counters.
|
||||
|
||||
.. _i2s-iram-safe:
|
||||
|
||||
IRAM Safe
|
||||
|
||||
@@ -313,6 +313,93 @@ I2S 的数据传输(包括数据发送和接收)由 DMA 实现。在传输
|
||||
- :cpp:func:`i2s_channel_preload_data`: 用于预加载音频数据到 I2S 内部缓存,使得 TX 通道使能后能够立即发送数据,以此降低音频初始输出延迟。
|
||||
- :cpp:func:`i2s_channel_tune_rate`: 用于在运行时动态微调音频速率,以匹配音频数据生产者和消费者的速度,从而防止因速率不匹配导致的中间缓存数据累积或不足。
|
||||
|
||||
.. only:: SOC_I2S_SUPPORTS_TX_SYNC_CNT
|
||||
|
||||
- :cpp:func:`i2s_channel_get_sync_count`:用于读取 TX BCLK/FIFO 同步计数器。该 API 也可通过 ``reset`` 参数主动清零计数器。
|
||||
|
||||
.. only:: SOC_I2S_SUPPORTS_TX_FIFO_SYNC
|
||||
|
||||
TX FIFO 同步
|
||||
""""""""""""
|
||||
|
||||
{IDF_TARGET_NAME} 支持 I2S TX FIFO 同步功能,可用于通过 ETM 周期性触发 ``I2S_ETM_TASK_SYNC_FIFO`` 任务,检查 TX FIFO 实际发送的数据计数与期望计数之间的偏差。该功能适用于需要多个 I2S TX 端口或外部时序源保持同步的场景。
|
||||
|
||||
TX FIFO 同步相关 API 包括:
|
||||
|
||||
- :cpp:func:`i2s_channel_get_sync_diff_count`:读取 TX FIFO 同步差值计数器。该值为有符号数,含义为 ``I2S_TX_FIFO_CNT - I2S_TX_FIFO_IDEAL_CNT``。
|
||||
- :cpp:func:`i2s_channel_config_tx_fifo_sync`:配置期望计数、自动补偿阈值、手动补偿阈值以及硬件补偿方式。
|
||||
启用硬件自动补偿后,硬件会根据 ``diff_count`` 自动补充或删除数据,使实际计数靠近 ``ideal_cnt``。
|
||||
- :cpp:func:`i2s_channel_register_intr_event_callback`:注册手动补偿阈值中断回调。当 ``diff_count`` 超过手动补偿阈值时,
|
||||
驱动会在 ISR 中调用该回调,并通过 :cpp:type:`i2s_sync_event_data_t` 提供 ``diff_count``。
|
||||
|
||||
使用该功能的一般步骤如下:
|
||||
|
||||
1. 创建并初始化 I2S TX 通道。
|
||||
2. 调用 :cpp:func:`i2s_channel_config_tx_fifo_sync` 配置 :cpp:type:`i2s_tx_fifo_sync_config_t`。其中 ``ideal_cnt``
|
||||
为每次 ETM 同步检查时期望发送的数据个数;``auto_suppl_thresh`` 为硬件自动补偿阈值,设置为 ``0`` 表示
|
||||
关闭硬件自动补偿,设置为大于 ``0`` 且小于 ``manual_suppl_thresh`` 表示开启硬件自动补偿;
|
||||
``manual_suppl_thresh`` 为触发回调并交由软件手动处理的阈值。开启后,如果偏差超过自动补偿阈值但尚未达到
|
||||
手动补偿阈值,硬件会自动补充或删除相应数量的数据,以实现与 ``ideal_cnt`` 同步。
|
||||
3. 如需处理严重不同步场景,调用 :cpp:func:`i2s_channel_register_intr_event_callback` 注册回调。注册回调后,驱动会使能
|
||||
TX 同步中断;如果偏差超过手动补偿阈值,驱动会在 ISR 中调用该回调,并通过
|
||||
:cpp:type:`i2s_sync_event_data_t` 提供 ``diff_count``。
|
||||
4. 调用 :cpp:func:`i2s_new_etm_task` 创建 ``I2S_ETM_TASK_SYNC_FIFO`` 任务,并将外部 ETM 事件连接到该任务。
|
||||
5. 使能 ETM 通道和 I2S TX 通道,由 ETM 事件周期性触发同步检查。
|
||||
|
||||
以下示例展示了如何使用 GPTimer alarm event 触发 I2S TX FIFO 同步检查,并在手动补偿阈值中断中获取
|
||||
``diff_count``:
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
#include "driver/i2s_common.h"
|
||||
#include "driver/i2s_etm.h"
|
||||
#include "driver/gptimer.h"
|
||||
#include "esp_etm.h"
|
||||
|
||||
/* 假设已经创建并初始化 I2S TX 通道、GPTimer 和 ETM 通道 */
|
||||
i2s_chan_handle_t tx_handle;
|
||||
gptimer_handle_t timer;
|
||||
esp_etm_channel_handle_t etm_channel;
|
||||
|
||||
static bool IRAM_ATTR i2s_tx_sync_callback(i2s_chan_handle_t handle,
|
||||
const i2s_sync_event_data_t *event,
|
||||
void *user_ctx)
|
||||
{
|
||||
// event->diff_count = I2S_TX_FIFO_CNT - I2S_TX_FIFO_IDEAL_CNT
|
||||
return false;
|
||||
}
|
||||
|
||||
i2s_tx_fifo_sync_config_t sync_cfg = {
|
||||
.ideal_cnt = 1000,
|
||||
.manual_suppl_thresh = 64,
|
||||
.auto_suppl_thresh = 32,
|
||||
.suppl_mode = I2S_TX_FIFO_SYNC_SUPPL_MODE_LAST_DATA,
|
||||
};
|
||||
i2s_intr_event_callbacks_t intr_cbs = {
|
||||
.on_tx_sync = i2s_tx_sync_callback,
|
||||
};
|
||||
i2s_channel_config_tx_fifo_sync(tx_handle, &sync_cfg);
|
||||
i2s_channel_register_intr_event_callback(tx_handle, &intr_cbs, NULL);
|
||||
|
||||
i2s_etm_task_config_t i2s_task_cfg = {
|
||||
.task_type = I2S_ETM_TASK_SYNC_FIFO,
|
||||
};
|
||||
esp_etm_task_handle_t i2s_sync_task = NULL;
|
||||
i2s_new_etm_task(tx_handle, &i2s_task_cfg, &i2s_sync_task);
|
||||
|
||||
gptimer_etm_event_config_t timer_event_cfg = {
|
||||
.event_type = GPTIMER_ETM_EVENT_ALARM_MATCH,
|
||||
};
|
||||
esp_etm_event_handle_t timer_event = NULL;
|
||||
gptimer_new_etm_event(timer, &timer_event_cfg, &timer_event);
|
||||
|
||||
esp_etm_channel_connect(etm_channel, timer_event, i2s_sync_task);
|
||||
esp_etm_channel_enable(etm_channel);
|
||||
|
||||
.. note::
|
||||
|
||||
``I2S_ETM_TASK_SYNC_FIFO`` 触发后,硬件会自动清零 TX FIFO/BCLK 同步计数器。
|
||||
|
||||
.. _i2s-iram-safe:
|
||||
|
||||
IRAM 安全
|
||||
|
||||
Reference in New Issue
Block a user