simon says with pro mini, display, ws2812 and touch keys
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
atmega-simon/lib/timebase.c

297 lines
6.2 KiB

8 years ago
//
// Created by MightyPork on 2017/06/08.
//
#include "timebase.h"
// Time base
volatile ms_time_t time_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;
// Slots
static periodic_task_t periodic_tasks[TIMEBASE_PERIODIC_COUNT];
static future_task_t future_tasks[TIMEBASE_FUTURE_COUNT];
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 < TIMEBASE_PERIODIC_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;
}
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 < TIMEBASE_FUTURE_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;
}
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, bool enable)
{
if (pid == PID_NONE) return false;
for (size_t i = 0; i < TIMEBASE_PERIODIC_COUNT; i++) {
periodic_task_t *task = &periodic_tasks[i];
if (task->pid != pid) continue;
task->enabled = 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 < TIMEBASE_PERIODIC_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 < TIMEBASE_PERIODIC_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 < TIMEBASE_PERIODIC_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 < TIMEBASE_PERIODIC_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 < TIMEBASE_FUTURE_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
// FIXME re-implement queue
//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
// FIXME re-implement queue
//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
time_ms++;
// run periodic tasks
for (size_t i = 0; i < TIMEBASE_PERIODIC_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 < TIMEBASE_FUTURE_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;
}
}
}
/** Helper for looping with periodic branches */
bool ms_loop_elapsed(ms_time_t *start, ms_time_t duration)
{
if (time_ms - *start >= duration) {
*start = time_ms;
return true;
}
return false;
}