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.
pull/9839/head
Omar Chebib 2022-09-28 11:45:58 +08:00
rodzic 439455b440
commit c8614a0dbf
5 zmienionych plików z 95 dodań i 42 usunięć

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

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

Wyświetl plik

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