设计说明
luban-lite/packages/artinchip/lvgl-ui ├── aic_demo // aic lvgl demo ├── lv_driver // lvgl 显示和 2D 加速对接 ├── lvgl // lvgl 库 ├── aic_ui.c // aic demo 入口 ├── aic_ui.h // aic demo 头文件 ├── lv_conf.h // lvgl 配置文件 ├── lv_demo.c // lvgl demo 入口函数 └── SConscript
LVGL 整体流程
-
lv_tick_get():获取以毫秒为单位的 tick 时间。
-
lv_timer_handler():在 while 循环中的基于定时器的任务处理
函数 lv_task_handler() 会调用 lv_timer_handler()。lv_tick_get() 决定了 lv_timer_handler() 基于定时器的任务处理的时间的准确性。

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
}#define LV_TICK_CUSTOM 1 #define LV_TICK_CUSTOM_INCLUDE LV_RTTHREAD_INCLUDE #define LV_TICK_CUSTOM_SYS_TIME_EXPR (rt_tick_get_millisecond()) /*Expression evaluating to current system time in ms*/
static void lvgl_thread_entry(void *parameter)
{
#if LV_USE_LOG
lv_log_register_print_cb(lv_rt_log);
#endif /* LV_USE_LOG */
lv_init();
lv_port_disp_init();
lv_port_indev_init();
lv_user_gui_init();
/* handle the tasks of LVGL */
while(1)
{
lv_task_handler();
rt_thread_mdelay(SLEEP_PERIOD);
}
}
int lvgl_thread_init(void)
{
rt_err_t err;
err = rt_thread_init(&lvgl_thread, "LVGL", lvgl_thread_entry, RT_NULL,
&lvgl_thread_stack[0], sizeof(lvgl_thread_stack), LPKG_LVGL_THREAD_PRIO, 0);
if(err != RT_EOK)
{
LOG_E("Failed to create LVGL thread");
return -1;
}
rt_thread_startup(&lvgl_thread);
return 0;
}-
lv_port_disp_init():实现显示接口的对接以及硬件 2D 加速的对接
-
lv_port_indev_init():实现触摸屏的对接
-
lv_user_gui_init():适配不同 UI 界面的初始化
LVGL 层次结构

-
Display:显示设备,是对显示驱动的封装和抽象。
-
Screen:屏幕对象,表示不同的界面。
-
Layer:图层,表示屏幕上的不同绘制区域。
-
一个 display 包含多个 screen,每个 screen 又包含多个 layer。layer 按照层级关系叠加在一起,形成最终的显示效果。具体来说:
-
Active Screen:当前活动的屏幕,承载主要的应用程序界面,是所有操作的主要界面,处于最底层。
一般在 Active Screen 实现不同的 app 界面,用户可以创建多个 screen,但只能有一个 screen 设置为 Active Screen
-
Top layer:位于 Active Screen 之上的图层,通常用来创建弹出窗口。
Top layer 永远在 Active Screen 之上。
-
System layer:位于最顶层,通常用于显示系统级别的信息,比如鼠标可以在 System layer,永远不会被遮挡
-

父子结构
- 每一个对象都包含一个父对象,screen 对象除外。
- 一个父对象可以包含任意数量的子对象。
- 创建对象时需要传入父对象的指针,如果父对象为 NULL,则表示创建的是 screen
对象。
lv_obj_create(NULL);
- 父对象和子对象一起移动。父子对象一起移动

图 4. 父子对象移动 - 子对象超出父对象部分不可见。

图 5. 子对象可见区域
FreeType 库支持
如需在 LVGL 中使用 FreeType 字体,执行以下配置流程:
- 使用 scons --menuconfig 或 me 进入配置菜单,选中
freetype 包:
Local packages options ---> Third-party packages options ---> [*] freetype --- - 打开头文件 luban-lite/packages/artinchip/lvgl-ui/lv_conf.h ,并定义宏
LV_USE_FREETYPE:
#define LV_USE_FREETYPE 1
- 编写 FreeType 字体调用代码,并将其添加在项目中。FreeType 字体调用示例如下:
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 = "/rodata/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); // 添加字库 style 到 label 控件 lv_label_set_text(label, "Hello world\nI'm a font created with FreeType"); lv_obj_center(label); }
- 编译项目。
确保所有依赖项都已正确安装。编译完成后,可在屏幕上看到使用 FreeType 字体渲染的文本。
LVGL Demo
- lvgl-demo.html#lvgl-demo__section_fyy_jjh_fdc:LVGL 的基础演示示例,展示了如何使用 LVGL 创建基本的
UI 元素,通常用于展示 LVGL 的基本功能和使用方法,包括:
- 创建按钮并响应点击事件。
- 显示文本标签。
- 使用滑块调整数值。
- 简单的动画效果。
- lvgl-demo.html#lvgl-demo__section_xgg_ljh_fdc:演示如何使用 LVGL 创建仪表盘 (meter),
常用于需要显示数据变化的场景,如速度表、温度计等。功能包括:
- 创建仪表盘并添加指针。
- 动态更新指针位置。
- 显示刻度和标签。
- 支持多种样式和颜色。
- Widgets demo:展示 LVGL 提供的各种 UI 控件,可以帮助开发者了解如何使用不同的控件来构建复杂的用户界面。UI 控件包括:
- 列表视图。
- 下拉菜单。
- 复选框和单选按钮。
- 开关按钮。
- 日期选择器。
- Benchmark demo:展示 LVGL 在不同硬件平台上的表现,用于测试和评估 LVGL 的性能,包括渲染速度、内存占用指标以及帧率统计。
本节主要介绍 lvgl-demo.html#lvgl-demo__section_fyy_jjh_fdc 和 lvgl-demo.html#lvgl-demo__section_xgg_ljh_fdc。
Base Demo

- 每个页面都可以通过滑动操作切换。页面滑动使用了 tabview 控件,每个页面都是一个 tab:
lv_obj_set_size(main_tabview, 1024, 600); lv_obj_set_pos(main_tabview, 0, 0); lv_obj_set_style_bg_opa(main_tabview, LV_OPA_0, 0); lv_obj_t *main_tab0 = lv_tabview_add_tab(main_tabview, "main page 0"); lv_obj_t *main_tab1 = lv_tabview_add_tab(main_tabview, "main page 1"); lv_obj_set_style_bg_opa(main_tab0, LV_OPA_0, 0); lv_obj_set_style_bg_opa(main_tab1, LV_OPA_0, 0); lv_obj_set_size(main_tab0, 1024, 600); lv_obj_set_size(main_tab1, 1024, 600); lv_obj_set_pos(main_tab0, 0, 0); lv_obj_set_pos(main_tab1, 0, 0);
- 背景图片通过 image 控件来创建,是一个名字为 global_bg 的 PNG 图片。此图片会采用注册的硬件解码器进行解码:
static lv_obj_t *img_bg = NULL; img_bg = lv_img_create(lv_scr_act()); lv_img_set_src(img_bg, LVGL_PATH(global_bg)); lv_obj_set_pos(img_bg, 0, 0);
- 菜单图片也通过 image 控件来创建,也是一个 PNG 图片,且此图片也会采用注册的硬件解码器进行解码:
lv_obj_t *sub_image00 = lv_img_create(sub_tab0); lv_img_set_src(sub_image00, LVGL_PATH(cook_0.jpg)); lv_obj_set_pos(sub_image00, 36, 100);
- Fake imageFake image 不是一个真实的图片,通过此方式可以方便的对一个矩形区域进行填充,包括 alpha、red、green、blue:
static lv_obj_t *img_bg = NULL; FAKE_IMAGE_DECLARE(bg_dark) // 声明 (bg_dark 名字可修改) /* 最后一个参数为要设置的颜色值:bit31:24 为 alpha */ FAKE_IMAGE_INIT(bg_dark, 1024, 600, 0, 0x00000000); lv_img_set_src(img_bg, FAKE_IMAGE_NAME(bg_dark)); // 设置 fake image 数据源
- Build-in image 是通过数组变量在程序中表示图像。图片转换成 .c 文件的工具,参考官网:http://lvgl.io/tools/imageconverter
uint8_t circle_white_map[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x50, 0xff, 0x7f, 0xff, ........................................................}; const lv_img_dsc_t circle_white = { .header.cf = LV_IMG_CF_TRUE_COLOR_ALPHA, .header.always_zero = 0, .header.reserved = 0, .header.w = 20, .header.h = 20, .data_size = 400 * LV_IMG_PX_SIZE_ALPHA_BYTE, .data = circle_white_map, }; static lv_obj_t * circle_0 = lv_img_create(img_bg); lv_img_set_src(circle_0, &circle_white); lv_obj_align(circle_0, LV_ALIGN_BOTTOM_MID, -16, -28);
Meter Demo
Meter demo 演示了硬件旋转,以及仪表盘的设计参考。

Meter demo 的详细说明及特点如下:
- 各种控件的动作通过 timer 来实现,每间隔一定的时间执行相应的回调函数:
lv_timer_create(point_callback, 10, 0); lv_timer_create(fps_callback, 1000, 0); lv_timer_create(speed_callback, 100, 0); lv_timer_create(time_callback, 1000 * 60, 0);
- 指针和光影通过贴图和硬件任意角度旋转来实现。其中,前 74 张指针通过切换 74 张图片来实现, 从第 75 张开始的红色指针,采用硬件任意角度旋转来实现:
static void point_callback(lv_timer_t *tmr) { char data_str[64]; (void)tmr; static bool first = true; static int id = 1; static int direct = 0; static int mode_id = 0; static int mode_num = sizeof(rot_mode_list) / sizeof(rot_mode_list[0]); static int start_id = 0; static int end_id = 0; if (first) { first = false; start_id = rot_mode_list[mode_id].start_id; end_id = rot_mode_list[mode_id].end_id; } direct = start_id < end_id ? 0: 1; if (id < 75) { lv_img_set_src(img_circle, LVGL_PATH(bg/small_blue)); lv_obj_clear_flag(img_circle, LV_OBJ_FLAG_HIDDEN); } else { lv_obj_add_flag(img_circle, LV_OBJ_FLAG_HIDDEN); } if (id < 75) { sprintf(data_str, "%spoint/point_%05d", LVGL_DIR, id); lv_img_set_src(img_point, data_str); lv_img_set_angle(img_point, 0); } else { // id to angle float rot_angle = ((float)(id - 75) * 2 * 10) * 0.84; sprintf(data_str, "%spoint/point_%05d", LVGL_DIR, 75); lv_img_set_src(img_point, data_str); lv_img_set_pivot(img_point, 210, 210); lv_img_set_angle(img_point, (int16_t)rot_angle); } if (direct == 0) { id++; } else { id--; } if ((!direct && (id > end_id) ) || (direct && (id < end_id))) { id = end_id; mode_id++; mode_id %= mode_num; start_id = rot_mode_list[mode_id].start_id; end_id = rot_mode_list[mode_id].end_id; } return; } - UI 设计方案比较。
针对本 demo 场景,有以下 UI 设计方案可供选择。其中 UI 设计方案 2 更高效,实现同样的界面效果,简化流程速度可以提升一倍以上:
- UI 设计方案 1

图 8. UI 设计方案 1 示例 -
准备光条、指针、光圈、底图四张图片。
-
按照先后顺序,依次对光圈和背景、光条和背景以及指针和背景进行 alpha blending。
-
每一个角度的旋转都需要进行三次 alpha blending。
-
- UI 设计方案 2

图 9. UI 设计方案 2 示例 -
光条和指针合并为一张图片。
-
光圈合并到背景图中。
-
准备光条和指针图片和底图。
-
每一个角度的旋转都只需要对光条和指针图片与背景图进行一次 alpha blending。
-
- UI 设计方案 1
