|
| 1 | +/* |
| 2 | + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD |
| 3 | + * |
| 4 | + * SPDX-License-Identifier: Apache-2.0 |
| 5 | + */ |
| 6 | +#include <stdio.h> |
| 7 | +#include <unistd.h> |
| 8 | +#include <stdlib.h> |
| 9 | +#include <math.h> |
| 10 | +#include <sys/time.h> |
| 11 | +#include <sys/param.h> |
| 12 | +#include "freertos/FreeRTOS.h" |
| 13 | +#include "freertos/task.h" |
| 14 | +#include "sdkconfig.h" |
| 15 | +#include "esp_timer.h" |
| 16 | +#include "unity.h" |
| 17 | +#include "esp_rom_sys.h" |
| 18 | +#include "esp_sleep.h" |
| 19 | +#include "esp_pm.h" |
| 20 | +#include "esp_log.h" |
| 21 | +#include "soc/soc_caps.h" |
| 22 | +#include "soc/rtc.h" |
| 23 | +#include "esp_rtc_time.h" |
| 24 | +#include "esp_private/esp_clk.h" |
| 25 | + |
| 26 | +#define ALARM_PERIOD_MS 100 |
| 27 | +#define ALARM_TIMES 200 // 200*100ms = 20s |
| 28 | + |
| 29 | +static const char* TAG = "ESP_TIMER with DFS"; |
| 30 | + |
| 31 | +static uint32_t s_current_alarm = 0; |
| 32 | +static uint64_t s_alarm_records[ALARM_TIMES + 1] = {0}; |
| 33 | + |
| 34 | +#if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 || CONFIG_IDF_TARGET_ESP32C5 |
| 35 | +static uint32_t supported_freq[] = {10, 20, 40, 80, 160, 240}; |
| 36 | +#elif CONFIG_IDF_TARGET_ESP32C2 |
| 37 | +static uint32_t supported_freq[] = {10, 20, 40, 80, 120}; |
| 38 | +#elif CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32C6 || CONFIG_IDF_TARGET_ESP32C61 |
| 39 | +static uint32_t supported_freq[] = {10, 20, 40, 80, 160}; |
| 40 | +#elif CONFIG_IDF_TARGET_ESP32H2 || CONFIG_IDF_TARGET_ESP32H21 |
| 41 | +static uint32_t supported_freq[] = {8, 16, 32, 48, 64, 96}; |
| 42 | +#elif CONFIG_IDF_TARGET_ESP32H4 |
| 43 | +static uint32_t supported_freq[] = {8, 16, 24, 32}; |
| 44 | +#elif CONFIG_IDF_TARGET_ESP32P4 |
| 45 | +static uint32_t supported_freq[] = {10, 20, 40, 90, 180, 360}; |
| 46 | +#endif |
| 47 | + |
| 48 | +static SemaphoreHandle_t s_alarm_finished; |
| 49 | + |
| 50 | +static IRAM_ATTR void periodic_timer_callback(void* arg) |
| 51 | +{ |
| 52 | + s_alarm_records[s_current_alarm] = esp_rtc_get_time_us(); |
| 53 | + BaseType_t yield; |
| 54 | + if (s_current_alarm == ALARM_TIMES) { |
| 55 | + xSemaphoreGiveFromISR(s_alarm_finished, &yield); |
| 56 | + } |
| 57 | + s_current_alarm++; |
| 58 | + if (yield) { |
| 59 | + portYIELD_FROM_ISR(); |
| 60 | + } |
| 61 | +} |
| 62 | + |
| 63 | +static int64_t measuring_periodic_timer_accuracy(uint64_t alarm_records[]) |
| 64 | +{ |
| 65 | + int64_t sum_jitter_us = 0; |
| 66 | + int64_t max_jitter_us = 0; |
| 67 | + int64_t jitter_array[ALARM_TIMES] = {0}; |
| 68 | + |
| 69 | + for (int i = 1; i <= ALARM_TIMES; ++i) { |
| 70 | + int64_t jitter_us = (int64_t)alarm_records[i] - (int64_t)alarm_records[i - 1] - ALARM_PERIOD_MS * 1000; |
| 71 | + jitter_array[i - 1] = jitter_us; |
| 72 | + if (llabs(jitter_us) > llabs(max_jitter_us)) { |
| 73 | + max_jitter_us = jitter_us; |
| 74 | + } |
| 75 | + sum_jitter_us += jitter_us; |
| 76 | + } |
| 77 | + int64_t avg_jitter_us = sum_jitter_us / ALARM_TIMES; |
| 78 | + |
| 79 | + // Calculates the sum of squares of standard deviations |
| 80 | + int64_t sum_sq_diff = 0; |
| 81 | + for (int i = 0; i < ALARM_TIMES; ++i) { |
| 82 | + sum_sq_diff += (jitter_array[i] - avg_jitter_us) * (jitter_array[i] - avg_jitter_us); |
| 83 | + } |
| 84 | + |
| 85 | + double variance = sum_sq_diff / ALARM_TIMES; |
| 86 | + double stddev = sqrt(variance); |
| 87 | + |
| 88 | + printf("Average jitter us: %"PRIi64"\n", avg_jitter_us); |
| 89 | + printf("Max jitter us: %"PRIi64"\n", max_jitter_us); |
| 90 | + printf("Standard Deviation: %.3f\n", stddev); |
| 91 | + printf("Drift Percentage: %.3f%%\n", (double)avg_jitter_us / (ALARM_PERIOD_MS * 10)); |
| 92 | + |
| 93 | + // Reset measurement |
| 94 | + s_current_alarm = 0; |
| 95 | + bzero(s_alarm_records, sizeof(s_alarm_records)); |
| 96 | + return avg_jitter_us; |
| 97 | +} |
| 98 | + |
| 99 | +static int64_t test_periodic_timer_accuracy_on_dfs(esp_timer_handle_t timer) |
| 100 | +{ |
| 101 | + // Calibrate slow clock. |
| 102 | +#if !CONFIG_ESP_SYSTEM_RTC_EXT_XTAL |
| 103 | + esp_clk_slowclk_cal_set(rtc_clk_cal(RTC_CAL_RTC_MUX, 8192)); |
| 104 | +#endif |
| 105 | + |
| 106 | + ESP_ERROR_CHECK(esp_timer_start_periodic(timer, ALARM_PERIOD_MS * 1000)); |
| 107 | + |
| 108 | + s_alarm_finished = xSemaphoreCreateBinary(); |
| 109 | + // Each FreeRTOS tick will perform a min_freq_mhz->max_freq_mhz -> min_freq_mhz frequency switch |
| 110 | + xSemaphoreTake(s_alarm_finished, portMAX_DELAY); |
| 111 | + ESP_ERROR_CHECK(esp_timer_stop(timer)); |
| 112 | + int64_t avg_jitter_us = measuring_periodic_timer_accuracy(s_alarm_records); |
| 113 | + vSemaphoreDelete(s_alarm_finished); |
| 114 | + return avg_jitter_us; |
| 115 | +} |
| 116 | + |
| 117 | +// The results of this test are meaningful only if `CONFIG_ESP_SYSTEM_RTC_EXT_XTAL` is enabled |
| 118 | +TEST_CASE("Test the periodic timer timing accuracy when doing DFS", "[esp_timer][manual][ignore]") |
| 119 | +{ |
| 120 | + esp_pm_config_t pm_config = { |
| 121 | + .light_sleep_enable = false |
| 122 | + }; |
| 123 | + |
| 124 | + const esp_timer_create_args_t periodic_timer_args = { |
| 125 | + .callback = &periodic_timer_callback, |
| 126 | + .dispatch_method = ESP_TIMER_ISR, |
| 127 | + .name = "periodic" |
| 128 | + }; |
| 129 | + esp_timer_handle_t periodic_timer; |
| 130 | + ESP_ERROR_CHECK(esp_timer_create(&periodic_timer_args, &periodic_timer)); |
| 131 | + |
| 132 | + for (int min = 0; min < sizeof(supported_freq) / sizeof(uint32_t); min++) { |
| 133 | + for (int max = 0; max < sizeof(supported_freq) / sizeof(uint32_t); max++) { |
| 134 | + if (supported_freq[max] <= supported_freq[min]) { |
| 135 | + continue; |
| 136 | + } |
| 137 | + pm_config.max_freq_mhz = supported_freq[max]; |
| 138 | + pm_config.min_freq_mhz = supported_freq[min]; |
| 139 | + ESP_LOGI(TAG, "Testing esp_timer accuracy on %d <-> %d DFS ...", pm_config.min_freq_mhz, pm_config.max_freq_mhz); |
| 140 | + ESP_ERROR_CHECK(esp_pm_configure(&pm_config)); |
| 141 | + test_periodic_timer_accuracy_on_dfs(periodic_timer); |
| 142 | + } |
| 143 | + } |
| 144 | +} |
| 145 | + |
| 146 | +#if CONFIG_IDF_TARGET_ESP32 |
| 147 | +int16_t test_lact_compensation_delay = 0; |
| 148 | +#define DO_MAGIC_TABLE_MEASUREMENT 0 |
| 149 | +#if DO_MAGIC_TABLE_MEASUREMENT |
| 150 | +IRAM_ATTR int16_t rtc_clk_get_lact_compensation_delay(uint32_t cur_freq, uint32_t cpu_freq_mhz) |
| 151 | +{ |
| 152 | + (void)cur_freq; (void)cpu_freq_mhz; |
| 153 | + return test_lact_compensation_delay; |
| 154 | +} |
| 155 | +#endif |
| 156 | + |
| 157 | +// Test Notes: |
| 158 | +// 1. The test requires the slow clock source to be selected to `CONFIG_ESP_SYSTEM_RTC_EXT_XTAL`. |
| 159 | +// 2. Manually set DO_MAGIC_TABLE_MEASUREMENT to 1 before test. |
| 160 | +TEST_CASE("Test DFS lact conpensate magic table ", "[esp_timer][manual][ignore]") |
| 161 | +{ |
| 162 | + esp_pm_config_t pm_config = { |
| 163 | + .light_sleep_enable = false |
| 164 | + }; |
| 165 | + |
| 166 | + const esp_timer_create_args_t periodic_timer_args = { |
| 167 | + .callback = &periodic_timer_callback, |
| 168 | + .dispatch_method = ESP_TIMER_ISR, |
| 169 | + .name = "periodic" |
| 170 | + }; |
| 171 | + esp_timer_handle_t periodic_timer; |
| 172 | + ESP_ERROR_CHECK(esp_timer_create(&periodic_timer_args, &periodic_timer)); |
| 173 | + |
| 174 | + for (int min = 0; min < sizeof(supported_freq) / sizeof(uint32_t); min++) { |
| 175 | + for (int max = 0; max < sizeof(supported_freq) / sizeof(uint32_t); max++) { |
| 176 | + if ((supported_freq[max] <= supported_freq[min]) || (supported_freq[min] >= 80) || (supported_freq[max] <= 40)) { |
| 177 | + continue; |
| 178 | + } |
| 179 | + |
| 180 | + pm_config.max_freq_mhz = supported_freq[max]; |
| 181 | + pm_config.min_freq_mhz = supported_freq[min]; |
| 182 | + ESP_LOGI(TAG, "Testing esp_timer accuracy on %d <-> %d DFS ...", pm_config.min_freq_mhz, pm_config.max_freq_mhz); |
| 183 | + esp_pm_configure(&pm_config); |
| 184 | + |
| 185 | + int32_t max_delay = 300; |
| 186 | + int32_t min_delay = (pm_config.max_freq_mhz == 240) ? -4000 : 0; |
| 187 | + int32_t test_delay = (max_delay + min_delay) / 2; |
| 188 | + int32_t last_delay = 0; |
| 189 | + int32_t best_delay = 0; |
| 190 | + int64_t min_avg = INT64_MAX; |
| 191 | + |
| 192 | + do { |
| 193 | + printf("Test delay %ld\n", test_delay); |
| 194 | + test_lact_compensation_delay = test_delay; |
| 195 | + int64_t avg = test_periodic_timer_accuracy_on_dfs(periodic_timer); |
| 196 | + last_delay = test_delay; |
| 197 | + if (avg < 0) { |
| 198 | + test_delay = (test_delay + max_delay) / 2; |
| 199 | + min_delay = last_delay; |
| 200 | + } else { |
| 201 | + test_delay = (test_delay + min_delay) / 2; |
| 202 | + max_delay = last_delay; |
| 203 | + } |
| 204 | + |
| 205 | + if (llabs(avg) < llabs(min_avg)) { |
| 206 | + best_delay = last_delay; |
| 207 | + min_avg = avg; |
| 208 | + } |
| 209 | + } while (test_delay != last_delay); |
| 210 | + |
| 211 | + printf("Switch between %d <-> %d\n", pm_config.min_freq_mhz, pm_config.max_freq_mhz); |
| 212 | + printf("Best delay is %ld\n", best_delay); |
| 213 | + printf("Min average is %lld\n", min_avg); |
| 214 | + } |
| 215 | + } |
| 216 | + ESP_ERROR_CHECK(esp_timer_delete(periodic_timer)); |
| 217 | +} |
| 218 | +#endif |
0 commit comments