目录
Preview
LVGL Tick
Timer Handler
lv_timer_exec
显式刷屏任务
_lv_disp_refr_timer
小结
Preview
整个 LVGL Framework 的运转,都是基于 LVGL 中定义的所谓的 ”Timer“ 定时器的,系统需要给 LVGL 一个 ”心跳“,LVGL Framework 才可以正常的运转起来;
基于这样的一个框架,LVGL 定义了两个函数,需要与系统进行对接:
- lv_tick_inc:用于系统告知 LVGL 时间;
- lv_timer_handler:用于处理 LVGL 的 timer 事件
典型的做法是,SystemTick 中调用 lv_tick_inc 来告知 LVGL 时间;while 1 中,调用 lv_timer_handler;
下面就来仔细分析他们的实现和原理;
LVGL Tick
LVGL Porting 的时候,需要再定时器中调用 lv_tick_inc 函数;我在做 Porting 的时候,开启的 M4 的 SystemTick,并且定时为 1ms,在 Systemtick 的 Handler 中调用这个函数:
/**
* @brief This function handles SysTick Handler.
* @param None
* @retval None
*/
void SysTick_Handler(void)
{
lv_tick_inc(1);
}
当然,你也可以不用 SystemTick,使用其他的硬件 Timer 也可以达到同样的效果;
lv_tick_inc 函数的入参是毫秒为单位,当然,也不是非要定义 1ms 的定时器,你定义 2ms 的定时器,在 ISR 中调用这个函数,并往里面传 2,也是可以的;
接着看下 lv_tick_inc 这个函数的实现:
#if !LV_TICK_CUSTOM
/**
* You have to call this function periodically
* @param tick_period the call period of this function in milliseconds
*/
LV_ATTRIBUTE_TICK_INC void lv_tick_inc(uint32_t tick_period)
{
tick_irq_flag = 0;
sys_time += tick_period;
}
#endif
对两个全局变量幅值:
sys_time 代表了当前 ms 数;上面这两个全局变量只在一个地方使用 lv_tick_get,即获取当前的时间:
/**
* Get the elapsed milliseconds since start up
* @return the elapsed milliseconds
*/
uint32_t lv_tick_get(void)
{
#if LV_TICK_CUSTOM == 0
/*If `lv_tick_inc` is called from an interrupt while `sys_time` is read
*the result might be corrupted.
*This loop detects if `lv_tick_inc` was called while reading `sys_time`.
*If `tick_irq_flag` was cleared in `lv_tick_inc` try to read again
*until `tick_irq_flag` remains `1`.*/
uint32_t result;
do {
tick_irq_flag = 1;
result = sys_time;
} while(!tick_irq_flag); /*Continue until see a non interrupted cycle*/
return result;
#else
return LV_TICK_CUSTOM_SYS_TIME_EXPR;
#endif
}
现在得到的结论是,LVGL 需要系统不间断的提供一个基于毫秒(ms) 的 Tick 维持 LVGL 的心跳;
值得一提的是,LVGL 的绘制,不是直接绘制到屏幕,首先是往内部缓冲区绘制,当绘图(渲染)准备好时,该缓冲区被刷到屏幕;
与直接绘制到屏幕相比,这种方法有两个主要优点:
- 避免绘制UI层时闪烁。例如,如果 LVGL 直接绘制到显示中,那么在绘制 *背景 + 按钮 + 文本 * 时,每个“阶段”都会在短时间内可见。
- 修改内部 RAM 中的缓冲区并最终仅写入一个像素一次比在每个像素访问时直接读取/写入显示更快。 (例如,通过带有 SPI 接口的显示控制器)。
请注意,此概念与“传统”双缓冲不同,后者有 2 个屏幕大小的帧缓冲区: 一个保存当前图像以显示在显示器上,渲染发生在另一个(非活动)帧缓冲区中,渲染完成后它们会被交换。 主要区别在于,使用 LVGL,您不必存储 2 个帧缓冲区(通常需要外部 RAM),而只需存储更小的绘图缓冲区,也可以轻松装入内部 RAM。
LVGL 按以下步骤刷新屏幕:
- UI 上发生了一些需要重绘的事情。例如,按下按钮、更改图表、发生动画等。
- LVGL 将改变对象的旧区和新区保存到一个缓冲区中,称为无效区缓冲区()。为了优化,在某些情况下,不会将对象添加到缓冲区中:
- 不添加隐藏对象;
- 不添加完全脱离其父对象的对象;
- 部分超出父级的区域被裁剪到父级的区域;
- 不添加其他屏幕上的对象
- 在每个 LV_DISP_DEF_REFR_PERIOD(在 lv_conf.h 中设置)发生以下情况:
- LVGL 检查无效区域并连接相邻或相交的区域;
- 获取第一个连接区域,如果它小于?draw buffer,则只需将该区域的内容渲染到?draw buffer?中。 如果该区域不适合缓冲区,则在?draw buffer?中绘制尽可能多的线;
- 当区域被渲染时,从显示驱动程序调用 flush_cb 来刷新显示;
- 如果该区域大于缓冲区,也渲染其余部分;
- 对所有连接的区域执行相同操作;
默认情况下在 lv_conf.h 中,LV_DISP_DEF_REFR_PERIOD = 30ms:
/*Default display refresh period. LVG will redraw changed areas with this period time*/
#define LV_DISP_DEF_REFR_PERIOD 30 /*[ms]*/
当一个区域被重绘时,库搜索覆盖该区域的最上面的对象,并从该对象开始绘制。 例如,如果按钮的标签发生了变化,库将看到在文本下方绘制按钮就足够了,并且不需要在按钮下方绘制屏幕。
这个 LV_DISP_DEF_REFR_PERIOD = 30ms:是在显示对接的时候,创建的 timer:
lv_disp_drv_register
|-------------------------> lv_timer_create(_lv_disp_refr_timer, LV_DISP_DEF_REFR_PERIOD, disp);
Timer Handler
这里澄清一下,有的地方叫做 task handler,其实是一回事,lv_task_handler 函数直接调用了 lv_timer_handler,所以我们这里看 lv_timer_handler;
lv_timer_handler 函数是一个需要被定期调用的函数:
- main 函数中设置 while(1) 调用
- 定期定时中断(低优先级然后是 lv_tick_inc()) 中调用
- 定期执行的 OS 任务中调用
计时并不严格,但应保持大约 5 毫秒以保持系统响应;
为了精确地知道经过的毫秒数,lv_tick_inc 应该在比 lv_task_handler() 更高优先级的例程中被调用(例如在中断中),即使 lv_task_handler 的执行花费较长时间;
lv_timer_handler 的代码如下所示:
/**
* Call it periodically to handle lv_timers.
* @return the time after which it must be called again
*/
LV_ATTRIBUTE_TIMER_HANDLER uint32_t lv_timer_handler(void)
{
TIMER_TRACE("begin");
/*Avoid concurrent running of the timer handler*/
// 使用 already_running 来防止该函数被重入
static bool already_running = false;
if(already_running) {
TIMER_TRACE("already running, concurrent calls are not allow, returning");
return 1;
}
already_running = true;
if(lv_timer_run == false) {
already_running = false; /*Release mutex*/
return 1;
}
static uint32_t idle_period_start = 0;
static uint32_t busy_time = 0;
// 获取当前的时间戳
uint32_t handler_start = lv_tick_get();
// 判断 lv_tick_inc 是否被正确调用,以 100 个 counter
作为判断标准
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.");
}
}
/*Run all timer from the list*/
lv_timer_t * next;
do {
timer_deleted = false;
timer_created = false;
// 取出 _lv_timer_ll 的 head
LV_GC_ROOT(_lv_timer_act) = _lv_ll_get_head(&LV_GC_ROOT(_lv_timer_ll));
// 如果 _lv_timer_ll head 不为空
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*/
// 从 _lv_timer_ll 链表中,访问第一个有效数据,赋值给全局变量 _lv_timer_act
next = _lv_ll_get_next(&LV_GC_ROOT(_lv_timer_ll), LV_GC_ROOT(_lv_timer_act));
// 调用 lv_timer_exec 执行 _lv_timer_act,再这个 lv_timer_exec 会判断定时的时间是否到达,如果到达,则执行 timer_cb;
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));
// 计算获取下一次最近的 timer 的时间,并获取到 time_till_next 变量中;
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*/
}
// busy_time 是一个累加的过程,这个 handler_start 是进入这个函数的时候获取的
// 这里获取了本次函数调用,从开始绘制到绘制结束的时间差认为是本次函数调用(绘制)消耗的时间,也就是 busy_time;
busy_time += lv_tick_elaps(handler_start);
// idle_period_start 和 idle_period_time,用于每隔 IDLE_MEAS_PERIOD=500ms 来计算一次 idle last 的值
// 计算完成后,将 busy_time 和 idle_period_start 更新;
uint32_t idle_period_time = lv_tick_elaps(idle_period_start);
if(idle_period_time >= IDLE_MEAS_PERIOD) {
// 计算一个间隔周期(约为 500ms)
内,busy_time 的百分比;
idle_last = (busy_time * 100) / idle_period_time; /*Calculate the busy percentage*/
// 用 100 减去 busy_time 的百分比,得到 idle_last,即,idle 的百分比
idle_last = idle_last > 100 ? 0 : 100 - idle_last; /*But we need idle time*/
busy_time = 0;
idle_period_start = lv_tick_get();
}
already_running = false; /*Release the mutex*/
TIMER_TRACE("finished (%d ms until the next timer call)", time_till_next);
return time_till_next;
}
代码中的 already_running 是为了防止这个函数被重入;
代码流程如下:
首先进行异常判断:
- 首先通过函数 lv_tick_get() 获取当前的时间戳;
- 累计 lv_timer_handler 函数被调用 100 次后,时间戳依然为 0,那么说明 lv_tick_inc 函数没有被调用到,报 Warning;
在这个函数中,随处可见的有两个破玩意:
- LV_GC_ROOT(_lv_timer_act)
- LV_GC_ROOT(_lv_timer_ll)
这里的 GC_ROOT,乱七八糟的东西,是为 MicroPython 准备的,C 语言的话,直接展开来看,在 lv_gc.h 中,直接将这些全部展开:
根据宏头文件定义,很多地方都使用了 LV_GC_ROOT(_lv_timer_ll) 这种定义方式,先来看看这些全局变量在哪里定义的;
注意到(LVGL (5) GC 全局变量定义):
LV_ITERATE_ROOTS(LV_EXTERN_ROOT);把他展开:
LV_DISPATCH(LV_EXTERN_ROOT, lv_ll_t, _lv_timer_ll):再展开
LV_EXTERN_ROOT(lv_ll_t, _lv_timer_ll) 继续展开:
extern lv_ll_t _lv_timer_ll;
那么就是再这个头文件中定义了一堆的 extern 全局变量:
extern lv_ll_t _lv_timer_ll;
extern lv_ll_t _lv_disp_ll;
extern lv_ll_t _lv_indev_ll;
extern lv_ll_t _lv_fsdrv_ll;
extern lv_ll_t _lv_anim_ll;
extern lv_ll_t _lv_group_ll;
extern lv_ll_t _lv_img_decoder_ll;
extern lv_ll_t _lv_obj_style_trans_ll;
extern lv_layout_dsc_t * _lv_layout_list;
extern lv_timer_t*, _lv_timer_act;
.....
lv_timer_exec
在 lv_timer_handler 中,经过一系列的操作后,将会查询 _lv_timer_ll 链表上的 timer,并执行 lv_timer_exec 函数
/**
* Execute timer if its remaining time is zero
* @param timer pointer to lv_timer
* @return true: execute, false: not executed
*/
static bool lv_timer_exec(lv_timer_t * timer)
{
//查看传入的 timer 是否被 paused
if(timer->paused) return false;
bool exec = false;
// 查看 timer 的时间是否到期
if(lv_timer_time_remaining(timer) == 0) {
/* Decrement the repeat count before executing the timer_cb.
* If any timer is deleted `if(timer->repeat_count == 0)` is not executed below
* but at least the repeat count is zero and the timer can be deleted in the next round*/
int32_t original_repeat_count = timer->repeat_count;
// 如果 repeat 的次数大于 0,那么自减 1;
if(timer->repeat_count > 0) timer->repeat_count--;
// 更新 last_run
timer->last_run = lv_tick_get();
TIMER_TRACE("calling timer callback: %p", *((void **)&timer->timer_cb));
// 调用 timer_cb
if(timer->timer_cb && original_repeat_count != 0) timer->timer_cb(timer);
TIMER_TRACE("timer callback %p finished", *((void **)&timer->timer_cb));
LV_ASSERT_MEM_INTEGRITY();
exec = true;
}
// 判断 repeat_count 是否为 0,如果为 0,代表这个 timer 的任务要被销毁,调用 lv_timer_del 删除这个 Timer 任务
if(timer_deleted == false) { /*The timer might be deleted by itself as well*/
if(timer->repeat_count == 0) { /*The repeat count is over, delete the timer*/
TIMER_TRACE("deleting timer with %p callback because the repeat count is over", *((void **)&timer->timer_cb));
lv_timer_del(timer);
}
}
return exec;
}
static uint32_t lv_timer_time_remaining(lv_timer_t * timer)
{
/*Check if at least 'period' time elapsed*/
uint32_t elp = lv_tick_elaps(timer->last_run);
if(elp >= timer->period)
return 0;
return timer->period - elp;
}
他的流程如下:
显式刷屏任务
前面说到 lv_timer_handler 会遍历 _lv_timer_ll 链表,然后调用 lv_timer_exec,查询到期的基于 timer 的任务,再调用它的 timer_cb 回调函数;
那么就要关注创建了哪些 timer 并挂接到了 _lv_timer_ll链表;
在 LVGL 中,使用 lv_timer_create 来创建一个基于 _lv_timer_ll的链表;
LVGL 支持显示 (Display) 和输入设备响应 (Input Device);
注册 Display 和 Input Device 的时候会创建他们基于 Timer 的任务:
- lv_disp_drv_register
- lv_indev_drv_register
这里我们只关注显示部分,所以暂时只关注 lv_disp_drv_register;
/ *
* Register an initialized display driver.
* Automatically set the first display as active.
* @param driver pointer to an initialized 'lv_disp_drv_t' variable. Only its pointer is saved!
* @return pointer to the new display or NULL on error
*/
lv_disp_t * lv_disp_drv_register(lv_disp_drv_t * driver)
{
............
/*Create a refresh timer*/
// 创建用于 refresh 的 Timer 任务,周期是 LV_DISP_DEF_REFR_PERIOD=30ms 一次,callback 是 _lv_disp_refr_timer
disp->refr_timer = lv_timer_create(_lv_disp_refr_timer, LV_DISP_DEF_REFR_PERIOD, disp);
LV_ASSERT_MALLOC(disp->refr_timer);
if(disp->refr_timer == NULL) {
lv_mem_free(disp);
return NULL;
}
............
return disp;
}
可以看到创建了一个用于刷新显示的基于 Timer 的任务,它的周期是 30ms,任务的执行函数是 _lv_disp_refr_timer;
lv_timer_t * lv_timer_create(lv_timer_cb_t timer_xcb, uint32_t period, void * user_data)
{
lv_timer_t * new_timer = NULL;
new_timer = _lv_ll_ins_head(&LV_GC_ROOT(_lv_timer_ll));
LV_ASSERT_MALLOC(new_timer);
if(new_timer == NULL) return NULL;
new_timer->period = period;
new_timer->timer_cb = timer_xcb;
new_timer->repeat_count = -1;
new_timer->paused = 0;
new_timer->last_run = lv_tick_get();
new_timer->user_data = user_data;
timer_created = true;
return new_timer;
}
可以看到,创建 timer_create 任务的时候,repeat_count 指定是 -1,也就是一直会 repeat;更新 last_run 是当前时刻;
_lv_disp_refr_timer
这个函数主要是用于刷新显示 30ms 触发一次;
void _lv_disp_refr_timer(lv_timer_t * tmr)
{
.........
uint32_t start = lv_tick_get();
volatile uint32_t elaps = 0;
// 获取 disp 结构
disp_refr = lv_disp_get_default();
? ? /*Refresh the screen's layout if required*/
? ? lv_obj_update_layout(disp_refr->act_scr);
? ? if(disp_refr->prev_scr) lv_obj_update_layout(disp_refr->prev_scr);
? ? lv_obj_update_layout(disp_refr->top_layer);
? ? lv_obj_update_layout(disp_refr->sys_layer);
.........
}
首先获取被注册的 disp 结构;
然后调用 lv_obj_update_layout 来更新 3 个 layer 的布局:被更新的对象就是当前激活的屏幕 act_scr,如果布局更新,那么相对应的就需要更新显示,此外还有上一屏prev_scr,主要是用于屏幕加载动画的时候使用,接着是顶层 top_layer 和系统层 sys_layer 的布局更新。
接着调用 lv_refr_join_area,用于计算需要更新的区域,剔除多控件重复覆盖的部分的无意义刷新,之后是?lv_refr_areas?用于刷新之前计算的需要刷新的区域:
void _lv_disp_refr_timer(lv_timer_t * tmr)
{
.........
// 计算需要更新的区域
lv_refr_join_area();
// 刷新之前计算的需要刷新的区域
lv_refr_areas();
.........
}
这个 lv_refr_areas() 就是刷新部分;
分别往下调用了:
lv_refr_area->lv_refr_area_part->draw_buf_flush->call_flush_cb->(drv->flush_cb)
也就是最初注册的显示驱动的刷新回调函数。之后清除刷新及计算时用到的内存和驱动成员变量。
它的实现如下:
/**
* Refresh the joined areas
*/
static void lv_refr_areas(void)
{
px_num = 0;
if(disp_refr->inv_p == 0) return;
/*Find the last area which will be drawn*/
int32_t i;
int32_t last_i = 0;
for(i = disp_refr->inv_p - 1; i >= 0; i--) {
if(disp_refr->inv_area_joined[i] == 0) {
last_i = i;
break;
}
}
disp_refr->driver->draw_buf->last_area = 0;
disp_refr->driver->draw_buf->last_part = 0;
for(i = 0; i < disp_refr->inv_p; i++) {
/*Refresh the unjoined areas*/
if(disp_refr->inv_area_joined[i] == 0) {
if(i == last_i) disp_refr->driver->draw_buf->last_area = 1;
disp_refr->driver->draw_buf->last_part = 0;
lv_refr_area(&disp_refr->inv_areas[i]);
px_num += lv_area_get_size(&disp_refr->inv_areas[i]);
}
}
}
针对每一块(area)区域,调用到了 lv_refr_area 来进行绘制;
/**
* Refresh an area if there is Virtual Display Buffer
* @param area_p pointer to an area to refresh
*/
static void lv_refr_area(const lv_area_t * area_p)
{
lv_draw_ctx_t * draw_ctx = disp_refr->driver->draw_ctx;
draw_ctx->buf = disp_refr->driver->draw_buf->buf_act;
/*With full refresh just redraw directly into the buffer*/
/*In direct mode draw directly on the absolute coordinates of the buffer*/
if(disp_refr->driver->full_refresh || disp_refr->driver->direct_mode) {
lv_area_t disp_area;
lv_area_set(&disp_area, 0, 0, lv_disp_get_hor_res(disp_refr) - 1, lv_disp_get_ver_res(disp_refr) - 1);
draw_ctx->buf_area = &disp_area;
if(disp_refr->driver->full_refresh) {
disp_refr->driver->draw_buf->last_part = 1;
draw_ctx->clip_area = &disp_area;
lv_refr_area_part(draw_ctx);
}
else {
disp_refr->driver->draw_buf->last_part = disp_refr->driver->draw_buf->last_area;
draw_ctx->clip_area = area_p;
lv_refr_area_part(draw_ctx);
}
return;
}
/*Normal refresh: draw the area in parts*/
/*Calculate the max row num*/
// 获取区域的宽高
lv_coord_t w = lv_area_get_width(area_p);
lv_coord_t h = lv_area_get_height(area_p);
/ *
* (x1, y1) (x2, y1)
* +-------------------------+
* | |
* | area |
* | |
* +-------------------------+
* (x1, y2) (x2, y2)
*/
// 计算终止的 y2,绘制的 area 的终点 y2 不能超过屏幕的最大行数
lv_coord_t y2 = area_p->y2 >= lv_disp_get_ver_res(disp_refr) ?
lv_disp_get_ver_res(disp_refr) - 1 : area_p->y2;
// 获取一次能够绘制的最大行数,在 lv_disp_draw_buf_init 的时候,配置的 10 行 buffer
int32_t max_row = get_max_row(disp_refr, w, h);
lv_coord_t row;
lv_coord_t row_last = 0;
lv_area_t sub_area;
// 每次按照 max_row(默认 10 行 像素 buffer) 进行绘制,一个 area 超过了 10 行的话,那么绘制多次
for(row = area_p->y1; row + max_row - 1 <= y2; row += max_row) {
/*Calc. the next y coordinates of draw_buf*/
sub_area.x1 = area_p->x1;
sub_area.x2 = area_p->x2;
sub_area.y1 = row;
sub_area.y2 = row + max_row - 1;
draw_ctx->buf_area = &sub_area;
draw_ctx->clip_area = &sub_area;
draw_ctx->buf = disp_refr->driver->draw_buf->buf_act;
if(sub_area.y2 > y2) sub_area.y2 = y2;
row_last = sub_area.y2;
if(y2 == row_last) disp_refr->driver->draw_buf->last_part = 1;
lv_refr_area_part(draw_ctx);
}
/*If the last y coordinates are not handled yet ...*/
if(y2 != row_last) {
/*Calc. the next y coordinates of draw_buf*/
sub_area.x1 = area_p->x1;
sub_area.x2 = area_p->x2;
sub_area.y1 = row;
sub_area.y2 = y2;
draw_ctx->buf_area = &sub_area;
draw_ctx->clip_area = &sub_area;
draw_ctx->buf = disp_refr->driver->draw_buf->buf_act;
disp_refr->driver->draw_buf->last_part = 1;
lv_refr_area_part(draw_ctx);
}
}
lv_refr_area 函数,针对一块(area),进行绘制;绘制的颗粒度大小,以初始化 display 的时候,传入的 buffer 为准,默认情况下:lv_disp_draw_buf_init 的时候,分配的是 10 行 draw buffer;
也就是 lv_refr_area 要绘制的 area,按照 10 行为颗粒度,调用 lv_refr_area_part 进行绘制;
下面来看 lv_refr_area_part 函数实现:
static void lv_refr_area_part(lv_draw_ctx_t * draw_ctx)
{
lv_disp_draw_buf_t * draw_buf = lv_disp_get_draw_buf(disp_refr);
/* Below the `area_p` area will be redrawn into the draw buffer.
* In single buffered mode wait here until the buffer is freed.*/
if(draw_buf->buf1 && !draw_buf->buf2) {
// 判断是否正在往 flushing
while(draw_buf->flushing) {
if(disp_refr->driver->wait_cb) disp_refr->driver->wait_cb(disp_refr->driver);
}
}
lv_obj_t * top_act_scr = NULL;
lv_obj_t * top_prev_scr = NULL;
/*Get the most top object which is not covered by others*/
// 获取 top active screen
top_act_scr = lv_refr_get_top_obj(draw_ctx->buf_area, lv_disp_get_scr_act(disp_refr));
if(disp_refr->prev_scr) {
top_prev_scr = lv_refr_get_top_obj(draw_ctx->buf_area, disp_refr->prev_scr);
}
/*Draw a display background if there is no top object*/
if(top_act_scr == NULL && top_prev_scr == NULL) {
// 绘制 background
if(draw_ctx->draw_bg) {
lv_draw_rect_dsc_t dsc;
lv_draw_rect_dsc_init(&dsc);
dsc.bg_img_src = disp_refr->bg_img;
dsc.bg_img_opa = disp_refr->bg_opa;
dsc.bg_color = disp_refr->bg_color;
dsc.bg_opa = disp_refr->bg_opa;
// 填充
draw_ctx->draw_bg(draw_ctx, &dsc, draw_ctx->buf_area);
}
else if(disp_refr->bg_img) {
lv_img_header_t header;
lv_res_t res;
res = lv_img_decoder_get_info(disp_refr->bg_img, &header);
if(res == LV_RES_OK) {
lv_area_t a;
lv_area_set(&a, 0, 0, header.w - 1, header.h - 1);
lv_draw_img_dsc_t dsc;
lv_draw_img_dsc_init(&dsc);
dsc.opa = disp_refr->bg_opa;
lv_draw_img(draw_ctx, &dsc, &a, disp_refr->bg_img);
}
else {
LV_LOG_WARN("Can't draw the background image");
}
}
else {
lv_draw_rect_dsc_t dsc;
lv_draw_rect_dsc_init(&dsc);
dsc.bg_color = disp_refr->bg_color;
dsc.bg_opa = disp_refr->bg_opa;
lv_draw_rect(draw_ctx, &dsc, draw_ctx->buf_area);
}
}
/*Refresh the previous screen if any*/
if(disp_refr->prev_scr) {
if(top_prev_scr == NULL) top_prev_scr = disp_refr->prev_scr;
lv_refr_obj_and_children(draw_ctx, top_prev_scr);
}
if(top_act_scr == NULL) top_act_scr = disp_refr->act_scr;
lv_refr_obj_and_children(draw_ctx, top_act_scr);
/*Also refresh top and sys layer unconditionally*/
lv_refr_obj_and_children(draw_ctx, lv_disp_get_layer_top(disp_refr));
lv_refr_obj_and_children(draw_ctx, lv_disp_get_layer_sys(disp_refr));
/*In true double buffered mode flush only once when all areas were rendered.
*In normal mode flush after every area*/
if(disp_refr->driver->full_refresh == false) {
draw_buf_flush(disp_refr);
}
}
在 lv_refr_area_part 函数中,通过调用 lv_refr_obj_and_children 函数,对 act_scr、top_layer 和 sys_layer上以及他们的子 obj,来进行在 draw_ctx 中的 buffer 的渲染;
首先在 lv_refr_obj_and_children 函数中获取了当前的 scr_act 屏幕;
然后调用 lv_refr_obj 函数,draw_ctx 的进行绘制刷新;里面是递归调用,以 scr_act 为父对象,遍历它的子对象,进行绘制;
使用了 lv_event_send 来向各个子对象发送 event 事件,包括了:
- LV_EVENT_DRAW_MAIN_BEGIN : 绘制开始
- LV_EVENT_DRAW_MAIN:绘制
- LV_EVENT_DRAW_MAIN_END:绘制结束;
- LV_EVENT_DRAW_POST_BEGIN:绘制 Post 开始;
- LV_EVENT_DRAW_POST:绘制 Post ;
- LV_EVENT_DRAW_POST_END:绘制 Post 结束;
static void lv_refr_obj_and_children(lv_draw_ctx_t * draw_ctx, lv_obj_t * top_obj)
{
/*Normally always will be a top_obj (at least the screen)
*but in special cases (e.g. if the screen has alpha) it won't.
*In this case use the screen directly*/
// 获取 act_scr 赋值给了 top_obj,这里的 top 不是我们图层的那个 top,而是绘制的 scr_act;
if(top_obj == NULL) top_obj = lv_disp_get_scr_act(disp_refr);
if(top_obj == NULL) return; /*Shouldn't happen*/
/*Refresh the top object and its children*/
lv_refr_obj(draw_ctx, top_obj);
/*Draw the 'younger' sibling objects because they can be on top_obj*/
lv_obj_t * parent;
lv_obj_t * border_p = top_obj;
parent = lv_obj_get_parent(top_obj);
/*Do until not reach the screen*/
while(parent != NULL) {
bool go = false;
uint32_t i;
uint32_t child_cnt = lv_obj_get_child_cnt(parent);
for(i = 0; i < child_cnt; i++) {
lv_obj_t * child = parent->spec_attr->children[i];
if(!go) {
if(child == border_p) go = true;
}
else {
/*Refresh the objects*/
lv_refr_obj(draw_ctx, child);
}
}
/*Call the post draw draw function of the parents of the to object*/
lv_event_send(parent, LV_EVENT_DRAW_POST_BEGIN, (void *)draw_ctx);
lv_event_send(parent, LV_EVENT_DRAW_POST, (void *)draw_ctx);
lv_event_send(parent, LV_EVENT_DRAW_POST_END, (void *)draw_ctx);
/*The new border will be the last parents,
*so the 'younger' brothers of parent will be refreshed*/
border_p = parent;
/*Go a level deeper*/
parent = lv_obj_get_parent(parent);
}
}
最后调用 draw_buf_flush -> call_flush_cb 函数,将在 buffer 中绘制好的内容,通过硬件传输,flush 到屏幕;
/**
* Flush the content of the draw buffer
*/
static void draw_buf_flush(lv_disp_t * disp)
{
lv_disp_draw_buf_t * draw_buf = lv_disp_get_draw_buf(disp_refr);
/*Flush the rendered content to the display*/
lv_draw_ctx_t * draw_ctx = disp->driver->draw_ctx;
.........
draw_buf->flushing = 1;
if(disp_refr->driver->draw_buf->last_area && disp_refr->driver->draw_buf->last_part) draw_buf->flushing_last = 1;
else draw_buf->flushing_last = 0;
bool flushing_last = draw_buf->flushing_last;
if(disp->driver->flush_cb) {
/*Rotate the buffer to the display's native orientation if necessary*/
if(disp->driver->rotated != LV_DISP_ROT_NONE && disp->driver->sw_rotate) {
draw_buf_rotate(draw_ctx->buf_area, draw_ctx->buf);
}
else {
call_flush_cb(disp->driver, draw_ctx->buf_area, draw_ctx->buf);
}
}
..........
}
call_flush_cb 进而调用到了 drv->flush_cb 函数,这个是在 display 初始化函数 lv_port_disp_init 的时候指定的,和固定硬件相关的部分:
static void call_flush_cb(lv_disp_drv_t * drv, const lv_area_t * area, lv_color_t * color_p)
{
REFR_TRACE("Calling flush_cb on (%d;%d)(%d;%d) area with %p image pointer", area->x1, area->y1, area->x2, area->y2,
(void *)color_p);
lv_area_t offset_area = {
.x1 = area->x1 + drv->offset_x,
.y1 = area->y1 + drv->offset_y,
.x2 = area->x2 + drv->offset_x,
.y2 = area->y2 + drv->offset_y
};
drv->flush_cb(drv, &offset_area, color_p);
}
void lv_port_disp_init(void)
{
............
? ? static lv_disp_drv_t disp_drv; ? ? ? ? ? ? ? ? ? ? ? ? /*Descriptor of a display driver*/
? ? lv_disp_drv_init(&disp_drv); ? ? ? ? ? ? ? ? ? ?/*Basic initialization*/
? ? /*Set up the functions to access to your display*/
? ? /*Set the resolution of the display*/
? ? disp_drv.hor_res = 128;
? ? disp_drv.ver_res = 128;
? ? /*Used to copy the buffer's content to the display*/
? ? disp_drv.flush_cb = disp_flush;
? ? /*Set a display buffer*/
? ? disp_drv.draw_buf = &draw_buf_dsc_1;
............
}
我的单板使用 STM32F407 + ST7735S 通过 SPI 进行数据传输,所以在 disp_flush 中实现了如下:
static void disp_flush(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p)
{
? ? /*The most simple case (but also the slowest) to put all pixels to the screen one-by-one*/
? ? int32_t x;
? ? int32_t y;
? ? for(y = area->y1; y <= area->y2; y++) {
? ? ? ? for(x = area->x1; x <= area->x2; x++) {
? ? ? ? ? ? /*Put a pixel to the display. For example:*/
? ? ? ? ? ? /*put_px(x, y, *color_p)*/
? ? ? ? ? ? LCD_DrawPoint(x, y, color_p->full);
? ? ? ? ? ? color_p++;
? ? ? ? }
? ? }
? ? /*IMPORTANT!!!
? ? ?*Inform the graphics library that you are ready with the flushing*/
? ? lv_disp_flush_ready(disp_drv);
}
void LCD_DrawPoint(u16 x,u16 y,u16 color)
{
? ? LCD_Address_Set(x,y,x,y);//设置光标位置
? ? LCD_WriteData_16Bit(color);
}
最后,在 _lv_disp_refr_timer 绘制的后面,如果
定义了 LV_USE_PERF_MONITOR 和 LV_USE_LABEL 也就是刷新监控和标签启用计算和刷新CPU利用率、刷新帧率,那么就进行帧率的计算并显示。
小结
总结一下,整个 LVGL Framework 的机制,主要是靠 LVGL 基于 Timer 的任务,主要的任务分为了 Display 显示部分的任务,和用于响应用户输入的 Input 任务;
为了给 LVGL 一个 Tick 心跳,所以需要定期的调用 LVGL 心跳的函数,并传入时间;
绘制的流程如下所示:
lv_timer_handler()
-->30ms _lv_disp_refr_timer()
|--> lv_obj_update_layout(act_scr, prev_scr, top_layer, sys_layer)
| |--> layout_update_core(scr);
| | |--> lv_obj_refr_size(obj);
| | | |--> lv_obj_invalidate(obj);
| | |--> lv_obj_refr_pos(obj);
| | | |--> lv_obj_invalidate(obj);
|--> lv_refr_join_area();
|--> lv_refr_areas();
| |--> lv_refr_area();
| | |--> lv_refr_area_part();
| | | |--> lv_refr_obj_and_children();
| | | |--> draw_buf_flush();
| | | | |--> call_flush_cb();
| | | | | |--> flush_cb(); ---> [ lv_port_disp_init()---disp_flush() ]
|