From 85aad225d032b2bdc0d6636e072ba34429495074 Mon Sep 17 00:00:00 2001 From: wuzhenghui Date: Fri, 16 May 2025 16:30:11 +0800 Subject: [PATCH 1/4] fix(esp_hw_support): update LACT clock prescale immediately when APB changes on esp32 --- .../include/esp_private/systimer.h | 20 +++++++- .../esp_hw_support/port/esp32/rtc_clk.c | 16 ++++++- components/esp_pm/pm_impl.c | 2 + components/esp_timer/src/esp_timer_impl_lac.c | 46 ++++--------------- components/hal/esp32/include/hal/timer_ll.h | 16 ++++++- 5 files changed, 61 insertions(+), 39 deletions(-) diff --git a/components/esp_hw_support/include/esp_private/systimer.h b/components/esp_hw_support/include/esp_private/systimer.h index 068b3fa91c..38debe6d41 100644 --- a/components/esp_hw_support/include/esp_private/systimer.h +++ b/components/esp_hw_support/include/esp_private/systimer.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -7,6 +7,24 @@ #pragma once #include +#include "sdkconfig.h" + +#if CONFIG_ESP_TIMER_IMPL_TG0_LAC +/* Selects which Timer Group peripheral to use */ +#define LACT_MODULE 0 + +/* Desired number of timer ticks per microsecond. + * This value should be small enough so that all possible APB frequencies + * could be divided by it without remainder. + * On the other hand, the smaller this value is, the longer we need to wait + * after setting UPDATE_REG before the timer value can be read. + * If LACT_TICKS_PER_US == 1, then we need to wait up to 1 microsecond, which + * makes esp_timer_impl_get_time function take too much time. + * The value LACT_TICKS_PER_US == 2 allows for most of the APB frequencies, and + * allows reading the counter quickly enough. + */ +#define LACT_TICKS_PER_US 2 +#endif // we assign the systimer resources statically #define SYSTIMER_COUNTER_ESPTIMER 0 // Counter used by esptimer, to generate the system level wall clock diff --git a/components/esp_hw_support/port/esp32/rtc_clk.c b/components/esp_hw_support/port/esp32/rtc_clk.c index 55c9aef6af..415b24cb93 100644 --- a/components/esp_hw_support/port/esp32/rtc_clk.c +++ b/components/esp_hw_support/port/esp32/rtc_clk.c @@ -8,6 +8,7 @@ #include #include #include +#include "esp_attr.h" #include "soc/rtc.h" #include "esp_private/rtc_clk.h" #include "soc/rtc_periph.h" @@ -26,6 +27,10 @@ #include "hal/clk_tree_ll.h" #include "soc/rtc_cntl_reg.h" #include "soc/io_mux_reg.h" +#ifndef BOOTLOADER_BUILD +#include "esp_private/systimer.h" +#include "hal/timer_ll.h" +#endif #define XTAL_32K_BOOTSTRAP_TIME_US 7 @@ -365,6 +370,9 @@ void rtc_clk_cpu_freq_to_xtal(int cpu_freq, int div) clk_ll_ref_tick_set_divider(SOC_CPU_CLK_SRC_XTAL, cpu_freq); /* switch clock source */ clk_ll_cpu_set_src(SOC_CPU_CLK_SRC_XTAL); +#ifndef BOOTLOADER_BUILD + timer_ll_set_lact_clock_prescale(TIMER_LL_GET_HW(LACT_MODULE), cpu_freq / LACT_TICKS_PER_US); +#endif rtc_clk_apb_freq_update(cpu_freq * MHZ); /* lower the voltage */ int dbias = (cpu_freq <= 2) ? DIG_DBIAS_2M : DIG_DBIAS_XTAL; @@ -380,6 +388,9 @@ static void rtc_clk_cpu_freq_to_8m(void) clk_ll_ref_tick_set_divider(SOC_CPU_CLK_SRC_RC_FAST, 8); /* switch clock source */ clk_ll_cpu_set_src(SOC_CPU_CLK_SRC_RC_FAST); +#ifndef BOOTLOADER_BUILD + timer_ll_set_lact_clock_prescale(TIMER_LL_GET_HW(LACT_MODULE), SOC_CLK_RC_FAST_FREQ_APPROX / MHZ / LACT_TICKS_PER_US); +#endif rtc_clk_apb_freq_update(SOC_CLK_RC_FAST_FREQ_APPROX); } @@ -391,8 +402,11 @@ static void rtc_clk_cpu_freq_to_8m(void) static void rtc_clk_cpu_freq_to_pll_mhz(int cpu_freq_mhz) { int dbias = (cpu_freq_mhz == 240) ? DIG_DBIAS_240M : DIG_DBIAS_80M_160M; - clk_ll_cpu_set_freq_mhz_from_pll(cpu_freq_mhz); REG_SET_FIELD(RTC_CNTL_REG, RTC_CNTL_DIG_DBIAS_WAK, dbias); +#ifndef BOOTLOADER_BUILD + timer_ll_set_lact_clock_prescale(TIMER_LL_GET_HW(LACT_MODULE), 80 / LACT_TICKS_PER_US); +#endif + clk_ll_cpu_set_freq_mhz_from_pll(cpu_freq_mhz); /* adjust ref_tick */ clk_ll_ref_tick_set_divider(SOC_CPU_CLK_SRC_PLL, cpu_freq_mhz); /* switch clock source */ diff --git a/components/esp_pm/pm_impl.c b/components/esp_pm/pm_impl.c index d38a40577c..6f02d5a8f8 100644 --- a/components/esp_pm/pm_impl.c +++ b/components/esp_pm/pm_impl.c @@ -567,12 +567,14 @@ void IRAM_ATTR esp_pm_impl_switch_mode(pm_mode_t mode, */ static void IRAM_ATTR on_freq_update(uint32_t old_ticks_per_us, uint32_t ticks_per_us) { +#if !CONFIG_IDF_TARGET_ESP32 uint32_t old_apb_ticks_per_us = MIN(old_ticks_per_us, 80); uint32_t apb_ticks_per_us = MIN(ticks_per_us, 80); /* Update APB frequency value used by the timer */ if (old_apb_ticks_per_us != apb_ticks_per_us) { esp_timer_private_update_apb_freq(apb_ticks_per_us); } +#endif #ifdef CONFIG_FREERTOS_SYSTICK_USES_CCOUNT #ifdef XT_RTOS_TIMER_INT diff --git a/components/esp_timer/src/esp_timer_impl_lac.c b/components/esp_timer/src/esp_timer_impl_lac.c index d557716e65..90b167b417 100644 --- a/components/esp_timer/src/esp_timer_impl_lac.c +++ b/components/esp_timer/src/esp_timer_impl_lac.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2017-2024 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2017-2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -16,6 +16,7 @@ #include "esp_log.h" #include "esp_private/esp_clk.h" #include "esp_private/periph_ctrl.h" +#include "esp_private/systimer.h" #include "soc/soc.h" #include "soc/timer_group_reg.h" #include "soc/rtc.h" @@ -32,9 +33,6 @@ * The timer can be configured to produce an edge or a level interrupt. */ -/* Selects which Timer Group peripheral to use */ -#define LACT_MODULE 0 - #if LACT_MODULE == 0 #define INTR_SOURCE_LACT ETS_TG0_LACT_LEVEL_INTR_SOURCE #define PERIPH_LACT PERIPH_TIMG0_MODULE @@ -45,18 +43,6 @@ #error "Incorrect the number of LACT module (only 0 or 1)" #endif -/* Desired number of timer ticks per microsecond. - * This value should be small enough so that all possible APB frequencies - * could be divided by it without remainder. - * On the other hand, the smaller this value is, the longer we need to wait - * after setting UPDATE_REG before the timer value can be read. - * If TICKS_PER_US == 1, then we need to wait up to 1 microsecond, which - * makes esp_timer_impl_get_time function take too much time. - * The value TICKS_PER_US == 2 allows for most of the APB frequencies, and - * allows reading the counter quickly enough. - */ -#define TICKS_PER_US 2 - /* Shorter register names, used in this file */ #define CONFIG_REG (TIMG_LACTCONFIG_REG(LACT_MODULE)) #define RTC_STEP_REG (TIMG_LACTRTC_REG(LACT_MODULE)) @@ -139,7 +125,7 @@ uint64_t IRAM_ATTR esp_timer_impl_get_counter_reg(void) int64_t IRAM_ATTR esp_timer_impl_get_time(void) { - return esp_timer_impl_get_counter_reg() / TICKS_PER_US; + return esp_timer_impl_get_counter_reg() / LACT_TICKS_PER_US; } int64_t esp_timer_get_time(void) __attribute__((alias("esp_timer_impl_get_time"))); @@ -151,9 +137,9 @@ void IRAM_ATTR esp_timer_impl_set_alarm_id(uint64_t timestamp, unsigned alarm_id timestamp_id[alarm_id] = timestamp; timestamp = MIN(timestamp_id[0], timestamp_id[1]); if (timestamp != UINT64_MAX) { - int64_t offset = TICKS_PER_US * 2; + int64_t offset = LACT_TICKS_PER_US * 2; uint64_t now_time = esp_timer_impl_get_counter_reg(); - timer_64b_reg_t alarm = { .val = MAX(timestamp * TICKS_PER_US, now_time + offset) }; + timer_64b_reg_t alarm = { .val = MAX(timestamp * LACT_TICKS_PER_US, now_time + offset) }; do { REG_CLR_BIT(CONFIG_REG, TIMG_LACT_ALARM_EN); REG_WRITE(ALARM_LO_REG, alarm.lo); @@ -163,7 +149,7 @@ void IRAM_ATTR esp_timer_impl_set_alarm_id(uint64_t timestamp, unsigned alarm_id int64_t delta = (int64_t)alarm.val - (int64_t)now_time; if (delta <= 0 && REG_GET_FIELD(INT_ST_REG, TIMG_LACT_INT_ST) == 0) { // new alarm is less than the counter and the interrupt flag is not set - offset += llabs(delta) + TICKS_PER_US * 2; + offset += llabs(delta) + LACT_TICKS_PER_US * 2; alarm.val = now_time + offset; } else { // finish if either (alarm > counter) or the interrupt flag is already set. @@ -220,19 +206,10 @@ static void IRAM_ATTR timer_alarm_isr(void *arg) #endif // ISR_HANDLERS != 1 } -void IRAM_ATTR esp_timer_impl_update_apb_freq(uint32_t apb_ticks_per_us) -{ - portENTER_CRITICAL(&s_time_update_lock); - assert(apb_ticks_per_us >= 3 && "divider value too low"); - assert(apb_ticks_per_us % TICKS_PER_US == 0 && "APB frequency (in MHz) should be divisible by TICK_PER_US"); - REG_SET_FIELD(CONFIG_REG, TIMG_LACT_DIVIDER, apb_ticks_per_us / TICKS_PER_US); - portEXIT_CRITICAL(&s_time_update_lock); -} - void esp_timer_impl_set(uint64_t new_us) { portENTER_CRITICAL(&s_time_update_lock); - timer_64b_reg_t dst = { .val = new_us * TICKS_PER_US }; + timer_64b_reg_t dst = { .val = new_us * LACT_TICKS_PER_US }; REG_WRITE(LOAD_LO_REG, dst.lo); REG_WRITE(LOAD_HI_REG, dst.hi); REG_WRITE(LOAD_REG, 1); @@ -261,7 +238,7 @@ esp_err_t esp_timer_impl_early_init(void) REG_WRITE(ALARM_HI_REG, UINT32_MAX); REG_WRITE(LOAD_REG, 1); REG_SET_BIT(INT_CLR_REG, TIMG_LACT_INT_CLR); - REG_SET_FIELD(CONFIG_REG, TIMG_LACT_DIVIDER, APB_CLK_FREQ / 1000000 / TICKS_PER_US); + REG_SET_FIELD(CONFIG_REG, TIMG_LACT_DIVIDER, APB_CLK_FREQ / 1000000 / LACT_TICKS_PER_US); REG_SET_BIT(CONFIG_REG, TIMG_LACT_INCREASE | TIMG_LACT_LEVEL_INT_EN | TIMG_LACT_EN); @@ -296,12 +273,10 @@ esp_err_t esp_timer_impl_init(intr_handler_t alarm_handler) * will not cause issues in practice. */ REG_SET_BIT(INT_ENA_REG, TIMG_LACT_INT_ENA); - - esp_timer_impl_update_apb_freq(esp_clk_apb_freq() / 1000000); - + timer_ll_set_lact_clock_prescale(TIMER_LL_GET_HW(LACT_MODULE), esp_clk_apb_freq() / MHZ / LACT_TICKS_PER_US); // Set the step for the sleep mode when the timer will work // from a slow_clk frequency instead of the APB frequency. - uint32_t slowclk_ticks_per_us = esp_clk_slowclk_cal_get() * TICKS_PER_US; + uint32_t slowclk_ticks_per_us = esp_clk_slowclk_cal_get() * LACT_TICKS_PER_US; REG_SET_FIELD(RTC_STEP_REG, TIMG_LACT_RTC_STEP_LEN, slowclk_ticks_per_us); } @@ -344,6 +319,5 @@ uint64_t esp_timer_impl_get_alarm_reg(void) return alarm.val; } -void esp_timer_private_update_apb_freq(uint32_t apb_ticks_per_us) __attribute__((alias("esp_timer_impl_update_apb_freq"))); void esp_timer_private_set(uint64_t new_us) __attribute__((alias("esp_timer_impl_set"))); void esp_timer_private_advance(int64_t time_diff_us) __attribute__((alias("esp_timer_impl_advance"))); diff --git a/components/hal/esp32/include/hal/timer_ll.h b/components/hal/esp32/include/hal/timer_ll.h index 8fa86e6044..9eb012ac6b 100644 --- a/components/hal/esp32/include/hal/timer_ll.h +++ b/components/hal/esp32/include/hal/timer_ll.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2021-2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -10,6 +10,7 @@ #pragma once #include +#include "esp_attr.h" #include "hal/assert.h" #include "hal/misc.h" #include "hal/timer_types.h" @@ -329,6 +330,19 @@ static inline volatile void *timer_ll_get_intr_status_reg(timg_dev_t *hw) return &hw->int_st_timers.val; } +/** + * @brief Set clock prescale for LACT timer + * + * @param hw Timer Group register base address + * @param timer_num Timer number in the group + * @param divider Prescale value (0 and 1 are not valid) + */ +FORCE_INLINE_ATTR void timer_ll_set_lact_clock_prescale(timg_dev_t *hw, uint32_t divider) +{ + HAL_ASSERT(divider>=2); + HAL_FORCE_MODIFY_U32_REG_FIELD(hw->lactconfig, lact_divider, divider); +} + #ifdef __cplusplus } #endif From bc1624879cfe19278a5b2d7e64bee9d3aa05ddf8 Mon Sep 17 00:00:00 2001 From: wuzhenghui Date: Fri, 16 May 2025 16:51:37 +0800 Subject: [PATCH 2/4] fix(esp_hw_support): update systimer step immediately when XTAL changes on esp32s2 --- components/esp_hw_support/port/esp32s2/rtc_clk.c | 7 +++++++ components/esp_pm/pm_impl.c | 9 --------- .../include/esp_private/esp_timer_private.h | 12 +----------- .../esp_timer/private_include/esp_timer_impl.h | 11 +---------- components/esp_timer/src/esp_timer_impl_systimer.c | 10 +--------- 5 files changed, 10 insertions(+), 39 deletions(-) diff --git a/components/esp_hw_support/port/esp32s2/rtc_clk.c b/components/esp_hw_support/port/esp32s2/rtc_clk.c index 50fa74c697..85e01d5591 100644 --- a/components/esp_hw_support/port/esp32s2/rtc_clk.c +++ b/components/esp_hw_support/port/esp32s2/rtc_clk.c @@ -20,6 +20,10 @@ #include "esp_hw_log.h" #include "sdkconfig.h" #include "hal/clk_tree_ll.h" +#ifndef BOOTLOADER_BUILD +#include "esp_private/systimer.h" +#include "hal/systimer_ll.h" +#endif static const char *TAG = "rtc_clk"; @@ -454,6 +458,9 @@ static void rtc_clk_cpu_freq_to_xtal(int cpu_freq, int div) /* no need to adjust the REF_TICK, default register value already set it to 1MHz with any cpu clock source */ /* switch clock source */ clk_ll_cpu_set_src(SOC_CPU_CLK_SRC_XTAL); +#ifndef BOOTLOADER_BUILD + systimer_ll_set_step_for_xtal(&SYSTIMER, systimer_us_to_ticks(1) / cpu_freq); +#endif rtc_clk_apb_freq_update(cpu_freq * MHZ); /* lower the voltage diff --git a/components/esp_pm/pm_impl.c b/components/esp_pm/pm_impl.c index 6f02d5a8f8..9d9cbd025a 100644 --- a/components/esp_pm/pm_impl.c +++ b/components/esp_pm/pm_impl.c @@ -567,15 +567,6 @@ void IRAM_ATTR esp_pm_impl_switch_mode(pm_mode_t mode, */ static void IRAM_ATTR on_freq_update(uint32_t old_ticks_per_us, uint32_t ticks_per_us) { -#if !CONFIG_IDF_TARGET_ESP32 - uint32_t old_apb_ticks_per_us = MIN(old_ticks_per_us, 80); - uint32_t apb_ticks_per_us = MIN(ticks_per_us, 80); - /* Update APB frequency value used by the timer */ - if (old_apb_ticks_per_us != apb_ticks_per_us) { - esp_timer_private_update_apb_freq(apb_ticks_per_us); - } -#endif - #ifdef CONFIG_FREERTOS_SYSTICK_USES_CCOUNT #ifdef XT_RTOS_TIMER_INT /* Calculate new tick divisor */ diff --git a/components/esp_timer/include/esp_private/esp_timer_private.h b/components/esp_timer/include/esp_private/esp_timer_private.h index 37aaeac03f..c40d96cd40 100644 --- a/components/esp_timer/include/esp_private/esp_timer_private.h +++ b/components/esp_timer/include/esp_private/esp_timer_private.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2017-2022 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2017-2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -24,16 +24,6 @@ #ifdef __cplusplus extern "C" { #endif - -/** - * @brief Notify esp_timer implementation that APB frequency has changed - * - * Called by the frequency switching code. - * - * @param apb_ticks_per_us new number of APB clock ticks per microsecond - */ -void esp_timer_private_update_apb_freq(uint32_t apb_ticks_per_us); - /** * @brief Set esp_timer time to a certain value * diff --git a/components/esp_timer/private_include/esp_timer_impl.h b/components/esp_timer/private_include/esp_timer_impl.h index d6484cbb95..6164cfe7d9 100644 --- a/components/esp_timer/private_include/esp_timer_impl.h +++ b/components/esp_timer/private_include/esp_timer_impl.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2017-2023 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2017-2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -69,15 +69,6 @@ void esp_timer_impl_set_alarm(uint64_t timestamp); */ void esp_timer_impl_set_alarm_id(uint64_t timestamp, unsigned alarm_id); -/** - * @brief Notify esp_timer implementation that APB frequency has changed - * - * Called by the frequency switching code. - * - * @param apb_ticks_per_us new number of APB clock ticks per microsecond - */ -void esp_timer_impl_update_apb_freq(uint32_t apb_ticks_per_us); - /** * @brief Adjust current esp_timer time by a certain value * diff --git a/components/esp_timer/src/esp_timer_impl_systimer.c b/components/esp_timer/src/esp_timer_impl_systimer.c index 549316a6da..f66b3a047e 100644 --- a/components/esp_timer/src/esp_timer_impl_systimer.c +++ b/components/esp_timer/src/esp_timer_impl_systimer.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2017-2024 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2017-2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -129,13 +129,6 @@ static void IRAM_ATTR timer_alarm_isr(void *arg) #endif // ISR_HANDLERS != 1 } -void IRAM_ATTR esp_timer_impl_update_apb_freq(uint32_t apb_ticks_per_us) -{ -#if !SOC_SYSTIMER_FIXED_DIVIDER - systimer_hal_on_apb_freq_update(&systimer_hal, apb_ticks_per_us); -#endif -} - void esp_timer_impl_set(uint64_t new_us) { portENTER_CRITICAL_SAFE(&s_time_update_lock); @@ -249,6 +242,5 @@ uint64_t esp_timer_impl_get_alarm_reg(void) return val; } -void esp_timer_private_update_apb_freq(uint32_t apb_ticks_per_us) __attribute__((alias("esp_timer_impl_update_apb_freq"))); void esp_timer_private_set(uint64_t new_us) __attribute__((alias("esp_timer_impl_set"))); void esp_timer_private_advance(int64_t time_diff_us) __attribute__((alias("esp_timer_impl_advance"))); From 326b32bd749971c0b9e973e065f586a9b77f99e8 Mon Sep 17 00:00:00 2001 From: wuzhenghui Date: Thu, 22 May 2025 14:22:47 +0800 Subject: [PATCH 3/4] feat(esp_hw_support): compensate the error introduced to LACT during APB frequency switching --- .../esp_hw_support/port/esp32/rtc_clk.c | 34 +++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/components/esp_hw_support/port/esp32/rtc_clk.c b/components/esp_hw_support/port/esp32/rtc_clk.c index 415b24cb93..699f62ac4d 100644 --- a/components/esp_hw_support/port/esp32/rtc_clk.c +++ b/components/esp_hw_support/port/esp32/rtc_clk.c @@ -394,19 +394,49 @@ static void rtc_clk_cpu_freq_to_8m(void) rtc_clk_apb_freq_update(SOC_CLK_RC_FAST_FREQ_APPROX); } +#ifndef BOOTLOADER_BUILD +static const DRAM_ATTR int16_t dfs_lact_conpensate_table[3][3] = { \ +/* From / To 80 160 240*/ \ +/* 10 */ {138, 220, 18}, \ +/* 20 */ {128, 205, -3579}, \ +/* 40 */ {34, 100, 0}, \ +}; + +__attribute__((weak)) IRAM_ATTR int16_t rtc_clk_get_lact_compensation_delay(uint32_t cur_freq, uint32_t tar_freq) +{ + return dfs_lact_conpensate_table[(cur_freq == 10) ? 0 : (cur_freq == 20) ? 1 : 2][(tar_freq == 80) ? 0 : (tar_freq == 160) ? 1 : 2]; +} +#endif + /** * Switch to one of PLL-based frequencies. Current frequency can be XTAL or PLL. * PLL must already be enabled. * @param cpu_freq new CPU frequency */ -static void rtc_clk_cpu_freq_to_pll_mhz(int cpu_freq_mhz) +__attribute__((optimize("-O2"))) +NOINLINE_ATTR static void rtc_clk_cpu_freq_to_pll_mhz(int cpu_freq_mhz) { int dbias = (cpu_freq_mhz == 240) ? DIG_DBIAS_240M : DIG_DBIAS_80M_160M; REG_SET_FIELD(RTC_CNTL_REG, RTC_CNTL_DIG_DBIAS_WAK, dbias); #ifndef BOOTLOADER_BUILD - timer_ll_set_lact_clock_prescale(TIMER_LL_GET_HW(LACT_MODULE), 80 / LACT_TICKS_PER_US); + uint32_t cur_freq = esp_rom_get_cpu_ticks_per_us(); + int16_t delay_cycle = rtc_clk_get_lact_compensation_delay(cur_freq, cpu_freq_mhz); + if (cur_freq <= 40 && delay_cycle >= 0) { + timer_ll_set_lact_clock_prescale(TIMER_LL_GET_HW(LACT_MODULE), 80 / LACT_TICKS_PER_US); + for (int i = 0; i < delay_cycle; ++i) { + __asm__ __volatile__("nop"); + } + } #endif clk_ll_cpu_set_freq_mhz_from_pll(cpu_freq_mhz); +#ifndef BOOTLOADER_BUILD + if (cur_freq <= 40 && delay_cycle < 0) { + for (int i = 0; i > delay_cycle; --i) { + __asm__ __volatile__("nop"); + } + timer_ll_set_lact_clock_prescale(TIMER_LL_GET_HW(LACT_MODULE), 80 / LACT_TICKS_PER_US); + } +#endif /* adjust ref_tick */ clk_ll_ref_tick_set_divider(SOC_CPU_CLK_SRC_PLL, cpu_freq_mhz); /* switch clock source */ From 162681d8eae85fbd85c4e08a99669f875ad92bac Mon Sep 17 00:00:00 2001 From: wuzhenghui Date: Mon, 19 May 2025 14:25:49 +0800 Subject: [PATCH 4/4] ci(esp_timer): add UT case for esp_timer period alarm with DFS --- .../esp_timer/test_apps/.build-test-rules.yml | 6 + .../esp_timer/test_apps/main/CMakeLists.txt | 21 +- .../test_apps/main/test_esp_timer_dfs.c | 238 ++++++++++++++++++ .../esp_timer/test_apps/sdkconfig.ci.dfs | 4 + 4 files changed, 267 insertions(+), 2 deletions(-) create mode 100644 components/esp_timer/test_apps/.build-test-rules.yml create mode 100644 components/esp_timer/test_apps/main/test_esp_timer_dfs.c create mode 100644 components/esp_timer/test_apps/sdkconfig.ci.dfs diff --git a/components/esp_timer/test_apps/.build-test-rules.yml b/components/esp_timer/test_apps/.build-test-rules.yml new file mode 100644 index 0000000000..b62f794ee4 --- /dev/null +++ b/components/esp_timer/test_apps/.build-test-rules.yml @@ -0,0 +1,6 @@ +# Documentation: .gitlab/ci/README.md#manifest-file-to-control-the-buildtest-apps + +components/esp_timer/test_apps: + disable: + - if: CONFIG_NAME == "dfs" and SOC_CLK_XTAL32K_SUPPORTED != 1 + reason: The test requires the XTAL32K clock to measure the esp_timer timing accuracy diff --git a/components/esp_timer/test_apps/main/CMakeLists.txt b/components/esp_timer/test_apps/main/CMakeLists.txt index 041c23bb57..730499f117 100644 --- a/components/esp_timer/test_apps/main/CMakeLists.txt +++ b/components/esp_timer/test_apps/main/CMakeLists.txt @@ -1,4 +1,21 @@ -idf_component_register(SRC_DIRS "." +set(srcs + "test_app_main.c" + "test_ets_timer.c" +) + +if(CONFIG_SOC_LIGHT_SLEEP_SUPPORTED) + list(APPEND srcs "test_esp_timer_light_sleep.c") +endif() + +if(CONFIG_SOC_GPTIMER_SUPPORTED) + list(APPEND srcs "test_esp_timer.c") +endif() + +if(CONFIG_ESP_TIMER_SUPPORTS_ISR_DISPATCH_METHOD) + list(APPEND srcs "test_esp_timer_dfs.c") +endif() + +idf_component_register(SRCS ${srcs} PRIV_INCLUDE_DIRS "../../private_include" - PRIV_REQUIRES cmock test_utils esp_timer spi_flash esp_psram + PRIV_REQUIRES cmock test_utils esp_timer spi_flash esp_psram esp_pm WHOLE_ARCHIVE) diff --git a/components/esp_timer/test_apps/main/test_esp_timer_dfs.c b/components/esp_timer/test_apps/main/test_esp_timer_dfs.c new file mode 100644 index 0000000000..e1752d7953 --- /dev/null +++ b/components/esp_timer/test_apps/main/test_esp_timer_dfs.c @@ -0,0 +1,238 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include +#include +#include +#include +#include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "sdkconfig.h" +#include "esp_timer.h" +#include "unity.h" +#include "esp_rom_sys.h" +#include "esp_sleep.h" +#include "esp_pm.h" +#include "esp_log.h" +#include "soc/soc_caps.h" +#include "soc/rtc.h" +#include "esp_private/esp_clk.h" + +#if CONFIG_IDF_TARGET_ESP32 +#include "esp32/rtc.h" +#elif CONFIG_IDF_TARGET_ESP32S2 +#include "esp32s2/rtc.h" +#elif CONFIG_IDF_TARGET_ESP32S3 +#include "esp32s3/rtc.h" +#elif CONFIG_IDF_TARGET_ESP32C3 +#include "esp32c3/rtc.h" +#elif CONFIG_IDF_TARGET_ESP32C2 +#include "esp32c2/rtc.h" +#elif CONFIG_IDF_TARGET_ESP32C6 +#include "esp32c6/rtc.h" +#elif CONFIG_IDF_TARGET_ESP32C61 +#include "esp32c61/rtc.h" +#elif CONFIG_IDF_TARGET_ESP32H2 +#include "esp32h2/rtc.h" +#elif CONFIG_IDF_TARGET_ESP32P4 +#include "esp32p4/rtc.h" +#elif CONFIG_IDF_TARGET_ESP32C5 +#include "esp32c5/rtc.h" +#endif + +#define ALARM_PERIOD_MS 100 +#define ALARM_TIMES 200 // 200*100ms = 20s + +static const char* TAG = "ESP_TIMER with DFS"; + +static uint32_t s_current_alarm = 0; +static uint64_t s_alarm_records[ALARM_TIMES + 1] = {0}; + +#if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 || CONFIG_IDF_TARGET_ESP32C5 +static uint32_t supported_freq[] = {10, 20, 40, 80, 160, 240}; +#elif CONFIG_IDF_TARGET_ESP32C2 +static uint32_t supported_freq[] = {10, 20, 40, 80, 120}; +#elif CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32C6 || CONFIG_IDF_TARGET_ESP32C61 +static uint32_t supported_freq[] = {10, 20, 40, 80, 160}; +#elif CONFIG_IDF_TARGET_ESP32H2 +static uint32_t supported_freq[] = {8, 16, 32, 48, 64, 96}; +#elif CONFIG_IDF_TARGET_ESP32P4 +static uint32_t supported_freq[] = {10, 20, 40, 90, 180, 360}; +#endif + +static SemaphoreHandle_t s_alarm_finished; + +static IRAM_ATTR void periodic_timer_callback(void* arg) +{ + s_alarm_records[s_current_alarm] = esp_rtc_get_time_us(); + BaseType_t yield; + if (s_current_alarm == ALARM_TIMES) { + xSemaphoreGiveFromISR(s_alarm_finished, &yield); + } + s_current_alarm++; + if (yield) { + portYIELD_FROM_ISR(); + } +} + +static int64_t measuring_periodic_timer_accuracy(uint64_t alarm_records[]) +{ + int64_t sum_jitter_us = 0; + int64_t max_jitter_us = 0; + int64_t jitter_array[ALARM_TIMES] = {0}; + + for (int i = 1; i <= ALARM_TIMES; ++i) { + int64_t jitter_us = (int64_t)alarm_records[i] - (int64_t)alarm_records[i - 1] - ALARM_PERIOD_MS * 1000; + jitter_array[i - 1] = jitter_us; + if (llabs(jitter_us) > llabs(max_jitter_us)) { + max_jitter_us = jitter_us; + } + sum_jitter_us += jitter_us; + } + int64_t avg_jitter_us = sum_jitter_us / ALARM_TIMES; + + // Calculates the sum of squares of standard deviations + int64_t sum_sq_diff = 0; + for (int i = 0; i < ALARM_TIMES; ++i) { + sum_sq_diff += (jitter_array[i] - avg_jitter_us) * (jitter_array[i] - avg_jitter_us); + } + + double variance = sum_sq_diff / ALARM_TIMES; + double stddev = sqrt(variance); + + printf("Average jitter us: %"PRIi64"\n", avg_jitter_us); + printf("Max jitter us: %"PRIi64"\n", max_jitter_us); + printf("Standard Deviation: %.3f\n", stddev); + printf("Drift Percentage: %.3f%%\n", (double)avg_jitter_us / (ALARM_PERIOD_MS * 10)); + + // Reset measurement + s_current_alarm = 0; + bzero(s_alarm_records, sizeof(s_alarm_records)); + return avg_jitter_us; +} + +static int64_t test_periodic_timer_accuracy_on_dfs(esp_timer_handle_t timer) +{ + // Calibrate slow clock. +#if !CONFIG_ESP_SYSTEM_RTC_EXT_XTAL + esp_clk_slowclk_cal_set(rtc_clk_cal(RTC_CAL_RTC_MUX, 8192)); +#endif + + ESP_ERROR_CHECK(esp_timer_start_periodic(timer, ALARM_PERIOD_MS * 1000)); + + s_alarm_finished = xSemaphoreCreateBinary(); + // Each FreeRTOS tick will perform a min_freq_mhz->max_freq_mhz -> min_freq_mhz frequency switch + xSemaphoreTake(s_alarm_finished, portMAX_DELAY); + ESP_ERROR_CHECK(esp_timer_stop(timer)); + int64_t avg_jitter_us = measuring_periodic_timer_accuracy(s_alarm_records); + vSemaphoreDelete(s_alarm_finished); + return avg_jitter_us; +} + +// The results of this test are meaningful only if `CONFIG_ESP_SYSTEM_RTC_EXT_XTAL` is enabled +TEST_CASE("Test the periodic timer timing accuracy when doing DFS", "[esp_timer][manual][ignore]") +{ + esp_pm_config_t pm_config = { + .light_sleep_enable = false + }; + + const esp_timer_create_args_t periodic_timer_args = { + .callback = &periodic_timer_callback, + .dispatch_method = ESP_TIMER_ISR, + .name = "periodic" + }; + esp_timer_handle_t periodic_timer; + ESP_ERROR_CHECK(esp_timer_create(&periodic_timer_args, &periodic_timer)); + + for (int min = 0; min < sizeof(supported_freq) / sizeof(uint32_t); min++) { + for (int max = 0; max < sizeof(supported_freq) / sizeof(uint32_t); max++) { + if (supported_freq[max] <= supported_freq[min]) { + continue; + } + pm_config.max_freq_mhz = supported_freq[max]; + pm_config.min_freq_mhz = supported_freq[min]; + ESP_LOGI(TAG, "Testing esp_timer accuracy on %d <-> %d DFS ...", pm_config.min_freq_mhz, pm_config.max_freq_mhz); + ESP_ERROR_CHECK(esp_pm_configure(&pm_config)); + test_periodic_timer_accuracy_on_dfs(periodic_timer); + } + } +} + +#if CONFIG_IDF_TARGET_ESP32 +int16_t test_lact_compensation_delay = 0; +#define DO_MAGIC_TABLE_MEASUREMENT 0 +#if DO_MAGIC_TABLE_MEASUREMENT +IRAM_ATTR int16_t rtc_clk_get_lact_compensation_delay(uint32_t cur_freq, uint32_t cpu_freq_mhz) +{ + (void)cur_freq; (void)cpu_freq_mhz; + return test_lact_compensation_delay; +} +#endif + +// Test Notes: +// 1. The test requires the slow clock source to be selected to `CONFIG_ESP_SYSTEM_RTC_EXT_XTAL`. +// 2. Manually set DO_MAGIC_TABLE_MEASUREMENT to 1 before test. +TEST_CASE("Test DFS lact conpensate magic table ", "[esp_timer][manual][ignore]") +{ + esp_pm_config_t pm_config = { + .light_sleep_enable = false + }; + + const esp_timer_create_args_t periodic_timer_args = { + .callback = &periodic_timer_callback, + .dispatch_method = ESP_TIMER_ISR, + .name = "periodic" + }; + esp_timer_handle_t periodic_timer; + ESP_ERROR_CHECK(esp_timer_create(&periodic_timer_args, &periodic_timer)); + + for (int min = 0; min < sizeof(supported_freq) / sizeof(uint32_t); min++) { + for (int max = 0; max < sizeof(supported_freq) / sizeof(uint32_t); max++) { + if ((supported_freq[max] <= supported_freq[min]) || (supported_freq[min] >= 80) || (supported_freq[max] <= 40)) { + continue; + } + + pm_config.max_freq_mhz = supported_freq[max]; + pm_config.min_freq_mhz = supported_freq[min]; + ESP_LOGI(TAG, "Testing esp_timer accuracy on %d <-> %d DFS ...", pm_config.min_freq_mhz, pm_config.max_freq_mhz); + esp_pm_configure(&pm_config); + + int32_t max_delay = 300; + int32_t min_delay = (pm_config.max_freq_mhz == 240) ? -4000 : 0; + int32_t test_delay = (max_delay + min_delay) / 2; + int32_t last_delay = 0; + int32_t best_delay = 0; + int64_t min_avg = INT64_MAX; + + do { + printf("Test delay %ld\n", test_delay); + test_lact_compensation_delay = test_delay; + int64_t avg = test_periodic_timer_accuracy_on_dfs(periodic_timer); + last_delay = test_delay; + if (avg < 0) { + test_delay = (test_delay + max_delay) / 2; + min_delay = last_delay; + } else { + test_delay = (test_delay + min_delay) / 2; + max_delay = last_delay; + } + + if (llabs(avg) < llabs(min_avg)) { + best_delay = last_delay; + min_avg = avg; + } + } while (test_delay != last_delay); + + printf("Switch between %d <-> %d\n", pm_config.min_freq_mhz, pm_config.max_freq_mhz); + printf("Best delay is %ld\n", best_delay); + printf("Min average is %lld\n", min_avg); + } + } + ESP_ERROR_CHECK(esp_timer_delete(periodic_timer)); +} +#endif diff --git a/components/esp_timer/test_apps/sdkconfig.ci.dfs b/components/esp_timer/test_apps/sdkconfig.ci.dfs new file mode 100644 index 0000000000..a47e5df367 --- /dev/null +++ b/components/esp_timer/test_apps/sdkconfig.ci.dfs @@ -0,0 +1,4 @@ +CONFIG_PM_ENABLE=y +CONFIG_FREERTOS_USE_TICKLESS_IDLE=y +CONFIG_RTC_CLK_SRC_EXT_CRYS=y +CONFIG_ESP_TIMER_SUPPORTS_ISR_DISPATCH_METHOD=y