最近在将LVGL移植到GD32的过程中发现动画效果在RT thread 上运行的不好,非常卡顿,似乎是没有起作用。下面记录一下排查方法。
使用自定义定时器
在配置文件中将 LV_TICK_CUSTOM 选项打开,将获取时钟间隔的函数配置为 rt thread 操作系统的 rt_tick_get() 函数
/*Use a custom tick source that tells the elapsed time in milliseconds.
*It removes the need to manually update the tick with `lv_tick_inc()`)*/
#define LV_TICK_CUSTOM 0
#if LV_TICK_CUSTOM
#define LV_TICK_CUSTOM_INCLUDE "rtthread.h" /*Header for the system time function*/
#define LV_TICK_CUSTOM_SYS_TIME_EXPR (rt_tick_get()) /*Expression evaluating to current system time in ms*/
#endif /*LV_TICK_CUSTOM*/
然后在rt thread 中创建一个 10ms 定时线程,定时调用 lv_task_handler();
while(1)
{
rt_thread_mdelay(10);
lv_task_handler();
}
到此,LVGL定时部分已经可以工作了,编写了一个动画显示程序,尝试去显示动画效果
1. 首先新建一个LVGL 定时器,定时500个周期调用屏幕运行函数,更新屏幕上显示控件。
lv_timer_t * UI_timer = lv_timer_create((lv_timer_cb_t)Screen_run, 500, NULL);
2. 在 Screen_run() 函数内创建一个动画效果
void Screen_run(void)
{
static short pre_value = 0;
/*Create an animation to set the value*/
lv_anim_t a;
lv_anim_init(&a);
lv_anim_set_exec_cb(&a, set_Heading_value); /*设置动画回调函数*/
lv_anim_set_var(&a, Heading_meter_indic);
lv_anim_set_values(&a, pre_value, value); /* 设置开始和结束值。 */
lv_anim_set_time(&a, 2000); /* 动画时长[ms] */
lv_anim_set_path_cb(&a, &lv_anim_path_overshoot); /* 设置路径(曲线) */
lv_anim_start(&a); /*动画开始*/
pre_value = value;
}
其中动画回调函数为set_Heading_value(),这个函数就是动画效果自动去调用的函数,自动去完成插值显示。
static void set_Heading_value(void *indic, int value)
{
lv_meter_indicator_t *bar = indic;
if (value > 0)
{
bar->start_value = 0;
bar->end_value = value;
bar->type_data.arc.color = lv_palette_main(LV_PALETTE_GREEN);
}
else if (value < 0)
{
bar->start_value = value;
bar->end_value = 0;
bar->type_data.arc.color = lv_palette_main(LV_PALETTE_RED);
}
else
{
bar->start_value = -1;
bar->end_value = 1;
bar->type_data.arc.color = lv_palette_main(LV_PALETTE_YELLOW);
}
}
3. 经过以上设置,以为动画效果会很平滑,结果运行起来之后和没有动画效果一样。问题出在那里了?
LVGL的运行机理就是自己管理了定时函数,实现自己的一套任务切换逻辑,当和其他操作系统联合使用时,就需要注意任务的优先级,传入LVGL 时间间隔的精度。
当LV_TICK_CUSTOM 选项打开后,原本是不需要再手动去调用lv_tick_inc(tick_period)函数(tick_period 为间隔时间,单位ms),lv_task_handler() 中会定时读取时间间隔,但是这样带来一个问题,在低优先级任务中,lv_task_handler的执行间隔不能被保证,会影响LVGL定时器的执行。从而影响了动画效果。
为了精确地知道经过的毫秒数,lv_tick_inc 应该在比 lv_task_handler() 更高优先级的例程中被调用(例如在中断中)。
1. 取消掉 LV_TICK_CUSTOM 选项,设置值为0 ;
2.在 SysTick_Handler 定时中断中手动调用 lv_tick_inc(1000/RT_TICK_PER_SECOND);
void SysTick_Handler(void)
{
/* enter interrupt */
rt_interrupt_enter();
rt_tick_increase();
lv_tick_inc(1000/RT_TICK_PER_SECOND);
/* leave interrupt */
rt_interrupt_leave();
}
3. 其他程序照旧
动画效果仍然不能正常显示。
分析源码了解到,不管lv_tick_inc以哪种方式调用,LVGL 都能执行定时刷新函数_lv_disp_refr_timer,刷新周期按照LV_DISP_DEF_REFR_PERIOD ,LVGL 已经注册了屏幕刷新定时器(lv_disp_drv_register)和动画定时器(_lv_anim_core_init)。
可能出问题的地方还是在应用程序这边。
分析_lv_disp_refr_timer 可以看到,LVGL是可以根据_lv_inv_area()指定的区域进行局部刷新,如果应用程序中没有按照LVGL提供的函数修改,而是直接修改了显示控件的参数,很有可能漏掉了指定刷新区域这一个步骤,我就是在应用程序中直接修改了控件的属性变量,没有使用LVGL提供的set 函数。
在动画回调函数中,使用LVGL提供的接口函数修改控件值,将set_Heading_value() 函数修改为
static void set_Heading_value(void *indic, int value)
{
if (value > 0)
{
lv_meter_set_indicator_start_value(ROT_meter, ROT_meter_indic, 0);
lv_meter_set_indicator_end_value(ROT_meter, ROT_meter_indic, value);
bar->type_data.arc.color = lv_palette_main(LV_PALETTE_GREEN);
}
else if (value < 0)
{
lv_meter_set_indicator_start_value(ROT_meter, ROT_meter_indic, value);
lv_meter_set_indicator_end_value(ROT_meter, ROT_meter_indic, 0);
bar->type_data.arc.color = lv_palette_main(LV_PALETTE_RED);
}
else
{
lv_meter_set_indicator_start_value(ROT_meter, ROT_meter_indic, -1);
lv_meter_set_indicator_end_value(ROT_meter, ROT_meter_indic, 1);
bar->type_data.arc.color = lv_palette_main(LV_PALETTE_YELLOW);
}
}
动画效果可以正常显示,就是刷新区域标志没有更新的原因。
同样的,如果其他控件显示正常,某几个控件不正常,即局部不刷新问题,很有可能是跳过了LVGL提供的函数,导致刷新标志没有正常更新导致的。