From bf8419fd6e5cddfcd284b2bd832fe7ee61fb57bc Mon Sep 17 00:00:00 2001 From: laokaiyao Date: Wed, 26 Apr 2023 16:39:13 +0800 Subject: [PATCH] i2s: public bclk_div and add a default config for PDM TX DAC --- components/driver/i2s/i2s_pdm.c | 6 +- .../driver/i2s/include/driver/i2s_pdm.h | 58 +++++++++++++++++-- examples/peripherals/.build-test-rules.yml | 9 --- .../i2s/i2s_basic/i2s_pdm/README.md | 10 ++-- .../i2s/i2s_basic/i2s_pdm/main/CMakeLists.txt | 4 +- .../i2s_basic/i2s_pdm/main/Kconfig.projbuild | 24 ++++++++ .../i2s/i2s_basic/i2s_pdm/main/i2s_pdm_tx.c | 6 ++ .../i2s/i2s_basic/i2s_pdm/pytest_i2s_pdm.py | 2 +- 8 files changed, 96 insertions(+), 23 deletions(-) diff --git a/components/driver/i2s/i2s_pdm.c b/components/driver/i2s/i2s_pdm.c index 655666373e..2f939c674e 100644 --- a/components/driver/i2s/i2s_pdm.c +++ b/components/driver/i2s/i2s_pdm.c @@ -30,6 +30,7 @@ static const char *TAG = "i2s_pdm"; ---------------------------------------------------------------*/ #if SOC_I2S_SUPPORTS_PDM_TX +#define I2S_PDM_TX_BCLK_DIV_MIN 8 /*!< The minimum bclk_div for PDM TX mode */ static esp_err_t i2s_pdm_tx_calculate_clock(i2s_chan_handle_t handle, const i2s_pdm_tx_clk_config_t *clk_cfg, i2s_hal_clock_info_t *clk_info) { uint32_t rate = clk_cfg->sample_rate_hz; @@ -38,7 +39,7 @@ static esp_err_t i2s_pdm_tx_calculate_clock(i2s_chan_handle_t handle, const i2s_ // Over sampling ratio (integer, mostly should be 1 or 2) uint32_t over_sample_ratio = pdm_tx_clk->up_sample_fp / pdm_tx_clk->up_sample_fs; clk_info->bclk = rate * I2S_LL_PDM_BCK_FACTOR * over_sample_ratio; - clk_info->bclk_div = 8; + clk_info->bclk_div = clk_cfg->bclk_div < I2S_PDM_TX_BCLK_DIV_MIN ? I2S_PDM_TX_BCLK_DIV_MIN : clk_cfg->bclk_div; clk_info->mclk = clk_info->bclk * clk_info->bclk_div; clk_info->sclk = i2s_get_source_clk_freq(clk_cfg->clk_src, clk_info->mclk); clk_info->mclk_div = clk_info->sclk / clk_info->mclk; @@ -318,13 +319,14 @@ err: ---------------------------------------------------------------*/ #if SOC_I2S_SUPPORTS_PDM_RX +#define I2S_PDM_RX_BCLK_DIV_MIN 8 /*!< The minimum bclk_div for PDM RX mode */ static esp_err_t i2s_pdm_rx_calculate_clock(i2s_chan_handle_t handle, const i2s_pdm_rx_clk_config_t *clk_cfg, i2s_hal_clock_info_t *clk_info) { uint32_t rate = clk_cfg->sample_rate_hz; i2s_pdm_rx_clk_config_t *pdm_rx_clk = (i2s_pdm_rx_clk_config_t *)clk_cfg; clk_info->bclk = rate * I2S_LL_PDM_BCK_FACTOR * (pdm_rx_clk->dn_sample_mode == I2S_PDM_DSR_16S ? 2 : 1); - clk_info->bclk_div = 8; + clk_info->bclk_div = clk_cfg->bclk_div < I2S_PDM_RX_BCLK_DIV_MIN ? I2S_PDM_RX_BCLK_DIV_MIN : clk_cfg->bclk_div; clk_info->mclk = clk_info->bclk * clk_info->bclk_div; clk_info->sclk = i2s_get_source_clk_freq(clk_cfg->clk_src, clk_info->mclk); clk_info->mclk_div = clk_info->sclk / clk_info->mclk; diff --git a/components/driver/i2s/include/driver/i2s_pdm.h b/components/driver/i2s/include/driver/i2s_pdm.h index 29107e8e1c..2265c7908a 100644 --- a/components/driver/i2s/include/driver/i2s_pdm.h +++ b/components/driver/i2s/include/driver/i2s_pdm.h @@ -44,7 +44,8 @@ extern "C" { .sample_rate_hz = rate, \ .clk_src = I2S_CLK_SRC_DEFAULT, \ .mclk_multiple = I2S_MCLK_MULTIPLE_256, \ - .dn_sample_mode = I2S_PDM_DSR_8S \ + .dn_sample_mode = I2S_PDM_DSR_8S, \ + .bclk_div = 8, \ } /** @@ -69,6 +70,8 @@ typedef struct { i2s_mclk_multiple_t mclk_multiple; /*!< The multiple of mclk to the sample rate */ /* Particular fields */ i2s_pdm_dsr_t dn_sample_mode; /*!< Down-sampling rate mode */ + uint32_t bclk_div; /*!< The division from mclk to bclk. The typical and minimum value is I2S_PDM_RX_BCLK_DIV_MIN. + * It will be set to I2S_PDM_RX_BCLK_DIV_MIN by default if it is smaller than I2S_PDM_RX_BCLK_DIV_MIN */ } i2s_pdm_rx_clk_config_t; /** @@ -165,7 +168,7 @@ esp_err_t i2s_channel_reconfig_pdm_rx_gpio(i2s_chan_handle_t handle, const i2s_p #if SOC_I2S_SUPPORTS_PDM_TX #if SOC_I2S_HW_VERSION_2 /** - * @brief PDM style in 2 slots(TX) + * @brief PDM style in 2 slots(TX) for codec line mode * @param bits_per_sample i2s data bit width, only support 16 bits for PDM mode * @param mono_or_stereo I2S_SLOT_MODE_MONO or I2S_SLOT_MODE_STEREO */ @@ -184,9 +187,33 @@ esp_err_t i2s_channel_reconfig_pdm_rx_gpio(i2s_chan_handle_t handle, const i2s_p .sd_dither = 0, \ .sd_dither2 = 1, \ } + +/** + * @brief PDM style in 1 slots(TX) for DAC line mode + * @note The noise might be different with different configurations, this macro provides a set of configurations + * that have relatively high SNR (Signal Noise Ratio), you can also adjust them to fit your case. + * @param bits_per_sample i2s data bit width, only support 16 bits for PDM mode + * @param mono_or_stereo I2S_SLOT_MODE_MONO or I2S_SLOT_MODE_STEREO + */ +#define I2S_PDM_TX_SLOT_DAC_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, \ + .sd_prescale = 0, \ + .sd_scale = I2S_PDM_SIG_SCALING_MUL_1, \ + .hp_scale = I2S_PDM_SIG_SCALING_MUL_1, \ + .lp_scale = I2S_PDM_SIG_SCALING_MUL_1, \ + .sinc_scale = I2S_PDM_SIG_SCALING_MUL_1, \ + .line_mode = ((mono_or_stereo) == I2S_SLOT_MODE_MONO ? \ + I2S_PDM_TX_ONE_LINE_DAC : I2S_PDM_TX_TWO_LINE_DAC), \ + .hp_en = true, \ + .hp_cut_off_freq_hz = 35.5, \ + .sd_dither = 0, \ + .sd_dither2 = 1, \ +} #else // SOC_I2S_HW_VERSION_2 /** - * @brief PDM style in 2 slots(TX) + * @brief PDM style in 2 slots(TX) for codec line mode * @param bits_per_sample i2s data bit width, only support 16 bits for PDM mode * @param mono_or_stereo I2S_SLOT_MODE_MONO or I2S_SLOT_MODE_STEREO */ @@ -204,7 +231,7 @@ esp_err_t i2s_channel_reconfig_pdm_rx_gpio(i2s_chan_handle_t handle, const i2s_p #endif // SOC_I2S_HW_VERSION_2 /** - * @brief i2s default pdm tx clock configuration + * @brief i2s default pdm tx clock configuration for codec line mode * @note TX PDM can only be set to the following two up-sampling rate configurations: * 1: fp = 960, fs = sample_rate_hz / 100, in this case, Fpdm = 128*48000 * 2: fp = 960, fs = 480, in this case, Fpdm = 128*Fpcm = 128*sample_rate_hz @@ -218,6 +245,27 @@ esp_err_t i2s_channel_reconfig_pdm_rx_gpio(i2s_chan_handle_t handle, const i2s_p .mclk_multiple = I2S_MCLK_MULTIPLE_256, \ .up_sample_fp = 960, \ .up_sample_fs = 480, \ + .bclk_div = 8, \ +} + +/** + * @brief i2s default pdm tx clock configuration for DAC line mode + * @note TX PDM can only be set to the following two up-sampling rate configurations: + * 1: fp = 960, fs = sample_rate_hz / 100, in this case, Fpdm = 128*48000 + * 2: fp = 960, fs = 480, in this case, Fpdm = 128*Fpcm = 128*sample_rate_hz + * If the pdm receiver do not care the pdm serial clock, it's recommended set Fpdm = 128*48000. + * Otherwise, the second configuration should be adopted. + * @note The noise might be different with different configurations, this macro provides a set of configurations + * that have relatively high SNR (Signal Noise Ratio), you can also adjust them to fit your case. + * @param rate sample rate (not suggest to exceed 48000 Hz, otherwise more glitches and noise may appear) + */ +#define I2S_PDM_TX_CLK_DAC_DEFAULT_CONFIG(rate) { \ + .sample_rate_hz = rate, \ + .clk_src = I2S_CLK_SRC_DEFAULT, \ + .mclk_multiple = I2S_MCLK_MULTIPLE_256, \ + .up_sample_fp = 960, \ + .up_sample_fs = (rate) / 100, \ + .bclk_div = 13, \ } /* @@ -275,6 +323,8 @@ typedef struct { /* Particular fields */ uint32_t up_sample_fp; /*!< Up-sampling param fp */ uint32_t up_sample_fs; /*!< Up-sampling param fs, not allowed to be greater than 480 */ + uint32_t bclk_div; /*!< The division from mclk to bclk. The minimum value is I2S_PDM_TX_BCLK_DIV_MIN. + * It will be set to I2S_PDM_TX_BCLK_DIV_MIN by default if it is smaller than I2S_PDM_TX_BCLK_DIV_MIN */ } i2s_pdm_tx_clk_config_t; /** diff --git a/examples/peripherals/.build-test-rules.yml b/examples/peripherals/.build-test-rules.yml index 39bfc110d1..46fbc9ee1e 100644 --- a/examples/peripherals/.build-test-rules.yml +++ b/examples/peripherals/.build-test-rules.yml @@ -45,9 +45,6 @@ examples/peripherals/i2s/i2s_adc_dac: examples/peripherals/i2s/i2s_basic/i2s_pdm: disable: - if: SOC_I2S_SUPPORTS_PDM != 1 - - if: IDF_TARGET == "esp32h2" - temporary: true - reason: rtc timer is not supported examples/peripherals/i2s/i2s_basic/i2s_std: disable: @@ -290,9 +287,3 @@ examples/peripherals/uart/uart_echo_rs485: examples/peripherals/usb: disable: - if: SOC_USB_PERIPH_NUM != 1 - -examples/peripherals/wave_gen: - enable: - - if: IDF_TARGET == "esp32" - temporary: true - reason: the other targets are not tested yet diff --git a/examples/peripherals/i2s/i2s_basic/i2s_pdm/README.md b/examples/peripherals/i2s/i2s_basic/i2s_pdm/README.md index a432426713..35bdcddf44 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-C6 | ESP32-S3 | -| ----------------- | ----- | -------- | -------- | -------- | +| Supported Targets | ESP32 | ESP32-C3 | ESP32-C6 | ESP32-H2 | ESP32-S3 | +| ----------------- | ----- | -------- | -------- | -------- | -------- | # I2S Basic PDM Mode Example @@ -99,9 +99,9 @@ This example is going to show how to use the PDM TX and RX mode. #### 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 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. +* An audio power amplifier that can input PDM signal. If the power amplifier can only receive the analog signal without PDM clock (i.e. DAC line mode, otherwise codec line mode), 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** +**MAX98358 (codec case)** Please refer to the [Datasheet of MAX98358](https://datasheets.maximintegrated.com/en/ds/MAX98358.pdf) for more details. @@ -121,7 +121,7 @@ Please refer to the [Datasheet of MAX98358](https://datasheets.maximintegrated.c └────────────────────────┘ └───────────────┘ ``` -**NS4150** +**NS4150 (dac case)** Please refer to the NS4150 datasheet for more details. diff --git a/examples/peripherals/i2s/i2s_basic/i2s_pdm/main/CMakeLists.txt b/examples/peripherals/i2s/i2s_basic/i2s_pdm/main/CMakeLists.txt index e1bdf7c96a..01e2ce81d4 100644 --- a/examples/peripherals/i2s/i2s_basic/i2s_pdm/main/CMakeLists.txt +++ b/examples/peripherals/i2s/i2s_basic/i2s_pdm/main/CMakeLists.txt @@ -1,10 +1,10 @@ set(srcs "i2s_pdm_example_main.c") -if(CONFIG_SOC_I2S_SUPPORTS_PDM_TX) +if(CONFIG_SOC_I2S_SUPPORTS_PDM_TX AND CONFIG_EXAMPLE_PDM_TX) list(APPEND srcs "i2s_pdm_tx.c") endif() -if(CONFIG_SOC_I2S_SUPPORTS_PDM_RX) +if(CONFIG_SOC_I2S_SUPPORTS_PDM_RX AND CONFIG_EXAMPLE_PDM_RX) list(APPEND srcs "i2s_pdm_rx.c") endif() diff --git a/examples/peripherals/i2s/i2s_basic/i2s_pdm/main/Kconfig.projbuild b/examples/peripherals/i2s/i2s_basic/i2s_pdm/main/Kconfig.projbuild index bc5712e29f..b02965543d 100644 --- a/examples/peripherals/i2s/i2s_basic/i2s_pdm/main/Kconfig.projbuild +++ b/examples/peripherals/i2s/i2s_basic/i2s_pdm/main/Kconfig.projbuild @@ -19,4 +19,28 @@ menu "I2S PDM Example Configuration" PDM RX example will show the received data from a PDM microphone. endchoice + choice EXAMPLE_PDM_TX_LINE_MODE + prompt "I2S PDM TX Line Mode" + depends on EXAMPLE_PDM_TX && SOC_I2S_HW_VERSION_2 + default EXAMPLE_PDM_TX_CODEC + help + Decide to output PDM signal into a PDM codec or a low-pass filter + + config EXAMPLE_PDM_TX_CODEC + bool "Codec line mode" + help + Output PDM signal to a PDM codec. The PDM clock signal is mandatory for PDM codec, + the codec can differentiate the left and right sound channels by sampling data + on positive or negative edges. That means the data of the left and right channels + can coexist on a same data line. + + config EXAMPLE_PDM_TX_DAC + bool "DAC line mode" + help + Output PDM signal to a low-pass filter, so that the low-pass filter can restore the PDM + signal to analog wave. Therefore, each data line can only contains one sound channel, + if both left and right channels are required, two data lines should be specified as well. + Normally the PDM signal is not sufficient in DAC line mode. + endchoice + endmenu 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 59e2186243..5bee1954b3 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 @@ -54,9 +54,15 @@ static i2s_chan_handle_t i2s_example_init_pdm_tx(void) * 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 = { +#if CONFIG_EXAMPLE_PDM_TX_DAC + .clk_cfg = I2S_PDM_TX_CLK_DAC_DEFAULT_CONFIG(EXAMPLE_PDM_TX_FREQ_HZ), + /* The data bit-width of PDM mode is fixed to 16 */ + .slot_cfg = I2S_PDM_TX_SLOT_DAC_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_MONO), +#else .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), +#endif .gpio_cfg = { .clk = EXAMPLE_PDM_TX_CLK_IO, .dout = EXAMPLE_PDM_TX_DOUT_IO, 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 29b3de13fb..561ee109dd 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,7 @@ from pytest_embedded import Dut @pytest.mark.esp32s3 @pytest.mark.esp32c3 @pytest.mark.esp32c6 -# @pytest.mark.esp32h2 IDF-6808 +@pytest.mark.esp32h2 @pytest.mark.generic @pytest.mark.parametrize( 'config',