unleashed-firmware/furi/core/event_loop_timer.c

216 lines
6.3 KiB
C
Raw Normal View History

[FL-3846] Event Loop Timers (#3721) * Implement POC event loop tmers (not all edge cases are handled) * Use a separate ready list to allow for (re)starting and stopping of timers from callback * Improve the test application * Improve timer API and test application * Improve timeout calculation logic * Improve timer API, update documentation * Fix API usage error * Update doxygen comments * Revert the old (correct) check * Improve function naming * Check whether a timer was on the expired list before processing it * Implement tick callback * Add critical sections to improve timer consistency * Simplify event loop timer API * Remove redundant search * Refactor timer logic, use message queue * Simplify FuriEventLoopTimer API * Improve event loop timer logic * Update the f18 target * Remove superfluous clears * Correct f18 api symbols * Fix doxygen comments * Update .pvsconfig * Use a double push list instead of deque * Update .pvsconfig * Add pending callback functionality * Restore unprocessed flags when applicable * Refactor Dolphin app to use FuriEventLoop * Improve naming * Update naming some more * Fix a typo Co-authored-by: Silent <CookiePLMonster@users.noreply.github.com> * Fix wait time in example * Bump API version * Debug: multiple of 25 timings in event loop blink test * Separate FuriEventLoopTimer to its own set of files * Improve start time calculation for periodic timers * Do not use dynamic allocations for timer requests * Split the tick functionality in separate files, rearrange code * Improve timer queue handling * Properly reset GPIO pins in the test app * Properly initialise GPIO pins in the test app too * Furi: variable naming in event loop * Furi: fix spelling in event loop Co-authored-by: あく <alleteam@gmail.com> Co-authored-by: Silent <CookiePLMonster@users.noreply.github.com>
2024-07-02 12:09:50 +00:00
#include "event_loop_i.h"
#include <FreeRTOS.h>
#include <task.h>
#include <furi.h>
/*
* Private functions
*/
static inline uint32_t furi_event_loop_timer_get_elapsed_time(const FuriEventLoopTimer* timer) {
return xTaskGetTickCount() - timer->start_time;
}
static inline uint32_t
furi_event_loop_timer_get_remaining_time_private(const FuriEventLoopTimer* timer) {
const uint32_t elapsed_time = furi_event_loop_timer_get_elapsed_time(timer);
return elapsed_time < timer->interval ? timer->interval - elapsed_time : 0;
}
static inline bool furi_event_loop_timer_is_expired(const FuriEventLoopTimer* timer) {
return furi_event_loop_timer_get_elapsed_time(timer) >= timer->interval;
}
static void furi_event_loop_schedule_timer(FuriEventLoop* instance, FuriEventLoopTimer* timer) {
FuriEventLoopTimer* timer_pos = NULL;
FURI_CRITICAL_ENTER();
const uint32_t remaining_time = furi_event_loop_timer_get_remaining_time_private(timer);
TimerList_it_t it;
for(TimerList_it_last(it, instance->timer_list); !TimerList_end_p(it);
TimerList_previous(it)) {
FuriEventLoopTimer* tmp = TimerList_ref(it);
if(remaining_time >= furi_event_loop_timer_get_remaining_time_private(tmp)) {
timer_pos = tmp;
break;
}
}
FURI_CRITICAL_EXIT();
if(timer_pos) {
TimerList_push_after(timer_pos, timer);
} else {
TimerList_push_front(instance->timer_list, timer);
}
// At this point, TimerList_front() points to the first timer to expire
}
static void furi_event_loop_timer_enqueue_request(
FuriEventLoopTimer* timer,
FuriEventLoopTimerRequest request) {
if(timer->request != FuriEventLoopTimerRequestNone) {
// You cannot change your mind after calling furi_event_loop_timer_free()
furi_check(timer->request != FuriEventLoopTimerRequestFree);
TimerQueue_unlink(timer);
}
timer->request = request;
FuriEventLoop* instance = timer->owner;
TimerQueue_push_back(instance->timer_queue, timer);
xTaskNotifyIndexed(
instance->thread_id, FURI_EVENT_LOOP_FLAG_NOTIFY_INDEX, FuriEventLoopFlagTimer, eSetBits);
}
/*
* Private API
*/
uint32_t furi_event_loop_get_timer_wait_time(const FuriEventLoop* instance) {
uint32_t wait_time = FuriWaitForever;
if(!TimerList_empty_p(instance->timer_list)) {
FuriEventLoopTimer* timer = TimerList_front(instance->timer_list);
wait_time = furi_event_loop_timer_get_remaining_time_private(timer);
}
return wait_time;
}
void furi_event_loop_process_timer_queue(FuriEventLoop* instance) {
while(!TimerQueue_empty_p(instance->timer_queue)) {
FuriEventLoopTimer* timer = TimerQueue_pop_front(instance->timer_queue);
if(timer->active) {
TimerList_unlink(timer);
}
if(timer->request == FuriEventLoopTimerRequestStart) {
timer->active = true;
timer->interval = timer->next_interval;
timer->start_time = xTaskGetTickCount();
timer->request = FuriEventLoopTimerRequestNone;
furi_event_loop_schedule_timer(instance, timer);
} else if(timer->request == FuriEventLoopTimerRequestStop) {
timer->active = false;
timer->request = FuriEventLoopTimerRequestNone;
} else if(timer->request == FuriEventLoopTimerRequestFree) {
free(timer);
} else {
furi_crash();
}
}
}
bool furi_event_loop_process_expired_timers(FuriEventLoop* instance) {
if(TimerList_empty_p(instance->timer_list)) {
return false;
}
// The front() element contains the earliest-expiring timer
FuriEventLoopTimer* timer = TimerList_front(instance->timer_list);
if(!furi_event_loop_timer_is_expired(timer)) {
return false;
}
TimerList_unlink(timer);
if(timer->periodic) {
const uint32_t num_events =
furi_event_loop_timer_get_elapsed_time(timer) / timer->interval;
timer->start_time += timer->interval * num_events;
furi_event_loop_schedule_timer(instance, timer);
} else {
timer->active = false;
}
timer->callback(timer->context);
return true;
}
/*
* Public timer API
*/
FuriEventLoopTimer* furi_event_loop_timer_alloc(
FuriEventLoop* instance,
FuriEventLoopTimerCallback callback,
FuriEventLoopTimerType type,
void* context) {
furi_check(instance);
furi_check(instance->thread_id == furi_thread_get_current_id());
furi_check(callback);
furi_check(type <= FuriEventLoopTimerTypePeriodic);
FuriEventLoopTimer* timer = malloc(sizeof(FuriEventLoopTimer));
timer->owner = instance;
timer->callback = callback;
timer->context = context;
timer->periodic = (type == FuriEventLoopTimerTypePeriodic);
TimerList_init_field(timer);
TimerQueue_init_field(timer);
return timer;
}
void furi_event_loop_timer_free(FuriEventLoopTimer* timer) {
furi_check(timer);
furi_check(timer->owner->thread_id == furi_thread_get_current_id());
furi_event_loop_timer_enqueue_request(timer, FuriEventLoopTimerRequestFree);
}
void furi_event_loop_timer_start(FuriEventLoopTimer* timer, uint32_t interval) {
furi_check(timer);
furi_check(timer->owner->thread_id == furi_thread_get_current_id());
timer->next_interval = interval;
furi_event_loop_timer_enqueue_request(timer, FuriEventLoopTimerRequestStart);
}
void furi_event_loop_timer_restart(FuriEventLoopTimer* timer) {
furi_check(timer);
furi_check(timer->owner->thread_id == furi_thread_get_current_id());
timer->next_interval = timer->interval;
furi_event_loop_timer_enqueue_request(timer, FuriEventLoopTimerRequestStart);
}
void furi_event_loop_timer_stop(FuriEventLoopTimer* timer) {
furi_check(timer);
furi_check(timer->owner->thread_id == furi_thread_get_current_id());
furi_event_loop_timer_enqueue_request(timer, FuriEventLoopTimerRequestStop);
}
uint32_t furi_event_loop_timer_get_remaining_time(const FuriEventLoopTimer* timer) {
furi_check(timer);
return furi_event_loop_timer_get_remaining_time_private(timer);
}
uint32_t furi_event_loop_timer_get_interval(const FuriEventLoopTimer* timer) {
furi_check(timer);
return timer->interval;
}
bool furi_event_loop_timer_is_running(const FuriEventLoopTimer* timer) {
furi_check(timer);
return timer->active;
}