专栏/LVGL-V8框架简析(3)

LVGL-V8框架简析(3)

2021年12月24日 10:11--浏览 · --点赞 · --评论
粉丝:1.0万文章:100

        之前我们已经简单了解了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虽然功能繁多复杂,不过程序架构上还是比较清晰的。

投诉或建议