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 c89a9424fc..49467353b2 100644 --- a/components/esp_timer/test_apps/main/CMakeLists.txt +++ b/components/esp_timer/test_apps/main/CMakeLists.txt @@ -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) 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..2fe568880e --- /dev/null +++ b/components/esp_timer/test_apps/main/test_esp_timer_dfs.c @@ -0,0 +1,218 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#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_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 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