设计GUI的显示元素动起来,实际上是多张图按照一定的节奏和变化依次刷屏实现的。LVGL在内核设计了timer
对象,用于实现周期回调的功能,开发者可以在周期调用的回调函数中,修改GUI显示内容的属性,触发LVGL显示内容的刷新,然后由LVGL的例行执行程序重新刷屏,从而实现动画效果。
LVGL的后台是通过时间片轮询对LVGL的交互任务进行调度的。
在具体的MCU平台上适配LVGL时,通常会使用SysTick定时器中断服务程序调用lv_tick_inc()
函数驱动LVGL内部的计数器递增,然后在主线程中调用lv_timer_handler()
函数以时间片为节点执行LVGL的日常任务(输入事件、刷屏等操作)。从源码上看,整个LVGL的程序框架,就是建立在定时器刻度的轮询调度之上的,可见包含LVGL的MCU工程的顶级业务逻辑代码如下:
lv_ui guider_ui;
int main(void)
{
BOARD_Init();
SysTick_Config(CLOCK_SYS_FREQ / 100); /* 10ms. */
lv_init();
lv_port_disp_init();
setup_ui(&guider_ui);
events_init(&guider_ui);
custom_init(&guider_ui);
while (1)
{
lv_timer_handler(); /* 执行LVGL时间片轮询. */
}
}
void SysTick_Handler(void)
{
lv_tick_inc(10); /* 驱动LVGL的定时器. */
}
在LVGL中,当需要实现一些基于时间变化显示内容的效果(动画),可以借用LVGL内部的定时器对象来实现周期执行程序的效果。关于LVGL应用程序中使用定时器的API主要有两个:
其中,使用lv_timer_create()
函数创建一个定时器实例,并会向其中传送初始化参数,指定本定时器的触发周期
和触发时执行的回调函数
。而lv_timer_del()
函数会回收这个定时器的资源(包括变量,以及挂在定时器任务列表中的指针),以减轻LVGL后台调度器的负担。
这里以设计一个速度表盘指针转动的样例,展示timer定时器对象的用法。实现原理,在定时器组件的回调函数中,会周期性地改变meter
组件中speed_scale_1_ndline_0
属性的值。当改变LVGL显示组件的任何可视化组件的属性后,LVGL会自动刷新显示内容,从而形成动画效果。
在GUI Guider的编辑页面中新建一个meter
组件的对象,如图x所示。
将新建meter对象改名为meter_speed
,这个名字将会在后面自定义编写代码时,用于引用这个meter对象实例。手工调整一下表盘的尺寸。
然后,选中主编辑区的页面区域(对应选中当前的screen对象),再在编辑区域的右侧,在事件(Events)
页面中,单击加号,创建一个事件,编辑事件。
编辑事件的触发方式,即在什么时机产生当前编辑的事件,常见的事件触发方式有:Load Start(开始载入当前对象时)、Loaded(完成载入当前对象时)、Unload Start(开始从本对象切换到别处时)、Unloaded(完成从本对象切换到别处时)等,如图x所示。每种显示元素的对象都有各自的触发方式,甚至还有一些显示元素的对象没有触发事件可创建。
选定Trigger触发方式
后,还需要选定Target目标对象
。此时,目标对象
的下拉列表中会自动整理出当前整个GUI Guider工程中已经创建的有所显示元素对象。如图x所示。
这里需要重点说明的是,触发方式描述的当前在编辑区域中选中的对象的动作,当选定触发方式后,本事件的标签名称也就自动改成了<当前对象名>_<触发方式>
。目标对象反映的是即将改变属性的对象,对应部分可选的属性也在事件页面下方展示出来,可在图形页面中配置。当然,并不是所有的可编辑属性都被做成了在对话框中可配置的,本例就在最后一行,选择了执行自定义程序,在弹出的代码编辑对话框中添加了创建定时器对象timer_meter_speed
的语句。如图x所示。
图x中所示,自定义编写C源码,当触发Screen的Loaded事件时,将执行图中代码,创建timer_meter_speed
定时器对象实例。保存工程后生成代码,对应地,可在GUI Guider工程中的./generated/event_init.c
文件中看到对应生成的代码。
#include "events_init.h"
#include
#include "lvgl.h"
#include "custom.h"
static lv_timer_t * timer_meter_speed;
void events_init(lv_ui *ui)
{
}
static void screen_event_handler(lv_event_t *e)
{
lv_event_code_t code = lv_event_get_code(e);
switch (code)
{
case LV_EVENT_SCREEN_LOADED:
{
timer_meter_speed = lv_timer_create(timer_meter_speed_cb, 100, &guider_ui);
}
break;
case LV_EVENT_SCREEN_UNLOADED:
{
lv_timer_del(timer_meter_speed);
}
break;
default:
break;
}
}
void events_init_screen(lv_ui *ui)
{
lv_obj_add_event_cb(ui->screen, screen_event_handler, LV_EVENT_ALL, ui);
}
但是回调函数timer_meter_speed_cb()
仍需要开发者自行在custom.c
文件中自行创建,GUI Guider不会自动更新。代码如下:
static int32_t speed = 50;
static bool is_increase = true;
/**
* Create a demo application
*/
void custom_init(lv_ui *ui)
{
/* Add your codes here */
}
void timer_meter_speed_cb(lv_timer_t *t)
{
lv_ui * gui = t->user_data;
lv_meter_set_indicator_value(gui->screen_meter_speed, gui->screen_meter_speed_scale_1_ndline_0, speed);
if (speed >= 90)
{
is_increase = false;
}
if (speed <= 20)
{
is_increase = true;
}
if (is_increase)
{
speed++;
}
else
{
speed--;
}
}
这里实现的内容是,在MCU上运行LVGL时,一旦显示完成当前的屏幕页面后,立即创建定时器对象timer_meter_speed
,这个定时器将会每隔100ms调用一次回调函数timer_meter_speed_cb()
,在timer_meter_speed_cb()
函数中,更新变量speed
的值,然后通过lv_meter_set_indicator_value()
函数,改变screen_meter_speed
对象中screen_meter_speed_scale_1_ndline_0
属性的值。每隔100ms改变一次screen_meter_speed
对象的属性值,将会触发显示内容的刷新,从而在显示屏上显现出动画效果。
将GUI Guider生成的程序下载到MCU之前,可以先在GUI Guider的工程中模拟运行一次,一方面可以帮忙排除编译错误(GUI Guider使用armgcc将LVGL和生成的源码一起编译),另一方面也方便预览运行效果。使用LVGL组件的过程不涉及到对具体MCU硬件的依赖,因此完全可以在模拟环境中预览实际执行的效果。如图x所示。
最后,编译Keil工程,下载到MCU开发板。可以看到在MCU开发板上运行动图的效果,如图x所示。
timer对象本身只是一个实现周期调度的机制,开发者可以在周期回调的函数内部,修改和重置显示各种对象的属性,触发LVGL刷屏,从而实现动画的效果。
(未完待续。。。)