mirror of
https://github.com/espressif/esp-idf.git
synced 2025-07-29 18:27:20 +02:00
Merge branch 'fix/fix_esp_timer_accuracy_when_do_dfs' into 'master'
fix(esp_hw_support): improve esp timer accuracy on DFS for esp32 & esp32s2 Closes PM-405, WIFI-4986, and WIFI-5942 See merge request espressif/esp-idf!39217
This commit is contained in:
@ -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
|
||||
|
@ -16,6 +16,9 @@ entries:
|
||||
if PM_SLP_IRAM_OPT = y:
|
||||
rtc_clk (noflash)
|
||||
rtc_time (noflash_text)
|
||||
if IDF_TARGET_ESP32 = y:
|
||||
rtc_clk:rtc_clk_cpu_freq_to_pll_mhz (noflash)
|
||||
rtc_clk:rtc_clk_cpu_freq_to_xtal (noflash)
|
||||
if SOC_CONFIGURABLE_VDDSDIO_SUPPORTED = y:
|
||||
rtc_init:rtc_vddsdio_get_config (noflash)
|
||||
rtc_init:rtc_vddsdio_set_config (noflash)
|
||||
|
@ -27,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
|
||||
|
||||
@ -374,6 +378,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;
|
||||
@ -389,19 +396,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 */
|
||||
|
@ -21,6 +21,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";
|
||||
|
||||
@ -468,6 +472,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
|
||||
|
@ -572,13 +572,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 */
|
||||
|
@ -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
|
||||
*
|
||||
|
@ -76,15 +76,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
|
||||
*
|
||||
|
@ -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 ESP_TIMER_IRAM_ATTR esp_timer_impl_get_counter_reg(void)
|
||||
|
||||
int64_t ESP_TIMER_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 ESP_TIMER_IRAM_ATTR esp_timer_impl_set_alarm_id(uint64_t timestamp, unsigne
|
||||
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 ESP_TIMER_IRAM_ATTR esp_timer_impl_set_alarm_id(uint64_t timestamp, unsigne
|
||||
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 ESP_TIMER_IRAM_ATTR timer_alarm_isr(void *arg)
|
||||
#endif // ISR_HANDLERS != 1
|
||||
}
|
||||
|
||||
void ESP_TIMER_IRAM_ATTR esp_timer_impl_update_apb_freq(uint32_t apb_ticks_per_us)
|
||||
{
|
||||
portENTER_CRITICAL_SAFE(&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_SAFE(&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);
|
||||
@ -299,12 +276,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);
|
||||
}
|
||||
|
||||
@ -347,6 +322,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")));
|
||||
|
@ -129,13 +129,6 @@ static void ESP_TIMER_IRAM_ATTR timer_alarm_isr(void *arg)
|
||||
#endif // ISR_HANDLERS != 1
|
||||
}
|
||||
|
||||
void ESP_TIMER_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);
|
||||
@ -253,6 +246,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")));
|
||||
|
6
components/esp_timer/test_apps/.build-test-rules.yml
Normal file
6
components/esp_timer/test_apps/.build-test-rules.yml
Normal 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
|
@ -15,7 +15,11 @@ 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 esp_driver_gpio
|
||||
PRIV_REQUIRES cmock test_utils esp_timer spi_flash esp_psram esp_driver_gpio esp_pm
|
||||
WHOLE_ARCHIVE)
|
||||
|
218
components/esp_timer/test_apps/main/test_esp_timer_dfs.c
Normal file
218
components/esp_timer/test_apps/main/test_esp_timer_dfs.c
Normal file
@ -0,0 +1,218 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
#include <stdlib.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_rtc_time.h"
|
||||
#include "esp_private/esp_clk.h"
|
||||
|
||||
#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 || CONFIG_IDF_TARGET_ESP32H21
|
||||
static uint32_t supported_freq[] = {8, 16, 32, 48, 64, 96};
|
||||
#elif CONFIG_IDF_TARGET_ESP32H4
|
||||
static uint32_t supported_freq[] = {8, 16, 24, 32};
|
||||
#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
|
4
components/esp_timer/test_apps/sdkconfig.ci.dfs
Normal file
4
components/esp_timer/test_apps/sdkconfig.ci.dfs
Normal 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
|
@ -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"
|
||||
@ -331,6 +332,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
|
||||
|
Reference in New Issue
Block a user