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