stm32f1xx_HBL/srv/task.h
Steins7 b893a0283f Reduce task's context size
Since RTC ans STK timestamps are now different, they don't need to be as big and
be can safely reduce their size to 24 bits to save space
2024-11-03 22:53:19 +01:00

287 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:24; //timestamp at wich to wakeup the task, if any
uint32_t count:5; //task counter: active step of task
enum TaskTrigger trigger:2; //triggers on wich to execute the task
uint32_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_