From c8614a0dbf4aaec86596bf521a168a152487d4d8 Mon Sep 17 00:00:00 2001 From: Omar Chebib Date: Wed, 28 Sep 2022 11:45:58 +0800 Subject: [PATCH] esp_timer: add a function to restart timer Timers, periodic or not, can now be restarted thanks to esp_timer_restart function. This is done atomically, which can be used to feed a periodic timer, or simply change the period. --- components/esp_pm/linker.lf | 4 +- .../task_wdt/task_wdt_impl_esp_timer.c | 9 +-- components/esp_timer/include/esp_timer.h | 16 ++++ components/esp_timer/src/esp_timer.c | 28 ++++--- components/esp_timer/test/test_esp_timer.c | 80 ++++++++++++++----- 5 files changed, 95 insertions(+), 42 deletions(-) diff --git a/components/esp_pm/linker.lf b/components/esp_pm/linker.lf index 95e8b48069..6ef1e90e06 100644 --- a/components/esp_pm/linker.lf +++ b/components/esp_pm/linker.lf @@ -47,10 +47,10 @@ entries: archive: libesp_timer.a entries: if PM_SLP_IRAM_OPT = y: - # esp_timer_feed is called from task_wdt_timer_feed, so put it + # esp_timer_restart is called from task_wdt_timer_feed, so put it # in IRAM if task_wdt_timer_feed itself is in IRAM. if ESP_TASK_WDT_USE_ESP_TIMER = y: - esp_timer:esp_timer_feed (noflash) + esp_timer:esp_timer_restart (noflash) if ESP_TIMER_IMPL_TG0_LAC = y: esp_timer_impl_lac:esp_timer_impl_lock (noflash) esp_timer_impl_lac:esp_timer_impl_unlock (noflash) diff --git a/components/esp_system/task_wdt/task_wdt_impl_esp_timer.c b/components/esp_system/task_wdt/task_wdt_impl_esp_timer.c index 35bc61bc3e..2c6b4cd6a8 100644 --- a/components/esp_system/task_wdt/task_wdt_impl_esp_timer.c +++ b/components/esp_system/task_wdt/task_wdt_impl_esp_timer.c @@ -17,12 +17,6 @@ #include "esp_timer.h" #include "esp_private/esp_task_wdt_impl.h" -/** - * Private API provided by esp_timer component to feed a timer without - * the need of disabling it, removing it and inserting it manually. - */ -esp_err_t esp_timer_feed(esp_timer_handle_t timer); - /** * Context for the software implementation of the Task WatchDog Timer. * This will be passed as a parameter to public functions below. */ @@ -108,7 +102,8 @@ esp_err_t esp_task_wdt_impl_timer_feed(twdt_ctx_t obj) } if (ret == ESP_OK) { - ret = esp_timer_feed(ctx->sw_timer); + /* Feed the periodic timer by restarting it, specifying the same period */ + ret = esp_timer_restart(ctx->sw_timer, ctx->period_ms * 1000); } return ret; diff --git a/components/esp_timer/include/esp_timer.h b/components/esp_timer/include/esp_timer.h index c62c8296e4..1fd8e049c4 100644 --- a/components/esp_timer/include/esp_timer.h +++ b/components/esp_timer/include/esp_timer.h @@ -164,6 +164,22 @@ esp_err_t esp_timer_start_once(esp_timer_handle_t timer, uint64_t timeout_us); */ esp_err_t esp_timer_start_periodic(esp_timer_handle_t timer, uint64_t period); +/** + * @brief Restart a currently running timer + * + * If the given timer is a one-shot timer, the timer is restarted immediately and will timeout once in `timeout_us` microseconds. + * If the given timer is a periodic timer, the timer is restarted immediately with a new period of `timeout_us` microseconds. + * + * @param timer timer Handle created using esp_timer_create + * @param timeout_us Timeout, in microseconds relative to the current time. + * In case of a periodic timer, also represents the new period. + * @return + * - ESP_OK on success + * - ESP_ERR_INVALID_ARG if the handle is invalid + * - ESP_ERR_INVALID_STATE if the timer is not running + */ +esp_err_t esp_timer_restart(esp_timer_handle_t timer, uint64_t timeout_us); + /** * @brief Stop the timer * diff --git a/components/esp_timer/src/esp_timer.c b/components/esp_timer/src/esp_timer.c index bca212d253..8d4d17db66 100644 --- a/components/esp_timer/src/esp_timer.c +++ b/components/esp_timer/src/esp_timer.c @@ -143,11 +143,7 @@ esp_err_t esp_timer_create(const esp_timer_create_args_t* args, return ESP_OK; } -/** - * Function to feed a timer. It is not part of the public header - * file on purpose as it shall only be used by the Task WDT component. - */ -esp_err_t esp_timer_feed(esp_timer_handle_t timer) +esp_err_t esp_timer_restart(esp_timer_handle_t timer, uint64_t timeout_us) { esp_err_t ret = ESP_OK; @@ -155,7 +151,7 @@ esp_err_t esp_timer_feed(esp_timer_handle_t timer) return ESP_ERR_INVALID_ARG; } - if (!is_initialized() || !timer_armed(timer) || timer->period == 0 ) { + if (!is_initialized() || !timer_armed(timer)) { return ESP_ERR_INVALID_STATE; } @@ -164,10 +160,6 @@ esp_err_t esp_timer_feed(esp_timer_handle_t timer) const int64_t now = esp_timer_impl_get_time(); const uint64_t period = timer->period; - /* Currently we are guaranteed that the remaining delay between now and the timer's - * alarm is less than the period, so the following won't cause the alarm to be - * triggered earlier than before feeding occurs. */ - const uint64_t alarm = now + period; /* We need to remove the timer to the list of timers and reinsert it at * the right position. In fact, the timers are sorted by their alarm value @@ -175,9 +167,19 @@ esp_err_t esp_timer_feed(esp_timer_handle_t timer) ret = timer_remove(timer); if (ret == ESP_OK) { - /* Remove function got rid of the alarm and period fields, restore them */ - timer->alarm = alarm; - timer->period = period; + /* Two cases here: + * - if the alarm was a periodic one, i.e. `period` is not 0, the given timeout_us becomes the new period + * - if the alarm was a one-shot one, i.e. `period` is 0, it remains non-periodic. */ + if (period != 0) { + /* Remove function got rid of the alarm and period fields, restore them */ + const uint64_t new_period = MAX(timeout_us, esp_timer_impl_get_min_period_us()); + timer->alarm = now + new_period; + timer->period = new_period; + } else { + /* The new one-shot alarm shall be triggered timeout_us after the current time */ + timer->alarm = now + timeout_us; + timer->period = 0; + } ret = timer_insert(timer, false); } diff --git a/components/esp_timer/test/test_esp_timer.c b/components/esp_timer/test/test_esp_timer.c index e26b0c40c2..b5fa41e13a 100644 --- a/components/esp_timer/test/test_esp_timer.c +++ b/components/esp_timer/test/test_esp_timer.c @@ -866,48 +866,88 @@ TEST_CASE("Test a latency between a call of callback and real event", "[esp_time TEST_ESP_OK(esp_timer_delete(periodic_timer)); } -static void test_periodic_timer_feed(void* timer1_fed) +static void test_timer_triggered(void* timer1_trig) { - *((int*) timer1_fed) = 1; + int* timer = (int *)timer1_trig; + *timer = *timer + 1; } -/** - * Feed function is not part of the esp_timer header file: it's a public in the sense that it is not static, - * but it is only meant to be used in IDF components. - */ -esp_err_t esp_timer_feed(esp_timer_handle_t timer); - -TEST_CASE("periodic esp_timer can be fed", "[esp_timer]") +TEST_CASE("periodic esp_timer can be restarted", "[esp_timer]") { const int delay_ms = 100; - int timer_fed = 0; + int timer_trig = 0; esp_timer_handle_t timer1; esp_timer_create_args_t create_args = { - .callback = &test_periodic_timer_feed, - .arg = &timer_fed, + .callback = &test_timer_triggered, + .arg = &timer_trig, .name = "timer1", }; TEST_ESP_OK(esp_timer_create(&create_args, &timer1)); TEST_ESP_OK(esp_timer_start_periodic(timer1, delay_ms * 1000)); - /* Sleep for delay_ms/2 and feed the timer */ + /* Sleep for delay_ms/2 and restart the timer */ vTaskDelay((delay_ms / 2) * portTICK_PERIOD_MS); /* Check that the alarm was not triggered */ - TEST_ASSERT_EQUAL(0, timer_fed); + TEST_ASSERT_EQUAL(0, timer_trig); /* Reaching this point, the timer will be triggered in delay_ms/2. - * Let's feed the timer now. */ - TEST_ESP_OK(esp_timer_feed(timer1)); + * Let's restart the timer now with the same period. */ + TEST_ESP_OK(esp_timer_restart(timer1, delay_ms * 1000)); /* Sleep for a bit more than delay_ms/2 */ vTaskDelay(((delay_ms / 2) + 1) * portTICK_PERIOD_MS); - /* If the alarm was triggered, feed didn't work */ - TEST_ASSERT_EQUAL(0, timer_fed); + /* If the alarm was triggered, restart didn't work */ + TEST_ASSERT_EQUAL(0, timer_trig); /* Else, wait for another delay_ms/2, which should trigger the alarm */ - vTaskDelay(((delay_ms / 2) + 1) * portTICK_PERIOD_MS); - TEST_ASSERT_EQUAL(1, timer_fed); + vTaskDelay(((delay_ms / 2) + 2) * portTICK_PERIOD_MS); + TEST_ASSERT_EQUAL(1, timer_trig); + /* Now wait for another delay_ms to make sure the timer is still periodic */ + timer_trig = 0; + vTaskDelay((delay_ms * portTICK_PERIOD_MS) + 1); + /* Make sure the timer was triggered */ + TEST_ASSERT_EQUAL(1, timer_trig); + /* Reduce the period of the timer to delay/2 */ + timer_trig = 0; + TEST_ESP_OK(esp_timer_restart(timer1, delay_ms / 2 * 1000)); + vTaskDelay((delay_ms * portTICK_PERIOD_MS) + 1); + /* Check that the alarm was triggered twice */ + TEST_ASSERT_EQUAL(2, timer_trig); TEST_ESP_OK( esp_timer_stop(timer1) ); TEST_ESP_OK( esp_timer_delete(timer1) ); } +TEST_CASE("one-shot esp_timer can be restarted", "[esp_timer]") +{ + const int delay_ms = 100; + int timer_trig = 0; + esp_timer_handle_t timer1; + esp_timer_create_args_t create_args = { + .callback = &test_timer_triggered, + .arg = &timer_trig, + .name = "timer1", + }; + TEST_ESP_OK(esp_timer_create(&create_args, &timer1)); + TEST_ESP_OK(esp_timer_start_once(timer1, delay_ms * 1000)); + vTaskDelay((delay_ms / 2) * portTICK_PERIOD_MS); + /* Check that the alarm was not triggered */ + TEST_ASSERT_EQUAL(0, timer_trig); + /* Reaching this point, the timer will be triggered in delay_ms/2. + * Let's restart the timer now with the same timeout. */ + TEST_ESP_OK(esp_timer_restart(timer1, delay_ms * 1000)); + vTaskDelay(((delay_ms / 2) + 1) * portTICK_PERIOD_MS); + /* If the alarm was triggered, restart didn't work */ + TEST_ASSERT_EQUAL(0, timer_trig); + /* Else, wait for another delay_ms/2, which should trigger the alarm */ + vTaskDelay(((delay_ms / 2) + 2) * portTICK_PERIOD_MS); + TEST_ASSERT_EQUAL(1, timer_trig); + /* Make sure the timer is NOT periodic, wait for another delay and make sure + * our callback was not called */ + timer_trig = 0; + vTaskDelay(delay_ms * 2 * portTICK_PERIOD_MS); + /* Make sure the timer was triggered */ + TEST_ASSERT_EQUAL(0, timer_trig); + + TEST_ESP_OK( esp_timer_delete(timer1) ); +} + #ifdef CONFIG_ESP_TIMER_SUPPORTS_ISR_DISPATCH_METHOD static int64_t old_time[2];