|
|
|
#include "timebase.h"
|
|
|
|
#include "bus/event_queue.h"
|
|
|
|
#include "com/debug.h"
|
|
|
|
#include "malloc_safe.h"
|
|
|
|
|
|
|
|
// Time base
|
|
|
|
static volatile ms_time_t SystemTime_ms = 0;
|
|
|
|
|
|
|
|
|
|
|
|
typedef struct {
|
|
|
|
/** User callback with arg */
|
|
|
|
void (*callback)(void *);
|
|
|
|
/** Arg for the arg callback */
|
|
|
|
void *cb_arg;
|
|
|
|
/** Callback interval */
|
|
|
|
ms_time_t interval_ms;
|
|
|
|
/** Counter, when reaches interval_ms, is cleared and callback is called. */
|
|
|
|
ms_time_t countup;
|
|
|
|
/** Unique task ID (for cancelling / modification) */
|
|
|
|
task_pid_t pid;
|
|
|
|
/** Enable flag - disabled tasks still count, but CB is not run */
|
|
|
|
bool enabled;
|
|
|
|
/** Marks that the task is due to be run */
|
|
|
|
bool enqueue;
|
|
|
|
} periodic_task_t;
|
|
|
|
|
|
|
|
|
|
|
|
typedef struct {
|
|
|
|
/** User callback with arg */
|
|
|
|
void (*callback)(void *);
|
|
|
|
/** Arg for the arg callback */
|
|
|
|
void *cb_arg;
|
|
|
|
/** Counter, when reaches 0ms, callback is called and the task is removed */
|
|
|
|
ms_time_t countdown_ms;
|
|
|
|
/** Unique task ID (for cancelling / modification) */
|
|
|
|
task_pid_t pid;
|
|
|
|
/** Whether this task is long and needs posting on the queue */
|
|
|
|
bool enqueue;
|
|
|
|
} future_task_t;
|
|
|
|
|
|
|
|
|
|
|
|
static size_t periodic_slot_count = 0;
|
|
|
|
static size_t future_slot_count = 0;
|
|
|
|
|
|
|
|
static periodic_task_t *periodic_tasks;
|
|
|
|
static future_task_t *future_tasks;
|
|
|
|
|
|
|
|
|
|
|
|
/** Init timebase */
|
|
|
|
void timebase_init(size_t periodic, size_t future)
|
|
|
|
{
|
|
|
|
periodic_slot_count = periodic;
|
|
|
|
future_slot_count = future;
|
|
|
|
|
|
|
|
periodic_tasks = calloc_s(periodic, sizeof(periodic_task_t));
|
|
|
|
future_tasks = calloc_s(future, sizeof(future_task_t));
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static task_pid_t next_task_pid = 1; // 0 (PID_NONE) is reserved
|
|
|
|
|
|
|
|
|
|
|
|
/** Get a valid free PID for a new task. */
|
|
|
|
static task_pid_t make_pid(void)
|
|
|
|
{
|
|
|
|
task_pid_t pid = next_task_pid++;
|
|
|
|
|
|
|
|
// make sure no task is given PID 0
|
|
|
|
if (next_task_pid == PID_NONE) {
|
|
|
|
next_task_pid++;
|
|
|
|
}
|
|
|
|
|
|
|
|
return pid;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** Take an empty periodic task slot and populate the basics. */
|
|
|
|
static periodic_task_t* claim_periodic_task_slot(ms_time_t interval, bool enqueue)
|
|
|
|
{
|
|
|
|
for (size_t i = 0; i < periodic_slot_count; i++) {
|
|
|
|
periodic_task_t *task = &periodic_tasks[i];
|
|
|
|
if (task->pid != PID_NONE) continue; // task is used
|
|
|
|
|
|
|
|
task->countup = 0;
|
|
|
|
task->interval_ms = interval - 1;
|
|
|
|
task->enqueue = enqueue;
|
|
|
|
task->pid = make_pid();
|
|
|
|
task->enabled = true;
|
|
|
|
return task;
|
|
|
|
}
|
|
|
|
|
|
|
|
error("Periodic task table full.");
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** Take an empty future task slot and populate the basics. */
|
|
|
|
static future_task_t* claim_future_task_slot(ms_time_t delay, bool enqueue)
|
|
|
|
{
|
|
|
|
for (size_t i = 0; i < future_slot_count; i++) {
|
|
|
|
future_task_t *task = &future_tasks[i];
|
|
|
|
if (task->pid != PID_NONE) continue; // task is used
|
|
|
|
|
|
|
|
task->countdown_ms = delay;
|
|
|
|
task->enqueue = enqueue;
|
|
|
|
task->pid = make_pid();
|
|
|
|
return task;
|
|
|
|
}
|
|
|
|
|
|
|
|
error("Future task table full.");
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Add a periodic task with an arg. */
|
|
|
|
task_pid_t add_periodic_task(void (*callback)(void*), void* arg, ms_time_t interval, bool enqueue)
|
|
|
|
{
|
|
|
|
periodic_task_t *task = claim_periodic_task_slot(interval, enqueue);
|
|
|
|
|
|
|
|
if (task == NULL) return PID_NONE;
|
|
|
|
|
|
|
|
task->callback = callback;
|
|
|
|
task->cb_arg = arg;
|
|
|
|
|
|
|
|
return task->pid;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** Schedule a future task, with uint32_t argument. */
|
|
|
|
task_pid_t schedule_task(void (*callback)(void*), void *arg, ms_time_t delay, bool enqueue)
|
|
|
|
{
|
|
|
|
future_task_t *task = claim_future_task_slot(delay, enqueue);
|
|
|
|
|
|
|
|
if (task == NULL) return PID_NONE;
|
|
|
|
|
|
|
|
task->callback = callback;
|
|
|
|
task->cb_arg = arg;
|
|
|
|
|
|
|
|
return task->pid;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** Enable or disable a periodic task. */
|
|
|
|
bool enable_periodic_task(task_pid_t pid, FunctionalState enable)
|
|
|
|
{
|
|
|
|
if (pid == PID_NONE) return false;
|
|
|
|
|
|
|
|
for (size_t i = 0; i < periodic_slot_count; i++) {
|
|
|
|
periodic_task_t *task = &periodic_tasks[i];
|
|
|
|
if (task->pid != pid) continue;
|
|
|
|
|
|
|
|
task->enabled = (enable == ENABLE);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** Check if a periodic task is enabled */
|
|
|
|
bool is_periodic_task_enabled(task_pid_t pid)
|
|
|
|
{
|
|
|
|
if (pid == PID_NONE) return false;
|
|
|
|
|
|
|
|
for (size_t i = 0; i < periodic_slot_count; i++) {
|
|
|
|
periodic_task_t *task = &periodic_tasks[i];
|
|
|
|
if (task->pid != pid) continue;
|
|
|
|
|
|
|
|
return task->enabled;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool reset_periodic_task(task_pid_t pid)
|
|
|
|
{
|
|
|
|
if (pid == PID_NONE) return false;
|
|
|
|
|
|
|
|
for (size_t i = 0; i < periodic_slot_count; i++) {
|
|
|
|
periodic_task_t *task = &periodic_tasks[i];
|
|
|
|
if (task->pid != pid) continue;
|
|
|
|
|
|
|
|
task->countup = 0;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool set_periodic_task_interval(task_pid_t pid, ms_time_t interval)
|
|
|
|
{
|
|
|
|
if (pid == PID_NONE) return false;
|
|
|
|
|
|
|
|
for (size_t i = 0; i < periodic_slot_count; i++) {
|
|
|
|
periodic_task_t *task = &periodic_tasks[i];
|
|
|
|
if (task->pid != pid) continue;
|
|
|
|
task->interval_ms = interval;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** Remove a periodic task. */
|
|
|
|
bool remove_periodic_task(task_pid_t pid)
|
|
|
|
{
|
|
|
|
if (pid == PID_NONE) return false;
|
|
|
|
|
|
|
|
for (size_t i = 0; i < periodic_slot_count; i++) {
|
|
|
|
periodic_task_t *task = &periodic_tasks[i];
|
|
|
|
if (task->pid != pid) continue;
|
|
|
|
|
|
|
|
task->pid = PID_NONE; // mark unused
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** Abort a scheduled task. */
|
|
|
|
bool abort_scheduled_task(task_pid_t pid)
|
|
|
|
{
|
|
|
|
if (pid == PID_NONE) return false;
|
|
|
|
|
|
|
|
for (size_t i = 0; i < future_slot_count; i++) {
|
|
|
|
future_task_t *task = &future_tasks[i];
|
|
|
|
if (task->pid != pid) continue;
|
|
|
|
|
|
|
|
task->pid = PID_NONE; // mark unused
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** Run a periodic task */
|
|
|
|
static void run_periodic_task(periodic_task_t *task)
|
|
|
|
{
|
|
|
|
if (!task->enabled) return;
|
|
|
|
|
|
|
|
if (task->enqueue) {
|
|
|
|
// queued task
|
|
|
|
tq_post(task->callback, task->cb_arg);
|
|
|
|
} else {
|
|
|
|
// immediate task
|
|
|
|
task->callback(task->cb_arg);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** Run a future task */
|
|
|
|
static void run_future_task(future_task_t *task)
|
|
|
|
{
|
|
|
|
if (task->enqueue) {
|
|
|
|
// queued task
|
|
|
|
tq_post(task->callback, task->cb_arg);
|
|
|
|
} else {
|
|
|
|
// immediate task
|
|
|
|
task->callback(task->cb_arg);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @brief Millisecond callback, should be run in the SysTick handler.
|
|
|
|
*/
|
|
|
|
void timebase_ms_cb(void)
|
|
|
|
{
|
|
|
|
// increment global time
|
|
|
|
SystemTime_ms++;
|
|
|
|
|
|
|
|
// run periodic tasks
|
|
|
|
for (size_t i = 0; i < periodic_slot_count; i++) {
|
|
|
|
periodic_task_t *task = &periodic_tasks[i];
|
|
|
|
if (task->pid == PID_NONE) continue; // unused
|
|
|
|
|
|
|
|
if (task->countup++ >= task->interval_ms) {
|
|
|
|
// run if enabled
|
|
|
|
run_periodic_task(task);
|
|
|
|
// restart counter
|
|
|
|
task->countup = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// run planned future tasks
|
|
|
|
for (size_t i = 0; i < future_slot_count; i++) {
|
|
|
|
future_task_t *task = &future_tasks[i];
|
|
|
|
if (task->pid == PID_NONE) continue; // unused
|
|
|
|
|
|
|
|
if (task->countdown_ms-- == 0) {
|
|
|
|
// run
|
|
|
|
run_future_task(task);
|
|
|
|
// release the slot
|
|
|
|
task->pid = PID_NONE;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** Seconds delay */
|
|
|
|
void delay_s(uint32_t s)
|
|
|
|
{
|
|
|
|
while (s-- != 0) {
|
|
|
|
delay_ms(1000);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** Delay N ms */
|
|
|
|
void delay_ms(ms_time_t ms)
|
|
|
|
{
|
|
|
|
ms_time_t start = SystemTime_ms;
|
|
|
|
while ((SystemTime_ms - start) < ms); // overrun solved by unsigned arithmetic
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** Get milliseconds elapsed since start timestamp */
|
|
|
|
ms_time_t ms_elapsed(ms_time_t start)
|
|
|
|
{
|
|
|
|
return SystemTime_ms - start;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** Get current timestamp. */
|
|
|
|
ms_time_t ms_now(void)
|
|
|
|
{
|
|
|
|
return SystemTime_ms;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** Helper for looping with periodic branches */
|
|
|
|
bool ms_loop_elapsed(ms_time_t *start, ms_time_t duration)
|
|
|
|
{
|
|
|
|
if (SystemTime_ms - *start >= duration) {
|
|
|
|
*start = SystemTime_ms;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|