mirror of
https://github.com/espressif/esp-idf.git
synced 2026-08-18 06:35:35 +03:00
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:
@@ -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
|
||||
----------------------------------------------------------------------------
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
单工模式
|
||||
|
||||
Reference in New Issue
Block a user