之前我们已经简单了解了LVGL的接口部分和初始化等,接下来要分析的部分就是事务调度器,即lv_task_handler函数。在V8版本中为lv_timer_handler,为的是兼容V7命名。
lv_timer_handler以一个非阻塞函数的方式完成了LVGL几乎所有的事务处理。首先是已运行标志(互斥体)。第一次为false,之后立马设置为true,防止重入。直至运行完一次事务调度结束时再恢复为false。方可下一次进入。
static bool already_running = false;
if(already_running) {
TIMER_TRACE("already running, concurrent calls are not allow, returning");
return 1;
}
already_running = true;
接着是handler_start变量用于检测时间,如果过多次调度检测的时间都为0,那么可能是你没有调用lv_tick_inc()用于提供时基给lvgl内核。
uint32_t handler_start = lv_tick_get();
if(handler_start == 0) {
static uint32_t run_cnt = 0;
run_cnt++;
if(run_cnt > 100) {
run_cnt = 0;
LV_LOG_WARN("It seems lv_tick_inc() is not called.");
}
}
接下来是定时器链表的调度运行,首先将全局创建和删除任务标志timer_deleted、timer_created清除,然后查找定时器链表的表头,然后运行定时器的回调函数timer_cb,然后往定时器链表后推继续运行,直至运行完所有定时器。途中有定时器创建和删除的时候将跳出循环,重新获取定时器链表表头,然后按上述过程继续运行。
/*Run all timer from the list*/
lv_timer_t * next;
do {
timer_deleted = false;
timer_created = false;
LV_GC_ROOT(_lv_timer_act) = _lv_ll_get_head(&LV_GC_ROOT(_lv_timer_ll));
while(LV_GC_ROOT(_lv_timer_act)) {
/*The timer might be deleted if it runs only once ('repeat_count = 1')
*So get next element until the current is surely valid*/
next = _lv_ll_get_next(&LV_GC_ROOT(_lv_timer_ll), LV_GC_ROOT(_lv_timer_act));
if(lv_timer_exec(LV_GC_ROOT(_lv_timer_act))) {
/*If a timer was created or deleted then this or the next item might be corrupted*/
if(timer_created || timer_deleted) {
TIMER_TRACE("Start from the first timer again because a timer was created or deleted");
break;
}
}
LV_GC_ROOT(_lv_timer_act) = next; /*Load the next timer*/
}
} while(LV_GC_ROOT(_lv_timer_act));
其中lv_timer_exec用于运行循环中获取的当前活动定时器_lv_timer_act,也就是调用定时器的timer_cb成员指向的函数。LVGL实现的刷屏和触摸输入等功能均是基于定时器运行。
接着计算下一次定时器调度所需的最少时间。通过循环读取定时器链表,根据运行状态和定时器下一次运行的时延对比获取最少的时间段,默认是最大值LV_NO_TIMER_READY。
uint32_t time_till_next = LV_NO_TIMER_READY;
next = _lv_ll_get_head(&LV_GC_ROOT(_lv_timer_ll));
while(next) {
if(!next->paused) {
uint32_t delay = lv_timer_time_remaining(next);
if(delay < time_till_next)
time_till_next = delay;
}
next = _lv_ll_get_next(&LV_GC_ROOT(_lv_timer_ll), next); /*Find the next timer*/
}
LV_NO_TIMER_READY定义如下。
#define LV_NO_TIMER_READY 0xFFFFFFFF
接着是空闲统计部分。通过定时器运行前后的时间计算忙碌时间部分,通过和配置文件设定的500ms周期共同计算忙碌百分比,目前看来不是很准。
busy_time += lv_tick_elaps(handler_start);
uint32_t idle_period_time = lv_tick_elaps(idle_period_start);
if(idle_period_time >= IDLE_MEAS_PERIOD) {
idle_last = (busy_time * 100) / idle_period_time; /*Calculate the busy percentage*/
idle_last = idle_last > 100 ? 0 : 100 - idle_last; /*But we need idle time*/
busy_time = 0;
idle_period_start = lv_tick_get();
}
其中idle_last用于定时器空闲百分比返回值,函数如下。
/**
* Get idle percentage
* @return the lv_timer idle in percentage
*/
uint8_t lv_timer_get_idle(void)
{
return idle_last;
}
最后在屏幕刷新定时器中调用此函数用于计算CPU负载。如果你启用了LABEL控件和刷新侦听功能的话就会在设定的右下角显示帧率和CPU百分比。
/*1: Show CPU usage and FPS count in the right bottom corner*/
#define LV_USE_PERF_MONITOR 1
#if LV_USE_PERF_MONITOR
#define LV_USE_PERF_MONITOR_POS LV_ALIGN_BOTTOM_RIGHT
#endif
#if LV_USE_PERF_MONITOR && LV_USE_LABEL
/* 忽略label创建、帧率计算等代码 */
unsigned int cpu = 100 - lv_timer_get_idle();
lv_label_set_text_fmt(perf_label, "%u FPS\n%u%% CPU", fps, cpu);
}
#endif
最后是释放上述的互斥运行标志,防止重入用的,接着把下次运行所需的时间通过log打印和返回。
already_running = false; /*Release the mutex*/
TIMER_TRACE("finished (%d ms until the next timer call)", time_till_next);
return time_till_next;
以上就是内核的调度部分程序,不过字面上我们只能看到和定时器相关的部分,具体的UI绘制及交互隐藏在内核初始化所创建的定时器任务里,即各定时器的回调函数。可以看出,LVGL虽然功能繁多复杂,不过程序架构上还是比较清晰的。