diff --git a/components/esp32/esp_timer_esp32.c b/components/esp32/esp_timer_esp32.c index 1bc488575..b2c29721b 100644 --- a/components/esp32/esp_timer_esp32.c +++ b/components/esp32/esp_timer_esp32.c @@ -165,6 +165,16 @@ static inline void IRAM_ATTR timer_count_reload(void) REG_WRITE(FRC_TIMER_LOAD_REG(1), REG_READ(FRC_TIMER_COUNT_REG(1)) - ALARM_OVERFLOW_VAL); } +void esp_timer_impl_lock() +{ + portENTER_CRITICAL(&s_time_update_lock); +} + +void esp_timer_impl_unlock() +{ + portEXIT_CRITICAL(&s_time_update_lock); +} + uint64_t IRAM_ATTR esp_timer_impl_get_time() { uint32_t timer_val; @@ -317,9 +327,13 @@ void esp_timer_impl_advance(int64_t time_us) portENTER_CRITICAL(&s_time_update_lock); uint64_t count = REG_READ(FRC_TIMER_COUNT_REG(1)); + /* Trigger an ISR to handle past alarms and set new one. + * ISR handler will run once we exit the critical section. + */ + REG_WRITE(FRC_TIMER_ALARM_REG(1), 0); REG_WRITE(FRC_TIMER_LOAD_REG(1), 0); s_time_base_us += count / s_timer_ticks_per_us + time_us; - esp_timer_impl_set_alarm(esp_timer_get_next_alarm()); + s_overflow_happened = false; portEXIT_CRITICAL(&s_time_update_lock); } diff --git a/components/esp32/esp_timer_impl.h b/components/esp32/esp_timer_impl.h index 9c4642c47..5871428a5 100644 --- a/components/esp32/esp_timer_impl.h +++ b/components/esp32/esp_timer_impl.h @@ -84,3 +84,18 @@ uint64_t esp_timer_impl_get_time(); * @return minimal period of periodic timer, in microseconds */ uint64_t esp_timer_impl_get_min_period_us(); + +/** + * @brief obtain internal critical section used esp_timer implementation + * This can be used when a sequence of calls to esp_timer has to be made, + * and it is necessary that the state of the timer is consistent between + * the calls. Should be treated in the same way as a spinlock. + * Call esp_timer_impl_unlock to release the lock + */ +void esp_timer_impl_lock(); + + +/** + * @brief counterpart of esp_timer_impl_lock + */ +void esp_timer_impl_unlock(); diff --git a/components/esp32/sleep_modes.c b/components/esp32/sleep_modes.c index a5f14d7f2..65d0f0568 100644 --- a/components/esp32/sleep_modes.c +++ b/components/esp32/sleep_modes.c @@ -279,6 +279,11 @@ esp_err_t esp_light_sleep_start() { static portMUX_TYPE light_sleep_lock = portMUX_INITIALIZER_UNLOCKED; portENTER_CRITICAL(&light_sleep_lock); + /* We will be calling esp_timer_impl_advance inside DPORT access critical + * section. Make sure the code on the other CPU is not holding esp_timer + * lock, otherwise there will be deadlock. + */ + esp_timer_impl_lock(); s_config.rtc_ticks_at_sleep_start = rtc_time_get(); uint64_t frc_time_at_start = esp_timer_get_time(); DPORT_STALL_OTHER_CPU_START(); @@ -332,6 +337,7 @@ esp_err_t esp_light_sleep_start() } esp_set_time_from_rtc(); + esp_timer_impl_unlock(); DPORT_STALL_OTHER_CPU_END(); rtc_wdt_disable(); portEXIT_CRITICAL(&light_sleep_lock); diff --git a/components/esp32/test/test_esp_timer.c b/components/esp32/test/test_esp_timer.c index 4007528bd..da8d8951c 100644 --- a/components/esp32/test/test_esp_timer.c +++ b/components/esp32/test/test_esp_timer.c @@ -9,6 +9,7 @@ #include "freertos/task.h" #include "freertos/semphr.h" #include "test_utils.h" +#include "../esp_timer_impl.h" #ifdef CONFIG_ESP_TIMER_PROFILING #define WITH_PROFILING 1 @@ -418,3 +419,62 @@ TEST_CASE("Can delete timer from callback", "[esp_timer]") vSemaphoreDelete(args.notify_from_timer_cb); } + +TEST_CASE("esp_timer_impl_advance moves time base correctly", "[esp_timer]") +{ + ref_clock_init(); + int64_t t0 = esp_timer_get_time(); + const int64_t diff_us = 1000000; + esp_timer_impl_advance(diff_us); + int64_t t1 = esp_timer_get_time(); + int64_t t_delta = t1 - t0; + printf("diff_us=%lld t1-t0=%lld\n", diff_us, t_delta); + TEST_ASSERT_INT_WITHIN(1000, diff_us, (int) t_delta); + ref_clock_deinit(); +} + + +TEST_CASE("after esp_timer_impl_advance, timers run when expected", "[esp_timer]") +{ + typedef struct { + int64_t cb_time; + } test_state_t; + + void timer_func(void* varg) { + test_state_t* arg = (test_state_t*) varg; + arg->cb_time = ref_clock_get(); + } + + ref_clock_init(); + + test_state_t state = { 0 }; + + esp_timer_create_args_t timer_args = { + .callback = &timer_func, + .arg = &state + }; + esp_timer_handle_t timer; + TEST_ESP_OK(esp_timer_create(&timer_args, &timer)); + + const int64_t interval = 10000; + const int64_t advance = 2000; + + printf("test 1\n"); + int64_t t_start = ref_clock_get(); + esp_timer_start_once(timer, interval); + esp_timer_impl_advance(advance); + vTaskDelay(2 * interval / 1000 / portTICK_PERIOD_MS); + + TEST_ASSERT_INT_WITHIN(portTICK_PERIOD_MS * 1000, interval - advance, state.cb_time - t_start); + + printf("test 2\n"); + state.cb_time = 0; + t_start = ref_clock_get(); + esp_timer_start_once(timer, interval); + esp_timer_impl_advance(interval); + vTaskDelay(1); + + TEST_ASSERT(state.cb_time > t_start); + + ref_clock_deinit(); +} diff --git a/components/esp32/test/test_sleep.c b/components/esp32/test/test_sleep.c index ac090fff9..c298d3220 100644 --- a/components/esp32/test/test_sleep.c +++ b/components/esp32/test/test_sleep.c @@ -91,6 +91,35 @@ TEST_CASE("light sleep stress test", "[deepsleep]") vSemaphoreDelete(done); } +TEST_CASE("light sleep stress test with periodic esp_timer", "[deepsleep]") +{ + void timer_func(void* arg) + { + ets_delay_us(50); + } + + SemaphoreHandle_t done = xSemaphoreCreateCounting(2, 0); + esp_sleep_enable_timer_wakeup(1000); + esp_timer_handle_t timer; + esp_timer_create_args_t config = { + .callback = &timer_func, + }; + TEST_ESP_OK(esp_timer_create(&config, &timer)); + esp_timer_start_periodic(timer, 500); + xTaskCreatePinnedToCore(&test_light_sleep, "ls1", 4096, done, UNITY_FREERTOS_PRIORITY + 1, NULL, 0); +#if portNUM_PROCESSORS == 2 + xTaskCreatePinnedToCore(&test_light_sleep, "ls1", 4096, done, UNITY_FREERTOS_PRIORITY + 1, NULL, 1); +#endif + xSemaphoreTake(done, portMAX_DELAY); +#if portNUM_PROCESSORS == 2 + xSemaphoreTake(done, portMAX_DELAY); +#endif + vSemaphoreDelete(done); + esp_timer_stop(timer); + esp_timer_delete(timer); +} + + #ifdef CONFIG_ESP32_RTC_CLOCK_SOURCE_EXTERNAL_CRYSTAL #define MAX_SLEEP_TIME_ERROR_US 200 #else