From eff3ac05b39f862d733f540dd340e098404736c5 Mon Sep 17 00:00:00 2001 From: kooho <2229179028@qq.com> Date: Thu, 1 Nov 2018 12:23:11 +0800 Subject: [PATCH] driver(ledc): fixed ledc clock selection bug for release/v4.0 --- components/driver/include/driver/ledc.h | 10 ++ components/driver/ledc.c | 111 +++++++++++++----- components/driver/test/test_ledc.c | 9 ++ components/driver/test/test_pcnt.c | 2 + .../internal_communication/main/mesh_light.c | 3 +- .../mesh/manual_networking/main/mesh_light.c | 3 +- .../peripherals/ledc/main/ledc_example_main.c | 3 +- .../peripherals/pcnt/main/pcnt_example_main.c | 1 + 8 files changed, 107 insertions(+), 35 deletions(-) diff --git a/components/driver/include/driver/ledc.h b/components/driver/include/driver/ledc.h index 6a82c19a2..62736a2ac 100644 --- a/components/driver/include/driver/ledc.h +++ b/components/driver/include/driver/ledc.h @@ -51,6 +51,13 @@ typedef enum { LEDC_APB_CLK, /*!< LEDC timer clock divided from APB clock (80Mhz) */ } ledc_clk_src_t; +typedef enum { + LEDC_AUTO_CLK, /*!< The driver will automatically select the source clock(REF_TICK or APB) based on the giving resolution and duty parameter when init the timer*/ + LEDC_USE_REF_TICK, /*!< LEDC timer select REF_TICK clock as source clock*/ + LEDC_USE_APB_CLK, /*!< LEDC timer select APB clock as source clock*/ + LEDC_USE_RTC8M_CLK, /*!< LEDC timer select RTC8M_CLK as source clock. Only for low speed channels and this parameter must be the same for all low speed channels*/ +} ledc_clk_cfg_t; + typedef enum { LEDC_TIMER_0 = 0, /*!< LEDC timer 0 */ LEDC_TIMER_1, /*!< LEDC timer 1 */ @@ -125,6 +132,9 @@ typedef struct { }; ledc_timer_t timer_num; /*!< The timer source of channel (0 - 3) */ uint32_t freq_hz; /*!< LEDC timer frequency (Hz) */ + ledc_clk_cfg_t clk_cfg; /*!< Configure LEDC source clock. + For low speed channels and high speed channels, you can specify the source clock using LEDC_USE_REF_TICK, LEDC_USE_APB_CLK or LEDC_AUTO_CLK. + For low speed channels, you can also specify the source clock using LEDC_USE_RTC8M_CLK, in this case, all low speed channel's source clock must be RTC8M_CLK*/ } ledc_timer_config_t; typedef intr_handle_t ledc_isr_handle_t; diff --git a/components/driver/ledc.c b/components/driver/ledc.c index 92a055cbc..1d677e9d1 100644 --- a/components/driver/ledc.c +++ b/components/driver/ledc.c @@ -19,6 +19,7 @@ #include "soc/gpio_periph.h" #include "driver/ledc.h" #include "soc/ledc_periph.h" +#include "soc/rtc.h" #include "esp_log.h" static const char* LEDC_TAG = "ledc"; @@ -51,12 +52,17 @@ static ledc_isr_handle_t s_ledc_fade_isr_handle = NULL; #define LEDC_VAL_NO_CHANGE (-1) #define LEDC_STEP_NUM_MAX (1023) #define LEDC_DUTY_DECIMAL_BIT_NUM (4) +#define DELAY_CLK8M_CLK_SWITCH (5) +#define SLOW_CLK_CYC_CALIBRATE (13) #define LEDC_HPOINT_VAL_MAX (LEDC_HPOINT_HSCH1_V) #define LEDC_FADE_TOO_SLOW_STR "LEDC FADE TOO SLOW" #define LEDC_FADE_TOO_FAST_STR "LEDC FADE TOO FAST" static const char *LEDC_FADE_SERVICE_ERR_STR = "LEDC fade service not installed"; static const char *LEDC_FADE_INIT_ERROR_STR = "LEDC fade channel init error, not enough memory or service not installed"; +//This value will be calibrated when in use. +static uint32_t s_ledc_slow_clk_8M = 0; + static void ledc_ls_timer_update(ledc_mode_t speed_mode, ledc_timer_t timer_sel) { if (speed_mode == LEDC_LOW_SPEED_MODE) { @@ -71,6 +77,23 @@ static IRAM_ATTR void ledc_ls_channel_update(ledc_mode_t speed_mode, ledc_channe } } +//We know that CLK8M is about 8M, but don't know the actual value. So we need to do a calibration. +static bool ledc_slow_clk_calibrate(void) +{ + //Enable CLK8M for LEDC + SET_PERI_REG_MASK(RTC_CNTL_CLK_CONF_REG, RTC_CNTL_DIG_CLK8M_EN_M); + //Waiting for CLK8M to turn on + ets_delay_us(DELAY_CLK8M_CLK_SWITCH); + uint32_t cal_val = rtc_clk_cal(RTC_CAL_8MD256, SLOW_CLK_CYC_CALIBRATE); + if(cal_val == 0) { + ESP_LOGE(LEDC_TAG, "CLK8M_CLK calibration failed"); + return false; + } + s_ledc_slow_clk_8M = 1000000ULL * (1 << RTC_CLK_CAL_FRACT) * 256 / cal_val; + ESP_LOGD(LEDC_TAG, "Calibrate CLK8M_CLK : %d Hz", s_ledc_slow_clk_8M); + return true; +} + static esp_err_t ledc_enable_intr_type(ledc_mode_t speed_mode, uint32_t channel, ledc_intr_type_t type) { LEDC_ARG_CHECK(speed_mode < LEDC_SPEED_MODE_MAX, "speed_mode"); @@ -219,6 +242,60 @@ esp_err_t ledc_isr_register(void (*fn)(void*), void * arg, int intr_alloc_flags, return ret; } +// Setting the LEDC timer divisor with the given source clock, frequency and resolution. +static esp_err_t ledc_set_timer_div(ledc_mode_t speed_mode, ledc_timer_t timer_num, ledc_clk_cfg_t clk_cfg, int freq_hz, int duty_resolution) +{ + uint32_t div_param = 0; + uint32_t precision = ( 0x1 << duty_resolution ); + ledc_clk_src_t timer_clk_src = LEDC_APB_CLK; + + // Calculate the divisor + // User specified source clock(RTC8M_CLK) for low speed channel + if ((speed_mode == LEDC_LOW_SPEED_MODE) && (clk_cfg == LEDC_USE_RTC8M_CLK)) { + if(s_ledc_slow_clk_8M == 0) { + if (ledc_slow_clk_calibrate() == false) { + goto error; + } + } + div_param = ( (uint64_t) s_ledc_slow_clk_8M << 8 ) / freq_hz / precision; + } else { + // Automatically select APB or REF_TICK as the source clock. + if (clk_cfg == LEDC_AUTO_CLK) { + // Try calculating divisor based on LEDC_APB_CLK + div_param = ( (uint64_t) LEDC_APB_CLK_HZ << 8 ) / freq_hz / precision; + if (div_param > LEDC_DIV_NUM_HSTIMER0_V) { + // APB_CLK results in divisor which too high. Try using REF_TICK as clock source. + timer_clk_src = LEDC_REF_TICK; + div_param = ((uint64_t) LEDC_REF_CLK_HZ << 8) / freq_hz / precision; + } else if (div_param < 256) { + // divisor is too low + goto error; + } + // User specified source clock(LEDC_APB_CLK_HZ or LEDC_REF_TICK) + } else { + timer_clk_src = (clk_cfg == LEDC_USE_APB_CLK) ? LEDC_APB_CLK : LEDC_REF_TICK; + uint32_t sclk_freq = (clk_cfg == LEDC_USE_APB_CLK) ? LEDC_APB_CLK_HZ : LEDC_REF_CLK_HZ; + div_param = ( (uint64_t) sclk_freq << 8 ) / freq_hz / precision; + } + } + if (div_param < 256 || div_param > LEDC_DIV_NUM_LSTIMER0_V) { + goto error; + } + // For low speed channels, if RTC_8MCLK is used as the source clock, the `slow_clk_sel` register should be cleared, otherwise it should be set. + if (speed_mode == LEDC_LOW_SPEED_MODE) { + LEDC.conf.slow_clk_sel = (clk_cfg == LEDC_USE_RTC8M_CLK) ? 0 : 1; + } + //Set the divisor + ledc_timer_set(speed_mode, timer_num, div_param, duty_resolution, timer_clk_src); + // reset the timer + ledc_timer_rst(speed_mode, timer_num); + return ESP_OK; +error: + ESP_LOGE(LEDC_TAG, "requested frequency and duty resolution can not be achieved, try reducing freq_hz or duty_resolution. div_param=%d", + (uint32_t ) div_param); + return ESP_FAIL; +} + esp_err_t ledc_timer_config(const ledc_timer_config_t* timer_conf) { LEDC_ARG_CHECK(timer_conf != NULL, "timer_conf"); @@ -227,6 +304,7 @@ esp_err_t ledc_timer_config(const ledc_timer_config_t* timer_conf) uint32_t timer_num = timer_conf->timer_num; uint32_t speed_mode = timer_conf->speed_mode; LEDC_ARG_CHECK(speed_mode < LEDC_SPEED_MODE_MAX, "speed_mode"); + LEDC_ARG_CHECK(!((timer_conf->clk_cfg == LEDC_USE_RTC8M_CLK) && (speed_mode != LEDC_LOW_SPEED_MODE)), "Only low speed channel support RTC8M_CLK"); periph_module_enable(PERIPH_LEDC_MODULE); if (freq_hz == 0 || duty_resolution == 0 || duty_resolution >= LEDC_TIMER_BIT_MAX) { ESP_LOGE(LEDC_TAG, "freq_hz=%u duty_resolution=%u", freq_hz, duty_resolution); @@ -236,38 +314,7 @@ esp_err_t ledc_timer_config(const ledc_timer_config_t* timer_conf) ESP_LOGE(LEDC_TAG, "invalid timer #%u", timer_num); return ESP_ERR_INVALID_ARG; } - esp_err_t ret = ESP_OK; - uint32_t precision = ( 0x1 << duty_resolution ); // 2**depth - // Try calculating divisor based on LEDC_APB_CLK - ledc_clk_src_t timer_clk_src = LEDC_APB_CLK; - // div_param is a Q10.8 fixed point value - uint64_t div_param = ( (uint64_t) LEDC_APB_CLK_HZ << 8 ) / freq_hz / precision; - if (div_param < 256) { - // divisor is too low - ESP_LOGE(LEDC_TAG, "requested frequency and duty resolution can not be achieved, try reducing freq_hz or duty_resolution. div_param=%d", - (uint32_t ) div_param); - ret = ESP_FAIL; - } - if (div_param > LEDC_DIV_NUM_HSTIMER0_V) { - // APB_CLK results in divisor which too high. Try using REF_TICK as clock source. - timer_clk_src = LEDC_REF_TICK; - div_param = ((uint64_t) LEDC_REF_CLK_HZ << 8) / freq_hz / precision; - if (div_param < 256 || div_param > LEDC_DIV_NUM_HSTIMER0_V) { - ESP_LOGE(LEDC_TAG, "requested frequency and duty resolution can not be achieved, try increasing freq_hz or duty_resolution. div_param=%d", - (uint32_t ) div_param); - ret = ESP_FAIL; - } - } else { - if (speed_mode == LEDC_LOW_SPEED_MODE) { - //for now, we only select 80mhz for slow clk of LEDC low speed channels. - LEDC.conf.slow_clk_sel = 1; - } - } - // set timer parameters - ledc_timer_set(speed_mode, timer_num, div_param, duty_resolution, timer_clk_src); - // reset timer - ledc_timer_rst(speed_mode, timer_num); - return ret; + return ledc_set_timer_div(speed_mode, timer_num, timer_conf->clk_cfg, freq_hz, duty_resolution); } esp_err_t ledc_set_pin(int gpio_num, ledc_mode_t speed_mode, ledc_channel_t ledc_channel) diff --git a/components/driver/test/test_ledc.c b/components/driver/test/test_ledc.c index d0e35851d..b676c4232 100644 --- a/components/driver/test/test_ledc.c +++ b/components/driver/test/test_ledc.c @@ -86,6 +86,7 @@ static void timer_frequency_test(ledc_channel_t channel, ledc_timer_bit_t timer_ .bit_num = timer_bit, .timer_num = timer, .freq_hz = 5000, + .clk_cfg = LEDC_AUTO_CLK, }; TEST_ESP_OK(ledc_channel_config(&ledc_ch_config)); TEST_ESP_OK(ledc_timer_config(&ledc_time_config)); @@ -126,6 +127,7 @@ static void timer_duty_test(ledc_channel_t channel, ledc_timer_bit_t timer_bit, .bit_num = timer_bit, .timer_num = timer, .freq_hz = 5000, + .clk_cfg = LEDC_AUTO_CLK, }; TEST_ESP_OK(ledc_channel_config(&ledc_ch_config)); TEST_ESP_OK(ledc_timer_config(&ledc_time_config)); @@ -188,6 +190,7 @@ TEST_CASE("LEDC error log channel and timer config", "[ledc][test_env=UT_T1_LEDC ledc_time_config.duty_resolution = LEDC_TIMER_13_BIT; ledc_time_config.timer_num = LEDC_TIMER_0; ledc_time_config.freq_hz = 5000; + ledc_time_config.clk_cfg = LEDC_AUTO_CLK; ledc_timer_config_t temp_timer_config = ledc_time_config; TEST_ESP_OK(ledc_timer_config(&ledc_time_config)); @@ -228,6 +231,7 @@ TEST_CASE("LEDC normal channel and timer config", "[ledc][test_env=UT_T1_LEDC]") .bit_num = LEDC_TIMER_13_BIT, .timer_num = LEDC_TIMER_0, .freq_hz = 5000, + .clk_cfg = LEDC_AUTO_CLK, }; ledc_timer_config_t temp_time_config = ledc_time_config; @@ -297,6 +301,7 @@ TEST_CASE("LEDC timer set", "[ledc][test_env=UT_T1_LEDC]") .bit_num = 13, .timer_num = LEDC_TIMER_0, .freq_hz = 5000, + .clk_cfg = LEDC_AUTO_CLK, }; TEST_ESP_OK(ledc_timer_config(&ledc_time_config)); @@ -347,6 +352,7 @@ TEST_CASE("LEDC timer pause and resume", "[ledc][test_env=UT_T1_LEDC]") .duty_resolution = LEDC_TIMER_13_BIT, .timer_num = LEDC_TIMER_0, .freq_hz = 5000, + .clk_cfg = LEDC_AUTO_CLK, }; TEST_ESP_OK(ledc_timer_config(&ledc_time_config)); @@ -392,6 +398,7 @@ TEST_CASE("LEDC fade with time(logic analyzer)", "[ledc][ignore]") .duty_resolution = LEDC_TIMER_13_BIT, .timer_num = LEDC_TIMER_0, .freq_hz = 5000, + .clk_cfg = LEDC_AUTO_CLK, }; TEST_ESP_OK(ledc_timer_config(&ledc_time_config)); @@ -429,6 +436,7 @@ TEST_CASE("LEDC fade with step(logic analyzer)", "[ledc][ignore]") .duty_resolution = LEDC_TIMER_13_BIT, .timer_num = LEDC_TIMER_0, .freq_hz = 5000, + .clk_cfg = LEDC_AUTO_CLK, }; TEST_ESP_OK(ledc_timer_config(&ledc_time_config)); @@ -470,6 +478,7 @@ TEST_CASE("LEDC memory test", "[ledc][test_env=UT_T1_LEDC]") .duty_resolution = LEDC_TIMER_13_BIT, .timer_num = LEDC_TIMER_0, .freq_hz = 5000, + .clk_cfg = LEDC_AUTO_CLK, }; TEST_ESP_OK(ledc_timer_config(&ledc_time_config)); diff --git a/components/driver/test/test_pcnt.c b/components/driver/test/test_pcnt.c index ae5656a84..af4e58fa6 100644 --- a/components/driver/test/test_pcnt.c +++ b/components/driver/test/test_pcnt.c @@ -55,6 +55,7 @@ static void produce_pulse(void) .timer_num = LEDC_TIMER_1, .duty_resolution = LEDC_TIMER_10_BIT, .freq_hz = 1, + .clk_cfg = LEDC_AUTO_CLK, }; ledc_timer_config(&ledc_timer); @@ -160,6 +161,7 @@ static void count_mode_test(gpio_num_t ctl_io) .timer_num = LEDC_TIMER_1, .duty_resolution = LEDC_TIMER_10_BIT, .freq_hz = 100, + .clk_cfg = LEDC_AUTO_CLK, }; ledc_timer_config(&ledc_timer); diff --git a/examples/mesh/internal_communication/main/mesh_light.c b/examples/mesh/internal_communication/main/mesh_light.c index 58a66e152..b1f4c6705 100644 --- a/examples/mesh/internal_communication/main/mesh_light.c +++ b/examples/mesh/internal_communication/main/mesh_light.c @@ -42,7 +42,8 @@ esp_err_t mesh_light_init(void) .bit_num = LEDC_TIMER_13_BIT, .freq_hz = 5000, .speed_mode = LEDC_HIGH_SPEED_MODE, - .timer_num = LEDC_TIMER_0 + .timer_num = LEDC_TIMER_0, + .clk_cfg = LEDC_AUTO_CLK, }; ledc_timer_config(&ledc_timer); diff --git a/examples/mesh/manual_networking/main/mesh_light.c b/examples/mesh/manual_networking/main/mesh_light.c index 478050381..e181be41b 100644 --- a/examples/mesh/manual_networking/main/mesh_light.c +++ b/examples/mesh/manual_networking/main/mesh_light.c @@ -42,7 +42,8 @@ esp_err_t mesh_light_init(void) .bit_num = LEDC_TIMER_13_BIT, .freq_hz = 5000, .speed_mode = LEDC_HIGH_SPEED_MODE, - .timer_num = LEDC_TIMER_0 + .timer_num = LEDC_TIMER_0, + .clk_cfg = LEDC_AUTO_CLK, }; ledc_timer_config(&ledc_timer); diff --git a/examples/peripherals/ledc/main/ledc_example_main.c b/examples/peripherals/ledc/main/ledc_example_main.c index d61e73fba..84ce9f093 100644 --- a/examples/peripherals/ledc/main/ledc_example_main.c +++ b/examples/peripherals/ledc/main/ledc_example_main.c @@ -63,7 +63,8 @@ void app_main() .duty_resolution = LEDC_TIMER_13_BIT, // resolution of PWM duty .freq_hz = 5000, // frequency of PWM signal .speed_mode = LEDC_HS_MODE, // timer mode - .timer_num = LEDC_HS_TIMER // timer index + .timer_num = LEDC_HS_TIMER, // timer index + .clk_cfg = LEDC_AUTO_CLK, // Auto select the source clock }; // Set configuration of timer0 for high speed channels ledc_timer_config(&ledc_timer); diff --git a/examples/peripherals/pcnt/main/pcnt_example_main.c b/examples/peripherals/pcnt/main/pcnt_example_main.c index f734501fc..1dfb1e794 100644 --- a/examples/peripherals/pcnt/main/pcnt_example_main.c +++ b/examples/peripherals/pcnt/main/pcnt_example_main.c @@ -100,6 +100,7 @@ static void ledc_init(void) ledc_timer.timer_num = LEDC_TIMER_1; ledc_timer.duty_resolution = LEDC_TIMER_10_BIT; ledc_timer.freq_hz = 1; // set output frequency at 1 Hz + ledc_timer.clk_cfg = LEDC_AUTO_CLK; ledc_timer_config(&ledc_timer); // Prepare and then apply the LEDC PWM channel configuration