diff --git a/components/esp_pm/pm_impl.c b/components/esp_pm/pm_impl.c index 83e923ece1..9f43317922 100644 --- a/components/esp_pm/pm_impl.c +++ b/components/esp_pm/pm_impl.c @@ -622,7 +622,7 @@ void IRAM_ATTR vApplicationSleep( TickType_t xExpectedIdleTime ) int core_id = xPortGetCoreID(); if (!should_skip_light_sleep(core_id)) { /* Calculate how much we can sleep */ - int64_t next_esp_timer_alarm = esp_timer_get_next_alarm(); + int64_t next_esp_timer_alarm = esp_timer_get_next_alarm_for_wake_up(); int64_t now = esp_timer_get_time(); int64_t time_until_next_alarm = next_esp_timer_alarm - now; int64_t wakeup_delay_us = portTICK_PERIOD_MS * 1000LL * xExpectedIdleTime; diff --git a/components/esp_pm/test/test_pm.c b/components/esp_pm/test/test_pm.c index 7a3abe8760..ee7319231e 100644 --- a/components/esp_pm/test/test_pm.c +++ b/components/esp_pm/test/test_pm.c @@ -360,6 +360,42 @@ TEST_CASE("esp_timer produces correct delays with light sleep", "[pm]") #undef NUM_INTERVALS } +static void timer_cb1(void *arg) +{ + ++*((int*) arg); +} + +TEST_CASE("esp_timer with SKIP_UNHANDLED_EVENTS does not wake up CPU from sleep", "[pm]") +{ + int count_calls = 0; + int timer_interval_ms = 50; + + 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); + TEST_ESP_OK(esp_timer_start_periodic(periodic_timer, timer_interval_ms * 1000)); + + light_sleep_enable(); + + const unsigned count_delays = 5; + unsigned i = count_delays; + while (i-- > 0) { + vTaskDelay(pdMS_TO_TICKS(500)); + } + TEST_ASSERT_INT_WITHIN(1, count_delays, count_calls); + + light_sleep_disable(); + + TEST_ESP_OK(esp_timer_stop(periodic_timer)); + TEST_ESP_OK(esp_timer_dump(stdout)); + TEST_ESP_OK(esp_timer_delete(periodic_timer)); +} + #endif // CONFIG_FREERTOS_USE_TICKLESS_IDLE #endif // CONFIG_PM_ENABLE diff --git a/components/esp_timer/include/esp_timer.h b/components/esp_timer/include/esp_timer.h index bb8879cd93..cbda4dfe66 100644 --- a/components/esp_timer/include/esp_timer.h +++ b/components/esp_timer/include/esp_timer.h @@ -195,6 +195,13 @@ int64_t esp_timer_get_time(void); */ int64_t esp_timer_get_next_alarm(void); +/** + * @brief Get the timestamp when the next timeout is expected to occur skipping those which have skip_unhandled_events flag + * @return Timestamp of the nearest timer event, in microseconds. + * The timebase is the same as for the values returned by esp_timer_get_time. + */ +int64_t esp_timer_get_next_alarm_for_wake_up(void); + /** * @brief Dump the list of timers to a stream * diff --git a/components/esp_timer/src/esp_timer.c b/components/esp_timer/src/esp_timer.c index 8e370a8604..3be8b257b8 100644 --- a/components/esp_timer/src/esp_timer.c +++ b/components/esp_timer/src/esp_timer.c @@ -598,3 +598,26 @@ int64_t IRAM_ATTR esp_timer_get_next_alarm(void) } return next_alarm; } + +int64_t IRAM_ATTR esp_timer_get_next_alarm_for_wake_up(void) +{ + int64_t next_alarm = INT64_MAX; + for (esp_timer_dispatch_t dispatch_method = ESP_TIMER_TASK; dispatch_method < ESP_TIMER_MAX; ++dispatch_method) { + timer_list_lock(dispatch_method); + esp_timer_handle_t it; + LIST_FOREACH(it, &s_timers[dispatch_method], list_entry) { + if (it == NULL) { + break; + } + // timers with the SKIP_UNHANDLED_EVENTS flag do not want to wake up CPU from a sleep mode. + if ((it->flags & FL_SKIP_UNHANDLED_EVENTS) == 0) { + if (next_alarm > it->alarm) { + next_alarm = it->alarm; + } + break; + } + } + timer_list_unlock(dispatch_method); + } + return next_alarm; +} diff --git a/components/freertos/Kconfig b/components/freertos/Kconfig index 38a4816ff3..ffe2f4987e 100644 --- a/components/freertos/Kconfig +++ b/components/freertos/Kconfig @@ -368,6 +368,7 @@ menu "FreeRTOS" Note that timers created using esp_timer APIs may prevent the system from entering sleep mode, even when no tasks need to run. + To skip unnecessary wake-up initialize a timer with the "skip_unhandled_events" option as true. If disabled, automatic light sleep support will be disabled. diff --git a/docs/en/api-reference/system/esp_timer.rst b/docs/en/api-reference/system/esp_timer.rst index cc6e13db73..2e6987bb68 100644 --- a/docs/en/api-reference/system/esp_timer.rst +++ b/docs/en/api-reference/system/esp_timer.rst @@ -82,6 +82,8 @@ you can use the `skip_unhandled_events` option during :cpp:func:`esp_timer_creat When the `skip_unhandled_events` is true, if a periodic timer expires one or more times during light sleep then only one callback is called on wake. +Using the `skip_unhandled_events` option with `automatic light sleep` (see :doc:`Power Management APIs `) helps to reduce the consumption of the system when it is in light sleep. The duration of light sleep is also determined by esp_timers. Timers with `skip_unhandled_events` option will not wake up the system. + Handling callbacks ------------------ diff --git a/docs/en/api-reference/system/power_management.rst b/docs/en/api-reference/system/power_management.rst index d0852e4ef4..a33df04d7e 100644 --- a/docs/en/api-reference/system/power_management.rst +++ b/docs/en/api-reference/system/power_management.rst @@ -87,6 +87,8 @@ If none of the locks are acquired, and light sleep is enabled in a call to :cpp: Light sleep duration will be chosen to wake up the chip before the nearest event (task being unblocked, or timer elapses). +To skip unnecessary wake-up you can consider initializing an esp_timer with the `skip_unhandled_events` option as true. Timers with this flag will not wake up the system and it helps to reduce consumption. + Dynamic Frequency Scaling and Peripheral Drivers ------------------------------------------------