diff --git a/components/esp_driver_i2s/i2s_common.c b/components/esp_driver_i2s/i2s_common.c index e4178cad53..96c8b053b0 100644 --- a/components/esp_driver_i2s/i2s_common.c +++ b/components/esp_driver_i2s/i2s_common.c @@ -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; diff --git a/components/esp_driver_i2s/i2s_private.h b/components/esp_driver_i2s/i2s_private.h index 89bff1cceb..b734f650d8 100644 --- a/components/esp_driver_i2s/i2s_private.h +++ b/components/esp_driver_i2s/i2s_private.h @@ -164,6 +164,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 diff --git a/components/esp_driver_i2s/i2s_std.c b/components/esp_driver_i2s/i2s_std.c index f2743b9f20..701b6df4ee 100644 --- a/components/esp_driver_i2s/i2s_std.c +++ b/components/esp_driver_i2s/i2s_std.c @@ -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 diff --git a/components/esp_driver_i2s/i2s_tdm.c b/components/esp_driver_i2s/i2s_tdm.c index e5d3f7892a..1b81620494 100644 --- a/components/esp_driver_i2s/i2s_tdm.c +++ b/components/esp_driver_i2s/i2s_tdm.c @@ -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 diff --git a/components/esp_driver_i2s/include/driver/i2s_std.h b/components/esp_driver_i2s/include/driver/i2s_std.h index d41af89418..d2ed0fbe2a 100644 --- a/components/esp_driver_i2s/include/driver/i2s_std.h +++ b/components/esp_driver_i2s/include/driver/i2s_std.h @@ -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 diff --git a/components/esp_driver_i2s/include/driver/i2s_tdm.h b/components/esp_driver_i2s/include/driver/i2s_tdm.h index ae07a24475..b8db9cc138 100644 --- a/components/esp_driver_i2s/include/driver/i2s_tdm.h +++ b/components/esp_driver_i2s/include/driver/i2s_tdm.h @@ -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 diff --git a/components/esp_driver_i2s/test_apps/i2s/main/test_i2s.c b/components/esp_driver_i2s/test_apps/i2s/main/test_i2s.c index 926d7cba85..9c949a1d0a 100644 --- a/components/esp_driver_i2s/test_apps/i2s/main/test_i2s.c +++ b/components/esp_driver_i2s/test_apps/i2s/main/test_i2s.c @@ -191,6 +191,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)); @@ -206,6 +238,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; @@ -231,6 +281,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; @@ -358,6 +409,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; diff --git a/docs/en/api-reference/peripherals/i2s.rst b/docs/en/api-reference/peripherals/i2s.rst index 0f973b0b1b..87f7f1d3ee 100644 --- a/docs/en/api-reference/peripherals/i2s.rst +++ b/docs/en/api-reference/peripherals/i2s.rst @@ -905,7 +905,9 @@ Full-duplex mode registers TX and RX channel in an I2S port at the same time, an Note that one handle can only stand for one channel. Therefore, it is still necessary to configure the slot and clock for both TX and RX channels one by one. -Here is an example of how to allocate a pair of full-duplex channels: +There are two methods to allocate a pair of full-duplex channels: + +1. Allocate both TX and RX handles in a single call of :cpp:func:`i2s_new_channel`. .. code-block:: c @@ -945,6 +947,48 @@ Here is an example of how to allocate a pair of full-duplex channels: ... +2. Allocate TX and RX handles separately, and initialize them with the same configuration. + +.. code-block:: c + + #include "driver/i2s_std.h" + #include "driver/gpio.h" + + i2s_chan_handle_t tx_handle; + i2s_chan_handle_t rx_handle; + + /* Allocate a pair of I2S channels on a same port */ + i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_0, I2S_ROLE_MASTER); + /* Allocate for TX and RX channel separately, they are not full-duplex yet */ + ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, &tx_handle, NULL)); + + /* Set the configurations for BOTH TWO channels, they will constitute in full-duplex mode automatically */ + i2s_std_config_t std_cfg = { + .clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(32000), + .slot_cfg = I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_STEREO), + .gpio_cfg = { + .mclk = I2S_GPIO_UNUSED, + .bclk = GPIO_NUM_4, + .ws = GPIO_NUM_5, + .dout = GPIO_NUM_18, + .din = GPIO_NUM_19, + .invert_flags = { + .mclk_inv = false, + .bclk_inv = false, + .ws_inv = false, + }, + }, + }; + ESP_ERROR_CHECK(i2s_channel_init_std_mode(tx_handle, &std_cfg)); + ESP_ERROR_CHECK(i2s_channel_enable(tx_handle)); + // ... + ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, NULL, &rx_handle)); + ESP_ERROR_CHECK(i2s_channel_init_std_mode(rx_handle, &std_cfg)); + ESP_ERROR_CHECK(i2s_channel_enable(rx_handle)); + + ... + + .. only:: SOC_I2S_HW_VERSION_1 Simplex Mode @@ -961,7 +1005,7 @@ Here is an example of how to allocate a pair of full-duplex channels: i2s_chan_handle_t rx_handle; i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_AUTO, I2S_ROLE_MASTER); - i2s_new_channel(&chan_cfg, &tx_handle, NULL); + ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, &tx_handle, NULL)); i2s_std_config_t std_tx_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), @@ -979,12 +1023,12 @@ Here is an example of how to allocate a pair of full-duplex channels: }, }; /* Initialize the channel */ - i2s_channel_init_std_mode(tx_handle, &std_tx_cfg); - i2s_channel_enable(tx_handle); + ESP_ERROR_CHECK(i2s_channel_init_std_mode(tx_handle, &std_tx_cfg)); + ESP_ERROR_CHECK(i2s_channel_enable(tx_handle)); /* RX channel will be registered on another I2S, if no other available I2S unit found * it will return ESP_ERR_NOT_FOUND */ - i2s_new_channel(&chan_cfg, NULL, &rx_handle); + ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, NULL, &rx_handle)); i2s_std_config_t std_rx_cfg = { .clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(16000), .slot_cfg = I2S_STD_MSB_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_32BIT, I2S_SLOT_MODE_STEREO), @@ -1001,8 +1045,8 @@ Here is an example of how to allocate a pair of full-duplex channels: }, }, }; - i2s_channel_init_std_mode(rx_handle, &std_rx_cfg); - i2s_channel_enable(rx_handle); + ESP_ERROR_CHECK(i2s_channel_init_std_mode(rx_handle, &std_rx_cfg)); + ESP_ERROR_CHECK(i2s_channel_enable(rx_handle)); .. only:: SOC_I2S_HW_VERSION_2 @@ -1021,7 +1065,7 @@ Here is an example of how to allocate a pair of full-duplex channels: 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_new_channel(&chan_cfg, &tx_handle, NULL); + ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, &tx_handle, NULL)); i2s_std_config_t std_tx_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), @@ -1039,12 +1083,12 @@ Here is an example of how to allocate a pair of full-duplex channels: }, }; /* Initialize the channel */ - i2s_channel_init_std_mode(tx_handle, &std_tx_cfg); - i2s_channel_enable(tx_handle); + ESP_ERROR_CHECK(i2s_channel_init_std_mode(tx_handle, &std_tx_cfg)); + ESP_ERROR_CHECK(i2s_channel_enable(tx_handle)); /* RX channel will be registered on another I2S, if no other available I2S unit found * it will return ESP_ERR_NOT_FOUND */ - i2s_new_channel(&chan_cfg, NULL, &rx_handle); // Both RX and TX channel will be registered on I2S0, but they can work with different configurations. + ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, NULL, &rx_handle)); // Both RX and TX channel will be registered on I2S0, but they can work with different configurations. i2s_std_config_t std_rx_cfg = { .clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(16000), .slot_cfg = I2S_STD_MSB_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_32BIT, I2S_SLOT_MODE_STEREO), @@ -1061,8 +1105,8 @@ Here is an example of how to allocate a pair of full-duplex channels: }, }, }; - i2s_channel_init_std_mode(rx_handle, &std_rx_cfg); - i2s_channel_enable(rx_handle); + ESP_ERROR_CHECK(i2s_channel_init_std_mode(rx_handle, &std_rx_cfg)); + ESP_ERROR_CHECK(i2s_channel_enable(rx_handle)); .. only:: SOC_I2S_SUPPORTS_ETM diff --git a/docs/zh_CN/api-reference/peripherals/i2s.rst b/docs/zh_CN/api-reference/peripherals/i2s.rst index 95c14a0fa5..5c2bd85812 100644 --- a/docs/zh_CN/api-reference/peripherals/i2s.rst +++ b/docs/zh_CN/api-reference/peripherals/i2s.rst @@ -905,7 +905,9 @@ STD RX 模式 请注意,一个句柄只能代表一个通道,因此仍然需要对 TX 和 RX 通道逐个进行声道和时钟配置。 -以下示例展示了如何分配两个全双工通道: +驱动支持两种分配全双工通道的方法: + +1. 在调用 :cpp:func:`i2s_new_channel` 函数时,同时分配 TX 和 RX 通道两个通道。 .. code-block:: c @@ -945,6 +947,47 @@ STD RX 模式 ... +2. 调用两次 :cpp:func:`i2s_new_channel` 函数分别分配 TX 和 RX 通道,但使用相同配置初始化 TX 和 RX 通道。 + +.. code-block:: c + + #include "driver/i2s_std.h" + #include "driver/gpio.h" + + i2s_chan_handle_t tx_handle; + i2s_chan_handle_t rx_handle; + + /* 分配两个 I2S 通道 */ + i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_0, I2S_ROLE_MASTER); + /* 分别分配给 TX 和 RX 通道 */ + ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, &tx_handle, NULL)); + + /* 为两个通道设置完全相同的配置,TX 和 RX 将自动组成全双工模式 */ + i2s_std_config_t std_cfg = { + .clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(32000), + .slot_cfg = I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_STEREO), + .gpio_cfg = { + .mclk = I2S_GPIO_UNUSED, + .bclk = GPIO_NUM_4, + .ws = GPIO_NUM_5, + .dout = GPIO_NUM_18, + .din = GPIO_NUM_19, + .invert_flags = { + .mclk_inv = false, + .bclk_inv = false, + .ws_inv = false, + }, + }, + }; + ESP_ERROR_CHECK(i2s_channel_init_std_mode(tx_handle, &std_cfg)); + ESP_ERROR_CHECK(i2s_channel_enable(tx_handle)); + // ... + ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, NULL, &rx_handle)); + ESP_ERROR_CHECK(i2s_channel_init_std_mode(rx_handle, &std_cfg)); + ESP_ERROR_CHECK(i2s_channel_enable(rx_handle)); + + ... + .. only:: SOC_I2S_HW_VERSION_1 单工模式 @@ -961,7 +1004,7 @@ STD RX 模式 i2s_chan_handle_t rx_handle; i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_AUTO, I2S_ROLE_MASTER); - i2s_new_channel(&chan_cfg, &tx_handle, NULL); + ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, &tx_handle, NULL)); i2s_std_config_t std_tx_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), @@ -979,12 +1022,12 @@ STD RX 模式 }, }; /* 初始化通道 */ - i2s_channel_init_std_mode(tx_handle, &std_tx_cfg); - i2s_channel_enable(tx_handle); + ESP_ERROR_CHECK(i2s_channel_init_std_mode(tx_handle, &std_tx_cfg)); + ESP_ERROR_CHECK(i2s_channel_enable(tx_handle)); /* 如果没有找到其他可用的 I2S 设备,RX 通道将被注册在另一个 I2S 上 * 并返回 ESP_ERR_NOT_FOUND */ - i2s_new_channel(&chan_cfg, NULL, &rx_handle); + ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, NULL, &rx_handle)); i2s_std_config_t std_rx_cfg = { .clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(16000), .slot_cfg = I2S_STD_MSB_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_32BIT, I2S_SLOT_MODE_STEREO), @@ -1001,8 +1044,8 @@ STD RX 模式 }, }, }; - i2s_channel_init_std_mode(rx_handle, &std_rx_cfg); - i2s_channel_enable(rx_handle); + ESP_ERROR_CHECK(i2s_channel_init_std_mode(rx_handle, &std_rx_cfg)); + ESP_ERROR_CHECK(i2s_channel_enable(rx_handle)); .. only:: SOC_I2S_HW_VERSION_2 @@ -1021,7 +1064,7 @@ STD RX 模式 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_new_channel(&chan_cfg, &tx_handle, NULL); + ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, &tx_handle, NULL)); i2s_std_config_t std_tx_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), @@ -1039,12 +1082,12 @@ STD RX 模式 }, }; /* 初始化通道 */ - i2s_channel_init_std_mode(tx_handle, &std_tx_cfg); - i2s_channel_enable(tx_handle); + ESP_ERROR_CHECK(i2s_channel_init_std_mode(tx_handle, &std_tx_cfg)); + ESP_ERROR_CHECK(i2s_channel_enable(tx_handle)); /* 如果没有找到其他可用的 I2S 设备,RX 通道将被注册在另一个 I2S 上 * 并返回 ESP_ERR_NOT_FOUND */ - i2s_new_channel(&chan_cfg, NULL, &rx_handle); // RX 和 TX 通道都将注册在 I2S0 上,但配置可以不同 + ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, NULL, &rx_handle)); // RX 和 TX 通道都将注册在 I2S0 上,但配置可以不同 i2s_std_config_t std_rx_cfg = { .clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(16000), .slot_cfg = I2S_STD_MSB_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_32BIT, I2S_SLOT_MODE_STEREO), @@ -1061,8 +1104,8 @@ STD RX 模式 }, }, }; - i2s_channel_init_std_mode(rx_handle, &std_rx_cfg); - i2s_channel_enable(rx_handle); + ESP_ERROR_CHECK(i2s_channel_init_std_mode(rx_handle, &std_rx_cfg)); + ESP_ERROR_CHECK(i2s_channel_enable(rx_handle)); .. only:: SOC_I2S_SUPPORTS_ETM