Merge branch 'ci/add_base_size_metric_apps' into 'master'

ci(size_metrics): add base size metric test apps

See merge request espressif/esp-idf!46208
This commit is contained in:
Marius Vikhammer
2026-07-29 20:50:13 +08:00
52 changed files with 259 additions and 3 deletions
@@ -0,0 +1,15 @@
#This is the project CMakeLists.txt file for the test subproject
cmake_minimum_required(VERSION 3.22)
list(PREPEND SDKCONFIG_DEFAULTS "$ENV{IDF_PATH}/tools/test_apps/configs/sdkconfig.debug_helpers" "sdkconfig.defaults")
if(NOT "${IDF_TARGET}" STREQUAL "linux")
list(APPEND SDKCONFIG_DEFAULTS "sdkconfig.defaults.no_linux")
endif()
# "Trim" the build. Include the minimal set of components, main, and anything it depends on.
set(COMPONENTS main)
set(EXTRA_COMPONENT_DIRS "$ENV{IDF_PATH}/tools/test_apps/components")
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(esp_timer_test)
@@ -0,0 +1,3 @@
| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C5 | ESP32-C6 | ESP32-C61 | ESP32-H2 | ESP32-H21 | ESP32-H4 | ESP32-P4 | ESP32-S2 | ESP32-S3 | ESP32-S31 | Linux |
| ----------------- | ----- | -------- | -------- | -------- | -------- | --------- | -------- | --------- | -------- | -------- | -------- | -------- | --------- | ----- |
@@ -0,0 +1,41 @@
idf_build_get_property(target IDF_TARGET)
set(srcs "test_app_main.c")
if(CONFIG_SOC_LIGHT_SLEEP_SUPPORTED)
list(APPEND srcs "test_esp_timer_light_sleep.c")
endif()
if(CONFIG_SOC_SYSTIMER_SUPPORT_ETM)
list(APPEND srcs "test_esp_timer_etm.c")
endif()
if(CONFIG_SOC_GPTIMER_SUPPORTED OR CONFIG_IDF_TARGET_LINUX)
list(APPEND srcs "test_esp_timer.c")
endif()
if(NOT ${target} STREQUAL "linux")
if(CONFIG_ESP_TIMER_SUPPORTS_ISR_DISPATCH_METHOD)
list(APPEND srcs "test_esp_timer_dfs.c")
endif()
set(priv_requires cmock test_utils esp_timer spi_flash esp_psram esp_driver_gpio esp_pm)
list(APPEND srcs "test_ets_timer.c")
else()
set(priv_requires cmock test_utils esp_timer)
endif()
idf_component_register(SRCS ${srcs}
PRIV_INCLUDE_DIRS "../../../private_include" "../../include"
PRIV_REQUIRES "${priv_requires}"
WHOLE_ARCHIVE)
if(CONFIG_IDF_TARGET_LINUX AND NOT CMAKE_HOST_SYSTEM_NAME STREQUAL "Darwin")
# Linux host heap integrity checks are stubs, so use ASan to catch dump overflows.
set(asan_options -fsanitize=address -fno-omit-frame-pointer)
idf_component_get_property(esp_timer_lib esp_timer COMPONENT_LIB)
target_compile_options(${esp_timer_lib} PRIVATE ${asan_options})
target_compile_options(${COMPONENT_LIB} PRIVATE ${asan_options})
idf_build_set_property(LINK_OPTIONS -fsanitize=address APPEND)
endif()
@@ -0,0 +1,52 @@
/*
* SPDX-FileCopyrightText: 2022-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#include "unity.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_task_wdt.h"
#include "esp_private/esp_timer_private.h"
// On C2 due to only have a single HW WDT we use the timer
// so some tests will interfere with the WDT
#if !CONFIG_IDF_TARGET_ESP32C2 && !CONFIG_IDF_TARGET_LINUX
#define WDT_MONITOR_UNITY_TASK
#endif
void setUp(void)
{
#ifdef WDT_MONITOR_UNITY_TASK
esp_task_wdt_add(NULL);
esp_task_wdt_reset();
#endif
}
void tearDown(void)
{
#ifdef WDT_MONITOR_UNITY_TASK
esp_task_wdt_reset();
esp_task_wdt_delete(NULL);
#endif
// Give some time for the esp_timer task to clean up between test runs, to avoid interference with the next test run.
vTaskDelay(pdMS_TO_TICKS(100));
}
void app_main(void)
{
// Configure the task watchdog timer to catch any tests that hang
#ifdef WDT_MONITOR_UNITY_TASK
esp_task_wdt_config_t config = {
.timeout_ms = 25 * 1000, // 25 seconds, smaller than the default pytest timeout
.idle_core_mask = 0,
.trigger_panic = true,
};
esp_task_wdt_init(&config);
#endif
vTaskPrioritySet(NULL, CONFIG_UNITY_FREERTOS_PRIORITY);
unity_run_menu();
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,229 @@
/*
* 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
ESP_LOG_ATTR_TAG(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};
#elif CONFIG_IDF_TARGET_ESP32S31
static uint32_t supported_freq[] = {10, 20, 40, 90, 180, 320};
#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 = -(ALARM_PERIOD_MS * 1000);
int64_t min_jitter_us = ALARM_PERIOD_MS * 1000;
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 (jitter_us > max_jitter_us) {
max_jitter_us = jitter_us;
}
if (jitter_us < min_jitter_us) {
min_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);
if (max_jitter_us > 0) {
printf("\e[1;31mMax jitter us: %"PRIi64"\n\e[0m", max_jitter_us);
} else {
printf("Max jitter us: %"PRIi64"\n", max_jitter_us);
}
printf("Min jitter us: %"PRIi64"\n", min_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 max_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(CLK_CAL_RTC_SLOW, 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 max_jitter_us = measuring_periodic_timer_accuracy(s_alarm_records);
vSemaphoreDelete(s_alarm_finished);
return max_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 : -300;
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 max_jitter_us = test_periodic_timer_accuracy_on_dfs(periodic_timer);
last_delay = test_delay;
if (max_jitter_us > 0) {
test_delay = (test_delay + min_delay) / 2;
max_delay = last_delay;
} else {
test_delay = (test_delay + max_delay) / 2;
min_delay = last_delay;
}
if (llabs(max_jitter_us) < llabs(min_avg)) {
best_delay = last_delay;
min_avg = max_jitter_us;
}
} 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
@@ -0,0 +1,86 @@
/*
* SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include <inttypes.h>
#include "unity.h"
#include "unity_test_utils.h"
#include "freertos/FreeRTOS.h"
#include "esp_attr.h"
#include "esp_etm.h"
#include "driver/gpio_etm.h"
#include "driver/gpio.h"
#include "esp_timer.h"
static void periodic_timer_callback(void *arg)
{
}
TEST_CASE("esp_timer_etm_event", "[etm]")
{
// systimer alarm ---> EMT channel ---> GPIO toggle
const uint32_t output_gpio = 1;
printf("allocate etm channels\r\n");
esp_etm_channel_config_t etm_config = {};
esp_etm_channel_handle_t etm_channel_a = NULL;
TEST_ESP_OK(esp_etm_new_channel(&etm_config, &etm_channel_a));
printf("allocate GPIO etm task\r\n");
esp_etm_task_handle_t gpio_task = NULL;
gpio_etm_task_config_t gpio_task_config = {
.action = GPIO_ETM_TASK_ACTION_CLR,
};
TEST_ESP_OK(gpio_new_etm_task(&gpio_task_config, &gpio_task));
// bind GPIO to the task
TEST_ESP_OK(gpio_etm_task_add_gpio(gpio_task, output_gpio));
printf("initialize gpio\r\n");
gpio_config_t task_gpio_config = {
.intr_type = GPIO_INTR_DISABLE,
.mode = GPIO_MODE_INPUT_OUTPUT,
.pin_bit_mask = 1ULL << output_gpio,
};
TEST_ESP_OK(gpio_config(&task_gpio_config));
// put the GPIO into initial state
TEST_ESP_OK(gpio_set_level(output_gpio, 1));
printf("acquire esp_timer etm event\r\n");
esp_etm_event_handle_t esp_timer_event = NULL;
TEST_ESP_OK(esp_timer_new_etm_alarm_event(&esp_timer_event));
printf("connect event and task to the channel\r\n");
TEST_ESP_OK(esp_etm_channel_connect(etm_channel_a, esp_timer_event, gpio_task));
TEST_ESP_OK(esp_etm_channel_enable(etm_channel_a));
printf("create a periodic esp_timer\r\n");
const esp_timer_create_args_t periodic_timer_args = {
.callback = periodic_timer_callback,
.name = "periodic"
};
esp_timer_handle_t periodic_timer = NULL;
TEST_ESP_OK(esp_timer_create(&periodic_timer_args, &periodic_timer));
TEST_ESP_OK(esp_timer_start_periodic(periodic_timer, 500000));
vTaskDelay(pdMS_TO_TICKS(100));
// etm event not active yet, so the GPIO level should still be the initial state
TEST_ASSERT_EQUAL(1, gpio_get_level(output_gpio));
vTaskDelay(pdMS_TO_TICKS(1000));
// etm event should have cleared the GPIO level
TEST_ASSERT_EQUAL(0, gpio_get_level(output_gpio));
TEST_ESP_OK(esp_timer_stop(periodic_timer));
TEST_ESP_OK(esp_timer_delete(periodic_timer));
// delete etm primitives
TEST_ESP_OK(gpio_etm_task_rm_gpio(gpio_task, output_gpio));
TEST_ESP_OK(esp_etm_del_task(gpio_task));
TEST_ESP_OK(esp_etm_del_event(esp_timer_event));
TEST_ESP_OK(esp_etm_channel_disable(etm_channel_a));
TEST_ESP_OK(esp_etm_del_channel(etm_channel_a));
}
@@ -0,0 +1,58 @@
/*
* SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <sys/time.h>
#include <sys/param.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_timer.h"
#include "unity.h"
#include "esp_rom_sys.h"
#include "esp_sleep.h"
#include "soc/soc_caps.h"
static void timer_cb1(void *arg)
{
++*((int*) arg);
}
TEST_CASE("Test the periodic timer does not handle lost events during light sleep", "[esp_timer][timeout=20]")
{
int count_calls;
const esp_timer_create_args_t timer_args = {
.name = "timer_cb1",
.arg = &count_calls,
.callback = &timer_cb1,
.skip_unhandled_events = true,
};
esp_timer_handle_t periodic_timer;
esp_timer_create(&timer_args, &periodic_timer);
int period_cb_ms = 10;
int interval_ms = 50;
printf("Run light sleep\n");
printf("count_calls should be around = %d\n", interval_ms / period_cb_ms);
TEST_ESP_OK(esp_sleep_enable_timer_wakeup(interval_ms * 1000));
TEST_ESP_OK(esp_timer_start_periodic(periodic_timer, period_cb_ms * 1000));
for (int i = 0; i < 3; i++) {
do {
count_calls = 0;
} while (esp_light_sleep_start() != ESP_OK);
esp_rom_delay_us(interval_ms * 1000);
int saved_count_calls = count_calls;
printf("count_calls = %d\n", saved_count_calls);
TEST_ESP_OK(esp_timer_dump(stdout));
TEST_ASSERT_INT_WITHIN(2, interval_ms / period_cb_ms, saved_count_calls);
}
TEST_ESP_OK(esp_timer_stop(periodic_timer));
// times_skipped is about 12 (4 from each sleep time).
TEST_ESP_OK(esp_timer_dump(stdout));
TEST_ESP_OK(esp_timer_delete(periodic_timer));
vTaskDelay(3); // wait for the esp_timer task to delete all timers
}
@@ -0,0 +1,262 @@
/*
* SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include <stdlib.h>
#include <inttypes.h>
#include <time.h>
#include <sys/time.h>
#include "unity.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "spi_flash_mmap.h"
#include "esp_rom_sys.h"
#include "esp_private/spi_flash_os.h"
#include "rom/ets_sys.h"
#include "unity_test_utils_cache.h"
static void test_correct_delay_timer_func(void* arg)
{
struct timeval* ptv = (struct timeval*) arg;
gettimeofday(ptv, NULL);
}
TEST_CASE("ets_timer produces correct delay", "[ets_timer]")
{
ETSTimer timer1 = {0};
const int delays_ms[] = {20, 100, 200, 250};
const size_t delays_count = sizeof(delays_ms) / sizeof(delays_ms[0]);
for (size_t i = 0; i < delays_count; ++i) {
struct timeval tv_end = {0};
ets_timer_setfn(&timer1, &test_correct_delay_timer_func, &tv_end);
struct timeval tv_start;
gettimeofday(&tv_start, NULL);
ets_timer_arm(&timer1, delays_ms[i], false);
vTaskDelay(delays_ms[i] * 2 / portTICK_PERIOD_MS);
int32_t ms_diff = (tv_end.tv_sec - tv_start.tv_sec) * 1000 +
(tv_end.tv_usec - tv_start.tv_usec) / 1000;
printf("%d %"PRIi32"\n", delays_ms[i], ms_diff);
TEST_ASSERT_INT32_WITHIN(portTICK_PERIOD_MS, delays_ms[i], ms_diff);
}
ets_timer_disarm(&timer1);
ets_timer_done(&timer1);
}
// no, we can't make this a const size_t (§6.7.5.2)
#define NUM_INTERVALS 16
typedef struct {
ETSTimer *timer;
size_t cur_interval;
int intervals[NUM_INTERVALS];
struct timeval tv_start;
} test_periodic_correct_delays_args_t;
static void test_periodic_correct_delays_timer_func(void* arg)
{
test_periodic_correct_delays_args_t *p_args = (test_periodic_correct_delays_args_t *) arg;
struct timeval tv_now;
gettimeofday(&tv_now, NULL);
int32_t ms_diff = (tv_now.tv_sec - p_args->tv_start.tv_sec) * 1000 +
(tv_now.tv_usec - p_args->tv_start.tv_usec) / 1000;
printf("timer #%d %"PRIi32"ms\n", p_args->cur_interval, ms_diff);
p_args->intervals[p_args->cur_interval++] = ms_diff;
// Deliberately make timer handler run longer.
// We check that this doesn't affect the result.
esp_rom_delay_us(10 * 1000);
if (p_args->cur_interval == NUM_INTERVALS) {
printf("done\n");
ets_timer_disarm(p_args->timer);
}
}
TEST_CASE("periodic ets_timer produces correct delays", "[ets_timer]")
{
const int delay_ms = 100;
ETSTimer timer1 = {0};
test_periodic_correct_delays_args_t args = {0};
args.timer = &timer1;
gettimeofday(&args.tv_start, NULL);
ets_timer_setfn(&timer1, &test_periodic_correct_delays_timer_func, &args);
ets_timer_arm(&timer1, delay_ms, true);
vTaskDelay(delay_ms * (NUM_INTERVALS + 1));
TEST_ASSERT_EQUAL_UINT32(NUM_INTERVALS, args.cur_interval);
for (size_t i = 0; i < NUM_INTERVALS; ++i) {
TEST_ASSERT_INT32_WITHIN(portTICK_PERIOD_MS, (i + 1) * delay_ms, args.intervals[i]);
}
ets_timer_done(&timer1);
}
#undef NUM_INTERVALS
#define N 5
typedef struct {
const int order[N * 3];
size_t count;
} test_timers_ordered_correctly_common_t;
typedef struct {
int timer_index;
const int intervals[N];
size_t intervals_count;
ETSTimer* timer;
test_timers_ordered_correctly_common_t* common;
bool pass;
SemaphoreHandle_t done;
} test_timers_ordered_correctly_args_t;
static void test_timers_ordered_correctly_timer_func(void* arg)
{
test_timers_ordered_correctly_args_t* p_args = (test_timers_ordered_correctly_args_t*) arg;
// check order
size_t count = p_args->common->count;
int expected_index = p_args->common->order[count];
printf("At count %d, expected timer %d, got timer %d\n",
count, expected_index, p_args->timer_index);
if (expected_index != p_args->timer_index) {
p_args->pass = false;
ets_timer_disarm(p_args->timer);
xSemaphoreGive(p_args->done);
return;
}
p_args->common->count++;
if (++p_args->intervals_count == N) {
ets_timer_disarm(p_args->timer);
xSemaphoreGive(p_args->done);
return;
}
int next_interval = p_args->intervals[p_args->intervals_count];
printf("timer %d interval #%d, %d ms\n",
p_args->timer_index, p_args->intervals_count, next_interval);
ets_timer_arm(p_args->timer, next_interval, false);
}
TEST_CASE("multiple ETSTimers are ordered correctly", "[ets_timer]")
{
ETSTimer timer1;
ETSTimer timer2;
ETSTimer timer3;
test_timers_ordered_correctly_common_t common = {
.order = {1, 2, 3, 2, 1, 3, 1, 2, 1, 3, 2, 1, 3, 3, 2},
.count = 0
};
SemaphoreHandle_t done = xSemaphoreCreateCounting(3, 0);
test_timers_ordered_correctly_args_t args1 = {
.timer_index = 1,
.intervals = {10, 40, 20, 40, 30},
.timer = &timer1,
.common = &common,
.pass = true,
.done = done
};
test_timers_ordered_correctly_args_t args2 = {
.timer_index = 2,
.intervals = {20, 20, 60, 30, 40},
.timer = &timer2,
.common = &common,
.pass = true,
.done = done
};
test_timers_ordered_correctly_args_t args3 = {
.timer_index = 3,
.intervals = {30, 30, 60, 30, 10},
.timer = &timer3,
.common = &common,
.pass = true,
.done = done
};
ets_timer_setfn(&timer1, &test_timers_ordered_correctly_timer_func, &args1);
ets_timer_setfn(&timer2, &test_timers_ordered_correctly_timer_func, &args2);
ets_timer_setfn(&timer3, &test_timers_ordered_correctly_timer_func, &args3);
ets_timer_arm(&timer1, args1.intervals[0], false);
ets_timer_arm(&timer2, args2.intervals[0], false);
ets_timer_arm(&timer3, args3.intervals[0], false);
for (int i = 0; i < 3; ++i) {
int result = xSemaphoreTake(done, 180 / portTICK_PERIOD_MS);
TEST_ASSERT_TRUE(result == pdPASS);
}
TEST_ASSERT_TRUE(args1.pass);
TEST_ASSERT_TRUE(args2.pass);
TEST_ASSERT_TRUE(args3.pass);
ets_timer_done(&timer1);
ets_timer_done(&timer2);
ets_timer_done(&timer3);
}
#undef N
static void IRAM_ATTR test_iram_timer_func(void* arg)
{
volatile bool *b = (volatile bool *)arg;
*b = true;
}
#define INTERVAL 5
void IRAM_ATTR test_iram_arm_disarm(void *ctx)
{
ETSTimer *timer1 = (ETSTimer*)ctx;
ets_timer_arm(timer1, INTERVAL, false);
// redundant call is deliberate (test code path if already armed)
ets_timer_arm(timer1, INTERVAL, false);
ets_timer_disarm(timer1);
}
void IRAM_ATTR test_iram_arm(void *ctx)
{
ETSTimer *timer1 = (ETSTimer*)ctx;
ets_timer_arm(timer1, INTERVAL, false);
}
void IRAM_ATTR test_iram_disarm(void *ctx)
{
ETSTimer *timer1 = (ETSTimer*)ctx;
ets_timer_disarm(timer1);
}
/* WiFi/BT coexistence will sometimes arm/disarm
timers from an ISR where flash may be disabled. */
IRAM_ATTR TEST_CASE("ETSTimers arm & disarm run from IRAM", "[ets_timer]")
{
volatile bool flag = false;
ETSTimer timer1;
ets_timer_setfn(&timer1, &test_iram_timer_func, (void *)&flag);
/* arm a disabled timer, then disarm a live timer */
unity_utils_run_cache_disable_stub(test_iram_arm_disarm, &timer1);
TEST_ASSERT_FALSE(flag); // didn't expire yet
/* do the same thing but wait for the timer to expire */
unity_utils_run_cache_disable_stub(test_iram_arm, &timer1);
vTaskDelay(2 * INTERVAL / portTICK_PERIOD_MS);
TEST_ASSERT_TRUE(flag);
unity_utils_run_cache_disable_stub(test_iram_disarm, &timer1);
ets_timer_done(&timer1);
}
@@ -0,0 +1,74 @@
# SPDX-FileCopyrightText: 2022-2026 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Unlicense OR CC0-1.0
import pytest
from pytest_embedded import Dut
from pytest_embedded_idf.utils import idf_parametrize
@pytest.mark.generic
@idf_parametrize(
'config,target',
[
('general', 'supported_targets'),
('release', 'supported_targets'),
('single_core', 'esp32'),
('freertos_compliance', 'esp32'),
('isr_dispatch_esp32', 'esp32'),
('isr_dispatch_esp32c3', 'esp32c3'),
('cpu1_esp32', 'esp32'),
('any_cpu_esp32', 'esp32'),
('cpu1_esp32s3', 'esp32s3'),
('any_cpu_esp32s3', 'esp32s3'),
],
indirect=['config', 'target'],
)
def test_esp_timer(dut: Dut) -> None:
dut.run_all_single_board_cases(timeout=120)
@pytest.mark.host_test
@pytest.mark.parametrize('config', ['default'], indirect=True)
@idf_parametrize('target', ['linux'], indirect=['target'])
def test_esp_timer_linux(dut: Dut) -> None:
dut.run_all_single_board_cases(timeout=180)
@pytest.mark.generic
@pytest.mark.psram
@pytest.mark.parametrize(
'config',
[
'psram',
],
indirect=True,
)
@idf_parametrize('target', ['esp32'], indirect=['target'])
def test_esp_timer_psram(dut: Dut) -> None:
dut.run_all_single_board_cases(timeout=120)
@pytest.mark.generic
@pytest.mark.xtal_26mhz
@pytest.mark.parametrize(
'config, baud',
[
('26mhz_esp32c2', '74880'),
],
indirect=True,
)
@idf_parametrize('target', ['esp32c2'], indirect=['target'])
def test_esp_timer_esp32c2_xtal_26mhz(dut: Dut) -> None:
dut.run_all_single_board_cases(timeout=120)
@pytest.mark.flash_suspend
@pytest.mark.parametrize(
'config',
[
'flash_auto_suspend',
],
indirect=True,
)
@idf_parametrize('target', ['esp32c3'], indirect=['target'])
def test_esp_timer_flash_auto_suspend(dut: Dut) -> None:
dut.run_all_single_board_cases(timeout=120)
@@ -0,0 +1,2 @@
CONFIG_IDF_TARGET="esp32c2"
CONFIG_XTAL_FREQ_26=y
@@ -0,0 +1,5 @@
CONFIG_IDF_TARGET="esp32"
CONFIG_ESP_TIMER_SUPPORTS_ISR_DISPATCH_METHOD=y
CONFIG_ESP_TIMER_SHOW_EXPERIMENTAL=y
CONFIG_ESP_TIMER_TASK_AFFINITY_NO_AFFINITY=y
CONFIG_ESP_TIMER_ISR_AFFINITY_NO_AFFINITY=y
@@ -0,0 +1,5 @@
CONFIG_IDF_TARGET="esp32s3"
CONFIG_ESP_TIMER_SUPPORTS_ISR_DISPATCH_METHOD=y
CONFIG_ESP_TIMER_SHOW_EXPERIMENTAL=y
CONFIG_ESP_TIMER_TASK_AFFINITY_NO_AFFINITY=y
CONFIG_ESP_TIMER_ISR_AFFINITY_NO_AFFINITY=y
@@ -0,0 +1,5 @@
CONFIG_IDF_TARGET="esp32"
CONFIG_ESP_TIMER_SUPPORTS_ISR_DISPATCH_METHOD=y
CONFIG_ESP_TIMER_SHOW_EXPERIMENTAL=y
CONFIG_ESP_TIMER_TASK_AFFINITY_CPU1=y
CONFIG_ESP_TIMER_ISR_AFFINITY_CPU1=y
@@ -0,0 +1,5 @@
CONFIG_IDF_TARGET="esp32s3"
CONFIG_ESP_TIMER_SUPPORTS_ISR_DISPATCH_METHOD=y
CONFIG_ESP_TIMER_SHOW_EXPERIMENTAL=y
CONFIG_ESP_TIMER_TASK_AFFINITY_CPU1=y
CONFIG_ESP_TIMER_ISR_AFFINITY_CPU1=y
@@ -0,0 +1 @@
# Default CI config uses sdkconfig.defaults plus target-specific defaults.
@@ -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
@@ -0,0 +1,3 @@
CONFIG_SPI_FLASH_AUTO_SUSPEND=y
CONFIG_ESP_TIMER_IN_IRAM=n
CONFIG_SPI_FLASH_FORCE_ENABLE_XMC_C_SUSPEND=y
@@ -0,0 +1 @@
CONFIG_FREERTOS_CHECK_PORT_CRITICAL_COMPLIANCE=y
@@ -0,0 +1,2 @@
CONFIG_IDF_TARGET="esp32"
CONFIG_ESP_TIMER_SUPPORTS_ISR_DISPATCH_METHOD=y
@@ -0,0 +1,2 @@
CONFIG_IDF_TARGET="esp32c3"
CONFIG_ESP_TIMER_SUPPORTS_ISR_DISPATCH_METHOD=y
@@ -0,0 +1,5 @@
CONFIG_IDF_TARGET="esp32"
CONFIG_SPIRAM=y
CONFIG_ESP_INT_WDT_TIMEOUT_MS=800
CONFIG_SPIRAM_OCCUPY_NO_HOST=y
CONFIG_SPIRAM_MALLOC_ALWAYSINTERNAL=0
@@ -0,0 +1,3 @@
CONFIG_COMPILER_OPTIMIZATION_SIZE=y
CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_SIZE=y
CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_SILENT=y
@@ -0,0 +1,2 @@
CONFIG_IDF_TARGET="esp32"
CONFIG_FREERTOS_UNICORE=y
@@ -0,0 +1,13 @@
# General options for additional checks
CONFIG_HEAP_POISONING_COMPREHENSIVE=y
CONFIG_COMPILER_WARN_WRITE_STRINGS=y
CONFIG_FREERTOS_WATCHPOINT_END_OF_STACK=y
CONFIG_COMPILER_STACK_CHECK_MODE_STRONG=y
CONFIG_COMPILER_STACK_CHECK=y
CONFIG_ESP_TASK_WDT_INIT=n
CONFIG_FREERTOS_HZ=1000
CONFIG_ESP_MAIN_TASK_STACK_SIZE=8192
CONFIG_ESP_TIMER_PROFILING=y
@@ -0,0 +1 @@
CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_240=y
@@ -0,0 +1 @@
CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_240=y
@@ -0,0 +1 @@
CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_240=y
@@ -0,0 +1,2 @@
CONFIG_FREERTOS_WATCHPOINT_END_OF_STACK=n
CONFIG_ESP_INT_WDT=n
@@ -0,0 +1 @@
CONFIG_BOOTLOADER_LOG_LEVEL_WARN=y