mirror of
https://github.com/espressif/esp-idf.git
synced 2026-10-02 11:10:54 +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;
|
||||
|
||||
Reference in New Issue
Block a user