一、前言
1、简述
开发中由于任务比较简单,思考着使用 lvgl使用裸机也可以实现相关功能。实际开发中也遇到了一些问题,因此对 lvgl裸机开发方式进行了总结对比,以及优化思路分享。开发使用 lvgl v7.11.0
二、LVGL裸机开发
1、基础实现方式
(1)main循环中调用 lv_task_handler(), 定时器中调用 lv_tick_inc()
//1ms 1中断
void timeIRQ()
{
static uint32_t tick = 0;
if(++tick %5 == 0) lv_tick_inc(5);
}
void main(void)
{
uint32_t cnt = 0;
while(1)
{
btnBuzzer(); //按下按钮时响
lv_task_handler();
cnt++;
}
}
(2)main 循环中调用 lv_task_handler() 及 lv_tick_inc()
void main(void)
{
uint32_t cnt = 0;
while(1)
{
btnBuzzer(); //按下按钮时响
if(++cnt %5 == 0) lv_tick_inc(5);
lv_task_handler();
cnt++;
}
}
2、利弊对比
(1)main 循环中调用 lv_task_handler(), 定时器中调用 lv_tick_inc()
-
优势
-
lvgl能获得
准确时基
,使得lvgl 能最大程度利用cpu,更好地保证UI实时性
存在问题
-
1.由于lvgl 内部调度是通过
循环遍历链表
,并且在检测到有任务计时到达时继续 loop,导致lvgl 任务足够多时lvgl 独占 cpu
,在这期间 main中其他功能无法执行 -
2.例子
-
例子 备注 当进行拖拽操作时 频繁触发新事件,导致一直处于 lv_task_handler
(2)main 循环中调用 lv_task_handler() 及 lv_tick_inc()
-
优势
-
1、由于时基必须在main中更新,lv_task_handler 执行的任务量在进入时就已经确定,即便在拖拽事件中也能
保证其他任务得到执行
存在问题
-
1.由于时基根据循环次数计算,lvgl的定时任务无法做到准确,
无法满足高精度要求任务
-
2.由于问题1的存在,在进行触屏检测时
最多会增加1个触摸检测间隔
(间隔计时需要退出 handler后才开始),显示也类似,因此检测间隔应尽可能小,例如设为 1 -
3、实现方式改进
(1)main循环中调用 lv_task_handler(), 定时器中调用 lv_tick_inc()
-
前面提到,该种方法主要问题在于存在
lvgl 独占 cpu
情况,即一直在 lv_task_handler 的 loop 中。那么有没有办法打断 loop 施法? -
通过 lvgl task 源码学习,发现了华点:
lvgl 在任务链表没有任何需要执行
或者(task_created || task_deleted)
成立时跳出循环。而任务链表是从最高优先级到最低,对最高优先级会无条件执行
,且会进行(task_created || task_deleted)
条件检测。 -
也就是说,如果执行最高级任务后检测到有任务创建或删除,则会跳出 lv_task_handler 。
-
那么优化思路就出来了:定时执行一个 lvgl 高级任务,任务中 创建一个只执行一次的 最高级空任务,这样lvgl 就会定时跳出
-
注:lvgl disp task 优先级为中级,indev task 优先级为高,且新增加的任务执行顺序低于同优先级,因此 看门狗任务必须高于中优先级
改进
:
static void __lvglHandlerBreakTask(lv_task_t * task){}
// dog task, create task to break lvgl, prevent lvgl use 100% cpu
void __wdgUpdataTask(lv_task_t * task)
{
lv_task_t* breakTask = lv_task_create( __lvglHandlerBreakTask, 0, LV_TASK_PRIO_HIGHEST, NULL); //when find task create/del, lvgl will break loop to updata task link list
lv_task_set_repeat_count(breakTask, 1);
}
//1ms 1中断
void timeIRQ()
{
static uint32_t tick = 0;
if(++tick %5 == 0) lv_tick_inc(5);
}
void main(void)
{
uint32_t cnt = 0;
lv_task_create( __wdgUpdataTask, 100, LV_TASK_PRIO_HIGH, NULL); //wdg Updata task, prio must high
while(1)
{
btnBuzzer(); //按下按钮时响
lv_task_handler();
cnt++;
}
}
另外,上述程序需求按键按下蜂鸣器对应响一次,结果可能出现蜂鸣和按键不匹配问题。原因在于 看门狗任务100ms执行一次,若按键按下切换时间为50ms,那么在看门狗触发前,按键就存在触发两次的可能。
因此,封装上述创建函数,在按键回调中调用即可保证 按钮按下和蜂鸣一一对应。
static void __lvglHandlerBreakTask(lv_task_t * task){}
// lvgl break, create task to break lvgl, use to sync lvgl and other task
void lvglBreakTask(void)
{
lv_task_t* breakTask = lv_task_create( __lvglHandlerBreakTask, 0, LV_TASK_PRIO_HIGHEST, NULL); //when find task create/del, lvgl will break loop to updata task link list
lv_task_set_repeat_count(breakTask, 1);
}
附 lv_task_handler 循环部分源码:
LV_ATTRIBUTE_TASK_HANDLER uint32_t lv_task_handler(void)
{
.......
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)) {
......
/*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) {
....
}
/* It is no interrupter task or we already reached it earlier.
* Just run the remaining tasks*/
else {
....
}
/*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;
}
.....
LV_GC_ROOT(_lv_task_act) = next; /*Load the next task*/
}
} while(!end_flag);
.....
return time_till_next;
}
(2)main 循环中调用 lv_task_handler() 及 lv_tick_inc()
-
该种方法主要问题在于
牺牲了lvgl时基
,同时可能对触摸显示增加延时
。优化思路为: -
将 触摸及显示间隔设置为1,保证 触摸和显示没有增加额外的延时
-
注:尽管该优化较好解决了额外延时问题,但lvgl 时基给定依然异常。若 lvgl有对时间特别敏感的需求,则该方法不适用。同时,lvgl 也无法计算出的 cpu占用及 fps。
改进
:
/* Default display refresh period.
* Can be changed in the display driver (`lv_disp_drv_t`).*/
#define LV_DISP_DEF_REFR_PERIOD 1 /*[ms]*/
/* Input device read period in milliseconds */
#define LV_INDEV_DEF_READ_PERIOD 1
void main(void)
{
uint32_t cnt = 0;
while(1)
{
btnBuzzer(); //按下按钮时响
if(++cnt %5 == 0) lv_tick_inc(5);
lv_task_handler();
cnt++;
}
}
4、总结
(1)对比整理如下表
方式 | 优势 | 存在问题 | 备注 |
---|---|---|---|
定时器中调用 lv_tick_inc() | lvgl准确时基 ,实时性 | 可能出现lvgl 独占 cpu | |
main 循环中调用 lv_tick_inc() | 保证其他任务得到执行 | 无法满足高精度要求任务 ;触屏检测、显示最多会增加1个 间隔 | |
定时器中调用 lv_tick_inc() 改进版 | lvgl准确时基 ,实时性 | lvgl单个task执行期间 无法执行其他任务,例如刷屏 task耗时100ms | 和UI同步动作的任务需要调用 lvglBreakTask() ,例如按键蜂鸣` |
main 循环中调用 lv_tick_inc() 改进版 | 保证其他任务得到执行 | 无法满足高精度要求任务 |
(2)使用建议
- 定时器中调用:该方法优先考虑了lvgl,对其他任务不友好。因此需要着重其他任务的处理。
对于要求和UI同步动作的任务需要调用 lvglBreakTask(),例如按键声音,保证不发生错乱 - main 循环中调用:该方法优先考虑了其他任务的执行,但牺牲了lvgl时基,同时可能对触摸显示增加延时。因此着重对lvgl相关内容进行处理。
将 触摸及显示间隔设置为1,保证 触摸和显示没有增加额外的延时
若lvgl有对时间特别敏感的需求,则该方法不适用 - 使用操作系统:LVGL 任务调度机制比较简单,没有对执行做时间片限制,裸机开发需要用户实现相关操作。有条件可以使用操作系统。