裸机LVGL开发方式比较

文章探讨了在裸机环境下使用LVGL进行图形界面开发的两种基础实现方式及其优缺点,包括在main循环中调用lv_task_handler()和定时器中调用lv_tick_inc()。针对存在的问题,提出了改进方案,如通过创建高级任务来防止LVGL过度占用CPU,并调整触摸和显示间隔以优化实时性和响应速度。最后,给出了不同场景下的使用建议。
摘要由CSDN通过智能技术生成


一、前言

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
间隔计时需要退出 handler后才开始

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 任务调度机制比较简单,没有对执行做时间片限制,裸机开发需要用户实现相关操作。有条件可以使用操作系统。
  • 7
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值