diff --git a/components/driver/dac/esp32/dac_dma.c b/components/driver/dac/esp32/dac_dma.c index cc2b5706a2..c5f8223a90 100644 --- a/components/driver/dac/esp32/dac_dma.c +++ b/components/driver/dac/esp32/dac_dma.c @@ -97,7 +97,7 @@ static esp_err_t s_dac_dma_periph_set_clock(uint32_t freq_hz, bool is_apll) ESP_LOGD(TAG, "[sclk] %"PRIu32" [mclk] %"PRIu32" [mclk_div] %"PRIu32" [bclk] %"PRIu32" [bclk_div] %"PRIu32, sclk, mclk, mclk_div, bclk, bclk_div); i2s_ll_tx_clk_set_src(s_ddp->periph_dev, is_apll ? I2S_CLK_SRC_APLL : I2S_CLK_SRC_DEFAULT); - i2s_ll_mclk_div_t mclk_div_coeff = {}; + hal_utils_clk_div_t mclk_div_coeff = {}; i2s_hal_calc_mclk_precise_division(sclk, mclk, &mclk_div_coeff); i2s_ll_tx_set_mclk(s_ddp->periph_dev, &mclk_div_coeff); i2s_ll_tx_set_bck_div_num(s_ddp->periph_dev, bclk_div); diff --git a/components/driver/dac/esp32s2/dac_dma.c b/components/driver/dac/esp32s2/dac_dma.c index 25d9ceaf40..af9dfb11c6 100644 --- a/components/driver/dac/esp32s2/dac_dma.c +++ b/components/driver/dac/esp32s2/dac_dma.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2019-2022 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2019-2023 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -18,6 +18,7 @@ #include "hal/spi_ll.h" #include "hal/dac_ll.h" #include "hal/adc_ll.h" +#include "hal/hal_utils.h" #include "soc/lldesc.h" #include "soc/soc.h" #include "soc/soc_caps.h" @@ -99,36 +100,21 @@ static esp_err_t s_dac_dma_periph_set_clock(uint32_t freq_hz, bool is_apll){ } ESP_RETURN_ON_FALSE(interval * 256 > total_div, ESP_ERR_INVALID_ARG, TAG, "the DAC frequency is too small"); - /* Step 3: Calculate the coefficients of ADC digital controller divider*/ - uint32_t fsclk = interval * freq_hz; /* The clock frequency that produced by ADC controller divider */ - uint32_t clk_div = digi_ctrl_freq / fsclk; - uint32_t mod = digi_ctrl_freq % fsclk; - uint32_t a = 0; - uint32_t b = 1; - if (mod == 0) { - goto finish; - } - uint32_t min_diff = mod + 1; - for (uint32_t tmp_b = 1; tmp_b < 64; tmp_b++) { - uint32_t tmp_a = (uint32_t)(((mod * b) / (float)fsclk) + 0.5); - uint32_t diff = (uint32_t)abs((int)(mod * tmp_b) - (int)(fsclk * tmp_a)); - if (diff == 0) { - a = tmp_a; - b = tmp_b; - goto finish; - } - if (diff < min_diff) { - min_diff = diff; - a = tmp_a; - b = tmp_b; - } - } + /* Step 3: Calculate the coefficients of ADC digital controller divider */ + hal_utils_clk_info_t adc_clk_info = { + .src_freq_hz = digi_ctrl_freq / interval, + .exp_freq_hz = freq_hz, + .max_integ = 257, + .min_integ = 1, + .max_fract = 64, + }; + hal_utils_clk_div_t adc_clk_div = {}; + hal_utils_calc_clk_div_frac_accurate(&adc_clk_info, &adc_clk_div); -finish: /* Step 4: Set the clock coefficients */ dac_ll_digi_clk_inv(true); dac_ll_digi_set_trigger_interval(interval); // secondary clock division - adc_ll_digi_controller_clk_div(clk_div - 1, b, a); + adc_ll_digi_controller_clk_div(adc_clk_div.integer - 1, adc_clk_div.denominator, adc_clk_div.numerator); adc_ll_digi_clk_sel(is_apll ? ADC_DIGI_CLK_SRC_APLL : ADC_DIGI_CLK_SRC_DEFAULT); return ESP_OK; } diff --git a/components/esp_lcd/linker.lf b/components/esp_lcd/linker.lf index e46cfad3d9..6e3aa0d3cd 100644 --- a/components/esp_lcd/linker.lf +++ b/components/esp_lcd/linker.lf @@ -9,3 +9,4 @@ archive: libhal.a entries: if LCD_RGB_ISR_IRAM_SAFE = y: lcd_hal: lcd_hal_cal_pclk_freq (noflash) + hal_utils: hal_utils_calc_clk_div_frac_fast (noflash) diff --git a/components/hal/.build-test-rules.yml b/components/hal/.build-test-rules.yml new file mode 100644 index 0000000000..af13d55d6c --- /dev/null +++ b/components/hal/.build-test-rules.yml @@ -0,0 +1,7 @@ +components/hal/test_apps/hal_utils: + enable: + - if: IDF_TARGET == "linux" + disable: + - if: IDF_TARGET == "linux" + temporary: true + reason: env not ready diff --git a/components/hal/adc_hal.c b/components/hal/adc_hal.c index 3e4f74da55..77b4809a75 100644 --- a/components/hal/adc_hal.c +++ b/components/hal/adc_hal.c @@ -180,7 +180,7 @@ static void adc_hal_digi_sample_freq_config(adc_hal_dma_ctx_t *hal, adc_continuo uint32_t bclk_div = 16; uint32_t bclk = sample_freq_hz * 2; uint32_t mclk = bclk * bclk_div; - i2s_ll_mclk_div_t mclk_div = {}; + hal_utils_clk_div_t mclk_div = {}; i2s_hal_calc_mclk_precise_division(I2S_BASE_CLK, mclk, &mclk_div); i2s_ll_rx_set_mclk(hal->dev, &mclk_div); i2s_ll_rx_set_bck_div_num(hal->dev, bclk_div); diff --git a/components/hal/esp32/include/hal/i2s_ll.h b/components/hal/esp32/include/hal/i2s_ll.h index 5f33bf4277..8f621d92e2 100644 --- a/components/hal/esp32/include/hal/i2s_ll.h +++ b/components/hal/esp32/include/hal/i2s_ll.h @@ -19,6 +19,8 @@ #include "soc/i2s_periph.h" #include "soc/i2s_struct.h" #include "hal/i2s_types.h" +#include "hal/hal_utils.h" + #ifdef __cplusplus extern "C" { @@ -30,8 +32,8 @@ extern "C" { #define I2S_LL_AD_BCK_FACTOR (2) #define I2S_LL_PDM_BCK_FACTOR (64) -#define I2S_LL_MCLK_DIVIDER_BIT_WIDTH (6) -#define I2S_LL_MCLK_DIVIDER_MAX ((1 << I2S_LL_MCLK_DIVIDER_BIT_WIDTH) - 1) +#define I2S_LL_CLK_FRAC_DIV_N_MAX 256 // I2S_MCLK = I2S_SRC_CLK / (N + b/a), the N register is 8 bit-width +#define I2S_LL_CLK_FRAC_DIV_AB_MAX 64 // I2S_MCLK = I2S_SRC_CLK / (N + b/a), the a/b register is 6 bit-width #define I2S_LL_BCK_MAX_PRESCALE (64) @@ -48,16 +50,6 @@ extern "C" { #define I2S_LL_PLL_F160M_CLK_FREQ (160 * 1000000) // PLL_F160M_CLK: 160MHz #define I2S_LL_DEFAULT_PLL_CLK_FREQ I2S_LL_PLL_F160M_CLK_FREQ // The default PLL clock frequency while using I2S_CLK_SRC_DEFAULT -/** - * @brief I2S clock configuration structure - * @note Fmclk = Fsclk /(integ+numer/denom) - */ -typedef struct { - uint16_t integ; // Integer part of I2S module clock divider - uint16_t denom; // Denominator part of I2S module clock divider - uint16_t numer; // Numerator part of I2S module clock divider -} i2s_ll_mclk_div_t; - /** * @brief Enable DMA descriptor owner check * @@ -312,14 +304,14 @@ static inline void i2s_ll_set_raw_mclk_div(i2s_dev_t *hw, uint32_t mclk_div, uin * @param hw Peripheral I2S hardware instance address. * @param mclk_div The mclk division coefficients */ -static inline void i2s_ll_tx_set_mclk(i2s_dev_t *hw, const i2s_ll_mclk_div_t *mclk_div) +static inline void i2s_ll_tx_set_mclk(i2s_dev_t *hw, const hal_utils_clk_div_t *mclk_div) { /* Workaround for inaccurate clock while switching from a relatively low sample rate to a high sample rate * Set to particular coefficients first then update to the target coefficients, * otherwise the clock division might be inaccurate. * the general idea is to set a value that unlike to calculate from the regular decimal */ i2s_ll_set_raw_mclk_div(hw, 7, 47, 3); - i2s_ll_set_raw_mclk_div(hw, mclk_div->integ, mclk_div->denom, mclk_div->numer); + i2s_ll_set_raw_mclk_div(hw, mclk_div->integer, mclk_div->denominator, mclk_div->numerator); } /** @@ -340,7 +332,7 @@ static inline void i2s_ll_rx_set_bck_div_num(i2s_dev_t *hw, uint32_t val) * @param hw Peripheral I2S hardware instance address. * @param mclk_div The mclk division coefficients */ -static inline void i2s_ll_rx_set_mclk(i2s_dev_t *hw, const i2s_ll_mclk_div_t *mclk_div) +static inline void i2s_ll_rx_set_mclk(i2s_dev_t *hw, const hal_utils_clk_div_t *mclk_div) { // TX and RX channel on ESP32 shares a same mclk i2s_ll_tx_set_mclk(hw, mclk_div); diff --git a/components/hal/esp32c3/include/hal/i2s_ll.h b/components/hal/esp32c3/include/hal/i2s_ll.h index cf492a72db..6f8dac94ee 100644 --- a/components/hal/esp32c3/include/hal/i2s_ll.h +++ b/components/hal/esp32c3/include/hal/i2s_ll.h @@ -18,6 +18,7 @@ #include "soc/i2s_periph.h" #include "soc/i2s_struct.h" #include "hal/i2s_types.h" +#include "hal/hal_utils.h" #ifdef __cplusplus @@ -29,22 +30,12 @@ extern "C" { #define I2S_LL_TDM_CH_MASK (0xffff) #define I2S_LL_PDM_BCK_FACTOR (64) -#define I2S_LL_MCLK_DIVIDER_BIT_WIDTH (9) -#define I2S_LL_MCLK_DIVIDER_MAX ((1 << I2S_LL_MCLK_DIVIDER_BIT_WIDTH) - 1) +#define I2S_LL_CLK_FRAC_DIV_N_MAX 256 // I2S_MCLK = I2S_SRC_CLK / (N + b/a), the N register is 8 bit-width +#define I2S_LL_CLK_FRAC_DIV_AB_MAX 512 // I2S_MCLK = I2S_SRC_CLK / (N + b/a), the a/b register is 9 bit-width #define I2S_LL_PLL_F160M_CLK_FREQ (160 * 1000000) // PLL_F160M_CLK: 160MHz #define I2S_LL_DEFAULT_PLL_CLK_FREQ I2S_LL_PLL_F160M_CLK_FREQ // The default PLL clock frequency while using I2S_CLK_SRC_DEFAULT -/** - * @brief I2S clock configuration structure - * @note Fmclk = Fsclk /(integ+numer/denom) - */ -typedef struct { - uint16_t integ; // Integer part of I2S module clock divider - uint16_t denom; // Denominator part of I2S module clock divider - uint16_t numer; // Numerator part of I2S module clock divider -} i2s_ll_mclk_div_t; - /** * @brief I2S module general init, enable I2S clock. * @@ -304,7 +295,7 @@ static inline void i2s_ll_rx_set_raw_clk_div(i2s_dev_t *hw, uint32_t div_int, ui * @param hw Peripheral I2S hardware instance address. * @param mclk_div The mclk division coefficients */ -static inline void i2s_ll_tx_set_mclk(i2s_dev_t *hw, const i2s_ll_mclk_div_t *mclk_div) +static inline void i2s_ll_tx_set_mclk(i2s_dev_t *hw, const hal_utils_clk_div_t *mclk_div) { /* Workaround for inaccurate clock while switching from a relatively low sample rate to a high sample rate * Set to particular coefficients first then update to the target coefficients, @@ -317,13 +308,13 @@ static inline void i2s_ll_tx_set_mclk(i2s_dev_t *hw, const i2s_ll_mclk_div_t *mc uint32_t div_z = 0; uint32_t div_yn1 = 0; /* If any of denominator and numerator is 0, set all the coefficients to 0 */ - if (mclk_div->denom && mclk_div->numer) { - div_yn1 = mclk_div->numer * 2 > mclk_div->denom; - div_z = div_yn1 ? mclk_div->denom - mclk_div->numer : mclk_div->numer; - div_x = mclk_div->denom / div_z - 1; - div_y = mclk_div->denom % div_z; + if (mclk_div->denominator && mclk_div->numerator) { + div_yn1 = mclk_div->numerator * 2 > mclk_div->denominator; + div_z = div_yn1 ? mclk_div->denominator - mclk_div->numerator : mclk_div->numerator; + div_x = mclk_div->denominator / div_z - 1; + div_y = mclk_div->denominator % div_z; } - i2s_ll_tx_set_raw_clk_div(hw, mclk_div->integ, div_x, div_y, div_z, div_yn1); + i2s_ll_tx_set_raw_clk_div(hw, mclk_div->integer, div_x, div_y, div_z, div_yn1); } /** @@ -344,7 +335,7 @@ static inline void i2s_ll_rx_set_bck_div_num(i2s_dev_t *hw, uint32_t val) * @param hw Peripheral I2S hardware instance address. * @param mclk_div The mclk division coefficients */ -static inline void i2s_ll_rx_set_mclk(i2s_dev_t *hw, const i2s_ll_mclk_div_t *mclk_div) +static inline void i2s_ll_rx_set_mclk(i2s_dev_t *hw, const hal_utils_clk_div_t *mclk_div) { /* Workaround for inaccurate clock while switching from a relatively low sample rate to a high sample rate * Set to particular coefficients first then update to the target coefficients, @@ -357,13 +348,13 @@ static inline void i2s_ll_rx_set_mclk(i2s_dev_t *hw, const i2s_ll_mclk_div_t *mc uint32_t div_z = 0; uint32_t div_yn1 = 0; /* If any of denominator and numerator is 0, set all the coefficients to 0 */ - if (mclk_div->denom && mclk_div->numer) { - div_yn1 = mclk_div->numer * 2 > mclk_div->denom; - div_z = div_yn1 ? mclk_div->denom - mclk_div->numer : mclk_div->numer; - div_x = mclk_div->denom / div_z - 1; - div_y = mclk_div->denom % div_z; + if (mclk_div->denominator && mclk_div->numerator) { + div_yn1 = mclk_div->numerator * 2 > mclk_div->denominator; + div_z = div_yn1 ? mclk_div->denominator - mclk_div->numerator : mclk_div->numerator; + div_x = mclk_div->denominator / div_z - 1; + div_y = mclk_div->denominator % div_z; } - i2s_ll_rx_set_raw_clk_div(hw, mclk_div->integ, div_x, div_y, div_z, div_yn1); + i2s_ll_rx_set_raw_clk_div(hw, mclk_div->integer, div_x, div_y, div_z, div_yn1); } /** diff --git a/components/hal/esp32c6/include/hal/i2s_ll.h b/components/hal/esp32c6/include/hal/i2s_ll.h index 347767cecc..687ef3162f 100644 --- a/components/hal/esp32c6/include/hal/i2s_ll.h +++ b/components/hal/esp32c6/include/hal/i2s_ll.h @@ -19,6 +19,7 @@ #include "soc/i2s_struct.h" #include "soc/pcr_struct.h" #include "hal/i2s_types.h" +#include "hal/hal_utils.h" #ifdef __cplusplus @@ -30,22 +31,12 @@ extern "C" { #define I2S_LL_TDM_CH_MASK (0xffff) #define I2S_LL_PDM_BCK_FACTOR (64) -#define I2S_LL_MCLK_DIVIDER_BIT_WIDTH (9) -#define I2S_LL_MCLK_DIVIDER_MAX ((1 << I2S_LL_MCLK_DIVIDER_BIT_WIDTH) - 1) +#define I2S_LL_CLK_FRAC_DIV_N_MAX 256 // I2S_MCLK = I2S_SRC_CLK / (N + b/a), the N register is 8 bit-width +#define I2S_LL_CLK_FRAC_DIV_AB_MAX 512 // I2S_MCLK = I2S_SRC_CLK / (N + b/a), the a/b register is 9 bit-width #define I2S_LL_PLL_F160M_CLK_FREQ (160 * 1000000) // PLL_F160M_CLK: 160MHz #define I2S_LL_DEFAULT_PLL_CLK_FREQ I2S_LL_PLL_F160M_CLK_FREQ // The default PLL clock frequency while using I2S_CLK_SRC_DEFAULT -/** - * @brief I2S clock configuration structure - * @note Fmclk = Fsclk /(integ+numer/denom) - */ -typedef struct { - uint16_t integ; // Integer part of I2S module clock divider - uint16_t denom; // Denominator part of I2S module clock divider - uint16_t numer; // Numerator part of I2S module clock divider -} i2s_ll_mclk_div_t; - /** * @brief I2S module general init, enable I2S clock. * @@ -313,7 +304,7 @@ static inline void i2s_ll_rx_set_raw_clk_div(i2s_dev_t *hw, uint32_t div_int, ui * @param hw Peripheral I2S hardware instance address. * @param mclk_div The mclk division coefficients */ -static inline void i2s_ll_tx_set_mclk(i2s_dev_t *hw, const i2s_ll_mclk_div_t *mclk_div) +static inline void i2s_ll_tx_set_mclk(i2s_dev_t *hw, const hal_utils_clk_div_t *mclk_div) { /* Workaround for inaccurate clock while switching from a relatively low sample rate to a high sample rate * Set to particular coefficients first then update to the target coefficients, @@ -326,13 +317,13 @@ static inline void i2s_ll_tx_set_mclk(i2s_dev_t *hw, const i2s_ll_mclk_div_t *mc uint32_t div_z = 0; uint32_t div_yn1 = 0; /* If any of denominator and numerator is 0, set all the coefficients to 0 */ - if (mclk_div->denom && mclk_div->numer) { - div_yn1 = mclk_div->numer * 2 > mclk_div->denom; - div_z = div_yn1 ? mclk_div->denom - mclk_div->numer : mclk_div->numer; - div_x = mclk_div->denom / div_z - 1; - div_y = mclk_div->denom % div_z; + if (mclk_div->denominator && mclk_div->numerator) { + div_yn1 = mclk_div->numerator * 2 > mclk_div->denominator; + div_z = div_yn1 ? mclk_div->denominator - mclk_div->numerator : mclk_div->numerator; + div_x = mclk_div->denominator / div_z - 1; + div_y = mclk_div->denominator % div_z; } - i2s_ll_tx_set_raw_clk_div(hw, mclk_div->integ, div_x, div_y, div_z, div_yn1); + i2s_ll_tx_set_raw_clk_div(hw, mclk_div->integer, div_x, div_y, div_z, div_yn1); } /** @@ -353,7 +344,7 @@ static inline void i2s_ll_rx_set_bck_div_num(i2s_dev_t *hw, uint32_t val) * @param hw Peripheral I2S hardware instance address. * @param mclk_div The mclk division coefficients */ -static inline void i2s_ll_rx_set_mclk(i2s_dev_t *hw, const i2s_ll_mclk_div_t *mclk_div) +static inline void i2s_ll_rx_set_mclk(i2s_dev_t *hw, const hal_utils_clk_div_t *mclk_div) { /* Workaround for inaccurate clock while switching from a relatively low sample rate to a high sample rate * Set to particular coefficients first then update to the target coefficients, @@ -366,13 +357,13 @@ static inline void i2s_ll_rx_set_mclk(i2s_dev_t *hw, const i2s_ll_mclk_div_t *mc uint32_t div_z = 0; uint32_t div_yn1 = 0; /* If any of denominator and numerator is 0, set all the coefficients to 0 */ - if (mclk_div->denom && mclk_div->numer) { - div_yn1 = mclk_div->numer * 2 > mclk_div->denom; - div_z = div_yn1 ? mclk_div->denom - mclk_div->numer : mclk_div->numer; - div_x = mclk_div->denom / div_z - 1; - div_y = mclk_div->denom % div_z; + if (mclk_div->denominator && mclk_div->numerator) { + div_yn1 = mclk_div->numerator * 2 > mclk_div->denominator; + div_z = div_yn1 ? mclk_div->denominator - mclk_div->numerator : mclk_div->numerator; + div_x = mclk_div->denominator / div_z - 1; + div_y = mclk_div->denominator % div_z; } - i2s_ll_rx_set_raw_clk_div(hw, mclk_div->integ, div_x, div_y, div_z, div_yn1); + i2s_ll_rx_set_raw_clk_div(hw, mclk_div->integer, div_x, div_y, div_z, div_yn1); } /** diff --git a/components/hal/esp32h2/include/hal/i2s_ll.h b/components/hal/esp32h2/include/hal/i2s_ll.h index d8a0b7ad04..8ebfa8518b 100644 --- a/components/hal/esp32h2/include/hal/i2s_ll.h +++ b/components/hal/esp32h2/include/hal/i2s_ll.h @@ -19,6 +19,7 @@ #include "soc/i2s_struct.h" #include "soc/pcr_struct.h" #include "hal/i2s_types.h" +#include "hal/hal_utils.h" #ifdef __cplusplus @@ -30,23 +31,13 @@ extern "C" { #define I2S_LL_TDM_CH_MASK (0xffff) #define I2S_LL_PDM_BCK_FACTOR (64) -#define I2S_LL_MCLK_DIVIDER_BIT_WIDTH (9) -#define I2S_LL_MCLK_DIVIDER_MAX ((1 << I2S_LL_MCLK_DIVIDER_BIT_WIDTH) - 1) +#define I2S_LL_CLK_FRAC_DIV_N_MAX 256 // I2S_MCLK = I2S_SRC_CLK / (N + b/a), the N register is 8 bit-width +#define I2S_LL_CLK_FRAC_DIV_AB_MAX 512 // I2S_MCLK = I2S_SRC_CLK / (N + b/a), the a/b register is 9 bit-width #define I2S_LL_PLL_F96M_CLK_FREQ (96 * 1000000) // PLL_F96M_CLK: 96MHz #define I2S_LL_PLL_F64M_CLK_FREQ (64 * 1000000) // PLL_F64M_CLK: 64MHz #define I2S_LL_DEFAULT_PLL_CLK_FREQ I2S_LL_PLL_F96M_CLK_FREQ // The default PLL clock frequency while using I2S_CLK_SRC_DEFAULT -/** - * @brief I2S clock configuration structure - * @note Fmclk = Fsclk /(integ+numer/denom) - */ -typedef struct { - uint16_t integ; // Integer part of I2S module clock divider - uint16_t denom; // Denominator part of I2S module clock divider - uint16_t numer; // Numerator part of I2S module clock divider -} i2s_ll_mclk_div_t; - /** * @brief I2S module general init, enable I2S clock. * @@ -320,7 +311,7 @@ static inline void i2s_ll_rx_set_raw_clk_div(i2s_dev_t *hw, uint32_t div_int, ui * @param hw Peripheral I2S hardware instance address. * @param mclk_div The mclk division coefficients */ -static inline void i2s_ll_tx_set_mclk(i2s_dev_t *hw, const i2s_ll_mclk_div_t *mclk_div) +static inline void i2s_ll_tx_set_mclk(i2s_dev_t *hw, const hal_utils_clk_div_t *mclk_div) { /* Workaround for inaccurate clock while switching from a relatively low sample rate to a high sample rate * Set to particular coefficients first then update to the target coefficients, @@ -333,13 +324,13 @@ static inline void i2s_ll_tx_set_mclk(i2s_dev_t *hw, const i2s_ll_mclk_div_t *mc uint32_t div_z = 0; uint32_t div_yn1 = 0; /* If any of denominator and numerator is 0, set all the coefficients to 0 */ - if (mclk_div->denom && mclk_div->numer) { - div_yn1 = mclk_div->numer * 2 > mclk_div->denom; - div_z = div_yn1 ? mclk_div->denom - mclk_div->numer : mclk_div->numer; - div_x = mclk_div->denom / div_z - 1; - div_y = mclk_div->denom % div_z; + if (mclk_div->denominator && mclk_div->numerator) { + div_yn1 = mclk_div->numerator * 2 > mclk_div->denominator; + div_z = div_yn1 ? mclk_div->denominator - mclk_div->numerator : mclk_div->numerator; + div_x = mclk_div->denominator / div_z - 1; + div_y = mclk_div->denominator % div_z; } - i2s_ll_tx_set_raw_clk_div(hw, mclk_div->integ, div_x, div_y, div_z, div_yn1); + i2s_ll_tx_set_raw_clk_div(hw, mclk_div->integer, div_x, div_y, div_z, div_yn1); } /** @@ -360,7 +351,7 @@ static inline void i2s_ll_rx_set_bck_div_num(i2s_dev_t *hw, uint32_t val) * @param hw Peripheral I2S hardware instance address. * @param mclk_div The mclk division coefficients */ -static inline void i2s_ll_rx_set_mclk(i2s_dev_t *hw, const i2s_ll_mclk_div_t *mclk_div) +static inline void i2s_ll_rx_set_mclk(i2s_dev_t *hw, const hal_utils_clk_div_t *mclk_div) { /* Workaround for inaccurate clock while switching from a relatively low sample rate to a high sample rate * Set to particular coefficients first then update to the target coefficients, @@ -373,13 +364,13 @@ static inline void i2s_ll_rx_set_mclk(i2s_dev_t *hw, const i2s_ll_mclk_div_t *mc uint32_t div_z = 0; uint32_t div_yn1 = 0; /* If any of denominator and numerator is 0, set all the coefficients to 0 */ - if (mclk_div->denom && mclk_div->numer) { - div_yn1 = mclk_div->numer * 2 > mclk_div->denom; - div_z = div_yn1 ? mclk_div->denom - mclk_div->numer : mclk_div->numer; - div_x = mclk_div->denom / div_z - 1; - div_y = mclk_div->denom % div_z; + if (mclk_div->denominator && mclk_div->numerator) { + div_yn1 = mclk_div->numerator * 2 > mclk_div->denominator; + div_z = div_yn1 ? mclk_div->denominator - mclk_div->numerator : mclk_div->numerator; + div_x = mclk_div->denominator / div_z - 1; + div_y = mclk_div->denominator % div_z; } - i2s_ll_rx_set_raw_clk_div(hw, mclk_div->integ, div_x, div_y, div_z, div_yn1); + i2s_ll_rx_set_raw_clk_div(hw, mclk_div->integer, div_x, div_y, div_z, div_yn1); } /** diff --git a/components/hal/esp32s2/include/hal/i2s_ll.h b/components/hal/esp32s2/include/hal/i2s_ll.h index aaed7ac505..4b419047e5 100644 --- a/components/hal/esp32s2/include/hal/i2s_ll.h +++ b/components/hal/esp32s2/include/hal/i2s_ll.h @@ -19,6 +19,7 @@ #include "soc/i2s_periph.h" #include "soc/i2s_struct.h" #include "hal/i2s_types.h" +#include "hal/hal_utils.h" #ifdef __cplusplus @@ -30,8 +31,9 @@ extern "C" { #define I2S_LL_BCK_MAX_PRESCALE (64) -#define I2S_LL_MCLK_DIVIDER_BIT_WIDTH (6) -#define I2S_LL_MCLK_DIVIDER_MAX ((1 << I2S_LL_MCLK_DIVIDER_BIT_WIDTH) - 1) +#define I2S_LL_CLK_FRAC_DIV_N_MAX 256 // I2S_MCLK = I2S_SRC_CLK / (N + b/a), the N register is 8 bit-width +#define I2S_LL_CLK_FRAC_DIV_AB_MAX 64 // I2S_MCLK = I2S_SRC_CLK / (N + b/a), the a/b register is 6 bit-width + #define I2S_LL_EVENT_RX_EOF BIT(9) #define I2S_LL_EVENT_TX_EOF BIT(12) @@ -45,16 +47,6 @@ extern "C" { #define I2S_LL_PLL_F160M_CLK_FREQ (160 * 1000000) // PLL_F160M_CLK: 160MHz #define I2S_LL_DEFAULT_PLL_CLK_FREQ I2S_LL_PLL_F160M_CLK_FREQ // The default PLL clock frequency while using I2S_CLK_SRC_DEFAULT -/** - * @brief I2S clock configuration structure - * @note Fmclk = Fsclk /(integ+numer/denom) - */ -typedef struct { - uint16_t integ; // Integer part of I2S module clock divider - uint16_t denom; // Denominator part of I2S module clock divider - uint16_t numer; // Numerator part of I2S module clock divider -} i2s_ll_mclk_div_t; - /** * @brief Enable DMA descriptor owner check * @@ -303,14 +295,14 @@ static inline void i2s_ll_set_raw_mclk_div(i2s_dev_t *hw, uint32_t mclk_div, uin * @param hw Peripheral I2S hardware instance address. * @param mclk_div The mclk division coefficients */ -static inline void i2s_ll_tx_set_mclk(i2s_dev_t *hw, const i2s_ll_mclk_div_t *mclk_div) +static inline void i2s_ll_tx_set_mclk(i2s_dev_t *hw, const hal_utils_clk_div_t *mclk_div) { /* Workaround for inaccurate clock while switching from a relatively low sample rate to a high sample rate * Set to particular coefficients first then update to the target coefficients, * otherwise the clock division might be inaccurate. * the general idea is to set a value that unlike to calculate from the regular decimal */ i2s_ll_set_raw_mclk_div(hw, 7, 47, 3); - i2s_ll_set_raw_mclk_div(hw, mclk_div->integ, mclk_div->denom, mclk_div->numer); + i2s_ll_set_raw_mclk_div(hw, mclk_div->integer, mclk_div->denominator, mclk_div->numerator); } /** @@ -331,7 +323,7 @@ static inline void i2s_ll_rx_set_bck_div_num(i2s_dev_t *hw, uint32_t val) * @param hw Peripheral I2S hardware instance address. * @param mclk_div The mclk division coefficients */ -static inline void i2s_ll_rx_set_mclk(i2s_dev_t *hw, const i2s_ll_mclk_div_t *mclk_div) +static inline void i2s_ll_rx_set_mclk(i2s_dev_t *hw, const hal_utils_clk_div_t *mclk_div) { // TX and RX channel on ESP32 shares a same mclk i2s_ll_tx_set_mclk(hw, mclk_div); diff --git a/components/hal/esp32s3/include/hal/i2s_ll.h b/components/hal/esp32s3/include/hal/i2s_ll.h index 4ae55469c4..1beb845b2d 100644 --- a/components/hal/esp32s3/include/hal/i2s_ll.h +++ b/components/hal/esp32s3/include/hal/i2s_ll.h @@ -18,6 +18,7 @@ #include "soc/i2s_periph.h" #include "soc/i2s_struct.h" #include "hal/i2s_types.h" +#include "hal/hal_utils.h" #ifdef __cplusplus @@ -30,22 +31,12 @@ extern "C" { #define I2S_LL_TDM_CH_MASK (0xffff) #define I2S_LL_PDM_BCK_FACTOR (64) -#define I2S_LL_MCLK_DIVIDER_BIT_WIDTH (9) -#define I2S_LL_MCLK_DIVIDER_MAX ((1 << I2S_LL_MCLK_DIVIDER_BIT_WIDTH) - 1) +#define I2S_LL_CLK_FRAC_DIV_N_MAX 256 // I2S_MCLK = I2S_SRC_CLK / (N + b/a), the N register is 8 bit-width +#define I2S_LL_CLK_FRAC_DIV_AB_MAX 512 // I2S_MCLK = I2S_SRC_CLK / (N + b/a), the a/b register is 9 bit-width #define I2S_LL_PLL_F160M_CLK_FREQ (160 * 1000000) // PLL_F160M_CLK: 160MHz #define I2S_LL_DEFAULT_PLL_CLK_FREQ I2S_LL_PLL_F160M_CLK_FREQ // The default PLL clock frequency while using I2S_CLK_SRC_DEFAULT -/** - * @brief I2S clock configuration structure - * @note Fmclk = Fsclk /(integ+numer/denom) - */ -typedef struct { - uint16_t integ; // Integer part of I2S module clock divider - uint16_t denom; // Denominator part of I2S module clock divider - uint16_t numer; // Numerator part of I2S module clock divider -} i2s_ll_mclk_div_t; - /** * @brief I2S module general init, enable I2S clock. * @@ -304,7 +295,7 @@ static inline void i2s_ll_rx_set_raw_clk_div(i2s_dev_t *hw, uint32_t div_int, ui * @param hw Peripheral I2S hardware instance address. * @param mclk_div The mclk division coefficients */ -static inline void i2s_ll_tx_set_mclk(i2s_dev_t *hw, const i2s_ll_mclk_div_t *mclk_div) +static inline void i2s_ll_tx_set_mclk(i2s_dev_t *hw, const hal_utils_clk_div_t *mclk_div) { /* Workaround for inaccurate clock while switching from a relatively low sample rate to a high sample rate * Set to particular coefficients first then update to the target coefficients, @@ -317,13 +308,13 @@ static inline void i2s_ll_tx_set_mclk(i2s_dev_t *hw, const i2s_ll_mclk_div_t *mc uint32_t div_z = 0; uint32_t div_yn1 = 0; /* If any of denominator and numerator is 0, set all the coefficients to 0 */ - if (mclk_div->denom && mclk_div->numer) { - div_yn1 = mclk_div->numer * 2 > mclk_div->denom; - div_z = div_yn1 ? mclk_div->denom - mclk_div->numer : mclk_div->numer; - div_x = mclk_div->denom / div_z - 1; - div_y = mclk_div->denom % div_z; + if (mclk_div->denominator && mclk_div->numerator) { + div_yn1 = mclk_div->numerator * 2 > mclk_div->denominator; + div_z = div_yn1 ? mclk_div->denominator - mclk_div->numerator : mclk_div->numerator; + div_x = mclk_div->denominator / div_z - 1; + div_y = mclk_div->denominator % div_z; } - i2s_ll_tx_set_raw_clk_div(hw, mclk_div->integ, div_x, div_y, div_z, div_yn1); + i2s_ll_tx_set_raw_clk_div(hw, mclk_div->integer, div_x, div_y, div_z, div_yn1); } /** @@ -344,7 +335,7 @@ static inline void i2s_ll_rx_set_bck_div_num(i2s_dev_t *hw, uint32_t val) * @param hw Peripheral I2S hardware instance address. * @param mclk_div The mclk division coefficients */ -static inline void i2s_ll_rx_set_mclk(i2s_dev_t *hw, const i2s_ll_mclk_div_t *mclk_div) +static inline void i2s_ll_rx_set_mclk(i2s_dev_t *hw, const hal_utils_clk_div_t *mclk_div) { /* Workaround for inaccurate clock while switching from a relatively low sample rate to a high sample rate * Set to particular coefficients first then update to the target coefficients, @@ -357,13 +348,13 @@ static inline void i2s_ll_rx_set_mclk(i2s_dev_t *hw, const i2s_ll_mclk_div_t *mc uint32_t div_z = 0; uint32_t div_yn1 = 0; /* If any of denominator and numerator is 0, set all the coefficients to 0 */ - if (mclk_div->denom && mclk_div->numer) { - div_yn1 = mclk_div->numer * 2 > mclk_div->denom; - div_z = div_yn1 ? mclk_div->denom - mclk_div->numer : mclk_div->numer; - div_x = mclk_div->denom / div_z - 1; - div_y = mclk_div->denom % div_z; + if (mclk_div->denominator && mclk_div->numerator) { + div_yn1 = mclk_div->numerator * 2 > mclk_div->denominator; + div_z = div_yn1 ? mclk_div->denominator - mclk_div->numerator : mclk_div->numerator; + div_x = mclk_div->denominator / div_z - 1; + div_y = mclk_div->denominator % div_z; } - i2s_ll_rx_set_raw_clk_div(hw, mclk_div->integ, div_x, div_y, div_z, div_yn1); + i2s_ll_rx_set_raw_clk_div(hw, mclk_div->integer, div_x, div_y, div_z, div_yn1); } diff --git a/components/hal/hal_utils.c b/components/hal/hal_utils.c index 1d9847c9ae..1213aa49cf 100644 --- a/components/hal/hal_utils.c +++ b/components/hal/hal_utils.c @@ -5,6 +5,7 @@ */ #include "hal/hal_utils.h" +#include "hal/assert.h" /** * @brief helper function, calculate the Greatest Common Divisor @@ -31,36 +32,42 @@ static inline uint32_t _sub_abs(uint32_t a, uint32_t b) return a > b ? a - b : b - a; } -uint32_t hal_utils_calc_clk_div_fast(const hal_utils_clk_info_t *clk_info, hal_utils_clk_div_t *clk_div) +uint32_t hal_utils_calc_clk_div_frac_fast(const hal_utils_clk_info_t *clk_info, hal_utils_clk_div_t *clk_div) { - uint32_t div_denom = 1; + HAL_ASSERT(clk_info->max_fract > 2); + uint32_t div_denom = 2; uint32_t div_numer = 0; uint32_t div_integ = clk_info->src_freq_hz / clk_info->exp_freq_hz; uint32_t freq_error = clk_info->src_freq_hz % clk_info->exp_freq_hz; + // fractional divider + if (freq_error) { + // Carry bit if the decimal is greater than 1.0 - 1.0 / ((max_fract - 1) * 2) + if (freq_error < clk_info->exp_freq_hz - clk_info->exp_freq_hz / (clk_info->max_fract - 1) * 2) { + // Calculate the Greatest Common Divisor, time complexity O(log n) + uint32_t gcd = _gcd(clk_info->exp_freq_hz, freq_error); + // divide by the Greatest Common Divisor to get the accurate fraction before normalization + div_denom = clk_info->exp_freq_hz / gcd; + div_numer = freq_error / gcd; + // normalize div_denom and div_numer + uint32_t d = div_denom / clk_info->max_fract + 1; + // divide by the normalization coefficient to get the denominator and numerator within range of clk_info->max_fract + div_denom /= d; + div_numer /= d; + } else { + div_integ++; + } + } + // If the expect frequency is too high or too low to satisfy the integral division range, failed and return 0 - if (div_integ < clk_info->min_integ || div_integ > clk_info->max_integ) { + if (div_integ < clk_info->min_integ || div_integ >= clk_info->max_integ || div_integ == 0) { return 0; } - // fractional divider - if (freq_error) { - // Calculate the Greatest Common Divisor, time complexity O(log n) - uint32_t gcd = _gcd(clk_info->exp_freq_hz, freq_error); - // divide by the Greatest Common Divisor to get the accurate fraction before normalization - div_denom = clk_info->exp_freq_hz / gcd; - div_numer = freq_error / gcd; - // normalize div_denom and div_numer - uint32_t d = div_denom / clk_info->max_fract + 1; - // divide by the normalization coefficient to get the denominator and numerator within range of clk_info->max_fract - div_denom /= d; - div_numer /= d; - } - // Assign result - clk_div->integ = div_integ; - clk_div->denom = div_denom; - clk_div->numer = div_numer; + clk_div->integer = div_integ; + clk_div->denominator = div_denom; + clk_div->numerator = div_numer; // Return the actual frequency if (div_numer) { @@ -70,23 +77,19 @@ uint32_t hal_utils_calc_clk_div_fast(const hal_utils_clk_info_t *clk_info, hal_u return clk_info->src_freq_hz / div_integ; } -uint32_t hal_utils_calc_clk_div_accurate(const hal_utils_clk_info_t *clk_info, hal_utils_clk_div_t *clk_div) +uint32_t hal_utils_calc_clk_div_frac_accurate(const hal_utils_clk_info_t *clk_info, hal_utils_clk_div_t *clk_div) { - uint32_t div_denom = 1; + HAL_ASSERT(clk_info->max_fract > 2); + uint32_t div_denom = 2; uint32_t div_numer = 0; uint32_t div_integ = clk_info->src_freq_hz / clk_info->exp_freq_hz; uint32_t freq_error = clk_info->src_freq_hz % clk_info->exp_freq_hz; - // If the expect frequency is too high to satisfy the minimum integral division, failed and return 0 - if (div_integ < clk_info->min_integ) { - return 0; - } - if (freq_error) { - // Carry bit if the decimal is greater than 1.0 - 1.0 / (PARLIO_LL_CLK_DIVIDER_MAX * 2) - if (freq_error < clk_info->exp_freq_hz - clk_info->exp_freq_hz / (clk_info->max_fract * 2)) { + // Carry bit if the decimal is greater than 1.0 - 1.0 / ((max_fract - 1) * 2) + if (freq_error < clk_info->exp_freq_hz - clk_info->exp_freq_hz / (clk_info->max_fract - 1) * 2) { // Search the closest fraction, time complexity O(n) - for (uint32_t sub = 0, a = 2, b = 0, min = UINT32_MAX; min && a <= clk_info->max_fract; a++) { + for (uint32_t sub = 0, a = 2, b = 0, min = UINT32_MAX; min && a < clk_info->max_fract; a++) { b = (a * freq_error + clk_info->exp_freq_hz / 2) / clk_info->exp_freq_hz; sub = _sub_abs(clk_info->exp_freq_hz * b, freq_error * a); if (sub < min) { @@ -99,15 +102,16 @@ uint32_t hal_utils_calc_clk_div_accurate(const hal_utils_clk_info_t *clk_info, h div_integ++; } } - // If the expect frequency is too low to satisfy the maximum integral division, failed and return 0 - if (div_integ > clk_info->max_integ) { + + // If the expect frequency is too high or too low to satisfy the integral division range, failed and return 0 + if (div_integ < clk_info->min_integ || div_integ >= clk_info->max_integ || div_integ == 0) { return 0; } // Assign result - clk_div->integ = div_integ; - clk_div->denom = div_denom; - clk_div->numer = div_numer; + clk_div->integer = div_integ; + clk_div->denominator = div_denom; + clk_div->numerator = div_numer; // Return the actual frequency if (div_numer) { @@ -116,3 +120,22 @@ uint32_t hal_utils_calc_clk_div_accurate(const hal_utils_clk_info_t *clk_info, h } return clk_info->src_freq_hz / div_integ; } + +uint32_t hal_utils_calc_clk_div_integer(const hal_utils_clk_info_t *clk_info, uint32_t *int_div) +{ + uint32_t div_integ = clk_info->src_freq_hz / clk_info->exp_freq_hz; + uint32_t freq_error = clk_info->src_freq_hz % clk_info->exp_freq_hz; + + /* If there is error and always round up, + Or, do the normal rounding and error >= (src/n + src/(n+1)) / 2, + then carry the bit */ + if ((freq_error && clk_info->round_opt == HAL_DIV_ROUND_UP) || (clk_info->round_opt == HAL_DIV_ROUND && + (freq_error >= clk_info->src_freq_hz / (2 * div_integ * (div_integ + 1))))) { + div_integ++; + } + + // Assign result + *int_div = div_integ; + // Return the actual frequency + return clk_info->src_freq_hz / div_integ; +} diff --git a/components/hal/i2s_hal.c b/components/hal/i2s_hal.c index 2883b4084c..ee3c94c29c 100644 --- a/components/hal/i2s_hal.c +++ b/components/hal/i2s_hal.c @@ -29,46 +29,18 @@ static const float cut_off_coef[21][3] = { * * @param sclk system clock * @param mclk module clock - * @param integer output the integer part of the division - * @param denominator output the denominator part of the division - * @param numerator output the numerator part of the division + * @param mclk_div output the mclk division coefficients */ -void i2s_hal_calc_mclk_precise_division(uint32_t sclk, uint32_t mclk, i2s_ll_mclk_div_t *mclk_div) +void i2s_hal_calc_mclk_precise_division(uint32_t sclk, uint32_t mclk, hal_utils_clk_div_t *mclk_div) { - int ma = 0; - int mb = 0; - int min = INT32_MAX; - uint32_t div_denom = 1; - uint32_t div_numer = 0; - uint32_t div_inter = sclk / mclk; - uint32_t freq_diff = sclk % mclk; - - if (freq_diff) { - float decimal = freq_diff / (float)mclk; - // Carry bit if the decimal is greater than 1.0 - 1.0 / (I2S_LL_MCLK_DIVIDER_MAX * 2) - if (decimal <= 1.0 - 1.0 / (float)(I2S_LL_MCLK_DIVIDER_MAX * 2)) { - for (int a = 2; a <= I2S_LL_MCLK_DIVIDER_MAX; a++) { - int b = (int)(a * (freq_diff / (double)mclk) + 0.5); - ma = freq_diff * a; - mb = mclk * b; - if (ma == mb) { - div_denom = (uint32_t)a; - div_numer = (uint32_t)b; - break; - } - if (abs(mb - ma) < min) { - div_denom = (uint32_t)a; - div_numer = (uint32_t)b; - min = abs(mb - ma); - } - } - } else { - div_inter++; - } - } - mclk_div->integ = div_inter; - mclk_div->denom = div_denom; - mclk_div->numer = div_numer; + hal_utils_clk_info_t i2s_clk_info = { + .src_freq_hz = sclk, + .exp_freq_hz = mclk, + .max_integ = I2S_LL_CLK_FRAC_DIV_N_MAX, + .min_integ = 1, + .max_fract = I2S_LL_CLK_FRAC_DIV_AB_MAX, + }; + hal_utils_calc_clk_div_frac_accurate(&i2s_clk_info, mclk_div); } void i2s_hal_init(i2s_hal_context_t *hal, int port_id) @@ -79,7 +51,7 @@ void i2s_hal_init(i2s_hal_context_t *hal, int port_id) void i2s_hal_set_tx_clock(i2s_hal_context_t *hal, const i2s_hal_clock_info_t *clk_info, i2s_clock_src_t clk_src) { - i2s_ll_mclk_div_t mclk_div = {}; + hal_utils_clk_div_t mclk_div = {}; #if SOC_I2S_HW_VERSION_2 i2s_ll_tx_enable_clock(hal->dev); i2s_ll_mclk_bind_to_tx_clk(hal->dev); @@ -92,7 +64,7 @@ void i2s_hal_set_tx_clock(i2s_hal_context_t *hal, const i2s_hal_clock_info_t *cl void i2s_hal_set_rx_clock(i2s_hal_context_t *hal, const i2s_hal_clock_info_t *clk_info, i2s_clock_src_t clk_src) { - i2s_ll_mclk_div_t mclk_div = {}; + hal_utils_clk_div_t mclk_div = {}; #if SOC_I2S_HW_VERSION_2 i2s_ll_rx_enable_clock(hal->dev); i2s_ll_mclk_bind_to_rx_clk(hal->dev); diff --git a/components/hal/include/hal/hal_utils.h b/components/hal/include/hal/hal_utils.h index 36ead6fbf4..7f5961ceca 100644 --- a/components/hal/include/hal/hal_utils.h +++ b/components/hal/include/hal/hal_utils.h @@ -12,6 +12,16 @@ extern "C" { #endif +/** + * @brief Integer division operation + * + */ +typedef enum { + HAL_DIV_ROUND_DOWN, /*!< Round the division down to the floor integer */ + HAL_DIV_ROUND_UP, /*!< Round the division up to the ceiling integer */ + HAL_DIV_ROUND, /*!< Round the division to the nearest integer (round up if fraction >= 1/2, round down if fraction < 1/2) */ +} hal_utils_div_round_opt_t; + /** * @brief Clock infomation * @@ -21,7 +31,11 @@ typedef struct { uint32_t exp_freq_hz; /*!< Expected output clock frequency, unit: Hz */ uint32_t max_integ; /*!< The max value of the integral part */ uint32_t min_integ; /*!< The min value of the integral part, integer range: [min_integ, max_integ) */ - uint32_t max_fract; /*!< The max value of the denominator and numerator, numerator range: [0, max_fract), denominator range: [1, max_fract) */ + union { + uint32_t max_fract; /*!< The max value of the denominator and numerator, numerator range: [0, max_fract), denominator range: [1, max_fract) + * Please make sure max_fract > 2 when calculate the division with fractal part */ + hal_utils_div_round_opt_t round_opt; /*!< Integer division operation. For the case that doesn't have fractal part, set this field to the to specify the rounding method */ + }; } hal_utils_clk_info_t; /** @@ -29,36 +43,47 @@ typedef struct { * */ typedef struct { - uint32_t integ; /*!< Integer part of division */ - uint32_t denom; /*!< Denominator part of division */ - uint32_t numer; /*!< Numerator part of division */ + uint32_t integer; /*!< Integer part of division */ + uint32_t denominator; /*!< Denominator part of division */ + uint32_t numerator; /*!< Numerator part of division */ } hal_utils_clk_div_t; /** - * @brief Calculate the clock division + * @brief Calculate the clock division with fractal part fast * @note Speed first algorithm, Time complexity O(log n). * About 8~10 times faster than the accurate algorithm * * @param[in] clk_info The clock infomation - * @param[out] clk_div The clock division + * @param[out] clk_div The clock division with integral and fractal part * @return * - 0: Failed to get the result because the division is out of range * - others: The real output clock frequency */ -uint32_t hal_utils_calc_clk_div_fast(const hal_utils_clk_info_t *clk_info, hal_utils_clk_div_t *clk_div); +uint32_t hal_utils_calc_clk_div_frac_fast(const hal_utils_clk_info_t *clk_info, hal_utils_clk_div_t *clk_div); /** - * @brief Calculate the clock division + * @brief Calculate the clock division with fractal part accurately * @note Accuracy first algorithm, Time complexity O(n). * About 1~hundreds times more accurate than the fast algorithm * * @param[in] clk_info The clock infomation - * @param[out] clk_div The clock division + * @param[out] clk_div The clock division with integral and fractal part * @return * - 0: Failed to get the result because the division is out of range * - others: The real output clock frequency */ -uint32_t hal_utils_calc_clk_div_accurate(const hal_utils_clk_info_t *clk_info, hal_utils_clk_div_t *clk_div); +uint32_t hal_utils_calc_clk_div_frac_accurate(const hal_utils_clk_info_t *clk_info, hal_utils_clk_div_t *clk_div); + +/** + * @brief Calculate the clock division without fractal part + * + * @param[in] clk_info The clock infomation + * @param[out] int_div The clock integral division + * @return + * - 0: Failed to get the result because the division is out of range + * - others: The real output clock frequency + */ +uint32_t hal_utils_calc_clk_div_integer(const hal_utils_clk_info_t *clk_info, uint32_t *int_div); #ifdef __cplusplus } diff --git a/components/hal/include/hal/i2s_hal.h b/components/hal/include/hal/i2s_hal.h index d6ca077e36..e1286b876f 100644 --- a/components/hal/include/hal/i2s_hal.h +++ b/components/hal/include/hal/i2s_hal.h @@ -130,7 +130,7 @@ void i2s_hal_init(i2s_hal_context_t *hal, int port_id); * @param mclk module clock * @param mclk_div mclk division coefficients, including integer part and decimal part */ -void i2s_hal_calc_mclk_precise_division(uint32_t sclk, uint32_t mclk, i2s_ll_mclk_div_t *mclk_div); +void i2s_hal_calc_mclk_precise_division(uint32_t sclk, uint32_t mclk, hal_utils_clk_div_t *mclk_div); /** * @brief Set tx channel clock diff --git a/components/hal/lcd_hal.c b/components/hal/lcd_hal.c index 8dbe8d4851..706f5787dc 100644 --- a/components/hal/lcd_hal.c +++ b/components/hal/lcd_hal.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -7,31 +7,13 @@ #include "hal/lcd_hal.h" #include "hal/lcd_ll.h" #include "hal/log.h" +#include "hal/hal_utils.h" void lcd_hal_init(lcd_hal_context_t *hal, int id) { hal->dev = LCD_LL_GET_HW(id); } -/** - * @brief helper function, calculate the Greatest Common Divisor - * @note gcd(a, b) = gcd(b, a % b) - * @param a bigger value - * @param b smaller value - * @return result of gcd(a, b) - */ -__attribute__((always_inline)) -static inline uint32_t _gcd(uint32_t a, uint32_t b) -{ - uint32_t c = a % b; - while (c != 0) { - a = b; - b = c; - c = a % b; - } - return b; -} - uint32_t lcd_hal_cal_pclk_freq(lcd_hal_context_t *hal, uint32_t src_freq_hz, uint32_t expect_pclk_freq_hz, int lcd_clk_flags) { // lcd_clk = module_clock_src / (n + b / a) @@ -40,30 +22,19 @@ uint32_t lcd_hal_cal_pclk_freq(lcd_hal_context_t *hal, uint32_t src_freq_hz, uin if (mo == 1 && !(lcd_clk_flags & LCD_HAL_PCLK_FLAG_ALLOW_EQUAL_SYSCLK)) { mo = 2; } - uint32_t n = src_freq_hz / expect_pclk_freq_hz / mo; - uint32_t a = 0; - uint32_t b = 0; - // delta_hz / expect_pclk_freq_hz <==> b / a - uint32_t delta_hz = src_freq_hz / mo - expect_pclk_freq_hz * n; - // fractional divider - if (delta_hz) { - uint32_t gcd = _gcd(expect_pclk_freq_hz, delta_hz); - a = expect_pclk_freq_hz / gcd; - b = delta_hz / gcd; - // normalize div_a and div_b - uint32_t d = a / LCD_LL_CLK_FRAC_DIV_AB_MAX + 1; - a /= d; - b /= d; - } + hal_utils_clk_info_t lcd_clk_info = { + .src_freq_hz = src_freq_hz, + .exp_freq_hz = expect_pclk_freq_hz * mo, + .max_integ = LCD_LL_CLK_FRAC_DIV_N_MAX, + .min_integ = 2, + .max_fract = LCD_LL_CLK_FRAC_DIV_AB_MAX, + }; + hal_utils_clk_div_t lcd_clk_div = {}; + uint32_t real_freq = hal_utils_calc_clk_div_frac_fast(&lcd_clk_info, &lcd_clk_div); + HAL_EARLY_LOGD("lcd_hal", "n=%"PRIu32",a=%"PRIu32",b=%"PRIu32",mo=%"PRIu32, lcd_clk_div.integer, lcd_clk_div.denominator, lcd_clk_div.numerator, mo); - HAL_EARLY_LOGD("lcd_hal", "n=%"PRIu32",a=%"PRIu32",b=%"PRIu32",mo=%"PRIu32"", n, a, b, mo); - - lcd_ll_set_group_clock_coeff(hal->dev, n, a, b); + lcd_ll_set_group_clock_coeff(hal->dev, lcd_clk_div.integer, lcd_clk_div.denominator, lcd_clk_div.numerator); lcd_ll_set_pixel_clock_prescale(hal->dev, mo); - if (delta_hz) { - return ((uint64_t)src_freq_hz * a) / (n * a + b) / mo; - } else { - return src_freq_hz / n / mo; - } + return real_freq / mo; } diff --git a/components/hal/test_apps/hal_utils/CMakeLists.txt b/components/hal/test_apps/hal_utils/CMakeLists.txt new file mode 100644 index 0000000000..8a65569576 --- /dev/null +++ b/components/hal/test_apps/hal_utils/CMakeLists.txt @@ -0,0 +1,10 @@ +# For more information about build system see +# https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/build-system.html +# The following five 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) +set(COMPONENTS main) + +project(test_hal_utils) diff --git a/components/hal/test_apps/hal_utils/README.md b/components/hal/test_apps/hal_utils/README.md new file mode 100644 index 0000000000..37c142df16 --- /dev/null +++ b/components/hal/test_apps/hal_utils/README.md @@ -0,0 +1,2 @@ +| Supported Targets | Linux | +| ----------------- | ----- | diff --git a/components/hal/test_apps/hal_utils/main/CMakeLists.txt b/components/hal/test_apps/hal_utils/main/CMakeLists.txt new file mode 100644 index 0000000000..86a4981ded --- /dev/null +++ b/components/hal/test_apps/hal_utils/main/CMakeLists.txt @@ -0,0 +1,5 @@ +idf_component_register(SRCS "test_app_main.c" + "test_calc_clk_div.c" + INCLUDE_DIRS "." + REQUIRES unity hal + WHOLE_ARCHIVE) diff --git a/components/hal/test_apps/hal_utils/main/test_app_main.c b/components/hal/test_apps/hal_utils/main/test_app_main.c new file mode 100644 index 0000000000..be6b1a087c --- /dev/null +++ b/components/hal/test_apps/hal_utils/main/test_app_main.c @@ -0,0 +1,27 @@ +/* + * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include "unity.h" +#include "unity_test_runner.h" + +void app_main(void) +{ +/** + * _ _ _ _ _ _ _____ ___ _ ____ + * | | | | / \ | | | | | |_ _|_ _| | / ___| + * | |_| | / _ \ | | | | | | | | | || | \___ \ + * | _ |/ ___ \| |___ | |_| | | | | || |___ ___) | + * |_| |_/_/ \_\_____| \___/ |_| |___|_____|____/ + */ + printf(" _ _ _ _ _ _ _____ ___ _ ____ \r\n"); + printf(" | | | | / \\ | | | | | |_ _|_ _| | / ___| \r\n"); + printf(" | |_| | / _ \\ | | | | | | | | | || | \\___ \\ \r\n"); + printf(" | _ |/ ___ \\| |___ | |_| | | | | || |___ ___) |\r\n"); + printf(" |_| |_/_/ \\_\\_____| \\___/ |_| |___|_____|____/ \r\n"); + + unity_run_menu(); +} diff --git a/components/hal/test_apps/hal_utils/main/test_calc_clk_div.c b/components/hal/test_apps/hal_utils/main/test_calc_clk_div.c new file mode 100644 index 0000000000..f332cb2269 --- /dev/null +++ b/components/hal/test_apps/hal_utils/main/test_calc_clk_div.c @@ -0,0 +1,150 @@ +/* + * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include "unity.h" +#include "hal/hal_utils.h" + + +TEST_CASE("test_integral_division", "[clk_div]") +{ + uint32_t int_div = 0; + hal_utils_clk_info_t clk_info = { + .src_freq_hz = 80 * 1000 * 1000, + .exp_freq_hz = 57 * 1000 * 1000, + .max_integ = 256, + .min_integ = 1, + .round_opt = 0, // round down + }; + // Round down test + uint32_t real_freq = 0; + real_freq = hal_utils_calc_clk_div_integer(&clk_info, &int_div); + TEST_ASSERT_EQUAL_UINT32(1, int_div); + TEST_ASSERT_EQUAL_UINT32(clk_info.src_freq_hz, real_freq); + + // Normal round test + clk_info.round_opt = 2; + real_freq = hal_utils_calc_clk_div_integer(&clk_info, &int_div); + TEST_ASSERT_EQUAL_UINT32(2, int_div); + TEST_ASSERT_EQUAL_UINT32(clk_info.src_freq_hz / 2, real_freq); + + clk_info.exp_freq_hz = 60 * 1000 * 1000; + real_freq = hal_utils_calc_clk_div_integer(&clk_info, &int_div); + TEST_ASSERT_EQUAL_UINT32(2, int_div); + TEST_ASSERT_EQUAL_UINT32(clk_info.src_freq_hz / 2, real_freq); + + clk_info.exp_freq_hz = 63 * 1000 * 1000; + real_freq = hal_utils_calc_clk_div_integer(&clk_info, &int_div); + TEST_ASSERT_EQUAL_UINT32(1, int_div); + TEST_ASSERT_EQUAL_UINT32(clk_info.src_freq_hz, real_freq); + + // Round up test + clk_info.round_opt = 1; + real_freq = hal_utils_calc_clk_div_integer(&clk_info, &int_div); + TEST_ASSERT_EQUAL_UINT32(2, int_div); + TEST_ASSERT_EQUAL_UINT32(clk_info.src_freq_hz / 2, real_freq); + + // Integral division + clk_info.exp_freq_hz = 40 * 1000 * 1000; + real_freq = hal_utils_calc_clk_div_integer(&clk_info, &int_div); + TEST_ASSERT_EQUAL_UINT32(2, int_div); + TEST_ASSERT_EQUAL_UINT32(clk_info.src_freq_hz / 2, real_freq); + clk_info.round_opt = 0; + real_freq = hal_utils_calc_clk_div_integer(&clk_info, &int_div); + TEST_ASSERT_EQUAL_UINT32(2, int_div); + TEST_ASSERT_EQUAL_UINT32(clk_info.src_freq_hz / 2, real_freq); + clk_info.round_opt = 2; + real_freq = hal_utils_calc_clk_div_integer(&clk_info, &int_div); + TEST_ASSERT_EQUAL_UINT32(2, int_div); + TEST_ASSERT_EQUAL_UINT32(clk_info.src_freq_hz / 2, real_freq); +} + +TEST_CASE("test_fractal_division", "[clk_div]") +{ + hal_utils_clk_div_t clk_div = {}; + hal_utils_clk_info_t clk_info = { + .src_freq_hz = 160 * 1000 * 1000, + .exp_freq_hz = 16 * 1024 * 1024, + .max_integ = 256, + .min_integ = 1, + .max_fract = 512, + }; + uint32_t real_freq = 0; + // Fractal division with error + real_freq = hal_utils_calc_clk_div_frac_fast(&clk_info, &clk_div); + TEST_ASSERT_EQUAL_UINT32(9, clk_div.integer); + TEST_ASSERT_UINT32_WITHIN(clk_info.exp_freq_hz * 0.001, clk_info.exp_freq_hz, real_freq); + real_freq = hal_utils_calc_clk_div_frac_accurate(&clk_info, &clk_div); + TEST_ASSERT_EQUAL_UINT32(9, clk_div.integer); + TEST_ASSERT_UINT32_WITHIN(clk_info.exp_freq_hz * 0.0001, clk_info.exp_freq_hz, real_freq); + + // Fractal division with no error + clk_info.exp_freq_hz = 50 * 1000 * 1000; + real_freq = hal_utils_calc_clk_div_frac_fast(&clk_info, &clk_div); + TEST_ASSERT_EQUAL_UINT32(3, clk_div.integer); + TEST_ASSERT_EQUAL_UINT32(clk_info.exp_freq_hz, real_freq); + real_freq = hal_utils_calc_clk_div_frac_accurate(&clk_info, &clk_div); + TEST_ASSERT_EQUAL_UINT32(3, clk_div.integer); + TEST_ASSERT_EQUAL_UINT32(clk_info.exp_freq_hz, real_freq); + + // Integral division + clk_info.exp_freq_hz = 40 * 1000 * 1000; + real_freq = hal_utils_calc_clk_div_frac_fast(&clk_info, &clk_div); + TEST_ASSERT_EQUAL_UINT32(4, clk_div.integer); + TEST_ASSERT_EQUAL_UINT32(0, clk_div.numerator); + TEST_ASSERT_EQUAL_UINT32(clk_info.exp_freq_hz, real_freq); + real_freq = hal_utils_calc_clk_div_frac_accurate(&clk_info, &clk_div); + TEST_ASSERT_EQUAL_UINT32(4, clk_div.integer); + TEST_ASSERT_EQUAL_UINT32(0, clk_div.numerator); + TEST_ASSERT_EQUAL_UINT32(clk_info.exp_freq_hz, real_freq); +} + +TEST_CASE("test_fault_division", "[clk_div]") +{ + hal_utils_clk_div_t clk_div = {}; + hal_utils_clk_info_t clk_info = { + .src_freq_hz = 160 * 1000 * 1000, + .exp_freq_hz = 1250 * 1000, + .max_integ = 128, + .min_integ = 2, + .max_fract = 512, + }; + uint32_t real_freq = 0; + // Equal to the max integer + real_freq = hal_utils_calc_clk_div_frac_fast(&clk_info, &clk_div); + TEST_ASSERT_FALSE(real_freq); + real_freq = hal_utils_calc_clk_div_frac_accurate(&clk_info, &clk_div); + TEST_ASSERT_FALSE(real_freq); + + // Exceed the max integer + clk_info.exp_freq_hz = 1000 * 1000; + real_freq = hal_utils_calc_clk_div_frac_fast(&clk_info, &clk_div); + TEST_ASSERT_FALSE(real_freq); + real_freq = hal_utils_calc_clk_div_frac_accurate(&clk_info, &clk_div); + TEST_ASSERT_FALSE(real_freq); + + // Blow the min integer + clk_info.exp_freq_hz = 125 * 1000 * 1000; + real_freq = hal_utils_calc_clk_div_frac_fast(&clk_info, &clk_div); + TEST_ASSERT_FALSE(real_freq); + real_freq = hal_utils_calc_clk_div_frac_accurate(&clk_info, &clk_div); + TEST_ASSERT_FALSE(real_freq); + + // Equal to the min integer + clk_info.exp_freq_hz = 80 * 1000 * 1000; + real_freq = hal_utils_calc_clk_div_frac_fast(&clk_info, &clk_div); + TEST_ASSERT_EQUAL_UINT32(clk_info.exp_freq_hz, real_freq); + real_freq = hal_utils_calc_clk_div_frac_accurate(&clk_info, &clk_div); + TEST_ASSERT_EQUAL_UINT32(clk_info.exp_freq_hz, real_freq); + + // divide 0 case + clk_info.exp_freq_hz = 200 * 1000 * 1000; + clk_info.min_integ = 0; + real_freq = hal_utils_calc_clk_div_frac_fast(&clk_info, &clk_div); + TEST_ASSERT_FALSE(real_freq); + real_freq = hal_utils_calc_clk_div_frac_accurate(&clk_info, &clk_div); + TEST_ASSERT_FALSE(real_freq); +} diff --git a/components/hal/test_apps/hal_utils/pytest_hal_utils.py b/components/hal/test_apps/hal_utils/pytest_hal_utils.py new file mode 100644 index 0000000000..425909be33 --- /dev/null +++ b/components/hal/test_apps/hal_utils/pytest_hal_utils.py @@ -0,0 +1,11 @@ +# SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: Apache-2.0 + +import pytest +from pytest_embedded import Dut + + +@pytest.mark.linux +@pytest.mark.host_test +def test_hal_utils(dut: Dut) -> None: + dut.run_all_single_board_cases() diff --git a/components/hal/test_apps/hal_utils/sdkconfig.defaults b/components/hal/test_apps/hal_utils/sdkconfig.defaults new file mode 100644 index 0000000000..d30b539092 --- /dev/null +++ b/components/hal/test_apps/hal_utils/sdkconfig.defaults @@ -0,0 +1,2 @@ +CONFIG_IDF_TARGET="linux" +CONFIG_ESP_TASK_WDT_EN=n