2.2. 使用指南¶
2.2.1. 代码目录¶
source/artinchip/lvgl-ui
├── lvgl           // lvgl库
├── lv_drivers     // lvgl平台适配
├── base_ui        // base_ui测试用例
├── lv_conf.h      // lvgl配置文件
└── main.c         // lvgl应用入口
2.2.2. LVGL整体流程¶
 
图 2.54 整体流程¶
LVGL框架的运行都是基于LVGL中定义的Timer定时器,系统需要给LVGL一个“心跳”, LVGL才能正常的运转起来,两个关键的函数:
- lv_tick_get(), 获取以ms为单位的tick时间 
- lv_timer_handler(),在while循环中的基于定时器的任务处理,函数lv_task_handler会调用lv_timer_handler, lv_tick_get 决定了lv_timer_handler基于定时器的任务处理的时间的准确性 
其中在文件lv_hal_tick.c中的lv_tick_get的实现代码如下:
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
}
在头文件lv_conf.h中定义了上述函数中的LV_TICK_CUSTOM_SYS_TIME_EXPR
#define LV_TICK_CUSTOM 1
#if LV_TICK_CUSTOM
    #define LV_TICK_CUSTOM_INCLUDE <aic_ui.h>
    #define LV_TICK_CUSTOM_SYS_TIME_EXPR (custom_tick_get()) /*system time in ms*/
#endif   /*LV_TICK_CUSTOM*/
LVGL应用主函数代码如下所示:
#define IMG_CACHE_NUM 10
#if LV_USE_LOG
static void lv_user_log(const char *buf)
{
    printf("%s\n", buf);
}
#endif /* LV_USE_LOG */
int main(void)
{
#if LV_USE_LOG
    lv_log_register_print_cb(lv_user_log);
#endif /* LV_USE_LOG */
    /*LittlevGL init*/
    lv_init();
#if LV_IMG_CACHE_DEF_SIZE == 1
    lv_img_cache_set_size(IMG_CACHE_NUM);
#endif
    aic_dec_create();
    lv_port_disp_init();
    lv_port_indev_init();
    /*Create a Demo*/
#if LV_USE_DEMO_MUSIC == 1
    void lv_demo_music(void);
    lv_demo_music();
#else
    void base_ui_init();
    base_ui_init();
#endif
    /*Handle LitlevGL tasks (tickless mode)*/
    while (1) {
        lv_timer_handler();
        usleep(1000);
    }
    return 0;
}
/*Set in lv_conf.h as `LV_TICK_CUSTOM_SYS_TIME_EXPR`*/
uint32_t custom_tick_get(void)
{
    static uint64_t start_ms = 0;
    if (start_ms == 0) {
        struct timeval tv_start;
        gettimeofday(&tv_start, NULL);
        start_ms = (tv_start.tv_sec * 1000000 + tv_start.tv_usec) / 1000;
    }
    struct timeval tv_now;
    gettimeofday(&tv_now, NULL);
    uint64_t now_ms;
    now_ms = (tv_now.tv_sec * 1000000 + tv_now.tv_usec) / 1000;
    uint32_t time_ms = now_ms - start_ms;
    return time_ms;
}
- 其中在函数lv_port_disp_init()中实现显示接口的对接以及硬件2D加速的对接 
- 在函数lv_port_indev_init()中实现触摸屏的对接 
- 函数aic_dec_create()注册硬件解码器 
- 用户只需替换base_ui_init()的实现来对接自己的应用 
2.2.3. LVGL层次结构¶
 
图 2.55 层次结构¶
- LVGL的display是对显示驱动的封装和抽象 
- display包含Active Screen、Top layer、System layer 
- Active Screen、Top layer、System layer是不同的screen对象,这里的screen用layer表达更准确一点, 表示的是图层的概念,其中Active Screen在最底层,System layer在最顶层 
- 一般在Active Screen实现不同的app界面,用户可以创建多个screen,但只能有一个screen设置为Active Screen 
- Top layer在Active Screen之上,可以用来创建弹出窗口,Top layer永远在Active Screen之上 
- System layer在最顶层,比如鼠标可以在System layer,永远不会被遮挡 
 
图 2.56 图层叠加¶
2.2.4. 父子结构¶
LVGL是面向对象的基于父子结构的设计,每一个对象都包含一个父对象(screen对象除外), 但是一个父对象可以包含任意数量的子对象。
/*
 * 创建对象的时候,需要传入父对象的指针,
 * 如果父对象对NULL, 表示创建的是screen对象
 */
lv_obj_create(NULL);
2.2.5. 显示对接¶
主要包括三部分:
- 绘制buffer初始化 
- flush_cb对接 
- 2D硬件加速对接 
2.2.5.1. 绘制buffer初始化¶
绘制buffer初始化函数如下:
void lv_disp_draw_buf_init(lv_disp_draw_buf_t * draw_buf, void * buf1, void * buf2, uint32_t size_in_px_cnt)
- buf1:当为单缓冲或多缓冲的时候,都要设置此buffer 
- buf2:当选择双缓冲的时候,需要配置此buffer,单缓冲不需要 
- size_in_px_cnt: 以像素为单位的buf大小 
 
图 2.59 双缓冲¶
2.2.5.2. flush_cb对接¶
flush_cb回调函数的处理流程,我们以双缓冲为例进行说明,绘制模式有full_refresh和direct_mode两种:
- 全刷新模式,每一帧都刷新整个显示屏 
 
图 2.60 全刷新模式¶
在虚线框中为flush_cb中处理部分,在全刷新的流程中,直接通过 pan_display接口送当前绘制buffer到显示,然后等待vsync中断, 等到中断后,当前的绘制buffer就真正的在显示屏中显示出来,然后调用lv_disp_flush_ready通知LVGL框架已经flush结束, 最后在LVGL框架中会进行绘制buffer的交换。
- 局部刷新,每一帧只刷新需要更新的无效区域(可以有多个无效区域) 
 
图 2.61 无效区域¶
 
图 2.62 局部刷新模式¶
上图中的示例,为了方便描述每一帧都有两个无效区域(invalid area0 和invalid area1),LVGL可以支持更多的无效区域,到了最后一个无效区域, 说明当前帧的数据已经处理完,才把绘制buffer送显示,然后进行buffer交换
flush_cb的实现代码fbdev_flush如下:
static void fbdev_flush(lv_disp_drv_t * drv, const lv_area_t * area, lv_color_t *color_p)
{
    lv_disp_t * disp = _lv_refr_get_disp_refreshing();
    lv_disp_draw_buf_t * draw_buf = lv_disp_get_draw_buf(disp);
    if (!disp->driver->direct_mode || draw_buf->flushing_last) {
        struct fb_var_screeninfo var = {0};
        if (ioctl(g_fb, FBIOGET_VSCREENINFO, &var) < 0) {
            LV_LOG_WARN("ioctl FBIOGET_VSCREENINFO");
            return;
        }
        if (color_p == (lv_color_t *)g_frame_buf[0]) {
            var.xoffset = 0;
            var.yoffset = 0;
        } else {
            var.xoffset = 0;
            var.yoffset = disp_drv.ver_res;
        }
        if (ioctl(g_fb, FBIOPAN_DISPLAY, &var) == 0) {
            int zero = 0;
            if (ioctl(g_fb, AICFB_WAIT_FOR_VSYNC, &zero) < 0) {
                LV_LOG_WARN("ioctl AICFB_WAIT_FOR_VSYNC fail");
                return;
            }
        } else {
            LV_LOG_WARN("pan display err");
        }
        if (drv->direct_mode == 1) {
            for (int i = 0; i < disp->inv_p; i++) {
                if (disp->inv_area_joined[i] == 0) {
                    sync_disp_buf(drv, color_p, &disp->inv_areas[i]);
                }
            }
        }
        lv_disp_flush_ready(drv);
    } else {
        lv_disp_flush_ready(drv);
    }
}
2.2.5.3. 2D硬件加速对接¶
2D加速主要对接 lv_draw_ctx_t中的绘制函数
| 成员 | 说明 | 是否硬件加速 | 
|---|---|---|
| void *buf | 当前要绘制的buffer | - | 
| const lv_area_t * clip_area | 绘制区域裁剪(以屏幕为参考的绝对坐标) | - | 
| void (*draw_rect)() | 绘制矩形(包括圆角、阴影、渐变等) | 否 | 
| void (*draw_arc)() | 绘制弧形 | 否 | 
| void (*draw_img_decoded)() | 绘制已经解码后的图像 | 是 | 
| lv_res_t (*draw_img)() | 绘制图像(包括图片解码) | 是 | 
| void (*draw_letter)() | 绘制文字 | 否 | 
| void (*draw_line)() | 绘制直线 | 否 | 
| void (*draw_polygon)() | 绘制多边形 | 否 | 
在lv_draw_aic_ctx_t(重定义了lv_draw_sw_ctx_t)结构体中包含lv_draw_ctx_t和blend函数:
typedef struct {
    lv_draw_ctx_t base_draw;
    /** Fill an area of the destination buffer with a color*/
    void (*blend)(lv_draw_ctx_t * draw_ctx, const lv_draw_sw_blend_dsc_t * dsc);
} lv_draw_sw_ctx_t;
在draw_rect、draw_line等操作的功能由多个步骤组成,虽然我们没有对这些接口进行硬件加速,但是这些操作的部分实现 会调用到blend,我们对blend接口进行了硬件加速对接:
void lv_draw_aic_ctx_init(lv_disp_drv_t * drv, lv_draw_ctx_t * draw_ctx)
{
    lv_draw_sw_init_ctx(drv, draw_ctx);
    lv_draw_aic_ctx_t * aic_draw_ctx = (lv_draw_aic_ctx_t *)draw_ctx;
    aic_draw_ctx->blend = lv_draw_aic_blend;
    aic_draw_ctx->base_draw.draw_img = lv_draw_aic_draw_img;
    aic_draw_ctx->base_draw.draw_img_decoded = lv_draw_aic_img_decoded;
    return;
}
先调用lv_draw_sw_init_ctx函数把所有绘制操作都初始化为软件实现,然后对可以硬件加速的接口重新实现, 覆盖原来的软件实现。
2.2.5.4. 显示驱动注册¶
所有的显示相关功能都包含在lv_disp_drv_t结构体中:
- 通过lv_disp_drv_init来初始化lv_disp_drv_t结构体 
- 通过lv_disp_draw_buf_init初始化绘制buffer 
- 通过回调flush_cb来注册显示接口 
- 通过lv_draw_aic_ctx_init来注册2D硬件加速相关接口 
- 通过lv_disp_drv_register来注册lv_disp_drv_t 
在源文件lv_port_disp.c中的函数lv_port_disp_init配置刷新模式,局部刷新模式配置如下:
disp_drv.full_refresh = 0;
disp_drv.direct_mode = 1;
全刷新模式参数配置如下:
disp_drv.full_refresh = 1;
disp_drv.direct_mode = 0;
2.2.6. 硬件解码对接¶
2.2.6.1. lv_img_decoder_t注册¶
我们通过lv_img_decoder_t来注册硬件解码器接口,主要实现了三个接口:
| 函数 | 说明 | 
|---|---|
| aic_decoder_info | 获取图片宽、高、图片格式信息 | 
| aic_decoder_open | 申请解码输出buffer,硬件解码输出 | 
| aic_decoder_close | 释放硬件解码资源(包括输出buffer) | 
注册解码器过程;
void aic_dec_create()
{
    lv_img_decoder_t *aic_dec = lv_img_decoder_create();
    /* init frame info lists */
    mpp_list_init(&buf_list);
    lv_img_decoder_set_info_cb(aic_dec, aic_decoder_info);
    lv_img_decoder_set_open_cb(aic_dec, aic_decoder_open);
    lv_img_decoder_set_close_cb(aic_dec, aic_decoder_close);
}
绘制函数draw_img_decoded需要的解码后数据,需要通过注册解码器回调去获取, 这是我们默认的图片处理流程:
 
图 2.63 draw_img_decoded¶
- 采用此流程需要额外申请一块解码buffer,占用内存增加 
- 缓存解码后的buffer,下次再显示同样的image,不用重复解码,加快UI加载速度 
当绘制函数为draw_img的时候,硬件解码在函数draw_img内部,无需注册解码回调函数,我们默认不采用此方法, 当在内存受限的场景下,可以评估此方法是否可满足场景需求。
 
图 2.64 draw_img¶
- 采用此流程无需额外申请解码buffer,直接解码到绘制buffer 
- 当需要进行alpha blending的时候,此方法不可行 
- 每次都要重新对image解码,速度不如draw_img_decoded 
- 当硬件解码不支持裁剪的时进行局部更新,此方法不可行 
2.2.6.2. 图片cache机制¶
- 采用lv_img_decoder_t提供的接口注册的解码器可以采用LVGL内部的图片缓冲机制, 在lv_conf.h 中宏定义LV_IMG_CACHE_DEF_SIZE为1的时候,表示打开图片缓冲机制, 当LV_IMG_CACHE_DEF_SIZE为0的时候,图片缓冲机制关闭。 
- 通过void lv_img_cache_set_size(uint16_t entry_cnt)来设置缓冲的图片张数,图片以张数为单位进行缓存。 
- 当图片缓存到设置的最大张数的时候,如果需要新的缓存,图片缓存机制内部会进行图片缓存价值的判断, 例如:如果某一张图片解码的时间比较久,或者某一张图片使用的更频繁,那么这种图片的缓存价值打分会更高, 优先缓存这些缓存价值更高的图片。 
如果一些图片的读取时间或者解码时间比较长,采用图片缓存机制可以提升UI流畅性
2.2.7. LVGL库中demos使用¶
在目录luban/source/artinchip/lvgl-ui/lvgl/demos下lvgl官方提供了多个示例demo
- 在lv_conf.h宏定义中打开#define LV_USE_DEMO_MUSIC 1, 则main.c中会调用相应的demo 
    /*Create a Demo*/
#if LV_USE_DEMO_MUSIC == 1
    void lv_demo_music(void);
    lv_demo_music();
#else
    void base_ui_init();
    base_ui_init();
#endif
- 如果要调用lvgl-ui/lvgl/demos下的benchmark,则需关闭LV_USE_DEMO_MUSIC, 打开宏定义#define LV_USE_DEMO_BENCHMARK 1, 修改main.c中的base_ui_init()为需要的demo入口函数即可, 如下所示: - /*Create a Demo*/ #if LV_USE_DEMO_MUSIC == 1 void lv_demo_music(void); lv_demo_music(); #else //void base_ui_init(); //base_ui_init(); void lv_demo_benchmark(); lv_demo_benchmark(); #endif 
2.2.8. LVGL库中samples使用¶
官方LVGL库lvgl-ui/lvgl/examples目录下是各种控件的测试用例, 以调用get_started目录下的lv_example_get_started_1.c为例进行流程说明:
- samples相应的宏定义在lv_conf.h中已经默认打开:#define LV_BUILD_EXAMPLES 1 
- 修改main.c中的入口函数: - /*Create a Demo*/ #if LV_USE_DEMO_MUSIC == 1 void lv_demo_music(void); lv_demo_music(); #else //void base_ui_init(); //base_ui_init(); void lv_example_get_started_1(); lv_example_get_started_1(); #endif 
2.2.9. 第三方库支持¶
- freetype库支持 - 选择freetype包,在 Luban根目录下执行 - make menuconfig,进入 menuconfig
 - ArtInChip Luban SDK Configuration ---> Third-party packages ---> [*] freetype --->- 目录lvgl-ui/lvgl/examples/libs/freetype/lv_example_freetype_1.c下示例调用 - 在lv_conf.h头文件中打开宏定义 - #define LV_USE_FREETYPE 1 #define LV_BUILD_EXAMPLES 1 
- 修改main.c中的入口函数: - /*Create a Demo*/ #if LV_USE_DEMO_MUSIC == 1 void lv_demo_music(void); lv_demo_music(); #else //void base_ui_init(); //base_ui_init(); void lv_example_freetype_1(void); lv_example_freetype_1(); #endif 
- 设置lvgl-ui/lvgl/examples/libs/freetype/Lato-Regular.ttf字体的打包目录, 复制字体到lvgl-ui/base_ui/asserts/font目录下,则会把字体打包到系统目录/usr/local/share/lvgl_data/font目录下 
- 修改代码lv_example_freetype_1.c中字体文件路径 - void lv_example_freetype_1(void) { /*Create a font*/ static lv_ft_info_t info; /*FreeType uses C standard file system, so no driver letter is required.*/ //info.name = "./lvgl/examples/libs/freetype/Lato-Regular.ttf"; info.name = "/usr/local/share/lvgl_data/font/Lato-Regular.ttf"; info.weight = 24; info.style = FT_FONT_STYLE_NORMAL; info.mem = NULL; if(!lv_ft_font_init(&info)) { LV_LOG_ERROR("create failed."); } /*Create style with the new font*/ static lv_style_t style; lv_style_init(&style); lv_style_set_text_font(&style, info.font); lv_style_set_text_align(&style, LV_TEXT_ALIGN_CENTER); /*Create a label with the new style*/ lv_obj_t * label = lv_label_create(lv_scr_act()); lv_obj_add_style(label, &style, 0); lv_label_set_text(label, "Hello world\nI'm a font created with FreeType"); lv_obj_center(label); } 
 
 
