Merge branch 'feat/i2s_duplex_update_v5.5' into 'release/v5.5'

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

See merge request espressif/esp-idf!50867
This commit is contained in:
morris
2026-07-18 15:57:50 +08:00
10 changed files with 395 additions and 127 deletions

View File

@@ -49,6 +49,8 @@
#include "driver/gpio.h"
#include "esp_private/gpio.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
@@ -83,6 +85,120 @@ 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;
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;
}
#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;
}
#endif
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;
ESP_LOGW(TAG, "the %s channel on I2S%d is switched from master to slave for full-duplex mode",
handle->dir == I2S_DIR_TX ? "tx" : "rx", handle->controller->id);
}
}
/*---------------------------------------------------------------------------
I2S Static APIs
----------------------------------------------------------------------------

View File

@@ -320,11 +320,12 @@ esp_err_t i2s_channel_reconfig_pdm_tx_slot(i2s_chan_handle_t handle, const i2s_p
i2s_pdm_tx_config_t *pdm_tx_cfg = (i2s_pdm_tx_config_t *)handle->mode_info;
ESP_GOTO_ON_FALSE(pdm_tx_cfg, ESP_ERR_INVALID_STATE, err, TAG, "initialization not complete");
uint32_t old_slot_bit_width = pdm_tx_cfg->slot_cfg.slot_bit_width;
ESP_GOTO_ON_ERROR(i2s_pdm_tx_set_slot(handle, slot_cfg), err, TAG, "set i2s standard slot failed");
/* If the slot bit width changed, then need to update the clock */
uint32_t slot_bits = slot_cfg->slot_bit_width == I2S_SLOT_BIT_WIDTH_AUTO ? slot_cfg->data_bit_width : slot_cfg->slot_bit_width;
if (pdm_tx_cfg->slot_cfg.slot_bit_width != slot_bits) {
if (pdm_tx_cfg->slot_cfg.slot_bit_width != old_slot_bit_width) {
ESP_GOTO_ON_ERROR(i2s_pdm_tx_set_clock(handle, &pdm_tx_cfg->clk_cfg), err, TAG, "update clock failed");
}
@@ -652,11 +653,12 @@ esp_err_t i2s_channel_reconfig_pdm_rx_slot(i2s_chan_handle_t handle, const i2s_p
i2s_pdm_rx_config_t *pdm_rx_cfg = (i2s_pdm_rx_config_t *)handle->mode_info;
ESP_GOTO_ON_FALSE(pdm_rx_cfg, ESP_ERR_INVALID_STATE, err, TAG, "initialization not complete");
uint32_t old_slot_bit_width = pdm_rx_cfg->slot_cfg.slot_bit_width;
ESP_GOTO_ON_ERROR(i2s_pdm_rx_set_slot(handle, slot_cfg), err, TAG, "set i2s standard slot failed");
/* If the slot bit width changed, then need to update the clock */
uint32_t slot_bits = slot_cfg->slot_bit_width == I2S_SLOT_BIT_WIDTH_AUTO ? slot_cfg->data_bit_width : slot_cfg->slot_bit_width;
if (pdm_rx_cfg->slot_cfg.slot_bit_width != slot_bits) {
if (pdm_rx_cfg->slot_cfg.slot_bit_width != old_slot_bit_width) {
ESP_GOTO_ON_ERROR(i2s_pdm_rx_set_clock(handle, &pdm_rx_cfg->clk_cfg), err, TAG, "update clock failed");
}

View File

@@ -126,6 +126,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

@@ -30,10 +30,7 @@ static esp_err_t i2s_std_calculate_clock(i2s_chan_handle_t handle, const i2s_std
{
uint32_t rate = clk_cfg->sample_rate_hz;
i2s_std_slot_config_t *slot_cfg = &((i2s_std_config_t *)(handle->mode_info))->slot_cfg;
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;
uint32_t slot_bits = slot_cfg->slot_bit_width;
/* Calculate multiple
* Fmclk = bck_div*fbck = fsclk/(mclk_div+b/a) */
if (handle->role == I2S_ROLE_MASTER || handle->full_duplex_slave) {
@@ -43,6 +40,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");
@@ -75,8 +82,7 @@ static esp_err_t i2s_std_set_clock(i2s_chan_handle_t handle, const i2s_std_clk_c
{
esp_err_t ret = ESP_OK;
i2s_std_config_t *std_cfg = (i2s_std_config_t *)(handle->mode_info);
i2s_data_bit_width_t real_slot_bit = (int)std_cfg->slot_cfg.slot_bit_width < (int)std_cfg->slot_cfg.data_bit_width ?
std_cfg->slot_cfg.data_bit_width : std_cfg->slot_cfg.slot_bit_width;
i2s_data_bit_width_t real_slot_bit = std_cfg->slot_cfg.slot_bit_width;
ESP_RETURN_ON_FALSE(real_slot_bit != I2S_DATA_BIT_WIDTH_24BIT ||
(clk_cfg->mclk_multiple % 3 == 0), ESP_ERR_INVALID_ARG, TAG,
"The 'mclk_multiple' should be the multiple of 3 while using 24-bit data width");
@@ -157,7 +163,7 @@ static esp_err_t i2s_std_set_slot(i2s_chan_handle_t handle, const i2s_std_slot_c
/* Update the mode info: slot configuration */
i2s_std_config_t *std_cfg = (i2s_std_config_t *)(handle->mode_info);
memcpy(&(std_cfg->slot_cfg), slot_cfg, sizeof(i2s_std_slot_config_t));
memcpy(&(std_cfg->slot_cfg), &norm_slot_cfg, sizeof(i2s_std_slot_config_t));
return ESP_OK;
}
@@ -242,61 +248,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 < SOC_I2S_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 < SOC_I2S_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;
}
@@ -319,7 +318,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
@@ -435,11 +434,12 @@ esp_err_t i2s_channel_reconfig_std_slot(i2s_chan_handle_t handle, const i2s_std_
i2s_std_config_t *std_cfg = (i2s_std_config_t *)handle->mode_info;
ESP_GOTO_ON_FALSE(std_cfg, ESP_ERR_INVALID_STATE, err, TAG, "initialization not complete");
uint32_t old_slot_bit_width = std_cfg->slot_cfg.slot_bit_width;
ESP_GOTO_ON_ERROR(i2s_std_set_slot(handle, slot_cfg), err, TAG, "set i2s standard slot failed");
/* If the slot bit width changed, then need to update the clock */
uint32_t slot_bits = slot_cfg->slot_bit_width == I2S_SLOT_BIT_WIDTH_AUTO ? slot_cfg->data_bit_width : slot_cfg->slot_bit_width;
if (std_cfg->slot_cfg.slot_bit_width != slot_bits) {
if (std_cfg->slot_cfg.slot_bit_width != old_slot_bit_width) {
ESP_GOTO_ON_ERROR(i2s_std_set_clock(handle, &std_cfg->clk_cfg), err, TAG, "update clock failed");
}

View File

@@ -31,10 +31,7 @@ static esp_err_t i2s_tdm_calculate_clock(i2s_chan_handle_t handle, const i2s_tdm
{
uint32_t rate = clk_cfg->sample_rate_hz;
i2s_tdm_slot_config_t *slot_cfg = &((i2s_tdm_config_t *)(handle->mode_info))->slot_cfg;
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;
uint32_t slot_bits = slot_cfg->slot_bit_width;
/* Calculate multiple
* Fmclk = bck_div*fbck = fsclk/(mclk_div+b/a) */
if (handle->role == I2S_ROLE_MASTER || handle->full_duplex_slave) {
@@ -50,6 +47,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");
@@ -110,30 +117,32 @@ static esp_err_t i2s_tdm_set_clock(i2s_chan_handle_t handle, const i2s_tdm_clk_c
return ret;
}
static i2s_tdm_slot_config_t s_i2s_tdm_normalize_slot_config(const i2s_tdm_slot_config_t *slot_cfg)
static esp_err_t s_i2s_tdm_normalize_slot_config(const i2s_tdm_slot_config_t *slot_cfg, i2s_tdm_slot_config_t *normalized_slot_cfg)
{
i2s_tdm_slot_config_t normalized_slot_cfg = *slot_cfg;
uint32_t max_slot_num = 32 - __builtin_clz(normalized_slot_cfg.slot_mask);
/* 1. Normalize the ws width */
normalized_slot_cfg.ws_width = normalized_slot_cfg.ws_width == I2S_TDM_AUTO_WS_WIDTH ?
normalized_slot_cfg.total_slot * normalized_slot_cfg.slot_bit_width / 2 : normalized_slot_cfg.ws_width;
ESP_RETURN_ON_FALSE(slot_cfg->slot_mask, ESP_ERR_INVALID_ARG, TAG, "At least one channel should be enabled");
*normalized_slot_cfg = *slot_cfg;
/* 1. Normalize the slot bit width */
normalized_slot_cfg->slot_bit_width = (int)normalized_slot_cfg->slot_bit_width < (int)normalized_slot_cfg->data_bit_width ?
normalized_slot_cfg->data_bit_width : normalized_slot_cfg->slot_bit_width;
/* 2. Normalize the total slot number */
normalized_slot_cfg.total_slot = normalized_slot_cfg.total_slot < max_slot_num ? max_slot_num : normalized_slot_cfg.total_slot;
uint32_t max_slot_num = 32 - __builtin_clz(normalized_slot_cfg->slot_mask);
normalized_slot_cfg->total_slot = normalized_slot_cfg->total_slot < max_slot_num ? max_slot_num : normalized_slot_cfg->total_slot;
// At least two slots in a frame if not using PCM short format
normalized_slot_cfg.total_slot = ((normalized_slot_cfg.total_slot < 2) && (normalized_slot_cfg.ws_width != 1)) ? 2 : normalized_slot_cfg.total_slot;
normalized_slot_cfg->total_slot = ((normalized_slot_cfg->total_slot < 2) && (normalized_slot_cfg->ws_width != 1)) ? 2 : normalized_slot_cfg->total_slot;
/* 3. Normalize the slot bit width */
normalized_slot_cfg.slot_bit_width = normalized_slot_cfg.slot_bit_width == I2S_SLOT_BIT_WIDTH_AUTO ?
normalized_slot_cfg.data_bit_width : normalized_slot_cfg.slot_bit_width;
return normalized_slot_cfg;
/* 3. Normalize the ws width */
normalized_slot_cfg->ws_width = normalized_slot_cfg->ws_width == I2S_TDM_AUTO_WS_WIDTH ?
normalized_slot_cfg->total_slot * normalized_slot_cfg->slot_bit_width / 2 : normalized_slot_cfg->ws_width;
return ESP_OK;
}
static esp_err_t i2s_tdm_set_slot(i2s_chan_handle_t handle, const i2s_tdm_slot_config_t *slot_cfg)
{
ESP_RETURN_ON_FALSE(slot_cfg->slot_mask, ESP_ERR_INVALID_ARG, TAG, "At least one channel should be enabled");
i2s_tdm_slot_config_t norm_slot_cfg = s_i2s_tdm_normalize_slot_config(slot_cfg);
i2s_tdm_slot_config_t norm_slot_cfg = {};
ESP_RETURN_ON_ERROR(s_i2s_tdm_normalize_slot_config(slot_cfg, &norm_slot_cfg), TAG, "invalid slot configuration");
/* Update the total slot num and active slot num */
handle->active_slot = norm_slot_cfg.slot_mode == I2S_SLOT_MODE_MONO ? 1 : __builtin_popcount(norm_slot_cfg.slot_mask);
handle->total_slot = norm_slot_cfg.total_slot;
@@ -161,9 +170,9 @@ static esp_err_t i2s_tdm_set_slot(i2s_chan_handle_t handle, const i2s_tdm_slot_c
portENTER_CRITICAL(&g_i2s.spinlock);
/* Configure the hardware to apply TDM format */
if (handle->dir == I2S_DIR_TX) {
i2s_hal_tdm_set_tx_slot(&(handle->controller->hal), is_slave, (i2s_hal_slot_config_t *)slot_cfg);
i2s_hal_tdm_set_tx_slot(&(handle->controller->hal), is_slave, (i2s_hal_slot_config_t *)&norm_slot_cfg);
} else {
i2s_hal_tdm_set_rx_slot(&(handle->controller->hal), is_slave, (i2s_hal_slot_config_t *)slot_cfg);
i2s_hal_tdm_set_rx_slot(&(handle->controller->hal), is_slave, (i2s_hal_slot_config_t *)&norm_slot_cfg);
}
portEXIT_CRITICAL(&g_i2s.spinlock);
@@ -255,35 +264,23 @@ 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 esp_err_t 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 = {};
ESP_RETURN_ON_ERROR(s_i2s_tdm_normalize_slot_config(&tdm_cfg->slot_cfg, &norm_slot), TAG, "invalid slot configuration");
candidate.total_frame_bits = norm_slot.total_slot * norm_slot.slot_bit_width;
i2s_channel_try_to_constitute_duplex(handle, &candidate);
return ESP_OK;
}
esp_err_t i2s_channel_init_tdm_mode(i2s_chan_handle_t handle, const i2s_tdm_config_t *tdm_cfg)
@@ -304,7 +301,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);
ESP_GOTO_ON_ERROR(s_i2s_channel_try_to_constitute_tdm_duplex(handle, tdm_cfg), err, TAG, "failed to constitute full-duplex mode");
/* 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
@@ -417,11 +414,13 @@ esp_err_t i2s_channel_reconfig_tdm_slot(i2s_chan_handle_t handle, const i2s_tdm_
i2s_tdm_config_t *tdm_cfg = (i2s_tdm_config_t *)handle->mode_info;
ESP_GOTO_ON_FALSE(tdm_cfg, ESP_ERR_INVALID_STATE, err, TAG, "initialization not complete");
uint32_t old_total_slot = handle->total_slot;
uint32_t old_slot_bit_width = tdm_cfg->slot_cfg.slot_bit_width;
ESP_GOTO_ON_ERROR(i2s_tdm_set_slot(handle, slot_cfg), err, TAG, "set i2s standard slot failed");
/* If the slot bit width changed, then need to update the clock */
uint32_t slot_bits = slot_cfg->slot_bit_width == I2S_SLOT_BIT_WIDTH_AUTO ? slot_cfg->data_bit_width : slot_cfg->slot_bit_width;
if (tdm_cfg->slot_cfg.slot_bit_width != slot_bits) {
/* If the total frame bits changed, then need to update the clock */
if (old_total_slot * old_slot_bit_width != handle->total_slot * tdm_cfg->slot_cfg.slot_bit_width) {
ESP_GOTO_ON_ERROR(i2s_tdm_set_clock(handle, &tdm_cfg->clk_cfg), err, TAG, "update clock failed");
}

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

@@ -237,6 +237,92 @@ 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_ASSERT_EQUAL(I2S_ROLE_SLAVE, chan_info.role);
TEST_ESP_OK(i2s_channel_get_info(rx_handle, &chan_info));
TEST_ASSERT(chan_info.pair_chan == tx_handle);
TEST_ASSERT_EQUAL(I2S_ROLE_MASTER, chan_info.role);
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

@@ -920,6 +920,10 @@ 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.
.. note::
In full-duplex mode, only one channel can work as the master that generates BCLK and WS. If both paired handles are configured as ``I2S_ROLE_MASTER``, the handle initialized later is automatically switched to ``I2S_ROLE_SLAVE``.
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`.
@@ -937,7 +941,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),
@@ -962,7 +966,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
@@ -977,7 +981,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),
@@ -1003,6 +1007,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

@@ -920,6 +920,10 @@ STD RX 模式
请注意,一个句柄只能代表一个通道,因此仍然需要对 TX 和 RX 通道逐个进行声道和时钟配置。
.. note::
全双工模式下只能有一个通道作为 master 生成 BCLK 和 WS。如果配对的两个句柄都配置为 ``I2S_ROLE_MASTER``,后初始化的句柄会被自动切换为 ``I2S_ROLE_SLAVE``
驱动支持两种分配全双工通道的方法:
1. 在调用 :cpp:func:`i2s_new_channel` 函数时,同时分配 TX 和 RX 通道两个通道。
@@ -937,7 +941,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),
@@ -962,7 +966,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
@@ -977,7 +981,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),
@@ -1003,6 +1007,16 @@ 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
单工模式