From 90866e99fbb553b20f89a6ca7ff7d63c33bdfabf Mon Sep 17 00:00:00 2001 From: laokaiyao Date: Mon, 20 Jun 2022 20:30:31 +0800 Subject: [PATCH 1/3] i2s: add basic examples for STD/TDM/PDM mode --- components/driver/deprecated/driver/i2s.h | 2 +- components/driver/deprecated/i2s_legacy.c | 4 +- components/driver/i2s/i2s_common.c | 4 +- components/driver/i2s/i2s_pdm.c | 11 +- components/driver/i2s/i2s_std.c | 2 + components/driver/i2s/i2s_tdm.c | 10 + components/driver/include/driver/i2s_common.h | 4 +- components/driver/include/driver/i2s_pdm.h | 28 ++- components/driver/include/driver/i2s_types.h | 1 + components/hal/esp32c3/include/hal/i2s_ll.h | 140 ++++++++++-- components/hal/esp32h2/include/hal/i2s_ll.h | 139 ++++++++++-- components/hal/esp32s3/include/hal/i2s_ll.h | 140 ++++++++++-- components/hal/i2s_hal.c | 39 ++-- components/hal/include/hal/i2s_hal.h | 8 +- components/hal/include/hal/i2s_types.h | 24 ++ components/soc/esp32/i2s_periph.c | 2 + components/soc/esp32c3/i2s_periph.c | 1 + .../soc/esp32c3/include/soc/gpio_sig_map.h | 19 +- components/soc/esp32h2/i2s_periph.c | 26 ++- components/soc/esp32s2/i2s_periph.c | 1 + components/soc/esp32s3/i2s_periph.c | 2 + components/soc/include/soc/i2s_periph.h | 1 + docs/en/api-reference/peripherals/i2s.rst | 10 +- .../release-5.x/peripherals.rst | 4 +- examples/peripherals/i2s/i2s_basic/README.md | 98 -------- .../i2s_basic/{ => i2s_pdm}/CMakeLists.txt | 2 +- .../i2s/i2s_basic/i2s_pdm/README.md | 165 ++++++++++++++ .../i2s/i2s_basic/i2s_pdm/main/CMakeLists.txt | 12 + .../i2s_basic/i2s_pdm/main/i2s_pdm_example.h | 23 ++ .../i2s_pdm/main/i2s_pdm_example_main.c | 31 +++ .../i2s/i2s_basic/i2s_pdm/main/i2s_pdm_rx.c | 78 +++++++ .../i2s/i2s_basic/i2s_pdm/main/i2s_pdm_tx.c | 109 +++++++++ .../i2s/i2s_basic/i2s_pdm/pytest_i2s_pdm.py | 22 ++ .../i2s/i2s_basic/i2s_pdm/sdkconfig.defaults | 1 + .../i2s/i2s_basic/i2s_std/CMakeLists.txt | 6 + .../i2s/i2s_basic/i2s_std/README.md | 73 ++++++ .../i2s/i2s_basic/i2s_std/main/CMakeLists.txt | 2 + .../i2s_std/main/i2s_std_example_main.c | 210 ++++++++++++++++++ .../i2s/i2s_basic/i2s_std/pytest_i2s_std.py | 35 +++ .../i2s/i2s_basic/i2s_std/sdkconfig.defaults | 1 + .../i2s/i2s_basic/i2s_tdm/CMakeLists.txt | 6 + .../i2s/i2s_basic/i2s_tdm/README.md | 72 ++++++ .../i2s/i2s_basic/i2s_tdm/main/CMakeLists.txt | 2 + .../i2s_tdm/main/i2s_tdm_example_main.c | 207 +++++++++++++++++ .../i2s/i2s_basic/i2s_tdm/pytest_i2s_tdm.py | 33 +++ .../i2s/i2s_basic/i2s_tdm/sdkconfig.defaults | 1 + .../i2s/i2s_basic/main/CMakeLists.txt | 2 - .../i2s/i2s_basic/main/i2s_example_main.c | 180 --------------- .../i2s/i2s_basic/pytest_i2s_basic.py | 18 -- .../{ => i2s_codec}/i2s_es8311/CMakeLists.txt | 0 .../i2s/{ => i2s_codec}/i2s_es8311/README.md | 0 .../i2s_es8311/main/CMakeLists.txt | 0 .../i2s_es8311/main/Kconfig.projbuild | 0 .../{ => i2s_codec}/i2s_es8311/main/canon.pcm | Bin .../i2s_es8311/main/i2s_es8311_example.c | 0 .../i2s_es8311/main/idf_component.yml | 0 .../i2s_es8311/pytest_i2s_es8311.py | 0 .../CMakeLists.txt | 0 .../README.md | 0 .../main/CMakeLists.txt | 0 .../main/Kconfig.projbuild | 0 .../main/i2s_recorder_main.c | 13 +- .../pytest_i2s_record.py | 0 tools/ci/check_copyright_ignore.txt | 1 - 64 files changed, 1616 insertions(+), 409 deletions(-) delete mode 100644 examples/peripherals/i2s/i2s_basic/README.md rename examples/peripherals/i2s/i2s_basic/{ => i2s_pdm}/CMakeLists.txt (86%) create mode 100644 examples/peripherals/i2s/i2s_basic/i2s_pdm/README.md create mode 100644 examples/peripherals/i2s/i2s_basic/i2s_pdm/main/CMakeLists.txt create mode 100644 examples/peripherals/i2s/i2s_basic/i2s_pdm/main/i2s_pdm_example.h create mode 100644 examples/peripherals/i2s/i2s_basic/i2s_pdm/main/i2s_pdm_example_main.c create mode 100644 examples/peripherals/i2s/i2s_basic/i2s_pdm/main/i2s_pdm_rx.c create mode 100644 examples/peripherals/i2s/i2s_basic/i2s_pdm/main/i2s_pdm_tx.c create mode 100644 examples/peripherals/i2s/i2s_basic/i2s_pdm/pytest_i2s_pdm.py create mode 100644 examples/peripherals/i2s/i2s_basic/i2s_pdm/sdkconfig.defaults create mode 100644 examples/peripherals/i2s/i2s_basic/i2s_std/CMakeLists.txt create mode 100644 examples/peripherals/i2s/i2s_basic/i2s_std/README.md create mode 100644 examples/peripherals/i2s/i2s_basic/i2s_std/main/CMakeLists.txt create mode 100644 examples/peripherals/i2s/i2s_basic/i2s_std/main/i2s_std_example_main.c create mode 100644 examples/peripherals/i2s/i2s_basic/i2s_std/pytest_i2s_std.py create mode 100644 examples/peripherals/i2s/i2s_basic/i2s_std/sdkconfig.defaults create mode 100644 examples/peripherals/i2s/i2s_basic/i2s_tdm/CMakeLists.txt create mode 100644 examples/peripherals/i2s/i2s_basic/i2s_tdm/README.md create mode 100644 examples/peripherals/i2s/i2s_basic/i2s_tdm/main/CMakeLists.txt create mode 100644 examples/peripherals/i2s/i2s_basic/i2s_tdm/main/i2s_tdm_example_main.c create mode 100644 examples/peripherals/i2s/i2s_basic/i2s_tdm/pytest_i2s_tdm.py create mode 100644 examples/peripherals/i2s/i2s_basic/i2s_tdm/sdkconfig.defaults delete mode 100644 examples/peripherals/i2s/i2s_basic/main/CMakeLists.txt delete mode 100644 examples/peripherals/i2s/i2s_basic/main/i2s_example_main.c delete mode 100644 examples/peripherals/i2s/i2s_basic/pytest_i2s_basic.py rename examples/peripherals/i2s/{ => i2s_codec}/i2s_es8311/CMakeLists.txt (100%) rename examples/peripherals/i2s/{ => i2s_codec}/i2s_es8311/README.md (100%) rename examples/peripherals/i2s/{ => i2s_codec}/i2s_es8311/main/CMakeLists.txt (100%) rename examples/peripherals/i2s/{ => i2s_codec}/i2s_es8311/main/Kconfig.projbuild (100%) rename examples/peripherals/i2s/{ => i2s_codec}/i2s_es8311/main/canon.pcm (100%) rename examples/peripherals/i2s/{ => i2s_codec}/i2s_es8311/main/i2s_es8311_example.c (100%) rename examples/peripherals/i2s/{ => i2s_codec}/i2s_es8311/main/idf_component.yml (100%) rename examples/peripherals/i2s/{ => i2s_codec}/i2s_es8311/pytest_i2s_es8311.py (100%) rename examples/peripherals/i2s/{i2s_audio_recorder_sdcard => i2s_recorder}/CMakeLists.txt (100%) rename examples/peripherals/i2s/{i2s_audio_recorder_sdcard => i2s_recorder}/README.md (100%) rename examples/peripherals/i2s/{i2s_audio_recorder_sdcard => i2s_recorder}/main/CMakeLists.txt (100%) rename examples/peripherals/i2s/{i2s_audio_recorder_sdcard => i2s_recorder}/main/Kconfig.projbuild (100%) rename examples/peripherals/i2s/{i2s_audio_recorder_sdcard => i2s_recorder}/main/i2s_recorder_main.c (92%) rename examples/peripherals/i2s/{i2s_audio_recorder_sdcard => i2s_recorder}/pytest_i2s_record.py (100%) diff --git a/components/driver/deprecated/driver/i2s.h b/components/driver/deprecated/driver/i2s.h index 0d9c686acb..07893c3e66 100644 --- a/components/driver/deprecated/driver/i2s.h +++ b/components/driver/deprecated/driver/i2s.h @@ -25,7 +25,7 @@ #if !CONFIG_I2S_SUPPRESS_DEPRECATE_WARN #warning "This set of I2S APIs has been deprecated, \ -please include 'driver/i2s_std.h', 'driver/i2s_pdm' or 'driver/i2s_tdm' instead. \ +please include 'driver/i2s_std.h', 'driver/i2s_pdm.h' or 'driver/i2s_tdm.h' instead. \ if you want to keep using the old APIs and ignore this warning, \ you can enable 'Suppress leagcy driver deprecated warning' option under 'I2S Configuration' menu in Kconfig" #endif diff --git a/components/driver/deprecated/i2s_legacy.c b/components/driver/deprecated/i2s_legacy.c index 798ef011ba..59a7e119c9 100644 --- a/components/driver/deprecated/i2s_legacy.c +++ b/components/driver/deprecated/i2s_legacy.c @@ -1315,11 +1315,11 @@ static esp_err_t i2s_config_transfer(i2s_port_t i2s_num, const i2s_config_t *i2s SLOT_CFG(pdm_tx).lp_scale = I2S_PDM_SIG_SCALING_MUL_1; SLOT_CFG(pdm_tx).sinc_scale = I2S_PDM_SIG_SCALING_MUL_1; #if SOC_I2S_HW_VERSION_2 - SLOT_CFG(pdm_tx).sd_en = true; + SLOT_CFG(pdm_tx).line_mode = I2S_PDM_TX_ONE_LINE_CODEC; SLOT_CFG(pdm_tx).hp_en = true; SLOT_CFG(pdm_tx).hp_cut_off_freq_hz = 49; SLOT_CFG(pdm_tx).sd_dither = 0; - SLOT_CFG(pdm_tx).sd_dither2 = 0; + SLOT_CFG(pdm_tx).sd_dither2 = 1; #endif // SOC_I2S_HW_VERSION_2 /* Generate PDM TX clock configuration */ diff --git a/components/driver/i2s/i2s_common.c b/components/driver/i2s/i2s_common.c index fb5c29bb18..880ad35bab 100644 --- a/components/driver/i2s/i2s_common.c +++ b/components/driver/i2s/i2s_common.c @@ -432,7 +432,7 @@ esp_err_t i2s_alloc_dma_desc(i2s_chan_handle_t handle, uint32_t num, uint32_t bu if (handle->dir == I2S_DIR_RX) { i2s_ll_rx_set_eof_num(handle->controller->hal.dev, bufsize); } - ESP_LOGD(TAG, "DMA malloc info: dma_desc_num = %d, dma_desc_buf_size = dma_frame_num * slot_num * data_bit_width = %d, ", num, bufsize); + ESP_LOGD(TAG, "DMA malloc info: dma_desc_num = %d, dma_desc_buf_size = dma_frame_num * slot_num * data_bit_width = %d", num, bufsize); return ESP_OK; err: i2s_free_dma_desc(handle); @@ -1112,7 +1112,7 @@ esp_err_t i2s_platform_acquire_occupation(int id, const char *comp_name) } portEXIT_CRITICAL(&g_i2s.spinlock); if (occupied_comp != NULL) { - ESP_LOGE(TAG, "i2s controller %d has been occupied by %s", id, occupied_comp); + ESP_LOGW(TAG, "i2s controller %d has been occupied by %s", id, occupied_comp); } return ret; } diff --git a/components/driver/i2s/i2s_pdm.c b/components/driver/i2s/i2s_pdm.c index 500f822ed0..b8e4d3300b 100644 --- a/components/driver/i2s/i2s_pdm.c +++ b/components/driver/i2s/i2s_pdm.c @@ -67,6 +67,8 @@ static esp_err_t i2s_pdm_tx_set_clock(i2s_chan_handle_t handle, const i2s_pdm_tx portENTER_CRITICAL(&g_i2s.spinlock); /* Set clock configurations in HAL*/ i2s_hal_set_tx_clock(&handle->controller->hal, &clk_info, clk_cfg->clk_src); + /* Work aroud for PDM TX clock, set the raw division directly to reduce the noise */ + i2s_ll_tx_set_raw_clk_div(handle->controller->hal.dev, 1, 1, 0, 0); portEXIT_CRITICAL(&g_i2s.spinlock); /* Update the mode info: clock configuration */ @@ -114,8 +116,14 @@ static esp_err_t i2s_pdm_tx_set_gpio(i2s_chan_handle_t handle, const i2s_pdm_tx_ ESP_ERR_INVALID_ARG, TAG, "clk gpio is invalid"); ESP_RETURN_ON_FALSE((gpio_cfg->dout == -1 || GPIO_IS_VALID_GPIO(gpio_cfg->dout)), ESP_ERR_INVALID_ARG, TAG, "dout gpio is invalid"); + i2s_pdm_tx_config_t *pdm_tx_cfg = (i2s_pdm_tx_config_t *)handle->mode_info; /* Set data output GPIO */ i2s_gpio_check_and_set(gpio_cfg->dout, i2s_periph_signal[id].data_out_sig, false, false); +#if SOC_I2S_HW_VERSION_2 + if (pdm_tx_cfg->slot_cfg.line_mode == I2S_PDM_TX_TWO_LINE_DAC) { + i2s_gpio_check_and_set(gpio_cfg->dout2, i2s_periph_signal[id].data_out1_sig, false, false); + } +#endif if (handle->role == I2S_ROLE_SLAVE) { /* For "tx + slave" mode, select TX signal index for ws and bck */ @@ -132,7 +140,6 @@ static esp_err_t i2s_pdm_tx_set_gpio(i2s_chan_handle_t handle, const i2s_pdm_tx_ i2s_ll_mclk_bind_to_tx_clk(handle->controller->hal.dev); #endif /* Update the mode info: gpio configuration */ - i2s_pdm_tx_config_t *pdm_tx_cfg = (i2s_pdm_tx_config_t *)handle->mode_info; memcpy(&(pdm_tx_cfg->gpio_cfg), gpio_cfg, sizeof(i2s_pdm_tx_gpio_config_t)); return ESP_OK; @@ -189,6 +196,7 @@ esp_err_t i2s_channel_init_pdm_tx_mode(i2s_chan_handle_t handle, const i2s_pdm_t /* Initialization finished, mark state as ready */ handle->state = I2S_CHAN_STATE_READY; xSemaphoreGive(handle->mutex); + ESP_LOGD(TAG, "The tx channel on I2S0 has been initialized to PDM TX mode successfully"); return ret; err: @@ -460,6 +468,7 @@ esp_err_t i2s_channel_init_pdm_rx_mode(i2s_chan_handle_t handle, const i2s_pdm_r /* Initialization finished, mark state as ready */ handle->state = I2S_CHAN_STATE_READY; xSemaphoreGive(handle->mutex); + ESP_LOGD(TAG, "The rx channel on I2S0 has been initialized to PDM RX mode successfully"); return ret; err: diff --git a/components/driver/i2s/i2s_std.c b/components/driver/i2s/i2s_std.c index f60f8a852c..1acafed8c0 100644 --- a/components/driver/i2s/i2s_std.c +++ b/components/driver/i2s/i2s_std.c @@ -247,6 +247,8 @@ esp_err_t i2s_channel_init_std_mode(i2s_chan_handle_t handle, const i2s_std_conf /* Initialization finished, mark state as ready */ handle->state = I2S_CHAN_STATE_READY; xSemaphoreGive(handle->mutex); + ESP_LOGD(TAG, "The %s channel on I2S%d has been initialized to STD mode successfully", + handle->dir == I2S_DIR_TX ? "tx" : "rx", handle->controller->id); return ret; err: diff --git a/components/driver/i2s/i2s_tdm.c b/components/driver/i2s/i2s_tdm.c index ce7a12522d..01fb79c117 100644 --- a/components/driver/i2s/i2s_tdm.c +++ b/components/driver/i2s/i2s_tdm.c @@ -40,6 +40,14 @@ static esp_err_t i2s_tdm_calculate_clock(i2s_chan_handle_t handle, const i2s_tdm clk_info->bclk = rate * handle->total_slot * slot_bits; clk_info->mclk = rate * clk_cfg->mclk_multiple; clk_info->bclk_div = clk_info->mclk / clk_info->bclk; + /* While RECEIVING multiple slots, the data will go wrong if the bclk_div is euqal or smaller than 2 */ + check: + if (clk_info->bclk_div <= 2) { + clk_info->mclk *= 2; + clk_info->bclk_div = clk_info->mclk / clk_info->bclk; + ESP_LOGW(TAG, "the current mclk multiple is too small, adjust the mclk multiple to %d", clk_info->mclk / rate); + goto check; + } } else { /* For slave mode, mclk >= bclk * 8, so fix bclk_div to 2 first */ clk_info->bclk_div = 8; @@ -241,6 +249,8 @@ esp_err_t i2s_channel_init_tdm_mode(i2s_chan_handle_t handle, const i2s_tdm_conf /* Initialization finished, mark state as ready */ handle->state = I2S_CHAN_STATE_READY; xSemaphoreGive(handle->mutex); + ESP_LOGD(TAG, "The %s channel on I2S%d has been initialized to TDM mode successfully", + handle->dir == I2S_DIR_TX ? "tx" : "rx", handle->controller->id); return ret; err: diff --git a/components/driver/include/driver/i2s_common.h b/components/driver/include/driver/i2s_common.h index f68f54814c..f1f06ff825 100644 --- a/components/driver/include/driver/i2s_common.h +++ b/components/driver/include/driver/i2s_common.h @@ -22,8 +22,8 @@ extern "C" { #define I2S_CHANNEL_DEFAULT_CONFIG(i2s_num, i2s_role) { \ .id = i2s_num, \ .role = i2s_role, \ - .dma_desc_num = 3, \ - .dma_frame_num = 500, \ + .dma_desc_num = 6, \ + .dma_frame_num = 250, \ .auto_clear = false, \ } diff --git a/components/driver/include/driver/i2s_pdm.h b/components/driver/include/driver/i2s_pdm.h index c07ddeb545..2ca0861dc3 100644 --- a/components/driver/include/driver/i2s_pdm.h +++ b/components/driver/include/driver/i2s_pdm.h @@ -32,6 +32,8 @@ extern "C" { .data_bit_width = bits_per_sample, \ .slot_bit_width = I2S_SLOT_BIT_WIDTH_AUTO, \ .slot_mode = mono_or_stereo, \ + .slot_mask = (mono_or_stereo == I2S_SLOT_MODE_MONO) ? \ + I2S_PDM_SLOT_LEFT : I2S_PDM_SLOT_BOTH, \ } /** @@ -53,6 +55,8 @@ typedef struct { i2s_data_bit_width_t data_bit_width; /*!< I2S sample data bit width (valid data bits per sample), only support 16 bits for PDM mode */ i2s_slot_bit_width_t slot_bit_width; /*!< I2S slot bit width (total bits per slot) , only support 16 bits for PDM mode */ i2s_slot_mode_t slot_mode; /*!< Set mono or stereo mode with I2S_SLOT_MODE_MONO or I2S_SLOT_MODE_STEREO */ + /* Particular fields */ + i2s_pdm_slot_mask_t slot_mask; /*!< Choose the slots to activate */ } i2s_pdm_rx_slot_config_t; /** @@ -168,16 +172,16 @@ esp_err_t i2s_channel_reconfig_pdm_rx_gpio(i2s_chan_handle_t handle, const i2s_p .slot_mode = mono_or_stereo, \ .sd_prescale = 0, \ .sd_scale = I2S_PDM_SIG_SCALING_MUL_1, \ - .hp_scale = I2S_PDM_SIG_SCALING_MUL_1, \ + .hp_scale = I2S_PDM_SIG_SCALING_DIV_2, \ .lp_scale = I2S_PDM_SIG_SCALING_MUL_1, \ .sinc_scale = I2S_PDM_SIG_SCALING_MUL_1, \ - .sd_en = true, \ + .line_mode = I2S_PDM_TX_ONE_LINE_CODEC, \ .hp_en = true, \ - .hp_cut_off_freq_hz = 49, \ + .hp_cut_off_freq_hz = 35.5, \ .sd_dither = 0, \ - .sd_dither2 = 0, \ + .sd_dither2 = 1, \ } -#else +#else // SOC_I2S_HW_VERSION_2 /** * @brief PDM style in 2 slots(TX) * @param bits_per_sample i2s data bit width, only support 16 bits for PDM mode @@ -209,7 +213,7 @@ esp_err_t i2s_channel_reconfig_pdm_rx_gpio(i2s_chan_handle_t handle, const i2s_p .clk_src = I2S_CLK_SRC_DEFAULT, \ .mclk_multiple = I2S_MCLK_MULTIPLE_256, \ .up_sample_fp = 960, \ - .up_sample_fs = ((rate) / 100), \ + .up_sample_fs = 480, \ } /* @@ -234,7 +238,10 @@ typedef struct { /* General fields */ i2s_data_bit_width_t data_bit_width; /*!< I2S sample data bit width (valid data bits per sample), only support 16 bits for PDM mode */ i2s_slot_bit_width_t slot_bit_width; /*!< I2S slot bit width (total bits per slot), only support 16 bits for PDM mode */ - i2s_slot_mode_t slot_mode; /*!< Set mono or stereo mode with I2S_SLOT_MODE_MONO or I2S_SLOT_MODE_STEREO */ + i2s_slot_mode_t slot_mode; /*!< Set mono or stereo mode with I2S_SLOT_MODE_MONO or I2S_SLOT_MODE_STEREO + * For PDM TX mode, mono means the data buffer only contains one slot data, + * Stereo means the data buffer contains two slots data + */ /* Particular fields */ uint32_t sd_prescale; /*!< Sigma-delta filter prescale */ i2s_pdm_sig_scale_t sd_scale; /*!< Sigma-delta filter scaling value */ @@ -242,7 +249,7 @@ typedef struct { i2s_pdm_sig_scale_t lp_scale; /*!< Low pass filter scaling value */ i2s_pdm_sig_scale_t sinc_scale; /*!< Sinc filter scaling value */ #if SOC_I2S_HW_VERSION_2 - bool sd_en; /*!< Sigma-delta filter enable */ + i2s_pdm_tx_line_mode_t line_mode; /*!< PDM TX line mode, on-line codec, one-line dac, two-line dac mode can be selected */ bool hp_en; /*!< High pass filter enable */ float hp_cut_off_freq_hz; /*!< High pass filter cut-off frequency, range 23.3Hz ~ 185Hz, see cut-off frequency sheet above */ uint32_t sd_dither; /*!< Sigma-delta filter dither */ @@ -269,6 +276,11 @@ typedef struct { typedef struct { gpio_num_t clk; /*!< PDM clk pin, output */ gpio_num_t dout; /*!< DATA pin, output */ +#if SOC_I2S_HW_VERSION_2 + gpio_num_t dout2; /*!< The second data pin for the DAC dual-line mode, + * only take effect when the line mode is `I2S_PDM_TX_TWO_LINE_DAC` + */ +#endif struct { uint32_t clk_inv: 1; /*!< Set 1 to invert the clk output */ } invert_flags; /*!< GPIO pin invert flags */ diff --git a/components/driver/include/driver/i2s_types.h b/components/driver/include/driver/i2s_types.h index 9709d91c71..84cd39241b 100644 --- a/components/driver/include/driver/i2s_types.h +++ b/components/driver/include/driver/i2s_types.h @@ -47,6 +47,7 @@ typedef enum { I2S_MCLK_MULTIPLE_128 = 128, /*!< mclk = sample_rate * 128 */ I2S_MCLK_MULTIPLE_256 = 256, /*!< mclk = sample_rate * 256 */ I2S_MCLK_MULTIPLE_384 = 384, /*!< mclk = sample_rate * 384 */ + I2S_MCLK_MULTIPLE_512 = 512, /*!< mclk = sample_rate * 512 */ } i2s_mclk_multiple_t; /** diff --git a/components/hal/esp32c3/include/hal/i2s_ll.h b/components/hal/esp32c3/include/hal/i2s_ll.h index db8321746c..de35fddfbd 100644 --- a/components/hal/esp32c3/include/hal/i2s_ll.h +++ b/components/hal/esp32c3/include/hal/i2s_ll.h @@ -218,6 +218,40 @@ static inline void i2s_ll_tx_set_bck_div_num(i2s_dev_t *hw, uint32_t val) hw->tx_conf1.tx_bck_div_num = val - 1; } +/** + * @brief Set I2S tx raw clock division + * + * @param hw Peripheral I2S hardware instance address. + * @param x div x + * @param y div y + * @param z div z + * @param yn1 yn1 + */ +static inline void i2s_ll_tx_set_raw_clk_div(i2s_dev_t *hw, uint32_t x, uint32_t y, uint32_t z, uint32_t yn1) +{ + hw->tx_clkm_div_conf.tx_clkm_div_x = x; + hw->tx_clkm_div_conf.tx_clkm_div_y = y; + hw->tx_clkm_div_conf.tx_clkm_div_z = z; + hw->tx_clkm_div_conf.tx_clkm_div_yn1 = yn1; +} + +/** + * @brief Set I2S rx raw clock division + * + * @param hw Peripheral I2S hardware instance address. + * @param x div x + * @param y div y + * @param z div z + * @param yn1 yn1 + */ +static inline void i2s_ll_rx_set_raw_clk_div(i2s_dev_t *hw, uint32_t x, uint32_t y, uint32_t z, uint32_t yn1) +{ + hw->rx_clkm_div_conf.rx_clkm_div_x = x; + hw->rx_clkm_div_conf.rx_clkm_div_y = y; + hw->rx_clkm_div_conf.rx_clkm_div_z = z; + hw->rx_clkm_div_conf.rx_clkm_div_yn1 = yn1; +} + /** * @brief Configure I2S TX module clock divider * @@ -272,7 +306,7 @@ finish: hw->tx_clkm_div_conf.tx_clkm_div_yn1 = 1; } else { hw->tx_clkm_div_conf.tx_clkm_div_x = denominator / numerator - 1; - hw->tx_clkm_div_conf.tx_clkm_div_y = denominator % numerator + 1; + hw->tx_clkm_div_conf.tx_clkm_div_y = denominator % numerator; hw->tx_clkm_div_conf.tx_clkm_div_z = numerator; hw->tx_clkm_div_conf.tx_clkm_div_yn1 = 0; } @@ -346,7 +380,7 @@ finish: hw->rx_clkm_div_conf.rx_clkm_div_yn1 = 1; } else { hw->rx_clkm_div_conf.rx_clkm_div_x = denominator / numerator - 1; - hw->rx_clkm_div_conf.rx_clkm_div_y = denominator % numerator + 1; + hw->rx_clkm_div_conf.rx_clkm_div_y = denominator % numerator; hw->rx_clkm_div_conf.rx_clkm_div_z = numerator; hw->rx_clkm_div_conf.rx_clkm_div_yn1 = 0; } @@ -607,6 +641,29 @@ static inline void i2s_ll_rx_select_slot(i2s_dev_t *hw, i2s_std_slot_mask_t slot } } +/** + * @brief PDM slot mode + * + * @param hw Peripheral I2S hardware instance address. + * @param mod Channel mode + * while tx_ws_idle_pol = 0: + * 0: stereo + * 1: Both slots transmit left + * 2: Both slots transmit right + * 3: Left transmits `conf_single_data` right transmits data + * 4: Right transmits `conf_single_data` left transmits data + * while tx_ws_idle_pol = 1: + 0: stereo + * 1: Both slots transmit right + * 2: Both slots transmit left + * 3: Right transmits `conf_single_data` left transmits data + * 4: Left transmits `conf_single_data` right transmits data + */ +static inline void i2s_ll_tx_set_pdm_chan_mod(i2s_dev_t *hw, uint32_t mod) +{ + hw->tx_conf.tx_chan_mod = mod; +} + /** * @brief Set TX WS signal pol level * @@ -772,18 +829,6 @@ static inline void i2s_ll_tx_enable_pdm_hp_filter(i2s_dev_t *hw, bool enable) hw->tx_pcm2pdm_conf.tx_pdm_hp_bypass = !enable; } -/** - * @brief Enable I2S TX PDM sigma-delta codec - * - * @param hw Peripheral I2S hardware instance address. - * @param dither I2S TX PDM sigmadelta dither value - */ -static inline void i2s_ll_tx_enable_pdm_sd_codec(i2s_dev_t *hw, bool enable) -{ - hw->tx_pcm2pdm_conf.tx_pdm_dac_2out_en = enable; - hw->tx_pcm2pdm_conf.tx_pdm_dac_mode_en = enable; -} - /** * @brief Set I2S TX PDM sigma-delta codec dither * @@ -1010,6 +1055,73 @@ static inline void i2s_ll_share_bck_ws(i2s_dev_t *hw, bool ena) hw->tx_conf.sig_loopback = ena; } +/** + * @brief PDM TX DMA data take mode + * + * @param hw Peripheral I2S hardware instance address. + * @param is_mono The DMA data only has one slot (mono) or contains two slots (stereo) + * @param is_fst_valid Whether take the DMA data at the first half period + * Only take effet when 'is_mono' is true + */ +static inline void i2s_ll_tx_pdm_dma_take_mode(i2s_dev_t *hw, bool is_mono, bool is_fst_valid) +{ + hw->tx_conf.tx_mono = is_mono; + hw->tx_conf.tx_mono_fst_vld = is_fst_valid; +} + +/** + * @brief PDM TX slot mode + * @note Mode Left Slot Right Slot Chan Mode WS Pol + * ----------------------------------------------------------------- + * Stereo Left Right 0 x + * ----------------------------------------------------------------- + * Mono Left Left 1 0 + * Mono Right Right 2 0 + * Mono Single Right 3 0 + * Mono Left Single 4 0 + * ----------------------------------------------------------------- + * Mono Right Right 1 1 + * Mono Left Left 2 1 + * Mono Left Single 3 1 + * Mono Single Right 4 1 + * + * @param hw Peripheral I2S hardware instance address. + * @param is_mono The DMA data only has one slot (mono) or contains two slots (stereo) + * @param is_copy Whether the un-selected slot copies the data from the selected one + * If not, the un-selected slot will transmit the data from 'conf_single_data' + * @param mask The slot mask to selet the slot + */ +static inline void i2s_ll_tx_pdm_slot_mode(i2s_dev_t *hw, bool is_mono, bool is_copy, i2s_pdm_slot_mask_t mask) +{ + if (is_mono) { + /* The default tx_ws_idle_pol is false */ + if (is_copy) { + hw->tx_conf.tx_chan_mod = mask == I2S_PDM_SLOT_LEFT ? 1 : 2; + } else { + hw->tx_conf.tx_chan_mod = mask == I2S_PDM_SLOT_LEFT ? 4 : 3; + } + } else { + hw->tx_conf.tx_chan_mod = 0; + } +} + +/** + * @brief PDM TX line mode + * @note Mode DAC Mode 2 lines output + * ------------------------------------------- + * PDM codec 0 1 + * DAC 1-line 1 0 + * DAC 2-line 1 1 + * + * @param hw Peripheral I2S hardware instance address. + * @param line_mode PDM TX line mode + */ +static inline void i2s_ll_tx_pdm_line_mode(i2s_dev_t *hw, i2s_pdm_tx_line_mode_t line_mode) +{ + hw->tx_pcm2pdm_conf.tx_pdm_dac_mode_en = line_mode > I2S_PDM_TX_ONE_LINE_CODEC; + hw->tx_pcm2pdm_conf.tx_pdm_dac_2out_en = line_mode != I2S_PDM_TX_ONE_LINE_DAC; +} + #ifdef __cplusplus } #endif diff --git a/components/hal/esp32h2/include/hal/i2s_ll.h b/components/hal/esp32h2/include/hal/i2s_ll.h index d87c2b8f73..50a9fc5b10 100644 --- a/components/hal/esp32h2/include/hal/i2s_ll.h +++ b/components/hal/esp32h2/include/hal/i2s_ll.h @@ -220,6 +220,40 @@ static inline void i2s_ll_tx_set_bck_div_num(i2s_dev_t *hw, uint32_t val) hw->tx_conf1.tx_bck_div_num = val - 1; } +/** + * @brief Set I2S tx raw clock division + * + * @param hw Peripheral I2S hardware instance address. + * @param x div x + * @param y div y + * @param z div z + * @param yn1 yn1 + */ +static inline void i2s_ll_tx_set_raw_clk_div(i2s_dev_t *hw, uint32_t x, uint32_t y, uint32_t z, uint32_t yn1) +{ + hw->tx_clkm_div_conf.tx_clkm_div_x = x; + hw->tx_clkm_div_conf.tx_clkm_div_y = y; + hw->tx_clkm_div_conf.tx_clkm_div_z = z; + hw->tx_clkm_div_conf.tx_clkm_div_yn1 = yn1; +} + +/** + * @brief Set I2S rx raw clock division + * + * @param hw Peripheral I2S hardware instance address. + * @param x div x + * @param y div y + * @param z div z + * @param yn1 yn1 + */ +static inline void i2s_ll_rx_set_raw_clk_div(i2s_dev_t *hw, uint32_t x, uint32_t y, uint32_t z, uint32_t yn1) +{ + hw->rx_clkm_div_conf.rx_clkm_div_x = x; + hw->rx_clkm_div_conf.rx_clkm_div_y = y; + hw->rx_clkm_div_conf.rx_clkm_div_z = z; + hw->rx_clkm_div_conf.rx_clkm_div_yn1 = yn1; +} + /** * @brief Configure I2S TX module clock divider * @@ -274,7 +308,7 @@ finish: hw->tx_clkm_div_conf.tx_clkm_div_yn1 = 1; } else { hw->tx_clkm_div_conf.tx_clkm_div_x = denominator / numerator - 1; - hw->tx_clkm_div_conf.tx_clkm_div_y = denominator % numerator + 1; + hw->tx_clkm_div_conf.tx_clkm_div_y = denominator % numerator; hw->tx_clkm_div_conf.tx_clkm_div_z = numerator; hw->tx_clkm_div_conf.tx_clkm_div_yn1 = 0; } @@ -348,7 +382,7 @@ finish: hw->rx_clkm_div_conf.rx_clkm_div_yn1 = 1; } else { hw->rx_clkm_div_conf.rx_clkm_div_x = denominator / numerator - 1; - hw->rx_clkm_div_conf.rx_clkm_div_y = denominator % numerator + 1; + hw->rx_clkm_div_conf.rx_clkm_div_y = denominator % numerator; hw->rx_clkm_div_conf.rx_clkm_div_z = numerator; hw->rx_clkm_div_conf.rx_clkm_div_yn1 = 0; } @@ -609,6 +643,28 @@ static inline void i2s_ll_rx_select_slot(i2s_dev_t *hw, i2s_std_slot_mask_t slot } } +/** + * @brief PDM slot mode + * + * @param hw Peripheral I2S hardware instance address. + * @param mod Channel mode + * while tx_ws_idle_pol = 0: + * 0: stereo + * 1: Both slots transmit left + * 2: Both slots transmit right + * 3: Left transmits `conf_single_data` right transmits data + * 4: Right transmits `conf_single_data` left transmits data + * while tx_ws_idle_pol = 1: + 0: stereo + * 1: Both slots transmit right + * 2: Both slots transmit left + * 3: Right transmits `conf_single_data` left transmits data + * 4: Left transmits `conf_single_data` right transmits data + */ +static inline void i2s_ll_tx_set_pdm_chan_mod(i2s_dev_t *hw, uint32_t mod) +{ + hw->tx_conf.tx_chan_mod = mod; +} /** * @brief Set TX WS signal pol level @@ -775,18 +831,6 @@ static inline void i2s_ll_tx_enable_pdm_hp_filter(i2s_dev_t *hw, bool enable) hw->tx_pcm2pdm_conf.tx_pdm_hp_bypass = !enable; } -/** - * @brief Enable I2S TX PDM sigma-delta codec - * - * @param hw Peripheral I2S hardware instance address. - * @param dither I2S TX PDM sigmadelta dither value - */ -static inline void i2s_ll_tx_enable_pdm_sd_codec(i2s_dev_t *hw, bool enable) -{ - hw->tx_pcm2pdm_conf.tx_pdm_dac_2out_en = enable; - hw->tx_pcm2pdm_conf.tx_pdm_dac_mode_en = enable; -} - /** * @brief Set I2S TX PDM sigma-delta codec dither * @@ -1024,6 +1068,73 @@ static inline void i2s_ll_rx_enable_mono_mode(i2s_dev_t *hw, bool mono_ena) hw->rx_conf.rx_mono_fst_vld = mono_ena; } +/** + * @brief PDM TX DMA data take mode + * + * @param hw Peripheral I2S hardware instance address. + * @param is_mono The DMA data only has one slot (mono) or contains two slots (stereo) + * @param is_fst_valid Whether take the DMA data at the first half period + * Only take effet when 'is_mono' is true + */ +static inline void i2s_ll_tx_pdm_dma_take_mode(i2s_dev_t *hw, bool is_mono, bool is_fst_valid) +{ + hw->tx_conf.tx_mono = is_mono; + hw->tx_conf.tx_mono_fst_vld = is_fst_valid; +} + +/** + * @brief PDM TX slot mode + * @note Mode Left Slot Right Slot Chan Mode WS Pol + * ----------------------------------------------------------------- + * Stereo Left Right 0 x + * ----------------------------------------------------------------- + * Mono Left Left 1 0 + * Mono Right Right 2 0 + * Mono Single Right 3 0 + * Mono Left Single 4 0 + * ----------------------------------------------------------------- + * Mono Right Right 1 1 + * Mono Left Left 2 1 + * Mono Left Single 3 1 + * Mono Single Right 4 1 + * + * @param hw Peripheral I2S hardware instance address. + * @param is_mono The DMA data only has one slot (mono) or contains two slots (stereo) + * @param is_copy Whether the un-selected slot copies the data from the selected one + * If not, the un-selected slot will transmit the data from 'conf_single_data' + * @param mask The slot mask to selet the slot + */ +static inline void i2s_ll_tx_pdm_slot_mode(i2s_dev_t *hw, bool is_mono, bool is_copy, i2s_pdm_slot_mask_t mask) +{ + if (is_mono) { + /* The default tx_ws_idle_pol is false */ + if (is_copy) { + hw->tx_conf.tx_chan_mod = mask == I2S_PDM_SLOT_LEFT ? 1 : 2; + } else { + hw->tx_conf.tx_chan_mod = mask == I2S_PDM_SLOT_LEFT ? 4 : 3; + } + } else { + hw->tx_conf.tx_chan_mod = 0; + } +} + +/** + * @brief PDM TX line mode + * @note Mode DAC Mode 2 lines output + * ------------------------------------------- + * PDM codec 0 1 + * DAC 1-line 1 0 + * DAC 2-line 1 1 + * + * @param hw Peripheral I2S hardware instance address. + * @param line_mode PDM TX line mode + */ +static inline void i2s_ll_tx_pdm_line_mode(i2s_dev_t *hw, i2s_pdm_tx_line_mode_t line_mode) +{ + hw->tx_pcm2pdm_conf.tx_pdm_dac_mode_en = line_mode > I2S_PDM_TX_ONE_LINE_CODEC; + hw->tx_pcm2pdm_conf.tx_pdm_dac_2out_en = line_mode != I2S_PDM_TX_ONE_LINE_DAC; +} + #ifdef __cplusplus } #endif diff --git a/components/hal/esp32s3/include/hal/i2s_ll.h b/components/hal/esp32s3/include/hal/i2s_ll.h index e5ebe9eaa8..193bea171f 100644 --- a/components/hal/esp32s3/include/hal/i2s_ll.h +++ b/components/hal/esp32s3/include/hal/i2s_ll.h @@ -221,6 +221,40 @@ static inline void i2s_ll_tx_set_bck_div_num(i2s_dev_t *hw, uint32_t val) hw->tx_conf1.tx_bck_div_num = val - 1; } +/** + * @brief Set I2S tx raw clock division + * + * @param hw Peripheral I2S hardware instance address. + * @param x div x + * @param y div y + * @param z div z + * @param yn1 yn1 + */ +static inline void i2s_ll_tx_set_raw_clk_div(i2s_dev_t *hw, uint32_t x, uint32_t y, uint32_t z, uint32_t yn1) +{ + hw->tx_clkm_div_conf.tx_clkm_div_x = x; + hw->tx_clkm_div_conf.tx_clkm_div_y = y; + hw->tx_clkm_div_conf.tx_clkm_div_z = z; + hw->tx_clkm_div_conf.tx_clkm_div_yn1 = yn1; +} + +/** + * @brief Set I2S rx raw clock division + * + * @param hw Peripheral I2S hardware instance address. + * @param x div x + * @param y div y + * @param z div z + * @param yn1 yn1 + */ +static inline void i2s_ll_rx_set_raw_clk_div(i2s_dev_t *hw, uint32_t x, uint32_t y, uint32_t z, uint32_t yn1) +{ + hw->rx_clkm_div_conf.rx_clkm_div_x = x; + hw->rx_clkm_div_conf.rx_clkm_div_y = y; + hw->rx_clkm_div_conf.rx_clkm_div_z = z; + hw->rx_clkm_div_conf.rx_clkm_div_yn1 = yn1; +} + /** * @brief Configure I2S TX module clock divider * @@ -275,7 +309,7 @@ finish: hw->tx_clkm_div_conf.tx_clkm_div_yn1 = 1; } else { hw->tx_clkm_div_conf.tx_clkm_div_x = denominator / numerator - 1; - hw->tx_clkm_div_conf.tx_clkm_div_y = denominator % numerator + 1; + hw->tx_clkm_div_conf.tx_clkm_div_y = denominator % numerator; hw->tx_clkm_div_conf.tx_clkm_div_z = numerator; hw->tx_clkm_div_conf.tx_clkm_div_yn1 = 0; } @@ -349,7 +383,7 @@ finish: hw->rx_clkm_div_conf.rx_clkm_div_yn1 = 1; } else { hw->rx_clkm_div_conf.rx_clkm_div_x = denominator / numerator - 1; - hw->rx_clkm_div_conf.rx_clkm_div_y = denominator % numerator + 1; + hw->rx_clkm_div_conf.rx_clkm_div_y = denominator % numerator; hw->rx_clkm_div_conf.rx_clkm_div_z = numerator; hw->rx_clkm_div_conf.rx_clkm_div_yn1 = 0; } @@ -610,6 +644,29 @@ static inline void i2s_ll_rx_select_slot(i2s_dev_t *hw, i2s_std_slot_mask_t slot } } +/** + * @brief PDM slot mode + * + * @param hw Peripheral I2S hardware instance address. + * @param mod Channel mode + * while tx_ws_idle_pol = 0: + * 0: stereo + * 1: Both slots transmit left + * 2: Both slots transmit right + * 3: Left transmits `conf_single_data` right transmits data + * 4: Right transmits `conf_single_data` left transmits data + * while tx_ws_idle_pol = 1: + 0: stereo + * 1: Both slots transmit right + * 2: Both slots transmit left + * 3: Right transmits `conf_single_data` left transmits data + * 4: Left transmits `conf_single_data` right transmits data + */ +static inline void i2s_ll_tx_set_pdm_chan_mod(i2s_dev_t *hw, uint32_t mod) +{ + hw->tx_conf.tx_chan_mod = mod; +} + /** * @brief Set TX WS signal pol level * @@ -827,18 +884,6 @@ static inline void i2s_ll_tx_enable_pdm_hp_filter(i2s_dev_t *hw, bool enable) hw->tx_pcm2pdm_conf.tx_hp_bypass = !enable; } -/** - * @brief Enable I2S TX PDM sigma-delta codec - * - * @param hw Peripheral I2S hardware instance address. - * @param dither I2S TX PDM sigmadelta dither value - */ -static inline void i2s_ll_tx_enable_pdm_sd_codec(i2s_dev_t *hw, bool enable) -{ - hw->tx_pcm2pdm_conf.tx_dac_2out_en = enable; - hw->tx_pcm2pdm_conf.tx_dac_mode_en = enable; -} - /** * @brief Set I2S TX PDM sigma-delta codec dither * @@ -1035,6 +1080,73 @@ static inline void i2s_ll_share_bck_ws(i2s_dev_t *hw, bool ena) hw->tx_conf.sig_loopback = ena; } +/** + * @brief PDM TX DMA data take mode + * + * @param hw Peripheral I2S hardware instance address. + * @param is_mono The DMA data only has one slot (mono) or contains two slots (stereo) + * @param is_fst_valid Whether take the DMA data at the first half period + * Only take effet when 'is_mono' is true + */ +static inline void i2s_ll_tx_pdm_dma_take_mode(i2s_dev_t *hw, bool is_mono, bool is_fst_valid) +{ + hw->tx_conf.tx_mono = is_mono; + hw->tx_conf.tx_mono_fst_vld = is_fst_valid; +} + +/** + * @brief PDM TX slot mode + * @note Mode Left Slot Right Slot Chan Mode WS Pol + * ----------------------------------------------------------------- + * Stereo Left Right 0 x + * ----------------------------------------------------------------- + * Mono Left Left 1 0 + * Mono Right Right 2 0 + * Mono Single Right 3 0 + * Mono Left Single 4 0 + * ----------------------------------------------------------------- + * Mono Right Right 1 1 + * Mono Left Left 2 1 + * Mono Left Single 3 1 + * Mono Single Right 4 1 + * + * @param hw Peripheral I2S hardware instance address. + * @param is_mono The DMA data only has one slot (mono) or contains two slots (stereo) + * @param is_copy Whether the un-selected slot copies the data from the selected one + * If not, the un-selected slot will transmit the data from 'conf_single_data' + * @param mask The slot mask to selet the slot + */ +static inline void i2s_ll_tx_pdm_slot_mode(i2s_dev_t *hw, bool is_mono, bool is_copy, i2s_pdm_slot_mask_t mask) +{ + if (is_mono) { + /* The default tx_ws_idle_pol is false */ + if (is_copy) { + hw->tx_conf.tx_chan_mod = mask == I2S_PDM_SLOT_LEFT ? 1 : 2; + } else { + hw->tx_conf.tx_chan_mod = mask == I2S_PDM_SLOT_LEFT ? 4 : 3; + } + } else { + hw->tx_conf.tx_chan_mod = 0; + } +} + +/** + * @brief PDM TX line mode + * @note Mode DAC Mode 2 lines output + * ------------------------------------------- + * PDM codec 0 1 + * DAC 1-line 1 0 + * DAC 2-line 1 1 + * + * @param hw Peripheral I2S hardware instance address. + * @param line_mode PDM TX line mode + */ +static inline void i2s_ll_tx_pdm_line_mode(i2s_dev_t *hw, i2s_pdm_tx_line_mode_t line_mode) +{ + hw->tx_pcm2pdm_conf.tx_dac_mode_en = line_mode > I2S_PDM_TX_ONE_LINE_CODEC; + hw->tx_pcm2pdm_conf.tx_dac_2out_en = line_mode != I2S_PDM_TX_ONE_LINE_DAC; +} + #ifdef __cplusplus } #endif diff --git a/components/hal/i2s_hal.c b/components/hal/i2s_hal.c index 6cadd8f591..89e26db79c 100644 --- a/components/hal/i2s_hal.c +++ b/components/hal/i2s_hal.c @@ -124,12 +124,10 @@ void i2s_hal_std_enable_rx_channel(i2s_hal_context_t *hal) #if SOC_I2S_SUPPORTS_PDM_TX void i2s_hal_pdm_set_tx_slot(i2s_hal_context_t *hal, bool is_slave, const i2s_hal_slot_config_t *slot_cfg) { - uint32_t slot_bit_width = (int)slot_cfg->slot_bit_width < (int)slot_cfg->data_bit_width ? - slot_cfg->data_bit_width : slot_cfg->slot_bit_width; + bool is_mono = slot_cfg->slot_mode == I2S_SLOT_MODE_MONO; i2s_ll_tx_reset(hal->dev); i2s_ll_tx_set_slave_mod(hal->dev, is_slave); //TX Slave - i2s_ll_tx_set_sample_bit(hal->dev, slot_bit_width, slot_cfg->data_bit_width); - i2s_ll_tx_enable_mono_mode(hal->dev, slot_cfg->slot_mode == I2S_SLOT_MODE_MONO); + i2s_ll_tx_enable_msb_shift(hal->dev, false); i2s_ll_tx_set_pdm_prescale(hal->dev, slot_cfg->pdm_tx.sd_prescale); i2s_ll_tx_set_pdm_hp_scale(hal->dev, slot_cfg->pdm_tx.hp_scale); @@ -138,11 +136,23 @@ void i2s_hal_pdm_set_tx_slot(i2s_hal_context_t *hal, bool is_slave, const i2s_ha i2s_ll_tx_set_pdm_sd_scale(hal->dev, slot_cfg->pdm_tx.sd_scale); #if SOC_I2S_HW_VERSION_1 + uint32_t slot_bit_width = (int)slot_cfg->slot_bit_width < (int)slot_cfg->data_bit_width ? + slot_cfg->data_bit_width : slot_cfg->slot_bit_width; i2s_ll_tx_force_enable_fifo_mod(hal->dev, true); + i2s_ll_tx_set_sample_bit(hal->dev, slot_bit_width, slot_cfg->data_bit_width); + i2s_ll_tx_enable_mono_mode(hal->dev, is_mono); #elif SOC_I2S_HW_VERSION_2 - /* Still need to enable the first 2 TDM channel mask to get the correct number of frame */ - i2s_ll_tx_set_active_chan_mask(hal->dev, I2S_TDM_SLOT0 | I2S_TDM_SLOT1); - i2s_ll_tx_enable_pdm_hp_filter(hal->dev, slot_cfg->pdm_tx.hp_en); + /* PDM TX line mode */ + i2s_ll_tx_pdm_line_mode(hal->dev, slot_cfg->pdm_tx.line_mode); + /* Force use 32 bit in PDM TX stereo mode to satisfy the frequency */ + uint32_t slot_bit_width = is_mono ? 16 : 32; + i2s_ll_tx_set_sample_bit(hal->dev, slot_bit_width, slot_bit_width); + i2s_ll_tx_set_half_sample_bit(hal->dev, 16); // Fixed to 16 in PDM mode + /* By default, taking the DMA data at the first half period of WS */ + i2s_ll_tx_pdm_dma_take_mode(hal->dev, is_mono, true); + i2s_ll_tx_set_ws_idle_pol(hal->dev, false); + /* Slot mode seems not take effect according to the test, leave it default here */ + i2s_ll_tx_pdm_slot_mode(hal->dev, is_mono, false, I2S_PDM_SLOT_BOTH); uint8_t cnt = 0; float min = 1000; float expt_cut_off = slot_cfg->pdm_tx.hp_cut_off_freq_hz; @@ -154,9 +164,9 @@ void i2s_hal_pdm_set_tx_slot(i2s_hal_context_t *hal, bool is_slave, const i2s_ha cnt = i; } } + i2s_ll_tx_enable_pdm_hp_filter(hal->dev, slot_cfg->pdm_tx.hp_en); i2s_ll_tx_set_pdm_hp_filter_param0(hal->dev, cut_off_coef[cnt][1]); i2s_ll_tx_set_pdm_hp_filter_param5(hal->dev, cut_off_coef[cnt][2]); - i2s_ll_tx_enable_pdm_sd_codec(hal->dev, slot_cfg->pdm_tx.sd_en); i2s_ll_tx_set_pdm_sd_dither(hal->dev, slot_cfg->pdm_tx.sd_dither); i2s_ll_tx_set_pdm_sd_dither2(hal->dev, slot_cfg->pdm_tx.sd_dither2); #endif @@ -176,12 +186,14 @@ void i2s_hal_pdm_set_rx_slot(i2s_hal_context_t *hal, bool is_slave, const i2s_ha i2s_ll_rx_reset(hal->dev); i2s_ll_rx_set_slave_mod(hal->dev, is_slave); //RX Slave i2s_ll_rx_set_sample_bit(hal->dev, slot_bit_width, slot_cfg->data_bit_width); - i2s_ll_rx_enable_mono_mode(hal->dev, slot_cfg->slot_mode == I2S_SLOT_MODE_MONO); #if SOC_I2S_HW_VERSION_1 + i2s_ll_rx_enable_mono_mode(hal->dev, slot_cfg->slot_mode == I2S_SLOT_MODE_MONO); + i2s_ll_rx_select_slot(hal->dev, slot_cfg->pdm_rx.slot_mask, false); i2s_ll_rx_force_enable_fifo_mod(hal->dev, true); #elif SOC_I2S_HW_VERSION_2 - /* Still need to enable the first 2 TDM channel mask to get the correct number of frame */ - i2s_ll_rx_set_active_chan_mask(hal->dev, I2S_TDM_SLOT0 | I2S_TDM_SLOT1); + i2s_ll_rx_enable_mono_mode(hal->dev, false); + /* Set the channel mask to enable corresponding slots */ + i2s_ll_rx_set_active_chan_mask(hal->dev, slot_cfg->pdm_rx.slot_mask); #endif } @@ -223,7 +235,7 @@ void i2s_hal_tdm_set_tx_slot(i2s_hal_context_t *hal, bool is_slave, const i2s_ha i2s_ll_tx_set_active_chan_mask(hal->dev, (slot_cfg->slot_mode == I2S_SLOT_MODE_MONO) ? I2S_TDM_SLOT0 : (uint32_t)slot_cfg->tdm.slot_mask); i2s_ll_tx_set_skip_mask(hal->dev, slot_cfg->tdm.skip_mask); - i2s_ll_tx_set_half_sample_bit(hal->dev, total_slot * slot_bit_width / 2); + i2s_ll_tx_set_half_sample_bit(hal->dev, __builtin_popcount(slot_cfg->tdm.slot_mask) * slot_bit_width / 2); i2s_ll_tx_set_bit_order(hal->dev, slot_cfg->tdm.bit_order_lsb); i2s_ll_tx_enable_left_align(hal->dev, slot_cfg->tdm.left_align); i2s_ll_tx_enable_big_endian(hal->dev, slot_cfg->tdm.big_endian); @@ -235,7 +247,6 @@ void i2s_hal_tdm_set_rx_slot(i2s_hal_context_t *hal, bool is_slave, const i2s_ha slot_cfg->data_bit_width : slot_cfg->slot_bit_width; uint32_t cnt; uint32_t msk = slot_cfg->tdm.slot_mask; - for (cnt = 0; msk; cnt++, msk >>= 1); /* Get the maximum slot number */ cnt = 32 - __builtin_clz(msk); /* There should be at least 2 slots in total even for mono mode */ @@ -257,7 +268,7 @@ void i2s_hal_tdm_set_rx_slot(i2s_hal_context_t *hal, bool is_slave, const i2s_ha /* In mono mode, there only should be one slot enabled, other inactive slots will transmit same data as enabled slot */ i2s_ll_rx_set_active_chan_mask(hal->dev, (slot_cfg->slot_mode == I2S_SLOT_MODE_MONO) ? I2S_TDM_SLOT0 : (uint32_t)slot_cfg->tdm.slot_mask); - i2s_ll_rx_set_half_sample_bit(hal->dev, total_slot * slot_bit_width / 2); + i2s_ll_rx_set_half_sample_bit(hal->dev, __builtin_popcount(slot_cfg->tdm.slot_mask) * slot_bit_width / 2); i2s_ll_rx_set_bit_order(hal->dev, slot_cfg->tdm.bit_order_lsb); i2s_ll_rx_enable_left_align(hal->dev, slot_cfg->tdm.left_align); i2s_ll_rx_enable_big_endian(hal->dev, slot_cfg->tdm.big_endian); diff --git a/components/hal/include/hal/i2s_hal.h b/components/hal/include/hal/i2s_hal.h index 71e5827fc7..4e80261dc1 100644 --- a/components/hal/include/hal/i2s_hal.h +++ b/components/hal/include/hal/i2s_hal.h @@ -73,13 +73,19 @@ typedef struct { i2s_pdm_sig_scale_t lp_scale; /*!< Low pass filter scaling value */ i2s_pdm_sig_scale_t sinc_scale; /*!< Sinc filter scaling value */ #if SOC_I2S_HW_VERSION_2 - bool sd_en; /*!< Sigma-delta filter enable */ + i2s_pdm_tx_line_mode_t line_mode; /*!< PDM TX line mode, on-line codec, one-line dac, two-line dac mode can be selected */ bool hp_en; /*!< High pass filter enable */ float hp_cut_off_freq_hz; /*!< High pass filter cut-off frequency, range 23.3Hz ~ 185Hz, see cut-off frequency sheet above */ uint32_t sd_dither; /*!< Sigma-delta filter dither */ uint32_t sd_dither2; /*!< Sigma-delta filter dither2 */ #endif // SOC_I2S_HW_VERSION_2 } pdm_tx; /*!< Specific configurations for PDM TX mode */ +#endif +#if SOC_I2S_SUPPORTS_PDM_RX + /* PDM TX configurations */ + struct { + i2s_pdm_slot_mask_t slot_mask; /*!< Choose the slots to activate */ + } pdm_rx; /*!< Specific configurations for PDM TX mode */ #endif }; diff --git a/components/hal/include/hal/i2s_types.h b/components/hal/include/hal/i2s_types.h index 272ae826ec..30767a7282 100644 --- a/components/hal/include/hal/i2s_types.h +++ b/components/hal/include/hal/i2s_types.h @@ -101,6 +101,20 @@ typedef enum { I2S_PDM_SIG_SCALING_MUL_2 = 2, /*!< I2S TX PDM signal scaling: x2 */ I2S_PDM_SIG_SCALING_MUL_4 = 3, /*!< I2S TX PDM signal scaling: x4 */ } i2s_pdm_sig_scale_t; + +#if SOC_I2S_HW_VERSION_2 +/** + * @brief PDM TX line mode + * @note For the standard codec mode, PDM pins are connect to a codec which requires both clock signal and data signal + * For the DAC output mode, PDM data signal can be connected to a power amplifier directly with a low-pass filter, + * normally, DAC output mode doesn't need the clock signal. + */ +typedef enum { + I2S_PDM_TX_ONE_LINE_CODEC, /*!< Standard PDM format output, left and right slot data on a single line */ + I2S_PDM_TX_ONE_LINE_DAC, /*!< PDM DAC format output, left or right slot data on a single line */ + I2S_PDM_TX_TWO_LINE_DAC, /*!< PDM DAC format output, left and right slot data on separated lines */ +} i2s_pdm_tx_line_mode_t; +#endif // SOC_I2S_HW_VERSION_2 #endif // SOC_I2S_SUPPORTS_PDM_TX /** @@ -112,6 +126,16 @@ typedef enum { I2S_STD_SLOT_LEFT_RIGHT = BIT(0) | BIT(1), /*!< I2S transmits or receives both left and right slot */ } i2s_std_slot_mask_t; +/** + * @brief I2S slot select in PDM mode + * + */ +typedef enum { + I2S_PDM_SLOT_RIGHT = BIT(0), /*!< I2S PDM only transmits or receives the PDM device whose 'select' pin is pulled up */ + I2S_PDM_SLOT_LEFT = BIT(1), /*!< I2S PDM only transmits or receives the PDM device whose 'select' pin is pulled down */ + I2S_PDM_SLOT_BOTH = BIT(0) | BIT(1), /*!< I2S PDM transmits or receives both two slots */ +} i2s_pdm_slot_mask_t; + #if SOC_I2S_SUPPORTS_TDM /** * @brief tdm slot number diff --git a/components/soc/esp32/i2s_periph.c b/components/soc/esp32/i2s_periph.c index d2c0c227a1..a96802b6ee 100644 --- a/components/soc/esp32/i2s_periph.c +++ b/components/soc/esp32/i2s_periph.c @@ -25,6 +25,7 @@ const i2s_signal_conn_t i2s_periph_signal[SOC_I2S_NUM] = { .s_rx_ws_sig = I2S0I_WS_IN_IDX, .data_out_sig = I2S0O_DATA_OUT23_IDX, + .data_out1_sig = -1, .data_in_sig = I2S0I_DATA_IN15_IDX, .irq = ETS_I2S0_INTR_SOURCE, @@ -44,6 +45,7 @@ const i2s_signal_conn_t i2s_periph_signal[SOC_I2S_NUM] = { .s_rx_ws_sig = I2S1I_WS_IN_IDX, .data_out_sig = I2S1O_DATA_OUT23_IDX, + .data_out1_sig = -1, .data_in_sig = I2S1I_DATA_IN15_IDX, .irq = ETS_I2S1_INTR_SOURCE, diff --git a/components/soc/esp32c3/i2s_periph.c b/components/soc/esp32c3/i2s_periph.c index daa8057277..44dd02d334 100644 --- a/components/soc/esp32c3/i2s_periph.c +++ b/components/soc/esp32c3/i2s_periph.c @@ -25,6 +25,7 @@ const i2s_signal_conn_t i2s_periph_signal[SOC_I2S_NUM] = { .s_rx_ws_sig = I2SI_WS_IN_IDX, .data_out_sig = I2SO_SD_OUT_IDX, + .data_out1_sig = I2SO_SD1_OUT_IDX, .data_in_sig = I2SI_SD_IN_IDX, .irq = -1, diff --git a/components/soc/esp32c3/include/soc/gpio_sig_map.h b/components/soc/esp32c3/include/soc/gpio_sig_map.h index 61aa95a3c4..badcefdc09 100644 --- a/components/soc/esp32c3/include/soc/gpio_sig_map.h +++ b/components/soc/esp32c3/include/soc/gpio_sig_map.h @@ -1,16 +1,8 @@ -// Copyright 2020 Espressif Systems (Shanghai) PTE LTD -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at - -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +/* + * SPDX-FileCopyrightText: 2020-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ #ifndef _SOC_GPIO_SIG_MAP_H_ #define _SOC_GPIO_SIG_MAP_H_ @@ -100,6 +92,7 @@ #define GPIO_SD1_OUT_IDX 56 #define GPIO_SD2_OUT_IDX 57 #define GPIO_SD3_OUT_IDX 58 +#define I2SO_SD1_OUT_IDX 59 #define FSPICLK_IN_IDX 63 #define FSPICLK_OUT_IDX 63 #define FSPIQ_IN_IDX 64 diff --git a/components/soc/esp32h2/i2s_periph.c b/components/soc/esp32h2/i2s_periph.c index bf258634ed..08a85b70d0 100644 --- a/components/soc/esp32h2/i2s_periph.c +++ b/components/soc/esp32h2/i2s_periph.c @@ -12,18 +12,22 @@ */ const i2s_signal_conn_t i2s_periph_signal[SOC_I2S_NUM] = { { - // TODO ESP32-H2 IDF-2098 + .mck_out_sig = I2S_MCLK_OUT_IDX, + + .m_tx_bck_sig = I2SO_BCK_OUT_IDX, + .m_rx_bck_sig = I2SI_BCK_OUT_IDX, + .m_tx_ws_sig = I2SO_WS_OUT_IDX, + .m_rx_ws_sig = I2SI_WS_OUT_IDX, + + .s_tx_bck_sig = I2SO_BCK_IN_IDX, + .s_rx_bck_sig = I2SI_BCK_IN_IDX, + .s_tx_ws_sig = I2SO_WS_IN_IDX, + .s_rx_ws_sig = I2SI_WS_IN_IDX, + + .data_out_sig = I2SO_SD_OUT_IDX, + .data_out1_sig = I2SO_SD1_OUT_IDX, + .data_in_sig = I2SI_SD_IN_IDX, - // .o_bck_in_sig = I2S0O_BCK_IN_IDX, - // .o_ws_in_sig = I2S0O_WS_IN_IDX, - // .o_bck_out_sig = I2S0O_BCK_OUT_IDX, - // .o_ws_out_sig = I2S0O_WS_OUT_IDX, - // .o_data_out_sig = I2S0O_SD_OUT_IDX, - // .i_bck_in_sig = I2S0I_BCK_OUT_IDX, - // .i_ws_in_sig = I2S0I_WS_OUT_IDX, - // .i_bck_out_sig = I2S0I_BCK_IN_IDX, - // .i_ws_out_sig = I2S0I_WS_IN_IDX, - // .i_data_in_sig = I2S0I_SD_IN_IDX, .irq = ETS_I2S1_INTR_SOURCE, .module = PERIPH_I2S1_MODULE, } diff --git a/components/soc/esp32s2/i2s_periph.c b/components/soc/esp32s2/i2s_periph.c index 2bee28695a..fd01440c65 100644 --- a/components/soc/esp32s2/i2s_periph.c +++ b/components/soc/esp32s2/i2s_periph.c @@ -25,6 +25,7 @@ const i2s_signal_conn_t i2s_periph_signal[SOC_I2S_NUM] = { .s_rx_ws_sig = I2S0I_WS_IN_IDX, .data_out_sig = I2S0O_DATA_OUT23_IDX, + .data_out1_sig = -1, .data_in_sig = I2S0I_DATA_IN15_IDX, .irq = ETS_I2S0_INTR_SOURCE, diff --git a/components/soc/esp32s3/i2s_periph.c b/components/soc/esp32s3/i2s_periph.c index 5fe15212c5..1f3715a9e7 100644 --- a/components/soc/esp32s3/i2s_periph.c +++ b/components/soc/esp32s3/i2s_periph.c @@ -25,6 +25,7 @@ const i2s_signal_conn_t i2s_periph_signal[SOC_I2S_NUM] = { .s_rx_ws_sig = I2S0I_WS_IN_IDX, .data_out_sig = I2S0O_SD_OUT_IDX, + .data_out1_sig = I2S0O_SD1_OUT_IDX, .data_in_sig = I2S0I_SD_IN_IDX, .irq = -1, @@ -44,6 +45,7 @@ const i2s_signal_conn_t i2s_periph_signal[SOC_I2S_NUM] = { .s_rx_ws_sig = I2S1I_WS_IN_IDX, .data_out_sig = I2S1O_SD_OUT_IDX, + .data_out1_sig = -1, .data_in_sig = I2S1I_SD_IN_IDX, .irq = -1, diff --git a/components/soc/include/soc/i2s_periph.h b/components/soc/include/soc/i2s_periph.h index 4f868c99b9..35522f385e 100644 --- a/components/soc/include/soc/i2s_periph.h +++ b/components/soc/include/soc/i2s_periph.h @@ -32,6 +32,7 @@ typedef struct { const uint8_t s_rx_ws_sig; const uint8_t data_out_sig; + const uint8_t data_out1_sig; // Only valid in version 2 const uint8_t data_in_sig; const uint8_t irq; diff --git a/docs/en/api-reference/peripherals/i2s.rst b/docs/en/api-reference/peripherals/i2s.rst index 975a7dcf5d..033ac45a3a 100644 --- a/docs/en/api-reference/peripherals/i2s.rst +++ b/docs/en/api-reference/peripherals/i2s.rst @@ -408,8 +408,8 @@ And for more details, please refer to :component_file:`driver/include/driver/i2s /* Init the channel into PDM TX mode */ i2s_pdm_tx_config_t pdm_tx_cfg = { - .clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(36000), - .slot_cfg = I2S_STD_MSB_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_MONO), + .clk_cfg = I2S_PDM_TX_CLK_DEFAULT_CONFIG(36000), + .slot_cfg = I2S_PDM_TX_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_MONO), .gpio_cfg = { .clk = GPIO_NUM_5, .dout = GPIO_NUM_18, @@ -448,12 +448,12 @@ And for more details, please refer to :component_file:`driver/include/driver/i2s /* Allocate an I2S rx channel */ i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_0, I2S_ROLE_MASTER); - i2s_new_channel(&chan_cfg, &rx_handle, NULL); + i2s_new_channel(&chan_cfg, NULL, &rx_handle); /* Init the channel into PDM RX mode */ i2s_pdm_rx_config_t pdm_rx_cfg = { - .clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(36000), - .slot_cfg = I2S_STD_MSB_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_MONO), + .clk_cfg = I2S_PDM_RX_CLK_DEFAULT_CONFIG(36000), + .slot_cfg = I2S_PDM_RX_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_MONO), .gpio_cfg = { .clk = GPIO_NUM_5, .din = GPIO_NUM_19, diff --git a/docs/en/migration-guides/release-5.x/peripherals.rst b/docs/en/migration-guides/release-5.x/peripherals.rst index dd3c47293a..fd980f58f3 100644 --- a/docs/en/migration-guides/release-5.x/peripherals.rst +++ b/docs/en/migration-guides/release-5.x/peripherals.rst @@ -277,9 +277,7 @@ LCD I2S driver ---------- - {I2S_DRIVER_HEADERS:default=":component_file:`driver/include/driver/i2s_std.h`, :component_file:`driver/include/driver/i2s_pdm.h` or :component_file:`driver/include/driver/i2s_tdm.h`", esp32=":component_file:`driver/include/driver/i2s_std.h` or :component_file:`driver/include/driver/i2s_pdm.h`", esp32s2=":component_file:`driver/include/driver/i2s_std.h`"} - - Shortcomings are exposed when supporting all the new features of ESP32-C3 & ESP32-S3 by the old I2S driver, so it is re-designed to make it more compatible and flexible to all the communication modes. New APIs are available by including corresponding mode header files {I2S_DRIVER_HEADERS}. Meanwhile, the old APIs in :component_file:`driver/deprecated/driver/i2s.h` are still supported for backward compatibility. But there will be warnings if you keep using the old APIs in your project, these warnings can be suppressed by the Kconfig option :ref:`CONFIG_I2S_SUPPRESS_DEPRECATE_WARN`. Here is the general overview of the current I2S files: + Shortcomings are exposed when supporting all the new features of ESP32-C3 & ESP32-S3 by the old I2S driver, so it is re-designed to make it more compatible and flexible to all the communication modes. New APIs are available by including corresponding mode header files :component_file:`driver/include/driver/i2s_std.h`, :component_file:`driver/include/driver/i2s_pdm.h` or :component_file:`driver/include/driver/i2s_tdm.h`. Meanwhile, the old APIs in :component_file:`driver/deprecated/driver/i2s.h` are still supported for backward compatibility. But there will be warnings if you keep using the old APIs in your project, these warnings can be suppressed by the Kconfig option :ref:`CONFIG_I2S_SUPPRESS_DEPRECATE_WARN`. Here is the general overview of the current I2S files: .. figure:: ../../../_static/diagrams/i2s/i2s_file_structure.png :align: center diff --git a/examples/peripherals/i2s/i2s_basic/README.md b/examples/peripherals/i2s/i2s_basic/README.md deleted file mode 100644 index 598482caba..0000000000 --- a/examples/peripherals/i2s/i2s_basic/README.md +++ /dev/null @@ -1,98 +0,0 @@ -| Supported Targets | ESP32 | ESP32-C3 | ESP32-S2 | ESP32-S3 | -| ----------------- | ----- | -------- | -------- | -------- | - -# I2S Example - -(See the README.md file in the upper level 'examples' directory for more information about examples.) - -In this example, we generate a 100Hz triangle and sine wave and send it out from left and right channels at a sample rate of 36kHz through the I2S bus. - -## How to Use Example - -### Hardware Required - -* A development board with ESP32/ESP32-S2/ESP32-C3/ESP32-S3 SoC (e.g., ESP32-DevKitC, ESP-WROVER-KIT, etc.) -* A USB cable for power supply and programming - -### Configure the Project - -``` -idf.py menuconfig -``` - -### Build and Flash - -Build the project and flash it to the board, then run monitor tool to view serial output: - -``` -idf.py -p PORT flash monitor -``` - -(To exit the serial monitor, type ``Ctrl-]``.) - -See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects. - -## Example Output - -Running this example, you will see I2S start to read and write data, and only the first 4 elements in the receive buffer will be displayed. The output log can be seen below: - -``` -[i2s write] 1440 bytes are written successfully - -[i2s read] 8192 bytes are read successfully ----------------------------------------------- -[0] 0 [1] 0 [2] 0 [3] 0 - -[i2s write] 1440 bytes are written successfully -[i2s write] 1440 bytes are written successfully -[i2s write] 1440 bytes are written successfully - -[i2s read] 8192 bytes are read successfully ----------------------------------------------- -[0] a7d468d9 [1] a88a6a1d [2] a9406b58 [3] a9f66c8b - -[i2s write] 1440 bytes are written successfully -[i2s write] 1440 bytes are written successfully - -[i2s read] 8192 bytes are read successfully ----------------------------------------------- -[0] 8b622120 [1] 8c182347 [2] 8cce256c [3] 8d84278d -``` - -There is a abnormal case that printing `Data dropped`, it is caused by a long polling time of `i2s_channel_read`, please refer to the `Application Notes` section in I2S API reference. - -``` -[i2s read] 8192 bytes are read successfully ----------------------------------------------- -[0] a7d468d9 [1] a88a6a1d [2] a9406b58 [3] a9f66c8b - - -[i2s monitor] Data dropped - - -[i2s monitor] Data dropped - - -[i2s monitor] Data dropped - -[i2s write] 1440 bytes are written successfully - -[i2s monitor] Data dropped -``` - -If you have a logic analyzer, you can use a logic analyzer to grab online data. The following table describes the pins we use by default (Note that you can also use other pins for the same purpose). - -| pin name| function | gpio_num | -|:---:|:---:|:---:| -| WS |word select| GPIO_NUM_15 | -| SCK |continuous serial clock| GPIO_NUM_13 | -| SD |serial data| GPIO_NUM_21 | - -## Troubleshooting - -* Program upload failure - - * Hardware connection is not correct: run `idf.py -p PORT monitor`, and reboot your board to see if there are any output logs. - * The baud rate for downloading is too high: lower your baud rate in the `menuconfig` menu, and try again. - -For any technical queries, please open an [issue](https://github.com/espressif/esp-idf/issues) on GitHub. We will get back to you soon. diff --git a/examples/peripherals/i2s/i2s_basic/CMakeLists.txt b/examples/peripherals/i2s/i2s_basic/i2s_pdm/CMakeLists.txt similarity index 86% rename from examples/peripherals/i2s/i2s_basic/CMakeLists.txt rename to examples/peripherals/i2s/i2s_basic/i2s_pdm/CMakeLists.txt index 4958e595d6..d36bacbe7b 100644 --- a/examples/peripherals/i2s/i2s_basic/CMakeLists.txt +++ b/examples/peripherals/i2s/i2s_basic/i2s_pdm/CMakeLists.txt @@ -3,4 +3,4 @@ cmake_minimum_required(VERSION 3.16) include($ENV{IDF_PATH}/tools/cmake/project.cmake) -project(esp32_i2s_driver_example) +project(i2s_pdm_example) diff --git a/examples/peripherals/i2s/i2s_basic/i2s_pdm/README.md b/examples/peripherals/i2s/i2s_basic/i2s_pdm/README.md new file mode 100644 index 0000000000..4d32f821ef --- /dev/null +++ b/examples/peripherals/i2s/i2s_basic/i2s_pdm/README.md @@ -0,0 +1,165 @@ +| Supported Targets | ESP32 | ESP32-C3 | ESP32-S3 | +| ----------------- | ----- | -------- | -------- | + +# I2S Basic Standard Mode Example + +(See the README.md file in the upper level 'examples' directory for more information about examples.) + +This example is going to show how to use the PDM TX and RX mode. + +## How to Use Example + +### Hardware Required + +#### General + +* A development board with any one of ESP32, ESP32-C3 or ESP32-S3 SoC +* A USB cable for power supply and programming + +#### PDM RX + +* A PDM microphone whose `sel` pin is supposed to be pulled down, and connecting its `clk` pin to `GPIO_NUM_4`, `data` pin to `GPIO_NUM_5`. + +``` +┌─────────────┐ ┌──────────────────┐ +│ ESP │ │ PDM microphone │ +│ │ PDM clock │ │ +│ GPIO 0 ├──────────────►│ CLK │ +│ │ PDM data │ │ +│ GPIO 2 │◄──────────────┤ DATA │ +│ │ │ │ +│ │ ┌─────┤ SEL │ +│ │ │ │ │ +│ GND ├─────────┴─────┤ GND │ +│ │ │ │ +│ VCC ├───────────────┤ VCC │ +└─────────────┘ └──────────────────┘ +``` + +#### PDM TX + +* An earphone or a speaker +* An audio power amplifier that can input PDM signal. If the power amplifier can only receive the analog signal without PDM clock, a band-pass filter is required to restore the PDM data wave into analog signal, before it is transmitted to the power amplifier. + +**MAX98358** + +``` +┌─────────────┐ ┌───────────────┐ +│ ESP │ │ MAX 98358 │ +│ │ PDM clock │ │ +│ GPIO 4 ├──────────────►│ CLK │ ┌─────────┐ +│ │ PDM data │ │ │ Speaker │ +│ GPIO 5 ├──────────────►│ DATA OUTP ├───┤ │ +│ │ │ │ │ │ +│ │ ┌─────┤ SD_MODE OUTN ├───┤ │ +│ │ │ │ │ │ │ +│ VCC ├─────────┴─────┤ VCC │ └─────────┘ +│ │ │ │ +│ GND ├───────────────┤ GND │ +└─────────────┘ └───────────────┘ +``` + +**NS4150** + +``` +┌─────────────┐ ┌───────────────┐ +│ ESP │ │ NS 4150 │ +│ │ │ │ +│ GPIO 4 │ │ INN │ ┌─────────┐ +│ │PDM data┌────────────────┐ │ │ │ Speaker │ +│ GPIO 5 ├────────┤Band-pass Filter├───►│ INP VoP ├───┤ │ +│ │ └────────────────┘ │ │ │ │ +│ │ ┌───┤ CTRL VoN ├───┤ │ +│ │ │ │ │ │ │ +│ VCC ├──────────────────────────┴───┤ VCC │ └─────────┘ +│ │ │ │ +│ GND ├──────────────────────────────┤ GND │ +└─────────────┘ └───────────────┘ +``` + +### Configure the Project + +PDM can only works in simplex mode, setting the macro `EXAMPLE_PDM_DIR` to `EXAMPLE_PDM_TX` or `EXAMPLE_PDM_RX` can choose the PDM direction of this example. But currently ESP32-C3 does not support PDM RX mode. + +### Build and Flash + +Build the project and flash it to the board, then run monitor tool to view serial output: + +``` +idf.py -p PORT build flash monitor +``` + +(To exit the serial monitor, type ``Ctrl-]``.) + +See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects. + +## Example Output + +### PDM TX + +While `EXAMPLE_PDM_DIR` is set to `EXAMPLE_PDM_TX`, then you can see the following log: + +``` +I2S PDM TX example start +--------------------------- +D (284) i2s_common: tx channel is registered on I2S0 successfully +D (294) i2s_common: DMA malloc info: dma_desc_num = 6, dma_desc_buf_size = dma_frame_num * slot_num * data_bit_width = 500 +D (304) i2s_pdm: Clock division info: [sclk] 160000000 Hz [mdiv] 3 [mclk] 49152000 Hz [bdiv] 8 [bclk] 6144000 Hz +D (314) i2s_pdm: The tx channel on I2S0 has been initialized to PDM TX mode successfully +D (324) i2s_common: i2s tx channel enabled +Playing bass `twinkle twinkle little star` +Playing alto `twinkle twinkle little star` +Playing treble `twinkle twinkle little star` +... +``` + +You can hear the audio 'twinkle twinkle little star' in three tones if you connected a speaker.on it. + +### PDM RX + +While `EXAMPLE_PDM_DIR` is set to `EXAMPLE_PDM_RX`, but without connecting a PDM microphone, then you can see the following log: + +``` +I2S PDM RX example start +--------------------------- +D (10) i2s_common: rx channel is registered on I2S0 successfully +D (10) i2s_common: DMA malloc info: dma_desc_num = 6, dma_desc_buf_size = dma_frame_num * slot_num * data_bit_width = 500 +D (20) i2s_common: i2s rx channel enabled +Read Task: i2s read 2048 bytes +----------------------------------- +[0] -6595 [1] 0 [2] -29199 [3] -32768 +[4] -30203 [5] -32156 [6] -30704 [7] -31348 + +Read Task: i2s read 2048 bytes +----------------------------------- +[0] -30935 [1] -30935 [2] -30935 [3] -30935 +[4] -30935 [5] -30935 [6] -30935 [7] -30935 + +Read Task: i2s read 2048 bytes +----------------------------------- +[0] -30935 [1] -30935 [2] -30935 [3] -30935 +[4] -30935 [5] -30935 [6] -30935 [7] -30935 +``` + +And only if you connect a PDM microphone, you can see the data is change: + +``` +I2S PDM RX example start +--------------------------- +D (10) i2s_common: rx channel is registered on I2S0 successfully +D (10) i2s_common: DMA malloc info: dma_desc_num = 6, dma_desc_buf_size = dma_frame_num * slot_num * data_bit_width = 500 +D (20) i2s_common: i2s rx channel enabled +Read Task: i2s read 2048 bytes +----------------------------------- +[0] -3181 [1] 0 [2] -7194 [3] -24288 +[4] -777 [5] 3650 [6] 109 [7] 571 + +Read Task: i2s read 2048 bytes +----------------------------------- +[0] 2391 [1] 2378 [2] 2397 [3] 2385 +[4] 2399 [5] 2375 [6] 2401 [7] 2389 +``` + +## Troubleshooting + +For any technical queries, please open an [issue](https://github.com/espressif/esp-idf/issues) on GitHub. We will get back to you soon. diff --git a/examples/peripherals/i2s/i2s_basic/i2s_pdm/main/CMakeLists.txt b/examples/peripherals/i2s/i2s_basic/i2s_pdm/main/CMakeLists.txt new file mode 100644 index 0000000000..e1bdf7c96a --- /dev/null +++ b/examples/peripherals/i2s/i2s_basic/i2s_pdm/main/CMakeLists.txt @@ -0,0 +1,12 @@ +set(srcs "i2s_pdm_example_main.c") + +if(CONFIG_SOC_I2S_SUPPORTS_PDM_TX) + list(APPEND srcs "i2s_pdm_tx.c") +endif() + +if(CONFIG_SOC_I2S_SUPPORTS_PDM_RX) + list(APPEND srcs "i2s_pdm_rx.c") +endif() + +idf_component_register(SRCS "${srcs}" + INCLUDE_DIRS ".") diff --git a/examples/peripherals/i2s/i2s_basic/i2s_pdm/main/i2s_pdm_example.h b/examples/peripherals/i2s/i2s_basic/i2s_pdm/main/i2s_pdm_example.h new file mode 100644 index 0000000000..e8251153b8 --- /dev/null +++ b/examples/peripherals/i2s/i2s_basic/i2s_pdm/main/i2s_pdm_example.h @@ -0,0 +1,23 @@ +/* + * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ + +#pragma once + +#define EXAMPLE_BUFF_SIZE 2048 + +/** + * @brief I2S PDM TX example task + * + * @param args The user data given from task creating, not used in this example + */ +void i2s_example_pdm_tx_task(void *args); + +/** + * @brief I2S PDM RX example task + * + * @param args The user data given from task creating, not used in this example + */ +void i2s_example_pdm_rx_task(void *args); diff --git a/examples/peripherals/i2s/i2s_basic/i2s_pdm/main/i2s_pdm_example_main.c b/examples/peripherals/i2s/i2s_basic/i2s_pdm/main/i2s_pdm_example_main.c new file mode 100644 index 0000000000..1fd7c0e529 --- /dev/null +++ b/examples/peripherals/i2s/i2s_basic/i2s_pdm/main/i2s_pdm_example_main.c @@ -0,0 +1,31 @@ +/* + * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ + +#include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "sdkconfig.h" +#include "i2s_pdm_example.h" + +#define EXAMPLE_PDM_TX 0 +/* ESP32-C3 does not support PDM RX currently */ +#if !CONFIG_IDF_TARGET_ESP32C3 +#define EXAMPLE_PDM_RX 1 +#endif + +#define EXAMPLE_PDM_DIR EXAMPLE_PDM_TX + +void app_main(void) +{ +#if EXAMPLE_PDM_DIR == EXAMPLE_PDM_TX + printf("I2S PDM TX example start\n---------------------------\n"); + xTaskCreate(i2s_example_pdm_tx_task, "i2s_example_pdm_tx_task", 4096, NULL, 5, NULL); +#else + printf("I2S PDM RX example start\n---------------------------\n"); + xTaskCreate(i2s_example_pdm_rx_task, "i2s_example_pdm_rx_task", 4096, NULL, 5, NULL); +#endif +} diff --git a/examples/peripherals/i2s/i2s_basic/i2s_pdm/main/i2s_pdm_rx.c b/examples/peripherals/i2s/i2s_basic/i2s_pdm/main/i2s_pdm_rx.c new file mode 100644 index 0000000000..adde3cedd1 --- /dev/null +++ b/examples/peripherals/i2s/i2s_basic/i2s_pdm/main/i2s_pdm_rx.c @@ -0,0 +1,78 @@ +/* + * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ + +#include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "driver/i2s_pdm.h" +#include "driver/i2s_std.h" +#include "driver/gpio.h" +#include "esp_check.h" +#include "sdkconfig.h" +#include "i2s_pdm_example.h" + +#include "hal/i2s_ll.h" + +#define EXAMPLE_PDM_RX_CLK_IO GPIO_NUM_0 // I2S PDM RX clock io number +#define EXAMPLE_PDM_RX_DIN_IO GPIO_NUM_2 // I2S PDM RX data in io number + +#define EXAMPLE_PDM_RX_FREQ_HZ 16000 // I2S PDM RX frequency + +static i2s_chan_handle_t rx_chan; // I2S rx channel handler + +static void i2s_example_init_pdm_rx(void) +{ + /* Setp 1: Determine the I2S channel configuration and allocate RX channel only + * The default configuration can be generated by the helper macro, + * but note that PDM channel can only be registered on I2S_NUM_0 */ + i2s_chan_config_t rx_chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_AUTO, I2S_ROLE_MASTER); + ESP_ERROR_CHECK(i2s_new_channel(&rx_chan_cfg, NULL, &rx_chan)); + + /* Step 2: Setting the configurations of PDM RX mode and initialize the RX channel + * The slot configuration and clock configuration can be generated by the macros + * These two helper macros is defined in 'i2s_pdm.h' which can only be used in PDM RX mode. + * They can help to specify the slot and clock configurations for initialization or re-configuring */ + i2s_pdm_rx_config_t pdm_rx_cfg = { + .clk_cfg = I2S_PDM_RX_CLK_DEFAULT_CONFIG(EXAMPLE_PDM_RX_FREQ_HZ), + /* The data bit-width of PDM mode is fixed to 16 */ + .slot_cfg = I2S_PDM_RX_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_MONO), + .gpio_cfg = { + .clk = EXAMPLE_PDM_RX_CLK_IO, + .din = EXAMPLE_PDM_RX_DIN_IO, + .invert_flags = { + .clk_inv = false, + }, + }, + }; + ESP_ERROR_CHECK(i2s_channel_init_pdm_rx_mode(rx_chan, &pdm_rx_cfg)); + + /* Step 3: Enable the rx channels before reading data */ + ESP_ERROR_CHECK(i2s_channel_enable(rx_chan)); +} + + +void i2s_example_pdm_rx_task(void *args) +{ + int16_t *r_buf = (int16_t *)calloc(1, EXAMPLE_BUFF_SIZE); + assert(r_buf); + i2s_example_init_pdm_rx(); + + size_t r_bytes = 0; + while (1) { + /* Read i2s data */ + if (i2s_channel_read(rx_chan, r_buf, EXAMPLE_BUFF_SIZE, &r_bytes, 1000) == ESP_OK) { + printf("Read Task: i2s read %d bytes\n-----------------------------------\n", r_bytes); + printf("[0] %d [1] %d [2] %d [3] %d\n[4] %d [5] %d [6] %d [7] %d\n\n", + r_buf[0], r_buf[1], r_buf[2], r_buf[3], r_buf[4], r_buf[5], r_buf[6], r_buf[7]); + } else { + printf("Read Task: i2s read failed\n"); + } + vTaskDelay(pdMS_TO_TICKS(200)); + } + free(r_buf); + vTaskDelete(NULL); +} diff --git a/examples/peripherals/i2s/i2s_basic/i2s_pdm/main/i2s_pdm_tx.c b/examples/peripherals/i2s/i2s_basic/i2s_pdm/main/i2s_pdm_tx.c new file mode 100644 index 0000000000..390e89dc01 --- /dev/null +++ b/examples/peripherals/i2s/i2s_basic/i2s_pdm/main/i2s_pdm_tx.c @@ -0,0 +1,109 @@ +/* + * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ + +#include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "driver/i2s_pdm.h" +#include "driver/gpio.h" +#include "esp_check.h" +#include "sdkconfig.h" +#include "i2s_pdm_example.h" + +#define EXAMPLE_PDM_TX_CLK_IO GPIO_NUM_4 // I2S PDM TX clock io number +#define EXAMPLE_PDM_TX_DOUT_IO GPIO_NUM_5 // I2S PDM TX data out io number + +#define EXAMPLE_PDM_TX_FREQ_HZ 44100 // I2S PDM TX frequency +#define EXAMPLE_WAVE_AMPTITUDE (1000.0) // 1~32767 +#define CONST_PI (3.1416f) +#define EXAMPLE_SINE_WAVE_LEN(tone) (uint32_t)((EXAMPLE_PDM_TX_FREQ_HZ / (float)tone) + 0.5) // The sample point number per sine wave to generate the tone +#define EXAMPLE_TONE_LAST_TIME_MS 500 +#define EXAMPLE_BYTE_NUM_EVERY_TONE (EXAMPLE_TONE_LAST_TIME_MS * EXAMPLE_PDM_TX_FREQ_HZ / 1000) + +static i2s_chan_handle_t tx_chan; // I2S tx channel handler + +static const uint32_t tone[3][7] = {{262, 294, 330, 349, 392, 440, 494}, + {523, 587, 659, 698, 784, 880, 988}, + {1046, 1175, 1318, 1397, 1568, 1760, 1976}}; // The frequency of tones: do, re, mi, fa, so, la, si, in Hz. +static const uint8_t song[28] = {1, 1, 5, 5, 6, 6, 5, + 4, 4, 3, 3, 2, 2, 1, + 5, 5, 4, 4, 3, 3, 2, + 5, 5, 4, 4, 3, 3, 2}; // Numbered musical notation of 'twinkle twinkle little star' +static const uint8_t rhythm[7] = {1, 1, 1, 1, 1, 1, 2}; // Rhythm of 'twinkle twinkle little star', it's repeated in four sections + +static const char *tone_name[3] = {"bass", "alto", "treble"}; + +void i2s_example_init_pdm_tx(void) +{ + /* Setp 1: Determine the I2S channel configuration and allocate TX channel only + * The default configuration can be generated by the helper macro, + * it only requires the I2S controller id and I2S role, + * but note that PDM channel can only be registered on I2S_NUM_0 */ + i2s_chan_config_t tx_chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_AUTO, I2S_ROLE_MASTER); + tx_chan_cfg.auto_clear = true; + ESP_ERROR_CHECK(i2s_new_channel(&tx_chan_cfg, &tx_chan, NULL)); + + /* Step 2: Setting the configurations of PDM TX mode and initialize the TX channel + * The slot configuration and clock configuration can be generated by the macros + * These two helper macros is defined in 'i2s_pdm.h' which can only be used in PDM TX mode. + * They can help to specify the slot and clock configurations for initialization or re-configuring */ + i2s_pdm_tx_config_t pdm_tx_cfg = { + .clk_cfg = I2S_PDM_TX_CLK_DEFAULT_CONFIG(EXAMPLE_PDM_TX_FREQ_HZ), + /* The data bit-width of PDM mode is fixed to 16 */ + .slot_cfg = I2S_PDM_TX_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_MONO), + .gpio_cfg = { + .clk = EXAMPLE_PDM_TX_CLK_IO, + .dout = EXAMPLE_PDM_TX_DOUT_IO, + .invert_flags = { + .clk_inv = false, + }, + }, + }; + ESP_ERROR_CHECK(i2s_channel_init_pdm_tx_mode(tx_chan, &pdm_tx_cfg)); + + /* Step 3: Enable the tx channel before writing data */ + ESP_ERROR_CHECK(i2s_channel_enable(tx_chan)); +} + +void i2s_example_pdm_tx_task(void *args) +{ + int16_t *w_buf = (int16_t *)calloc(1, EXAMPLE_BUFF_SIZE); + assert(w_buf); + i2s_example_init_pdm_tx(); + + size_t w_bytes = 0; + + uint8_t cnt = 0; // The current index of the song + uint8_t tone_select = 0; // To selecting the tone level + + printf("Playing %s `twinkle twinkle little star`\n", tone_name[tone_select]); + while (1) { + int tone_point = EXAMPLE_SINE_WAVE_LEN(tone[tone_select][song[cnt]-1]); + /* Generate the tone buffer */ + for (int i = 0; i < tone_point; i++) { + w_buf[i] = (int16_t)((sin(2 * (float)i * CONST_PI / tone_point)) * EXAMPLE_WAVE_AMPTITUDE); + } + for (int tot_bytes = 0; tot_bytes < EXAMPLE_BYTE_NUM_EVERY_TONE * rhythm[cnt % 7]; tot_bytes += w_bytes) { + /* Play the tone */ + if (i2s_channel_write(tx_chan, w_buf, tone_point * sizeof(int16_t), &w_bytes, 1000) != ESP_OK) { + printf("Write Task: i2s write failed\n"); + } + } + cnt++; + /* If finished played, switch the tone level */ + if (cnt == sizeof(song)) { + cnt = 0; + tone_select++; + tone_select %= 3; + printf("Playing %s `twinkle twinkle little star`\n", tone_name[tone_select]); + } + /* Gap between the tones */ + vTaskDelay(15); + } + free(w_buf); + vTaskDelete(NULL); +} diff --git a/examples/peripherals/i2s/i2s_basic/i2s_pdm/pytest_i2s_pdm.py b/examples/peripherals/i2s/i2s_basic/i2s_pdm/pytest_i2s_pdm.py new file mode 100644 index 0000000000..a23c37fa88 --- /dev/null +++ b/examples/peripherals/i2s/i2s_basic/i2s_pdm/pytest_i2s_pdm.py @@ -0,0 +1,22 @@ +# SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: CC0-1.0 + +import pytest +from pytest_embedded import Dut + + +@pytest.mark.esp32 +@pytest.mark.esp32s3 +@pytest.mark.esp32c3 +@pytest.mark.generic +def test_i2s_pdm_example(dut: Dut) -> None: + dut.expect(r'I2S PDM TX example start', timeout=5) + dut.expect(r'---------------------------', timeout=5) + dut.expect(r'D \(([0-9]+)\) i2s_common: tx channel is registered on I2S0 successfully', timeout=5) + dut.expect(r'D \(([0-9]+)\) i2s_common: DMA malloc info: dma_desc_num = ([0-9]+), ' + r'dma_desc_buf_size = dma_frame_num \* slot_num \* data_bit_width = ([0-9]+)', timeout=5) + dut.expect(r'D \(([0-9]+)\) i2s_pdm: Clock division info: \[sclk\] ([0-9]+) Hz ' + r'\[mdiv\] ([0-9]+) \[mclk\] ([0-9]+) Hz \[bdiv\] ([0-9]+) \[bclk\] ([0-9]+) Hz', timeout=5) + dut.expect(r'D \(([0-9]+)\) i2s_pdm: The tx channel on I2S0 has been initialized to PDM TX mode successfully', timeout=5) + dut.expect(r'D \(([0-9]+)\) i2s_common: i2s tx channel enabled', timeout=5) + dut.expect(r'Playing bass `twinkle twinkle little star`', timeout=5) diff --git a/examples/peripherals/i2s/i2s_basic/i2s_pdm/sdkconfig.defaults b/examples/peripherals/i2s/i2s_basic/i2s_pdm/sdkconfig.defaults new file mode 100644 index 0000000000..5b3efe27a4 --- /dev/null +++ b/examples/peripherals/i2s/i2s_basic/i2s_pdm/sdkconfig.defaults @@ -0,0 +1 @@ +CONFIG_I2S_ENABLE_DEBUG_LOG=y diff --git a/examples/peripherals/i2s/i2s_basic/i2s_std/CMakeLists.txt b/examples/peripherals/i2s/i2s_basic/i2s_std/CMakeLists.txt new file mode 100644 index 0000000000..3a5fb14727 --- /dev/null +++ b/examples/peripherals/i2s/i2s_basic/i2s_std/CMakeLists.txt @@ -0,0 +1,6 @@ +# The following lines of boilerplate have to be in your project's CMakeLists +# in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.16) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(i2s_std_example) diff --git a/examples/peripherals/i2s/i2s_basic/i2s_std/README.md b/examples/peripherals/i2s/i2s_basic/i2s_std/README.md new file mode 100644 index 0000000000..fc8dc48b7b --- /dev/null +++ b/examples/peripherals/i2s/i2s_basic/i2s_std/README.md @@ -0,0 +1,73 @@ +| Supported Targets | ESP32 | ESP32-S2 | ESP32-C3 | ESP32-S3 | +| ----------------- | ----- | -------- | -------- | -------- | + +# I2S Basic Standard Mode Example + +(See the README.md file in the upper level 'examples' directory for more information about examples.) + +This example is going to show how to use the standard mode in simplex mode or full-duplex mode. + +## How to Use Example + +### Hardware Required + +* A development board with any one of ESP32, ESP32-S2, ESP32-C3 or ESP32-S3 SoC +* A USB cable for power supply and programming + +### Configure the Project + +There are simplex mode and duplex mode can be chosen in this example, setting `EXAMPLE_I2S_DUPLEX_MODE` to `0` will adopt the simplex mode, otherwise it will adopt the full-duplex mode. + +Note that ESP32-S2 simplex mode is not available because this example requires both TX & RX channels, however, ESP32-S2 has only one I2S controller and the simplex TX & RX channels can't coexist in a same controller. By the way, the simplex TX & RX channels can't coexist on ESP32 as well, but ESP32 has two I2S controllers so the simplex TX & RX channels can be registered on the different I2S controllers. + +### Build and Flash + +Build the project and flash it to the board, then run monitor tool to view serial output: + +``` +idf.py -p PORT build flash monitor +``` + +(To exit the serial monitor, type ``Ctrl-]``.) + +See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects. + +## Example Output + +While `EXAMPLE_I2S_DUPLEX_MODE` is set to `1`, then you can see the following log: + +``` +Write Task: i2s write 2048 bytes +Read Task: i2s read 2048 bytes +----------------------------------- +[0] 12 [1] 34 [2] 56 [3] 78 +[4] 9a [5] bc [6] de [7] f0 + +Write Task: i2s write 2048 bytes +Read Task: i2s read 2048 bytes +----------------------------------- +[0] 12 [1] 34 [2] 56 [3] 78 +[4] 9a [5] bc [6] de [7] f0 +... +``` + +While `EXAMPLE_I2S_DUPLEX_MODE` is set to `0`, you can see the receiving data is always `0` because no signal inputted: + +``` +Write Task: i2s write 2048 bytes +Read Task: i2s read 2048 bytes +----------------------------------- +[0] 0 [1] 0 [2] 0 [3] 0 +[4] 0 [5] 0 [6] 0 [7] 0 + +Write Task: i2s write 2048 bytes +Read Task: i2s read 2048 bytes +----------------------------------- +[0] 0 [1] 0 [2] 0 [3] 0 +[4] 0 [5] 0 [6] 0 [7] 0 +... +``` + +## Troubleshooting + +For any technical queries, please open an [issue](https://github.com/espressif/esp-idf/issues) on GitHub. We will get back to you soon. diff --git a/examples/peripherals/i2s/i2s_basic/i2s_std/main/CMakeLists.txt b/examples/peripherals/i2s/i2s_basic/i2s_std/main/CMakeLists.txt new file mode 100644 index 0000000000..6ae8228b9d --- /dev/null +++ b/examples/peripherals/i2s/i2s_basic/i2s_std/main/CMakeLists.txt @@ -0,0 +1,2 @@ +idf_component_register(SRCS "i2s_std_example_main.c" + INCLUDE_DIRS ".") diff --git a/examples/peripherals/i2s/i2s_basic/i2s_std/main/i2s_std_example_main.c b/examples/peripherals/i2s/i2s_basic/i2s_std/main/i2s_std_example_main.c new file mode 100644 index 0000000000..f759b2740e --- /dev/null +++ b/examples/peripherals/i2s/i2s_basic/i2s_std/main/i2s_std_example_main.c @@ -0,0 +1,210 @@ +/* + * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ + +#include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "driver/i2s_std.h" +#include "driver/gpio.h" +#include "esp_check.h" +#include "sdkconfig.h" + +/* Set 1 to allocate rx & tx channels in duplex mode on a same I2S controller, they will share the BCLK and WS signal + * Set 0 to allocate rx & tx channels in simplex mode, these two channels will be totally separated, + * Specifically, due to the hardware limitation, the simplex rx & tx channels can't be registered on the same controllers on ESP32 and ESP32-S2, + * and ESP32-S2 has only one I2S controller, so it can't allocate two simplex channels */ +#define EXAMPLE_I2S_DUPLEX_MODE (1 || CONFIG_IDF_TARGET_ESP32S2) + +#if CONFIG_IDF_TARGET_ESP32 + #define EXAMPLE_STD_BCLK_IO1 GPIO_NUM_4 // I2S bit clock io number + #define EXAMPLE_STD_WS_IO1 GPIO_NUM_5 // I2S word select io number + #define EXAMPLE_STD_DOUT_IO1 GPIO_NUM_18 // I2S data out io number + #define EXAMPLE_STD_DIN_IO1 GPIO_NUM_19 // I2S data in io number + #if !EXAMPLE_I2S_DUPLEX_MODE + #define EXAMPLE_STD_BCLK_IO2 GPIO_NUM_22 // I2S bit clock io number + #define EXAMPLE_STD_WS_IO2 GPIO_NUM_23 // I2S word select io number + #define EXAMPLE_STD_DOUT_IO2 GPIO_NUM_25 // I2S data out io number + #define EXAMPLE_STD_DIN_IO2 GPIO_NUM_26 // I2S data in io number + #endif +#else + #define EXAMPLE_STD_BCLK_IO1 GPIO_NUM_2 // I2S bit clock io number + #define EXAMPLE_STD_WS_IO1 GPIO_NUM_3 // I2S word select io number + #define EXAMPLE_STD_DOUT_IO1 GPIO_NUM_4 // I2S data out io number + #define EXAMPLE_STD_DIN_IO1 GPIO_NUM_5 // I2S data in io number + #if !EXAMPLE_I2S_DUPLEX_MODE + #define EXAMPLE_STD_BCLK_IO2 GPIO_NUM_6 // I2S bit clock io number + #define EXAMPLE_STD_WS_IO2 GPIO_NUM_7 // I2S word select io number + #define EXAMPLE_STD_DOUT_IO2 GPIO_NUM_8 // I2S data out io number + #define EXAMPLE_STD_DIN_IO2 GPIO_NUM_9 // I2S data in io number + #endif +#endif + +#define EXAMPLE_BUFF_SIZE 2048 + +static i2s_chan_handle_t tx_chan; // I2S tx channel handler +static i2s_chan_handle_t rx_chan; // I2S rx channel handler + +static void i2s_example_read_task(void *args) +{ + uint8_t *r_buf = (uint8_t *)calloc(1, EXAMPLE_BUFF_SIZE); + assert(r_buf); // Check if r_buf allocation success + size_t r_bytes = 0; + while (1) { + /* Read i2s data */ + if (i2s_channel_read(rx_chan, r_buf, EXAMPLE_BUFF_SIZE, &r_bytes, 1000) == ESP_OK) { + printf("Read Task: i2s read %d bytes\n-----------------------------------\n", r_bytes); + printf("[0] %x [1] %x [2] %x [3] %x\n[4] %x [5] %x [6] %x [7] %x\n\n", + r_buf[0], r_buf[1], r_buf[2], r_buf[3], r_buf[4], r_buf[5], r_buf[6], r_buf[7]); + } else { + printf("Read Task: i2s read failed\n"); + } + vTaskDelay(pdMS_TO_TICKS(200)); + } + free(r_buf); + vTaskDelete(NULL); +} + +static void i2s_example_write_task(void *args) +{ + uint8_t *w_buf = (uint8_t *)calloc(1, EXAMPLE_BUFF_SIZE); + assert(w_buf); // Check if w_buf allocation success + + /* Assign w_buf */ + for (int i = 0; i < EXAMPLE_BUFF_SIZE; i += 8) { + w_buf[i] = 0x12; + w_buf[i + 1] = 0x34; + w_buf[i + 2] = 0x56; + w_buf[i + 3] = 0x78; + w_buf[i + 4] = 0x9A; + w_buf[i + 5] = 0xBC; + w_buf[i + 6] = 0xDE; + w_buf[i + 7] = 0xF0; + } + + size_t w_bytes = 0; + while (1) { + /* Write i2s data */ + if (i2s_channel_write(tx_chan, w_buf, EXAMPLE_BUFF_SIZE, &w_bytes, 1000) == ESP_OK) { + printf("Write Task: i2s write %d bytes\n", w_bytes); + } else { + printf("Write Task: i2s write failed\n"); + } + vTaskDelay(pdMS_TO_TICKS(200)); + } + free(w_buf); + vTaskDelete(NULL); +} + +#if EXAMPLE_I2S_DUPLEX_MODE +static void i2s_example_init_std_duplex(void) +{ + /* Setp 1: Determine the I2S channel configuration and allocate both channels + * The default configuration can be generated by the helper macro, + * it only requires the I2S controller id and I2S role */ + i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_AUTO, I2S_ROLE_MASTER); + ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, &tx_chan, &rx_chan)); + + /* Step 2: Setting the configurations of standard mode, and initialize rx & tx channels + * The slot configuration and clock configuration can be generated by the macros + * These two helper macros is defined in 'i2s_std.h' which can only be used in STD mode. + * They can help to specify the slot and clock configurations for initialization or re-configuring */ + i2s_std_config_t std_cfg = { + .clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(16000), + .slot_cfg = I2S_STD_MSB_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_32BIT, I2S_SLOT_MODE_STEREO), + .gpio_cfg = { + .mclk = I2S_GPIO_UNUSED, // some codecs may require mclk signal, this example doesn't need it + .bclk = EXAMPLE_STD_BCLK_IO1, + .ws = EXAMPLE_STD_WS_IO1, + .dout = EXAMPLE_STD_DOUT_IO1, + .din = EXAMPLE_STD_DOUT_IO1, // In duplex mode, bind output and input to a same gpio can loopback internally + .invert_flags = { + .mclk_inv = false, + .bclk_inv = false, + .ws_inv = false, + }, + }, + }; + /* Initialize the channels */ + ESP_ERROR_CHECK(i2s_channel_init_std_mode(tx_chan, &std_cfg)); + ESP_ERROR_CHECK(i2s_channel_init_std_mode(rx_chan, &std_cfg)); +} + +#else + +static void i2s_example_init_std_simplex(void) +{ + /* Setp 1: Determine the I2S channel configuration and allocate two channels one by one + * The default configuration can be generated by the helper macro, + * it only requires the I2S controller id and I2S role + * The tx and rx channels here are registered on different I2S controller, + * only ESP32-C3, ESP32-S3 and ESP32-H2 allow to register two separate tx & rx channels on a same controller */ + i2s_chan_config_t tx_chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_AUTO, I2S_ROLE_MASTER); + ESP_ERROR_CHECK(i2s_new_channel(&tx_chan_cfg, &tx_chan, NULL)); + i2s_chan_config_t rx_chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_AUTO, I2S_ROLE_MASTER); + ESP_ERROR_CHECK(i2s_new_channel(&rx_chan_cfg, NULL, &rx_chan)); + + /* Step 2: Setting the configurations of standard mode and initialize each channels one by one + * The slot configuration and clock configuration can be generated by the macros + * These two helper macros is defined in 'i2s_std.h' which can only be used in STD mode. + * They can help to specify the slot and clock configurations for initialization or re-configuring */ + i2s_std_config_t tx_std_cfg = { + .clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(16000), + .slot_cfg = I2S_STD_MSB_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_32BIT, I2S_SLOT_MODE_STEREO), + .gpio_cfg = { + .mclk = I2S_GPIO_UNUSED, // some codecs may require mclk signal, this example doesn't need it + .bclk = EXAMPLE_STD_BCLK_IO1, + .ws = EXAMPLE_STD_WS_IO1, + .dout = EXAMPLE_STD_DOUT_IO1, + .din = EXAMPLE_STD_DIN_IO1, + .invert_flags = { + .mclk_inv = false, + .bclk_inv = false, + .ws_inv = false, + }, + }, + }; + ESP_ERROR_CHECK(i2s_channel_init_std_mode(tx_chan, &tx_std_cfg)); + + i2s_std_config_t rx_std_cfg = { + .clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(44100), + .slot_cfg = I2S_STD_MSB_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_MONO), + .gpio_cfg = { + .mclk = I2S_GPIO_UNUSED, // some codecs may require mclk signal, this example doesn't need it + .bclk = EXAMPLE_STD_BCLK_IO2, + .ws = EXAMPLE_STD_WS_IO2, + .dout = EXAMPLE_STD_DOUT_IO2, + .din = EXAMPLE_STD_DIN_IO2, + .invert_flags = { + .mclk_inv = false, + .bclk_inv = false, + .ws_inv = false, + }, + }, + }; + /* Default is only receiving left slot in mono mode, + * update to right here to show how to change the default configuration */ + rx_std_cfg.slot_cfg.slot_mask = I2S_STD_SLOT_ONLY_RIGHT; + ESP_ERROR_CHECK(i2s_channel_init_std_mode(rx_chan, &rx_std_cfg)); +} +#endif + +void app_main(void) +{ +#if EXAMPLE_I2S_DUPLEX_MODE + i2s_example_init_std_duplex(); +#else + i2s_example_init_std_simplex(); +#endif + + /* Step 3: Enable the tx and rx channels before writing or reading data */ + ESP_ERROR_CHECK(i2s_channel_enable(tx_chan)); + ESP_ERROR_CHECK(i2s_channel_enable(rx_chan)); + + /* Step 4: Create writing and reading task */ + xTaskCreate(i2s_example_read_task, "i2s_example_read_task", 4096, NULL, 5, NULL); + xTaskCreate(i2s_example_write_task, "i2s_example_write_task", 4096, NULL, 5, NULL); +} diff --git a/examples/peripherals/i2s/i2s_basic/i2s_std/pytest_i2s_std.py b/examples/peripherals/i2s/i2s_basic/i2s_std/pytest_i2s_std.py new file mode 100644 index 0000000000..18018f11d8 --- /dev/null +++ b/examples/peripherals/i2s/i2s_basic/i2s_std/pytest_i2s_std.py @@ -0,0 +1,35 @@ +# SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: CC0-1.0 + +import pytest +from pytest_embedded import Dut + + +@pytest.mark.esp32 +@pytest.mark.esp32s2 +@pytest.mark.esp32s3 +@pytest.mark.esp32c3 +@pytest.mark.generic +def test_i2s_basic_example(dut: Dut) -> None: + + dut.expect(r'D \(([0-9]+)\) i2s_common: tx channel is registered on I2S0 successfully', timeout=5) + dut.expect(r'D \(([0-9]+)\) i2s_common: rx channel is registered on I2S0 successfully', timeout=5) + dut.expect(r'D \(([0-9]+)\) i2s_common: DMA malloc info: dma_desc_num = ([0-9]+), ' + r'dma_desc_buf_size = dma_frame_num \* slot_num \* data_bit_width = ([0-9]+)', timeout=5) + dut.expect(r'D \(([0-9]+)\) i2s_std: Clock division info: \[sclk\] ([0-9]+) Hz ' + r'\[mdiv\] ([0-9]+) \[mclk\] ([0-9]+) Hz \[bdiv\] ([0-9]+) \[bclk\] ([0-9]+) Hz', timeout=5) + dut.expect(r'D \(([0-9]+)\) i2s_std: The tx channel on I2S0 has been initialized to STD mode successfully', timeout=5) + dut.expect(r'D \(([0-9]+)\) i2s_common: DMA malloc info: dma_desc_num = ([0-9]+), ' + r'dma_desc_buf_size = dma_frame_num \* slot_num \* data_bit_width = ([0-9]+)', timeout=5) + dut.expect(r'D \(([0-9]+)\) i2s_std: Clock division info: \[sclk\] ([0-9]+) Hz ' + r'\[mdiv\] ([0-9]+) \[mclk\] ([0-9]+) Hz \[bdiv\] ([0-9]+) \[bclk\] ([0-9]+) Hz', timeout=5) + dut.expect(r'D \(([0-9]+)\) i2s_std: The rx channel on I2S0 has been initialized to STD mode successfully', timeout=5) + dut.expect(r'D \(([0-9]+)\) i2s_common: i2s tx channel enabled', timeout=5) + dut.expect(r'D \(([0-9]+)\) i2s_common: i2s rx channel enabled', timeout=5) + dut.expect(r'Write Task: i2s write ([0-9]+) bytes', timeout=5) + dut.expect(r'Read Task: i2s read ([0-9]+) bytes', timeout=5) + dut.expect(r'-----------------------------------', timeout=5) + dut.expect(r'\[0\] 0 \[1\] 0 \[2\] 0 \[3\] 0', timeout=5) + dut.expect(r'\[4\] 0 \[5\] 0 \[6\] 0 \[7\] 0', timeout=5) + dut.expect(r'\[0\] 12 \[1\] 34 \[2\] 56 \[3\] 78', timeout=10) + dut.expect(r'\[4\] 9a \[5\] bc \[6\] de \[7\] f0', timeout=10) diff --git a/examples/peripherals/i2s/i2s_basic/i2s_std/sdkconfig.defaults b/examples/peripherals/i2s/i2s_basic/i2s_std/sdkconfig.defaults new file mode 100644 index 0000000000..5b3efe27a4 --- /dev/null +++ b/examples/peripherals/i2s/i2s_basic/i2s_std/sdkconfig.defaults @@ -0,0 +1 @@ +CONFIG_I2S_ENABLE_DEBUG_LOG=y diff --git a/examples/peripherals/i2s/i2s_basic/i2s_tdm/CMakeLists.txt b/examples/peripherals/i2s/i2s_basic/i2s_tdm/CMakeLists.txt new file mode 100644 index 0000000000..6158d496e1 --- /dev/null +++ b/examples/peripherals/i2s/i2s_basic/i2s_tdm/CMakeLists.txt @@ -0,0 +1,6 @@ +# The following lines of boilerplate have to be in your project's CMakeLists +# in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.16) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(i2s_tdm_example) diff --git a/examples/peripherals/i2s/i2s_basic/i2s_tdm/README.md b/examples/peripherals/i2s/i2s_basic/i2s_tdm/README.md new file mode 100644 index 0000000000..a51e0e4d26 --- /dev/null +++ b/examples/peripherals/i2s/i2s_basic/i2s_tdm/README.md @@ -0,0 +1,72 @@ +| Supported Targets | ESP32-C3 | ESP32-S3 | +| ----------------- | -------- | -------- | + +# I2S Basic TDM Mode Example + +(See the README.md file in the upper level 'examples' directory for more information about examples.) + +This example is going to show how to use the TDM mode in simplex mode or full-duplex mode. + +## How to Use Example + +### Hardware Required + +* A development board with any one of ESP32-C3 or ESP32-S3 SoC +* A USB cable for power supply and programming + +### Configure the Project + +There are simplex mode and duplex mode can be chosen in this example, setting `EXAMPLE_I2S_DUPLEX_MODE` to `0` will adopt the simplex mode, otherwise it will adopt the full-duplex mode. + +### Build and Flash + +Build the project and flash it to the board, then run monitor tool to view serial output: + +``` +idf.py -p PORT build flash monitor +``` + +(To exit the serial monitor, type ``Ctrl-]``.) + +See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects. + +## Example Output + +While `EXAMPLE_I2S_DUPLEX_MODE` is set to `1`, then you can see the following log: + +``` +Write Task: i2s write 2048 bytes +Read Task: i2s read 2048 bytes +----------------------------------- +[0] 12 [1] 34 [2] 56 [3] 78 +[4] 9a [5] bc [6] de [7] f0 + +Write Task: i2s write 2048 bytes +Read Task: i2s read 2048 bytes +----------------------------------- +[0] 12 [1] 34 [2] 56 [3] 78 +[4] 9a [5] bc [6] de [7] f0 +... +``` + +While `EXAMPLE_I2S_DUPLEX_MODE` is set to `0`, you can see the receiving data is always `0` because no signal inputted: + +``` +Write Task: i2s write 2048 bytes +Read Task: i2s read 2048 bytes +----------------------------------- +[0] 0 [1] 0 [2] 0 [3] 0 +[4] 0 [5] 0 [6] 0 [7] 0 + +Write Task: i2s write 2048 bytes +Read Task: i2s read 2048 bytes +----------------------------------- +[0] 0 [1] 0 [2] 0 [3] 0 +[4] 0 [5] 0 [6] 0 [7] 0 +... +``` + +## Troubleshooting + +For any technical queries, please open an [issue](https://github.com/espressif/esp-idf/issues) on GitHub. We will get back to you soon. + diff --git a/examples/peripherals/i2s/i2s_basic/i2s_tdm/main/CMakeLists.txt b/examples/peripherals/i2s/i2s_basic/i2s_tdm/main/CMakeLists.txt new file mode 100644 index 0000000000..90a398149f --- /dev/null +++ b/examples/peripherals/i2s/i2s_basic/i2s_tdm/main/CMakeLists.txt @@ -0,0 +1,2 @@ +idf_component_register(SRCS "i2s_tdm_example_main.c" + INCLUDE_DIRS ".") diff --git a/examples/peripherals/i2s/i2s_basic/i2s_tdm/main/i2s_tdm_example_main.c b/examples/peripherals/i2s/i2s_basic/i2s_tdm/main/i2s_tdm_example_main.c new file mode 100644 index 0000000000..c0c3536609 --- /dev/null +++ b/examples/peripherals/i2s/i2s_basic/i2s_tdm/main/i2s_tdm_example_main.c @@ -0,0 +1,207 @@ +/* + * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ + +#include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "driver/i2s_tdm.h" +#include "driver/gpio.h" +#include "esp_check.h" +#include "sdkconfig.h" + +/* Set 1 to allocate rx & tx channels in duplex mode on a same I2S controller, they will share the BCLK and WS signal + * Set 0 to allocate rx & tx channels in simplex mode, these two channels will be totally separated */ +#define EXAMPLE_I2S_DUPLEX_MODE 1 + +#define EXAMPLE_TDM_BCLK_IO1 GPIO_NUM_2 // I2S bit clock io number +#define EXAMPLE_TDM_WS_IO1 GPIO_NUM_3 // I2S word select io number +#define EXAMPLE_TDM_DOUT_IO1 GPIO_NUM_4 // I2S data out io number +#define EXAMPLE_TDM_DIN_IO1 GPIO_NUM_5 // I2S data in io number +#if !EXAMPLE_I2S_DUPLEX_MODE + #define EXAMPLE_TDM_BCLK_IO2 GPIO_NUM_6 // I2S bit clock io number + #define EXAMPLE_TDM_WS_IO2 GPIO_NUM_7 // I2S word select io number + #define EXAMPLE_TDM_DOUT_IO2 GPIO_NUM_8 // I2S data out io number + #define EXAMPLE_TDM_DIN_IO2 GPIO_NUM_9 // I2S data in io number +#endif + +#define EXAMPLE_BUFF_SIZE 2048 + +static i2s_chan_handle_t tx_chan; // I2S tx channel handler +static i2s_chan_handle_t rx_chan; // I2S rx channel handler + +static void i2s_example_read_task(void *args) +{ + uint8_t *r_buf = (uint8_t *)calloc(1, EXAMPLE_BUFF_SIZE); + assert(r_buf); // Check if r_buf allocation success + size_t r_bytes = 0; + while (1) { + /* Read i2s data */ + if (i2s_channel_read(rx_chan, r_buf, EXAMPLE_BUFF_SIZE, &r_bytes, 1000) == ESP_OK) { + printf("Read Task: i2s read %d bytes\n-----------------------------------\n", r_bytes); + printf("[0] %x [1] %x [2] %x [3] %x\n[4] %x [5] %x [6] %x [7] %x\n\n", + r_buf[0], r_buf[1], r_buf[2], r_buf[3], r_buf[4], r_buf[5], r_buf[6], r_buf[7]); + } else { + printf("Read Task: i2s read failed\n"); + } + vTaskDelay(pdMS_TO_TICKS(200)); + } + free(r_buf); + vTaskDelete(NULL); +} + +static void i2s_example_write_task(void *args) +{ + uint8_t *w_buf = (uint8_t *)calloc(1, EXAMPLE_BUFF_SIZE); + assert(w_buf); // Check if w_buf allocation success + + /* Assign w_buf */ + for (int i = 0; i < EXAMPLE_BUFF_SIZE; i += 8) { + w_buf[i] = 0x12; + w_buf[i + 1] = 0x34; + w_buf[i + 2] = 0x56; + w_buf[i + 3] = 0x78; + w_buf[i + 4] = 0x9A; + w_buf[i + 5] = 0xBC; + w_buf[i + 6] = 0xDE; + w_buf[i + 7] = 0xF0; + } + + size_t w_bytes = 0; + while (1) { + /* Write i2s data */ + if (i2s_channel_write(tx_chan, w_buf, EXAMPLE_BUFF_SIZE, &w_bytes, 1000) == ESP_OK) { + printf("Write Task: i2s write %d bytes\n", w_bytes); + } else { + printf("Write Task: i2s write failed\n"); + } + vTaskDelay(pdMS_TO_TICKS(200)); + } + free(w_buf); + vTaskDelete(NULL); +} + +#if EXAMPLE_I2S_DUPLEX_MODE +static void i2s_example_init_tdm_duplex(void) +{ + /* Setp 1: Determine the I2S channel configuration and allocate both channels + * The default configuration can be generated by the helper macro, + * it only requires the I2S controller id and I2S role */ + i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_AUTO, I2S_ROLE_MASTER); + ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, &tx_chan, &rx_chan)); + + /* Step 2: Setting the configurations of TDM mode, and initialize rx & tx channels + * The slot configuration and clock configuration can be generated by the macros + * These two helper macros is defined in 'i2s_tdm.h' which can only be used in TDM mode. + * They can help to specify the slot and clock configurations for initialization or re-configuring */ + i2s_tdm_config_t tdm_cfg = { + .clk_cfg = I2S_TDM_CLK_DEFAULT_CONFIG(16000), + /* Limited by the hardware, the number of bit clock can't exceed 128 in one frame, + * which is to say, TDM mode can only support 32 bit-width data upto 4 slots, + * 16 bit-width data upto 8 slots and 8 bit-width data upto 16 slots */ + .slot_cfg = I2S_TDM_MSB_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_32BIT, I2S_SLOT_MODE_STEREO, + I2S_TDM_SLOT0 | I2S_TDM_SLOT1 | I2S_TDM_SLOT2 | I2S_TDM_SLOT3), + .gpio_cfg = { + .mclk = I2S_GPIO_UNUSED, // some codecs may require mclk signal, this example doesn't need it + .bclk = EXAMPLE_TDM_BCLK_IO1, + .ws = EXAMPLE_TDM_WS_IO1, + .dout = EXAMPLE_TDM_DOUT_IO1, + .din = EXAMPLE_TDM_DOUT_IO1, // In duplex mode, bind output and input to a same gpio can loopback internally + .invert_flags = { + .mclk_inv = false, + .bclk_inv = false, + .ws_inv = false, + }, + }, + }; + /* While there are more than two slots activated, receiving data might be wrong if the mclk multiple is too samll + * The driver will increasing the multiple automatically to ensure the bclk_div bigger than 2. + * Modify the mclk_multiple to 512 directly here to avoid the warning */ + tdm_cfg.clk_cfg.mclk_multiple = I2S_MCLK_MULTIPLE_512; + + /* Initialize the channels */ + ESP_ERROR_CHECK(i2s_channel_init_tdm_mode(tx_chan, &tdm_cfg)); + ESP_ERROR_CHECK(i2s_channel_init_tdm_mode(rx_chan, &tdm_cfg)); +} + +#else + +static void i2s_example_init_tdm_simplex(void) +{ + /* Setp 1: Determine the I2S channel configuration and allocate two channels one by one + * The default configuration can be generated by the helper macro, + * it only requires the I2S controller id and I2S role */ + i2s_chan_config_t tx_chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_AUTO, I2S_ROLE_MASTER); + ESP_ERROR_CHECK(i2s_new_channel(&tx_chan_cfg, &tx_chan, NULL)); + i2s_chan_config_t rx_chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_AUTO, I2S_ROLE_MASTER); + ESP_ERROR_CHECK(i2s_new_channel(&rx_chan_cfg, NULL, &rx_chan)); + + /* Step 2: Setting the configurations of TDM mode and initialize each channels one by one + * The slot configuration and clock configuration can be generated by the macros + * These two helper macros is defined in 'i2s_tdm.h' which can only be used in TDM mode. + * They can help to specify the slot and clock configurations for initialization or re-configuring */ + i2s_tdm_config_t tx_tdm_cfg = { + .clk_cfg = I2S_TDM_CLK_DEFAULT_CONFIG(16000), + .slot_cfg = I2S_TDM_MSB_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_32BIT, I2S_SLOT_MODE_STEREO, + I2S_TDM_SLOT0 | I2S_TDM_SLOT1 | I2S_TDM_SLOT2 | I2S_TDM_SLOT3), + .gpio_cfg = { + .mclk = I2S_GPIO_UNUSED, // some codecs may require mclk signal, this example doesn't need it + .bclk = EXAMPLE_TDM_BCLK_IO1, + .ws = EXAMPLE_TDM_WS_IO1, + .dout = EXAMPLE_TDM_DOUT_IO1, + .din = EXAMPLE_TDM_DIN_IO1, + .invert_flags = { + .mclk_inv = false, + .bclk_inv = false, + .ws_inv = false, + }, + }, + }; + tx_tdm_cfg.clk_cfg.mclk_multiple = I2S_MCLK_MULTIPLE_512; + ESP_ERROR_CHECK(i2s_channel_init_tdm_mode(tx_chan, &tx_tdm_cfg)); + + i2s_tdm_config_t rx_tdm_cfg = { + .clk_cfg = I2S_TDM_CLK_DEFAULT_CONFIG(44100), + .slot_cfg = I2S_TDM_MSB_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_MONO, + I2S_TDM_SLOT0 | I2S_TDM_SLOT1 | I2S_TDM_SLOT2 | I2S_TDM_SLOT3 | + I2S_TDM_SLOT4 | I2S_TDM_SLOT5 | I2S_TDM_SLOT6 | I2S_TDM_SLOT7), + .gpio_cfg = { + .mclk = I2S_GPIO_UNUSED, // some codecs may require mclk signal, this example doesn't need it + .bclk = EXAMPLE_TDM_BCLK_IO2, + .ws = EXAMPLE_TDM_WS_IO2, + .dout = EXAMPLE_TDM_DOUT_IO2, + .din = EXAMPLE_TDM_DIN_IO2, + .invert_flags = { + .mclk_inv = false, + .bclk_inv = false, + .ws_inv = false, + }, + }, + }; + /* While there are more than two slots activated, receiving data might be wrong if the mclk multiple is too samll + * The driver will increasing the multiple automatically to ensure the bclk_div bigger than 2. + * Modify the mclk_multiple to 512 directly here to avoid the warning */ + rx_tdm_cfg.clk_cfg.mclk_multiple = I2S_MCLK_MULTIPLE_512; + ESP_ERROR_CHECK(i2s_channel_init_tdm_mode(rx_chan, &rx_tdm_cfg)); +} +#endif + +void app_main(void) +{ +#if EXAMPLE_I2S_DUPLEX_MODE + i2s_example_init_tdm_duplex(); +#else + i2s_example_init_tdm_simplex(); +#endif + + /* Step 3: Enable the tx and rx channels before writing or reading data */ + ESP_ERROR_CHECK(i2s_channel_enable(tx_chan)); + ESP_ERROR_CHECK(i2s_channel_enable(rx_chan)); + + /* Step 4: Create writing and reading task */ + xTaskCreate(i2s_example_read_task, "i2s_example_read_task", 4096, NULL, 5, NULL); + xTaskCreate(i2s_example_write_task, "i2s_example_write_task", 4096, NULL, 5, NULL); +} diff --git a/examples/peripherals/i2s/i2s_basic/i2s_tdm/pytest_i2s_tdm.py b/examples/peripherals/i2s/i2s_basic/i2s_tdm/pytest_i2s_tdm.py new file mode 100644 index 0000000000..afef7bdf76 --- /dev/null +++ b/examples/peripherals/i2s/i2s_basic/i2s_tdm/pytest_i2s_tdm.py @@ -0,0 +1,33 @@ +# SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: CC0-1.0 + +import pytest +from pytest_embedded import Dut + + +@pytest.mark.esp32s3 +@pytest.mark.esp32c3 +@pytest.mark.generic +def test_i2s_tdm_example(dut: Dut) -> None: + + dut.expect(r'D \(([0-9]+)\) i2s_common: tx channel is registered on I2S0 successfully', timeout=5) + dut.expect(r'D \(([0-9]+)\) i2s_common: rx channel is registered on I2S0 successfully', timeout=5) + dut.expect(r'D \(([0-9]+)\) i2s_common: DMA malloc info: dma_desc_num = ([0-9]+), ' + r'dma_desc_buf_size = dma_frame_num \* slot_num \* data_bit_width = ([0-9]+)', timeout=5) + dut.expect(r'D \(([0-9]+)\) i2s_tdm: Clock division info: \[sclk\] ([0-9]+) Hz ' + r'\[mdiv\] ([0-9]+) \[mclk\] ([0-9]+) Hz \[bdiv\] ([0-9]+) \[bclk\] ([0-9]+) Hz', timeout=5) + dut.expect(r'D \(([0-9]+)\) i2s_tdm: The tx channel on I2S0 has been initialized to TDM mode successfully', timeout=5) + dut.expect(r'D \(([0-9]+)\) i2s_common: DMA malloc info: dma_desc_num = ([0-9]+), ' + r'dma_desc_buf_size = dma_frame_num \* slot_num \* data_bit_width = ([0-9]+)', timeout=5) + dut.expect(r'D \(([0-9]+)\) i2s_tdm: Clock division info: \[sclk\] ([0-9]+) Hz ' + r'\[mdiv\] ([0-9]+) \[mclk\] ([0-9]+) Hz \[bdiv\] ([0-9]+) \[bclk\] ([0-9]+) Hz', timeout=5) + dut.expect(r'D \(([0-9]+)\) i2s_tdm: The rx channel on I2S0 has been initialized to TDM mode successfully', timeout=5) + dut.expect(r'D \(([0-9]+)\) i2s_common: i2s tx channel enabled', timeout=5) + dut.expect(r'D \(([0-9]+)\) i2s_common: i2s rx channel enabled', timeout=5) + dut.expect(r'Write Task: i2s write ([0-9]+) bytes', timeout=5) + dut.expect(r'Read Task: i2s read ([0-9]+) bytes', timeout=5) + dut.expect(r'-----------------------------------', timeout=5) + dut.expect(r'\[0\] 0 \[1\] 0 \[2\] 0 \[3\] 0', timeout=5) + dut.expect(r'\[4\] 0 \[5\] 0 \[6\] 0 \[7\] 0', timeout=5) + dut.expect(r'\[0\] 12 \[1\] 34 \[2\] 56 \[3\] 78', timeout=10) + dut.expect(r'\[4\] 9a \[5\] bc \[6\] de \[7\] f0', timeout=10) diff --git a/examples/peripherals/i2s/i2s_basic/i2s_tdm/sdkconfig.defaults b/examples/peripherals/i2s/i2s_basic/i2s_tdm/sdkconfig.defaults new file mode 100644 index 0000000000..5b3efe27a4 --- /dev/null +++ b/examples/peripherals/i2s/i2s_basic/i2s_tdm/sdkconfig.defaults @@ -0,0 +1 @@ +CONFIG_I2S_ENABLE_DEBUG_LOG=y diff --git a/examples/peripherals/i2s/i2s_basic/main/CMakeLists.txt b/examples/peripherals/i2s/i2s_basic/main/CMakeLists.txt deleted file mode 100644 index 4c583513cd..0000000000 --- a/examples/peripherals/i2s/i2s_basic/main/CMakeLists.txt +++ /dev/null @@ -1,2 +0,0 @@ -idf_component_register(SRCS "i2s_example_main.c" - INCLUDE_DIRS ".") diff --git a/examples/peripherals/i2s/i2s_basic/main/i2s_example_main.c b/examples/peripherals/i2s/i2s_basic/main/i2s_example_main.c deleted file mode 100644 index 414bcbe03e..0000000000 --- a/examples/peripherals/i2s/i2s_basic/main/i2s_example_main.c +++ /dev/null @@ -1,180 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD - * - * SPDX-License-Identifier: Unlicense OR CC0-1.0 - */ -/* I2S Example - * This example code will output 100Hz sine wave and triangle wave to 2-channel of I2S driver - * Every 5 seconds, it will change bits_per_sample [16, 24, 32] for i2s data - */ -#include -#include "freertos/FreeRTOS.h" -#include "freertos/queue.h" -#include "freertos/task.h" -#include "driver/i2s_std.h" -#include "driver/gpio.h" -#include "esp_system.h" -#include "esp_log.h" -#include "esp_attr.h" -#include - -#define EXAMPLE_SAMPLE_RATE (36000) -#define EXAMPLE_DATA_BIT_WIDTH (I2S_DATA_BIT_WIDTH_16BIT) - -#define I2S_NUM (0) -#define WAVE_FREQ_HZ (100) -#define PI (3.14159265) -#define I2S_BCK_IO (GPIO_NUM_4) -#define I2S_WS_IO (GPIO_NUM_5) -#define I2S_DO_IO (GPIO_NUM_18) -#define I2S_DI_IO (GPIO_NUM_18) /// Loopback internally if data_out and data_in signal are bound to a same GPIO - -#define SAMPLE_PER_CYCLE (EXAMPLE_SAMPLE_RATE/WAVE_FREQ_HZ) - -static i2s_chan_handle_t tx_handle = NULL; -static i2s_chan_handle_t rx_handle = NULL; - -static volatile int is_overflow = 0; - -static uint32_t* example_generate_triangle_sine_waves(int bits, uint32_t *buf_len) -{ - uint32_t len = ((bits + 8) / 16)*SAMPLE_PER_CYCLE * 4; - uint32_t *samples_data = malloc(len); - - double triangle_float = -(pow(2, bits) / 2 - 1); - double triangle_step = (double) pow(2, bits) / SAMPLE_PER_CYCLE; - - for (int i = 0; i < SAMPLE_PER_CYCLE; i++) { - double sin_float = sin(i * 2 * PI / SAMPLE_PER_CYCLE); - if (sin_float >= 0) { - triangle_float += triangle_step; - } else { - triangle_float -= triangle_step; - } - sin_float *= (pow(2, bits) / 2 - 1); - if (bits == 16) { - samples_data[i] = ((short)triangle_float << 16) | (short)sin_float; - } else if (bits == 24) { //1-bytes unused - samples_data[i * 2] = ((int) triangle_float) << 8; - samples_data[i * 2 + 1] = ((int) sin_float) << 8; - } else { - samples_data[i * 2] = ((int) triangle_float); - samples_data[i * 2 + 1] = ((int) sin_float); - } - - } - *buf_len = len; - return samples_data; -} - -static IRAM_ATTR bool i2s_rx_queue_overflow_callback(i2s_chan_handle_t handle, i2s_event_data_t *event, void *user_ctx) -{ - is_overflow++; - return false; -} - -static void example_i2s_read_task(void * args) -{ - uint32_t *rx_buf = calloc(1, 8192); - size_t bytes_read = 0; - uint32_t cnt = 0; - - while (1) { - if (i2s_channel_read(rx_handle, rx_buf, 8192, &bytes_read, 1000) == ESP_OK) { - if (cnt == 0) { - printf("\n[i2s read] %d bytes are read successfully\n", bytes_read); - printf("----------------------------------------------\n"); - printf("[0] %4x [1] %4x [2] %4x [3] %4x\n\n", rx_buf[0], rx_buf[1], rx_buf[2], rx_buf[3]); - } - cnt++; - cnt %= 10; - /* If the polling time is too long, there will be data dropped event */ - // vTaskDelay(10); - } else { - printf("[i2s read] %d bytes are read, timeout triggered\n\n", bytes_read); - } - } - vTaskDelete(NULL); -} - -static void example_i2s_write_task(void * args) -{ - uint32_t buf_len = 0; - uint32_t *tx_buf = example_generate_triangle_sine_waves(EXAMPLE_DATA_BIT_WIDTH, &buf_len); - size_t bytes_written = 0; - uint32_t cnt = 0; - - while (1) { - if (i2s_channel_write(tx_handle, tx_buf, buf_len, &bytes_written, 1000) == ESP_OK) { - if (cnt == 0) { - printf("[i2s write] %d bytes are written successfully\n", bytes_written); - } - cnt++; - cnt %= 20; - } else { - printf("[i2s write] %d bytes are written, timeout triggered\n", bytes_written); - } - } - vTaskDelete(NULL); -} - -static void example_i2s_init_std_duplex(void) -{ - i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_AUTO, I2S_ROLE_MASTER); - /* Giving both tx and rx handle will make the i2s works in full-duplex mode and can share the bclk and ws signal */ - ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, &tx_handle, &rx_handle)); - i2s_std_config_t std_cfg = { - .clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(EXAMPLE_SAMPLE_RATE), - .slot_cfg = I2S_STD_MSB_SLOT_DEFAULT_CONFIG(EXAMPLE_DATA_BIT_WIDTH, I2S_SLOT_MODE_STEREO), - .gpio_cfg = { - .mclk = I2S_GPIO_UNUSED, - .bclk = I2S_BCK_IO, - .ws = I2S_WS_IO, - .dout = I2S_DO_IO, - .din = I2S_DI_IO, - .invert_flags = { - .mclk_inv = false, - .bclk_inv = false, - .ws_inv = false, - }, - }, - }; -#if SOC_I2S_SUPPORTS_APLL - // APLL clock is more accurate when sample rate is high - std_cfg.clk_cfg.clk_src = I2S_CLK_SRC_APLL; -#endif - /* Initialize the tx channel handle to standard mode */ - ESP_ERROR_CHECK(i2s_channel_init_std_mode(tx_handle, &std_cfg)); - /* Initialize the rx channel handle to standard mode */ - ESP_ERROR_CHECK(i2s_channel_init_std_mode(rx_handle, &std_cfg)); - - printf("I2S tx and rx channels have been initialized to standard duplex mode\n"); - - i2s_event_callbacks_t cbs = { - .on_recv = NULL, - .on_recv_q_ovf = i2s_rx_queue_overflow_callback, - .on_sent = NULL, - .on_send_q_ovf = NULL, - }; - ESP_ERROR_CHECK(i2s_channel_register_event_callback(rx_handle, &cbs, NULL)); - - ESP_ERROR_CHECK(i2s_channel_enable(tx_handle)); - ESP_ERROR_CHECK(i2s_channel_enable(rx_handle)); - printf("I2S tx and rx channels enabled\n"); -} - -void app_main(void) -{ - //for 36Khz sample rates, we create 100Hz sine wave, every cycle need 36000/100 = 360 samples (4-bytes or 8-bytes each sample) - example_i2s_init_std_duplex(); - xTaskCreate(example_i2s_write_task, "i2s write task", 4096, NULL, 5, NULL); - xTaskCreate(example_i2s_read_task, "i2s read task", 8192, NULL, 5, NULL); - - while (1) { - if (is_overflow > 0) { - printf("[i2s monitor] RX message Queue overflowed\n"); - is_overflow--; - } - vTaskDelay(1); - } -} diff --git a/examples/peripherals/i2s/i2s_basic/pytest_i2s_basic.py b/examples/peripherals/i2s/i2s_basic/pytest_i2s_basic.py deleted file mode 100644 index 691aa7f186..0000000000 --- a/examples/peripherals/i2s/i2s_basic/pytest_i2s_basic.py +++ /dev/null @@ -1,18 +0,0 @@ -# SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD -# SPDX-License-Identifier: CC0-1.0 - -import pytest -from pytest_embedded import Dut - - -@pytest.mark.esp32 -@pytest.mark.esp32s2 -@pytest.mark.esp32s3 -@pytest.mark.esp32c3 -@pytest.mark.generic -def test_i2s_basic_example(dut: Dut) -> None: - dut.expect_exact('I2S tx and rx channels have been initialized to standard duplex mode', timeout=30) - dut.expect_exact('I2S tx and rx channels enabled', timeout=30) - dut.expect_exact('[i2s write] 1440 bytes are written successfully', timeout=30) - dut.expect_exact('', timeout=30) - dut.expect_exact('[i2s read] 8192 bytes are read successfully', timeout=30) diff --git a/examples/peripherals/i2s/i2s_es8311/CMakeLists.txt b/examples/peripherals/i2s/i2s_codec/i2s_es8311/CMakeLists.txt similarity index 100% rename from examples/peripherals/i2s/i2s_es8311/CMakeLists.txt rename to examples/peripherals/i2s/i2s_codec/i2s_es8311/CMakeLists.txt diff --git a/examples/peripherals/i2s/i2s_es8311/README.md b/examples/peripherals/i2s/i2s_codec/i2s_es8311/README.md similarity index 100% rename from examples/peripherals/i2s/i2s_es8311/README.md rename to examples/peripherals/i2s/i2s_codec/i2s_es8311/README.md diff --git a/examples/peripherals/i2s/i2s_es8311/main/CMakeLists.txt b/examples/peripherals/i2s/i2s_codec/i2s_es8311/main/CMakeLists.txt similarity index 100% rename from examples/peripherals/i2s/i2s_es8311/main/CMakeLists.txt rename to examples/peripherals/i2s/i2s_codec/i2s_es8311/main/CMakeLists.txt diff --git a/examples/peripherals/i2s/i2s_es8311/main/Kconfig.projbuild b/examples/peripherals/i2s/i2s_codec/i2s_es8311/main/Kconfig.projbuild similarity index 100% rename from examples/peripherals/i2s/i2s_es8311/main/Kconfig.projbuild rename to examples/peripherals/i2s/i2s_codec/i2s_es8311/main/Kconfig.projbuild diff --git a/examples/peripherals/i2s/i2s_es8311/main/canon.pcm b/examples/peripherals/i2s/i2s_codec/i2s_es8311/main/canon.pcm similarity index 100% rename from examples/peripherals/i2s/i2s_es8311/main/canon.pcm rename to examples/peripherals/i2s/i2s_codec/i2s_es8311/main/canon.pcm diff --git a/examples/peripherals/i2s/i2s_es8311/main/i2s_es8311_example.c b/examples/peripherals/i2s/i2s_codec/i2s_es8311/main/i2s_es8311_example.c similarity index 100% rename from examples/peripherals/i2s/i2s_es8311/main/i2s_es8311_example.c rename to examples/peripherals/i2s/i2s_codec/i2s_es8311/main/i2s_es8311_example.c diff --git a/examples/peripherals/i2s/i2s_es8311/main/idf_component.yml b/examples/peripherals/i2s/i2s_codec/i2s_es8311/main/idf_component.yml similarity index 100% rename from examples/peripherals/i2s/i2s_es8311/main/idf_component.yml rename to examples/peripherals/i2s/i2s_codec/i2s_es8311/main/idf_component.yml diff --git a/examples/peripherals/i2s/i2s_es8311/pytest_i2s_es8311.py b/examples/peripherals/i2s/i2s_codec/i2s_es8311/pytest_i2s_es8311.py similarity index 100% rename from examples/peripherals/i2s/i2s_es8311/pytest_i2s_es8311.py rename to examples/peripherals/i2s/i2s_codec/i2s_es8311/pytest_i2s_es8311.py diff --git a/examples/peripherals/i2s/i2s_audio_recorder_sdcard/CMakeLists.txt b/examples/peripherals/i2s/i2s_recorder/CMakeLists.txt similarity index 100% rename from examples/peripherals/i2s/i2s_audio_recorder_sdcard/CMakeLists.txt rename to examples/peripherals/i2s/i2s_recorder/CMakeLists.txt diff --git a/examples/peripherals/i2s/i2s_audio_recorder_sdcard/README.md b/examples/peripherals/i2s/i2s_recorder/README.md similarity index 100% rename from examples/peripherals/i2s/i2s_audio_recorder_sdcard/README.md rename to examples/peripherals/i2s/i2s_recorder/README.md diff --git a/examples/peripherals/i2s/i2s_audio_recorder_sdcard/main/CMakeLists.txt b/examples/peripherals/i2s/i2s_recorder/main/CMakeLists.txt similarity index 100% rename from examples/peripherals/i2s/i2s_audio_recorder_sdcard/main/CMakeLists.txt rename to examples/peripherals/i2s/i2s_recorder/main/CMakeLists.txt diff --git a/examples/peripherals/i2s/i2s_audio_recorder_sdcard/main/Kconfig.projbuild b/examples/peripherals/i2s/i2s_recorder/main/Kconfig.projbuild similarity index 100% rename from examples/peripherals/i2s/i2s_audio_recorder_sdcard/main/Kconfig.projbuild rename to examples/peripherals/i2s/i2s_recorder/main/Kconfig.projbuild diff --git a/examples/peripherals/i2s/i2s_audio_recorder_sdcard/main/i2s_recorder_main.c b/examples/peripherals/i2s/i2s_recorder/main/i2s_recorder_main.c similarity index 92% rename from examples/peripherals/i2s/i2s_audio_recorder_sdcard/main/i2s_recorder_main.c rename to examples/peripherals/i2s/i2s_recorder/main/i2s_recorder_main.c index f0c7c46d4f..dbb17482f5 100644 --- a/examples/peripherals/i2s/i2s_audio_recorder_sdcard/main/i2s_recorder_main.c +++ b/examples/peripherals/i2s/i2s_recorder/main/i2s_recorder_main.c @@ -145,10 +145,14 @@ void record_wav(uint32_t rec_time) // Start recording while (flash_wr_size < flash_rec_time) { // Read the RAW samples from the microphone - i2s_channel_read(rx_handle, (char *)i2s_readraw_buff, SAMPLE_SIZE, &bytes_read, 1000); - // Write the samples to the WAV file - fwrite(i2s_readraw_buff, 1, bytes_read, f); - flash_wr_size += bytes_read; + if (i2s_channel_read(rx_handle, (char *)i2s_readraw_buff, SAMPLE_SIZE, &bytes_read, 1000) == ESP_OK) { + printf("[0] %d [1] %d [2] %d [3]%d ...\n", i2s_readraw_buff[0], i2s_readraw_buff[1], i2s_readraw_buff[2], i2s_readraw_buff[3]); + // Write the samples to the WAV file + fwrite(i2s_readraw_buff, 1, bytes_read, f); + flash_wr_size += bytes_read; + } else { + printf("Read Failed!\n"); + } } ESP_LOGI(TAG, "Recording done!"); @@ -169,6 +173,7 @@ void init_microphone(void) i2s_pdm_rx_config_t pdm_rx_cfg = { .clk_cfg = I2S_PDM_RX_CLK_DEFAULT_CONFIG(CONFIG_EXAMPLE_SAMPLE_RATE), + /* The default mono slot is the left slot (whose 'select pin' of the PDM microphone is pulled down) */ .slot_cfg = I2S_PDM_RX_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_MONO), .gpio_cfg = { .clk = CONFIG_EXAMPLE_I2S_CLK_GPIO, diff --git a/examples/peripherals/i2s/i2s_audio_recorder_sdcard/pytest_i2s_record.py b/examples/peripherals/i2s/i2s_recorder/pytest_i2s_record.py similarity index 100% rename from examples/peripherals/i2s/i2s_audio_recorder_sdcard/pytest_i2s_record.py rename to examples/peripherals/i2s/i2s_recorder/pytest_i2s_record.py diff --git a/tools/ci/check_copyright_ignore.txt b/tools/ci/check_copyright_ignore.txt index d5bb308fdd..22861fa4e2 100644 --- a/tools/ci/check_copyright_ignore.txt +++ b/tools/ci/check_copyright_ignore.txt @@ -1078,7 +1078,6 @@ components/soc/esp32c3/include/soc/extmem_reg.h components/soc/esp32c3/include/soc/fe_reg.h components/soc/esp32c3/include/soc/gpio_pins.h components/soc/esp32c3/include/soc/gpio_reg.h -components/soc/esp32c3/include/soc/gpio_sig_map.h components/soc/esp32c3/include/soc/gpio_struct.h components/soc/esp32c3/include/soc/hwcrypto_reg.h components/soc/esp32c3/include/soc/i2c_reg.h From 92ea22fe8170e433ef9f580124dea457bcae7802 Mon Sep 17 00:00:00 2001 From: laokaiyao Date: Fri, 1 Jul 2022 14:53:35 +0800 Subject: [PATCH 2/3] i2s: support esp32h2 --- components/driver/CMakeLists.txt | 2 +- components/driver/i2s/i2s_common.c | 25 ++++++++-- components/driver/i2s/i2s_pdm.c | 2 + components/driver/i2s/i2s_private.h | 1 + components/driver/include/driver/i2s_common.h | 49 ++++++++++--------- .../test_apps/i2s_test_apps/i2s/README.md | 4 +- .../i2s_test_apps/legacy_i2s_driver/README.md | 4 +- .../i2s_test_apps/test_inc/test_i2s.h | 8 +++ .../i2s/i2s_basic/i2s_pdm/README.md | 4 +- .../i2s/i2s_basic/i2s_std/README.md | 4 +- .../i2s/i2s_basic/i2s_tdm/README.md | 4 +- .../i2s/i2s_codec/i2s_es8311/README.md | 18 +++---- .../i2s_es8311/main/i2s_es8311_example.c | 31 +++++++----- .../i2s_es8311/main/idf_component.yml | 17 ++++--- 14 files changed, 106 insertions(+), 67 deletions(-) diff --git a/components/driver/CMakeLists.txt b/components/driver/CMakeLists.txt index 5c1de62755..bfd40b99b8 100644 --- a/components/driver/CMakeLists.txt +++ b/components/driver/CMakeLists.txt @@ -116,7 +116,7 @@ else() idf_component_register(SRCS "${srcs}" INCLUDE_DIRS ${includes} PRIV_INCLUDE_DIRS "include/driver" - PRIV_REQUIRES efuse esp_timer esp_adc + PRIV_REQUIRES efuse esp_timer esp_adc esp_psram REQUIRES esp_pm esp_ringbuf freertos soc hal esp_hw_support LDFRAGMENTS linker.lf) endif() diff --git a/components/driver/i2s/i2s_common.c b/components/driver/i2s/i2s_common.c index 880ad35bab..c03ebe9d03 100644 --- a/components/driver/i2s/i2s_common.c +++ b/components/driver/i2s/i2s_common.c @@ -46,6 +46,9 @@ #include "esp_attr.h" #include "esp_rom_gpio.h" +#if CONFIG_SPIRAM && !CONFIG_IDF_TARGET_ESP32 +#include "esp_psram.h" +#endif #define I2S_DMA_BUFFER_MAX_SIZE (4092) @@ -59,6 +62,7 @@ #define I2S_MEM_ALLOC_CAPS MALLOC_CAP_DEFAULT #endif //CONFIG_I2S_ISR_IRAM_SAFE #define I2S_DMA_ALLOC_CAPS (MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA) +#define I2S_PSRAM_ALLOC_CAPS (MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT) /** * @brief Global i2s platform object @@ -204,7 +208,6 @@ static i2s_controller_t *i2s_acquire_controller_obj(int id) portENTER_CRITICAL(&g_i2s.spinlock); if (g_i2s.controller[id]) { i2s_obj = g_i2s.controller[id]; - } else { } portEXIT_CRITICAL(&g_i2s.spinlock); if (i2s_obj == NULL) { @@ -407,12 +410,22 @@ esp_err_t i2s_alloc_dma_desc(i2s_chan_handle_t handle, uint32_t num, uint32_t bu handle->dma.desc_num = num; handle->dma.buf_size = bufsize; - handle->dma.desc = (lldesc_t **)heap_caps_calloc(num, sizeof(lldesc_t *), I2S_MEM_ALLOC_CAPS); + uint32_t dma_alloc_caps = I2S_DMA_ALLOC_CAPS; + uint32_t mem_alloc_caps = I2S_MEM_ALLOC_CAPS; + +#if (CONFIG_SPIRAM_USE_MALLOC || CONFIG_SPIRAM_USE_CAPS_ALLOC) && !CONFIG_IDF_TARGET_ESP32 + if (handle->dma.psram_en && esp_psram_is_initialized()) { + ESP_LOGD(TAG, "DMA buffer will be allocate in the psram"); + dma_alloc_caps = I2S_PSRAM_ALLOC_CAPS; + mem_alloc_caps = I2S_PSRAM_ALLOC_CAPS; + } +#endif + handle->dma.desc = (lldesc_t **)heap_caps_aligned_calloc(4, num, sizeof(lldesc_t *), mem_alloc_caps); ESP_GOTO_ON_FALSE(handle->dma.desc, ESP_ERR_NO_MEM, err, TAG, "create I2S DMA decriptor array failed"); - handle->dma.bufs = (uint8_t **)heap_caps_calloc(num, sizeof(uint8_t *), I2S_MEM_ALLOC_CAPS); + handle->dma.bufs = (uint8_t **)heap_caps_aligned_calloc(4, num, sizeof(uint8_t *), mem_alloc_caps); for (int i = 0; i < num; i++) { /* Allocate DMA descriptor */ - handle->dma.desc[i] = (lldesc_t *) heap_caps_calloc(1, sizeof(lldesc_t), I2S_DMA_ALLOC_CAPS); + handle->dma.desc[i] = (lldesc_t *) heap_caps_aligned_calloc(4, 1, sizeof(lldesc_t), dma_alloc_caps); ESP_GOTO_ON_FALSE(handle->dma.desc[i], ESP_ERR_NO_MEM, err, TAG, "allocate DMA description failed"); handle->dma.desc[i]->owner = 1; handle->dma.desc[i]->eof = 1; @@ -420,7 +433,7 @@ esp_err_t i2s_alloc_dma_desc(i2s_chan_handle_t handle, uint32_t num, uint32_t bu handle->dma.desc[i]->length = bufsize; handle->dma.desc[i]->size = bufsize; handle->dma.desc[i]->offset = 0; - handle->dma.bufs[i] = (uint8_t *) heap_caps_calloc(1, bufsize * sizeof(uint8_t), I2S_DMA_ALLOC_CAPS); + handle->dma.bufs[i] = (uint8_t *) heap_caps_aligned_calloc(4, 1, bufsize * sizeof(uint8_t), dma_alloc_caps); handle->dma.desc[i]->buf = handle->dma.bufs[i]; ESP_GOTO_ON_FALSE(handle->dma.desc[i]->buf, ESP_ERR_NO_MEM, err, TAG, "allocate DMA buffer failed"); } @@ -782,6 +795,7 @@ esp_err_t i2s_new_channel(const i2s_chan_config_t *chan_cfg, i2s_chan_handle_t * err, TAG, "register I2S tx channel failed"); i2s_obj->tx_chan->role = chan_cfg->role; i2s_obj->tx_chan->dma.auto_clear = chan_cfg->auto_clear; + i2s_obj->tx_chan->dma.psram_en = chan_cfg->dma_buf_in_psram; i2s_obj->tx_chan->dma.desc_num = chan_cfg->dma_desc_num; i2s_obj->tx_chan->dma.frame_num = chan_cfg->dma_frame_num; i2s_obj->tx_chan->start = i2s_tx_channel_start; @@ -794,6 +808,7 @@ esp_err_t i2s_new_channel(const i2s_chan_config_t *chan_cfg, i2s_chan_handle_t * ESP_GOTO_ON_ERROR(i2s_register_channel(i2s_obj, I2S_DIR_RX, chan_cfg->dma_desc_num), err, TAG, "register I2S rx channel failed"); i2s_obj->rx_chan->role = chan_cfg->role; + i2s_obj->rx_chan->dma.psram_en = chan_cfg->dma_buf_in_psram; i2s_obj->rx_chan->dma.desc_num = chan_cfg->dma_desc_num; i2s_obj->rx_chan->dma.frame_num = chan_cfg->dma_frame_num; i2s_obj->rx_chan->start = i2s_rx_channel_start; diff --git a/components/driver/i2s/i2s_pdm.c b/components/driver/i2s/i2s_pdm.c index b8e4d3300b..8a231c6f4b 100644 --- a/components/driver/i2s/i2s_pdm.c +++ b/components/driver/i2s/i2s_pdm.c @@ -67,8 +67,10 @@ static esp_err_t i2s_pdm_tx_set_clock(i2s_chan_handle_t handle, const i2s_pdm_tx portENTER_CRITICAL(&g_i2s.spinlock); /* Set clock configurations in HAL*/ i2s_hal_set_tx_clock(&handle->controller->hal, &clk_info, clk_cfg->clk_src); +#if SOC_I2S_HW_VERSION_2 /* Work aroud for PDM TX clock, set the raw division directly to reduce the noise */ i2s_ll_tx_set_raw_clk_div(handle->controller->hal.dev, 1, 1, 0, 0); +#endif portEXIT_CRITICAL(&g_i2s.spinlock); /* Update the mode info: clock configuration */ diff --git a/components/driver/i2s/i2s_private.h b/components/driver/i2s/i2s_private.h index e615be0f1d..18d5df40bb 100644 --- a/components/driver/i2s/i2s_private.h +++ b/components/driver/i2s/i2s_private.h @@ -47,6 +47,7 @@ typedef struct { uint32_t desc_num; /*!< I2S DMA buffer number, it is also the number of DMA descriptor */ uint32_t frame_num; /*!< I2S frame number in one DMA buffer. One frame means one-time sample data in all slots */ uint32_t buf_size; /*!< dma buffer size */ + bool psram_en; /*!< Whether prefer to allocate the DMA buffers in the psram */ bool auto_clear; /*!< Set to auto clear DMA TX descriptor, i2s will always send zero automatically if no data to send */ uint32_t rw_pos; /*!< reading/writing pointer position */ void *curr_ptr; /*!< Pointer to current dma buffer */ diff --git a/components/driver/include/driver/i2s_common.h b/components/driver/include/driver/i2s_common.h index f1f06ff825..8cd9297a67 100644 --- a/components/driver/include/driver/i2s_common.h +++ b/components/driver/include/driver/i2s_common.h @@ -24,6 +24,7 @@ extern "C" { .role = i2s_role, \ .dma_desc_num = 6, \ .dma_frame_num = 250, \ + .dma_buf_in_psram = false, \ .auto_clear = false, \ } @@ -36,43 +37,45 @@ extern "C" { * The variables used in the function should be in the SRAM as well. */ typedef struct { - i2s_isr_callback_t on_recv; /**< Callback of data received event, only for rx channel - * The event data includes DMA buffer address and size that just finished receiving data - */ - i2s_isr_callback_t on_recv_q_ovf; /**< Callback of receiving queue overflowed event, only for rx channel - * The event data includes buffer size that has been overwritten - */ - i2s_isr_callback_t on_sent; /**< Callback of data sent event, only for tx channel - * The event data includes DMA buffer address and size that just finished sending data - */ - i2s_isr_callback_t on_send_q_ovf; /**< Callback of sending queue overflowed evnet, only for tx channel - * The event data includes buffer size that has been overwritten - */ + i2s_isr_callback_t on_recv; /**< Callback of data received event, only for rx channel + * The event data includes DMA buffer address and size that just finished receiving data + */ + i2s_isr_callback_t on_recv_q_ovf; /**< Callback of receiving queue overflowed event, only for rx channel + * The event data includes buffer size that has been overwritten + */ + i2s_isr_callback_t on_sent; /**< Callback of data sent event, only for tx channel + * The event data includes DMA buffer address and size that just finished sending data + */ + i2s_isr_callback_t on_send_q_ovf; /**< Callback of sending queue overflowed evnet, only for tx channel + * The event data includes buffer size that has been overwritten + */ } i2s_event_callbacks_t; /** * @brief I2S controller channel configuration */ typedef struct { - i2s_port_t id; /*!< I2S port id */ - i2s_role_t role; /*!< I2S role, I2S_ROLE_MASTER or I2S_ROLE_SLAVE */ + i2s_port_t id; /*!< I2S port id */ + i2s_role_t role; /*!< I2S role, I2S_ROLE_MASTER or I2S_ROLE_SLAVE */ /* DMA configurations */ - uint32_t dma_desc_num; /*!< I2S DMA buffer number, it is also the number of DMA descriptor */ - uint32_t dma_frame_num; /*!< I2S frame number in one DMA buffer. One frame means one-time sample data in all slots */ - bool auto_clear; /*!< Set to auto clear DMA TX buffer, i2s will always send zero automatically if no data to send */ - + uint32_t dma_desc_num; /*!< I2S DMA buffer number, it is also the number of DMA descriptor */ + uint32_t dma_frame_num; /*!< I2S frame number in one DMA buffer. One frame means one-time sample data in all slots */ + bool dma_buf_in_psram; /*!< Prefer to allocate the DMA buffers in the psram (not supported on ESP32) + * To allocate the DMA buffers in the psram, SPIRAM should be enabled in menuconfig + */ + bool auto_clear; /*!< Set to auto clear DMA TX buffer, i2s will always send zero automatically if no data to send */ } i2s_chan_config_t; /** * @brief I2S channel information */ typedef struct { - i2s_port_t id; /*!< I2S port id */ - i2s_role_t role; /*!< I2S role, I2S_ROLE_MASTER or I2S_ROLE_SLAVE */ - i2s_dir_t dir; /*!< I2S channel direction */ - i2s_comm_mode_t mode; /*!< I2S channel communication mode */ - i2s_chan_handle_t pair_chan; /*!< I2S pair channel handle in duplex mode, always NULL in simplex mode */ + i2s_port_t id; /*!< I2S port id */ + i2s_role_t role; /*!< I2S role, I2S_ROLE_MASTER or I2S_ROLE_SLAVE */ + i2s_dir_t dir; /*!< I2S channel direction */ + i2s_comm_mode_t mode; /*!< I2S channel communication mode */ + i2s_chan_handle_t pair_chan; /*!< I2S pair channel handle in duplex mode, always NULL in simplex mode */ } i2s_chan_info_t; /** diff --git a/components/driver/test_apps/i2s_test_apps/i2s/README.md b/components/driver/test_apps/i2s_test_apps/i2s/README.md index 55e880bf84..4cf5da5af4 100644 --- a/components/driver/test_apps/i2s_test_apps/i2s/README.md +++ b/components/driver/test_apps/i2s_test_apps/i2s/README.md @@ -1,2 +1,2 @@ -| Supported Targets | ESP32 | ESP32-C3 | ESP32-S2 | ESP32-S3 | -| ----------------- | ----- | -------- | -------- | -------- | \ No newline at end of file +| Supported Targets | ESP32 | ESP32-C3 | ESP32-S2 | ESP32-S3 | ESP32-H2 | +| ----------------- | ----- | -------- | -------- | -------- | -------- | diff --git a/components/driver/test_apps/i2s_test_apps/legacy_i2s_driver/README.md b/components/driver/test_apps/i2s_test_apps/legacy_i2s_driver/README.md index 55e880bf84..4cf5da5af4 100644 --- a/components/driver/test_apps/i2s_test_apps/legacy_i2s_driver/README.md +++ b/components/driver/test_apps/i2s_test_apps/legacy_i2s_driver/README.md @@ -1,2 +1,2 @@ -| Supported Targets | ESP32 | ESP32-C3 | ESP32-S2 | ESP32-S3 | -| ----------------- | ----- | -------- | -------- | -------- | \ No newline at end of file +| Supported Targets | ESP32 | ESP32-C3 | ESP32-S2 | ESP32-S3 | ESP32-H2 | +| ----------------- | ----- | -------- | -------- | -------- | -------- | diff --git a/components/driver/test_apps/i2s_test_apps/test_inc/test_i2s.h b/components/driver/test_apps/i2s_test_apps/test_inc/test_i2s.h index 90d7ea7060..cf346b3518 100644 --- a/components/driver/test_apps/i2s_test_apps/test_inc/test_i2s.h +++ b/components/driver/test_apps/i2s_test_apps/test_inc/test_i2s.h @@ -44,6 +44,14 @@ extern "C" { #define SLAVE_WS_IO 15 #define DATA_IN_IO 19 #define DATA_OUT_IO 18 +#elif CONFIG_IDF_TARGET_ESP32H2 +#define MASTER_MCK_IO 0 +#define MASTER_BCK_IO 4 +#define MASTER_WS_IO 5 +#define SLAVE_BCK_IO 2 +#define SLAVE_WS_IO 3 +#define DATA_IN_IO 6 +#define DATA_OUT_IO 7 #endif #ifdef __cplusplus diff --git a/examples/peripherals/i2s/i2s_basic/i2s_pdm/README.md b/examples/peripherals/i2s/i2s_basic/i2s_pdm/README.md index 4d32f821ef..b0d1f75dc9 100644 --- a/examples/peripherals/i2s/i2s_basic/i2s_pdm/README.md +++ b/examples/peripherals/i2s/i2s_basic/i2s_pdm/README.md @@ -1,5 +1,5 @@ -| Supported Targets | ESP32 | ESP32-C3 | ESP32-S3 | -| ----------------- | ----- | -------- | -------- | +| Supported Targets | ESP32 | ESP32-C3 | ESP32-S3 | ESP32-H2 | +| ----------------- | ----- | -------- | -------- | -------- | # I2S Basic Standard Mode Example diff --git a/examples/peripherals/i2s/i2s_basic/i2s_std/README.md b/examples/peripherals/i2s/i2s_basic/i2s_std/README.md index fc8dc48b7b..fe5d4ed44e 100644 --- a/examples/peripherals/i2s/i2s_basic/i2s_std/README.md +++ b/examples/peripherals/i2s/i2s_basic/i2s_std/README.md @@ -1,5 +1,5 @@ -| Supported Targets | ESP32 | ESP32-S2 | ESP32-C3 | ESP32-S3 | -| ----------------- | ----- | -------- | -------- | -------- | +| Supported Targets | ESP32 | ESP32-S2 | ESP32-C3 | ESP32-S3 | ESP32-H2 | +| ----------------- | ----- | -------- | -------- | -------- | -------- | # I2S Basic Standard Mode Example diff --git a/examples/peripherals/i2s/i2s_basic/i2s_tdm/README.md b/examples/peripherals/i2s/i2s_basic/i2s_tdm/README.md index a51e0e4d26..467f4951ce 100644 --- a/examples/peripherals/i2s/i2s_basic/i2s_tdm/README.md +++ b/examples/peripherals/i2s/i2s_basic/i2s_tdm/README.md @@ -1,5 +1,5 @@ -| Supported Targets | ESP32-C3 | ESP32-S3 | -| ----------------- | -------- | -------- | +| Supported Targets | ESP32-C3 | ESP32-S3 | ESP32-H2 | +| ----------------- | -------- | -------- | -------- | # I2S Basic TDM Mode Example diff --git a/examples/peripherals/i2s/i2s_codec/i2s_es8311/README.md b/examples/peripherals/i2s/i2s_codec/i2s_es8311/README.md index 16c89ee4a9..426b8ac197 100644 --- a/examples/peripherals/i2s/i2s_codec/i2s_es8311/README.md +++ b/examples/peripherals/i2s/i2s_codec/i2s_es8311/README.md @@ -1,5 +1,5 @@ -| Supported Targets | ESP32 | ESP32-C3 | ESP32-S2 | ESP32-S3 | -| ----------------- | ----- | -------- | -------- | -------- | +| Supported Targets | ESP32 | ESP32-C3 | ESP32-S2 | ESP32-S3 | ESP32-H2 | +| ----------------- | ----- | -------- | -------- | -------- | -------- | # I2S ES8311 Example @@ -41,20 +41,20 @@ For more details, see [ES8311 datasheet](http://www.everest-semi.com/pdf/ES8311% │ WS-GPIO 5 ├──────────►│PIN8-LRCK PIN13-OUTN├───────────┤ │ │ │ │ │ └─────────┘ │ SDOUT-GPIO 18├──────────►│PIN9-SDIN │ -│ │ │ │ +│ (GPIO 2)│ │ │ │ SDIN-GPIO 19│◄──────────┤PIN7-SDOUT │ -│ │ │ │ ┌─────────┐ +│ (GPIO 3)│ │ │ ┌─────────┐ │ │ │ PIN18-MIC1P├───────────┤ │ │ SCL-GPIO 16├──────────►│PIN1 -CCLK │ │ MIC │ -│ (GPIO 7)│ │ PIN17-MIC1N├───────────┤ │ +│ (GPIO 6)│ │ PIN17-MIC1N├───────────┤ │ │ SDA-GPIO 17│◄─────────►│PIN19-CDATA │ └─────────┘ -│ (GPIO 8)│ │ │ +│ (GPIO 7)│ │ │ │ VCC 3.3├───────────┤VCC │ │ │ │ │ │ GND├───────────┤GND │ └─────────────────┘ └──────────────────────────┘ ``` -Note: Since ESP32-C3 board does not have GPIO 16/17, you can use other available GPIOs instead. In this example, we set GPIO 7/8 as I2C pins for ESP32-C3 and GPIO 16/17 for other chips. +Note: Since ESP32-C3 & ESP32-H2 board does not have GPIO 16/17, you can use other available GPIOs instead. In this example, we set GPIO 6/7 as I2C pins for ESP32-C3 & ESP32-H2 and GPIO 16/17 for other chips, same as GPIO 18/19, we use GPIO 2/3 instead. ### Dependency @@ -123,8 +123,8 @@ If you have a logic analyzer, you can use a logic analyzer to grab GPIO signal d | MCLK |module clock | GPIO_NUM_0| | BCLK |bit clock | GPIO_NUM_4 | | WS |word select | GPIO_NUM_5 | -| SDOUT |serial data out| GPIO_NUM_18 | -| SDIN |serial data in | GPIO_NUM_19 | +| SDOUT |serial data out| GPIO_NUM_18/2 | +| SDIN |serial data in | GPIO_NUM_19/3 | ### Customize your own music diff --git a/examples/peripherals/i2s/i2s_codec/i2s_es8311/main/i2s_es8311_example.c b/examples/peripherals/i2s/i2s_codec/i2s_es8311/main/i2s_es8311_example.c index 984796d9c0..34b0ed0c3c 100644 --- a/examples/peripherals/i2s/i2s_codec/i2s_es8311/main/i2s_es8311_example.c +++ b/examples/peripherals/i2s/i2s_codec/i2s_es8311/main/i2s_es8311_example.c @@ -15,12 +15,12 @@ /* I2C port and GPIOs */ #define I2C_NUM (0) -#ifdef CONFIG_IDF_TARGET_ESP32C3 -#define I2C_SDA_IO (GPIO_NUM_17) -#define I2C_SCL_IO (GPIO_NUM_16) +#if CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32H2 +#define I2C_SCL_IO (GPIO_NUM_6) +#define I2C_SDA_IO (GPIO_NUM_7) #else -#define I2C_SDA_IO (GPIO_NUM_15) -#define I2C_SCL_IO (GPIO_NUM_14) +#define I2C_SCL_IO (GPIO_NUM_16) +#define I2C_SDA_IO (GPIO_NUM_17) #endif /* I2S port and GPIOs */ @@ -28,13 +28,18 @@ #define I2S_MCK_IO (GPIO_NUM_0) #define I2S_BCK_IO (GPIO_NUM_4) #define I2S_WS_IO (GPIO_NUM_5) +#if CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32H2 +#define I2S_DO_IO (GPIO_NUM_2) +#define I2S_DI_IO (GPIO_NUM_3) +#else #define I2S_DO_IO (GPIO_NUM_18) #define I2S_DI_IO (GPIO_NUM_19) - +#endif /* Example configurations */ #define EXAMPLE_RECV_BUF_SIZE (2048) #define EXAMPLE_SAMPLE_RATE (16000) #define EXAMPLE_MCLK_MULTIPLE (256) +#define EXAMPLE_MCLK_FREQ_HZ (EXAMPLE_SAMPLE_RATE * EXAMPLE_MCLK_MULTIPLE) #define EXAMPLE_VOICE_VOLUME CONFIG_EXAMPLE_VOICE_VOLUME #if CONFIG_EXAMPLE_MODE_ECHO #define EXAMPLE_MIC_GAIN CONFIG_EXAMPLE_MIC_GAIN @@ -71,7 +76,10 @@ static esp_err_t es8311_codec_init(void) es8311_handle_t es_handle = es8311_create(I2C_NUM, ES8311_ADDRRES_0); ESP_RETURN_ON_FALSE(es_handle, ESP_FAIL, TAG, "es8311 create failed"); es8311_clock_config_t es_clk = { + .mclk_inverted = false, + .sclk_inverted = false, .mclk_from_mclk_pin = true, + .mclk_frequency = EXAMPLE_MCLK_FREQ_HZ, .sample_frequency = EXAMPLE_SAMPLE_RATE }; @@ -88,16 +96,17 @@ static esp_err_t es8311_codec_init(void) static esp_err_t i2s_driver_init(void) { i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM, I2S_ROLE_MASTER); + chan_cfg.auto_clear = true; // Auto clear the legacy data in the DMA buffer ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, &tx_handle, &rx_handle)); i2s_std_config_t std_cfg = { .clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(EXAMPLE_SAMPLE_RATE), .slot_cfg = I2S_STD_PHILIP_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_STEREO), .gpio_cfg = { - .mclk = GPIO_NUM_0, - .bclk = GPIO_NUM_4, - .ws = GPIO_NUM_5, - .dout = GPIO_NUM_18, - .din = GPIO_NUM_19, + .mclk = I2S_MCK_IO, + .bclk = I2S_BCK_IO, + .ws = I2S_WS_IO, + .dout = I2S_DO_IO, + .din = I2S_DI_IO, .invert_flags = { .mclk_inv = false, .bclk_inv = false, diff --git a/examples/peripherals/i2s/i2s_codec/i2s_es8311/main/idf_component.yml b/examples/peripherals/i2s/i2s_codec/i2s_es8311/main/idf_component.yml index 1bdf904cb3..36baaa8825 100644 --- a/examples/peripherals/i2s/i2s_codec/i2s_es8311/main/idf_component.yml +++ b/examples/peripherals/i2s/i2s_codec/i2s_es8311/main/idf_component.yml @@ -1,16 +1,17 @@ ## IDF Component Manager Manifest File -## Version of your project. Should conform Semantic Versioning 2.0 rules https://semver.org/ -version: "1.0.0" - dependencies: - espressif/es8311: ">=0.0.2" + espressif/es8311: "==1.0.0" ## Required IDF version idf: - version: "^5.0" + version: ">=4.1.0" # # Put list of dependencies here # # For components maintained by Espressif: - # component: - # version: "~1.0.0" + # component: "~1.0.0" # # For 3rd party components: - # username/component: + # username/component: ">=1.0.0,<2.0.0" + # username2/component2: # version: "~1.0.0" + # # For transient dependencies `public` flag can be set. + # # `public` flag doesn't have an effect dependencies of the `main` component. + # # All dependencies of `main` are public by default. + # public: true From edee3ee3cd20ec0e57b5aa8fc3d466278fa9ad3f Mon Sep 17 00:00:00 2001 From: laokaiyao Date: Tue, 5 Jul 2022 11:22:27 +0800 Subject: [PATCH 3/3] i2s: add slot sequence table Closes: https://github.com/espressif/esp-idf/issues/9208 When I2S is configured into different modes, the slot sequence varies. This commit updates slot sequence tables and corresponding descriptions in (both code and programming guide). --- components/driver/CMakeLists.txt | 2 +- components/driver/deprecated/i2s_legacy.c | 12 +- components/driver/i2s/i2s_common.c | 32 +-- components/driver/i2s/i2s_pdm.c | 8 +- components/driver/i2s/i2s_private.h | 1 - components/driver/i2s/i2s_std.c | 7 +- components/driver/i2s/i2s_tdm.c | 11 +- components/driver/include/driver/i2s_common.h | 8 +- components/driver/include/driver/i2s_pdm.h | 4 + components/driver/include/driver/i2s_std.h | 86 +++++- .../test_apps/i2s_test_apps/i2s/README.md | 4 +- .../i2s_test_apps/i2s/main/CMakeLists.txt | 1 - .../i2s_test_apps/i2s/main/test_i2s.c | 8 +- .../i2s_test_apps/i2s/main/test_i2s_iram.c | 2 +- .../i2s_test_apps/legacy_i2s_driver/README.md | 4 +- .../legacy_i2s_driver/main/CMakeLists.txt | 1 - .../legacy_i2s_driver/main/test_legacy_i2s.c | 27 +- components/esp_lcd/src/esp_lcd_panel_io_i2s.c | 2 +- components/hal/esp32/include/hal/i2s_ll.h | 117 ++++++-- components/hal/esp32c3/include/hal/i2s_ll.h | 18 +- components/hal/esp32h2/include/hal/i2s_ll.h | 18 +- components/hal/esp32s2/include/hal/i2s_ll.h | 54 ++-- components/hal/esp32s3/include/hal/i2s_ll.h | 18 +- components/hal/i2s_hal.c | 27 +- components/hal/include/hal/i2s_hal.h | 3 + components/hal/include/hal/i2s_types.h | 13 +- components/soc/esp32/include/soc/i2s_struct.h | 5 +- .../soc/esp32c3/include/soc/i2s_struct.h | 8 +- .../soc/esp32h2/include/soc/i2s_struct.h | 8 +- .../soc/esp32s2/include/soc/i2s_struct.h | 8 +- .../soc/esp32s3/include/soc/i2s_struct.h | 9 +- .../diagrams/i2s/i2s_state_machine.png | Bin 53689 -> 55562 bytes docs/en/api-reference/peripherals/i2s.rst | 255 +++++++++++++++++- examples/peripherals/.build-test-rules.yml | 26 +- .../i2s/i2s_basic/i2s_pdm/README.md | 98 +++---- .../i2s_basic/i2s_pdm/main/Kconfig.projbuild | 22 ++ .../i2s_basic/i2s_pdm/main/i2s_pdm_example.h | 8 + .../i2s_pdm/main/i2s_pdm_example_main.c | 12 +- .../i2s/i2s_basic/i2s_pdm/main/i2s_pdm_rx.c | 12 +- .../i2s/i2s_basic/i2s_pdm/main/i2s_pdm_tx.c | 22 +- .../i2s/i2s_basic/i2s_pdm/pytest_i2s_pdm.py | 28 +- .../i2s/i2s_basic/i2s_pdm/sdkconfig.ci.pdm_rx | 1 + .../i2s/i2s_basic/i2s_pdm/sdkconfig.ci.pdm_tx | 1 + .../i2s/i2s_basic/i2s_std/README.md | 6 +- .../i2s_basic/i2s_std/main/Kconfig.projbuild | 21 ++ .../i2s_std/main/i2s_std_example_main.c | 4 +- .../i2s/i2s_basic/i2s_tdm/README.md | 4 +- .../i2s/i2s_codec/i2s_es8311/README.md | 26 +- .../i2s_es8311/main/i2s_es8311_example.c | 5 +- .../i2s_es8311/main/idf_component.yml | 2 +- tools/ci/check_public_headers_exceptions.txt | 8 - 51 files changed, 795 insertions(+), 292 deletions(-) create mode 100644 examples/peripherals/i2s/i2s_basic/i2s_pdm/main/Kconfig.projbuild create mode 100644 examples/peripherals/i2s/i2s_basic/i2s_pdm/sdkconfig.ci.pdm_rx create mode 100644 examples/peripherals/i2s/i2s_basic/i2s_pdm/sdkconfig.ci.pdm_tx create mode 100644 examples/peripherals/i2s/i2s_basic/i2s_std/main/Kconfig.projbuild diff --git a/components/driver/CMakeLists.txt b/components/driver/CMakeLists.txt index bfd40b99b8..5c1de62755 100644 --- a/components/driver/CMakeLists.txt +++ b/components/driver/CMakeLists.txt @@ -116,7 +116,7 @@ else() idf_component_register(SRCS "${srcs}" INCLUDE_DIRS ${includes} PRIV_INCLUDE_DIRS "include/driver" - PRIV_REQUIRES efuse esp_timer esp_adc esp_psram + PRIV_REQUIRES efuse esp_timer esp_adc REQUIRES esp_pm esp_ringbuf freertos soc hal esp_hw_support LDFRAGMENTS linker.lf) endif() diff --git a/components/driver/deprecated/i2s_legacy.c b/components/driver/deprecated/i2s_legacy.c index 59a7e119c9..5b29e52d62 100644 --- a/components/driver/deprecated/i2s_legacy.c +++ b/components/driver/deprecated/i2s_legacy.c @@ -1057,15 +1057,15 @@ esp_err_t i2s_set_clk(i2s_port_t i2s_num, uint32_t rate, uint32_t bits_cfg, i2s_ slot_cfg->slot_mode = ((ch & 0xFFFF) == I2S_CHANNEL_MONO) ? I2S_SLOT_MODE_MONO : I2S_SLOT_MODE_STEREO; if (p_i2s[i2s_num]->mode == I2S_COMM_MODE_STD) { if (slot_cfg->slot_mode == I2S_SLOT_MODE_MONO) { - if (slot_cfg->std.slot_mask == I2S_STD_SLOT_LEFT_RIGHT) { - slot_cfg->std.slot_mask = I2S_STD_SLOT_ONLY_LEFT; + if (slot_cfg->std.slot_mask == I2S_STD_SLOT_BOTH) { + slot_cfg->std.slot_mask = I2S_STD_SLOT_LEFT; #if SOC_I2S_HW_VERSION_1 // Enable right first to get correct data sequence slot_cfg->std.ws_pol = !slot_cfg->std.ws_pol; #endif } } else { - slot_cfg->std.slot_mask = I2S_STD_SLOT_LEFT_RIGHT; + slot_cfg->std.slot_mask = I2S_STD_SLOT_BOTH; } } #if SOC_I2S_SUPPORTS_TDM @@ -1279,12 +1279,12 @@ static esp_err_t i2s_config_transfer(i2s_port_t i2s_num, const i2s_config_t *i2s SLOT_CFG(std).ws_width = i2s_config->bits_per_sample; SLOT_CFG(std).ws_pol = false; if (i2s_config->channel_format == I2S_CHANNEL_FMT_RIGHT_LEFT) { - SLOT_CFG(std).slot_mask = I2S_STD_SLOT_LEFT_RIGHT; + SLOT_CFG(std).slot_mask = I2S_STD_SLOT_BOTH; } else if (i2s_config->channel_format == I2S_CHANNEL_FMT_ALL_LEFT || i2s_config->channel_format == I2S_CHANNEL_FMT_ONLY_LEFT) { - SLOT_CFG(std).slot_mask = I2S_STD_SLOT_ONLY_LEFT; + SLOT_CFG(std).slot_mask = I2S_STD_SLOT_LEFT; } else { - SLOT_CFG(std).slot_mask = I2S_STD_SLOT_ONLY_RIGHT; + SLOT_CFG(std).slot_mask = I2S_STD_SLOT_RIGHT; } if (i2s_config->communication_format == I2S_COMM_FORMAT_STAND_I2S) { SLOT_CFG(std).bit_shift = true; diff --git a/components/driver/i2s/i2s_common.c b/components/driver/i2s/i2s_common.c index c03ebe9d03..4c4ebbb626 100644 --- a/components/driver/i2s/i2s_common.c +++ b/components/driver/i2s/i2s_common.c @@ -46,10 +46,9 @@ #include "esp_attr.h" #include "esp_rom_gpio.h" -#if CONFIG_SPIRAM && !CONFIG_IDF_TARGET_ESP32 -#include "esp_psram.h" -#endif +/* The actual max size of DMA buffer is 4095 + * Set 4092 here to align with 4-byte, so that the position of the slot data in the buffer will be relatively fixed */ #define I2S_DMA_BUFFER_MAX_SIZE (4092) // If ISR handler is allowed to run whilst cache is disabled, @@ -62,7 +61,6 @@ #define I2S_MEM_ALLOC_CAPS MALLOC_CAP_DEFAULT #endif //CONFIG_I2S_ISR_IRAM_SAFE #define I2S_DMA_ALLOC_CAPS (MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA) -#define I2S_PSRAM_ALLOC_CAPS (MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT) /** * @brief Global i2s platform object @@ -410,22 +408,13 @@ esp_err_t i2s_alloc_dma_desc(i2s_chan_handle_t handle, uint32_t num, uint32_t bu handle->dma.desc_num = num; handle->dma.buf_size = bufsize; - uint32_t dma_alloc_caps = I2S_DMA_ALLOC_CAPS; - uint32_t mem_alloc_caps = I2S_MEM_ALLOC_CAPS; - -#if (CONFIG_SPIRAM_USE_MALLOC || CONFIG_SPIRAM_USE_CAPS_ALLOC) && !CONFIG_IDF_TARGET_ESP32 - if (handle->dma.psram_en && esp_psram_is_initialized()) { - ESP_LOGD(TAG, "DMA buffer will be allocate in the psram"); - dma_alloc_caps = I2S_PSRAM_ALLOC_CAPS; - mem_alloc_caps = I2S_PSRAM_ALLOC_CAPS; - } -#endif - handle->dma.desc = (lldesc_t **)heap_caps_aligned_calloc(4, num, sizeof(lldesc_t *), mem_alloc_caps); + /* Descriptors must be in the internal RAM */ + handle->dma.desc = (lldesc_t **)heap_caps_calloc(num, sizeof(lldesc_t *), I2S_MEM_ALLOC_CAPS); ESP_GOTO_ON_FALSE(handle->dma.desc, ESP_ERR_NO_MEM, err, TAG, "create I2S DMA decriptor array failed"); - handle->dma.bufs = (uint8_t **)heap_caps_aligned_calloc(4, num, sizeof(uint8_t *), mem_alloc_caps); + handle->dma.bufs = (uint8_t **)heap_caps_calloc(num, sizeof(uint8_t *), I2S_MEM_ALLOC_CAPS); for (int i = 0; i < num; i++) { /* Allocate DMA descriptor */ - handle->dma.desc[i] = (lldesc_t *) heap_caps_aligned_calloc(4, 1, sizeof(lldesc_t), dma_alloc_caps); + handle->dma.desc[i] = (lldesc_t *) heap_caps_calloc(1, sizeof(lldesc_t), I2S_DMA_ALLOC_CAPS); ESP_GOTO_ON_FALSE(handle->dma.desc[i], ESP_ERR_NO_MEM, err, TAG, "allocate DMA description failed"); handle->dma.desc[i]->owner = 1; handle->dma.desc[i]->eof = 1; @@ -433,9 +422,10 @@ esp_err_t i2s_alloc_dma_desc(i2s_chan_handle_t handle, uint32_t num, uint32_t bu handle->dma.desc[i]->length = bufsize; handle->dma.desc[i]->size = bufsize; handle->dma.desc[i]->offset = 0; - handle->dma.bufs[i] = (uint8_t *) heap_caps_aligned_calloc(4, 1, bufsize * sizeof(uint8_t), dma_alloc_caps); + handle->dma.bufs[i] = (uint8_t *) heap_caps_calloc(1, bufsize * sizeof(uint8_t), I2S_DMA_ALLOC_CAPS); handle->dma.desc[i]->buf = handle->dma.bufs[i]; ESP_GOTO_ON_FALSE(handle->dma.desc[i]->buf, ESP_ERR_NO_MEM, err, TAG, "allocate DMA buffer failed"); + ESP_LOGV(TAG, "desc addr: %8p\tbuffer addr:%8p", handle->dma.desc[i], handle->dma.bufs[i]); } /* Connect DMA descriptor as a circle */ for (int i = 0; i < num; i++) { @@ -463,6 +453,10 @@ uint32_t i2s_set_get_apll_freq(uint32_t mclk_freq_hz) * So the div here should be at least 2 */ mclk_div = mclk_div < 2 ? 2 : mclk_div; uint32_t expt_freq = mclk_freq_hz * mclk_div; + if (expt_freq > SOC_APLL_MAX_HZ) { + ESP_LOGE(TAG, "The required APLL frequecy exceed its maximum value"); + return 0; + } uint32_t real_freq = 0; esp_err_t ret = periph_rtc_apll_freq_set(expt_freq, &real_freq); if (ret == ESP_ERR_INVALID_ARG) { @@ -795,7 +789,6 @@ esp_err_t i2s_new_channel(const i2s_chan_config_t *chan_cfg, i2s_chan_handle_t * err, TAG, "register I2S tx channel failed"); i2s_obj->tx_chan->role = chan_cfg->role; i2s_obj->tx_chan->dma.auto_clear = chan_cfg->auto_clear; - i2s_obj->tx_chan->dma.psram_en = chan_cfg->dma_buf_in_psram; i2s_obj->tx_chan->dma.desc_num = chan_cfg->dma_desc_num; i2s_obj->tx_chan->dma.frame_num = chan_cfg->dma_frame_num; i2s_obj->tx_chan->start = i2s_tx_channel_start; @@ -808,7 +801,6 @@ esp_err_t i2s_new_channel(const i2s_chan_config_t *chan_cfg, i2s_chan_handle_t * ESP_GOTO_ON_ERROR(i2s_register_channel(i2s_obj, I2S_DIR_RX, chan_cfg->dma_desc_num), err, TAG, "register I2S rx channel failed"); i2s_obj->rx_chan->role = chan_cfg->role; - i2s_obj->rx_chan->dma.psram_en = chan_cfg->dma_buf_in_psram; i2s_obj->rx_chan->dma.desc_num = chan_cfg->dma_desc_num; i2s_obj->rx_chan->dma.frame_num = chan_cfg->dma_frame_num; i2s_obj->rx_chan->start = i2s_rx_channel_start; diff --git a/components/driver/i2s/i2s_pdm.c b/components/driver/i2s/i2s_pdm.c index 8a231c6f4b..a08d630f30 100644 --- a/components/driver/i2s/i2s_pdm.c +++ b/components/driver/i2s/i2s_pdm.c @@ -68,7 +68,8 @@ static esp_err_t i2s_pdm_tx_set_clock(i2s_chan_handle_t handle, const i2s_pdm_tx /* Set clock configurations in HAL*/ i2s_hal_set_tx_clock(&handle->controller->hal, &clk_info, clk_cfg->clk_src); #if SOC_I2S_HW_VERSION_2 - /* Work aroud for PDM TX clock, set the raw division directly to reduce the noise */ + /* Work aroud for PDM TX clock, overwrite the raw division directly to reduce the noise + * This set of coefficients is a special division to reduce the background noise in PDM TX mode */ i2s_ll_tx_set_raw_clk_div(handle->controller->hal.dev, 1, 1, 0, 0); #endif portEXIT_CRITICAL(&g_i2s.spinlock); @@ -167,9 +168,10 @@ esp_err_t i2s_channel_init_pdm_tx_mode(i2s_chan_handle_t handle, const i2s_pdm_t } handle->mode_info = calloc(1, sizeof(i2s_pdm_tx_config_t)); ESP_GOTO_ON_FALSE(handle->mode_info, ESP_ERR_NO_MEM, err, TAG, "no memory for storing the configurations"); - ESP_GOTO_ON_ERROR(i2s_pdm_tx_set_gpio(handle, &pdm_tx_cfg->gpio_cfg), err, TAG, "initialize channel failed while setting gpio pins"); - /* i2s_set_pdm_tx_slot should be called before i2s_set_pdm_tx_clock while initializing, because clock is relay on the slot */ + /* i2s_set_pdm_tx_slot should be called before i2s_set_pdm_tx_clock and i2s_pdm_tx_set_gpio + * while initializing, because clock and gpio is relay on the slot */ ESP_GOTO_ON_ERROR(i2s_pdm_tx_set_slot(handle, &pdm_tx_cfg->slot_cfg), err, TAG, "initialize channel failed while setting slot"); + ESP_GOTO_ON_ERROR(i2s_pdm_tx_set_gpio(handle, &pdm_tx_cfg->gpio_cfg), err, TAG, "initialize channel failed while setting gpio pins"); #if SOC_I2S_SUPPORTS_APLL /* Enable APLL and acquire its lock when the clock source is APLL */ if (pdm_tx_cfg->clk_cfg.clk_src == I2S_CLK_SRC_APLL) { diff --git a/components/driver/i2s/i2s_private.h b/components/driver/i2s/i2s_private.h index 18d5df40bb..e615be0f1d 100644 --- a/components/driver/i2s/i2s_private.h +++ b/components/driver/i2s/i2s_private.h @@ -47,7 +47,6 @@ typedef struct { uint32_t desc_num; /*!< I2S DMA buffer number, it is also the number of DMA descriptor */ uint32_t frame_num; /*!< I2S frame number in one DMA buffer. One frame means one-time sample data in all slots */ uint32_t buf_size; /*!< dma buffer size */ - bool psram_en; /*!< Whether prefer to allocate the DMA buffers in the psram */ bool auto_clear; /*!< Set to auto clear DMA TX descriptor, i2s will always send zero automatically if no data to send */ uint32_t rw_pos; /*!< reading/writing pointer position */ void *curr_ptr; /*!< Pointer to current dma buffer */ diff --git a/components/driver/i2s/i2s_std.c b/components/driver/i2s/i2s_std.c index 1acafed8c0..5b9ba1e91e 100644 --- a/components/driver/i2s/i2s_std.c +++ b/components/driver/i2s/i2s_std.c @@ -62,6 +62,9 @@ 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); + ESP_RETURN_ON_FALSE(std_cfg->slot_cfg.data_bit_width != 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"); i2s_hal_clock_info_t clk_info; /* Calculate clock parameters */ @@ -86,10 +89,6 @@ static esp_err_t i2s_std_set_clock(i2s_chan_handle_t handle, const i2s_std_clk_c static esp_err_t i2s_std_set_slot(i2s_chan_handle_t handle, const i2s_std_slot_config_t *slot_cfg) { - /* Check configuration validity */ - ESP_RETURN_ON_FALSE((slot_cfg->slot_mode == I2S_SLOT_MODE_STEREO) || (slot_cfg->slot_mask != I2S_STD_SLOT_LEFT_RIGHT), - ESP_ERR_INVALID_ARG, TAG, "Can't select both left and right slot in mono mode"); - /* Update the total slot num and active slot num */ handle->total_slot = 2; handle->active_slot = slot_cfg->slot_mode == I2S_SLOT_MODE_MONO ? 1 : 2; diff --git a/components/driver/i2s/i2s_tdm.c b/components/driver/i2s/i2s_tdm.c index 01fb79c117..aaf39a6632 100644 --- a/components/driver/i2s/i2s_tdm.c +++ b/components/driver/i2s/i2s_tdm.c @@ -41,19 +41,20 @@ static esp_err_t i2s_tdm_calculate_clock(i2s_chan_handle_t handle, const i2s_tdm clk_info->mclk = rate * clk_cfg->mclk_multiple; clk_info->bclk_div = clk_info->mclk / clk_info->bclk; /* While RECEIVING multiple slots, the data will go wrong if the bclk_div is euqal or smaller than 2 */ - check: - if (clk_info->bclk_div <= 2) { + do { clk_info->mclk *= 2; clk_info->bclk_div = clk_info->mclk / clk_info->bclk; - ESP_LOGW(TAG, "the current mclk multiple is too small, adjust the mclk multiple to %d", clk_info->mclk / rate); - goto check; - } + if (clk_info->bclk_div <= 2) { + ESP_LOGW(TAG, "the current mclk multiple is too small, adjust the mclk multiple to %d", clk_info->mclk / rate); + } + } while (clk_info->bclk_div <= 2); } else { /* For slave mode, mclk >= bclk * 8, so fix bclk_div to 2 first */ clk_info->bclk_div = 8; clk_info->bclk = rate * handle->total_slot * slot_bits; clk_info->mclk = clk_info->bclk * clk_info->bclk_div; } + #if SOC_I2S_SUPPORTS_APLL clk_info->sclk = clk_cfg->clk_src == I2S_CLK_SRC_APLL ? i2s_set_get_apll_freq(clk_info->mclk) : I2S_LL_BASE_CLK; #else diff --git a/components/driver/include/driver/i2s_common.h b/components/driver/include/driver/i2s_common.h index 8cd9297a67..63fec906d0 100644 --- a/components/driver/include/driver/i2s_common.h +++ b/components/driver/include/driver/i2s_common.h @@ -23,8 +23,7 @@ extern "C" { .id = i2s_num, \ .role = i2s_role, \ .dma_desc_num = 6, \ - .dma_frame_num = 250, \ - .dma_buf_in_psram = false, \ + .dma_frame_num = 240, \ .auto_clear = false, \ } @@ -60,9 +59,8 @@ typedef struct { /* DMA configurations */ uint32_t dma_desc_num; /*!< I2S DMA buffer number, it is also the number of DMA descriptor */ - uint32_t dma_frame_num; /*!< I2S frame number in one DMA buffer. One frame means one-time sample data in all slots */ - bool dma_buf_in_psram; /*!< Prefer to allocate the DMA buffers in the psram (not supported on ESP32) - * To allocate the DMA buffers in the psram, SPIRAM should be enabled in menuconfig + uint32_t dma_frame_num; /*!< I2S frame number in one DMA buffer. One frame means one-time sample data in all slots, + * it should be the multiple of '3' when the data bit width is 24. */ bool auto_clear; /*!< Set to auto clear DMA TX buffer, i2s will always send zero automatically if no data to send */ } i2s_chan_config_t; diff --git a/components/driver/include/driver/i2s_pdm.h b/components/driver/include/driver/i2s_pdm.h index 2ca0861dc3..26a20df8da 100644 --- a/components/driver/include/driver/i2s_pdm.h +++ b/components/driver/include/driver/i2s_pdm.h @@ -191,6 +191,7 @@ esp_err_t i2s_channel_reconfig_pdm_rx_gpio(i2s_chan_handle_t handle, const i2s_p .data_bit_width = bits_per_sample, \ .slot_bit_width = I2S_SLOT_BIT_WIDTH_AUTO, \ .slot_mode = mono_or_stereo, \ + .slot_mask = I2S_PDM_SLOT_BOTH, \ .sd_prescale = 0, \ .sd_scale = I2S_PDM_SIG_SCALING_MUL_1, \ .hp_scale = I2S_PDM_SIG_SCALING_MUL_1, \ @@ -243,6 +244,9 @@ typedef struct { * Stereo means the data buffer contains two slots data */ /* Particular fields */ +#if SOC_I2S_HW_VERSION_1 + i2s_pdm_slot_mask_t slot_mask; /*!< Slot mask to choose left or right slot */ +#endif uint32_t sd_prescale; /*!< Sigma-delta filter prescale */ i2s_pdm_sig_scale_t sd_scale; /*!< Sigma-delta filter scaling value */ i2s_pdm_sig_scale_t hp_scale; /*!< High pass filter scaling value */ diff --git a/components/driver/include/driver/i2s_std.h b/components/driver/include/driver/i2s_std.h index 06cbf58b69..95f1162928 100644 --- a/components/driver/include/driver/i2s_std.h +++ b/components/driver/include/driver/i2s_std.h @@ -16,11 +16,13 @@ #include "hal/gpio_types.h" #include "driver/i2s_common.h" +#include "sdkconfig.h" + #ifdef __cplusplus extern "C" { #endif -#if SOC_I2S_HW_VERSION_1 // For esp32/esp32-s2 +#if CONFIG_IDF_TARGET_ESP32 /** * @brief Philip format in 2 slots * @param bits_per_sample i2s data bit width @@ -31,7 +33,63 @@ extern "C" { .slot_bit_width = I2S_SLOT_BIT_WIDTH_AUTO, \ .slot_mode = mono_or_stereo, \ .slot_mask = (mono_or_stereo == I2S_SLOT_MODE_MONO) ? \ - I2S_STD_SLOT_ONLY_LEFT : I2S_STD_SLOT_LEFT_RIGHT, \ + I2S_STD_SLOT_LEFT : I2S_STD_SLOT_BOTH, \ + .ws_width = bits_per_sample, \ + .ws_pol = false, \ + .bit_shift = true, \ + .msb_right = (bits_per_sample <= I2S_DATA_BIT_WIDTH_16BIT) ? \ + true : false, \ +} + +/** + * @brief PCM(short) format in 2 slots + * @note PCM(long) is sample as philip in 2 slots + * @param bits_per_sample i2s data bit width + * @param mono_or_stereo I2S_SLOT_MODE_MONO or I2S_SLOT_MODE_STEREO + */ +#define I2S_STD_PCM_SLOT_DEFAULT_CONFIG(bits_per_sample, mono_or_stereo) { \ + .data_bit_width = bits_per_sample, \ + .slot_bit_width = I2S_SLOT_BIT_WIDTH_AUTO, \ + .slot_mode = mono_or_stereo, \ + .slot_mask = (mono_or_stereo == I2S_SLOT_MODE_MONO) ? \ + I2S_STD_SLOT_LEFT : I2S_STD_SLOT_BOTH, \ + .ws_width = 1, \ + .ws_pol = true, \ + .bit_shift = true, \ + .msb_right = (bits_per_sample <= I2S_DATA_BIT_WIDTH_16BIT) ? \ + true : false, \ +} + +/** + * @brief MSB format in 2 slots + * @param bits_per_sample i2s data bit width + * @param mono_or_stereo I2S_SLOT_MODE_MONO or I2S_SLOT_MODE_STEREO + */ +#define I2S_STD_MSB_SLOT_DEFAULT_CONFIG(bits_per_sample, mono_or_stereo) { \ + .data_bit_width = bits_per_sample, \ + .slot_bit_width = I2S_SLOT_BIT_WIDTH_AUTO, \ + .slot_mode = mono_or_stereo, \ + .slot_mask = (mono_or_stereo == I2S_SLOT_MODE_MONO) ? \ + I2S_STD_SLOT_LEFT : I2S_STD_SLOT_BOTH, \ + .ws_width = bits_per_sample, \ + .ws_pol = false, \ + .bit_shift = false, \ + .msb_right = (bits_per_sample <= I2S_DATA_BIT_WIDTH_16BIT) ? \ + true : false, \ +} + +#elif CONFIG_IDF_TARGET_ESP32S2 +/** + * @brief Philip format in 2 slots + * @param bits_per_sample i2s data bit width + * @param mono_or_stereo I2S_SLOT_MODE_MONO or I2S_SLOT_MODE_STEREO + */ +#define I2S_STD_PHILIP_SLOT_DEFAULT_CONFIG(bits_per_sample, mono_or_stereo) { \ + .data_bit_width = bits_per_sample, \ + .slot_bit_width = I2S_SLOT_BIT_WIDTH_AUTO, \ + .slot_mode = mono_or_stereo, \ + .slot_mask = (mono_or_stereo == I2S_SLOT_MODE_MONO) ? \ + I2S_STD_SLOT_LEFT : I2S_STD_SLOT_BOTH, \ .ws_width = bits_per_sample, \ .ws_pol = false, \ .bit_shift = true, \ @@ -49,7 +107,7 @@ extern "C" { .slot_bit_width = I2S_SLOT_BIT_WIDTH_AUTO, \ .slot_mode = mono_or_stereo, \ .slot_mask = (mono_or_stereo == I2S_SLOT_MODE_MONO) ? \ - I2S_STD_SLOT_ONLY_LEFT : I2S_STD_SLOT_LEFT_RIGHT, \ + I2S_STD_SLOT_LEFT : I2S_STD_SLOT_BOTH, \ .ws_width = 1, \ .ws_pol = true, \ .bit_shift = true, \ @@ -66,7 +124,7 @@ extern "C" { .slot_bit_width = I2S_SLOT_BIT_WIDTH_AUTO, \ .slot_mode = mono_or_stereo, \ .slot_mask = (mono_or_stereo == I2S_SLOT_MODE_MONO) ? \ - I2S_STD_SLOT_ONLY_LEFT : I2S_STD_SLOT_LEFT_RIGHT, \ + I2S_STD_SLOT_LEFT : I2S_STD_SLOT_BOTH, \ .ws_width = bits_per_sample, \ .ws_pol = false, \ .bit_shift = false, \ @@ -83,8 +141,7 @@ extern "C" { .data_bit_width = bits_per_sample, \ .slot_bit_width = I2S_SLOT_BIT_WIDTH_AUTO, \ .slot_mode = mono_or_stereo, \ - .slot_mask = (mono_or_stereo == I2S_SLOT_MODE_MONO) ? \ - I2S_STD_SLOT_ONLY_LEFT : I2S_STD_SLOT_LEFT_RIGHT, \ + .slot_mask = I2S_STD_SLOT_BOTH, \ .ws_width = bits_per_sample, \ .ws_pol = false, \ .bit_shift = true, \ @@ -103,8 +160,7 @@ extern "C" { .data_bit_width = bits_per_sample, \ .slot_bit_width = I2S_SLOT_BIT_WIDTH_AUTO, \ .slot_mode = mono_or_stereo, \ - .slot_mask = (mono_or_stereo == I2S_SLOT_MODE_MONO) ? \ - I2S_STD_SLOT_ONLY_LEFT : I2S_STD_SLOT_LEFT_RIGHT, \ + .slot_mask = I2S_STD_SLOT_BOTH, \ .ws_width = 1, \ .ws_pol = true, \ .bit_shift = true, \ @@ -122,8 +178,7 @@ extern "C" { .data_bit_width = bits_per_sample, \ .slot_bit_width = I2S_SLOT_BIT_WIDTH_AUTO, \ .slot_mode = mono_or_stereo, \ - .slot_mask = (mono_or_stereo == I2S_SLOT_MODE_MONO) ? \ - I2S_STD_SLOT_ONLY_LEFT : I2S_STD_SLOT_LEFT_RIGHT, \ + .slot_mask = I2S_STD_SLOT_BOTH, \ .ws_width = bits_per_sample, \ .ws_pol = false, \ .bit_shift = false, \ @@ -152,7 +207,10 @@ typedef struct { /* General fields */ i2s_data_bit_width_t data_bit_width; /*!< I2S sample data bit width (valid data bits per sample) */ i2s_slot_bit_width_t slot_bit_width; /*!< I2S slot bit width (total bits per slot) */ - i2s_slot_mode_t slot_mode; /*!< Set mono or stereo mode with I2S_SLOT_MODE_MONO or I2S_SLOT_MODE_STEREO */ + i2s_slot_mode_t slot_mode; /*!< Set mono or stereo mode with I2S_SLOT_MODE_MONO or I2S_SLOT_MODE_STEREO + * In TX direction, mono means the written buffer contains only one slot data + * and stereo means the written buffer contains both left and right data + */ /* Particular fields */ i2s_std_slot_mask_t slot_mask; /*!< Select the left, right or both slot */ @@ -175,7 +233,11 @@ typedef struct { /* General fields */ uint32_t sample_rate_hz; /*!< I2S sample rate */ i2s_clock_src_t clk_src; /*!< Choose clock source */ - i2s_mclk_multiple_t mclk_multiple; /*!< The multiple of mclk to the sample rate */ + i2s_mclk_multiple_t mclk_multiple; /*!< The multiple of mclk to the sample rate + * Default is 256 in the helper macro, it can satisfy most of cases, + * but please set this field a multiple of '3' (like 384) when using 24-bit data width, + * otherwise the sample rate might be inaccurate + */ } i2s_std_clk_config_t; /** diff --git a/components/driver/test_apps/i2s_test_apps/i2s/README.md b/components/driver/test_apps/i2s_test_apps/i2s/README.md index 4cf5da5af4..497a93ba72 100644 --- a/components/driver/test_apps/i2s_test_apps/i2s/README.md +++ b/components/driver/test_apps/i2s_test_apps/i2s/README.md @@ -1,2 +1,2 @@ -| Supported Targets | ESP32 | ESP32-C3 | ESP32-S2 | ESP32-S3 | ESP32-H2 | -| ----------------- | ----- | -------- | -------- | -------- | -------- | +| Supported Targets | ESP32 | ESP32-C3 | ESP32-S2 | ESP32-S3 | +| ----------------- | ----- | -------- | -------- | -------- | diff --git a/components/driver/test_apps/i2s_test_apps/i2s/main/CMakeLists.txt b/components/driver/test_apps/i2s_test_apps/i2s/main/CMakeLists.txt index 3abecadcbd..22bdc0d96d 100644 --- a/components/driver/test_apps/i2s_test_apps/i2s/main/CMakeLists.txt +++ b/components/driver/test_apps/i2s_test_apps/i2s/main/CMakeLists.txt @@ -3,5 +3,4 @@ set(srcs "test_app_main.c" "test_i2s_iram.c") idf_component_register(SRCS ${srcs} - PRIV_INCLUDE_DIRS "../../" WHOLE_ARCHIVE) diff --git a/components/driver/test_apps/i2s_test_apps/i2s/main/test_i2s.c b/components/driver/test_apps/i2s_test_apps/i2s/main/test_i2s.c index bc7badeb80..6d028e4dde 100644 --- a/components/driver/test_apps/i2s_test_apps/i2s/main/test_i2s.c +++ b/components/driver/test_apps/i2s_test_apps/i2s/main/test_i2s.c @@ -32,7 +32,7 @@ #include "soc/pcnt_periph.h" #endif -#include "test_inc/test_i2s.h" +#include "../../test_inc/test_i2s.h" #define I2S_TEST_MODE_SLAVE_TO_MASTER 0 #define I2S_TEST_MODE_MASTER_TO_SLAVE 1 @@ -416,7 +416,7 @@ TEST_CASE("I2S_mono_stereo_loopback_test", "[i2s]") }; i2s_std_config_t rx_std_cfg = tx_std_cfg; rx_std_cfg.slot_cfg.slot_mode = I2S_SLOT_MODE_MONO; - rx_std_cfg.slot_cfg.slot_mask = I2S_STD_SLOT_ONLY_RIGHT; + rx_std_cfg.slot_cfg.slot_mask = I2S_STD_SLOT_RIGHT; /* TX channel basic test */ TEST_ESP_OK(i2s_new_channel(&chan_cfg, &tx_handle, &rx_handle)); @@ -463,7 +463,7 @@ TEST_CASE("I2S_mono_stereo_loopback_test", "[i2s]") * rx receive: 0x00[R] 0x02[R] ... */ TEST_ESP_OK(i2s_channel_disable(tx_handle)); TEST_ESP_OK(i2s_channel_disable(rx_handle)); - rx_std_cfg.slot_cfg.slot_mask = I2S_STD_SLOT_ONLY_LEFT; + rx_std_cfg.slot_cfg.slot_mask = I2S_STD_SLOT_LEFT; TEST_ESP_OK(i2s_channel_reconfig_std_slot(rx_handle, &rx_std_cfg.slot_cfg)); TEST_ESP_OK(i2s_channel_enable(tx_handle)); TEST_ESP_OK(i2s_channel_enable(rx_handle)); @@ -493,7 +493,7 @@ TEST_CASE("I2S_mono_stereo_loopback_test", "[i2s]") TEST_ESP_OK(i2s_channel_disable(tx_handle)); TEST_ESP_OK(i2s_channel_disable(rx_handle)); rx_std_cfg.slot_cfg.slot_mode = I2S_SLOT_MODE_STEREO; - rx_std_cfg.slot_cfg.slot_mask = I2S_STD_SLOT_LEFT_RIGHT; + rx_std_cfg.slot_cfg.slot_mask = I2S_STD_SLOT_BOTH; TEST_ESP_OK(i2s_channel_reconfig_std_slot(rx_handle, &rx_std_cfg.slot_cfg)); TEST_ESP_OK(i2s_channel_enable(tx_handle)); TEST_ESP_OK(i2s_channel_enable(rx_handle)); diff --git a/components/driver/test_apps/i2s_test_apps/i2s/main/test_i2s_iram.c b/components/driver/test_apps/i2s_test_apps/i2s/main/test_i2s_iram.c index bda7d40e12..792be79a76 100644 --- a/components/driver/test_apps/i2s_test_apps/i2s/main/test_i2s_iram.c +++ b/components/driver/test_apps/i2s_test_apps/i2s/main/test_i2s_iram.c @@ -15,7 +15,7 @@ #include "soc/soc_caps.h" #include "esp_private/i2s_platform.h" #include "esp_private/spi_flash_os.h" -#include "test_inc/test_i2s.h" +#include "../../test_inc/test_i2s.h" #if CONFIG_I2S_ISR_IRAM_SAFE diff --git a/components/driver/test_apps/i2s_test_apps/legacy_i2s_driver/README.md b/components/driver/test_apps/i2s_test_apps/legacy_i2s_driver/README.md index 4cf5da5af4..497a93ba72 100644 --- a/components/driver/test_apps/i2s_test_apps/legacy_i2s_driver/README.md +++ b/components/driver/test_apps/i2s_test_apps/legacy_i2s_driver/README.md @@ -1,2 +1,2 @@ -| Supported Targets | ESP32 | ESP32-C3 | ESP32-S2 | ESP32-S3 | ESP32-H2 | -| ----------------- | ----- | -------- | -------- | -------- | -------- | +| Supported Targets | ESP32 | ESP32-C3 | ESP32-S2 | ESP32-S3 | +| ----------------- | ----- | -------- | -------- | -------- | diff --git a/components/driver/test_apps/i2s_test_apps/legacy_i2s_driver/main/CMakeLists.txt b/components/driver/test_apps/i2s_test_apps/legacy_i2s_driver/main/CMakeLists.txt index 5a3a449155..60f64e8760 100644 --- a/components/driver/test_apps/i2s_test_apps/legacy_i2s_driver/main/CMakeLists.txt +++ b/components/driver/test_apps/i2s_test_apps/legacy_i2s_driver/main/CMakeLists.txt @@ -2,5 +2,4 @@ set(srcs "test_app_main.c" "test_legacy_i2s.c") idf_component_register(SRCS ${srcs} - PRIV_INCLUDE_DIRS "../../" WHOLE_ARCHIVE) diff --git a/components/driver/test_apps/i2s_test_apps/legacy_i2s_driver/main/test_legacy_i2s.c b/components/driver/test_apps/i2s_test_apps/legacy_i2s_driver/main/test_legacy_i2s.c index a6ea825982..f8ab460e54 100644 --- a/components/driver/test_apps/i2s_test_apps/legacy_i2s_driver/main/test_legacy_i2s.c +++ b/components/driver/test_apps/i2s_test_apps/legacy_i2s_driver/main/test_legacy_i2s.c @@ -30,7 +30,7 @@ #include "soc/pcnt_periph.h" #endif -#include "test_inc/test_i2s.h" +#include "../../test_inc/test_i2s.h" #define PERCENT_DIFF 0.0001 @@ -254,9 +254,9 @@ TEST_CASE("I2S_mono_stereo_loopback_test", "[i2s_legacy]") TEST_ESP_OK(i2s_stop(I2S_NUM_0)); /* Config TX as stereo channel directly, because legacy driver can't support config tx&rx separately */ #if SOC_I2S_HW_VERSION_1 - i2s_ll_tx_select_slot(&I2S0, I2S_STD_SLOT_LEFT_RIGHT, true); + i2s_ll_tx_select_std_slot(&I2S0, I2S_STD_SLOT_BOTH, false); #else - i2s_ll_tx_select_slot(&I2S0, I2S_STD_SLOT_LEFT_RIGHT); + i2s_ll_tx_select_std_slot(&I2S0, I2S_STD_SLOT_BOTH); #endif i2s_ll_tx_enable_mono_mode(&I2S0, false); @@ -352,9 +352,9 @@ TEST_CASE("I2S_mono_stereo_loopback_test", "[i2s_legacy]") TEST_ESP_OK(i2s_driver_install(I2S_NUM_0, &master_i2s_config, 0, NULL)); TEST_ESP_OK(i2s_stop(I2S_NUM_0)); #if SOC_I2S_HW_VERSION_1 - i2s_ll_tx_select_slot(&I2S0, I2S_STD_SLOT_LEFT_RIGHT, true); + i2s_ll_tx_select_std_slot(&I2S0, I2S_STD_SLOT_BOTH, false); #else - i2s_ll_tx_select_slot(&I2S0, I2S_STD_SLOT_LEFT_RIGHT); + i2s_ll_tx_select_std_slot(&I2S0, I2S_STD_SLOT_BOTH); #endif i2s_ll_tx_enable_mono_mode(&I2S0, false); @@ -676,7 +676,7 @@ TEST_CASE("I2S_write_and_read_test_master_rx_and_slave_tx", "[i2s_legacy]") TEST_CASE("I2S_memory_leaking_test", "[i2s_legacy]") { i2s_config_t master_i2s_config = { - .mode = I2S_MODE_MASTER | I2S_MODE_RX, + .mode = I2S_MODE_MASTER | I2S_MODE_RX | I2S_MODE_TX, .sample_rate = SAMPLE_RATE, .bits_per_sample = SAMPLE_BITS, .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT, @@ -701,20 +701,33 @@ TEST_CASE("I2S_memory_leaking_test", "[i2s_legacy]") .data_out_num = -1, .data_in_num = DATA_IN_IO }; + uint8_t *w_buf = calloc(1, 2000); + TEST_ASSERT(w_buf); + uint8_t *r_buf = calloc(1, 2000); + TEST_ASSERT(r_buf); + size_t w_bytes = 0; + size_t r_bytes = 0; TEST_ESP_OK(i2s_driver_install(I2S_NUM_0, &master_i2s_config, 0, NULL)); TEST_ESP_OK(i2s_set_pin(I2S_NUM_0, &master_pin_config)); + TEST_ESP_OK(i2s_write(I2S_NUM_0, w_buf, 2000, &w_bytes, portMAX_DELAY)); + TEST_ESP_OK(i2s_read(I2S_NUM_0, r_buf, 2000, &r_bytes, portMAX_DELAY)); i2s_driver_uninstall(I2S_NUM_0); int initial_size = esp_get_free_heap_size(); - for (int i = 0; i < 100; i++) { + for (int i = 0; i < 50; i++) { TEST_ESP_OK(i2s_driver_install(I2S_NUM_0, &master_i2s_config, 0, NULL)); TEST_ESP_OK(i2s_set_pin(I2S_NUM_0, &master_pin_config)); + TEST_ESP_OK(i2s_write(I2S_NUM_0, w_buf, 2000, &w_bytes, portMAX_DELAY)); + TEST_ESP_OK(i2s_read(I2S_NUM_0, r_buf, 2000, &r_bytes, portMAX_DELAY)); i2s_driver_uninstall(I2S_NUM_0); TEST_ASSERT(initial_size == esp_get_free_heap_size()); } vTaskDelay(100 / portTICK_PERIOD_MS); TEST_ASSERT(initial_size == esp_get_free_heap_size()); + + free(w_buf); + free(r_buf); } #if SOC_I2S_SUPPORTS_APLL diff --git a/components/esp_lcd/src/esp_lcd_panel_io_i2s.c b/components/esp_lcd/src/esp_lcd_panel_io_i2s.c index 9cdd282b97..e57d3303fb 100644 --- a/components/esp_lcd/src/esp_lcd_panel_io_i2s.c +++ b/components/esp_lcd/src/esp_lcd_panel_io_i2s.c @@ -185,7 +185,7 @@ esp_err_t esp_lcd_new_i80_bus(const esp_lcd_i80_bus_config_t *bus_config, esp_lc i2s_ll_tx_bypass_pcm(bus->hal.dev, true); i2s_ll_tx_set_slave_mod(bus->hal.dev, false); i2s_ll_tx_set_bits_mod(bus->hal.dev, bus_config->bus_width); - i2s_ll_tx_select_slot(bus->hal.dev, I2S_STD_SLOT_ONLY_LEFT, false); // mono + i2s_ll_tx_select_std_slot(bus->hal.dev, I2S_STD_SLOT_BOTH, true); // copy mono bus->bus_width = bus_config->bus_width; i2s_ll_tx_enable_right_first(bus->hal.dev, true); #if SOC_I2S_SUPPORTS_DMA_EQUAL diff --git a/components/hal/esp32/include/hal/i2s_ll.h b/components/hal/esp32/include/hal/i2s_ll.h index 5a1603eac2..e7665288da 100644 --- a/components/hal/esp32/include/hal/i2s_ll.h +++ b/components/hal/esp32/include/hal/i2s_ll.h @@ -745,28 +745,107 @@ static inline void i2s_ll_rx_enable_msb_shift(i2s_dev_t *hw, bool msb_shift_enab hw->conf.rx_msb_shift = msb_shift_enable; } +/** + * @brief Set I2S PDM TX chan mode + * @param slot_mask select slot to send data + * @param is_mono is mono mode + */ +static inline void i2s_ll_tx_select_pdm_slot(i2s_dev_t *hw, i2s_pdm_slot_mask_t slot_mask, bool is_mono) +{ + if (is_mono) { + switch (slot_mask) + { + case I2S_PDM_SLOT_RIGHT: + hw->conf_chan.tx_chan_mod = 3; + break; + case I2S_PDM_SLOT_LEFT: + hw->conf_chan.tx_chan_mod = 4; + break; + case I2S_PDM_SLOT_BOTH: + hw->conf_chan.tx_chan_mod = 1; // 1 & 2 has same effect + break; + default: + break; + } + } else { + switch (slot_mask) + { + case I2S_PDM_SLOT_RIGHT: + hw->conf_chan.tx_chan_mod = 1; + break; + case I2S_PDM_SLOT_LEFT: + hw->conf_chan.tx_chan_mod = 2; + break; + case I2S_PDM_SLOT_BOTH: + hw->conf_chan.tx_chan_mod = 0; + break; + default: + break; + } + } +} + +/** + * @brief Set I2S PDM RX chan mode + * @param slot_mask select slot to send data + */ +static inline void i2s_ll_rx_select_pdm_slot(i2s_dev_t *hw, i2s_pdm_slot_mask_t slot_mask) +{ + switch (slot_mask) + { + case I2S_PDM_SLOT_RIGHT: + hw->conf_chan.rx_chan_mod = 1; + break; + case I2S_PDM_SLOT_LEFT: + hw->conf_chan.rx_chan_mod = 2; + break; + case I2S_PDM_SLOT_BOTH: + hw->conf_chan.rx_chan_mod = 0; + break; + default: + break; + } +} + /** * @brief Set I2S tx chan mode * * @param hw Peripheral I2S hardware instance address. * @param slot_mask select slot to send data - * @param is_msb_right the slot sequence is affected by msb_right according to TRM + * @param is_mono is mono mode */ -static inline void i2s_ll_tx_select_slot(i2s_dev_t *hw, i2s_std_slot_mask_t slot_mask, bool is_msb_right) +static inline void i2s_ll_tx_select_std_slot(i2s_dev_t *hw, i2s_std_slot_mask_t slot_mask, bool is_mono) { - switch (slot_mask) - { - case I2S_STD_SLOT_ONLY_RIGHT: - hw->conf_chan.tx_chan_mod = is_msb_right ? 1 : 2; - break; - case I2S_STD_SLOT_ONLY_LEFT: - hw->conf_chan.tx_chan_mod = is_msb_right ? 2 : 1; - break; - case I2S_STD_SLOT_LEFT_RIGHT: - hw->conf_chan.tx_chan_mod = 0; - break; - default: - break; + if (is_mono) { + switch (slot_mask) + { + case I2S_STD_SLOT_RIGHT: + hw->conf_chan.tx_chan_mod = 3; + break; + case I2S_STD_SLOT_LEFT: + hw->conf_chan.tx_chan_mod = 4; + break; + case I2S_STD_SLOT_BOTH: + hw->conf_chan.tx_chan_mod = 1; // 1 & 2 has same effect + break; + default: + break; + } + } else { + switch (slot_mask) + { + case I2S_STD_SLOT_RIGHT: + hw->conf_chan.tx_chan_mod = 1; + break; + case I2S_STD_SLOT_LEFT: + hw->conf_chan.tx_chan_mod = 2; + break; + case I2S_STD_SLOT_BOTH: + hw->conf_chan.tx_chan_mod = 0; + break; + default: + break; + } } } @@ -777,17 +856,17 @@ static inline void i2s_ll_tx_select_slot(i2s_dev_t *hw, i2s_std_slot_mask_t slot * @param slot_mask select slot to receive data * @param is_msb_right the slot sequence is affected by msb_right according to TRM */ -static inline void i2s_ll_rx_select_slot(i2s_dev_t *hw, i2s_std_slot_mask_t slot_mask, bool is_msb_right) +static inline void i2s_ll_rx_select_std_slot(i2s_dev_t *hw, i2s_std_slot_mask_t slot_mask, bool is_msb_right) { switch (slot_mask) { - case I2S_STD_SLOT_ONLY_RIGHT: + case I2S_STD_SLOT_RIGHT: hw->conf_chan.rx_chan_mod = is_msb_right ? 1 : 2; break; - case I2S_STD_SLOT_ONLY_LEFT: + case I2S_STD_SLOT_LEFT: hw->conf_chan.rx_chan_mod = is_msb_right ? 2 : 1; break; - case I2S_STD_SLOT_LEFT_RIGHT: + case I2S_STD_SLOT_BOTH: hw->conf_chan.rx_chan_mod = 0; break; default: diff --git a/components/hal/esp32c3/include/hal/i2s_ll.h b/components/hal/esp32c3/include/hal/i2s_ll.h index de35fddfbd..579f356768 100644 --- a/components/hal/esp32c3/include/hal/i2s_ll.h +++ b/components/hal/esp32c3/include/hal/i2s_ll.h @@ -591,7 +591,7 @@ static inline void i2s_ll_rx_set_active_chan_mask(i2s_dev_t *hw, uint32_t chan_m * @param hw Peripheral I2S hardware instance address. * @param slot_mask select slot to send data */ -static inline void i2s_ll_tx_select_slot(i2s_dev_t *hw, i2s_std_slot_mask_t slot_mask) +static inline void i2s_ll_tx_select_std_slot(i2s_dev_t *hw, i2s_std_slot_mask_t slot_mask) { /* In mono mode, there only should be one slot enabled, another inactive slot will transmit same data as enabled slot * Otherwise always enable the first two slots */ @@ -599,13 +599,13 @@ static inline void i2s_ll_tx_select_slot(i2s_dev_t *hw, i2s_std_slot_mask_t slot hw->tx_tdm_ctrl.val &= ~I2S_LL_TDM_CH_MASK; switch (slot_mask) { - case I2S_STD_SLOT_ONLY_LEFT: + case I2S_STD_SLOT_LEFT: hw->tx_tdm_ctrl.val |= 0x01; break; - case I2S_STD_SLOT_ONLY_RIGHT: + case I2S_STD_SLOT_RIGHT: hw->tx_tdm_ctrl.val |= 0x02; break; - case I2S_STD_SLOT_LEFT_RIGHT: + case I2S_STD_SLOT_BOTH: hw->tx_tdm_ctrl.val |= 0x03; break; default: @@ -619,7 +619,7 @@ static inline void i2s_ll_tx_select_slot(i2s_dev_t *hw, i2s_std_slot_mask_t slot * @param hw Peripheral I2S hardware instance address. * @param slot_mask select slot to receive data */ -static inline void i2s_ll_rx_select_slot(i2s_dev_t *hw, i2s_std_slot_mask_t slot_mask) +static inline void i2s_ll_rx_select_std_slot(i2s_dev_t *hw, i2s_std_slot_mask_t slot_mask) { /* In mono mode, there only should be one slot enabled, another inactive slot will transmit same data as enabled slot * Otherwise always enable the first two slots */ @@ -627,13 +627,13 @@ static inline void i2s_ll_rx_select_slot(i2s_dev_t *hw, i2s_std_slot_mask_t slot hw->rx_tdm_ctrl.val &= ~I2S_LL_TDM_CH_MASK; switch (slot_mask) { - case I2S_STD_SLOT_ONLY_LEFT: + case I2S_STD_SLOT_LEFT: hw->rx_tdm_ctrl.val |= 0x01; break; - case I2S_STD_SLOT_ONLY_RIGHT: + case I2S_STD_SLOT_RIGHT: hw->rx_tdm_ctrl.val |= 0x02; break; - case I2S_STD_SLOT_LEFT_RIGHT: + case I2S_STD_SLOT_BOTH: hw->rx_tdm_ctrl.val |= 0x03; break; default: @@ -1084,6 +1084,8 @@ static inline void i2s_ll_tx_pdm_dma_take_mode(i2s_dev_t *hw, bool is_mono, bool * Mono Left Left 2 1 * Mono Left Single 3 1 * Mono Single Right 4 1 + * @note The 'Single' above means always sending the value of `conf_single_data` reg + * The default value of `conf_single_data` reg is '0', it is not public for now * * @param hw Peripheral I2S hardware instance address. * @param is_mono The DMA data only has one slot (mono) or contains two slots (stereo) diff --git a/components/hal/esp32h2/include/hal/i2s_ll.h b/components/hal/esp32h2/include/hal/i2s_ll.h index 50a9fc5b10..edbafcc252 100644 --- a/components/hal/esp32h2/include/hal/i2s_ll.h +++ b/components/hal/esp32h2/include/hal/i2s_ll.h @@ -593,7 +593,7 @@ static inline void i2s_ll_rx_set_active_chan_mask(i2s_dev_t *hw, uint32_t chan_m * @param hw Peripheral I2S hardware instance address. * @param slot_mask select slot to send data */ -static inline void i2s_ll_tx_select_slot(i2s_dev_t *hw, i2s_std_slot_mask_t slot_mask) +static inline void i2s_ll_tx_select_std_slot(i2s_dev_t *hw, i2s_std_slot_mask_t slot_mask) { /* In mono mode, there only should be one slot enabled, another inactive slot will transmit same data as enabled slot * Otherwise always enable the first two slots */ @@ -601,13 +601,13 @@ static inline void i2s_ll_tx_select_slot(i2s_dev_t *hw, i2s_std_slot_mask_t slot hw->tx_tdm_ctrl.val &= ~I2S_LL_TDM_CH_MASK; switch (slot_mask) { - case I2S_STD_SLOT_ONLY_LEFT: + case I2S_STD_SLOT_LEFT: hw->tx_tdm_ctrl.val |= 0x01; break; - case I2S_STD_SLOT_ONLY_RIGHT: + case I2S_STD_SLOT_RIGHT: hw->tx_tdm_ctrl.val |= 0x02; break; - case I2S_STD_SLOT_LEFT_RIGHT: + case I2S_STD_SLOT_BOTH: hw->tx_tdm_ctrl.val |= 0x03; break; default: @@ -621,7 +621,7 @@ static inline void i2s_ll_tx_select_slot(i2s_dev_t *hw, i2s_std_slot_mask_t slot * @param hw Peripheral I2S hardware instance address. * @param slot_mask select slot to receive data */ -static inline void i2s_ll_rx_select_slot(i2s_dev_t *hw, i2s_std_slot_mask_t slot_mask) +static inline void i2s_ll_rx_select_std_slot(i2s_dev_t *hw, i2s_std_slot_mask_t slot_mask) { /* In mono mode, there only should be one slot enabled, another inactive slot will transmit same data as enabled slot * Otherwise always enable the first two slots */ @@ -629,13 +629,13 @@ static inline void i2s_ll_rx_select_slot(i2s_dev_t *hw, i2s_std_slot_mask_t slot hw->rx_tdm_ctrl.val &= ~I2S_LL_TDM_CH_MASK; switch (slot_mask) { - case I2S_STD_SLOT_ONLY_LEFT: + case I2S_STD_SLOT_LEFT: hw->rx_tdm_ctrl.val |= 0x01; break; - case I2S_STD_SLOT_ONLY_RIGHT: + case I2S_STD_SLOT_RIGHT: hw->rx_tdm_ctrl.val |= 0x02; break; - case I2S_STD_SLOT_LEFT_RIGHT: + case I2S_STD_SLOT_BOTH: hw->rx_tdm_ctrl.val |= 0x03; break; default: @@ -1097,6 +1097,8 @@ static inline void i2s_ll_tx_pdm_dma_take_mode(i2s_dev_t *hw, bool is_mono, bool * Mono Left Left 2 1 * Mono Left Single 3 1 * Mono Single Right 4 1 + * @note The 'Single' above means always sending the value of `conf_single_data` reg + * The default value of `conf_single_data` reg is '0', it is not public for now * * @param hw Peripheral I2S hardware instance address. * @param is_mono The DMA data only has one slot (mono) or contains two slots (stereo) diff --git a/components/hal/esp32s2/include/hal/i2s_ll.h b/components/hal/esp32s2/include/hal/i2s_ll.h index 1263517c15..e50a26fafb 100644 --- a/components/hal/esp32s2/include/hal/i2s_ll.h +++ b/components/hal/esp32s2/include/hal/i2s_ll.h @@ -841,22 +841,40 @@ static inline void i2s_ll_rx_enable_msb_shift(i2s_dev_t *hw, bool msb_shift_enab * * @param hw Peripheral I2S hardware instance address. * @param slot_mask select slot to send data + * @param is_mono is mono mode */ -static inline void i2s_ll_tx_select_slot(i2s_dev_t *hw, i2s_std_slot_mask_t slot_mask, bool is_msb_right) +static inline void i2s_ll_tx_select_std_slot(i2s_dev_t *hw, i2s_std_slot_mask_t slot_mask, bool is_mono) { - switch (slot_mask) - { - case I2S_STD_SLOT_ONLY_RIGHT: - hw->conf_chan.tx_chan_mod = is_msb_right ? 1 : 2; - break; - case I2S_STD_SLOT_ONLY_LEFT: - hw->conf_chan.tx_chan_mod = is_msb_right ? 2 : 1; - break; - case I2S_STD_SLOT_LEFT_RIGHT: - hw->conf_chan.tx_chan_mod = 0; - break; - default: - break; + if (is_mono) { + switch (slot_mask) + { + case I2S_STD_SLOT_RIGHT: + hw->conf_chan.tx_chan_mod = 3; + break; + case I2S_STD_SLOT_LEFT: + hw->conf_chan.tx_chan_mod = 4; + break; + case I2S_STD_SLOT_BOTH: + hw->conf_chan.tx_chan_mod = 1; // 1 & 2 has same effect + break; + default: + break; + } + } else { + switch (slot_mask) + { + case I2S_STD_SLOT_RIGHT: + hw->conf_chan.tx_chan_mod = 1; + break; + case I2S_STD_SLOT_LEFT: + hw->conf_chan.tx_chan_mod = 2; + break; + case I2S_STD_SLOT_BOTH: + hw->conf_chan.tx_chan_mod = 0; + break; + default: + break; + } } } @@ -866,17 +884,17 @@ static inline void i2s_ll_tx_select_slot(i2s_dev_t *hw, i2s_std_slot_mask_t slot * @param hw Peripheral I2S hardware instance address. * @param slot_mask select slot to receive data */ -static inline void i2s_ll_rx_select_slot(i2s_dev_t *hw, i2s_std_slot_mask_t slot_mask, bool is_msb_right) +static inline void i2s_ll_rx_select_std_slot(i2s_dev_t *hw, i2s_std_slot_mask_t slot_mask, bool is_msb_right) { switch (slot_mask) { - case I2S_STD_SLOT_ONLY_RIGHT: + case I2S_STD_SLOT_RIGHT: hw->conf_chan.rx_chan_mod = is_msb_right ? 1 : 2; break; - case I2S_STD_SLOT_ONLY_LEFT: + case I2S_STD_SLOT_LEFT: hw->conf_chan.rx_chan_mod = is_msb_right ? 2 : 1; break; - case I2S_STD_SLOT_LEFT_RIGHT: + case I2S_STD_SLOT_BOTH: hw->conf_chan.rx_chan_mod = 0; break; default: diff --git a/components/hal/esp32s3/include/hal/i2s_ll.h b/components/hal/esp32s3/include/hal/i2s_ll.h index 193bea171f..14b4e6d4e7 100644 --- a/components/hal/esp32s3/include/hal/i2s_ll.h +++ b/components/hal/esp32s3/include/hal/i2s_ll.h @@ -594,7 +594,7 @@ static inline void i2s_ll_rx_set_active_chan_mask(i2s_dev_t *hw, uint32_t chan_m * @param hw Peripheral I2S hardware instance address. * @param slot_mask select slot to send data */ -static inline void i2s_ll_tx_select_slot(i2s_dev_t *hw, i2s_std_slot_mask_t slot_mask) +static inline void i2s_ll_tx_select_std_slot(i2s_dev_t *hw, i2s_std_slot_mask_t slot_mask) { /* In mono mode, there only should be one slot enabled, another inactive slot will transmit same data as enabled slot * Otherwise always enable the first two slots */ @@ -602,13 +602,13 @@ static inline void i2s_ll_tx_select_slot(i2s_dev_t *hw, i2s_std_slot_mask_t slot hw->tx_tdm_ctrl.val &= ~I2S_LL_TDM_CH_MASK; switch (slot_mask) { - case I2S_STD_SLOT_ONLY_LEFT: + case I2S_STD_SLOT_LEFT: hw->tx_tdm_ctrl.val |= 0x01; break; - case I2S_STD_SLOT_ONLY_RIGHT: + case I2S_STD_SLOT_RIGHT: hw->tx_tdm_ctrl.val |= 0x02; break; - case I2S_STD_SLOT_LEFT_RIGHT: + case I2S_STD_SLOT_BOTH: hw->tx_tdm_ctrl.val |= 0x03; break; default: @@ -622,7 +622,7 @@ static inline void i2s_ll_tx_select_slot(i2s_dev_t *hw, i2s_std_slot_mask_t slot * @param hw Peripheral I2S hardware instance address. * @param slot_mask select slot to receive data */ -static inline void i2s_ll_rx_select_slot(i2s_dev_t *hw, i2s_std_slot_mask_t slot_mask) +static inline void i2s_ll_rx_select_std_slot(i2s_dev_t *hw, i2s_std_slot_mask_t slot_mask) { /* In mono mode, there only should be one slot enabled, another inactive slot will transmit same data as enabled slot * Otherwise always enable the first two slots */ @@ -630,13 +630,13 @@ static inline void i2s_ll_rx_select_slot(i2s_dev_t *hw, i2s_std_slot_mask_t slot hw->rx_tdm_ctrl.val &= ~I2S_LL_TDM_CH_MASK; switch (slot_mask) { - case I2S_STD_SLOT_ONLY_LEFT: + case I2S_STD_SLOT_LEFT: hw->rx_tdm_ctrl.val |= 0x01; break; - case I2S_STD_SLOT_ONLY_RIGHT: + case I2S_STD_SLOT_RIGHT: hw->rx_tdm_ctrl.val |= 0x02; break; - case I2S_STD_SLOT_LEFT_RIGHT: + case I2S_STD_SLOT_BOTH: hw->rx_tdm_ctrl.val |= 0x03; break; default: @@ -1109,6 +1109,8 @@ static inline void i2s_ll_tx_pdm_dma_take_mode(i2s_dev_t *hw, bool is_mono, bool * Mono Left Left 2 1 * Mono Left Single 3 1 * Mono Single Right 4 1 + * @note The 'Single' above means always sending the value of `conf_single_data` reg + * The default value of `conf_single_data` reg is '0', it is not public for now * * @param hw Peripheral I2S hardware instance address. * @param is_mono The DMA data only has one slot (mono) or contains two slots (stereo) diff --git a/components/hal/i2s_hal.c b/components/hal/i2s_hal.c index 89e26db79c..6c7f51e49f 100644 --- a/components/hal/i2s_hal.c +++ b/components/hal/i2s_hal.c @@ -62,18 +62,22 @@ void i2s_hal_std_set_tx_slot(i2s_hal_context_t *hal, bool is_slave, const i2s_ha i2s_ll_tx_reset(hal->dev); i2s_ll_tx_set_slave_mod(hal->dev, is_slave); //TX Slave i2s_ll_tx_set_sample_bit(hal->dev, slot_bit_width, slot_cfg->data_bit_width); - i2s_ll_tx_enable_mono_mode(hal->dev, slot_cfg->slot_mode == I2S_SLOT_MODE_MONO); i2s_ll_tx_enable_msb_shift(hal->dev, slot_cfg->std.bit_shift); i2s_ll_tx_set_ws_width(hal->dev, slot_cfg->std.ws_width); #if SOC_I2S_HW_VERSION_1 - i2s_ll_tx_select_slot(hal->dev, slot_cfg->std.slot_mask, slot_cfg->std.msb_right); + i2s_ll_tx_enable_mono_mode(hal->dev, slot_cfg->slot_mode == I2S_SLOT_MODE_MONO); + i2s_ll_tx_select_std_slot(hal->dev, slot_cfg->std.slot_mask, slot_cfg->slot_mode == I2S_SLOT_MODE_MONO); // According to the test, the behavior of tx_msb_right is opposite with TRM, TRM is wrong? i2s_ll_tx_enable_msb_right(hal->dev, slot_cfg->std.msb_right); i2s_ll_tx_enable_right_first(hal->dev, slot_cfg->std.ws_pol); /* Should always enable fifo */ i2s_ll_tx_force_enable_fifo_mod(hal->dev, true); #elif SOC_I2S_HW_VERSION_2 - i2s_ll_tx_select_slot(hal->dev, slot_cfg->std.slot_mask); + bool is_copy_mono = slot_cfg->slot_mode == I2S_SLOT_MODE_MONO && slot_cfg->std.slot_mask == I2S_STD_SLOT_BOTH; + i2s_ll_tx_enable_mono_mode(hal->dev, is_copy_mono); + i2s_ll_tx_select_std_slot(hal->dev, is_copy_mono ? I2S_STD_SLOT_LEFT : slot_cfg->std.slot_mask); + i2s_ll_tx_set_skip_mask(hal->dev, (slot_cfg->std.slot_mask != I2S_STD_SLOT_BOTH) && + (slot_cfg->slot_mode == I2S_SLOT_MODE_STEREO)); i2s_ll_tx_set_half_sample_bit(hal->dev, slot_bit_width); i2s_ll_tx_set_ws_idle_pol(hal->dev, slot_cfg->std.ws_pol); i2s_ll_tx_set_bit_order(hal->dev, slot_cfg->std.bit_order_lsb); @@ -93,13 +97,13 @@ void i2s_hal_std_set_rx_slot(i2s_hal_context_t *hal, bool is_slave, const i2s_ha i2s_ll_rx_enable_msb_shift(hal->dev, slot_cfg->std.bit_shift); i2s_ll_rx_set_ws_width(hal->dev, slot_cfg->std.ws_width); #if SOC_I2S_HW_VERSION_1 - i2s_ll_rx_select_slot(hal->dev, slot_cfg->std.slot_mask, slot_cfg->std.msb_right); + i2s_ll_rx_select_std_slot(hal->dev, slot_cfg->std.slot_mask, slot_cfg->std.msb_right); i2s_ll_rx_enable_msb_right(hal->dev, slot_cfg->std.msb_right); i2s_ll_rx_enable_right_first(hal->dev, slot_cfg->std.ws_pol); /* Should always enable fifo */ i2s_ll_rx_force_enable_fifo_mod(hal->dev, true); #elif SOC_I2S_HW_VERSION_2 - i2s_ll_rx_select_slot(hal->dev, slot_cfg->std.slot_mask); + i2s_ll_rx_select_std_slot(hal->dev, slot_cfg->std.slot_mask); i2s_ll_rx_set_half_sample_bit(hal->dev, slot_bit_width); i2s_ll_rx_set_ws_idle_pol(hal->dev, slot_cfg->std.ws_pol); i2s_ll_rx_set_bit_order(hal->dev, slot_cfg->std.bit_order_lsb); @@ -141,6 +145,9 @@ void i2s_hal_pdm_set_tx_slot(i2s_hal_context_t *hal, bool is_slave, const i2s_ha i2s_ll_tx_force_enable_fifo_mod(hal->dev, true); i2s_ll_tx_set_sample_bit(hal->dev, slot_bit_width, slot_cfg->data_bit_width); i2s_ll_tx_enable_mono_mode(hal->dev, is_mono); + i2s_ll_tx_select_pdm_slot(hal->dev, slot_cfg->pdm_tx.slot_mask, is_mono); + i2s_ll_tx_enable_msb_right(hal->dev, false); + i2s_ll_tx_enable_right_first(hal->dev, false); #elif SOC_I2S_HW_VERSION_2 /* PDM TX line mode */ i2s_ll_tx_pdm_line_mode(hal->dev, slot_cfg->pdm_tx.line_mode); @@ -188,12 +195,16 @@ void i2s_hal_pdm_set_rx_slot(i2s_hal_context_t *hal, bool is_slave, const i2s_ha i2s_ll_rx_set_sample_bit(hal->dev, slot_bit_width, slot_cfg->data_bit_width); #if SOC_I2S_HW_VERSION_1 i2s_ll_rx_enable_mono_mode(hal->dev, slot_cfg->slot_mode == I2S_SLOT_MODE_MONO); - i2s_ll_rx_select_slot(hal->dev, slot_cfg->pdm_rx.slot_mask, false); + i2s_ll_rx_select_pdm_slot(hal->dev, slot_cfg->pdm_rx.slot_mask); i2s_ll_rx_force_enable_fifo_mod(hal->dev, true); + i2s_ll_rx_enable_msb_right(hal->dev, false); + i2s_ll_rx_enable_right_first(hal->dev, false); #elif SOC_I2S_HW_VERSION_2 + i2s_ll_tx_set_half_sample_bit(hal->dev, 16); i2s_ll_rx_enable_mono_mode(hal->dev, false); - /* Set the channel mask to enable corresponding slots */ - i2s_ll_rx_set_active_chan_mask(hal->dev, slot_cfg->pdm_rx.slot_mask); + /* Set the channel mask to enable corresponding slots, always enable two slots for stereo mode */ + i2s_ll_rx_set_active_chan_mask(hal->dev, slot_cfg->slot_mode == I2S_SLOT_MODE_STEREO ? + I2S_PDM_SLOT_BOTH : slot_cfg->pdm_rx.slot_mask); #endif } diff --git a/components/hal/include/hal/i2s_hal.h b/components/hal/include/hal/i2s_hal.h index 4e80261dc1..8a8a476e8f 100644 --- a/components/hal/include/hal/i2s_hal.h +++ b/components/hal/include/hal/i2s_hal.h @@ -67,6 +67,9 @@ typedef struct { #if SOC_I2S_SUPPORTS_PDM_TX /* PDM TX configurations */ struct { +#if SOC_I2S_HW_VERSION_1 + i2s_pdm_slot_mask_t slot_mask; /*!< Slot mask to choose left or right slot */ +#endif uint32_t sd_prescale; /*!< Sigma-delta filter prescale */ i2s_pdm_sig_scale_t sd_scale; /*!< Sigma-delta filter scaling value */ i2s_pdm_sig_scale_t hp_scale; /*!< High pass filter scaling value */ diff --git a/components/hal/include/hal/i2s_types.h b/components/hal/include/hal/i2s_types.h index 30767a7282..c7efe1be22 100644 --- a/components/hal/include/hal/i2s_types.h +++ b/components/hal/include/hal/i2s_types.h @@ -64,7 +64,12 @@ typedef enum { I2S_SLOT_BIT_WIDTH_32BIT = (32), /*!< I2S channel slot bit-width: 32 */ } i2s_slot_bit_width_t; +#if SOC_I2S_SUPPORTED typedef soc_periph_i2s_clk_src_t i2s_clock_src_t; /*!< I2S clock source */ +#else +typedef int i2s_clock_src_t; /*!< Define a default type to avoid compiling warnings */ +#endif + #if SOC_I2S_SUPPORTS_PCM /** @@ -119,11 +124,13 @@ typedef enum { /** * @brief I2S slot select in standard mode + * @note It has different meanings in tx/rx/mono/stereo mode, and it may have differen behaviors on different targets + * For the details, please refer to the I2S API reference */ typedef enum { - I2S_STD_SLOT_ONLY_LEFT = BIT(0), /*!< I2S only transmits or receives left slot */ - I2S_STD_SLOT_ONLY_RIGHT = BIT(1), /*!< I2S only transmits or receives right slot */ - I2S_STD_SLOT_LEFT_RIGHT = BIT(0) | BIT(1), /*!< I2S transmits or receives both left and right slot */ + I2S_STD_SLOT_LEFT = BIT(0), /*!< I2S transmits or receives left slot */ + I2S_STD_SLOT_RIGHT = BIT(1), /*!< I2S transmits or receives right slot */ + I2S_STD_SLOT_BOTH = BIT(0) | BIT(1), /*!< I2S transmits or receives both left and right slot */ } i2s_std_slot_mask_t; /** diff --git a/components/soc/esp32/include/soc/i2s_struct.h b/components/soc/esp32/include/soc/i2s_struct.h index c02c80fe9c..5f37cf7917 100644 --- a/components/soc/esp32/include/soc/i2s_struct.h +++ b/components/soc/esp32/include/soc/i2s_struct.h @@ -3,8 +3,7 @@ * * SPDX-License-Identifier: Apache-2.0 */ -#ifndef _SOC_I2S_STRUCT_H_ -#define _SOC_I2S_STRUCT_H_ +#pragma once #include @@ -460,5 +459,3 @@ extern i2s_dev_t I2S1; #ifdef __cplusplus } #endif - -#endif /* _SOC_I2S_STRUCT_H_ */ diff --git a/components/soc/esp32c3/include/soc/i2s_struct.h b/components/soc/esp32c3/include/soc/i2s_struct.h index b827424743..de550a6c58 100644 --- a/components/soc/esp32c3/include/soc/i2s_struct.h +++ b/components/soc/esp32c3/include/soc/i2s_struct.h @@ -3,8 +3,10 @@ * * SPDX-License-Identifier: Apache-2.0 */ -#ifndef _SOC_I2S_STRUCT_H_ -#define _SOC_I2S_STRUCT_H_ +#pragma once + +#include + #ifdef __cplusplus extern "C" { #endif @@ -324,5 +326,3 @@ extern i2s_dev_t I2S0; #ifdef __cplusplus } #endif - -#endif /* _SOC_I2S_STRUCT_H_ */ diff --git a/components/soc/esp32h2/include/soc/i2s_struct.h b/components/soc/esp32h2/include/soc/i2s_struct.h index 0b5a4eae25..f58a924987 100644 --- a/components/soc/esp32h2/include/soc/i2s_struct.h +++ b/components/soc/esp32h2/include/soc/i2s_struct.h @@ -3,8 +3,10 @@ * * SPDX-License-Identifier: Apache-2.0 */ -#ifndef _SOC_I2S_STRUCT_H_ -#define _SOC_I2S_STRUCT_H_ +#pragma once + +#include + #ifdef __cplusplus extern "C" { #endif @@ -330,5 +332,3 @@ extern i2s_dev_t I2S0; #ifdef __cplusplus } #endif - -#endif /* _SOC_I2S_STRUCT_H_ */ diff --git a/components/soc/esp32s2/include/soc/i2s_struct.h b/components/soc/esp32s2/include/soc/i2s_struct.h index a251c09bba..f7fd04f63b 100644 --- a/components/soc/esp32s2/include/soc/i2s_struct.h +++ b/components/soc/esp32s2/include/soc/i2s_struct.h @@ -3,8 +3,10 @@ * * SPDX-License-Identifier: Apache-2.0 */ -#ifndef _SOC_I2S_STRUCT_H_ -#define _SOC_I2S_STRUCT_H_ +#pragma once + +#include + #ifdef __cplusplus extern "C" { #endif @@ -407,5 +409,3 @@ _Static_assert(sizeof(i2s_dev_t)==0x100, "invalid i2s_dev_t size"); #ifdef __cplusplus } #endif - -#endif /* _SOC_I2S_STRUCT_H_ */ diff --git a/components/soc/esp32s3/include/soc/i2s_struct.h b/components/soc/esp32s3/include/soc/i2s_struct.h index e42e6c5cc4..587011c4b6 100644 --- a/components/soc/esp32s3/include/soc/i2s_struct.h +++ b/components/soc/esp32s3/include/soc/i2s_struct.h @@ -3,11 +3,10 @@ * * SPDX-License-Identifier: Apache-2.0 */ -#ifndef _SOC_I2S_STRUCT_H_ -#define _SOC_I2S_STRUCT_H_ - +#pragma once #include + #ifdef __cplusplus extern "C" { #endif @@ -336,7 +335,3 @@ extern i2s_dev_t I2S1; #ifdef __cplusplus } #endif - - - -#endif /*_SOC_I2S_STRUCT_H_ */ diff --git a/docs/_static/diagrams/i2s/i2s_state_machine.png b/docs/_static/diagrams/i2s/i2s_state_machine.png index e5223bf878e9b4152c7797824164d574bb3cdf7c..8c56ca9063eca4c3017c927008c39bfa2d9ae389 100644 GIT binary patch literal 55562 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 literal 53689 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 diff --git a/docs/en/api-reference/peripherals/i2s.rst b/docs/en/api-reference/peripherals/i2s.rst index 033ac45a3a..54abfe0645 100644 --- a/docs/en/api-reference/peripherals/i2s.rst +++ b/docs/en/api-reference/peripherals/i2s.rst @@ -135,7 +135,7 @@ Standard mode always has left and right two sound channels which are called 'slo PDM Mode (TX) ^^^^^^^^^^^^^ - PDM mode for tx channel can convert PCM data into PDM format which always has left and right slots. PDM TX can only support 16 bits width sample data. PDM TX only needs CLK pin for clock signal and DOUT pin for data signal (i.e. WS and SD signal in the following figure, the BCK signal is an internal bit sampling clock, not needed between PDM devices). This mode allows user to configure the up-sampling parameters :cpp:member:`i2s_pdm_tx_clk_config_t::up_sample_fp` :cpp:member:`i2s_pdm_tx_clk_config_t::up_sample_fs`. The up-sampling rate can be calculated by ``up_sample_rate = fp / fs``, there are up-sampling modes in PDM TX: + PDM(Pulse-density Modulation) mode for tx channel can convert PCM data into PDM format which always has left and right slots. PDM TX can only support 16 bits width sample data. PDM TX only needs CLK pin for clock signal and DOUT pin for data signal (i.e. WS and SD signal in the following figure, the BCK signal is an internal bit sampling clock, not needed between PDM devices). This mode allows user to configure the up-sampling parameters :cpp:member:`i2s_pdm_tx_clk_config_t::up_sample_fp` :cpp:member:`i2s_pdm_tx_clk_config_t::up_sample_fs`. The up-sampling rate can be calculated by ``up_sample_rate = fp / fs``, there are up-sampling modes in PDM TX: - **Fixed Clock Frequency**: In this mode the up-sampling rate will change according to the sample rate. Setting ``fp = 960`` and ``fs = sample_rate / 100``, then the clock frequency(Fpdm) on CLK pin will be fixed to 128 * 48 KHz = 6.144 MHz, note that this frequency is not equal to the sample rate(Fpcm). - **Fixed Up-sampling Rate**: In this mode the up-sampling rate is fixed to 2. Setting ``fp = 960`` and ``fs = 480``, then the clock frequency(Fpdm) on CLK pin will be ``128 * sample_rate`` @@ -148,7 +148,7 @@ Standard mode always has left and right two sound channels which are called 'slo PDM Mode (RX) ^^^^^^^^^^^^^ - PDM mode for rx channel can receive PDM format data and convert the data into PCM format. PDM RX can only support 16 bits width sample data. PDM RX only need WS pin for clock signal and DIN pin for data signal. This mode allows user to configure the down-sampling parameter :cpp:member:`i2s_pdm_rx_clk_config_t::dn_sample_mode`, there are two down-sampling modes in PDM RX: + PDM(Pulse-density Modulation) mode for rx channel can receive PDM format data and convert the data into PCM format. PDM RX can only support 16 bits width sample data. PDM RX only need WS pin for clock signal and DIN pin for data signal. This mode allows user to configure the down-sampling parameter :cpp:member:`i2s_pdm_rx_clk_config_t::dn_sample_mode`, there are two down-sampling modes in PDM RX: - :cpp:enumerator:`i2s_pdm_dsr_t::I2S_PDM_DSR_8S`: In this mode, the clock frequency(Fpdm) on WS pin will be sample_rate(Fpcm) * 64. - :cpp:enumerator:`i2s_pdm_dsr_t::I2S_PDM_DSR_16S`: In this mode, the clock frequency(Fpdm) on WS pin will be sample_rate(Fpcm) * 128. @@ -159,9 +159,9 @@ Standard mode always has left and right two sound channels which are called 'slo TDM Mode ^^^^^^^^ - TDM mode supports upto 16 slots, these slots can be enabled by :cpp:member:`i2s_tdm_slot_config_t::slot_mask`. But due to the hardware limitation, only upto 4 slots are supported while the slot is set to 32 bit-width, and 8 slots for 16 bit-width, 16 slots for 8 bit-width. The slot communication format of TDM is almost same as standard mode, but there are some small differences between them. + TDM(Time Division Multiplexing) mode supports upto 16 slots, these slots can be enabled by :cpp:member:`i2s_tdm_slot_config_t::slot_mask`. But due to the hardware limitation, only upto 4 slots are supported while the slot is set to 32 bit-width, and 8 slots for 16 bit-width, 16 slots for 8 bit-width. The slot communication format of TDM is almost same as standard mode, but there are some small differences between them. - - **Philip Format**: Data signal have one bit shift comparing to the WS(word select) signal. And no matter how many slots are contained in one frame, the duty of WS signal will always keep 50%. + - **Philip Format**: Data signal have one bit shift comparing to the WS(word select) signal. And no matter how many slots are contained in one frame, the duty of WS signal will always keep 50%. .. wavedrom:: /../_static/diagrams/i2s/tdm_philip.json @@ -228,7 +228,7 @@ The ```` in the diagram can be replaced by corresponding I2S communication Data Transport ^^^^^^^^^^^^^^ -The data transport of I2S peripheral, including sending and receiving, is realized by DMA. Before transporting data, please call :cpp:func:`i2s_channel_enable` to enable the specific channel. When the sent or received data reach the size of one DMA buffer, ``I2S_OUT_EOF`` or ``I2S_IN_SUC_EOF`` interrupt will be triggered. Note that the DMA buffer size is not equal to :cpp:member:`i2s_std_slot_config_t::dma_frame_num`, one frame here means all the sampled data in one WS circle. Therefore, ``dma_buffer_size = dma_frame_num * slot_num * slot_bit_width / 8``. For the transmit case, users can input the data by calling :cpp:func:`i2s_channel_write`. This function will help users to copy the data from the source buffer to the DMA tx buffer and wait for the transmition finished. Then it'll repeat until the sent bytes reach the given size. For the receive case, the function :cpp:func:`i2s_channel_read` will wait for receiving the message queue which contains the DMA buffer address, it will help users to copy the data from DMA rx buffer to the destination buffer. +The data transport of I2S peripheral, including sending and receiving, is realized by DMA. Before transporting data, please call :cpp:func:`i2s_channel_enable` to enable the specific channel. When the sent or received data reach the size of one DMA buffer, ``I2S_OUT_EOF`` or ``I2S_IN_SUC_EOF`` interrupt will be triggered. Note that the DMA buffer size is not equal to :cpp:member:`i2s_chan_config_t::dma_frame_num`, one frame here means all the sampled data in one WS circle. Therefore, ``dma_buffer_size = dma_frame_num * slot_num * slot_bit_width / 8``. For the transmit case, users can input the data by calling :cpp:func:`i2s_channel_write`. This function will help users to copy the data from the source buffer to the DMA tx buffer and wait for the transmition finished. Then it'll repeat until the sent bytes reach the given size. For the receive case, the function :cpp:func:`i2s_channel_read` will wait for receiving the message queue which contains the DMA buffer address, it will help users to copy the data from DMA rx buffer to the destination buffer. Both :cpp:func:`i2s_channel_write` and :cpp:func:`i2s_channel_read` are blocking functions, they will keep waiting until the whole source buffer are sent or the whole destination buffer loaded, unless they exceed the max blocking time, then the error code `ESP_ERR_TIMEOUT` will return in this case. To send or receive data asynchronously, callbacks can be registered by :cpp:func:`i2s_channel_register_event_callback`, users are able to access the DMA buffer directly in the callback function instead of transmitting or receiving by the two blocking functions. However, please be aware that it is an interrupt callback, don't do complex logic, floating operation or call non-reentrant functions in the callback. @@ -268,7 +268,7 @@ Application Example The examples of the I2S driver can be found in the directory :example:`peripherals/i2s`. Here are some simple usages of each mode: -Standard TX/RX usage +Standard TX/RX Usage ^^^^^^^^^^^^^^^^^^^^ Different slot communication formats can be generated by following helper macros for standard mode. As described above, there are three formats in standard mode, their helper macros are: @@ -284,6 +284,87 @@ The clock config helper macro is: Please refer to :ref:`i2s-api-reference-i2s_std` for STD API information. And for more details, please refer to :component_file:`driver/include/driver/i2s_std.h`. +STD TX Mode +~~~~~~~~~~~ + +Take 16-bit data width for example, when the data in a ``uint16_t`` writting buffer are: + ++--------+--------+--------+--------+--------+--------+--------+--------+--------+ +| data 0 | data 1 | data 2 | data 3 | data 4 | data 5 | data 6 | data 7 | ... | ++========+========+========+========+========+========+========+========+========+ +| 0x0001 | 0x0002 | 0x0003 | 0x0004 | 0x0005 | 0x0006 | 0x0007 | 0x0008 | ... | ++--------+--------+--------+--------+--------+--------+--------+--------+--------+ + +Here is the table of the real data on the line with different :cpp:member:`i2s_std_slot_config_t::slot_mode` and :cpp:member:`i2s_std_slot_config_t::slot_mask` + +.. only:: esp32 + + +----------------+-----------+-----------+----------+----------+----------+----------+----------+----------+----------+----------+ + | data bit width | slot mode | slot mask | ws low | ws high | ws low | ws high | ws low | ws high | ws low | ws high | + +================+===========+===========+==========+==========+==========+==========+==========+==========+==========+==========+ + | | mono | left | 0x0002 | 0x0000 | 0x0001 | 0x0000 | 0x0004 | 0x0000 | 0x0003 | 0x0000 | + | 16 bit | +-----------+----------+----------+----------+----------+----------+----------+----------+----------+ + | | | right | 0x0000 | 0x0002 | 0x0000 | 0x0001 | 0x0000 | 0x0004 | 0x0000 | 0x0003 | + | | +-----------+----------+----------+----------+----------+----------+----------+----------+----------+ + | | | both | 0x0002 | 0x0002 | 0x0001 | 0x0001 | 0x0004 | 0x0004 | 0x0003 | 0x0003 | + | +-----------+-----------+----------+----------+----------+----------+----------+----------+----------+----------+ + | | stereo | left | 0x0001 | 0x0001 | 0x0003 | 0x0003 | 0x0005 | 0x0005 | 0x0007 | 0x0007 | + | | +-----------+----------+----------+----------+----------+----------+----------+----------+----------+ + | | | right | 0x0002 | 0x0002 | 0x0004 | 0x0004 | 0x0006 | 0x0006 | 0x0008 | 0x0008 | + | | +-----------+----------+----------+----------+----------+----------+----------+----------+----------+ + | | | both | 0x0001 | 0x0002 | 0x0003 | 0x0004 | 0x0005 | 0x0006 | 0x0007 | 0x0008 | + +----------------+-----------+-----------+----------+----------+----------+----------+----------+----------+----------+----------+ + + .. note:: + + It's similar when the data is 32-bit width, but take care when using 8-bit and 24-bit data width. For 8-bit width, the written buffer should still using ``uint16_t`` (i.e. align with 2 bytes), and only the high 8 bits will be valid, the low 8 bits are dropped, and for 24-bit width, the buffer is supposed to use ``uint32_t`` (i.e. align with 4 bytes), and only the high 24 bits valid, the low 8 bits are dropped. + + Another point is that, for the ``8-bit`` and ``16-bit`` mono mode, the real data on the line are swapped. To get the correct sequence, the writting buffer need to swap the data every two bytes. + +.. only:: esp32s2 + + +----------------+-----------+-----------+----------+----------+----------+----------+----------+----------+----------+----------+ + | data bit width | slot mode | slot mask | ws low | ws high | ws low | ws high | ws low | ws high | ws low | ws high | + +================+===========+===========+==========+==========+==========+==========+==========+==========+==========+==========+ + | | mono | left | 0x0001 | 0x0000 | 0x0002 | 0x0000 | 0x0003 | 0x0000 | 0x0004 | 0x0000 | + | 16 bit | +-----------+----------+----------+----------+----------+----------+----------+----------+----------+ + | | | right | 0x0000 | 0x0001 | 0x0000 | 0x0002 | 0x0000 | 0x0003 | 0x0000 | 0x0004 | + | | +-----------+----------+----------+----------+----------+----------+----------+----------+----------+ + | | | both | 0x0001 | 0x0001 | 0x0002 | 0x0002 | 0x0003 | 0x0003 | 0x0004 | 0x0004 | + | +-----------+-----------+----------+----------+----------+----------+----------+----------+----------+----------+ + | | stereo | left | 0x0001 | 0x0001 | 0x0003 | 0x0003 | 0x0005 | 0x0005 | 0x0007 | 0x0007 | + | | +-----------+----------+----------+----------+----------+----------+----------+----------+----------+ + | | | right | 0x0002 | 0x0002 | 0x0004 | 0x0004 | 0x0006 | 0x0006 | 0x0008 | 0x0008 | + | | +-----------+----------+----------+----------+----------+----------+----------+----------+----------+ + | | | both | 0x0001 | 0x0002 | 0x0003 | 0x0004 | 0x0005 | 0x0006 | 0x0007 | 0x0008 | + +----------------+-----------+-----------+----------+----------+----------+----------+----------+----------+----------+----------+ + + .. note:: + + Similar for 8-bit and 32-bit data width, the type of the buffer is better to be ``uint8_t`` and ``uint32_t`` type. But specially, when the data width is 24-bit, the data buffer should aligned with 3-byte(i.e. every 3 bytes stands for a 24-bit data in one slot), additionally, :cpp:member:`i2s_chan_config_t::dma_frame_num`, :cpp:member:`i2s_std_clk_config_t::mclk_multiple` and the writting buffer size should be the multiple of ``3``, otherwise the data on the line or the sample rate will be incorrect. + +.. only:: esp32c3 or esp32s3 or esp32h2 + + +----------------+-----------+-----------+----------+----------+----------+----------+----------+----------+----------+----------+ + | data bit width | slot mode | slot mask | ws low | ws high | ws low | ws high | ws low | ws high | ws low | ws high | + +================+===========+===========+==========+==========+==========+==========+==========+==========+==========+==========+ + | | mono | left | 0x0001 | 0x0000 | 0x0002 | 0x0000 | 0x0003 | 0x0000 | 0x0004 | 0x0000 | + | 16 bit | +-----------+----------+----------+----------+----------+----------+----------+----------+----------+ + | | | right | 0x0000 | 0x0001 | 0x0000 | 0x0002 | 0x0000 | 0x0003 | 0x0000 | 0x0004 | + | | +-----------+----------+----------+----------+----------+----------+----------+----------+----------+ + | | | both | 0x0001 | 0x0001 | 0x0002 | 0x0002 | 0x0003 | 0x0003 | 0x0004 | 0x0004 | + | +-----------+-----------+----------+----------+----------+----------+----------+----------+----------+----------+ + | | stereo | left | 0x0001 | 0x0001 | 0x0003 | 0x0003 | 0x0005 | 0x0005 | 0x0007 | 0x0007 | + | | +-----------+----------+----------+----------+----------+----------+----------+----------+----------+ + | | | right | 0x0002 | 0x0002 | 0x0004 | 0x0004 | 0x0006 | 0x0006 | 0x0008 | 0x0008 | + | | +-----------+----------+----------+----------+----------+----------+----------+----------+----------+ + | | | both | 0x0001 | 0x0002 | 0x0003 | 0x0004 | 0x0005 | 0x0006 | 0x0007 | 0x0008 | + +----------------+-----------+-----------+----------+----------+----------+----------+----------+----------+----------+----------+ + + .. note:: + + Similar for 8-bit and 32-bit data width, the type of the buffer is better to be ``uint8_t`` and ``uint32_t`` type. But specially, when the data width is 24-bit, the data buffer should aligned with 3-byte(i.e. every 3 bytes stands for a 24-bit data in one slot), additionally, :cpp:member:`i2s_chan_config_t::dma_frame_num`, :cpp:member:`i2s_std_clk_config_t::mclk_multiple` and the writting buffer size should be the multiple of ``3``, otherwise the data on the line or the sample rate will be incorrect. + .. code-block:: c #include "driver/i2s_std.h" @@ -336,6 +417,69 @@ And for more details, please refer to :component_file:`driver/include/driver/i2s /* If the handle is not needed any more, delete it to release the channel resources */ i2s_del_channel(tx_handle); +STD RX Mode +~~~~~~~~~~~ + +Take 16-bit data width for example, when the data on the line are: + ++--------+--------+--------+--------+--------+--------+--------+--------+--------+ +| ws low | ws high| ws low | ws high| ws low | ws high| ws low | ws high| ... | ++========+========+========+========+========+========+========+========+========+ +| 0x0001 | 0x0002 | 0x0003 | 0x0004 | 0x0005 | 0x0006 | 0x0007 | 0x0008 | ... | ++--------+--------+--------+--------+--------+--------+--------+--------+--------+ + +Here is the table of the data that received in the buffer with different :cpp:member:`i2s_std_slot_config_t::slot_mode` and :cpp:member:`i2s_std_slot_config_t::slot_mask` + +.. only:: esp32 + + +----------------+-----------+-----------+----------+----------+----------+----------+----------+----------+----------+----------+ + | data bit width | slot mode | slot mask | data 0 | data 1 | data 2 | data 3 | data 4 | data 5 | data 6 | data 7 | + +================+===========+===========+==========+==========+==========+==========+==========+==========+==========+==========+ + | | mono | left | 0x0001 | 0x0000 | 0x0005 | 0x0003 | 0x0009 | 0x0007 | 0x000d | 0x000b | + | | +-----------+----------+----------+----------+----------+----------+----------+----------+----------+ + | 16 bit | | right | 0x0002 | 0x0000 | 0x0006 | 0x0004 | 0x000a | 0x0008 | 0x000e | 0x000c | + | +-----------+-----------+----------+----------+----------+----------+----------+----------+----------+----------+ + | | stereo | any | 0x0001 | 0x0002 | 0x0003 | 0x0004 | 0x0005 | 0x0006 | 0x0007 | 0x0008 | + +----------------+-----------+-----------+----------+----------+----------+----------+----------+----------+----------+----------+ + + .. note:: + + The receive case is a little bit complicated on ESP32. + Firstly, when the data width are ``8-bit`` or ``24-bit``, the received data will still align with two bytes or four bytes, which means the valid data are put in the high 8 bits in every two bytes and high 24 bits in every four bytes. For example, the received data will be ``0x5A00`` when the data on the line is ``0x5A`` in 8-bit width, and receive ``0x0000 5A00`` if the data ``0x00 005A`` on the line. + Secondly, for ``8-bit`` and ``16-bit`` mono case, the data in buffer are swapped every two data, they may need to be swapped back manually to get the correct order. + +.. only:: esp32s2 + + +----------------+-----------+-----------+----------+----------+----------+----------+----------+----------+----------+----------+ + | data bit width | slot mode | slot mask | data 0 | data 1 | data 2 | data 3 | data 4 | data 5 | data 6 | data 7 | + +================+===========+===========+==========+==========+==========+==========+==========+==========+==========+==========+ + | | mono | left | 0x0001 | 0x0003 | 0x0005 | 0x0007 | 0x0009 | 0x000b | 0x000d | 0x000f | + | | +-----------+----------+----------+----------+----------+----------+----------+----------+----------+ + | 16 bit | | right | 0x0002 | 0x0004 | 0x0006 | 0x0008 | 0x000a | 0x000c | 0x000e | 0x0010 | + | +-----------+-----------+----------+----------+----------+----------+----------+----------+----------+----------+ + | | stereo | any | 0x0001 | 0x0002 | 0x0003 | 0x0004 | 0x0005 | 0x0006 | 0x0007 | 0x0008 | + +----------------+-----------+-----------+----------+----------+----------+----------+----------+----------+----------+----------+ + + .. note:: + + ``8-bit``, ``24-bit`` and ``32-bit`` are similar as ``16-bit``, the data bit-width in the receiving buffer are equal to the data bit-width on the line. Additionally, when using ``24-bit`` data width, :cpp:member:`i2s_chan_config_t::dma_frame_num`, :cpp:member:`i2s_std_clk_config_t::mclk_multiple` and the receiving buffer size should be the multiple of ``3``, otherwise the data on the line or the sample rate will be incorrect. + +.. only:: esp32c3 or esp32s3 or esp32h2 + + +----------------+-----------+-----------+----------+----------+----------+----------+----------+----------+----------+----------+ + | data bit width | slot mode | slot mask | data 0 | data 1 | data 2 | data 3 | data 4 | data 5 | data 6 | data 7 | + +================+===========+===========+==========+==========+==========+==========+==========+==========+==========+==========+ + | | mono | left | 0x0001 | 0x0003 | 0x0005 | 0x0007 | 0x0009 | 0x000b | 0x000d | 0x000f | + | | +-----------+----------+----------+----------+----------+----------+----------+----------+----------+ + | 16 bit | | right | 0x0002 | 0x0004 | 0x0006 | 0x0008 | 0x000a | 0x000c | 0x000e | 0x0010 | + | +-----------+-----------+----------+----------+----------+----------+----------+----------+----------+----------+ + | | stereo | any | 0x0001 | 0x0002 | 0x0003 | 0x0004 | 0x0005 | 0x0006 | 0x0007 | 0x0008 | + +----------------+-----------+-----------+----------+----------+----------+----------+----------+----------+----------+----------+ + + .. note:: + + ``8-bit``, ``24-bit`` and ``32-bit`` are similar as ``16-bit``, the data bit-width in the receiving buffer are equal to the data bit-width on the line. Additionally, when using ``24-bit`` data width, :cpp:member:`i2s_chan_config_t::dma_frame_num`, :cpp:member:`i2s_std_clk_config_t::mclk_multiple` and the receiving buffer size should be the multiple of ``3``, otherwise the data on the line or the sample rate will be incorrect. + .. code-block:: c #include "driver/i2s_std.h" @@ -397,6 +541,61 @@ And for more details, please refer to :component_file:`driver/include/driver/i2s Please refer to :ref:`i2s-api-reference-i2s_pdm` for PDM TX API information. And for more details, please refer to :component_file:`driver/include/driver/i2s_pdm.h`. + The PDM data width is fixed to 16-bit, when the data in a ``int16_t`` writting buffer are: + + +--------+--------+--------+--------+--------+--------+--------+--------+--------+ + | data 0 | data 1 | data 2 | data 3 | data 4 | data 5 | data 6 | data 7 | ... | + +========+========+========+========+========+========+========+========+========+ + | 0x0001 | 0x0002 | 0x0003 | 0x0004 | 0x0005 | 0x0006 | 0x0007 | 0x0008 | ... | + +--------+--------+--------+--------+--------+--------+--------+--------+--------+ + + .. only:: esp32 + + Here is the table of the real data on the line with different :cpp:member:`i2s_pdm_tx_slot_config_t::slot_mode` and :cpp:member:`i2s_pdm_tx_slot_config_t::slot_mask` (The PDM format on the line is transferred to PCM format for better comprehension). + + +-----------+-----------+----------+----------+----------+----------+----------+----------+----------+----------+ + | slot mode | slot mask | left | right | left | right | left | right | left | right | + +===========+===========+==========+==========+==========+==========+==========+==========+==========+==========+ + | mono | left | 0x0001 | 0x0000 | 0x0002 | 0x0000 | 0x0003 | 0x0000 | 0x0004 | 0x0000 | + | +-----------+----------+----------+----------+----------+----------+----------+----------+----------+ + | | right | 0x0000 | 0x0001 | 0x0000 | 0x0002 | 0x0000 | 0x0003 | 0x0000 | 0x0004 | + | +-----------+----------+----------+----------+----------+----------+----------+----------+----------+ + | | both | 0x0001 | 0x0001 | 0x0002 | 0x0002 | 0x0003 | 0x0003 | 0x0004 | 0x0004 | + +-----------+-----------+----------+----------+----------+----------+----------+----------+----------+----------+ + | stereo | left | 0x0001 | 0x0001 | 0x0003 | 0x0003 | 0x0005 | 0x0005 | 0x0007 | 0x0007 | + | +-----------+----------+----------+----------+----------+----------+----------+----------+----------+ + | | right | 0x0002 | 0x0002 | 0x0004 | 0x0004 | 0x0006 | 0x0006 | 0x0008 | 0x0008 | + | +-----------+----------+----------+----------+----------+----------+----------+----------+----------+ + | | both | 0x0001 | 0x0002 | 0x0003 | 0x0004 | 0x0005 | 0x0006 | 0x0007 | 0x0008 | + +-----------+-----------+----------+----------+----------+----------+----------+----------+----------+----------+ + + .. only:: esp32c3 or esp32s3 or esp32h2 + + Here is the table of the real data on the line with different :cpp:member:`i2s_pdm_tx_slot_config_t::slot_mode` and :cpp:member:`i2s_pdm_tx_slot_config_t::line_mode` (The PDM format on the line is transferred to PCM format for easier comprehension). + + +----------------+-----------+------+--------+--------+--------+--------+--------+--------+--------+--------+ + | line mode | slot mode | line | left | right | left | right | left | right | left | right | + +================+===========+======+========+========+========+========+========+========+========+========+ + | | mono | dout | 0x0001 | 0x0000 | 0x0002 | 0x0000 | 0x0003 | 0x0000 | 0x0004 | 0x0000 | + | one-line codec +-----------+------+--------+--------+--------+--------+--------+--------+--------+--------+ + | | stereo | dout | 0x0001 | 0x0002 | 0x0003 | 0x0004 | 0x0005 | 0x0006 | 0x0007 | 0x0008 | + +----------------+-----------+------+--------+--------+--------+--------+--------+--------+--------+--------+ + | one-line dac | mono | dout | 0x0001 | 0x0001 | 0x0002 | 0x0002 | 0x0003 | 0x0003 | 0x0004 | 0x0004 | + +----------------+-----------+------+--------+--------+--------+--------+--------+--------+--------+--------+ + | | mono | dout | 0x0002 | 0x0002 | 0x0004 | 0x0004 | 0x0006 | 0x0006 | 0x0008 | 0x0008 | + | | +------+--------+--------+--------+--------+--------+--------+--------+--------+ + | | | dout2| 0x0000 | 0x0000 | 0x0000 | 0x0000 | 0x0000 | 0x0000 | 0x0000 | 0x0000 | + | two-line dac +-----------+------+--------+--------+--------+--------+--------+--------+--------+--------+ + | | stereo | dout | 0x0002 | 0x0002 | 0x0004 | 0x0004 | 0x0006 | 0x0006 | 0x0008 | 0x0008 | + | | +------+--------+--------+--------+--------+--------+--------+--------+--------+ + | | | dout2| 0x0001 | 0x0001 | 0x0003 | 0x0003 | 0x0005 | 0x0005 | 0x0007 | 0x0007 | + +----------------+-----------+------+--------+--------+--------+--------+--------+--------+--------+--------+ + + .. note:: + + There are three line modes for PDM TX mode, they are ``I2S_PDM_TX_ONE_LINE_CODEC``, ``I2S_PDM_TX_ONE_LINE_DAC`` and ``I2S_PDM_TX_TWO_LINE_DAC``. One-line codec is for the PDM codecs those require clock signal, the PDM codec can differentiate the left and right slots by the clock level, and the other two are used to driver power amplifiers directly with a low-pass filter, they do not need the clock signal, so there are two lines to differentiate the left and right slots. Additionally, for the mono mode of one-line codec, the slot can be force to change to the right by setting the clock invert flag in gpio configuration. + + .. code-block:: c #include "driver/i2s_pdm.h" @@ -439,6 +638,44 @@ And for more details, please refer to :component_file:`driver/include/driver/i2s Please refer to :ref:`i2s-api-reference-i2s_pdm` for PDM RX API information. And for more details, please refer to :component_file:`driver/include/driver/i2s_pdm.h`. + The PDM data width is fixed to 16-bit, when the data on the line (The PDM format on the line is transferred to PCM format for easier comprehension) are: + + +--------+--------+--------+--------+--------+--------+--------+--------+--------+ + | left | right | left | right | left | right | left | right | ... | + +========+========+========+========+========+========+========+========+========+ + | 0x0001 | 0x0002 | 0x0003 | 0x0004 | 0x0005 | 0x0006 | 0x0007 | 0x0008 | ... | + +--------+--------+--------+--------+--------+--------+--------+--------+--------+ + + Here is the table of the data that received in a 'int16_t' buffer with different :cpp:member:`i2s_pdm_rx_slot_config_t::slot_mode` and :cpp:member:`i2s_pdm_rx_slot_config_t::slot_mask` + + .. only:: esp32 + + +-----------+-----------+----------+----------+----------+----------+----------+----------+----------+----------+ + | slot mode | slot mask | data 0 | data 1 | data 2 | data 3 | data 4 | data 5 | data 6 | data 7 | + +===========+===========+==========+==========+==========+==========+==========+==========+==========+==========+ + | mono | left | 0x0001 | 0x0003 | 0x0005 | 0x0007 | 0x0009 | 0x000b | 0x000d | 0x000f | + | +-----------+----------+----------+----------+----------+----------+----------+----------+----------+ + | | right | 0x0002 | 0x0004 | 0x0006 | 0x0008 | 0x000a | 0x000c | 0x000e | 0x0010 | + +-----------+-----------+----------+----------+----------+----------+----------+----------+----------+----------+ + | stereo | both | 0x0001 | 0x0002 | 0x0003 | 0x0004 | 0x0005 | 0x0006 | 0x0007 | 0x0008 | + +-----------+-----------+----------+----------+----------+----------+----------+----------+----------+----------+ + + .. only:: esp32s3 + + +-----------+-----------+----------+----------+----------+----------+----------+----------+----------+----------+ + | slot mode | slot mask | data 0 | data 1 | data 2 | data 3 | data 4 | data 5 | data 6 | data 7 | + +===========+===========+==========+==========+==========+==========+==========+==========+==========+==========+ + | mono | left | 0x0001 | 0x0003 | 0x0005 | 0x0007 | 0x0009 | 0x000b | 0x000d | 0x000f | + | +-----------+----------+----------+----------+----------+----------+----------+----------+----------+ + | | right | 0x0002 | 0x0004 | 0x0006 | 0x0008 | 0x000a | 0x000c | 0x000e | 0x0010 | + +-----------+-----------+----------+----------+----------+----------+----------+----------+----------+----------+ + | stereo | both | 0x0002 | 0x0001 | 0x0004 | 0x0003 | 0x0006 | 0x0005 | 0x0008 | 0x0007 | + +-----------+-----------+----------+----------+----------+----------+----------+----------+----------+----------+ + + .. note:: + + The right slot is received first in stereo mode. To switch the left and right slot in the buffer, please set the :cpp:member:`i2s_pdm_rx_gpio_config_t::invert_flags::clk_inv` to force invert the clock signal. + .. code-block:: c #include "driver/i2s_pdm.h" @@ -486,6 +723,9 @@ And for more details, please refer to :component_file:`driver/include/driver/i2s Please refer to :ref:`i2s-api-reference-i2s_tdm` for TDM API information. And for more details, please refer to :component_file:`driver/include/driver/i2s_tdm.h`. + TDM TX Mode + ~~~~~~~~~~~ + .. code-block:: c #include "driver/i2s_tdm.h" @@ -517,6 +757,9 @@ And for more details, please refer to :component_file:`driver/include/driver/i2s ... + TDM RX Mode + ~~~~~~~~~~~ + .. code-block:: c #include "driver/i2s_tdm.h" diff --git a/examples/peripherals/.build-test-rules.yml b/examples/peripherals/.build-test-rules.yml index 2211b1f480..134372ba28 100644 --- a/examples/peripherals/.build-test-rules.yml +++ b/examples/peripherals/.build-test-rules.yml @@ -34,24 +34,30 @@ examples/peripherals/i2c/i2c_tools: temporary: true reason: lack of runners -examples/peripherals/i2s: - disable: - - if: SOC_I2S_SUPPORTED != 1 - examples/peripherals/i2s/i2s_adc_dac: disable: - if: SOC_I2S_SUPPORTS_ADC_DAC != 1 -examples/peripherals/i2s/i2s_audio_recorder_sdcard: - enable: - - if: IDF_TARGET == "esp32" or IDF_TARGET == "esp32s3" - temporary: true - reason: the other targets are not tested yet +examples/peripherals/i2s/i2s_basic/i2s_pdm: + disable: + - if: SOC_I2S_SUPPORTS_PDM != 1 -examples/peripherals/i2s/i2s_basic: +examples/peripherals/i2s/i2s_basic/i2s_std: disable: - if: SOC_I2S_SUPPORTED != 1 +examples/peripherals/i2s/i2s_basic/i2s_tdm: + disable: + - if: SOC_I2S_SUPPORTS_TDM != 1 + +examples/peripherals/i2s/i2s_codec/i2s_es8311: + disable: + - if: SOC_I2S_SUPPORTED != 1 + +examples/peripherals/i2s/i2s_recorder: + disable: + - if: SOC_I2S_SUPPORTS_PDM_RX != 1 + examples/peripherals/lcd/i80_controller: disable: - if: SOC_LCD_I80_SUPPORTED != 1 diff --git a/examples/peripherals/i2s/i2s_basic/i2s_pdm/README.md b/examples/peripherals/i2s/i2s_basic/i2s_pdm/README.md index b0d1f75dc9..bcb7f73f00 100644 --- a/examples/peripherals/i2s/i2s_basic/i2s_pdm/README.md +++ b/examples/peripherals/i2s/i2s_basic/i2s_pdm/README.md @@ -1,7 +1,7 @@ -| Supported Targets | ESP32 | ESP32-C3 | ESP32-S3 | ESP32-H2 | -| ----------------- | ----- | -------- | -------- | -------- | +| Supported Targets | ESP32 | ESP32-C3 | ESP32-S3 | +| ----------------- | ----- | -------- | -------- | -# I2S Basic Standard Mode Example +# I2S Basic PDM Mode Example (See the README.md file in the upper level 'examples' directory for more information about examples.) @@ -18,68 +18,72 @@ This example is going to show how to use the PDM TX and RX mode. #### PDM RX -* A PDM microphone whose `sel` pin is supposed to be pulled down, and connecting its `clk` pin to `GPIO_NUM_4`, `data` pin to `GPIO_NUM_5`. +* A PDM microphone whose `sel` pin is supposed to be pulled down, and connecting its `clk` pin to `EXAMPLE_PDM_RX_CLK_IO`, `data` pin to `EXAMPLE_PDM_RX_DIN_IO`. ``` -┌─────────────┐ ┌──────────────────┐ -│ ESP │ │ PDM microphone │ -│ │ PDM clock │ │ -│ GPIO 0 ├──────────────►│ CLK │ -│ │ PDM data │ │ -│ GPIO 2 │◄──────────────┤ DATA │ -│ │ │ │ -│ │ ┌─────┤ SEL │ -│ │ │ │ │ -│ GND ├─────────┴─────┤ GND │ -│ │ │ │ -│ VCC ├───────────────┤ VCC │ -└─────────────┘ └──────────────────┘ +┌───────────────────────┐ ┌──────────────────┐ +│ ESP │ │ PDM microphone │ +│ │ PDM clock │ │ +│ EXAMPLE_PDM_RX_CLK_IO ├──────────────►│ CLK │ +│ │ PDM data │ │ +│ EXAMPLE_PDM_RX_DIN_IO │◄──────────────┤ DATA │ +│ │ │ │ +│ │ ┌─────┤ SEL │ +│ │ │ │ │ +│ GND ├─────────┴─────┤ GND │ +│ │ │ │ +│ VCC ├───────────────┤ VCC │ +└───────────────────────┘ └──────────────────┘ ``` #### PDM TX * An earphone or a speaker -* An audio power amplifier that can input PDM signal. If the power amplifier can only receive the analog signal without PDM clock, a band-pass filter is required to restore the PDM data wave into analog signal, before it is transmitted to the power amplifier. +* An audio power amplifier that can input PDM signal. If the power amplifier can only receive the analog signal without PDM clock, a low-pass passive or active filter is required to restore the PDM data wave into analog signal, before it is transmitted to the power amplifier. **MAX98358** +Please refer to the [Datasheet of MAX98358](https://datasheets.maximintegrated.com/en/ds/MAX98358.pdf) for more details. + ``` -┌─────────────┐ ┌───────────────┐ -│ ESP │ │ MAX 98358 │ -│ │ PDM clock │ │ -│ GPIO 4 ├──────────────►│ CLK │ ┌─────────┐ -│ │ PDM data │ │ │ Speaker │ -│ GPIO 5 ├──────────────►│ DATA OUTP ├───┤ │ -│ │ │ │ │ │ -│ │ ┌─────┤ SD_MODE OUTN ├───┤ │ -│ │ │ │ │ │ │ -│ VCC ├─────────┴─────┤ VCC │ └─────────┘ -│ │ │ │ -│ GND ├───────────────┤ GND │ -└─────────────┘ └───────────────┘ +┌────────────────────────┐ ┌───────────────┐ +│ ESP │ │ MAX 98358 │ +│ │ PDM clock │ │ +│ EXAMPLE_PDM_TX_CLK_IO ├──────────────►│ CLK │ ┌─────────┐ +│ │ PDM data │ │ │ Speaker │ +│ EXAMPLE_PDM_TX_DOUT_IO ├──────────────►│ DATA OUTP ├───┤ │ +│ │ │ │ │ │ +│ │ ┌─────┤ SD_MODE OUTN ├───┤ │ +│ │ │ │ │ │ │ +│ VCC ├─────────┴─────┤ VCC │ └─────────┘ +│ │ │ │ +│ GND ├───────────────┤ GND │ +└────────────────────────┘ └───────────────┘ ``` **NS4150** +Please refer to the [Datasheet of NS4150](http://www.nsiway.com.cn/product/44.html) for more details. + ``` -┌─────────────┐ ┌───────────────┐ -│ ESP │ │ NS 4150 │ -│ │ │ │ -│ GPIO 4 │ │ INN │ ┌─────────┐ -│ │PDM data┌────────────────┐ │ │ │ Speaker │ -│ GPIO 5 ├────────┤Band-pass Filter├───►│ INP VoP ├───┤ │ -│ │ └────────────────┘ │ │ │ │ -│ │ ┌───┤ CTRL VoN ├───┤ │ -│ │ │ │ │ │ │ -│ VCC ├──────────────────────────┴───┤ VCC │ └─────────┘ -│ │ │ │ -│ GND ├──────────────────────────────┤ GND │ -└─────────────┘ └───────────────┘ +┌────────────────────────┐ ┌───────────────┐ +│ ESP │ │ NS 4150 │ +│ │ │ │ +│ EXAMPLE_PDM_TX_CLK_IO │(No need to connect) │ INN │ ┌─────────┐ +│ │PDM data┌────────────────┐ │ │ │ Speaker │ +│ EXAMPLE_PDM_TX_DOUT_IO ├────────┤ Low-pass Filter├───►│ INP VoP ├───┤ │ +│ │ └────────────────┘ │ │ │ │ +│ │ ┌───┤ CTRL VoN ├───┤ │ +│ │ │ │ │ │ │ +│ VCC ├──────────────────────────┴───┤ VCC │ └─────────┘ +│ │ │ │ +│ GND ├──────────────────────────────┤ GND │ +└────────────────────────┘ └───────────────┘ ``` ### Configure the Project -PDM can only works in simplex mode, setting the macro `EXAMPLE_PDM_DIR` to `EXAMPLE_PDM_TX` or `EXAMPLE_PDM_RX` can choose the PDM direction of this example. But currently ESP32-C3 does not support PDM RX mode. +PDM can only works in simplex mode, you can select the PDM direction in the menu config, or just setting the macro `EXAMPLE_PDM_DIR` directly. Setting it to `EXAMPLE_PDM_TX` or `EXAMPLE_PDM_RX` can choose the PDM direction of this example. But currently ESP32-C3 does not support PDM RX mode. ### Build and Flash @@ -113,7 +117,7 @@ Playing treble `twinkle twinkle little star` ... ``` -You can hear the audio 'twinkle twinkle little star' in three tones if you connected a speaker.on it. +You can hear the audio 'twinkle twinkle little star' in three tones if you connect a speaker to it. ### PDM RX @@ -141,7 +145,7 @@ Read Task: i2s read 2048 bytes [4] -30935 [5] -30935 [6] -30935 [7] -30935 ``` -And only if you connect a PDM microphone, you can see the data is change: +And only if you connect a PDM microphone, you can see the data changes: ``` I2S PDM RX example start diff --git a/examples/peripherals/i2s/i2s_basic/i2s_pdm/main/Kconfig.projbuild b/examples/peripherals/i2s/i2s_basic/i2s_pdm/main/Kconfig.projbuild new file mode 100644 index 0000000000..3b12ee07aa --- /dev/null +++ b/examples/peripherals/i2s/i2s_basic/i2s_pdm/main/Kconfig.projbuild @@ -0,0 +1,22 @@ +menu "I2S PDM Example Configuration" + + choice EXAMPLE_PDM_DIR + prompt "I2S PDM direction" + default EXAMPLE_PDM_TX + help + Select example PDM direction + + config EXAMPLE_PDM_TX + bool "PDM TX" + help + PDM TX example will play 'twinkle twinkle little star' in three tones. + + config EXAMPLE_PDM_RX + bool "PDM RX" + # ESP32-C3 not support PDM RX for now, its hardware does not fully supported PDM RX mode + depends on !IDF_TARGET_ESP32C3 + help + PDM RX example will show the received data from a PDM microphone. + endchoice + +endmenu diff --git a/examples/peripherals/i2s/i2s_basic/i2s_pdm/main/i2s_pdm_example.h b/examples/peripherals/i2s/i2s_basic/i2s_pdm/main/i2s_pdm_example.h index e8251153b8..bce50660e3 100644 --- a/examples/peripherals/i2s/i2s_basic/i2s_pdm/main/i2s_pdm_example.h +++ b/examples/peripherals/i2s/i2s_basic/i2s_pdm/main/i2s_pdm_example.h @@ -6,6 +6,10 @@ #pragma once +#ifdef __cplusplus +extern "C" { +#endif + #define EXAMPLE_BUFF_SIZE 2048 /** @@ -21,3 +25,7 @@ void i2s_example_pdm_tx_task(void *args); * @param args The user data given from task creating, not used in this example */ void i2s_example_pdm_rx_task(void *args); + +#ifdef __cplusplus +} +#endif diff --git a/examples/peripherals/i2s/i2s_basic/i2s_pdm/main/i2s_pdm_example_main.c b/examples/peripherals/i2s/i2s_basic/i2s_pdm/main/i2s_pdm_example_main.c index 1fd7c0e529..e8395ee57a 100644 --- a/examples/peripherals/i2s/i2s_basic/i2s_pdm/main/i2s_pdm_example_main.c +++ b/examples/peripherals/i2s/i2s_basic/i2s_pdm/main/i2s_pdm_example_main.c @@ -11,20 +11,12 @@ #include "sdkconfig.h" #include "i2s_pdm_example.h" -#define EXAMPLE_PDM_TX 0 -/* ESP32-C3 does not support PDM RX currently */ -#if !CONFIG_IDF_TARGET_ESP32C3 -#define EXAMPLE_PDM_RX 1 -#endif - -#define EXAMPLE_PDM_DIR EXAMPLE_PDM_TX - void app_main(void) { -#if EXAMPLE_PDM_DIR == EXAMPLE_PDM_TX +#if CONFIG_EXAMPLE_PDM_TX printf("I2S PDM TX example start\n---------------------------\n"); xTaskCreate(i2s_example_pdm_tx_task, "i2s_example_pdm_tx_task", 4096, NULL, 5, NULL); -#else +#elif CONFIG_EXAMPLE_PDM_RX printf("I2S PDM RX example start\n---------------------------\n"); xTaskCreate(i2s_example_pdm_rx_task, "i2s_example_pdm_rx_task", 4096, NULL, 5, NULL); #endif diff --git a/examples/peripherals/i2s/i2s_basic/i2s_pdm/main/i2s_pdm_rx.c b/examples/peripherals/i2s/i2s_basic/i2s_pdm/main/i2s_pdm_rx.c index adde3cedd1..6b758b347f 100644 --- a/examples/peripherals/i2s/i2s_basic/i2s_pdm/main/i2s_pdm_rx.c +++ b/examples/peripherals/i2s/i2s_basic/i2s_pdm/main/i2s_pdm_rx.c @@ -9,23 +9,20 @@ #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "driver/i2s_pdm.h" -#include "driver/i2s_std.h" #include "driver/gpio.h" -#include "esp_check.h" +#include "esp_err.h" #include "sdkconfig.h" #include "i2s_pdm_example.h" -#include "hal/i2s_ll.h" - #define EXAMPLE_PDM_RX_CLK_IO GPIO_NUM_0 // I2S PDM RX clock io number #define EXAMPLE_PDM_RX_DIN_IO GPIO_NUM_2 // I2S PDM RX data in io number #define EXAMPLE_PDM_RX_FREQ_HZ 16000 // I2S PDM RX frequency -static i2s_chan_handle_t rx_chan; // I2S rx channel handler -static void i2s_example_init_pdm_rx(void) +static i2s_chan_handle_t i2s_example_init_pdm_rx(void) { + i2s_chan_handle_t rx_chan; // I2S rx channel handler /* Setp 1: Determine the I2S channel configuration and allocate RX channel only * The default configuration can be generated by the helper macro, * but note that PDM channel can only be registered on I2S_NUM_0 */ @@ -52,6 +49,7 @@ static void i2s_example_init_pdm_rx(void) /* Step 3: Enable the rx channels before reading data */ ESP_ERROR_CHECK(i2s_channel_enable(rx_chan)); + return rx_chan; } @@ -59,7 +57,7 @@ void i2s_example_pdm_rx_task(void *args) { int16_t *r_buf = (int16_t *)calloc(1, EXAMPLE_BUFF_SIZE); assert(r_buf); - i2s_example_init_pdm_rx(); + i2s_chan_handle_t rx_chan = i2s_example_init_pdm_rx(); size_t r_bytes = 0; while (1) { diff --git a/examples/peripherals/i2s/i2s_basic/i2s_pdm/main/i2s_pdm_tx.c b/examples/peripherals/i2s/i2s_basic/i2s_pdm/main/i2s_pdm_tx.c index 390e89dc01..1da7040b4e 100644 --- a/examples/peripherals/i2s/i2s_basic/i2s_pdm/main/i2s_pdm_tx.c +++ b/examples/peripherals/i2s/i2s_basic/i2s_pdm/main/i2s_pdm_tx.c @@ -24,21 +24,23 @@ #define EXAMPLE_TONE_LAST_TIME_MS 500 #define EXAMPLE_BYTE_NUM_EVERY_TONE (EXAMPLE_TONE_LAST_TIME_MS * EXAMPLE_PDM_TX_FREQ_HZ / 1000) -static i2s_chan_handle_t tx_chan; // I2S tx channel handler - -static const uint32_t tone[3][7] = {{262, 294, 330, 349, 392, 440, 494}, - {523, 587, 659, 698, 784, 880, 988}, - {1046, 1175, 1318, 1397, 1568, 1760, 1976}}; // The frequency of tones: do, re, mi, fa, so, la, si, in Hz. +/* The frequency of tones: do, re, mi, fa, so, la, si, in Hz. */ +static const uint32_t tone[3][7] = {{262, 294, 330, 349, 392, 440, 494}, // bass + {523, 587, 659, 698, 784, 880, 988}, // alto + {1046, 1175, 1318, 1397, 1568, 1760, 1976}}; // treble +/* Numbered musical notation of 'twinkle twinkle little star' */ static const uint8_t song[28] = {1, 1, 5, 5, 6, 6, 5, 4, 4, 3, 3, 2, 2, 1, 5, 5, 4, 4, 3, 3, 2, - 5, 5, 4, 4, 3, 3, 2}; // Numbered musical notation of 'twinkle twinkle little star' -static const uint8_t rhythm[7] = {1, 1, 1, 1, 1, 1, 2}; // Rhythm of 'twinkle twinkle little star', it's repeated in four sections + 5, 5, 4, 4, 3, 3, 2}; +/* Rhythm of 'twinkle twinkle little star', it's repeated in four sections */ +static const uint8_t rhythm[7] = {1, 1, 1, 1, 1, 1, 2}; static const char *tone_name[3] = {"bass", "alto", "treble"}; -void i2s_example_init_pdm_tx(void) +static i2s_chan_handle_t i2s_example_init_pdm_tx(void) { + i2s_chan_handle_t tx_chan; // I2S tx channel handler /* Setp 1: Determine the I2S channel configuration and allocate TX channel only * The default configuration can be generated by the helper macro, * it only requires the I2S controller id and I2S role, @@ -67,13 +69,15 @@ void i2s_example_init_pdm_tx(void) /* Step 3: Enable the tx channel before writing data */ ESP_ERROR_CHECK(i2s_channel_enable(tx_chan)); + + return tx_chan; } void i2s_example_pdm_tx_task(void *args) { int16_t *w_buf = (int16_t *)calloc(1, EXAMPLE_BUFF_SIZE); assert(w_buf); - i2s_example_init_pdm_tx(); + i2s_chan_handle_t tx_chan = i2s_example_init_pdm_tx(); size_t w_bytes = 0; diff --git a/examples/peripherals/i2s/i2s_basic/i2s_pdm/pytest_i2s_pdm.py b/examples/peripherals/i2s/i2s_basic/i2s_pdm/pytest_i2s_pdm.py index a23c37fa88..c31210811f 100644 --- a/examples/peripherals/i2s/i2s_basic/i2s_pdm/pytest_i2s_pdm.py +++ b/examples/peripherals/i2s/i2s_basic/i2s_pdm/pytest_i2s_pdm.py @@ -9,7 +9,12 @@ from pytest_embedded import Dut @pytest.mark.esp32s3 @pytest.mark.esp32c3 @pytest.mark.generic -def test_i2s_pdm_example(dut: Dut) -> None: +@pytest.mark.parametrize( + 'config', + ['pdm_tx'], + indirect=True +) +def test_i2s_pdm_tx_example(dut: Dut) -> None: dut.expect(r'I2S PDM TX example start', timeout=5) dut.expect(r'---------------------------', timeout=5) dut.expect(r'D \(([0-9]+)\) i2s_common: tx channel is registered on I2S0 successfully', timeout=5) @@ -20,3 +25,24 @@ def test_i2s_pdm_example(dut: Dut) -> None: dut.expect(r'D \(([0-9]+)\) i2s_pdm: The tx channel on I2S0 has been initialized to PDM TX mode successfully', timeout=5) dut.expect(r'D \(([0-9]+)\) i2s_common: i2s tx channel enabled', timeout=5) dut.expect(r'Playing bass `twinkle twinkle little star`', timeout=5) + + +@pytest.mark.esp32 +@pytest.mark.esp32s3 +@pytest.mark.generic +@pytest.mark.parametrize( + 'config', + ['pdm_rx'], + indirect=True +) +def test_i2s_pdm_rx_example(dut: Dut) -> None: + dut.expect(r'I2S PDM RX example start', timeout=5) + dut.expect(r'---------------------------', timeout=5) + dut.expect(r'D \(([0-9]+)\) i2s_common: rx channel is registered on I2S0 successfully', timeout=5) + dut.expect(r'D \(([0-9]+)\) i2s_common: DMA malloc info: dma_desc_num = ([0-9]+), ' + r'dma_desc_buf_size = dma_frame_num \* slot_num \* data_bit_width = ([0-9]+)', timeout=5) + dut.expect(r'D \(([0-9]+)\) i2s_common: i2s rx channel enabled', timeout=5) + dut.expect(r'Read Task: i2s read ([0-9]+) bytes', timeout=5) + dut.expect(r'-----------------------------------', timeout=5) + dut.expect(r'\[0\] ([-]?[0-9]+) \[1\] ([-]?[0-9]+) \[2\] ([-]?[0-9]+) \[3\] ([-]?[0-9]+)', timeout=5) + dut.expect(r'\[4\] ([-]?[0-9]+) \[5\] ([-]?[0-9]+) \[6\] ([-]?[0-9]+) \[7\] ([-]?[0-9]+)', timeout=5) diff --git a/examples/peripherals/i2s/i2s_basic/i2s_pdm/sdkconfig.ci.pdm_rx b/examples/peripherals/i2s/i2s_basic/i2s_pdm/sdkconfig.ci.pdm_rx new file mode 100644 index 0000000000..506a1c855b --- /dev/null +++ b/examples/peripherals/i2s/i2s_basic/i2s_pdm/sdkconfig.ci.pdm_rx @@ -0,0 +1 @@ +CONFIG_EXAMPLE_PDM_RX=y diff --git a/examples/peripherals/i2s/i2s_basic/i2s_pdm/sdkconfig.ci.pdm_tx b/examples/peripherals/i2s/i2s_basic/i2s_pdm/sdkconfig.ci.pdm_tx new file mode 100644 index 0000000000..5580054ca9 --- /dev/null +++ b/examples/peripherals/i2s/i2s_basic/i2s_pdm/sdkconfig.ci.pdm_tx @@ -0,0 +1 @@ +CONFIG_EXAMPLE_PDM_TX=y diff --git a/examples/peripherals/i2s/i2s_basic/i2s_std/README.md b/examples/peripherals/i2s/i2s_basic/i2s_std/README.md index fe5d4ed44e..552b4697d6 100644 --- a/examples/peripherals/i2s/i2s_basic/i2s_std/README.md +++ b/examples/peripherals/i2s/i2s_basic/i2s_std/README.md @@ -1,5 +1,5 @@ -| Supported Targets | ESP32 | ESP32-S2 | ESP32-C3 | ESP32-S3 | ESP32-H2 | -| ----------------- | ----- | -------- | -------- | -------- | -------- | +| Supported Targets | ESP32 | ESP32-C3 | ESP32-S2 | ESP32-S3 | +| ----------------- | ----- | -------- | -------- | -------- | # I2S Basic Standard Mode Example @@ -16,7 +16,7 @@ This example is going to show how to use the standard mode in simplex mode or fu ### Configure the Project -There are simplex mode and duplex mode can be chosen in this example, setting `EXAMPLE_I2S_DUPLEX_MODE` to `0` will adopt the simplex mode, otherwise it will adopt the full-duplex mode. +There are simplex mode and duplex mode can be chosen in this example, you can choose the mode in the menuconfig or just setting `EXAMPLE_I2S_DUPLEX_MODE` derectly, when `EXAMPLE_I2S_DUPLEX_MODE` is `0` the example will adopt the simplex mode, otherwise it will adopt the full-duplex mode. Note that ESP32-S2 simplex mode is not available because this example requires both TX & RX channels, however, ESP32-S2 has only one I2S controller and the simplex TX & RX channels can't coexist in a same controller. By the way, the simplex TX & RX channels can't coexist on ESP32 as well, but ESP32 has two I2S controllers so the simplex TX & RX channels can be registered on the different I2S controllers. diff --git a/examples/peripherals/i2s/i2s_basic/i2s_std/main/Kconfig.projbuild b/examples/peripherals/i2s/i2s_basic/i2s_std/main/Kconfig.projbuild new file mode 100644 index 0000000000..fd28420085 --- /dev/null +++ b/examples/peripherals/i2s/i2s_basic/i2s_std/main/Kconfig.projbuild @@ -0,0 +1,21 @@ +menu "I2S STD Example Configuration" + + choice DUPLEX_MODE + prompt "I2S STD duplex/simplex select" + default USE_DUPLEX + help + Select duplex mode or simplex mode for the example + + config USE_DUPLEX + bool "Duplex TX and RX channels" + help + Allocate TX and RX channels on a same I2S controller in duplex mode, sharing the BCLK and WS signal + + config USE_SIMPLEX + bool "Simplex TX and RX channels" + depends on !IDF_TARGET_ESP32S2 + help + Allocate TX and RX channels in duplex mode, they are totally separate. + endchoice + +endmenu diff --git a/examples/peripherals/i2s/i2s_basic/i2s_std/main/i2s_std_example_main.c b/examples/peripherals/i2s/i2s_basic/i2s_std/main/i2s_std_example_main.c index f759b2740e..b46b41fff9 100644 --- a/examples/peripherals/i2s/i2s_basic/i2s_std/main/i2s_std_example_main.c +++ b/examples/peripherals/i2s/i2s_basic/i2s_std/main/i2s_std_example_main.c @@ -17,7 +17,7 @@ * Set 0 to allocate rx & tx channels in simplex mode, these two channels will be totally separated, * Specifically, due to the hardware limitation, the simplex rx & tx channels can't be registered on the same controllers on ESP32 and ESP32-S2, * and ESP32-S2 has only one I2S controller, so it can't allocate two simplex channels */ -#define EXAMPLE_I2S_DUPLEX_MODE (1 || CONFIG_IDF_TARGET_ESP32S2) +#define EXAMPLE_I2S_DUPLEX_MODE CONFIG_USE_DUPLEX #if CONFIG_IDF_TARGET_ESP32 #define EXAMPLE_STD_BCLK_IO1 GPIO_NUM_4 // I2S bit clock io number @@ -187,7 +187,7 @@ static void i2s_example_init_std_simplex(void) }; /* Default is only receiving left slot in mono mode, * update to right here to show how to change the default configuration */ - rx_std_cfg.slot_cfg.slot_mask = I2S_STD_SLOT_ONLY_RIGHT; + rx_std_cfg.slot_cfg.slot_mask = I2S_STD_SLOT_RIGHT; ESP_ERROR_CHECK(i2s_channel_init_std_mode(rx_chan, &rx_std_cfg)); } #endif diff --git a/examples/peripherals/i2s/i2s_basic/i2s_tdm/README.md b/examples/peripherals/i2s/i2s_basic/i2s_tdm/README.md index 467f4951ce..a51e0e4d26 100644 --- a/examples/peripherals/i2s/i2s_basic/i2s_tdm/README.md +++ b/examples/peripherals/i2s/i2s_basic/i2s_tdm/README.md @@ -1,5 +1,5 @@ -| Supported Targets | ESP32-C3 | ESP32-S3 | ESP32-H2 | -| ----------------- | -------- | -------- | -------- | +| Supported Targets | ESP32-C3 | ESP32-S3 | +| ----------------- | -------- | -------- | # I2S Basic TDM Mode Example diff --git a/examples/peripherals/i2s/i2s_codec/i2s_es8311/README.md b/examples/peripherals/i2s/i2s_codec/i2s_es8311/README.md index 426b8ac197..f10fbe7154 100644 --- a/examples/peripherals/i2s/i2s_codec/i2s_es8311/README.md +++ b/examples/peripherals/i2s/i2s_codec/i2s_es8311/README.md @@ -1,5 +1,5 @@ -| Supported Targets | ESP32 | ESP32-C3 | ESP32-S2 | ESP32-S3 | ESP32-H2 | -| ----------------- | ----- | -------- | -------- | -------- | -------- | +| Supported Targets | ESP32 | ESP32-C3 | ESP32-S2 | ESP32-S3 | +| ----------------- | ----- | -------- | -------- | -------- | # I2S ES8311 Example @@ -34,21 +34,21 @@ For more details, see [ES8311 datasheet](http://www.everest-semi.com/pdf/ES8311% ┌─────────────────┐ ┌──────────────────────────┐ │ ESP │ │ ES8311 │ │ │ │ │ -│ MCLK-GPIO 0 ├──────────►│PIN2-MCLK │ +│ I2S_MCK_IO├──────────►│PIN2-MCLK │ │ │ │ │ ┌─────────┐ -│ BCLK-GPIO 4 ├──────────►│PIN6-BCLK PIN12-OUTP├───────────┤ │ +│ I2S_BCK_IO├──────────►│PIN6-BCLK PIN12-OUTP├───────────┤ │ │ │ │ │ │ EARPHONE│ -│ WS-GPIO 5 ├──────────►│PIN8-LRCK PIN13-OUTN├───────────┤ │ +│ I2S_WS_IO├──────────►│PIN8-LRCK PIN13-OUTN├───────────┤ │ │ │ │ │ └─────────┘ -│ SDOUT-GPIO 18├──────────►│PIN9-SDIN │ -│ (GPIO 2)│ │ │ -│ SDIN-GPIO 19│◄──────────┤PIN7-SDOUT │ -│ (GPIO 3)│ │ │ ┌─────────┐ +│ I2S_DO_IO├──────────►│PIN9-SDIN │ +│ │ │ │ +│ I2S_DI_IO│◄──────────┤PIN7-SDOUT │ +│ │ │ │ ┌─────────┐ │ │ │ PIN18-MIC1P├───────────┤ │ -│ SCL-GPIO 16├──────────►│PIN1 -CCLK │ │ MIC │ -│ (GPIO 6)│ │ PIN17-MIC1N├───────────┤ │ -│ SDA-GPIO 17│◄─────────►│PIN19-CDATA │ └─────────┘ -│ (GPIO 7)│ │ │ +│ I2C_SCL_IO├──────────►│PIN1 -CCLK │ │ MIC │ +│ │ │ PIN17-MIC1N├───────────┤ │ +│ I2C_SDA_IO│◄─────────►│PIN19-CDATA │ └─────────┘ +│ │ │ │ │ VCC 3.3├───────────┤VCC │ │ │ │ │ │ GND├───────────┤GND │ diff --git a/examples/peripherals/i2s/i2s_codec/i2s_es8311/main/i2s_es8311_example.c b/examples/peripherals/i2s/i2s_codec/i2s_es8311/main/i2s_es8311_example.c index 34b0ed0c3c..541efcf9c8 100644 --- a/examples/peripherals/i2s/i2s_codec/i2s_es8311/main/i2s_es8311_example.c +++ b/examples/peripherals/i2s/i2s_codec/i2s_es8311/main/i2s_es8311_example.c @@ -36,9 +36,9 @@ #define I2S_DI_IO (GPIO_NUM_19) #endif /* Example configurations */ -#define EXAMPLE_RECV_BUF_SIZE (2048) +#define EXAMPLE_RECV_BUF_SIZE (2400) #define EXAMPLE_SAMPLE_RATE (16000) -#define EXAMPLE_MCLK_MULTIPLE (256) +#define EXAMPLE_MCLK_MULTIPLE (384) // If not using 24-bit data width, 256 should be enough #define EXAMPLE_MCLK_FREQ_HZ (EXAMPLE_SAMPLE_RATE * EXAMPLE_MCLK_MULTIPLE) #define EXAMPLE_VOICE_VOLUME CONFIG_EXAMPLE_VOICE_VOLUME #if CONFIG_EXAMPLE_MODE_ECHO @@ -114,6 +114,7 @@ static esp_err_t i2s_driver_init(void) }, }, }; + std_cfg.clk_cfg.mclk_multiple = EXAMPLE_MCLK_MULTIPLE; ESP_ERROR_CHECK(i2s_channel_init_std_mode(tx_handle, &std_cfg)); ESP_ERROR_CHECK(i2s_channel_init_std_mode(rx_handle, &std_cfg)); diff --git a/examples/peripherals/i2s/i2s_codec/i2s_es8311/main/idf_component.yml b/examples/peripherals/i2s/i2s_codec/i2s_es8311/main/idf_component.yml index 36baaa8825..5617c1c865 100644 --- a/examples/peripherals/i2s/i2s_codec/i2s_es8311/main/idf_component.yml +++ b/examples/peripherals/i2s/i2s_codec/i2s_es8311/main/idf_component.yml @@ -3,7 +3,7 @@ dependencies: espressif/es8311: "==1.0.0" ## Required IDF version idf: - version: ">=4.1.0" + version: "^5.0" # # Put list of dependencies here # # For components maintained by Espressif: # component: "~1.0.0" diff --git a/tools/ci/check_public_headers_exceptions.txt b/tools/ci/check_public_headers_exceptions.txt index a29ec676c2..880e4fb445 100644 --- a/tools/ci/check_public_headers_exceptions.txt +++ b/tools/ci/check_public_headers_exceptions.txt @@ -168,7 +168,6 @@ components/soc/esp32s2/include/soc/efuse_struct.h components/soc/esp32s2/include/soc/gpio_sd_struct.h components/soc/esp32s2/include/soc/gpio_struct.h components/soc/esp32s2/include/soc/i2c_struct.h -components/soc/esp32s2/include/soc/i2s_struct.h components/soc/esp32s2/include/soc/ledc_struct.h components/soc/esp32s2/include/soc/rtc_i2c_struct.h components/soc/esp32s2/include/soc/rtc_io_struct.h @@ -201,7 +200,6 @@ components/soc/esp32c3/include/soc/apb_ctrl_struct.h components/soc/esp32c3/include/soc/apb_saradc_struct.h components/soc/esp32c3/include/soc/efuse_struct.h components/soc/esp32c3/include/soc/gpio_sd_struct.h -components/soc/esp32c3/include/soc/i2s_struct.h components/soc/esp32c3/include/soc/ledc_struct.h components/soc/esp32c3/include/soc/rtc_cntl_struct.h components/soc/esp32c3/include/soc/rtc_i2c_struct.h @@ -213,12 +211,6 @@ components/soc/esp32c3/include/soc/uhci_struct.h ### To be fixed: files which don't compile for esp32c2 target: -components/driver/deprecated/driver/i2s_types_legacy.h -components/driver/deprecated/driver/i2s.h -components/driver/include/driver/i2s_common.h -components/driver/include/driver/i2s_pdm.h -components/driver/include/driver/i2s_std.h -components/driver/include/driver/i2s_tdm.h components/efuse/esp32c2/include/esp_efuse_table.h components/soc/esp32c2/include/soc/rtc_cntl_struct.h components/soc/esp32c2/include/soc/spi_mem_struct.h