288 lines
12 KiB
C
288 lines
12 KiB
C
/** @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 <stdint.h>
|
|
#include <stdbool.h>
|
|
|
|
|
|
//--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*, uint32_t);
|
|
|
|
/**
|
|
* 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, \
|
|
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 \
|
|
_TASK_COUNT_INIT; \
|
|
(void) __task_time; \
|
|
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 = __task_time + 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 = __task_time + delay_s * 1000; \
|
|
__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 = __task_time + 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; \
|
|
} \
|
|
/* fall through */ \
|
|
} while (0)
|
|
|
|
#define _TASK_SLEEP_UNTIL(cond, delay_s, count_val) do { \
|
|
__task_state->count = count_val; \
|
|
__task_state->timestamp = __taks_time + delay_s * 1000; \
|
|
__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_
|
|
|