Merge branch 'feature/i2s_support_merge_simplex_to_duplex_v5.5' into 'release/v5.5'

feat(i2s): support to lazy constitute full-duplex mode (v5.5)

See merge request espressif/esp-idf!41842
This commit is contained in:
morris
2025-10-14 11:58:57 +08:00
9 changed files with 388 additions and 86 deletions
-6
View File
@@ -297,12 +297,6 @@ static inline bool i2s_take_available_channel(i2s_controller_t *i2s_obj, uint8_t
{
bool is_available = false;
#if SOC_I2S_HW_VERSION_1
/* In ESP32 and ESP32-S2, tx channel and rx channel are not totally separated
* Take both two channels in case one channel can affect another
*/
chan_search_mask = I2S_DIR_RX | I2S_DIR_TX;
#endif
portENTER_CRITICAL(&g_i2s.spinlock);
if (!(chan_search_mask & i2s_obj->chan_occupancy)) {
i2s_obj->chan_occupancy |= chan_search_mask;
+1
View File
@@ -163,6 +163,7 @@ struct i2s_channel_obj_t {
bool is_etm_stop: 1; /*!< Whether stop by etm tasks */
bool is_raw_pdm: 1; /*!< Flag of whether send/receive PDM in raw data, i.e., no PCM2PDM/PDM2PCM filter enabled */
bool is_external: 1; /*!< Whether use external clock */
bool full_duplex_slave: 1; /*!< whether the channel is forced to switch to slave role for full duplex */
#if SOC_I2S_SUPPORTS_APLL
bool apll_en: 1; /*!< Flag of whether APLL enabled */
#endif
+86 -27
View File
@@ -33,9 +33,10 @@ static esp_err_t i2s_std_calculate_clock(i2s_chan_handle_t handle, const i2s_std
uint32_t slot_bits = (slot_cfg->slot_bit_width == I2S_SLOT_BIT_WIDTH_AUTO) ||
((int)slot_cfg->slot_bit_width < (int)slot_cfg->data_bit_width) ?
slot_cfg->data_bit_width : slot_cfg->slot_bit_width;
slot_cfg->slot_bit_width = slot_bits;
/* Calculate multiple
* Fmclk = bck_div*fbck = fsclk/(mclk_div+b/a) */
if (handle->role == I2S_ROLE_MASTER) {
if (handle->role == I2S_ROLE_MASTER || handle->full_duplex_slave) {
clk_info->bclk = rate * handle->total_slot * slot_bits;
clk_info->mclk = rate * clk_cfg->mclk_multiple;
clk_info->bclk_div = clk_info->mclk / clk_info->bclk;
@@ -122,18 +123,13 @@ static esp_err_t i2s_std_set_slot(i2s_chan_handle_t handle, const i2s_std_slot_c
ESP_RETURN_ON_ERROR(i2s_alloc_dma_desc(handle, buf_size),
TAG, "allocate memory for dma descriptor failed");
}
bool is_slave = handle->role == I2S_ROLE_SLAVE;
/* Share bck and ws signal in full-duplex mode */
if (handle->controller->full_duplex) {
i2s_ll_share_bck_ws(handle->controller->hal.dev, true);
/* Since bck and ws are shared, only tx or rx can be master
Force to set rx as slave to avoid conflict of clock signal */
if (handle->dir == I2S_DIR_RX) {
is_slave = true;
}
} else {
i2s_ll_share_bck_ws(handle->controller->hal.dev, false);
}
bool is_slave = handle->role == I2S_ROLE_SLAVE;
portENTER_CRITICAL(&g_i2s.spinlock);
/* Configure the hardware to apply STD format */
@@ -178,43 +174,101 @@ static esp_err_t i2s_std_set_gpio(i2s_chan_handle_t handle, const i2s_std_gpio_c
/* Set mclk pin */
ESP_RETURN_ON_ERROR(i2s_check_set_mclk(handle, id, gpio_cfg->mclk, std_cfg->clk_cfg.clk_src, gpio_cfg->invert_flags.mclk_inv), TAG, "mclk config failed");
if (handle->role == I2S_ROLE_SLAVE) {
/* For "tx + slave" mode, select TX signal index for ws and bck */
if (handle->dir == I2S_DIR_TX && !handle->controller->full_duplex) {
#if SOC_I2S_HW_VERSION_2
/* Bind the MCLK signal to the TX or RX clock source */
if (!handle->controller->full_duplex) {
if (handle->dir == I2S_DIR_TX) {
I2S_CLOCK_SRC_ATOMIC() {
i2s_ll_mclk_bind_to_tx_clk(handle->controller->hal.dev);
}
#endif
i2s_gpio_check_and_set(handle, gpio_cfg->ws, i2s_periph_signal[id].s_tx_ws_sig, true, gpio_cfg->invert_flags.ws_inv);
i2s_gpio_check_and_set(handle, gpio_cfg->bclk, i2s_periph_signal[id].s_tx_bck_sig, true, gpio_cfg->invert_flags.bclk_inv);
/* For "tx + rx + slave" or "rx + slave" mode, select RX signal index for ws and bck */
} else {
i2s_gpio_check_and_set(handle, gpio_cfg->ws, i2s_periph_signal[id].s_rx_ws_sig, true, gpio_cfg->invert_flags.ws_inv);
i2s_gpio_check_and_set(handle, gpio_cfg->bclk, i2s_periph_signal[id].s_rx_bck_sig, true, gpio_cfg->invert_flags.bclk_inv);
}
} else {
/* For "rx + master" mode, select RX signal index for ws and bck */
if (handle->dir == I2S_DIR_RX && !handle->controller->full_duplex) {
#if SOC_I2S_HW_VERSION_2
I2S_CLOCK_SRC_ATOMIC() {
i2s_ll_mclk_bind_to_rx_clk(handle->controller->hal.dev);
}
#endif
i2s_gpio_check_and_set(handle, gpio_cfg->ws, i2s_periph_signal[id].m_rx_ws_sig, false, gpio_cfg->invert_flags.ws_inv);
i2s_gpio_check_and_set(handle, gpio_cfg->bclk, i2s_periph_signal[id].m_rx_bck_sig, false, gpio_cfg->invert_flags.bclk_inv);
/* For "tx + rx + master" or "tx + master" mode, select TX signal index for ws and bck */
}
} else if (handle->role == I2S_ROLE_MASTER) {
if (handle->dir == I2S_DIR_TX) {
I2S_CLOCK_SRC_ATOMIC() {
i2s_ll_mclk_bind_to_tx_clk(handle->controller->hal.dev);
}
} else {
i2s_gpio_check_and_set(handle, gpio_cfg->ws, i2s_periph_signal[id].m_tx_ws_sig, false, gpio_cfg->invert_flags.ws_inv);
i2s_gpio_check_and_set(handle, gpio_cfg->bclk, i2s_periph_signal[id].m_tx_bck_sig, false, gpio_cfg->invert_flags.bclk_inv);
I2S_CLOCK_SRC_ATOMIC() {
i2s_ll_mclk_bind_to_rx_clk(handle->controller->hal.dev);
}
}
}
#endif
uint32_t ws_sig = 0;
uint32_t bck_sig = 0;
bool is_input = handle->role == I2S_ROLE_SLAVE;
if (handle->role == I2S_ROLE_SLAVE) {
// Assign slave signals
if (handle->dir == I2S_DIR_TX) {
ws_sig = i2s_periph_signal[id].s_tx_ws_sig;
bck_sig = i2s_periph_signal[id].s_tx_bck_sig;
} else {
ws_sig = i2s_periph_signal[id].s_rx_ws_sig;
bck_sig = i2s_periph_signal[id].s_rx_bck_sig;
}
} else {
// Assign master signals
if (handle->dir == I2S_DIR_TX) {
ws_sig = i2s_periph_signal[id].m_tx_ws_sig;
bck_sig = i2s_periph_signal[id].m_tx_bck_sig;
} else {
ws_sig = i2s_periph_signal[id].m_rx_ws_sig;
bck_sig = i2s_periph_signal[id].m_rx_bck_sig;
}
}
i2s_gpio_check_and_set(handle, gpio_cfg->ws, ws_sig, is_input, gpio_cfg->invert_flags.ws_inv);
i2s_gpio_check_and_set(handle, gpio_cfg->bclk, bck_sig, is_input, gpio_cfg->invert_flags.bclk_inv);
/* Update the mode info: gpio configuration */
memcpy(&(std_cfg->gpio_cfg), gpio_cfg, sizeof(i2s_std_gpio_config_t));
return ESP_OK;
}
static esp_err_t s_i2s_channel_try_to_constitude_std_duplex(i2s_chan_handle_t handle, const i2s_std_config_t *std_cfg)
{
/* Get another direction handle */
i2s_chan_handle_t another_handle = handle->dir == I2S_DIR_RX ? handle->controller->tx_chan : handle->controller->rx_chan;
/* Condition: 1. Another direction channel is registered
* 2. Not a full-duplex channel yet
* 3. Another channel is initialized, try to compare the configurations */
if (another_handle && another_handle->state >= I2S_CHAN_STATE_READY) {
/* Judge if the two channels can constitute full-duplex */
if (!handle->controller->full_duplex) {
i2s_std_config_t curr_cfg = *std_cfg;
/* Override the slot bit width to the actual slot bit width */
curr_cfg.slot_cfg.slot_bit_width = (int)curr_cfg.slot_cfg.slot_bit_width < (int)curr_cfg.slot_cfg.data_bit_width ?
curr_cfg.slot_cfg.data_bit_width : curr_cfg.slot_cfg.slot_bit_width;
/* Compare the hardware configurations of the two channels, constitute the full-duplex if they are the same */
if (memcmp(another_handle->mode_info, &curr_cfg, sizeof(i2s_std_config_t)) == 0) {
handle->controller->full_duplex = true;
ESP_LOGD(TAG, "Constitude full-duplex on port %d", handle->controller->id);
}
#if SOC_I2S_HW_VERSION_1
else {
ESP_LOGE(TAG, "Can't set different channel configurations on a same port");
return ESP_ERR_INVALID_ARG;
}
#endif
}
/* Switch to the slave role if needed */
if (handle->controller->full_duplex &&
handle->role == I2S_ROLE_MASTER &&
another_handle->role == I2S_ROLE_MASTER) {
/* The later initialized channel must be slave for full duplex */
handle->role = I2S_ROLE_SLAVE;
handle->full_duplex_slave = true;
}
}
return ESP_OK;
}
esp_err_t i2s_channel_init_std_mode(i2s_chan_handle_t handle, const i2s_std_config_t *std_cfg)
{
#if CONFIG_I2S_ENABLE_DEBUG_LOG
@@ -232,6 +286,11 @@ esp_err_t i2s_channel_init_std_mode(i2s_chan_handle_t handle, const i2s_std_conf
handle->mode_info = calloc(1, sizeof(i2s_std_config_t));
ESP_GOTO_ON_FALSE(handle->mode_info, ESP_ERR_NO_MEM, err, TAG, "no memory for storing the configurations");
ESP_GOTO_ON_FALSE(handle->state == I2S_CHAN_STATE_REGISTER, ESP_ERR_INVALID_STATE, err, TAG, "the channel has initialized already");
/* Try to constitute full-duplex mode if the STD configuration is totally same as another channel */
ret = s_i2s_channel_try_to_constitude_std_duplex(handle, std_cfg);
#if SOC_I2S_HW_VERSION_1
ESP_GOTO_ON_ERROR(ret, err, TAG, "Failed to constitute full-duplex mode");
#endif
/* i2s_set_std_slot should be called before i2s_set_std_clock while initializing, because clock is relay on the slot */
ESP_GOTO_ON_ERROR(i2s_std_set_slot(handle, &std_cfg->slot_cfg), err, TAG, "initialize channel failed while setting slot");
#if SOC_I2S_SUPPORTS_APLL
+74 -27
View File
@@ -34,9 +34,10 @@ static esp_err_t i2s_tdm_calculate_clock(i2s_chan_handle_t handle, const i2s_tdm
uint32_t slot_bits = (slot_cfg->slot_bit_width == I2S_SLOT_BIT_WIDTH_AUTO) ||
((int)slot_cfg->slot_bit_width < (int)slot_cfg->data_bit_width) ?
slot_cfg->data_bit_width : slot_cfg->slot_bit_width;
slot_cfg->slot_bit_width = slot_bits;
/* Calculate multiple
* Fmclk = bck_div*fbck = fsclk/(mclk_div+b/a) */
if (handle->role == I2S_ROLE_MASTER) {
if (handle->role == I2S_ROLE_MASTER || handle->full_duplex_slave) {
clk_info->bclk = rate * handle->total_slot * slot_bits;
clk_info->mclk = rate * clk_cfg->mclk_multiple;
clk_info->bclk_div = clk_info->mclk / clk_info->bclk;
@@ -126,18 +127,13 @@ static esp_err_t i2s_tdm_set_slot(i2s_chan_handle_t handle, const i2s_tdm_slot_c
ESP_RETURN_ON_ERROR(i2s_alloc_dma_desc(handle, buf_size),
TAG, "allocate memory for dma descriptor failed");
}
bool is_slave = handle->role == I2S_ROLE_SLAVE;
/* Share bck and ws signal in full-duplex mode */
if (handle->controller->full_duplex) {
i2s_ll_share_bck_ws(handle->controller->hal.dev, true);
/* Since bck and ws are shared, only tx or rx can be master
Force to set rx as slave to avoid conflict of clock signal */
if (handle->dir == I2S_DIR_RX) {
is_slave = true;
}
} else {
i2s_ll_share_bck_ws(handle->controller->hal.dev, false);
}
bool is_slave = handle->role == I2S_ROLE_SLAVE;
portENTER_CRITICAL(&g_i2s.spinlock);
/* Configure the hardware to apply TDM format */
@@ -183,43 +179,92 @@ static esp_err_t i2s_tdm_set_gpio(i2s_chan_handle_t handle, const i2s_tdm_gpio_c
/* Set mclk pin */
ESP_RETURN_ON_ERROR(i2s_check_set_mclk(handle, id, gpio_cfg->mclk, tdm_cfg->clk_cfg.clk_src, gpio_cfg->invert_flags.mclk_inv), TAG, "mclk config failed");
if (handle->role == I2S_ROLE_SLAVE) {
/* For "tx + slave" mode, select TX signal index for ws and bck */
if (handle->dir == I2S_DIR_TX && !handle->controller->full_duplex) {
#if SOC_I2S_HW_VERSION_2
/* Bind the MCLK signal to the TX or RX clock source */
if (!handle->controller->full_duplex) {
if (handle->dir == I2S_DIR_TX) {
I2S_CLOCK_SRC_ATOMIC() {
i2s_ll_mclk_bind_to_tx_clk(handle->controller->hal.dev);
}
#endif
i2s_gpio_check_and_set(handle, gpio_cfg->ws, i2s_periph_signal[id].s_tx_ws_sig, true, gpio_cfg->invert_flags.ws_inv);
i2s_gpio_check_and_set(handle, gpio_cfg->bclk, i2s_periph_signal[id].s_tx_bck_sig, true, gpio_cfg->invert_flags.bclk_inv);
/* For "tx + rx + slave" or "rx + slave" mode, select RX signal index for ws and bck */
} else {
i2s_gpio_check_and_set(handle, gpio_cfg->ws, i2s_periph_signal[id].s_rx_ws_sig, true, gpio_cfg->invert_flags.ws_inv);
i2s_gpio_check_and_set(handle, gpio_cfg->bclk, i2s_periph_signal[id].s_rx_bck_sig, true, gpio_cfg->invert_flags.bclk_inv);
}
} else {
/* For "rx + master" mode, select RX signal index for ws and bck */
if (handle->dir == I2S_DIR_RX && !handle->controller->full_duplex) {
#if SOC_I2S_HW_VERSION_2
I2S_CLOCK_SRC_ATOMIC() {
i2s_ll_mclk_bind_to_rx_clk(handle->controller->hal.dev);
}
#endif
i2s_gpio_check_and_set(handle, gpio_cfg->ws, i2s_periph_signal[id].m_rx_ws_sig, false, gpio_cfg->invert_flags.ws_inv);
i2s_gpio_check_and_set(handle, gpio_cfg->bclk, i2s_periph_signal[id].m_rx_bck_sig, false, gpio_cfg->invert_flags.bclk_inv);
/* For "tx + rx + master" or "tx + master" mode, select TX signal index for ws and bck */
}
} else if (handle->role == I2S_ROLE_MASTER) {
if (handle->dir == I2S_DIR_TX) {
I2S_CLOCK_SRC_ATOMIC() {
i2s_ll_mclk_bind_to_tx_clk(handle->controller->hal.dev);
}
} else {
i2s_gpio_check_and_set(handle, gpio_cfg->ws, i2s_periph_signal[id].m_tx_ws_sig, false, gpio_cfg->invert_flags.ws_inv);
i2s_gpio_check_and_set(handle, gpio_cfg->bclk, i2s_periph_signal[id].m_tx_bck_sig, false, gpio_cfg->invert_flags.bclk_inv);
I2S_CLOCK_SRC_ATOMIC() {
i2s_ll_mclk_bind_to_rx_clk(handle->controller->hal.dev);
}
}
}
#endif
uint32_t ws_sig = 0;
uint32_t bck_sig = 0;
bool is_input = handle->role == I2S_ROLE_SLAVE;
if (handle->role == I2S_ROLE_SLAVE) {
// Assign slave signals
if (handle->dir == I2S_DIR_TX) {
ws_sig = i2s_periph_signal[id].s_tx_ws_sig;
bck_sig = i2s_periph_signal[id].s_tx_bck_sig;
} else {
ws_sig = i2s_periph_signal[id].s_rx_ws_sig;
bck_sig = i2s_periph_signal[id].s_rx_bck_sig;
}
} else {
// Assign master signals
if (handle->dir == I2S_DIR_TX) {
ws_sig = i2s_periph_signal[id].m_tx_ws_sig;
bck_sig = i2s_periph_signal[id].m_tx_bck_sig;
} else {
ws_sig = i2s_periph_signal[id].m_rx_ws_sig;
bck_sig = i2s_periph_signal[id].m_rx_bck_sig;
}
}
i2s_gpio_check_and_set(handle, gpio_cfg->ws, ws_sig, is_input, gpio_cfg->invert_flags.ws_inv);
i2s_gpio_check_and_set(handle, gpio_cfg->bclk, bck_sig, is_input, gpio_cfg->invert_flags.bclk_inv);
/* Update the mode info: gpio configuration */
memcpy(&(tdm_cfg->gpio_cfg), gpio_cfg, sizeof(i2s_tdm_gpio_config_t));
return ESP_OK;
}
static void s_i2s_channel_try_to_constitude_tdm_duplex(i2s_chan_handle_t handle, const i2s_tdm_config_t *tdm_cfg)
{
/* Get another direction handle */
i2s_chan_handle_t another_handle = handle->dir == I2S_DIR_RX ? handle->controller->tx_chan : handle->controller->rx_chan;
/* Condition: 1. Another direction channel is registered
* 2. Not a full-duplex channel yet
* 3. Another channel is initialized, try to compare the configurations */
if (another_handle && another_handle->state >= I2S_CHAN_STATE_READY) {
if (!handle->controller->full_duplex) {
i2s_tdm_config_t curr_cfg = *tdm_cfg;
/* Override the slot bit width to the actual slot bit width */
curr_cfg.slot_cfg.slot_bit_width = (int)curr_cfg.slot_cfg.slot_bit_width < (int)curr_cfg.slot_cfg.data_bit_width ?
curr_cfg.slot_cfg.data_bit_width : curr_cfg.slot_cfg.slot_bit_width;
/* Compare the hardware configurations of the two channels, constitute the full-duplex if they are the same */
if (memcmp(another_handle->mode_info, &curr_cfg, sizeof(i2s_tdm_config_t)) == 0) {
handle->controller->full_duplex = true;
ESP_LOGD(TAG, "Constitude full-duplex on port %d", handle->controller->id);
}
}
/* Switch to the slave role if needed */
if (handle->controller->full_duplex &&
handle->role == I2S_ROLE_MASTER &&
another_handle->role == I2S_ROLE_MASTER) {
/* The later initialized channel must be slave for full duplex */
handle->role = I2S_ROLE_SLAVE;
handle->full_duplex_slave = true;
}
}
}
esp_err_t i2s_channel_init_tdm_mode(i2s_chan_handle_t handle, const i2s_tdm_config_t *tdm_cfg)
{
#if CONFIG_I2S_ENABLE_DEBUG_LOG
@@ -237,6 +282,8 @@ esp_err_t i2s_channel_init_tdm_mode(i2s_chan_handle_t handle, const i2s_tdm_conf
}
handle->mode_info = calloc(1, sizeof(i2s_tdm_config_t));
ESP_GOTO_ON_FALSE(handle->mode_info, ESP_ERR_NO_MEM, err, TAG, "no memory for storing the configurations");
/* Try to constitute full-duplex mode if the TDM configuration is totally same as another channel */
s_i2s_channel_try_to_constitude_tdm_duplex(handle, tdm_cfg);
/* i2s_set_tdm_slot should be called before i2s_set_tdm_clock while initializing, because clock is relay on the slot */
ESP_GOTO_ON_ERROR(i2s_tdm_set_slot(handle, &tdm_cfg->slot_cfg), err, TAG, "initialize channel failed while setting slot");
#if SOC_I2S_SUPPORTS_APLL
@@ -299,6 +299,8 @@ typedef struct {
* @brief Initialize I2S channel to standard mode
* @note Only allowed to be called when the channel state is REGISTERED, (i.e., channel has been allocated, but not initialized)
* and the state will be updated to READY if initialization success, otherwise the state will return to REGISTERED.
* @note When initialize the STD mode with a same configuration as another channel on a same port,
* these two channels can constitude as full-duplex mode automatically
*
* @param[in] handle I2S channel handler
* @param[in] std_cfg Configurations for standard mode, including clock, slot and GPIO
@@ -196,6 +196,8 @@ typedef struct {
* @brief Initialize I2S channel to TDM mode
* @note Only allowed to be called when the channel state is REGISTERED, (i.e., channel has been allocated, but not initialized)
* and the state will be updated to READY if initialization success, otherwise the state will return to REGISTERED.
* @note When initialize the TDM mode with a same configuration as another channel on a same port,
* these two channels can constitude as full-duplex mode automatically
*
* @param[in] handle I2S channel handler
* @param[in] tdm_cfg Configurations for TDM mode, including clock, slot and GPIO
@@ -192,6 +192,38 @@ TEST_CASE("I2S_basic_channel_allocation_reconfig_deleting_test", "[i2s]")
TEST_ESP_OK(i2s_del_channel(tx_handle));
TEST_ESP_OK(i2s_del_channel(rx_handle));
/* Lazy initialize std duplex test */
chan_cfg.id = I2S_NUM_0; // Specify port id to I2S port 0
TEST_ESP_OK(i2s_new_channel(&chan_cfg, &tx_handle, NULL));
TEST_ESP_OK(i2s_new_channel(&chan_cfg, NULL, &rx_handle));
TEST_ESP_OK(i2s_channel_get_info(tx_handle, &chan_info));
TEST_ASSERT(chan_info.pair_chan == NULL);
TEST_ESP_OK(i2s_channel_init_std_mode(tx_handle, &std_cfg));
TEST_ESP_OK(i2s_channel_init_std_mode(rx_handle, &std_cfg));
TEST_ESP_OK(i2s_channel_get_info(tx_handle, &chan_info));
TEST_ASSERT(chan_info.pair_chan == rx_handle);
TEST_ESP_OK(i2s_del_channel(tx_handle));
TEST_ESP_OK(i2s_del_channel(rx_handle));
#if SOC_I2S_SUPPORTS_TDM
/* Lazy initialize tdm duplex test */
TEST_ESP_OK(i2s_new_channel(&chan_cfg, NULL, &rx_handle));
TEST_ESP_OK(i2s_new_channel(&chan_cfg, &tx_handle, NULL));
TEST_ESP_OK(i2s_channel_get_info(tx_handle, &chan_info));
TEST_ASSERT(chan_info.pair_chan == NULL);
i2s_tdm_config_t tdm_cfg = {
.clk_cfg = I2S_TDM_CLK_DEFAULT_CONFIG(SAMPLE_RATE),
.slot_cfg = I2S_TDM_PHILIPS_SLOT_DEFAULT_CONFIG(SAMPLE_BITS, I2S_SLOT_MODE_STEREO, 0x0F),
.gpio_cfg = I2S_TEST_MASTER_DEFAULT_PIN,
};
TEST_ESP_OK(i2s_channel_init_tdm_mode(tx_handle, &tdm_cfg));
TEST_ESP_OK(i2s_channel_init_tdm_mode(rx_handle, &tdm_cfg));
TEST_ESP_OK(i2s_channel_get_info(tx_handle, &chan_info));
TEST_ASSERT(chan_info.pair_chan == rx_handle);
TEST_ESP_OK(i2s_del_channel(tx_handle));
TEST_ESP_OK(i2s_del_channel(rx_handle));
#endif
/* Repeat to check if a same port can be allocated again */
TEST_ESP_OK(i2s_new_channel(&chan_cfg, NULL, &rx_handle));
TEST_ESP_OK(i2s_del_channel(rx_handle));
@@ -207,6 +239,24 @@ TEST_CASE("I2S_basic_channel_allocation_reconfig_deleting_test", "[i2s]")
static volatile bool task_run_flag;
#define TEST_I2S_DATA 0x78
static void i2s_read_check_task(void *args)
{
i2s_chan_handle_t rx_handle = (i2s_chan_handle_t)args;
uint8_t *recv_buf = (uint8_t *)calloc(1, 2000);
TEST_ASSERT(recv_buf);
size_t recv_size = 0;
while (task_run_flag) {
TEST_ASSERT_EQUAL(i2s_channel_read(rx_handle, recv_buf, 2000, &recv_size, 300), ESP_OK);
TEST_ASSERT_EQUAL(recv_buf[0], TEST_I2S_DATA);
}
free(recv_buf);
vTaskDelete(NULL);
}
static void i2s_read_task(void *args)
{
i2s_chan_handle_t rx_handle = (i2s_chan_handle_t)args;
@@ -232,6 +282,7 @@ static void i2s_write_task(void *args)
i2s_chan_handle_t tx_handle = (i2s_chan_handle_t)args;
uint8_t *send_buf = (uint8_t *)calloc(1, 2000);
TEST_ASSERT(send_buf);
memset(send_buf, TEST_I2S_DATA, 2000);
size_t send_size = 0;
esp_err_t ret = ESP_OK;
uint32_t cnt = 1;
@@ -359,6 +410,65 @@ TEST_CASE("I2S_thread_concurrent_safety_test", "[i2s]")
TEST_ESP_OK(i2s_del_channel(rx_handle));
}
TEST_CASE("I2S_lazy_duplex_test", "[i2s]")
{
i2s_chan_handle_t tx_handle;
i2s_chan_handle_t rx_handle;
i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_0, I2S_ROLE_MASTER);
i2s_std_config_t std_cfg = {
.clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(SAMPLE_RATE),
.slot_cfg = I2S_STD_MSB_SLOT_DEFAULT_CONFIG(SAMPLE_BITS, I2S_SLOT_MODE_STEREO),
.gpio_cfg = {
.mclk = MASTER_MCK_IO,
.bclk = MASTER_BCK_IO,
.ws = MASTER_WS_IO,
.dout = DATA_OUT_IO,
.din = DATA_OUT_IO,
.invert_flags = {
.mclk_inv = false,
.bclk_inv = false,
.ws_inv = false,
},
},
};
TEST_ESP_OK(i2s_new_channel(&chan_cfg, &tx_handle, NULL));
TEST_ESP_OK(i2s_channel_init_std_mode(tx_handle, &std_cfg));
TEST_ESP_OK(i2s_channel_enable(tx_handle));
printf("Enabled TX channel\n");
TEST_ESP_OK(i2s_new_channel(&chan_cfg, NULL, &rx_handle));
TEST_ESP_OK(i2s_channel_init_std_mode(rx_handle, &std_cfg));
/* Enable the channels before creating reading/writing task*/
TEST_ESP_OK(i2s_channel_enable(rx_handle));
printf("Enabled RX channel\n");
task_run_flag = true;
/* writing task to keep writing */
xTaskCreate(i2s_write_task, "i2s_write_task", 4096, tx_handle, 5, NULL);
printf("TX started\n");
vTaskDelay(pdMS_TO_TICKS(1000));
/* reading task to keep reading */
xTaskCreate(i2s_read_check_task, "i2s_read_check_task", 4096, rx_handle, 5, NULL);
printf("RX started\n");
/* Wait 3 seconds to see if any failures occur */
vTaskDelay(pdMS_TO_TICKS(1000));
printf("Finished\n");
/* Stop those three tasks */
task_run_flag = false;
/* Wait for the three thread deleted */
vTaskDelay(pdMS_TO_TICKS(1000));
/* Disable the channels, they will keep waiting until the current reading / writing finished */
TEST_ESP_OK(i2s_channel_disable(tx_handle));
TEST_ESP_OK(i2s_channel_disable(rx_handle));
/* Delete the channels */
TEST_ESP_OK(i2s_del_channel(tx_handle));
TEST_ESP_OK(i2s_del_channel(rx_handle));
}
static bool whether_contains_exapected_data(uint16_t *src, uint32_t src_len, uint32_t src_step, uint32_t start_val, uint32_t val_step)
{
uint32_t val = start_val;