Compare commits

...

3 Commits

Author SHA1 Message Date
d5c70a3a04 Document the task module 2024-07-10 21:54:23 +02:00
93b383be49 Add API to access task's system time 2024-07-09 22:07:35 +02:00
dd1756221d Fix typo in task macros 2024-07-09 21:46:15 +02:00
2 changed files with 147 additions and 9 deletions

View File

@ -1,9 +1,24 @@
/** @file task.c /** @file task.h
* Module handling the task creation and management * Module handling the task creation and management
* *
* The module provides an API to create, run and manage lightweight, stack-less * The module provides an API to create, run and manage lightweight, stack-less
* threads (tasks). This system is based on protothreads, * threads (tasks). This system is based on protothreads,
* see https://dunkels.com/adam/pt/index.html * 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
*/ */
//--includes-------------------------------------------------------------------- //--includes--------------------------------------------------------------------
@ -58,6 +73,11 @@ void task_start_scheduler(void)
} }
} }
uint32_t task_current_time(void)
{
return timestamp;
}
void task_start(TaskFunction function) void task_start(TaskFunction function)
{ {
for (uint8_t i = 0; i < MAX_TASK_NB; ++i) { for (uint8_t i = 0; i < MAX_TASK_NB; ++i) {
@ -101,7 +121,6 @@ bool task_is_running(TaskFunction function)
return false; return false;
} }
//--local functions------------------------------------------------------------- //--local functions-------------------------------------------------------------
static bool execute_task(struct Task* restrict task, uint8_t triggers) static bool execute_task(struct Task* restrict task, uint8_t triggers)

View File

@ -4,6 +4,21 @@
* The module provides an API to create, run and manage lightweight, stack-less * The module provides an API to create, run and manage lightweight, stack-less
* threads (tasks). This system is based on protothreads, * threads (tasks). This system is based on protothreads,
* see https://dunkels.com/adam/pt/index.html * 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_ #ifndef _task_h_
@ -17,6 +32,9 @@
//--type definitions------------------------------------------------------------ //--type definitions------------------------------------------------------------
/**
* Available triggers for a task
*/
enum TaskTrigger { enum TaskTrigger {
TASK_TRIGGER_ANY, TASK_TRIGGER_ANY,
TASK_TRIGGER_STK, TASK_TRIGGER_STK,
@ -24,15 +42,26 @@ enum TaskTrigger {
TASK_TRIGGER_BOTH, TASK_TRIGGER_BOTH,
}; };
/**
* State of a task at any given time. Every single task is described by such a
* struct
*/
struct TaskState { struct TaskState {
uint32_t timestamp; uint32_t timestamp; //timestamp at wich to wakeup the task, if any
uint8_t count:5; uint8_t count:5; //task counter: active step of task
enum TaskTrigger trigger:2; enum TaskTrigger trigger:2; //triggers on wich to execute the task
uint8_t timeout_mode:1; uint8_t timeout_mode:1; //whether the timestamp is a timeout or a delay
}; };
/**
* Function prototype of tasks
*/
typedef void(*TaskFunction)(struct TaskState*, uint32_t); typedef void(*TaskFunction)(struct TaskState*, uint32_t);
/**
* Full definition of a task. Contains the function supporting the task as well
* as the state of said task
*/
struct Task { struct Task {
TaskFunction function; TaskFunction function;
struct TaskState state; struct TaskState state;
@ -41,41 +70,133 @@ struct Task {
//--functions------------------------------------------------------------------- //--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, \ #define TASK(fct_name) void fct_name(struct TaskState* restrict __task_state, \
uint32_t __task_time) uint32_t __task_time)
/**
* 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 \ #define TASK_ENTRY \
_TASK_COUNT_INIT; \ _TASK_COUNT_INIT; \
(void*) __task_time; \ (void*) __task_time; \
switch (__task_state->count) { 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 \ #define TASK_CLEANUP \
case (_TASK_COUNT_CLEANUP): \ case (_TASK_COUNT_CLEANUP): \
/* fall through */ /* 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 \ #define TASK_EXIT \
} \ } \
__task_state->count = _TASK_COUNT_EXIT & 0x1F; \ __task_state->count = _TASK_COUNT_EXIT & 0x1F; \
return; 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) #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) #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) #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) #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) #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) \ #define TASK_PAUSE_UNTIL(cond, delay_ms) \
_TASK_PAUSE_UNTIL(cond, delay_ms, _TASK_COUNT_INCR) _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) \ #define TASK_SLEEP_UNTIL(cond, delay_s) \
_TASK_SLEEP_UNTIL(cond, delay_s, _TASK_COUNT_INCR) _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) #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); void task_start_scheduler(void);
void task_declare(); /**
* 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); 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); void task_stop(TaskFunction task);
/**
* Returns whether the specified task is currently running or not
*/
bool task_is_running(TaskFunction task); bool task_is_running(TaskFunction task);
@ -127,7 +248,6 @@ bool task_is_running(TaskFunction task);
__task_state->timestamp = __task_time + delay_ms; \ __task_state->timestamp = __task_time + delay_ms; \
__task_state->trigger = TASK_TRIGGER_STK; \ __task_state->trigger = TASK_TRIGGER_STK; \
__task_state->timeout_mode = true; \ __task_state->timeout_mode = true; \
} \
case (count_val): \ case (count_val): \
if (!(cond) && __task_state->timestamp != 0) { \ if (!(cond) && __task_state->timestamp != 0) { \
return; \ return; \
@ -142,7 +262,6 @@ bool task_is_running(TaskFunction task);
__task_state->timestamp = __taks_time + delay_s * 1000; \ __task_state->timestamp = __taks_time + delay_s * 1000; \
__task_state->trigger = TASK_TRIGGER_RTC; \ __task_state->trigger = TASK_TRIGGER_RTC; \
__task_state->timeout_mode = true; \ __task_state->timeout_mode = true; \
} \
case (count_val): \ case (count_val): \
if (!(cond) && __task_state->timestamp != 0) { \ if (!(cond) && __task_state->timestamp != 0) { \
return; \ return; \