feat(i2s): release i2s tx sync APIs

This commit is contained in:
Chen Chen
2026-07-13 14:43:55 +08:00
parent f44f4a82e6
commit c27bd91874
14 changed files with 746 additions and 246 deletions
+151 -55
View File
@@ -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
+1 -1
View File
@@ -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;
}
+5
View File
@@ -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
+2 -8
View File
@@ -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;