diff --git a/components/driver/i2s.c b/components/driver/i2s.c index 5f7447f67..2b3b15b3f 100644 --- a/components/driver/i2s.c +++ b/components/driver/i2s.c @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. #include +#include #include #include "freertos/FreeRTOS.h" @@ -22,6 +23,8 @@ #include "soc/rtc_cntl_reg.h" #include "soc/rtc_io_reg.h" #include "soc/sens_reg.h" +#include "soc/rtc.h" +#include "soc/efuse_reg.h" #include "rom/lldesc.h" #include "driver/gpio.h" @@ -45,7 +48,9 @@ static const char* I2S_TAG = "I2S"; #define I2S_EXIT_CRITICAL() portEXIT_CRITICAL(&i2s_spinlock[i2s_num]) #define I2S_FULL_DUPLEX_SLAVE_MODE_MASK (I2S_MODE_TX | I2S_MODE_RX | I2S_MODE_SLAVE) #define I2S_FULL_DUPLEX_MASTER_MODE_MASK (I2S_MODE_TX | I2S_MODE_RX | I2S_MODE_MASTER) - +#define APLL_MIN_FREQ (350000000) +#define APLL_MAX_FREQ (500000000) +#define APLL_I2S_MIN_RATE (10675) //in Hz, I2S Clock rate limited by hardware /** * @brief DMA buffer object * @@ -77,12 +82,28 @@ typedef struct { int bytes_per_sample; /*!< Bytes per sample*/ int bits_per_sample; /*!< Bits per sample*/ i2s_mode_t mode; /*!< I2S Working mode*/ + int use_apll; /*!< I2S use APLL clock */ } i2s_obj_t; static i2s_obj_t *p_i2s_obj[I2S_NUM_MAX] = {0}; static i2s_dev_t* I2S[I2S_NUM_MAX] = {&I2S0, &I2S1}; static portMUX_TYPE i2s_spinlock[I2S_NUM_MAX] = {portMUX_INITIALIZER_UNLOCKED, portMUX_INITIALIZER_UNLOCKED}; +/** + * @brief Pre define APLL parameters, save compute time + * | bits_per_sample | rate | sdm0 | sdm1 | sdm2 | odir + */ +static const int apll_predefine[][6] = { + {16, 11025, 38, 80, 5, 31}, + {16, 16000, 147, 107, 5, 21}, + {16, 22050, 130, 152, 5, 15}, + {16, 32000, 129, 212, 5, 10}, + {16, 44100, 15, 8, 5, 6}, + {16, 48000, 136, 212, 5, 6}, + {16, 96000, 143, 212, 5, 2}, + {0, 0, 0, 0, 0, 0} +}; + static i2s_dma_t *i2s_create_dma_queue(i2s_port_t i2s_num, int dma_buf_count, int dma_buf_len); static esp_err_t i2s_destroy_dma_queue(i2s_port_t i2s_num, i2s_dma_t *dma); static esp_err_t i2s_reset_fifo(i2s_port_t i2s_num) @@ -166,6 +187,125 @@ static esp_err_t i2s_isr_register(i2s_port_t i2s_num, uint8_t intr_alloc_flags, return esp_intr_alloc(ETS_I2S0_INTR_SOURCE + i2s_num, intr_alloc_flags, fn, arg, handle); } + +static float i2s_get_apll_real_rate(int bits_per_sample, int sdm0, int sdm1, int sdm2, int odir) +{ + int f_xtal = (int)rtc_clk_xtal_freq_get() * 1000000; + uint32_t is_rev0 = (GET_PERI_REG_BITS2(EFUSE_BLK0_RDATA3_REG, 1, 15) == 0); + if (is_rev0) { + sdm0 = 0; + sdm1 = 0; + } + float fout = f_xtal * (sdm2 + sdm1 / 256.0f + sdm0 / 65536.0f + 4); + if (fout < APLL_MIN_FREQ || fout > APLL_MAX_FREQ) { + return 9999999; + } + float fpll = fout / (2 * (odir+2)); //== fi2s (N=1, b=0, a=1) + return fpll/(8*4*bits_per_sample); //fbck = fi2s/bck_div +} + +/** + * @brief APLL calculate function, was described by following: + * APLL Output frequency is given by the formula: + * + * apll_freq = xtal_freq * (4 + sdm2 + sdm1/256 + sdm0/65536)/((o_div + 2) * 2) + * apll_freq = fout / ((o_div + 2) * 2) + * + * The dividend in this expression should be in the range of 240 - 600 MHz. + * In rev. 0 of ESP32, sdm0 and sdm1 are unused and always set to 0. + * * sdm0 frequency adjustment parameter, 0..255 + * * sdm1 frequency adjustment parameter, 0..255 + * * sdm2 frequency adjustment parameter, 0..63 + * * o_div frequency divider, 0..31 + * + * The most accurate way to find the sdm0..2 and odir parameters is to loop through them all, + * then apply the above formula, finding the closest frequency to the desired one. + * But 256*256*64*32 = 134.217.728 loops are too slow with ESP32 + * 1. We will choose the parameters with the highest level of change, + * With 350MHzclkm_conf.clka_en = 0; - I2S[i2s_num]->clkm_conf.clkm_div_a = 63; - I2S[i2s_num]->clkm_conf.clkm_div_b = clkmDecimals; - I2S[i2s_num]->clkm_conf.clkm_div_num = clkmInteger; - I2S[i2s_num]->sample_rate_conf.tx_bck_div_num = bck; - I2S[i2s_num]->sample_rate_conf.rx_bck_div_num = bck; + int sdm0, sdm1, sdm2, odir; + if(p_i2s_obj[i2s_num]->use_apll && i2s_apll_calculate(rate, bits, &sdm0, &sdm1, &sdm2, &odir) == ESP_OK) { + rtc_clk_apll_enable(1, sdm0, sdm1, sdm2, odir); + I2S[i2s_num]->clkm_conf.clkm_div_num = 1; + I2S[i2s_num]->clkm_conf.clkm_div_b = 0; + I2S[i2s_num]->clkm_conf.clkm_div_a = 1; + I2S[i2s_num]->sample_rate_conf.tx_bck_div_num = 8; + I2S[i2s_num]->sample_rate_conf.rx_bck_div_num = 8; + I2S[i2s_num]->clkm_conf.clka_en = 1; + double real_rate = i2s_get_apll_real_rate(bits, sdm0, sdm1, sdm2, odir); + ESP_LOGI(I2S_TAG, "APLL: Req RATE: %d, real rate: %0.3f, BITS: %u, CLKM: %u, BCK: %u, MCLK: %0.3f, SCLK: %f, diva: %d, divb: %d", + rate, real_rate, bits, 1, 8, (double)I2S_BASE_CLK / mclk, real_rate*bits*channel, 1, 0); + } else { + I2S[i2s_num]->clkm_conf.clka_en = 0; + I2S[i2s_num]->clkm_conf.clkm_div_a = 63; + I2S[i2s_num]->clkm_conf.clkm_div_b = clkmDecimals; + I2S[i2s_num]->clkm_conf.clkm_div_num = clkmInteger; + I2S[i2s_num]->sample_rate_conf.tx_bck_div_num = bck; + I2S[i2s_num]->sample_rate_conf.rx_bck_div_num = bck; + double real_rate = (double) (I2S_BASE_CLK / (bck * bits * clkmInteger) / 2); + ESP_LOGI(I2S_TAG, "PLL_D2: Req RATE: %d, real rate: %0.3f, BITS: %u, CLKM: %u, BCK: %u, MCLK: %0.3f, SCLK: %f, diva: %d, divb: %d", + rate, real_rate, bits, clkmInteger, bck, (double)I2S_BASE_CLK / mclk, real_rate*bits*channel, 64, clkmDecimals); + } + I2S[i2s_num]->sample_rate_conf.tx_bits_mod = bits; I2S[i2s_num]->sample_rate_conf.rx_bits_mod = bits; - double real_rate = (double) (I2S_BASE_CLK / (bck * bits * clkmInteger) / 2); - ESP_LOGI(I2S_TAG, "Req RATE: %d, real rate: %0.3f, BITS: %u, CLKM: %u, BCK: %u, MCLK: %0.3f, SCLK: %f, diva: %d, divb: %d", - rate, real_rate, bits, clkmInteger, bck, (double)I2S_BASE_CLK / mclk, real_rate*bits*channel, 64, clkmDecimals); - + // wait all writing on-going finish if ((p_i2s_obj[i2s_num]->mode & I2S_MODE_TX) && p_i2s_obj[i2s_num]->tx) { xSemaphoreGive(p_i2s_obj[i2s_num]->tx->mux); @@ -535,7 +689,7 @@ esp_err_t i2s_stop(i2s_port_t i2s_num) esp_err_t i2s_set_dac_mode(i2s_dac_mode_t dac_mode) { I2S_CHECK((dac_mode < I2S_DAC_CHANNEL_MAX), "i2s dac mode error", ESP_ERR_INVALID_ARG); - if(dac_mode == I2S_DAC_CHANNEL_DISABLE) { + if (dac_mode == I2S_DAC_CHANNEL_DISABLE) { dac_output_disable(DAC_CHANNEL_1); dac_output_disable(DAC_CHANNEL_2); dac_i2s_disable(); @@ -813,6 +967,8 @@ static esp_err_t i2s_param_config(i2s_port_t i2s_num, const i2s_config_t *i2s_co I2S[i2s_num]->conf.rx_slave_mod = 1; //RX Slave } } + + p_i2s_obj[i2s_num]->use_apll = i2s_config->use_apll; return ESP_OK; } @@ -914,6 +1070,10 @@ esp_err_t i2s_driver_uninstall(i2s_port_t i2s_num) p_i2s_obj[i2s_num]->i2s_queue = NULL; } + if(p_i2s_obj[i2s_num]->use_apll) { + rtc_clk_apll_enable(0, 0, 0, 0, 0); + } + free(p_i2s_obj[i2s_num]); p_i2s_obj[i2s_num] = NULL; diff --git a/components/driver/include/driver/i2s.h b/components/driver/include/driver/i2s.h index 5777e34ba..354dd2a89 100644 --- a/components/driver/include/driver/i2s.h +++ b/components/driver/include/driver/i2s.h @@ -122,6 +122,8 @@ typedef enum { I2S_MODE_PDM = 64, } i2s_mode_t; + + /** * @brief I2S configuration parameters for i2s_param_config function * @@ -135,6 +137,7 @@ typedef struct { int intr_alloc_flags; /*!< Flags used to allocate the interrupt. One or multiple (ORred) ESP_INTR_FLAG_* values. See esp_intr_alloc.h for more info */ int dma_buf_count; /*!< I2S DMA Buffer Count */ int dma_buf_len; /*!< I2S DMA Buffer Length */ + int use_apll; /*!< I2S using APLL as main I2S clock, enable it to get accurate clock */ } i2s_config_t; /** diff --git a/docs/api-reference/peripherals/i2s.rst b/docs/api-reference/peripherals/i2s.rst index f3d372978..0ec2840e8 100644 --- a/docs/api-reference/peripherals/i2s.rst +++ b/docs/api-reference/peripherals/i2s.rst @@ -10,6 +10,8 @@ The I2S peripheral supports DMA meaning it can stream sample data without requir I2S output can also be routed directly to the Digital/Analog Converter output channels (GPIO 25 & GPIO 26) to produce analog output directly, rather than via an external I2S codec. +.. note:: For high accuracy clock applications, APLL clock source can be used with `.use_apll = 1` and ESP32 will automatic caculate APLL parameter. + Application Example ------------------- @@ -34,7 +36,9 @@ Short example of I2S configuration: .communication_format = I2S_COMM_FORMAT_I2S | I2S_COMM_FORMAT_I2S_MSB, .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1, // high interrupt priority .dma_buf_count = 8, - .dma_buf_len = 64 + .dma_buf_len = 64, + .use_apll = 0, + .apll_param = I2S_APLL_NONE }; static const i2s_pin_config_t pin_config = { diff --git a/examples/peripherals/i2s/main/i2s_example_main.c b/examples/peripherals/i2s/main/i2s_example_main.c index 578b1e817..7bdd16c73 100644 --- a/examples/peripherals/i2s/main/i2s_example_main.c +++ b/examples/peripherals/i2s/main/i2s_example_main.c @@ -86,7 +86,8 @@ void app_main() .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT, //2-channels .communication_format = I2S_COMM_FORMAT_I2S | I2S_COMM_FORMAT_I2S_MSB, .dma_buf_count = 6, - .dma_buf_len = 60, // + .dma_buf_len = 60, + .use_apll = 0, .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1 //Interrupt level 1 }; i2s_pin_config_t pin_config = {