/** @file task.h * Module handling the task creation and management * * The module provides an API to create, run and manage lightweight, stack-less * threads (tasks). This system is based on protothreads, * see https://dunkels.com/adam/pt/index.html * * Task can be viewed as a lightweight, somewhat restricted, cooperative OS. * Tasks are runned on every events. Since the RTC is enabled by the scheduler, * every task should be run at least once per second. If at least one task is * currently paused, the systick is enabled and all tasks are updated every * millisecond * * State machine should mainly yield (see TASK_YIELD) while time sensitive * applications should pause (see TASK_PAUSE) instead. This configuration allows * state machines to be updated every time something changes in the system while * allowing the cpu to enter low power mode when possible. When time sensitive * applications are paused, they cause the systick to start which stops the cpu * from entering low power but making sure that the task is polled quickly * enough. Tasks requiring longer delay and less sensitive to timings can sleep * (see TASK_SLEEP) instead, which relies on the RTC rather than the systick */ #ifndef _task_h_ #define _task_h_ //--includes-------------------------------------------------------------------- #include #include //--type definitions------------------------------------------------------------ /** * Available triggers for a task */ enum TaskTrigger { TASK_TRIGGER_ANY, TASK_TRIGGER_STK, TASK_TRIGGER_RTC, TASK_TRIGGER_BOTH, }; /** * State of a task at any given time. Every single task is described by such a * struct */ struct TaskState { uint32_t timestamp; //timestamp at wich to wakeup the task, if any uint8_t count:5; //task counter: active step of task enum TaskTrigger trigger:2; //triggers on wich to execute the task uint8_t timeout_mode:1; //whether the timestamp is a timeout or a delay }; /** * Function prototype of tasks */ typedef void(*TaskFunction)(struct TaskState*); /** * Full definition of a task. Contains the function supporting the task as well * as the state of said task */ struct Task { TaskFunction function; struct TaskState state; }; //--functions------------------------------------------------------------------- /** * Task declaration macro, to be used to declare and define a task instead of a * regular function declaration/defintion */ #define TASK(fct_name) void fct_name(struct TaskState* restrict __task_state) /** * Task entry macro, must be present at the begin of every task. Setup code to * be run indepently of the task state may be put before that (static variables, * init code, ...) */ #define TASK_ENTRY \ _TASK_COUNT_INIT; \ switch (__task_state->count) { /** * Task cleanup macro. Option, can be use right before TASK_EXIT. This step * will be executed before exiting the task when task_stop() is called. As the * name suggest, this is mainly usefull to implement cleanup code and allow for * gracefull shutdowns */ #define TASK_CLEANUP \ case (_TASK_COUNT_CLEANUP): \ /* fall through */ /** * Tasks exit macro, must be present at the end of every task. Any code written * after that will never be executed */ #define TASK_EXIT \ } \ __task_state->count = _TASK_COUNT_EXIT & 0x1F; \ return; /** * Returns whether the task was timed-out or not. This macro can be used after * TASK_PAUSE_UNTIL and TASK_SLEEP_UNTIL to know if the task resumed because of * the condition or because of the timeout. Does not correspond to anything * when called after any other step */ #define TASK_TIMEOUT (__task_state->timeout == 0) /** * Give up the cpu, allowing the other tasks to run. The task will resume at the * next event. Between events, the cpu can enter various power saving states * depending on the other tasks running */ #define TASK_YIELD() _TASK_YIELD(_TASK_COUNT_INCR) /** * Suspend the task for the specified amount of milliseconds. The systick will * remain active while the task is suspended to provide milliseconds counting, * limiting the ability of the cpu to save power */ #define TASK_PAUSE(delay_ms) _TASK_PAUSE(delay_ms, _TASK_COUNT_INCR) /** * Suspend the task for the specified amount of seconds. The RTC will be used to * provide seconds counting. If no other tasks requires it, the systick is * disabled to save more power */ #define TASK_SLEEP(delay_s) _TASK_SLEEP(delay_s, _TASK_COUNT_INCR) /** * Execute TASK_YIELD until the provided condition is reached. The condition * will be checked on every event */ #define TASK_YIELD_UNTIL(cond) _TASK_YIELD_UNTIL(cond, _TASK_COUNT_INCR) /** * Execute TASK_PAUSE until either the condition or the delay is reached. * TASK_TIMEOUT can be used to know what cause the task to resume. The condition * will be checked every millisecond */ #define TASK_PAUSE_UNTIL(cond, delay_ms) \ _TASK_PAUSE_UNTIL(cond, delay_ms, _TASK_COUNT_INCR) /** * Execute TASK_SLEEP until either the condition or the delay is reached. * TASK_TIMEOUT can be used to know what cause the task to resume. The condition * will be checked every seconds */ #define TASK_SLEEP_UNTIL(cond, delay_s) \ _TASK_SLEEP_UNTIL(cond, delay_s, _TASK_COUNT_INCR) /** * Execute the specified task, suspending the current one until the task exits */ #define TASK_EXECUTE(task) _TASK_EXECUTE(task, _TASK_COUNT_INCR) /** * Starts the task scheduler and run it until the system is shutdown. This * function never returns. * * All tasks started using task_start() are run according to their current * state (see TASK_* macros). Since this system is cooperative, the scheduler * does not preempt the tasks when running. The RTC is automatically configured * and started to provide events every seconds. The systick is automatically * started and stop to provide events every milliseconds when needed */ void task_start_scheduler(void); /** * Returns the current system time. The epoc is undefined. The time is provided * in milliseconds, though the millisecond precision is only available while the * systick is running (at least one task paused) */ uint32_t task_current_time(void); /** * Starts the specified task. The task will only trully be runned after * task_start_scheduler() is called. * A task already started will be left as-is */ void task_start(TaskFunction task); /** * Stops the specified task. The task's cleanup state will be executed before * it exits */ void task_stop(TaskFunction task); /** * Returns whether the specified task is currently running or not */ bool task_is_running(TaskFunction task); //--internal_functions---------------------------------------------------------- #define _TASK_COUNT_INIT enum { TASK_COUNTER_BASE = __COUNTER__ } #define _TASK_COUNT_INCR (uint8_t)(__COUNTER__ - TASK_COUNTER_BASE - 1) #define _TASK_COUNT_EXIT (UINT8_MAX & 0x3F) #define _TASK_COUNT_CLEANUP (UINT8_MAX - 1) #define _TASK_YIELD(count_val) do { \ __task_state->count = count_val; \ __task_state->trigger = TASK_TRIGGER_ANY; \ return; \ case (count_val): \ /* fall through */ \ } while (0) #define _TASK_PAUSE(delay_ms, count_val) do { \ __task_state->count = count_val; \ __task_state->timestamp += delay_ms; \ __task_state->trigger = TASK_TRIGGER_STK; \ return; \ case (count_val): \ /* fall through */ \ } while (0) #define _TASK_SLEEP(delay_s, count_val) do { \ __task_state->count = count_val; \ __task_state->timestamp += delay_s; \ __task_state->trigger = TASK_TRIGGER_RTC; \ return; \ case (count_val): \ /* fall through */ \ } while (0) #define _TASK_YIELD_UNTIL(cond, count_val) do { \ __task_state->count = count_val; \ __task_state->trigger = TASK_TRIGGER_ANY; \ case (count_val): \ if (!(cond)) { \ return; \ } \ /* fall through */ \ } while (0) #define _TASK_PAUSE_UNTIL(cond, delay_ms, count_val) do { \ __task_state->count = count_val; \ __task_state->timestamp += delay_ms; \ __task_state->trigger = TASK_TRIGGER_STK; \ __task_state->timeout_mode = true; \ case (count_val): \ if (!(cond) && __task_state->timestamp != 0) { \ return; \ } else { \ __task_state->timeout_mode = false; \ __task_state->timestamp = 0; \ } \ /* fall through */ \ } while (0) #define _TASK_SLEEP_UNTIL(cond, delay_s, count_val) do { \ __task_state->count = count_val; \ __task_state->timestamp += delay_s; \ __task_state->trigger = TASK_TRIGGER_RTC; \ __task_state->timeout_mode = true; \ case (count_val): \ if (!(cond) && __task_state->timestamp != 0) { \ return; \ } else { \ __task_state->timeout_mode = false; \ } \ /* fall through */ \ } while (0) #define _TASK_EXECUTE(task, count_val) do { \ __task_state->count = count_val; \ __task_state->trigger = TASK_TRIGGER_ANY; \ task_start(Task task); \ return; \ case (count_val): \ if (task_is_running(task)) { \ return; \ } \ /* fall through */ \ } while (0) #endif //_task_h_