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.
This commit is contained in:
Omar Chebib 2022-09-28 11:45:58 +08:00
parent 1169dfa1eb
commit 3e9701205c
6 changed files with 97 additions and 44 deletions

View File

@ -46,10 +46,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)

View File

@ -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;

View File

@ -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
*

View File

@ -141,11 +141,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;
@ -153,7 +149,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;
}
@ -162,10 +158,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
@ -173,9 +165,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);
}

View File

@ -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];

View File

@ -19,7 +19,7 @@ The various watchdog timers can be enabled using the :ref:`project-configuration
Interrupt Watchdog Timer (IWDT)
-------------------------------
{IDF_IWDT_TIMER_GROUP:default="Timer Group 1", esp32c2="Timer Group 0"}
{IDF_TARGET_IWDT_TIMER_GROUP:default="Timer Group 1", esp32c2="Timer Group 0"}
The purpose of the IWDT is to ensure that interrupt service routines (ISRs) are not blocked from running for a prolonged period of time (i.e., the IWDT timeout period). Blocking ISRs from running in a timely manner is undesirable as it can increases ISR latency, and also prevents task switching (as task switching is executed form an ISR). The things that can block ISRs from running include:
@ -27,7 +27,7 @@ The purpose of the IWDT is to ensure that interrupt service routines (ISRs) are
- Critical Sections (also disables interrupts)
- Other same/higher priority ISRs (will block same/lower priority ISRs from running it completes execution)
The IWDT utilizes the watchdog timer in {IDF_IWDT_TIMER_GROUP} as its underlying hardware timer and leverages the FreeRTOS tick interrupt on each CPU to feed the watchdog timer. If the tick interrupt on a particular CPU is not run at within the IWDT timeout period, it is indicative that something is blocking ISRs from being run on that CPU (see the list of reasons above).
The IWDT utilizes the watchdog timer in {IDF_TARGET_IWDT_TIMER_GROUP} as its underlying hardware timer and leverages the FreeRTOS tick interrupt on each CPU to feed the watchdog timer. If the tick interrupt on a particular CPU is not run at within the IWDT timeout period, it is indicative that something is blocking ISRs from being run on that CPU (see the list of reasons above).
When the IWDT times out, the default action is to invoke the panic handler and display the panic reason as ``Interrupt wdt timeout on CPU0`` or ``Interrupt wdt timeout on CPU1`` (as applicable). Depending on the panic handler's configured behavior (see :ref:`CONFIG_ESP_SYSTEM_PANIC`), users can then debug the source of the IWDT timeout (via the backtrace, OpenOCD, gdbstub etc) or simply reset the chip (which may be preferred in a production environment).