459 lines
12 KiB
C
459 lines
12 KiB
C
/**
|
|
* @file lv_task.c
|
|
* An 'lv_task' is a void (*fp) (struct _lv_task_t* param) type function which will be called periodically.
|
|
* A priority (5 levels + disable) can be assigned to lv_tasks.
|
|
*/
|
|
|
|
/*********************
|
|
* INCLUDES
|
|
*********************/
|
|
#include <stddef.h>
|
|
#include "lv_task.h"
|
|
#include "../lv_misc/lv_debug.h"
|
|
#include "../lv_hal/lv_hal_tick.h"
|
|
#include "lv_gc.h"
|
|
|
|
/*********************
|
|
* DEFINES
|
|
*********************/
|
|
#define IDLE_MEAS_PERIOD 500 /*[ms]*/
|
|
#define DEF_PRIO LV_TASK_PRIO_MID
|
|
#define DEF_PERIOD 500
|
|
|
|
/**********************
|
|
* TYPEDEFS
|
|
**********************/
|
|
|
|
/**********************
|
|
* STATIC PROTOTYPES
|
|
**********************/
|
|
static bool lv_task_exec(lv_task_t *task);
|
|
static uint32_t lv_task_time_remaining(lv_task_t *task);
|
|
|
|
/**********************
|
|
* STATIC VARIABLES
|
|
**********************/
|
|
static bool lv_task_run = false;
|
|
static uint8_t idle_last = 0;
|
|
static bool task_deleted;
|
|
static bool task_list_changed;
|
|
static bool task_created;
|
|
|
|
/**********************
|
|
* MACROS
|
|
**********************/
|
|
|
|
/**********************
|
|
* GLOBAL FUNCTIONS
|
|
**********************/
|
|
|
|
/**
|
|
* Init the lv_task module
|
|
*/
|
|
void _lv_task_core_init(void)
|
|
{
|
|
_lv_ll_init(&LV_GC_ROOT(_lv_task_ll), sizeof(lv_task_t));
|
|
|
|
/*Initially enable the lv_task handling*/
|
|
lv_task_enable(true);
|
|
}
|
|
|
|
/**
|
|
* Call it periodically to handle lv_tasks.
|
|
* @return the time after which it must be called again
|
|
*/
|
|
LV_ATTRIBUTE_TASK_HANDLER uint32_t lv_task_handler(void)
|
|
{
|
|
LV_LOG_TRACE("lv_task_handler started");
|
|
|
|
/*Avoid concurrent running of the task handler*/
|
|
static bool already_running = false;
|
|
|
|
if (already_running) {
|
|
return 1;
|
|
}
|
|
|
|
already_running = true;
|
|
|
|
if (lv_task_run == false) {
|
|
already_running = false; /*Release mutex*/
|
|
return 1;
|
|
}
|
|
|
|
static uint32_t idle_period_start = 0;
|
|
static uint32_t busy_time = 0;
|
|
|
|
uint32_t handler_start = lv_tick_get();
|
|
|
|
/* Run all task from the highest to the lowest priority
|
|
* If a lower priority task is executed check task again from the highest priority
|
|
* but on the priority of executed tasks don't run tasks before the executed*/
|
|
lv_task_t *task_interrupter = NULL;
|
|
lv_task_t *next;
|
|
bool end_flag;
|
|
|
|
do {
|
|
end_flag = true;
|
|
task_deleted = false;
|
|
task_created = false;
|
|
task_list_changed = false;
|
|
LV_GC_ROOT(_lv_task_act) = _lv_ll_get_head(&LV_GC_ROOT(_lv_task_ll));
|
|
|
|
while (LV_GC_ROOT(_lv_task_act)) {
|
|
/* The task might be deleted if it runs only once ('once = 1')
|
|
* So get next element until the current is surely valid*/
|
|
next = _lv_ll_get_next(&LV_GC_ROOT(_lv_task_ll), LV_GC_ROOT(_lv_task_act));
|
|
|
|
/*We reach priority of the turned off task. There is nothing more to do.*/
|
|
if (LV_GC_ROOT(_lv_task_act)->prio == LV_TASK_PRIO_OFF) {
|
|
break;
|
|
}
|
|
|
|
/*Here is the interrupter task. Don't execute it again.*/
|
|
if (LV_GC_ROOT(_lv_task_act) == task_interrupter) {
|
|
task_interrupter = NULL; /*From this point only task after the interrupter comes, so
|
|
the interrupter is not interesting anymore*/
|
|
LV_GC_ROOT(_lv_task_act) = next;
|
|
continue; /*Load the next task*/
|
|
}
|
|
|
|
/*Just try to run the tasks with highest priority.*/
|
|
if (LV_GC_ROOT(_lv_task_act)->prio == LV_TASK_PRIO_HIGHEST) {
|
|
lv_task_exec(LV_GC_ROOT(_lv_task_act));
|
|
}
|
|
/*Tasks with higher priority than the interrupted shall be run in every case*/
|
|
else if (task_interrupter) {
|
|
if (LV_GC_ROOT(_lv_task_act)->prio > task_interrupter->prio) {
|
|
if (lv_task_exec(LV_GC_ROOT(_lv_task_act))) {
|
|
if (!task_created && !task_deleted) {
|
|
/*Check all tasks again from the highest priority */
|
|
task_interrupter = LV_GC_ROOT(_lv_task_act);
|
|
end_flag = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
/* It is no interrupter task or we already reached it earlier.
|
|
* Just run the remaining tasks*/
|
|
else {
|
|
if (lv_task_exec(LV_GC_ROOT(_lv_task_act))) {
|
|
if (!task_created && !task_deleted) {
|
|
task_interrupter = LV_GC_ROOT(_lv_task_act); /*Check all tasks again from the highest priority */
|
|
end_flag = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*If a task was created or deleted then this or the next item might be corrupted*/
|
|
if (task_created || task_deleted) {
|
|
task_interrupter = NULL;
|
|
break;
|
|
}
|
|
|
|
if (task_list_changed) {
|
|
task_interrupter = NULL;
|
|
end_flag = false;
|
|
break;
|
|
}
|
|
|
|
LV_GC_ROOT(_lv_task_act) = next; /*Load the next task*/
|
|
}
|
|
} while (!end_flag);
|
|
|
|
uint32_t time_till_next = LV_NO_TASK_READY;
|
|
next = _lv_ll_get_head(&LV_GC_ROOT(_lv_task_ll));
|
|
|
|
while (next && next->prio != LV_TASK_PRIO_OFF) {
|
|
uint32_t delay = lv_task_time_remaining(next);
|
|
|
|
if (delay < time_till_next) {
|
|
time_till_next = delay;
|
|
}
|
|
|
|
next = _lv_ll_get_next(&LV_GC_ROOT(_lv_task_ll), next); /*Find the next task*/
|
|
}
|
|
|
|
busy_time += lv_tick_elaps(handler_start);
|
|
uint32_t idle_period_time = lv_tick_elaps(idle_period_start);
|
|
|
|
if (idle_period_time >= IDLE_MEAS_PERIOD) {
|
|
idle_last = (busy_time * 100) / idle_period_time; /*Calculate the busy percentage*/
|
|
idle_last = idle_last > 100 ? 0 : 100 - idle_last; /*But we need idle time*/
|
|
busy_time = 0;
|
|
idle_period_start = lv_tick_get();
|
|
}
|
|
|
|
already_running = false; /*Release the mutex*/
|
|
|
|
LV_LOG_TRACE("lv_task_handler ready");
|
|
return time_till_next;
|
|
}
|
|
/**
|
|
* Create an "empty" task. It needs to initialized with at least
|
|
* `lv_task_set_cb` and `lv_task_set_period`
|
|
* @return pointer to the created task
|
|
*/
|
|
lv_task_t *lv_task_create_basic(void)
|
|
{
|
|
return lv_task_create(NULL, DEF_PERIOD, DEF_PRIO, NULL);
|
|
}
|
|
|
|
/**
|
|
* Create a new lv_task
|
|
* @param task_xcb a callback which is the task itself. It will be called periodically.
|
|
* (the 'x' in the argument name indicates that its not a fully generic function because it not follows
|
|
* the `func_name(object, callback, ...)` convention)
|
|
* @param period call period in ms unit
|
|
* @param prio priority of the task (LV_TASK_PRIO_OFF means the task is stopped)
|
|
* @param user_data custom parameter
|
|
* @return pointer to the new task
|
|
*/
|
|
lv_task_t *lv_task_create(lv_task_cb_t task_xcb, uint32_t period, lv_task_prio_t prio, void *user_data)
|
|
{
|
|
lv_task_t *new_task = NULL;
|
|
lv_task_t *tmp;
|
|
|
|
/*Create task lists in order of priority from high to low*/
|
|
tmp = _lv_ll_get_head(&LV_GC_ROOT(_lv_task_ll));
|
|
|
|
/*It's the first task*/
|
|
if (NULL == tmp) {
|
|
new_task = _lv_ll_ins_head(&LV_GC_ROOT(_lv_task_ll));
|
|
LV_ASSERT_MEM(new_task);
|
|
|
|
if (new_task == NULL) {
|
|
return NULL;
|
|
}
|
|
}
|
|
/*Insert the new task to proper place according to its priority*/
|
|
else {
|
|
do {
|
|
if (tmp->prio <= prio) {
|
|
new_task = _lv_ll_ins_prev(&LV_GC_ROOT(_lv_task_ll), tmp);
|
|
LV_ASSERT_MEM(new_task);
|
|
|
|
if (new_task == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
tmp = _lv_ll_get_next(&LV_GC_ROOT(_lv_task_ll), tmp);
|
|
} while (tmp != NULL);
|
|
|
|
/*Only too high priority tasks were found. Add the task to the end*/
|
|
if (tmp == NULL) {
|
|
new_task = _lv_ll_ins_tail(&LV_GC_ROOT(_lv_task_ll));
|
|
LV_ASSERT_MEM(new_task);
|
|
|
|
if (new_task == NULL) {
|
|
return NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
task_list_changed = true;
|
|
|
|
new_task->period = period;
|
|
new_task->task_cb = task_xcb;
|
|
new_task->prio = prio;
|
|
|
|
new_task->repeat_count = -1;
|
|
new_task->last_run = lv_tick_get();
|
|
|
|
new_task->user_data = user_data;
|
|
|
|
task_created = true;
|
|
|
|
return new_task;
|
|
}
|
|
|
|
/**
|
|
* Set the callback the task (the function to call periodically)
|
|
* @param task pointer to a task
|
|
* @param task_cb the function to call periodically
|
|
*/
|
|
void lv_task_set_cb(lv_task_t *task, lv_task_cb_t task_cb)
|
|
{
|
|
task->task_cb = task_cb;
|
|
}
|
|
|
|
/**
|
|
* Delete a lv_task
|
|
* @param task pointer to task created by task
|
|
*/
|
|
void lv_task_del(lv_task_t *task)
|
|
{
|
|
_lv_ll_remove(&LV_GC_ROOT(_lv_task_ll), task);
|
|
task_list_changed = true;
|
|
|
|
lv_mem_free(task);
|
|
|
|
if (LV_GC_ROOT(_lv_task_act) == task) {
|
|
task_deleted = true; /*The active task was deleted*/
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Set new priority for a lv_task
|
|
* @param task pointer to a lv_task
|
|
* @param prio the new priority
|
|
*/
|
|
void lv_task_set_prio(lv_task_t *task, lv_task_prio_t prio)
|
|
{
|
|
if (task->prio == prio) {
|
|
return;
|
|
}
|
|
|
|
/*Find the tasks with new priority*/
|
|
lv_task_t *i;
|
|
_LV_LL_READ(LV_GC_ROOT(_lv_task_ll), i)
|
|
{
|
|
if (i->prio <= prio) {
|
|
if (i != task) {
|
|
_lv_ll_move_before(&LV_GC_ROOT(_lv_task_ll), task, i);
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*There was no such a low priority so far then add the node to the tail*/
|
|
if (i == NULL) {
|
|
_lv_ll_move_before(&LV_GC_ROOT(_lv_task_ll), task, NULL);
|
|
}
|
|
|
|
task_list_changed = true;
|
|
|
|
task->prio = prio;
|
|
}
|
|
|
|
/**
|
|
* Set new period for a lv_task
|
|
* @param task pointer to a lv_task
|
|
* @param period the new period
|
|
*/
|
|
void lv_task_set_period(lv_task_t *task, uint32_t period)
|
|
{
|
|
task->period = period;
|
|
}
|
|
|
|
/**
|
|
* Make a lv_task ready. It will not wait its period.
|
|
* @param task pointer to a lv_task.
|
|
*/
|
|
void lv_task_ready(lv_task_t *task)
|
|
{
|
|
task->last_run = lv_tick_get() - task->period - 1;
|
|
}
|
|
|
|
/**
|
|
* Set the number of times a task will repeat.
|
|
* @param task pointer to a lv_task.
|
|
* @param repeat_count -1 : infinity; 0 : stop ; n>0: residual times
|
|
*/
|
|
void lv_task_set_repeat_count(lv_task_t *task, int32_t repeat_count)
|
|
{
|
|
task->repeat_count = repeat_count;
|
|
}
|
|
|
|
/**
|
|
* Reset a lv_task.
|
|
* It will be called the previously set period milliseconds later.
|
|
* @param task pointer to a lv_task.
|
|
*/
|
|
void lv_task_reset(lv_task_t *task)
|
|
{
|
|
task->last_run = lv_tick_get();
|
|
}
|
|
|
|
/**
|
|
* Enable or disable the whole lv_task handling
|
|
* @param en: true: lv_task handling is running, false: lv_task handling is suspended
|
|
*/
|
|
void lv_task_enable(bool en)
|
|
{
|
|
lv_task_run = en;
|
|
}
|
|
|
|
/**
|
|
* Get idle percentage
|
|
* @return the lv_task idle in percentage
|
|
*/
|
|
uint8_t lv_task_get_idle(void)
|
|
{
|
|
return idle_last;
|
|
}
|
|
|
|
/**
|
|
* Iterate through the tasks
|
|
* @param task NULL to start iteration or the previous return value to get the next task
|
|
* @return the next task or NULL if there is no more task
|
|
*/
|
|
lv_task_t *lv_task_get_next(lv_task_t *task)
|
|
{
|
|
if (task == NULL) {
|
|
return _lv_ll_get_head(&LV_GC_ROOT(_lv_task_ll));
|
|
} else {
|
|
return _lv_ll_get_next(&LV_GC_ROOT(_lv_task_ll), task);
|
|
}
|
|
}
|
|
|
|
/**********************
|
|
* STATIC FUNCTIONS
|
|
**********************/
|
|
|
|
/**
|
|
* Execute task if its the priority is appropriate
|
|
* @param task pointer to lv_task
|
|
* @return true: execute, false: not executed
|
|
*/
|
|
static bool lv_task_exec(lv_task_t *task)
|
|
{
|
|
bool exec = false;
|
|
|
|
if (lv_task_time_remaining(task) == 0) {
|
|
task->last_run = lv_tick_get();
|
|
|
|
if (task->task_cb) {
|
|
task->task_cb(task);
|
|
}
|
|
|
|
/*Delete if it was a one shot lv_task*/
|
|
if (task_deleted == false) /*The task might be deleted by itself as well*/
|
|
{
|
|
if (task->repeat_count > 0) {
|
|
task->repeat_count--;
|
|
}
|
|
|
|
if (task->repeat_count == 0) {
|
|
lv_task_del(task);
|
|
}
|
|
}
|
|
|
|
exec = true;
|
|
}
|
|
|
|
return exec;
|
|
}
|
|
|
|
/**
|
|
* Find out how much time remains before a task must be run.
|
|
* @param task pointer to lv_task
|
|
* @return the time remaining, or 0 if it needs to be run again
|
|
*/
|
|
static uint32_t lv_task_time_remaining(lv_task_t *task)
|
|
{
|
|
/*Check if at least 'period' time elapsed*/
|
|
uint32_t elp = lv_tick_elaps(task->last_run);
|
|
|
|
if (elp >= task->period) {
|
|
return 0;
|
|
}
|
|
|
|
return task->period - elp;
|
|
}
|