1 /**
2  * @file lv_task.c
3  * An 'lv_task'  is a void (*fp) (void* param) type function which will be called periodically.
4  * A priority (5 levels + disable) can be assigned to lv_tasks.
5  */
6 
7 /*********************
8  *      INCLUDES
9  *********************/
10 #include <stddef.h>
11 #include "lv_task.h"
12 #include "../lv_misc/lv_debug.h"
13 #include "../lv_hal/lv_hal_tick.h"
14 #include "lv_gc.h"
15 
16 #if defined(LV_GC_INCLUDE)
17     #include LV_GC_INCLUDE
18 #endif /* LV_ENABLE_GC */
19 
20 /*********************
21  *      DEFINES
22  *********************/
23 #define IDLE_MEAS_PERIOD 500 /*[ms]*/
24 #define DEF_PRIO LV_TASK_PRIO_MID
25 #define DEF_PERIOD 500
26 
27 /**********************
28  *      TYPEDEFS
29  **********************/
30 
31 /**********************
32  *  STATIC PROTOTYPES
33  **********************/
34 static bool lv_task_exec(lv_task_t * task);
35 static uint32_t lv_task_time_remaining(lv_task_t * task);
36 
37 /**********************
38  *  STATIC VARIABLES
39  **********************/
40 static bool lv_task_run  = false;
41 static uint8_t idle_last = 0;
42 static bool task_deleted;
43 static bool task_list_changed;
44 static bool task_created;
45 
46 /**********************
47  *      MACROS
48  **********************/
49 
50 /**********************
51  *   GLOBAL FUNCTIONS
52  **********************/
53 
54 /**
55  * Init the lv_task module
56  */
_lv_task_core_init(void)57 void _lv_task_core_init(void)
58 {
59     _lv_ll_init(&LV_GC_ROOT(_lv_task_ll), sizeof(lv_task_t));
60 
61     task_list_changed = false;
62     /*Initially enable the lv_task handling*/
63     lv_task_enable(true);
64 }
65 
66 /**
67  * Call it  periodically to handle lv_tasks.
68  * @return the time after which it must be called again
69  */
lv_task_handler(void)70 LV_ATTRIBUTE_TASK_HANDLER uint32_t lv_task_handler(void)
71 {
72 
73 
74     LV_LOG_TRACE("lv_task_handler started");
75 
76     /*Avoid concurrent running of the task handler*/
77     static bool already_running = false;
78     if(already_running) return 1;
79     already_running = true;
80 
81     static uint32_t idle_period_start = 0;
82     static uint32_t handler_start     = 0;
83     static uint32_t busy_time         = 0;
84     static uint32_t time_till_next;
85 
86     if(lv_task_run == false) {
87         already_running = false; /*Release mutex*/
88         return 1;
89     }
90 
91     handler_start = lv_tick_get();
92 
93     /* Run all task from the highest to the lowest priority
94      * If a lower priority task is executed check task again from the highest priority
95      * but on the priority of executed tasks don't run tasks before the executed*/
96     lv_task_t * task_interrupter = NULL;
97     lv_task_t * next;
98     bool end_flag;
99     do {
100         end_flag                 = true;
101         task_deleted             = false;
102         task_created             = false;
103         LV_GC_ROOT(_lv_task_act) = _lv_ll_get_head(&LV_GC_ROOT(_lv_task_ll));
104         while(LV_GC_ROOT(_lv_task_act)) {
105             /* The task might be deleted if it runs only once ('once = 1')
106              * So get next element until the current is surely valid*/
107             next = _lv_ll_get_next(&LV_GC_ROOT(_lv_task_ll), LV_GC_ROOT(_lv_task_act));
108 
109             /*We reach priority of the turned off task. There is nothing more to do.*/
110             if(((lv_task_t *)LV_GC_ROOT(_lv_task_act))->prio == LV_TASK_PRIO_OFF) {
111                 break;
112             }
113 
114             /*Here is the interrupter task. Don't execute it again.*/
115             if(LV_GC_ROOT(_lv_task_act) == task_interrupter) {
116                 task_interrupter = NULL; /*From this point only task after the interrupter comes, so
117                                             the interrupter is not interesting anymore*/
118                 LV_GC_ROOT(_lv_task_act) = next;
119                 continue; /*Load the next task*/
120             }
121 
122             /*Just try to run the tasks with highest priority.*/
123             if(((lv_task_t *)LV_GC_ROOT(_lv_task_act))->prio == LV_TASK_PRIO_HIGHEST) {
124                 lv_task_exec(LV_GC_ROOT(_lv_task_act));
125             }
126             /*Tasks with higher priority than the interrupted shall be run in every case*/
127             else if(task_interrupter) {
128                 if(((lv_task_t *)LV_GC_ROOT(_lv_task_act))->prio > task_interrupter->prio) {
129                     if(lv_task_exec(LV_GC_ROOT(_lv_task_act))) {
130                         if(!task_created && !task_deleted) {
131                             /*Check all tasks again from the highest priority */
132                             task_interrupter = LV_GC_ROOT(_lv_task_act);
133                             end_flag = false;
134                             break;
135                         }
136                     }
137                 }
138             }
139             /* It is no interrupter task or we already reached it earlier.
140              * Just run the remaining tasks*/
141             else {
142                 if(lv_task_exec(LV_GC_ROOT(_lv_task_act))) {
143                     if(!task_created && !task_deleted) {
144                         task_interrupter = LV_GC_ROOT(_lv_task_act); /*Check all tasks again from the highest priority */
145                         end_flag         = false;
146                         break;
147                     }
148                 }
149             }
150 
151             /*If a task was created or deleted then this or the next item might be corrupted*/
152             if(task_created || task_deleted) {
153                 task_interrupter = NULL;
154                 break;
155             }
156 
157             if(task_list_changed) {
158                 task_interrupter = NULL;
159                 end_flag = false;
160                 task_list_changed = false;
161                 break;
162             }
163 
164             LV_GC_ROOT(_lv_task_act) = next; /*Load the next task*/
165         }
166     } while(!end_flag);
167 
168     busy_time += lv_tick_elaps(handler_start);
169     uint32_t idle_period_time = lv_tick_elaps(idle_period_start);
170     if(idle_period_time >= IDLE_MEAS_PERIOD) {
171 
172         idle_last         = (uint32_t)((uint32_t)busy_time * 100) / IDLE_MEAS_PERIOD; /*Calculate the busy percentage*/
173         idle_last         = idle_last > 100 ? 0 : 100 - idle_last;                    /*But we need idle time*/
174         busy_time         = 0;
175         idle_period_start = lv_tick_get();
176     }
177 
178     time_till_next = LV_NO_TASK_READY;
179     next = _lv_ll_get_head(&LV_GC_ROOT(_lv_task_ll));
180     while(next) {
181         if(next->prio != LV_TASK_PRIO_OFF) {
182             uint32_t delay = lv_task_time_remaining(next);
183             if(delay < time_till_next)
184                 time_till_next = delay;
185         }
186 
187         next = _lv_ll_get_next(&LV_GC_ROOT(_lv_task_ll), next); /*Find the next task*/
188     }
189 
190     already_running = false; /*Release the mutex*/
191 
192     LV_LOG_TRACE("lv_task_handler ready");
193     return time_till_next;
194 }
195 /**
196  * Create an "empty" task. It needs to initialized with at least
197  * `lv_task_set_cb` and `lv_task_set_period`
198  * @return pointer to the created task
199  */
lv_task_create_basic(void)200 lv_task_t * lv_task_create_basic(void)
201 {
202     lv_task_t * new_task = NULL;
203     lv_task_t * tmp;
204 
205     /*Create task lists in order of priority from high to low*/
206     tmp = _lv_ll_get_head(&LV_GC_ROOT(_lv_task_ll));
207 
208     /*It's the first task*/
209     if(NULL == tmp) {
210         new_task = _lv_ll_ins_head(&LV_GC_ROOT(_lv_task_ll));
211         LV_ASSERT_MEM(new_task);
212         if(new_task == NULL) return NULL;
213     }
214     /*Insert the new task to proper place according to its priority*/
215     else {
216         do {
217             if(tmp->prio <= DEF_PRIO) {
218                 new_task = _lv_ll_ins_prev(&LV_GC_ROOT(_lv_task_ll), tmp);
219                 LV_ASSERT_MEM(new_task);
220                 if(new_task == NULL) return NULL;
221                 break;
222             }
223             tmp = _lv_ll_get_next(&LV_GC_ROOT(_lv_task_ll), tmp);
224         } while(tmp != NULL);
225 
226         /*Only too high priority tasks were found. Add the task to the end*/
227         if(tmp == NULL) {
228             new_task = _lv_ll_ins_tail(&LV_GC_ROOT(_lv_task_ll));
229             LV_ASSERT_MEM(new_task);
230             if(new_task == NULL) return NULL;
231         }
232     }
233     task_list_changed = true;
234 
235     new_task->period  = DEF_PERIOD;
236     new_task->task_cb = NULL;
237     new_task->prio    = DEF_PRIO;
238 
239     new_task->repeat_count = -1;
240     new_task->last_run = lv_tick_get();
241 
242     new_task->user_data = NULL;
243 
244     task_created = true;
245 
246     return new_task;
247 }
248 
249 /**
250  * Create a new lv_task
251  * @param task_xcb a callback which is the task itself. It will be called periodically.
252  *                 (the 'x' in the argument name indicates that its not a fully generic function because it not follows
253  *                  the `func_name(object, callback, ...)` convention)
254  * @param period call period in ms unit
255  * @param prio priority of the task (LV_TASK_PRIO_OFF means the task is stopped)
256  * @param user_data custom parameter
257  * @return pointer to the new task
258  */
lv_task_create(lv_task_cb_t task_xcb,uint32_t period,lv_task_prio_t prio,void * user_data)259 lv_task_t * lv_task_create(lv_task_cb_t task_xcb, uint32_t period, lv_task_prio_t prio, void * user_data)
260 {
261     lv_task_t * new_task = lv_task_create_basic();
262     LV_ASSERT_MEM(new_task);
263     if(new_task == NULL) return NULL;
264 
265     lv_task_set_cb(new_task, task_xcb);
266     lv_task_set_period(new_task, period);
267     lv_task_set_prio(new_task, prio);
268     new_task->user_data = user_data;
269 
270     return new_task;
271 }
272 
273 /**
274  * Set the callback the task (the function to call periodically)
275  * @param task pointer to a task
276  * @param task_cb the function to call periodically
277  */
lv_task_set_cb(lv_task_t * task,lv_task_cb_t task_cb)278 void lv_task_set_cb(lv_task_t * task, lv_task_cb_t task_cb)
279 {
280     task->task_cb = task_cb;
281 }
282 
283 /**
284  * Delete a lv_task
285  * @param task pointer to task created by task
286  */
lv_task_del(lv_task_t * task)287 void lv_task_del(lv_task_t * task)
288 {
289     _lv_ll_remove(&LV_GC_ROOT(_lv_task_ll), task);
290     task_list_changed = true;
291 
292     lv_mem_free(task);
293 
294     if(LV_GC_ROOT(_lv_task_act) == task) task_deleted = true; /*The active task was deleted*/
295 }
296 
297 /**
298  * Set new priority for a lv_task
299  * @param task pointer to a lv_task
300  * @param prio the new priority
301  */
lv_task_set_prio(lv_task_t * task,lv_task_prio_t prio)302 void lv_task_set_prio(lv_task_t * task, lv_task_prio_t prio)
303 {
304     if(task->prio == prio) return;
305 
306     /*Find the tasks with new priority*/
307     lv_task_t * i;
308     _LV_LL_READ(LV_GC_ROOT(_lv_task_ll), i) {
309         if(i->prio <= prio) {
310             if(i != task) _lv_ll_move_before(&LV_GC_ROOT(_lv_task_ll), task, i);
311             break;
312         }
313     }
314 
315     /*There was no such a low priority so far then add the node to the tail*/
316     if(i == NULL) {
317         _lv_ll_move_before(&LV_GC_ROOT(_lv_task_ll), task, NULL);
318     }
319     task_list_changed = true;
320 
321     task->prio = prio;
322 }
323 
324 /**
325  * Set new period for a lv_task
326  * @param task pointer to a lv_task
327  * @param period the new period
328  */
lv_task_set_period(lv_task_t * task,uint32_t period)329 void lv_task_set_period(lv_task_t * task, uint32_t period)
330 {
331     task->period = period;
332 }
333 
334 /**
335  * Make a lv_task ready. It will not wait its period.
336  * @param task pointer to a lv_task.
337  */
lv_task_ready(lv_task_t * task)338 void lv_task_ready(lv_task_t * task)
339 {
340     task->last_run = lv_tick_get() - task->period - 1;
341 }
342 
343 /**
344  * Set the number of times a task will repeat.
345  * @param task pointer to a lv_task.
346  * @param repeat_count -1 : infinity;  0 : stop ;  n>0: residual times
347  */
lv_task_set_repeat_count(lv_task_t * task,int32_t repeat_count)348 void lv_task_set_repeat_count(lv_task_t * task, int32_t repeat_count)
349 {
350     task->repeat_count = repeat_count;
351 }
352 
353 /**
354  * Reset a lv_task.
355  * It will be called the previously set period milliseconds later.
356  * @param task pointer to a lv_task.
357  */
lv_task_reset(lv_task_t * task)358 void lv_task_reset(lv_task_t * task)
359 {
360     task->last_run = lv_tick_get();
361 }
362 
363 /**
364  * Enable or disable the whole lv_task handling
365  * @param en: true: lv_task handling is running, false: lv_task handling is suspended
366  */
lv_task_enable(bool en)367 void lv_task_enable(bool en)
368 {
369     lv_task_run = en;
370 }
371 
372 /**
373  * Get idle percentage
374  * @return the lv_task idle in percentage
375  */
lv_task_get_idle(void)376 uint8_t lv_task_get_idle(void)
377 {
378     return idle_last;
379 }
380 
381 /**
382  * Iterate through the tasks
383  * @param task NULL to start iteration or the previous return value to get the next task
384  * @return the next task or NULL if there is no more task
385  */
lv_task_get_next(lv_task_t * task)386 lv_task_t * lv_task_get_next(lv_task_t * task)
387 {
388     if(task == NULL) return _lv_ll_get_head(&LV_GC_ROOT(_lv_task_ll));
389     else return _lv_ll_get_next(&LV_GC_ROOT(_lv_task_ll), task);
390 }
391 
392 /**********************
393  *   STATIC FUNCTIONS
394  **********************/
395 
396 /**
397  * Execute task if its the priority is appropriate
398  * @param task pointer to lv_task
399  * @return true: execute, false: not executed
400  */
lv_task_exec(lv_task_t * task)401 static bool lv_task_exec(lv_task_t * task)
402 {
403     bool exec = false;
404 
405     if(lv_task_time_remaining(task) == 0) {
406         task->last_run = lv_tick_get();
407         task_deleted   = false;
408         task_created   = false;
409         if(task->task_cb) task->task_cb(task);
410 
411         /*Delete if it was a one shot lv_task*/
412         if(task_deleted == false) { /*The task might be deleted by itself as well*/
413             if(task->repeat_count > 0) {
414                 task->repeat_count--;
415             }
416             if(task->repeat_count == 0) {
417                 lv_task_del(task);
418             }
419         }
420         exec = true;
421     }
422 
423     return exec;
424 }
425 
426 /**
427  * Find out how much time remains before a task must be run.
428  * @param task pointer to lv_task
429  * @return the time remaining, or 0 if it needs to be run again
430  */
lv_task_time_remaining(lv_task_t * task)431 static uint32_t lv_task_time_remaining(lv_task_t * task)
432 {
433     /*Check if at least 'period' time elapsed*/
434     uint32_t elp = lv_tick_elaps(task->last_run);
435     if(elp >= task->period)
436         return 0;
437     return task->period - elp;
438 }
439