/** * @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 #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; }