diff --git a/components/bootloader_support/src/bootloader_clock.c b/components/bootloader_support/src/bootloader_clock.c index 2937210da..64ef9c233 100644 --- a/components/bootloader_support/src/bootloader_clock.c +++ b/components/bootloader_support/src/bootloader_clock.c @@ -55,7 +55,7 @@ void bootloader_clock_configure() */ #ifdef CONFIG_ESP32_RTC_CLOCK_SOURCE_EXTERNAL_CRYSTAL if (!rtc_clk_32k_enabled()) { - rtc_clk_32k_bootstrap(); + rtc_clk_32k_bootstrap(CONFIG_ESP32_RTC_XTAL_BOOTSTRAP_CYCLES); } #endif } diff --git a/components/esp32/Kconfig b/components/esp32/Kconfig index b57856234..ea12b1de8 100644 --- a/components/esp32/Kconfig +++ b/components/esp32/Kconfig @@ -700,6 +700,16 @@ config ESP32_RTC_CLK_CAL_CYCLES - 150000 Hz if internal RC oscillator is used as clock source - 32768 Hz if the 32k crystal oscillator is used +config ESP32_RTC_XTAL_BOOTSTRAP_CYCLES + int "Bootstrap cycles for external 32kHz crystal" + default 100 + range 0 32768 + help + To reduce the startup time of an external RTC crystal, + we bootstrap it with a 32kHz square wave for a fixed number of cycles. + Setting 0 will disable bootstrapping (if disabled, the crystal may take + longer to start up or fail to oscillate under some conditions). + config ESP32_DEEP_SLEEP_WAKEUP_DELAY int "Extra delay in deep sleep wake stub (in us)" default 2000 diff --git a/components/esp32/clk.c b/components/esp32/clk.c index f9621a204..0aa931acd 100644 --- a/components/esp32/clk.c +++ b/components/esp32/clk.c @@ -127,44 +127,54 @@ void IRAM_ATTR ets_update_cpu_frequency(uint32_t ticks_per_us) static void select_rtc_slow_clk(rtc_slow_freq_t slow_clk) { - if (slow_clk == RTC_SLOW_FREQ_32K_XTAL) { - /* 32k XTAL oscillator needs to be enabled and running before it can - * be used. Hardware doesn't have a direct way of checking if the - * oscillator is running. Here we use rtc_clk_cal function to count - * the number of main XTAL cycles in the given number of 32k XTAL - * oscillator cycles. If the 32k XTAL has not started up, calibration - * will time out, returning 0. - */ - rtc_clk_32k_enable(true); - uint32_t cal_val = 0; - uint32_t wait = 0; - // increment of 'wait' counter equivalent to 3 seconds - const uint32_t warning_timeout = 3 /* sec */ * 32768 /* Hz */ / (2 * XTAL_32K_DETECT_CYCLES); - ESP_EARLY_LOGD(TAG, "waiting for 32k oscillator to start up") - do { - ++wait; - cal_val = rtc_clk_cal(RTC_CAL_32K_XTAL, XTAL_32K_DETECT_CYCLES); - if (wait % warning_timeout == 0) { - ESP_EARLY_LOGW(TAG, "still waiting for 32k oscillator to start up"); - } - } while (cal_val == 0); - ESP_EARLY_LOGD(TAG, "32k oscillator ready, wait=%d", wait); - } - rtc_clk_slow_freq_set(slow_clk); - uint32_t cal_val; - if (SLOW_CLK_CAL_CYCLES > 0) { - /* TODO: 32k XTAL oscillator has some frequency drift at startup. - * Improve calibration routine to wait until the frequency is stable. - */ - cal_val = rtc_clk_cal(RTC_CAL_RTC_MUX, SLOW_CLK_CAL_CYCLES); - } else { - const uint64_t cal_dividend = (1ULL << RTC_CLK_CAL_FRACT) * 1000000ULL; - cal_val = (uint32_t) (cal_dividend / rtc_clk_slow_freq_get_hz()); - } + uint32_t cal_val = 0; + do { + if (slow_clk == RTC_SLOW_FREQ_32K_XTAL) { + /* 32k XTAL oscillator needs to be enabled and running before it can + * be used. Hardware doesn't have a direct way of checking if the + * oscillator is running. Here we use rtc_clk_cal function to count + * the number of main XTAL cycles in the given number of 32k XTAL + * oscillator cycles. If the 32k XTAL has not started up, calibration + * will time out, returning 0. + */ + uint32_t wait = 0; + // increment of 'wait' counter equivalent to 3 seconds + const uint32_t warning_timeout = 3 /* sec */ * 32768 /* Hz */ / (2 * XTAL_32K_DETECT_CYCLES); + ESP_EARLY_LOGD(TAG, "waiting for 32k oscillator to start up") + do { + ++wait; + rtc_clk_32k_enable(true); + cal_val = rtc_clk_cal(RTC_CAL_32K_XTAL, XTAL_32K_DETECT_CYCLES); + if (wait % warning_timeout == 0) { + ESP_EARLY_LOGW(TAG, "still waiting for 32k oscillator to start up"); + } + if(cal_val == 0){ + rtc_clk_32k_enable(false); + rtc_clk_32k_bootstrap(CONFIG_ESP32_RTC_XTAL_BOOTSTRAP_CYCLES); + } + } while (cal_val == 0); + } + rtc_clk_slow_freq_set(slow_clk); + + if (SLOW_CLK_CAL_CYCLES > 0) { + /* TODO: 32k XTAL oscillator has some frequency drift at startup. + * Improve calibration routine to wait until the frequency is stable. + */ + cal_val = rtc_clk_cal(RTC_CAL_RTC_MUX, SLOW_CLK_CAL_CYCLES); + } else { + const uint64_t cal_dividend = (1ULL << RTC_CLK_CAL_FRACT) * 1000000ULL; + cal_val = (uint32_t) (cal_dividend / rtc_clk_slow_freq_get_hz()); + } + } while (cal_val == 0); ESP_EARLY_LOGD(TAG, "RTC_SLOW_CLK calibration value: %d", cal_val); esp_clk_slowclk_cal_set(cal_val); } +void rtc_clk_select_rtc_slow_clk() +{ + select_rtc_slow_clk(RTC_SLOW_FREQ_32K_XTAL); +} + /* This function is not exposed as an API at this point. * All peripheral clocks are default enabled after chip is powered on. * This function disables some peripheral clocks when cpu starts. diff --git a/components/esp32/esp_clk_internal.h b/components/esp32/esp_clk_internal.h index d1fd434cc..53af81c28 100644 --- a/components/esp32/esp_clk_internal.h +++ b/components/esp32/esp_clk_internal.h @@ -37,3 +37,8 @@ void esp_clk_init(void); * This function disables clock of useless peripherals when cpu starts. */ void esp_perip_clk_init(void); + +/* Selects an external clock source (32 kHz) for RTC. + * Only internal use in unit test. + */ +void rtc_clk_select_rtc_slow_clk(); diff --git a/components/soc/esp32/include/soc/rtc.h b/components/soc/esp32/include/soc/rtc.h index fa34caaa8..ec9935fd5 100644 --- a/components/soc/esp32/include/soc/rtc.h +++ b/components/soc/esp32/include/soc/rtc.h @@ -185,8 +185,11 @@ bool rtc_clk_32k_enabled(); * must be called one the 32k XTAL oscillator has started up. This function * will initially disable the 32k XTAL oscillator, so it should not be called * when the system is using 32k XTAL as RTC_SLOW_CLK. + * + * @param cycle Number of 32kHz cycles to bootstrap external crystal. + * If 0, no square wave will be used to bootstrap crystal oscillation. */ -void rtc_clk_32k_bootstrap(); +void rtc_clk_32k_bootstrap(uint32_t cycle); /** * @brief Enable or disable 8 MHz internal oscillator @@ -604,7 +607,6 @@ rtc_vddsdio_config_t rtc_vddsdio_get_config(); */ void rtc_vddsdio_set_config(rtc_vddsdio_config_t config); - #ifdef __cplusplus } #endif diff --git a/components/soc/esp32/rtc_clk.c b/components/soc/esp32/rtc_clk.c index 54dea84c5..3e4680851 100644 --- a/components/soc/esp32/rtc_clk.c +++ b/components/soc/esp32/rtc_clk.c @@ -19,6 +19,7 @@ #include "rom/ets_sys.h" #include "rom/rtc.h" #include "rom/uart.h" +#include "rom/gpio.h" #include "soc/rtc.h" #include "soc/rtc_cntl_reg.h" #include "soc/rtc_io_reg.h" @@ -122,11 +123,37 @@ void rtc_clk_32k_enable(bool enable) } } -void rtc_clk_32k_bootstrap() +/* Helping external 32kHz crystal to start up. + * External crystal connected to outputs GPIO32 GPIO33. + * Forms N pulses with a frequency of about 32KHz on the outputs of the crystal. + */ +void rtc_clk_32k_bootstrap(uint32_t cycle) { + if (cycle){ + const uint32_t pin_32 = 32; + const uint32_t pin_33 = 33; + const uint32_t mask_32 = (1 << (pin_32 - 32)); + const uint32_t mask_33 = (1 << (pin_33 - 32)); + + gpio_pad_select_gpio(pin_32); + gpio_pad_select_gpio(pin_33); + gpio_output_set_high(mask_32, mask_33, mask_32 | mask_33, 0); + + const uint32_t delay_us = (1000000 / RTC_SLOW_CLK_FREQ_32K / 2); + while(cycle){ + gpio_output_set_high(mask_32, mask_33, mask_32 | mask_33, 0); + ets_delay_us(delay_us); + gpio_output_set_high(mask_33, mask_32, mask_32 | mask_33, 0); + ets_delay_us(delay_us); + cycle--; + } + gpio_output_set_high(0, 0, 0, mask_32 | mask_33); // disable pins + } + CLEAR_PERI_REG_MASK(RTC_IO_XTAL_32K_PAD_REG, RTC_IO_XPD_XTAL_32K); SET_PERI_REG_MASK(RTC_IO_XTAL_32K_PAD_REG, RTC_IO_X32P_RUE | RTC_IO_X32N_RDE); ets_delay_us(XTAL_32K_BOOTSTRAP_TIME_US); + rtc_clk_32k_enable_internal(XTAL_32K_BOOTSTRAP_DAC_VAL, XTAL_32K_BOOTSTRAP_DRES_VAL, XTAL_32K_BOOTSTRAP_DBIAS_VAL); } diff --git a/components/soc/esp32/test/test_rtc_clk.c b/components/soc/esp32/test/test_rtc_clk.c index 3773f9401..b8f7b3109 100644 --- a/components/soc/esp32/test/test_rtc_clk.c +++ b/components/soc/esp32/test/test_rtc_clk.c @@ -12,6 +12,8 @@ #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "freertos/semphr.h" +#include "../esp_clk_internal.h" + #define CALIBRATE_ONE(cali_clk) calibrate_one(cali_clk, #cali_clk) @@ -131,4 +133,52 @@ TEST_CASE("Test fast switching between PLL and XTAL", "[rtc_clk]") test_clock_switching(rtc_clk_cpu_freq_set_fast); } +#define COUNT_TEST 10 +#define TIMEOUT_TEST_MS 50 +void stop_rtc_external_quartz(){ + const uint8_t pin_32 = 32; + const uint8_t pin_33 = 33; + const uint8_t mask_32 = (1 << (pin_32 - 32)); + const uint8_t mask_33 = (1 << (pin_33 - 32)); + + rtc_clk_32k_enable(false); + + gpio_pad_select_gpio(pin_32); + gpio_pad_select_gpio(pin_33); + gpio_output_set_high(0, mask_32 | mask_33, mask_32 | mask_33, 0); + ets_delay_us(500000); + gpio_output_set_high(0, 0, 0, mask_32 | mask_33); // disable pins +} + +TEST_CASE("Test starting external RTC quartz", "[rtc_clk]") +{ + int i = 0, fail = 0; + uint32_t start_time; + uint32_t end_time; + + stop_rtc_external_quartz(); + printf("Start test. Number of oscillation cycles = %d\n", CONFIG_ESP32_RTC_XTAL_BOOTSTRAP_CYCLES); + while(i < COUNT_TEST){ + start_time = xTaskGetTickCount() * (1000 / configTICK_RATE_HZ); + i++; + printf("attempt #%d/%d...", i, COUNT_TEST); + rtc_clk_32k_bootstrap(CONFIG_ESP32_RTC_XTAL_BOOTSTRAP_CYCLES); + rtc_clk_select_rtc_slow_clk(); + end_time = xTaskGetTickCount() * (1000 / configTICK_RATE_HZ); + if((end_time - start_time) > TIMEOUT_TEST_MS){ + printf("FAIL\n"); + fail = 1; + } else { + printf("PASS\n"); + } + stop_rtc_external_quartz(); + ets_delay_us(100000); + } + if (fail == 1){ + printf("Test failed\n"); + TEST_ASSERT(false); + } else { + printf("Test passed successfully\n"); + } +}