Merge branch 'fix/fix_esp_timer_accuracy_when_do_dfs_v5.3' into 'release/v5.3'

fix(esp_hw_support): improve esp timer accuracy on DFS for esp32 & esp32s2 (v5.3)

See merge request espressif/esp-idf!39341
This commit is contained in:
Jiang Jiang Jian
2025-05-26 19:54:44 +08:00
13 changed files with 367 additions and 79 deletions

View File

@ -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 <stdint.h>
#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

View File

@ -8,6 +8,7 @@
#include <stdint.h>
#include <stddef.h>
#include <stdlib.h>
#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,19 +388,55 @@ 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);
}
#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;
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
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 */

View File

@ -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

View File

@ -567,13 +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)
{
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);
}
#ifdef CONFIG_FREERTOS_SYSTICK_USES_CCOUNT
#ifdef XT_RTOS_TIMER_INT
/* Calculate new tick divisor */

View File

@ -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
*

View File

@ -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
*

View File

@ -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")));

View File

@ -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")));

View File

@ -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

View File

@ -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)

View File

@ -0,0 +1,238 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <sys/time.h>
#include <sys/param.h>
#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

View File

@ -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

View File

@ -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 <stdbool.h>
#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