feat(i2s): allow full duplex mode in less strict condition

This commit is contained in:
Chen Chen
2026-06-15 17:01:24 +08:00
parent 6a24b81921
commit 47bb180049
9 changed files with 350 additions and 90 deletions

View File

@@ -48,6 +48,8 @@
#include "esp_private/gpio.h"
#include "esp_private/i2s_sync.h"
#include "driver/i2s_common.h"
#include "driver/i2s_std.h"
#include "driver/i2s_tdm.h"
#include "i2s_private.h"
#if CONFIG_IDF_TARGET_ESP32
@@ -82,6 +84,125 @@ inline void *i2s_dma_calloc(i2s_chan_handle_t handle, size_t num, size_t size)
return heap_caps_aligned_calloc(4, num, size, I2S_DMA_ALLOC_CAPS);
}
/*---------------------------------------------------------------------------
Duplex Constitution Helpers
----------------------------------------------------------------------------
Scope: This file only
----------------------------------------------------------------------------*/
/* Extract the duplex candidate from a fully-initialized channel's stored configuration.
* @note Only valid for a channel that has reached the READY state, because the slot/clock/gpio
* information is read back from `handle->mode_info`. */
static esp_err_t s_i2s_extract_duplex_candidate(i2s_chan_handle_t handle, i2s_duplex_candidate_t *out)
{
/* Only STD and TDM modes can constitute duplex, reject the others (e.g. PDM) up front */
#if SOC_I2S_SUPPORTS_TDM
if (handle->mode != I2S_COMM_MODE_STD && handle->mode != I2S_COMM_MODE_TDM) {
#else
if (handle->mode != I2S_COMM_MODE_STD) {
#endif
return ESP_ERR_NOT_SUPPORTED;
}
memset(out, 0, sizeof(*out));
/* The clk_cfg and gpio_cfg sub-structures share the same layout between STD and TDM modes
* (only the slot_cfg differs), so resolve the mode-specific pointers here and fill the
* candidate with the shared logic below. */
i2s_std_clk_config_t *clk_cfg = NULL;
i2s_std_gpio_config_t *gpio_cfg = NULL;
uint32_t slot_bits = 0;
uint32_t data_bits = 0;
if (handle->mode == I2S_COMM_MODE_STD) {
i2s_std_config_t *cfg = (i2s_std_config_t *)handle->mode_info;
clk_cfg = &cfg->clk_cfg;
gpio_cfg = &cfg->gpio_cfg;
slot_bits = cfg->slot_cfg.slot_bit_width;
data_bits = cfg->slot_cfg.data_bit_width;
}
#if SOC_I2S_SUPPORTS_TDM
else {
i2s_tdm_config_t *cfg = (i2s_tdm_config_t *)handle->mode_info;
clk_cfg = (i2s_std_clk_config_t *)&cfg->clk_cfg;
gpio_cfg = (i2s_std_gpio_config_t *)&cfg->gpio_cfg;
slot_bits = cfg->slot_cfg.slot_bit_width;
data_bits = cfg->slot_cfg.data_bit_width;
}
#endif
if (slot_bits == I2S_SLOT_BIT_WIDTH_AUTO || (int)slot_bits < (int)data_bits) {
slot_bits = data_bits;
}
out->ws_pin = gpio_cfg->ws;
out->bclk_pin = gpio_cfg->bclk;
out->total_frame_bits = handle->total_slot * slot_bits;
out->sample_rate_hz = clk_cfg->sample_rate_hz;
out->clk_src = clk_cfg->clk_src;
out->bclk_inv = gpio_cfg->invert_flags.bclk_inv;
out->ws_inv = gpio_cfg->invert_flags.ws_inv;
return ESP_OK;
}
/* Whether the shared BCLK/WS configurations of the two candidates are identical.
* The slot layout and clock source are intentionally NOT compared here, only the
* resulting frame timing (sample rate + total frame bits) and the shared BCLK/WS
* settings are. */
static bool s_i2s_duplex_candidate_match(const i2s_duplex_candidate_t *a, const i2s_duplex_candidate_t *b)
{
return a->ws_pin == b->ws_pin &&
a->bclk_pin == b->bclk_pin &&
a->sample_rate_hz == b->sample_rate_hz &&
a->total_frame_bits == b->total_frame_bits &&
a->bclk_inv == b->bclk_inv &&
a->ws_inv == b->ws_inv;
}
void i2s_channel_try_to_constitute_duplex(i2s_chan_handle_t handle, const i2s_duplex_candidate_t *candidate)
{
i2s_chan_handle_t another_handle = handle->dir == I2S_DIR_RX ? handle->controller->tx_chan : handle->controller->rx_chan;
/* The other direction channel must be registered and fully initialized to be compared with */
if (!another_handle || another_handle->state < I2S_CHAN_STATE_READY) {
return;
}
if (!handle->controller->full_duplex) {
/* Sharing BCLK/WS requires valid (used) WS and BCK pins on the current channel */
if (candidate->ws_pin < 0 || candidate->bclk_pin < 0) {
ESP_LOGD(TAG, "%s channel on I2S%d: WS/BCK pins unused, cannot constitute duplex",
handle->dir == I2S_DIR_TX ? "tx" : "rx", handle->controller->id);
return;
}
i2s_duplex_candidate_t another_candidate;
if (s_i2s_extract_duplex_candidate(another_handle, &another_candidate) != ESP_OK) {
return;
}
/* Constitute duplex only when the shared BCLK/WS configurations are identical and the frame
* timing (sample rate + total frame bits) matches. The slot layout and clock config may differ. */
if (!s_i2s_duplex_candidate_match(candidate, &another_candidate)) {
ESP_LOGD(TAG, "%s channel on I2S%d: BCLK/WS/frame configurations don't match, cannot constitute duplex",
handle->dir == I2S_DIR_TX ? "tx" : "rx", handle->controller->id);
return;
}
/* All conditions met, constitute duplex */
handle->controller->full_duplex = true;
ESP_LOGD(TAG, "Constitute full-duplex on port %d", handle->controller->id);
}
/* Handle slave role fallback when both channels are master.
* The later initialized channel must be slave for full duplex */
if (handle->controller->full_duplex &&
handle->role == I2S_ROLE_MASTER &&
another_handle->role == I2S_ROLE_MASTER) {
handle->role = I2S_ROLE_SLAVE;
handle->full_duplex_slave = true;
}
}
/*---------------------------------------------------------------------------
I2S Static APIs
----------------------------------------------------------------------------

View File

@@ -127,6 +127,38 @@ typedef struct {
uint8_t **bufs; /*!< dma buffer array */
} i2s_dma_t;
/**
* @brief Duplex candidate information extracted from channel configuration
* @note Used by the generic duplex constitution function to check whether two channels can form duplex.
* Two channels can share the BCLK/WS lines only when the BCLK/WS configurations are identical
* and the per-frame bit count (sample_rate * total_frame_bits) is the same. The slot layout,
* clock source, external clock frequency and MCLK configuration are allowed to differ as long
* as the BCLK/WS frequency and frame timing match.
*/
typedef struct {
int ws_pin; /*!< WS GPIO pin number, -1 if unused */
int bclk_pin; /*!< BCLK GPIO pin number, -1 if unused */
uint32_t sample_rate_hz; /*!< Sample rate in Hz */
uint32_t total_frame_bits; /*!< Total bits per frame: total_slot * slot_bit_width */
i2s_clock_src_t clk_src; /*!< Clock source (informational, not compared) */
bool bclk_inv; /*!< Whether the BCLK is inverted */
bool ws_inv; /*!< Whether the WS is inverted */
} i2s_duplex_candidate_t;
/**
* @brief Try to constitute full-duplex between two channels on the same controller
* @note The two channels can constitute full-duplex only when they share the same valid WS/BCK pins,
* the same BCLK/WS invert flags, the same sample rate and the same total frame bits.
* The slot layout, clock source, external clock frequency and MCLK configuration may differ
* as long as the BCLK/WS frequency and frame timing match (e.g. STD 2ch/32bit can pair
* with TDM 4ch/16bit).
* If the conditions match, mark the controller as full-duplex. If both channels are master,
* the later one is forced to slave.
* @param handle Current channel being initialized
* @param candidate Duplex configuration of the current channel
*/
void i2s_channel_try_to_constitute_duplex(i2s_chan_handle_t handle, const i2s_duplex_candidate_t *candidate);
/**
* @brief i2s controller level configurations
* @note Both i2s rx and tx channel are under its control

View File

@@ -43,6 +43,16 @@ static esp_err_t i2s_std_calculate_clock(i2s_chan_handle_t handle, const i2s_std
if (clk_info->mclk % clk_info->bclk != 0) {
ESP_LOGW(TAG, "the current mclk multiple cannot perform integer division (slot_num: %"PRIu32", slot_bits: %"PRIu32")", handle->total_slot, slot_bits);
}
/* As an internal full-duplex slave, the BCLK/WS are looped back from the on-chip master, but the slave logic
* still needs enough MCLK/BCLK ratio to sample correctly. Measured minimums: TX slave >= 6, RX slave >= 4. */
if (handle->full_duplex_slave) {
uint32_t min_bclk_div = handle->dir == I2S_DIR_TX ? 6 : 4;
if (clk_info->bclk_div < min_bclk_div) {
ESP_LOGW(TAG, "the mclk/bclk ratio %"PRIu32" is too small for the full-duplex %s slave (min %"PRIu32"), "
"data might be sampled incorrectly, please increase mclk_multiple",
clk_info->bclk_div, handle->dir == I2S_DIR_TX ? "tx" : "rx", min_bclk_div);
}
}
} else {
if (clk_cfg->bclk_div < 8) {
ESP_LOGW(TAG, "the current bclk division is too small, adjust the bclk division to 8");
@@ -254,61 +264,54 @@ static esp_err_t i2s_std_set_gpio(i2s_chan_handle_t handle, const i2s_std_gpio_c
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)
static esp_err_t s_i2s_channel_try_to_constitute_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;
i2s_std_slot_config_t norm_slot_cfg = s_i2s_std_normalize_slot_config(&(std_cfg->slot_cfg));
memcpy(&curr_cfg.slot_cfg, &norm_slot_cfg, sizeof(i2s_std_slot_config_t));
/* 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);
} else {
/* Build the duplex candidate from the current channel's configuration */
i2s_duplex_candidate_t candidate = {0};
candidate.ws_pin = std_cfg->gpio_cfg.ws;
candidate.bclk_pin = std_cfg->gpio_cfg.bclk;
candidate.sample_rate_hz = std_cfg->clk_cfg.sample_rate_hz;
candidate.clk_src = std_cfg->clk_cfg.clk_src;
candidate.bclk_inv = std_cfg->gpio_cfg.invert_flags.bclk_inv;
candidate.ws_inv = std_cfg->gpio_cfg.invert_flags.ws_inv;
/* Compute total frame bits for standard mode: always 2 slots */
i2s_std_slot_config_t norm_slot = s_i2s_std_normalize_slot_config(&std_cfg->slot_cfg);
candidate.total_frame_bits = 2 * norm_slot.slot_bit_width;
i2s_channel_try_to_constitute_duplex(handle, &candidate);
/* On HW v1, if duplex constitution fails (pins/configs don't match),
* try to move the channel to another port to avoid sharing a port with incompatible configs.
* Only do this when the other channel was actually ready for comparison; if it wasn't
* initialized yet, duplex will be constituted when the second channel is initialized. */
#if SOC_I2S_HW_VERSION_1
bool port_changed = false;
if (handle->is_port_auto) {
ESP_LOGD(TAG, "TX & RX on I2S%d are simplex", handle->controller->id);
for (int i = 0; i < I2S_LL_GET(INST_NUM); i++) {
if (i == handle->controller->id) {
continue;
}
ESP_LOGD(TAG, "Trying to move %s channel from port %d to %d",
handle->dir == I2S_DIR_TX ? "TX" : "RX", handle->controller->id, i);
if (i2s_channel_change_port(handle, i) == ESP_OK) {
ESP_LOGD(TAG, "Move success!");
port_changed = true;
break;
} else {
ESP_LOGD(TAG, "Move failed...");
}
if (!handle->controller->full_duplex) {
i2s_chan_handle_t another = handle->dir == I2S_DIR_RX ? handle->controller->tx_chan : handle->controller->rx_chan;
if (another && another->state >= I2S_CHAN_STATE_READY) {
bool port_changed = false;
if (handle->is_port_auto) {
ESP_LOGD(TAG, "TX & RX on I2S%d are simplex", handle->controller->id);
for (int i = 0; i < I2S_LL_GET(INST_NUM); i++) {
if (i == handle->controller->id) {
continue;
}
ESP_LOGD(TAG, "Trying to move %s channel from port %d to %d",
handle->dir == I2S_DIR_TX ? "TX" : "RX", handle->controller->id, i);
if (i2s_channel_change_port(handle, i) == ESP_OK) {
ESP_LOGD(TAG, "Move success!");
port_changed = true;
break;
} else {
ESP_LOGD(TAG, "Move failed...");
}
}
if (!port_changed) {
ESP_LOGE(TAG, "Can't set different channel configurations on a same port");
return ESP_ERR_INVALID_ARG;
}
#else
ESP_LOGD(TAG, "TX & RX on I2S%d are simplex", handle->controller->id);
#endif
}
if (!port_changed) {
ESP_LOGE(TAG, "Can't set different channel configurations on a same port");
return ESP_ERR_INVALID_ARG;
}
}
/* 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;
}
}
#endif
return ESP_OK;
}
@@ -331,7 +334,7 @@ esp_err_t i2s_channel_init_std_mode(i2s_chan_handle_t handle, const i2s_std_conf
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);
ret = s_i2s_channel_try_to_constitute_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

View File

@@ -50,6 +50,16 @@ static esp_err_t i2s_tdm_calculate_clock(i2s_chan_handle_t handle, const i2s_tdm
if (clk_info->mclk % clk_info->bclk != 0) {
ESP_LOGW(TAG, "the current mclk multiple cannot perform integer division (slot_num: %"PRIu32", slot_bits: %"PRIu32")", handle->total_slot, slot_bits);
}
/* As an internal full-duplex slave, the BCLK/WS are looped back from the on-chip master, but the slave logic
* still needs enough MCLK/BCLK ratio to sample correctly. Measured minimums: TX slave >= 6, RX slave >= 4. */
if (handle->full_duplex_slave) {
uint32_t min_bclk_div = handle->dir == I2S_DIR_TX ? 6 : 4;
if (clk_info->bclk_div < min_bclk_div) {
ESP_LOGW(TAG, "the mclk/bclk ratio %"PRIu32" is too small for the full-duplex %s slave (min %"PRIu32"), "
"data might be sampled incorrectly, please increase mclk_multiple",
clk_info->bclk_div, handle->dir == I2S_DIR_TX ? "tx" : "rx", min_bclk_div);
}
}
} else {
if (clk_cfg->bclk_div < 8) {
ESP_LOGW(TAG, "the current bclk division is too small, adjust the bclk division to 8");
@@ -267,35 +277,21 @@ static esp_err_t i2s_tdm_set_gpio(i2s_chan_handle_t handle, const i2s_tdm_gpio_c
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)
static void s_i2s_channel_try_to_constitute_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;
i2s_tdm_slot_config_t norm_slot_cfg = s_i2s_tdm_normalize_slot_config(&(tdm_cfg->slot_cfg));
memcpy(&curr_cfg.slot_cfg, &norm_slot_cfg, sizeof(i2s_tdm_slot_config_t));
/* 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);
} else {
ESP_LOGD(TAG, "TX & RX on I2S%d are simplex", 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;
}
}
/* Build the duplex candidate from the current channel's configuration */
i2s_duplex_candidate_t candidate = {0};
candidate.ws_pin = tdm_cfg->gpio_cfg.ws;
candidate.bclk_pin = tdm_cfg->gpio_cfg.bclk;
candidate.sample_rate_hz = tdm_cfg->clk_cfg.sample_rate_hz;
candidate.clk_src = tdm_cfg->clk_cfg.clk_src;
candidate.bclk_inv = tdm_cfg->gpio_cfg.invert_flags.bclk_inv;
candidate.ws_inv = tdm_cfg->gpio_cfg.invert_flags.ws_inv;
/* Compute total frame bits for TDM mode */
i2s_tdm_slot_config_t norm_slot = s_i2s_tdm_normalize_slot_config(&tdm_cfg->slot_cfg);
candidate.total_frame_bits = norm_slot.total_slot * norm_slot.slot_bit_width;
i2s_channel_try_to_constitute_duplex(handle, &candidate);
}
esp_err_t i2s_channel_init_tdm_mode(i2s_chan_handle_t handle, const i2s_tdm_config_t *tdm_cfg)
@@ -316,7 +312,7 @@ 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);
s_i2s_channel_try_to_constitute_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");
ESP_GOTO_ON_ERROR(i2s_tdm_set_clock(handle, &tdm_cfg->clk_cfg), err, TAG, "initialize channel failed while setting clock");

View File

@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2022-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -299,8 +299,11 @@ 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
* @note When initializing the STD mode on a channel that shares the same BCLK/WS configuration and
* the same frame timing (sample rate and total bits per frame) as the other channel on the same port,
* these two channels can constitute full-duplex mode automatically. The slot layout, clock source,
* external clock frequency, and MCLK configuration may differ (e.g. an STD channel can pair with
* a TDM channel) as long as the total bits per frame are equal and the BCLK/WS frequency matches.
*
* @param[in] handle I2S channel handler
* @param[in] std_cfg Configurations for standard mode, including clock, slot and GPIO

View File

@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2022-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -196,8 +196,11 @@ 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
* @note When initializing the TDM mode on a channel that shares the same BCLK/WS configuration and
* the same frame timing (sample rate and total bits per frame) as the other channel on the same port,
* these two channels can constitute full-duplex mode automatically. The slot layout, clock source,
* external clock frequency, and MCLK configuration may differ (e.g. a TDM channel can pair with
* an STD channel) as long as the total bits per frame are equal and the BCLK/WS frequency matches.
*
* @param[in] handle I2S channel handler
* @param[in] tdm_cfg Configurations for TDM mode, including clock, slot and GPIO

View File

@@ -236,6 +236,90 @@ TEST_CASE("I2S_basic_channel_allocation_reconfig_deleting_test", "[i2s]")
TEST_ESP_OK(i2s_del_channel(rx_handle));
}
#if SOC_I2S_HW_VERSION_2 && SOC_I2S_SUPPORTS_TDM
/* Cover the relaxed lazy-duplex constitution conditions:
* Two channels constitute full-duplex when they share the same valid WS/BCK pins,
* the same BCLK/WS inversion settings and the same frame timing (sample rate + total frame bits),
* even if their slot layout (mode / slot number / slot bit width) or MCLK configuration differs. */
TEST_CASE("I2S_lazy_duplex_constitution_boundary_test", "[i2s]")
{
i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_0, I2S_ROLE_MASTER);
i2s_chan_info_t chan_info;
i2s_chan_handle_t tx_handle;
i2s_chan_handle_t rx_handle;
/* STD stereo 32-bit => 2 slots * 32 bits = 64 bits per frame */
i2s_std_config_t std_64bit = {
.clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(SAMPLE_RATE),
.slot_cfg = I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_32BIT, I2S_SLOT_MODE_STEREO),
.gpio_cfg = I2S_TEST_MASTER_DEFAULT_PIN,
};
/* TDM 4-slot 16-bit => 4 slots * 16 bits = 64 bits per frame (same frame width, different slot layout) */
i2s_tdm_config_t tdm_64bit = {
.clk_cfg = I2S_TDM_CLK_DEFAULT_CONFIG(SAMPLE_RATE),
.slot_cfg = I2S_TDM_PHILIPS_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_STEREO, 0x0F),
.gpio_cfg = I2S_TEST_MASTER_DEFAULT_PIN,
};
/* Case 1: STD and TDM with the same frame width and identical BCLK/WS config can constitute duplex
* across modes. The pair channel handle should be retrievable from either side. */
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_init_std_mode(rx_handle, &std_64bit));
TEST_ESP_OK(i2s_channel_init_tdm_mode(tx_handle, &tdm_64bit));
TEST_ESP_OK(i2s_channel_get_info(tx_handle, &chan_info));
TEST_ASSERT(chan_info.pair_chan == rx_handle);
TEST_ESP_OK(i2s_channel_get_info(rx_handle, &chan_info));
TEST_ASSERT(chan_info.pair_chan == tx_handle);
TEST_ESP_OK(i2s_del_channel(tx_handle));
TEST_ESP_OK(i2s_del_channel(rx_handle));
/* Case 2: Different frame width (STD 16-bit => 32 bits vs TDM => 64 bits) must NOT constitute duplex. */
i2s_std_config_t std_32bit = std_64bit;
std_32bit.slot_cfg = (i2s_std_slot_config_t)I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_STEREO);
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_init_std_mode(rx_handle, &std_32bit));
TEST_ESP_OK(i2s_channel_init_tdm_mode(tx_handle, &tdm_64bit));
TEST_ESP_OK(i2s_channel_get_info(tx_handle, &chan_info));
TEST_ASSERT(chan_info.pair_chan == NULL);
TEST_ESP_OK(i2s_del_channel(tx_handle));
TEST_ESP_OK(i2s_del_channel(rx_handle));
/* Case 3: Same frame width and BCLK/WS pins but different MCLK/external clock configs can still
* constitute duplex. */
i2s_std_config_t std_diff_mclk = std_64bit;
std_diff_mclk.clk_cfg.mclk_multiple = I2S_MCLK_MULTIPLE_384;
std_diff_mclk.clk_cfg.ext_clk_freq_hz = 24000000;
std_diff_mclk.gpio_cfg.mclk = I2S_GPIO_UNUSED;
std_diff_mclk.gpio_cfg.invert_flags.mclk_inv = true;
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_init_std_mode(rx_handle, &std_64bit));
TEST_ESP_OK(i2s_channel_init_std_mode(tx_handle, &std_diff_mclk));
TEST_ESP_OK(i2s_channel_get_info(tx_handle, &chan_info));
TEST_ASSERT(chan_info.pair_chan == rx_handle);
TEST_ESP_OK(i2s_channel_get_info(rx_handle, &chan_info));
TEST_ASSERT(chan_info.pair_chan == tx_handle);
TEST_ESP_OK(i2s_del_channel(tx_handle));
TEST_ESP_OK(i2s_del_channel(rx_handle));
/* Case 4: WS/BCK left unused (-1) on both channels must NOT constitute duplex even if everything
* else matches, because there is no shared clock line to combine. */
i2s_std_config_t std_no_clk_pin = std_64bit;
std_no_clk_pin.gpio_cfg.bclk = I2S_GPIO_UNUSED;
std_no_clk_pin.gpio_cfg.ws = I2S_GPIO_UNUSED;
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_init_std_mode(rx_handle, &std_no_clk_pin));
TEST_ESP_OK(i2s_channel_init_std_mode(tx_handle, &std_no_clk_pin));
TEST_ESP_OK(i2s_channel_get_info(tx_handle, &chan_info));
TEST_ASSERT(chan_info.pair_chan == NULL);
TEST_ESP_OK(i2s_del_channel(tx_handle));
TEST_ESP_OK(i2s_del_channel(rx_handle));
}
#endif // SOC_I2S_HW_VERSION_2 && SOC_I2S_SUPPORTS_TDM
static volatile bool task_run_flag;
static volatile bool read_task_success = true;
static volatile bool write_task_success = true;

View File

@@ -908,7 +908,7 @@ There are two methods to allocate a pair of full-duplex channels:
/* Allocate for TX and RX channel at the same time, then they will work in full-duplex mode */
i2s_new_channel(&chan_cfg, &tx_handle, &rx_handle);
/* Set the configurations for BOTH TWO channels, since TX and RX channel have to be same in full-duplex mode */
/* Set the configurations for both channels. BCLK/WS and frame timing must match in full-duplex mode. */
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),
@@ -933,7 +933,7 @@ There are two methods to allocate a pair of full-duplex channels:
...
2. Allocate TX and RX handles separately, and initialize them with the same configuration.
2. Allocate TX and RX handles separately, and initialize them with compatible configurations.
.. code-block:: c
@@ -948,7 +948,7 @@ There are two methods to allocate a pair of full-duplex channels:
/* 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 */
/* Set compatible configurations for both channels, then 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),
@@ -974,6 +974,15 @@ There are two methods to allocate a pair of full-duplex channels:
...
.. only:: SOC_I2S_HW_VERSION_2
When the TX and RX channels are allocated separately (the second method above), they do not have to be configured exactly the same to constitute full-duplex. The driver lets them share the BCLK and WS lines as long as:
- both channels use the same valid ``bclk`` and ``ws`` pins;
- both channels use the same BCLK/WS inversion settings;
- both channels produce the same frame timing, i.e. the same ``sample_rate_hz`` and the same total bits per frame (``total_slot * slot_bit_width``).
The clock source, external clock frequency (``ext_clk_freq_hz``), and MCLK-related configuration, including the ``mclk`` pin, ``mclk_multiple``, and MCLK inversion setting, are not used as conditions for constituting full-duplex. The slot layout itself may also differ. For example, an STD channel and a TDM channel, or a 2-slot/32-bit channel paired with a 4-slot/16-bit channel, can still constitute full-duplex because the number of bits per frame is the same. Once a pair of full-duplex channels is established, the paired channel handle can be retrieved from :cpp:type:`i2s_chan_info_t`::pair_chan returned by :cpp:func:`i2s_channel_get_info`.
.. only:: SOC_I2S_HW_VERSION_1

View File

@@ -908,7 +908,7 @@ STD RX 模式
/* 同时分配给 TX 和 RX 通道,使其进入全双工模式。 */
i2s_new_channel(&chan_cfg, &tx_handle, &rx_handle);
/* 配置两个通道,因为在全双工模式TX 和 RX 通道必须相同。 */
/* 配置两个通道全双工模式要求 BCLK/WS 与帧时序匹配。 */
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),
@@ -933,7 +933,7 @@ STD RX 模式
...
2. 调用两次 :cpp:func:`i2s_new_channel` 函数分别分配 TX 和 RX 通道,使用相同配置初始化 TX 和 RX 通道。
2. 调用两次 :cpp:func:`i2s_new_channel` 函数分别分配 TX 和 RX 通道,使用兼容配置初始化 TX 和 RX 通道。
.. code-block:: c
@@ -948,7 +948,7 @@ STD RX 模式
/* 分别分配给 TX 和 RX 通道 */
ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, &tx_handle, NULL));
/* 为两个通道设置完全相同的配置TX 和 RX 将自动组成全双工模式 */
/* 为两个通道设置兼容配置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),
@@ -974,6 +974,15 @@ STD RX 模式
...
.. only:: SOC_I2S_HW_VERSION_2
当 TX 和 RX 通道分别分配时(即上述第二种方法),二者无需配置得完全相同即可组成全双工。只要满足以下条件,驱动就会让它们共享 BCLK 和 WS 信号线:
- 两个通道使用相同且有效的 ``bclk````ws`` 管脚;
- 两个通道使用相同的 BCLK/WS 反相配置;
- 二者产生相同的帧时序,即 ``sample_rate_hz`` 相同且每帧总位数(``total_slot * slot_bit_width``)相同。
时钟源、外部时钟频率(``ext_clk_freq_hz``)以及 MCLK 相关配置不作为组成全双工的判据MCLK 相关配置包括 ``mclk`` 管脚、``mclk_multiple`` 和 MCLK 反相配置。槽slot布局本身也可以不同。例如一个 STD 通道与一个 TDM 通道,或者 2 槽/32 位通道与 4 槽/16 位通道配对,只要每帧的位数相同,仍可组成全双工。一旦组成了一对全双工通道,便可通过 :cpp:func:`i2s_channel_get_info` 返回的 :cpp:type:`i2s_chan_info_t`::pair_chan 获取配对通道的句柄。
.. only:: SOC_I2S_HW_VERSION_1