mirror of
https://github.com/espressif/esp-idf.git
synced 2026-10-01 18:50:34 +03:00
Merge branch 'feature/esp_timer_is_active_when_cb_is_running_v5.5' into 'release/v5.5'
feat(esp_timer): Adds blocking stop API (v5.5) See merge request espressif/esp-idf!52241
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2017-2024 Espressif Systems (Shanghai) CO LTD
|
||||
* SPDX-FileCopyrightText: 2017-2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
@@ -196,6 +196,12 @@ esp_err_t esp_timer_restart(esp_timer_handle_t timer, uint64_t timeout_us);
|
||||
* This function stops the timer previously started using esp_timer_start_once()
|
||||
* or esp_timer_start_periodic().
|
||||
*
|
||||
* This function disarms the timer (prevents future callbacks) but does NOT
|
||||
* preempt an already-dispatched or currently-running callback.
|
||||
*
|
||||
* For a hard guarantee that no callback is running or will run after stop,
|
||||
* use esp_timer_stop_blocking() or poll esp_timer_is_active() until it returns false.
|
||||
*
|
||||
* @param timer timer handle created using esp_timer_create()
|
||||
* @return
|
||||
* - ESP_OK on success
|
||||
@@ -203,6 +209,49 @@ esp_err_t esp_timer_restart(esp_timer_handle_t timer, uint64_t timeout_us);
|
||||
*/
|
||||
esp_err_t esp_timer_stop(esp_timer_handle_t timer);
|
||||
|
||||
/**
|
||||
* @brief Stop a running timer and wait for any in-flight callback to complete
|
||||
*
|
||||
* This function disarms the timer (prevents future callbacks). If the timer callback is
|
||||
* currently executing, it waits for the callback to finish, taking into account the specified waiting time.
|
||||
*
|
||||
* If this function returns ESP_OK, the caller is guaranteed that the timer is disarmed and
|
||||
* no callback is executing after the function returns (see the calling-context rules below).
|
||||
*
|
||||
* @note Behavior depends on calling context:
|
||||
* - **User task context (normal case)**: Disarms the timer and waits up to timeout_ticks
|
||||
* for any in-flight callback to complete. Returns ESP_OK when no callback is running,
|
||||
* or ESP_ERR_TIMEOUT if the callback didn't finish in time.
|
||||
*
|
||||
* - **ISR context**: Never blocks. Disarms the timer and returns immediately.
|
||||
* If the timer callback may still be running, returns ESP_ERR_NOT_FINISHED.
|
||||
*
|
||||
* - **Callback context**: Disarms the timer and returns immediately. Blocking from a callback
|
||||
* would delay the esp_timer task and distort timing of other TASK-dispatch timers.
|
||||
* Return value depends on the stopped timer state and the currently running callback:
|
||||
* - Returns ESP_OK when the stop request is issued from the stopped timer’s own callback
|
||||
* (the callback will complete naturally after it returns).
|
||||
* - Returns ESP_ERR_NOT_FINISHED when stopping an ISR-dispatch timer from a foreign callback
|
||||
* (i.e., the currently running callback belongs to another timer). In this case the caller
|
||||
* cannot safely wait for ISR context to complete, so it is informed that the callback may
|
||||
* still be running.
|
||||
*
|
||||
* If ESP_ERR_NOT_FINISHED is returned, it means that the timer is disarmed, but its callback
|
||||
* may is still running or will run soon. In this case, you can wait for the callback to complete
|
||||
* using esp_timer_is_active().
|
||||
*
|
||||
* @param timer Timer handle created using esp_timer_create()
|
||||
* @param timeout_ticks Maximum time to wait for callback completion, in FreeRTOS ticks.
|
||||
* Use portMAX_DELAY to wait indefinitely.
|
||||
* @return
|
||||
* - ESP_OK on success (timer is disarmed, no callback is running or will run)
|
||||
* - ESP_ERR_TIMEOUT if the callback didn't complete within timeout_ticks
|
||||
* - ESP_ERR_INVALID_ARG if the timer handle is invalid
|
||||
* - ESP_ERR_INVALID_STATE if esp_timer is not initialized
|
||||
* - ESP_ERR_NOT_FINISHED if the callback may still be running (e.g., ISR context, or TIMER_TASK while an ISR callback is in-flight)
|
||||
*/
|
||||
esp_err_t esp_timer_stop_blocking(esp_timer_handle_t timer, uint32_t timeout_ticks);
|
||||
|
||||
/**
|
||||
* @brief Delete an esp_timer instance
|
||||
*
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2017-2025 Espressif Systems (Shanghai) CO LTD
|
||||
* SPDX-FileCopyrightText: 2017-2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
@@ -38,12 +38,13 @@
|
||||
typedef enum {
|
||||
FL_ISR_DISPATCH_METHOD = (1 << 0), //!< 0=Callback is called from timer task, 1=Callback is called from timer ISR
|
||||
FL_SKIP_UNHANDLED_EVENTS = (1 << 1), //!< 0=NOT skip unhandled events for periodic timers, 1=Skip unhandled events for periodic timers
|
||||
FL_CALLBACK_IS_RUNNING = (1 << 2), //!< 0=Callback is NOT running, 1=Callback is running
|
||||
} flags_t;
|
||||
|
||||
struct esp_timer {
|
||||
uint64_t alarm;
|
||||
uint64_t period: 56;
|
||||
flags_t flags: 8;
|
||||
volatile flags_t flags: 8;
|
||||
union {
|
||||
esp_timer_cb_t callback;
|
||||
uint32_t event_id;
|
||||
@@ -61,10 +62,11 @@ struct esp_timer {
|
||||
|
||||
static inline bool is_initialized(void);
|
||||
static esp_err_t timer_insert(esp_timer_handle_t timer, bool without_update_alarm);
|
||||
static esp_err_t timer_remove(esp_timer_handle_t timer);
|
||||
static void timer_remove(esp_timer_handle_t timer);
|
||||
static bool timer_armed(esp_timer_handle_t timer);
|
||||
static void timer_list_lock(esp_timer_dispatch_t timer_type);
|
||||
static void timer_list_unlock(esp_timer_dispatch_t timer_type);
|
||||
static esp_err_t timer_restart(esp_timer_handle_t timer, uint64_t timeout_us, uint64_t alarm_us);
|
||||
|
||||
#if WITH_PROFILING
|
||||
static void timer_insert_inactive(esp_timer_handle_t timer);
|
||||
@@ -133,6 +135,11 @@ esp_err_t esp_timer_create(const esp_timer_create_args_t* args,
|
||||
* in IRAM when PM_SLP_IRAM_OPT = y and ESP_TASK_WDT USE ESP_TIMER = y.
|
||||
*/
|
||||
esp_err_t ESP_TIMER_IRAM_ATTR esp_timer_restart(esp_timer_handle_t timer, uint64_t timeout_us)
|
||||
{
|
||||
return timer_restart(timer, timeout_us, 0);
|
||||
}
|
||||
|
||||
static esp_err_t ESP_TIMER_IRAM_ATTR timer_restart(esp_timer_handle_t timer, uint64_t timeout_us, uint64_t first_alarm_us)
|
||||
{
|
||||
esp_err_t ret = ESP_OK;
|
||||
|
||||
@@ -159,24 +166,23 @@ esp_err_t ESP_TIMER_IRAM_ATTR esp_timer_restart(esp_timer_handle_t timer, uint64
|
||||
/* 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
|
||||
* (earliest first) */
|
||||
ret = timer_remove(timer);
|
||||
timer_remove(timer);
|
||||
|
||||
if (ret == ESP_OK) {
|
||||
/* 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);
|
||||
/* 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 min_period = esp_timer_impl_get_min_period_us();
|
||||
const uint64_t new_period = MAX(timeout_us, min_period);
|
||||
timer->alarm = (first_alarm_us != 0) ? first_alarm_us : now + new_period;
|
||||
timer->period = new_period;
|
||||
} else {
|
||||
/* The new one-shot alarm shall be triggered timeout_us after the current time */
|
||||
timer->alarm = (first_alarm_us != 0) ? first_alarm_us : now + timeout_us;
|
||||
timer->period = 0;
|
||||
}
|
||||
ret = timer_insert(timer, false);
|
||||
|
||||
timer_list_unlock(dispatch_method);
|
||||
|
||||
@@ -255,7 +261,7 @@ esp_err_t ESP_TIMER_IRAM_ATTR esp_timer_stop(esp_timer_handle_t timer)
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
esp_timer_dispatch_t dispatch_method = timer->flags & FL_ISR_DISPATCH_METHOD;
|
||||
esp_err_t err;
|
||||
esp_err_t err = ESP_OK;
|
||||
|
||||
timer_list_lock(dispatch_method);
|
||||
|
||||
@@ -263,12 +269,88 @@ esp_err_t ESP_TIMER_IRAM_ATTR esp_timer_stop(esp_timer_handle_t timer)
|
||||
if (!timer_armed(timer)) {
|
||||
err = ESP_ERR_INVALID_STATE;
|
||||
} else {
|
||||
err = timer_remove(timer);
|
||||
timer_remove(timer);
|
||||
}
|
||||
timer_list_unlock(dispatch_method);
|
||||
return err;
|
||||
}
|
||||
|
||||
static inline bool is_callback_running(esp_timer_handle_t timer, esp_timer_dispatch_t dispatch_method)
|
||||
{
|
||||
timer_list_lock(dispatch_method);
|
||||
bool callback_running = (timer != NULL) && (timer->flags & FL_CALLBACK_IS_RUNNING) != 0;
|
||||
timer_list_unlock(dispatch_method);
|
||||
return callback_running;
|
||||
}
|
||||
|
||||
esp_err_t esp_timer_stop_blocking(esp_timer_handle_t timer, uint32_t timeout_ticks)
|
||||
{
|
||||
if (timer == NULL) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
if (!is_initialized()) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
esp_timer_dispatch_t dispatch_method = timer->flags & FL_ISR_DISPATCH_METHOD;
|
||||
|
||||
timer_list_lock(dispatch_method);
|
||||
|
||||
bool callback_running = (timer->flags & FL_CALLBACK_IS_RUNNING) != 0;
|
||||
/* Check if the timer is armed once the list is locked to avoid a data race */
|
||||
if (timer_armed(timer)) {
|
||||
timer_remove(timer);
|
||||
}
|
||||
|
||||
timer_list_unlock(dispatch_method);
|
||||
|
||||
if (callback_running) {
|
||||
// timer_process_alarm() releases the timer list lock while executing the callback.
|
||||
// So it is possible that timer is disarmed but its callback is still running.
|
||||
// To guarantee that the callback will not run after esp_timer_stop_blocking(),
|
||||
// we need to wait for the callback to complete here.
|
||||
|
||||
// In ISR context: do not wait to avoid blocking
|
||||
if (xPortInIsrContext()) {
|
||||
return ESP_ERR_NOT_FINISHED;
|
||||
}
|
||||
|
||||
if (xTaskGetCurrentTaskHandle() == s_timer_task) {
|
||||
// Called from the esp_timer task context (i.e., the callback owner is a TASK-dispatch timer).
|
||||
// Concurrency model:
|
||||
// - TASK-dispatch callbacks are executed by a single esp_timer task and are strictly serialized.
|
||||
// Therefore, only one TASK callback can be running at any time.
|
||||
// - If CONFIG_ESP_TIMER_SUPPORTS_ISR_DISPATCH_METHOD is enabled,
|
||||
// ISR-dispatch callbacks and TASK-dispatch callbacks may be running at the same time.
|
||||
if (dispatch_method == ESP_TIMER_TASK) {
|
||||
// Only one ESP_TIMER_TASK callback can be running at any time.
|
||||
// So we are stopping the timer from its own callback context:
|
||||
// the callback will complete naturally after this function returns.
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
// Stopping a running ISR-dispatch timer from a foreign callback:
|
||||
// we cannot wait for ISR context to complete, and blocking the esp_timer task would
|
||||
// stall TASK-dispatch callbacks. Report that the timer callback is still running.
|
||||
return ESP_ERR_NOT_FINISHED;
|
||||
}
|
||||
|
||||
TickType_t start_time = xTaskGetTickCount();
|
||||
while (is_callback_running(timer, dispatch_method)) {
|
||||
if (timeout_ticks != portMAX_DELAY) {
|
||||
TickType_t elapsed = xTaskGetTickCount() - start_time;
|
||||
if (elapsed >= timeout_ticks) {
|
||||
return ESP_ERR_TIMEOUT;
|
||||
}
|
||||
}
|
||||
vTaskDelay(1);
|
||||
}
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t esp_timer_delete(esp_timer_handle_t timer)
|
||||
{
|
||||
if (timer == NULL) {
|
||||
@@ -325,10 +407,10 @@ static ESP_TIMER_IRAM_ATTR esp_err_t timer_insert(esp_timer_handle_t timer, bool
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static ESP_TIMER_IRAM_ATTR esp_err_t timer_remove(esp_timer_handle_t timer)
|
||||
// It should be always called with the timer list locked
|
||||
static ESP_TIMER_IRAM_ATTR void timer_remove(esp_timer_handle_t timer)
|
||||
{
|
||||
esp_timer_dispatch_t dispatch_method = timer->flags & FL_ISR_DISPATCH_METHOD;
|
||||
timer_list_lock(dispatch_method);
|
||||
esp_timer_handle_t first_timer = LIST_FIRST(&s_timers[dispatch_method]);
|
||||
LIST_REMOVE(timer, list_entry);
|
||||
timer->alarm = 0;
|
||||
@@ -344,8 +426,6 @@ static ESP_TIMER_IRAM_ATTR esp_err_t timer_remove(esp_timer_handle_t timer)
|
||||
#if WITH_PROFILING
|
||||
timer_insert_inactive(timer);
|
||||
#endif
|
||||
timer_list_unlock(dispatch_method);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
#if WITH_PROFILING
|
||||
@@ -415,6 +495,7 @@ static bool timer_process_alarm(esp_timer_dispatch_t dispatch_method)
|
||||
free(it);
|
||||
it = NULL;
|
||||
} else {
|
||||
it->flags |= FL_CALLBACK_IS_RUNNING;
|
||||
if (it->period > 0) {
|
||||
int skipped = (now - it->alarm) / it->period;
|
||||
if ((it->flags & FL_SKIP_UNHANDLED_EVENTS) && (skipped > 1)) {
|
||||
@@ -440,6 +521,7 @@ static bool timer_process_alarm(esp_timer_dispatch_t dispatch_method)
|
||||
timer_list_unlock(dispatch_method);
|
||||
(*callback)(arg);
|
||||
timer_list_lock(dispatch_method);
|
||||
it->flags &= ~FL_CALLBACK_IS_RUNNING;
|
||||
#if WITH_PROFILING
|
||||
it->times_triggered++;
|
||||
it->total_callback_run_time += esp_timer_impl_get_time() - callback_start;
|
||||
@@ -770,5 +852,14 @@ bool ESP_TIMER_IRAM_ATTR esp_timer_is_active(esp_timer_handle_t timer)
|
||||
if (timer == NULL) {
|
||||
return false;
|
||||
}
|
||||
return timer_armed(timer);
|
||||
esp_timer_dispatch_t dispatch_method = timer->flags & FL_ISR_DISPATCH_METHOD;
|
||||
|
||||
timer_list_lock(dispatch_method);
|
||||
// Timer is active if it is armed or its callback is currently running
|
||||
// After esp_timer_stop() timer is disarmed, but its callback may still be running
|
||||
bool active = (timer_armed(timer) && timer->event_id != EVENT_ID_DELETE_TIMER)
|
||||
|| ((timer->flags & FL_CALLBACK_IS_RUNNING) != 0);
|
||||
timer_list_unlock(dispatch_method);
|
||||
|
||||
return active;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD
|
||||
* SPDX-FileCopyrightText: 2022-2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
@@ -1393,3 +1393,146 @@ TEST_CASE("Test ISR dispatch callbacks are not blocked even if TASK callbacks ta
|
||||
}
|
||||
|
||||
#endif // CONFIG_ESP_TIMER_SUPPORTS_ISR_DISPATCH_METHOD
|
||||
|
||||
typedef struct {
|
||||
SemaphoreHandle_t callback_started;
|
||||
SemaphoreHandle_t stop_called_from_cb;
|
||||
SemaphoreHandle_t callback_can_finish;
|
||||
SemaphoreHandle_t callback_finished;
|
||||
volatile int callback_count;
|
||||
esp_timer_handle_t timer;
|
||||
} test_stop_blocking_state_t;
|
||||
|
||||
static void test_stop_blocking_callback(void* arg)
|
||||
{
|
||||
test_stop_blocking_state_t* state = (test_stop_blocking_state_t*) arg;
|
||||
state->callback_count++;
|
||||
|
||||
// Notify that callback has started
|
||||
xSemaphoreGive(state->callback_started);
|
||||
|
||||
// Wait for permission to finish (simulating long-running callback)
|
||||
xSemaphoreTake(state->callback_can_finish, portMAX_DELAY);
|
||||
|
||||
// Notify that callback has finished
|
||||
xSemaphoreGive(state->callback_finished);
|
||||
}
|
||||
|
||||
TEST_CASE("esp_timer_stop_blocking waits for callback completion", "[esp_timer]")
|
||||
{
|
||||
test_stop_blocking_state_t state = {
|
||||
.callback_started = xSemaphoreCreateBinary(),
|
||||
.stop_called_from_cb = NULL,
|
||||
.callback_can_finish = xSemaphoreCreateBinary(),
|
||||
.callback_finished = xSemaphoreCreateBinary(),
|
||||
.callback_count = 0,
|
||||
.timer = NULL,
|
||||
};
|
||||
|
||||
esp_timer_create_args_t timer_args = {
|
||||
.callback = test_stop_blocking_callback,
|
||||
.arg = &state,
|
||||
.name = "test_blocking",
|
||||
};
|
||||
|
||||
esp_timer_handle_t timer;
|
||||
TEST_ESP_OK(esp_timer_create(&timer_args, &timer));
|
||||
|
||||
// Start a one-shot timer
|
||||
TEST_ESP_OK(esp_timer_start_once(timer, 1000));
|
||||
|
||||
// Wait for callback to start
|
||||
TEST_ASSERT_TRUE(xSemaphoreTake(state.callback_started, pdMS_TO_TICKS(100)));
|
||||
|
||||
// At this point, callback is running
|
||||
TEST_ASSERT_TRUE(esp_timer_is_active(timer));
|
||||
|
||||
// Try to stop with blocking - this should wait for callback
|
||||
TEST_ESP_ERR(ESP_ERR_TIMEOUT, esp_timer_stop_blocking(timer, pdMS_TO_TICKS(100)));
|
||||
|
||||
// Now allow callback to finish
|
||||
xSemaphoreGive(state.callback_can_finish);
|
||||
|
||||
// Wait for callback to complete
|
||||
TEST_ASSERT_TRUE(xSemaphoreTake(state.callback_finished, pdMS_TO_TICKS(100)));
|
||||
|
||||
// Timer should no longer be active
|
||||
TEST_ASSERT_FALSE(esp_timer_is_active(timer));
|
||||
|
||||
// Callback should have run exactly once
|
||||
TEST_ASSERT_EQUAL(1, state.callback_count);
|
||||
|
||||
// Clean up
|
||||
TEST_ESP_OK(esp_timer_delete(timer));
|
||||
vSemaphoreDelete(state.callback_started);
|
||||
vSemaphoreDelete(state.callback_can_finish);
|
||||
vSemaphoreDelete(state.callback_finished);
|
||||
}
|
||||
|
||||
static void test_stop_blocking_callback_stop_inside(void* arg)
|
||||
{
|
||||
test_stop_blocking_state_t* state = (test_stop_blocking_state_t*) arg;
|
||||
state->callback_count++;
|
||||
|
||||
// Notify that callback has started (we are now in esp_timer task context for TASK dispatch)
|
||||
xSemaphoreGive(state->callback_started);
|
||||
|
||||
// Call stop_blocking from the timer's own callback:
|
||||
// This must NOT block (otherwise we deadlock waiting for ourselves).
|
||||
TEST_ESP_OK(esp_timer_stop_blocking(state->timer, portMAX_DELAY));
|
||||
|
||||
// Signal to the test that stop_blocking has returned from inside callback
|
||||
xSemaphoreGive(state->stop_called_from_cb);
|
||||
|
||||
// Keep callback running until the test allows us to finish
|
||||
xSemaphoreTake(state->callback_can_finish, portMAX_DELAY);
|
||||
|
||||
xSemaphoreGive(state->callback_finished);
|
||||
}
|
||||
|
||||
TEST_CASE("esp_timer_stop_blocking from timer callback and does not block", "[esp_timer]")
|
||||
{
|
||||
test_stop_blocking_state_t state = {
|
||||
.callback_started = xSemaphoreCreateBinary(),
|
||||
.stop_called_from_cb = xSemaphoreCreateBinary(),
|
||||
.callback_can_finish = xSemaphoreCreateBinary(),
|
||||
.callback_finished = xSemaphoreCreateBinary(),
|
||||
.timer = NULL,
|
||||
.callback_count = 0,
|
||||
};
|
||||
|
||||
esp_timer_create_args_t timer_args = {
|
||||
.callback = test_stop_blocking_callback_stop_inside,
|
||||
.arg = &state,
|
||||
.name = "stop_from_cb",
|
||||
};
|
||||
|
||||
esp_timer_handle_t timer;
|
||||
TEST_ESP_OK(esp_timer_create(&timer_args, &timer));
|
||||
state.timer = timer;
|
||||
|
||||
TEST_ESP_OK(esp_timer_start_once(timer, 1000));
|
||||
|
||||
// Wait until callback starts
|
||||
TEST_ASSERT_TRUE(xSemaphoreTake(state.callback_started, portMAX_DELAY));
|
||||
|
||||
// Ensure stop_blocking has returned from inside the callback (i.e., it did not block)
|
||||
TEST_ASSERT_TRUE(xSemaphoreTake(state.stop_called_from_cb, pdMS_TO_TICKS(200)));
|
||||
|
||||
// While callback is still running, timer should still be considered active
|
||||
TEST_ASSERT_TRUE(esp_timer_is_active(timer));
|
||||
|
||||
// Now let callback finish
|
||||
xSemaphoreGive(state.callback_can_finish);
|
||||
TEST_ASSERT_TRUE(xSemaphoreTake(state.callback_finished, pdMS_TO_TICKS(1000)));
|
||||
|
||||
// After callback finishes, timer should be inactive (one-shot already disarmed)
|
||||
TEST_ASSERT_FALSE(esp_timer_is_active(timer));
|
||||
TEST_ASSERT_EQUAL(1, state.callback_count);
|
||||
|
||||
TEST_ESP_OK(esp_timer_delete(timer));
|
||||
vSemaphoreDelete(state.callback_started);
|
||||
vSemaphoreDelete(state.stop_called_from_cb);
|
||||
vSemaphoreDelete(state.callback_can_finish);
|
||||
vSemaphoreDelete(state.callback_finished);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user