From 863d6eeec96894eac1359a732079b7be712ec86f Mon Sep 17 00:00:00 2001 From: laokaiyao Date: Mon, 26 May 2025 22:44:20 +0800 Subject: [PATCH] feat(i2s): support to lazy constitute full-duplex mode --- components/esp_driver_i2s/i2s_common.c | 16 +-- components/esp_driver_i2s/i2s_private.h | 1 + components/esp_driver_i2s/i2s_std.c | 113 +++++++++++++----- components/esp_driver_i2s/i2s_tdm.c | 101 +++++++++++----- .../esp_driver_i2s/include/driver/i2s_std.h | 2 + .../esp_driver_i2s/include/driver/i2s_tdm.h | 2 + .../test_apps/i2s/main/test_i2s.c | 110 +++++++++++++++++ docs/en/api-reference/peripherals/i2s.rst | 70 +++++++++-- docs/zh_CN/api-reference/peripherals/i2s.rst | 69 +++++++++-- 9 files changed, 393 insertions(+), 91 deletions(-) diff --git a/components/esp_driver_i2s/i2s_common.c b/components/esp_driver_i2s/i2s_common.c index 84eb46a5de..9af28dad87 100644 --- a/components/esp_driver_i2s/i2s_common.c +++ b/components/esp_driver_i2s/i2s_common.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -229,12 +229,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; @@ -810,12 +804,12 @@ void i2s_gpio_check_and_set(i2s_chan_handle_t handle, int gpio, uint32_t signal_ if (gpio != (int)I2S_GPIO_UNUSED) { gpio_hal_iomux_func_sel(GPIO_PIN_MUX_REG[gpio], PIN_FUNC_GPIO); if (is_input) { - /* Set direction, for some GPIOs, the input function are not enabled as default */ - gpio_set_direction(gpio, GPIO_MODE_INPUT); + /* Enable the input, for some GPIOs, the input function are not enabled as default */ + gpio_ll_input_enable(GPIO_HAL_GET_HW(GPIO_PORT_0), (gpio_num_t)gpio); esp_rom_gpio_connect_in_signal(gpio, signal_idx, is_invert); } else { i2s_output_gpio_reserve(handle, gpio); - gpio_set_direction(gpio, GPIO_MODE_OUTPUT); + /* output will be enabled in esp_rom_gpio_connect_out_signal */ esp_rom_gpio_connect_out_signal(gpio, signal_idx, is_invert, 0); } } @@ -826,7 +820,7 @@ void i2s_gpio_loopback_set(i2s_chan_handle_t handle, int gpio, uint32_t out_sig_ if (gpio != (int)I2S_GPIO_UNUSED) { i2s_output_gpio_reserve(handle, gpio); gpio_hal_iomux_func_sel(GPIO_PIN_MUX_REG[gpio], PIN_FUNC_GPIO); - gpio_set_direction(gpio, GPIO_MODE_INPUT_OUTPUT); + gpio_ll_input_enable(GPIO_HAL_GET_HW(GPIO_PORT_0), (gpio_num_t)gpio); esp_rom_gpio_connect_out_signal(gpio, out_sig_idx, 0, 0); esp_rom_gpio_connect_in_signal(gpio, in_sig_idx, 0); } diff --git a/components/esp_driver_i2s/i2s_private.h b/components/esp_driver_i2s/i2s_private.h index 6ae68a71fd..1675baf9f3 100644 --- a/components/esp_driver_i2s/i2s_private.h +++ b/components/esp_driver_i2s/i2s_private.h @@ -135,6 +135,7 @@ struct i2s_channel_obj_t { /* Stored configurations */ int intr_prio_flags;/*!< i2s interrupt priority flags */ void *mode_info; /*!< Slot, clock and gpio information of each mode */ + bool full_duplex_slave; /*!< whether the channel is forced to switch to slave role for full duplex */ #if SOC_I2S_SUPPORTS_APLL bool apll_en; /*!< 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 51fff5e961..9e3cc017cd 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; @@ -111,18 +112,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 */ @@ -167,43 +163,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 @@ -221,6 +275,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 d3e5a60535..19ad9c9774 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; @@ -119,18 +120,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 */ @@ -176,43 +172,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 @@ -230,6 +275,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 038ff2825a..b039c9ce57 100644 --- a/components/esp_driver_i2s/include/driver/i2s_std.h +++ b/components/esp_driver_i2s/include/driver/i2s_std.h @@ -296,6 +296,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 cf39e5853b..6339056686 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 @@ -217,6 +217,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)); @@ -232,6 +264,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; @@ -257,6 +307,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; @@ -384,6 +435,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 5a60b0116c..e3afe2dcd9 100644 --- a/docs/en/api-reference/peripherals/i2s.rst +++ b/docs/en/api-reference/peripherals/i2s.rst @@ -815,7 +815,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 @@ -855,6 +857,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 @@ -871,7 +915,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), @@ -889,12 +933,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), @@ -911,8 +955,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 @@ -931,7 +975,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), @@ -949,12 +993,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), @@ -971,8 +1015,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)); Application Notes diff --git a/docs/zh_CN/api-reference/peripherals/i2s.rst b/docs/zh_CN/api-reference/peripherals/i2s.rst index 1c4103d045..6a0b8c6f84 100644 --- a/docs/zh_CN/api-reference/peripherals/i2s.rst +++ b/docs/zh_CN/api-reference/peripherals/i2s.rst @@ -815,7 +815,9 @@ STD RX 模式 请注意,一个句柄只能代表一个通道,因此仍然需要对 TX 和 RX 通道逐个进行声道和时钟配置。 -以下示例展示了如何分配两个全双工通道: +驱动支持两种分配全双工通道的方法: + +1. 在调用 :cpp:func:`i2s_new_channel` 函数时,同时分配 TX 和 RX 通道两个通道。 .. code-block:: c @@ -855,6 +857,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 单工模式 @@ -871,7 +914,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), @@ -889,12 +932,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), @@ -911,8 +954,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 @@ -931,7 +974,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), @@ -949,12 +992,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), @@ -971,8 +1014,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)); 应用注意事项