使用 LVGL 设计应用程序
LVGL 简介
LVGL(Light and Versatile Graphics Library)是最受欢迎的免费开源嵌入式图形库,可为任何 MCU、MPU 和显示类型创建漂亮的用户界面,提供了一切你需要创建嵌入式 GUI 所需的功能,包括易于使用的图形元素、美观的视觉效果和低内存占用。
LVGL 在其官网上展示了 Demo 效果以体现 LVGL 的 UI 构建能力。在线文档是 LVGL 的主要开发资料,其中详细介绍了 LVGL 的设计和运行逻辑、各个控件的使用方法、丰富的示例程序以及移植方法等。无论是新手还是有经验的开发者,都可以基于在线在线文档快速上手并深入理解LVGL的功能和特性。
HoneyGUI 模拟器
模拟器是开发 UI 时使用的一个强大的工具,用于在计算机上模拟嵌入式设备的UI界面。它可以模拟真实硬件平台的行为和外观,提供给开发人员一个便捷的环境来快速创建、调试和测试UI设计。
模拟器的主要作用是实时展示和交互测试设计的UI界面,从而减少在实际硬件上进行反复测试的时间和成本。通过使用模拟器,开发人员可以快速迭代设计,实时查看效果,并进行调试和验证。这大大加快了UI的开发速度和质量,并提高了工作效率。
使用模拟器有以下优点:
实时预览:模拟器可以即时显示UI界面的效果,使开发人员能够快速看到设计的外观和功能效果,方便进行调整和修改。
跨平台支持:模拟器可以在计算机上运行,开发人员不需要依赖具体的硬件平台。
节省时间和资源:使用模拟器可以避免在实际硬件上频繁烧录和测试 UI,减少了额外的时间和成本开销。
调试和测试:模拟器提供了丰富的调试和测试功能,可以检查 UI 元素的交互、事件处理和布局效果,有助于解决问题和优化性能。
在 HoneyGUI 模拟器中运行 LVGL
HoneyGUI 模拟器基于 scons 工具 和 MinGW-w64 工具链,在 VScode 中运行和进行调试,具体的环境配置和启动运行请参考 入门指南 章节。
完成 HoneyGUI 模拟器的环境安装后,启动运行将看到模拟器默认的 HoneyGUI 工程。修改模拟器配置文件以运行 LVGL 的工程,在路径 your HoneyGUI dir/win32_sim/
下的 menu_config.h
文件为模拟器的配置文件,在 HoneyGUI Demo Select 下注释掉所有的 Demo,在 HoneyGUI Enable LVGL 下使能 CONFIG_REALTEK_BUILD_LVGL_GUI
。在 VScode 中再次启动运行,构建编译通过后即可看到 LVGL 默认的 Demo 工程运行。

1. 当需要修改屏幕尺寸时,修改文件 your HoneyGUI dir/realgui/example/demo/
下的 SConscript
文件,修改其中的屏幕宽度 DRV_LCD_WIDTH
和 屏幕高度 DRV_LCD_HIGHT
,均为像素单位。

HoneyGUI LVGL
以下为 HoneyGUI 中与 LVGL 相关的目录及文件:
HoneyGUI Dir
|-- Arm2D
|-- cmake
|-- doc
|-- realgui
| |-- 3rd
| |-- app
| |-- core
| |-- dc
| |-- engine
| :
| |__ example
| |-- BAK
| |-- demo
| | |__ app_ui_lvgl.c // 模拟器 LVGL UI 入口
| :
| :
| |__ screen_lvgl
| |-- assets // LVGL 用户图片和字库 C 文件
| | |__ lvgl_example_assets.c // assets example
| |
| |-- root // 文件系统根目录
| |-- _bin_mkromfs.py
| |-- mkromfs_0x4600000.bat // User Data 打包脚本
| |-- resource.h // 打包的文件资源地址映射
| |__ root(0x4600000).bin // 打包的 User Data
|
|-- keil_sim
|-- lib
|-- lvgl_v8 // LVGL v8.3
| |-- demos // LVGL demo 源文件
| | |-- benchmark
| | |-- keypad_encoder
| | |-- music
| | |-- stress
| | |__ widgets
| |
| |-- docs
| |-- env_support
| |-- examples // LVGL example 源文件
| | |-- anim
| | |-- arduino
| | |-- assets
| | |-- event
| | |-- get_started
| | |-- layouts
| | |-- libs
| | |-- others
| | |-- porting // LVGL porting 模板
| | |-- scroll
| | |-- styles
| | |__ widgets // LVGL example 控件源文件,包含各控件 example
| |
| |-- rlottie
| |-- scripts
| |-- src
| | :
| | |-- widgets
| | |__ font // LVGL 内置字库
| |
| |__ tests
|
|-- lvgl_v9 // LVGL v9
|
:
:
|__ win32_sim
:
|__ port // 模拟器 porting
|-- realgui_port // 模拟器 HoneyGUI porting
|-- lvgl_port // 模拟器 LVGLv8 porting
| |-- lv_conf.h // 模拟器 LVGL 配置定义
| |-- lv_port_disp.c
| |-- lv_port_disp.h
| |-- lv_port_fs.c
| |-- lv_port_fs.h
| |-- lv_port_indev.c
| |__ lv_port_indev.h
|
|__ lvglv9_port // 模拟器 LVGLv9 porting
HoneyGUI 中 LVGL 源文件在目录
your HoneyGUI dir/lvgl
下:
demos:存放 LVGL 一些综合的内置示例,部分示例可以在 LVGL Demo 中体验。
docs:存放 LVGL 的开发文档,可在 LVGL 的文档站点在线阅读:LVGL Document 。
env_support:一些环境或者平台的支持。
examples:存放 LVGL 的内置示例,可在 LVGL Example 中体验。
scripts:存放一些处理脚本,在使用 LVGL 时基本不会用到。
src:存放 LVGL 实际的源码,使用 LVGL 进行开发时,都是使用这里面的代码文件。
tests:存放一些 CI 测试文件,在使用 LVGL 时不会用到。
HoneyGUI 模拟器运行 LVGL 时,LVGL UI 将从目录
your HoneyGUI dir/realgui/example/demo
下的app_ui_lvgl.c
开始运行。使用 HoneyGUI 模拟器运行 LVGL 时,调用的 LVGL 文件系统接口所指向的根目录为
your HoneyGUI dir/realgui/example/screen_lvgl/root/
。
实机移植
文档说明: LVGL Porting
LVGL 提供了广泛的移植支持,使开发者可以将其轻松地集成到各种嵌入式系统和平台中。它支持各种显示设备的驱动、触摸屏、输入设备和自定义 GPU 等。开发者可以根据项目的需求进行移植配置,例如更换显示设备时调整显示参数,替换输入设备时适配输入接口等。本文以显示设备、输入设备和文件系统为例,介绍移植过程和方法,更多细节请参考 LVGL Porting。
备注
以下示例不包含硬件设备驱动的具体实现,仅示例如何将驱动对接到 LVGL 的接口。开发者在实现硬件设备驱动时,可在与示例驱动一致的 api 框架下来完成驱动功能,以对接到 HoneyGUI driver 层接口,往上则可复用示例工程的 porting 接口。
显示
在开发者完成显示设备的驱动功能调试后,设备能够与显示设备正常通信并显示色彩。本小节介绍如何将驱动与 LVGL 的显示接口进行对接以展现 LVGL 的 UI 界面。
LVGL 的显示接口在文件 lv_port_disp.c
中实现,显示参数在初始化函数 void lv_port_disp_init(void)()
中进行配置,如屏幕尺寸和 frame buffer 配置准备等,显示刷新函数为 void disp_flush(lv_disp_drv_t *disp_drv, const lv_area_t *area, lv_color_t *color_p)()
。
文件 lv_port_disp.c
中已配置好不同的绘制和推屏方式供参考,配置 DISPLAY_FLUSH_TYPE
以切换模式,其中 RAMLESS_XXX
适用于不带有 RAM 的 display IC, RAM_XXX
适用于带有 RAM 的 display IC,XXX_FULL_SCREEN_XXX
表示为每次整屏推出,XXX_TWO_SEC
表示为只绘制变化的显示内容,单位为两个 buffer 大小,buffer 的像素高度由 SECTION_HEIGHT
定义。
详尽的显示设备移植方法和注意事项请参阅文档 LVGL Porting Display,以下代码段示例了 porting 不带有 RAM 的 display IC:
使用不带有 RAM 的 display IC 时,必须为其分配整屏尺寸的 frame buffer,因此在 PSRAM 上分配了两个整屏尺寸的 frame buffer 用于显示。显示的参数宏定义已定义在文件
lv_conf.h
中。若使用的 display IC 带有 RAM,则 frame buffer 的大小不必为整屏尺寸。由于刷屏方式的不同,需要配置
lv_port_disp.c
中的LVGL_USE_EDPI
为不启用(0),以切换disp_flush()
函数适配刷屏。
// flush func 1
#define RAMLESS_TWO_FULL_SCREEN 0 // double buffer, full refresh
// flush func 2
#define RAM_TWO_FULL_SCREEN_NO_SEC 1 // double buffer, full refresh
#define RAM_ONE_FULL_SCREEN_TWO_SEC 2 // two buffer
#define RAM_DIRECT_TWO_SEC 3 // two buffer
// two buffer: section height
#define SECTION_HEIGHT 40
#define DISPLAY_FLUSH_TYPE RAMLESS_TWO_FULL_SCREEN
#if (DISPLAY_FLUSH_TYPE == RAMLESS_TWO_FULL_SCREEN)
#define LVGL_USE_EDPI 1
#else
#define LVGL_USE_EDPI 0
#endif
// frame buffer config
#define LV_PORT_BUF1 (uint32_t)0x08000000 // address in PSRAM
#define LV_PORT_BUF2 (uint32_t)(0x08000000 + MY_DISP_HOR_RES * MY_DISP_VER_RES * LV_COLOR_DEPTH / 8)
void lv_port_disp_init(void)
{
/*-------------------------
* Initialize your display
* -----------------------*/
disp_init();
/*-----------------------------------
* Register the display in LVGL
*----------------------------------*/
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 = MY_DISP_HOR_RES;
disp_drv.ver_res = MY_DISP_VER_RES;
/*Used to copy the buffer's content to the display*/
disp_drv.flush_cb = disp_flush;
/*-----------------------------
* Create a buffer for drawing
*----------------------------*/
/**
* LVGL requires a buffer where it internally draws the widgets.
* Later this buffer will passed to your display driver's `flush_cb` to copy its content to your display.
* The buffer has to be greater than 1 display row
*
* There are 3 buffering configurations:
* 1. Create ONE buffer:
* LVGL will draw the display's content here and writes it to your display
*
* 2. Create TWO buffer:
* LVGL will draw the display's content to a buffer and writes it your display.
* You should use DMA to write the buffer's content to the display.
* It will enable LVGL to draw the next part of the screen to the other buffer while
* the data is being sent form the first buffer. It makes rendering and flushing parallel.
*
* 3. Double buffering
* Set 2 screens sized buffers and set disp_drv.full_refresh = 1.
* This way LVGL will always provide the whole rendered screen in `flush_cb`
* and you only need to change the frame buffer's address.
*/
#if (DISPLAY_FLUSH_TYPE == RAMLESS_TWO_FULL_SCREEN || DISPLAY_FLUSH_TYPE == RAM_TWO_FULL_SCREEN_NO_SEC)
static lv_disp_draw_buf_t draw_buf_dsc_3;
lv_color_t *buf_3_1 = (lv_color_t *)LV_PORT_BUF1; /*A screen sized buffer*/
lv_color_t *buf_3_2 = (lv_color_t *)LV_PORT_BUF2; /*Another screen sized buffer*/
lv_disp_draw_buf_init(&draw_buf_dsc_3, buf_3_1, buf_3_2,
MY_DISP_VER_RES * MY_DISP_HOR_RES); /*Initialize the display buffer*/
/*Set a display buffer*/
disp_drv.draw_buf = &draw_buf_dsc_3;
/*Required for Example 3)*/
disp_drv.full_refresh = 1;
#elif (DISPLAY_FLUSH_TYPE == RAM_DIRECT_TWO_SEC || DISPLAY_FLUSH_TYPE == RAM_ONE_FULL_SCREEN_TWO_SEC)
#if 1
static uint8_t __attribute__((aligned(4))) disp_buff1[MY_DISP_HOR_RES * SECTION_HEIGHT *
LV_COLOR_DEPTH / 8];
static uint8_t __attribute__((aligned(4))) disp_buff2[MY_DISP_HOR_RES * SECTION_HEIGHT *
LV_COLOR_DEPTH / 8];
#else
uint8_t *disp_buff1 = lv_mem_alloc(MY_DISP_HOR_RES * SECTION_HEIGHT * LV_COLOR_DEPTH / 8);
uint8_t *disp_buff2 = lv_mem_alloc(MY_DISP_HOR_RES * SECTION_HEIGHT * LV_COLOR_DEPTH / 8);
#endif
static lv_disp_draw_buf_t draw_buf_dsc_2;
lv_color_t *buf_2_1 = (lv_color_t *)disp_buff1;
lv_color_t *buf_2_2 = (lv_color_t *)disp_buff2;
if (!buf_2_1 || !buf_2_2)
{
DBG_DIRECT("LVGL frame buffer is NULL");
while (1);
}
lv_disp_draw_buf_init(&draw_buf_dsc_2, buf_2_1, buf_2_2,
MY_DISP_HOR_RES * SECTION_HEIGHT); /*Initialize the display buffer*/
/*Set a display buffer*/
disp_drv.draw_buf = &draw_buf_dsc_2;
/*Required for Example 2)*/
disp_drv.full_refresh = 0;
// disp_drv.rounder_cb = rounder_cb;
#endif
/*Finally register the driver*/
lv_disp_drv_register(&disp_drv);
}
输入设备
在开发者完成输入设备的驱动功能调试后,设备能够与输入设备正常通信。本小节介绍如何将驱动与 LVGL 的输入接口进行对接以与 LVGL 的 UI 界面进行交互。
LVGL 的输入接口在文件 lv_port_indev.c
中实现,输入设备参数在初始化函数 void lv_port_indev_init(void)()
中进行配置,如选择设备类型等,输入数据获取函数配置在函数指针 indev_drv.read_cb()
,取决于输入设备类型,均在 lv_port_indev.c
中对接。
详尽的输入设备移植方法和注意事项请参阅文档 LVGL Porting Input devices,以下代码段示例了 porting 触屏 IC:
在初始化函数
void lv_port_indev_init(void)()
中选择注册对应类型的输入设备,如触屏设备则选择 TouchpadLVGL 将通过函数指针
indev_drv.read_cb()
获取输入的数据,开发者需要在其指向的函数中提供输入数据,如触屏设备则为函数void touchpad_read(lv_indev_drv_t *indev_drv, lv_indev_data_t *data)()
。触屏输入设备仅需提供触点的坐标及触摸状态即可。
void lv_port_indev_init(void)
{
/**
* Here you will find example implementation of input devices supported by LittelvGL:
* - Touchpad
* - Mouse (with cursor support)
* - Keypad (supports GUI usage only with key)
* - Encoder (supports GUI usage only with: left, right, push)
* - Button (external buttons to press points on the screen)
*
* The `..._read()` function are only examples.
* You should shape them according to your hardware
*/
static lv_indev_drv_t indev_drv;
/*------------------
* Touchpad
* -----------------*/
/*Initialize your touchpad if you have*/
touchpad_init();
/*Register a touchpad input device*/
lv_indev_drv_init(&indev_drv);
indev_drv.type = LV_INDEV_TYPE_POINTER;
indev_drv.read_cb = touchpad_read;
indev_touchpad = lv_indev_drv_register(&indev_drv);
}
/*------------------
* Touchpad
* -----------------*/
static uint16_t touch_x = 0;
static uint16_t touch_y = 0;
static bool touch_pressing = 0;
/*Initialize your touchpad*/
static void touchpad_init(void)
{
/*Your code comes here*/
}
/*Will be called by the library to read the touchpad*/
static void touchpad_read(lv_indev_drv_t *indev_drv, lv_indev_data_t *data)
{
static lv_coord_t last_x = 0;
static lv_coord_t last_y = 0;
/* rt touch read port */
if (drv_touch_read(&touch_x, &touch_y, &touch_pressing) == false)
{
return;
}
/*Save the pressed coordinates and the state*/
if (touchpad_is_pressed())
{
touchpad_get_xy(&last_x, &last_y);
data->state = LV_INDEV_STATE_PR;
}
else
{
data->state = LV_INDEV_STATE_REL;
}
/*Set the last pressed coordinates*/
data->point.x = last_x;
data->point.y = last_y;
}
/*Return true is the touchpad is pressed*/
// static lv_coord_t touch_x;
// static lv_coord_t touch_y;
static bool touchpad_is_pressed(void)
{
/*Your code comes here*/
return touch_pressing;
}
/*Get the x and y coordinates if the touchpad is pressed*/
static void touchpad_get_xy(lv_coord_t *x, lv_coord_t *y)
{
/*Your code comes here*/
(*x) = touch_x;
(*y) = touch_y;
}
文件系统
使用文件系统来管理存储介质使数据更加有条理和易于维护,可以提高外部存储设备的兼容性和跨平台性,通过文件系统接口,开发者可以方便地操作文件数据,更加灵活和高效。开发者对接文件系统到 LVGL 的文件系统接口,使资源数据与工程代码得以分开存储,缩短编译时间,提高开发效率,也增强了 UI 设计的灵活性。
LVGL 的文件系统接口在文件 lv_port_fs.c
中实现,文件系统在初始化函数 void lv_port_fs_init(void)()
中进行配置,包括文件系统的初始化、挂载盘符等,开发者需要将文件系统各功能的接口对接到对应的 LVGL fs porting 函数中,保证输入输出数据格式与接口定义的相一致。
详尽的文件系统移植方法和注意事项请参阅文档 LVGL Overview File system,以下示例了 ROMFS porting 的部分接口。
备注
ROMFS 是一个只读文件系统,故不支持文件写入。
#include "romfs.h"
/**********************
* MACROS
**********************/
#define ROMFS_ADDR 0x04600000
/**********************
* GLOBAL FUNCTIONS
**********************/
void lv_port_fs_init(void)
{
/*----------------------------------------------------
* Initialize your storage device and File System
* -------------------------------------------------*/
fs_init();
/*---------------------------------------------------
* Register the file system interface in LVGL
*--------------------------------------------------*/
/*Add a simple drive to open images*/
static lv_fs_drv_t fs_drv;
lv_fs_drv_init(&fs_drv);
/*Set up fields...*/
fs_drv.letter = 'F';
fs_drv.open_cb = fs_open;
fs_drv.close_cb = fs_close;
fs_drv.read_cb = fs_read;
fs_drv.write_cb = fs_write;
fs_drv.seek_cb = fs_seek;
fs_drv.tell_cb = fs_tell;
fs_drv.dir_close_cb = fs_dir_close;
fs_drv.dir_open_cb = fs_dir_open;
fs_drv.dir_read_cb = fs_dir_read;
lv_fs_drv_register(&fs_drv);
}
/**********************
* STATIC FUNCTIONS
**********************/
/*Initialize your Storage device and File system.*/
static void fs_init(void)
{
/*E.g. for FatFS initialize the SD card and FatFS itself*/
/*You code here*/
romfs_mount((void *)ROMFS_ADDR);
}
/**
* Open a file
* @param drv pointer to a driver where this function belongs
* @param path path to the file beginning with the driver letter (e.g. S:/folder/file.txt)
* @param mode read: FS_MODE_RD, write: FS_MODE_WR, both: FS_MODE_RD | FS_MODE_WR
* @return a file descriptor or NULL on error
*/
static void *fs_open(lv_fs_drv_t *drv, const char *path, lv_fs_mode_t mode)
{
lv_fs_res_t res = LV_FS_RES_NOT_IMP;
void *f = NULL;
if (mode == LV_FS_MODE_WR)
{
/*Open a file for write*/
f = NULL; /*Add your code here*/
}
else if (mode == LV_FS_MODE_RD)
{
/*Open a file for read*/
const char *filePath = path;
f = (void *)open(filePath, O_RDONLY); /*Add your code here*/
}
else if (mode == (LV_FS_MODE_WR | LV_FS_MODE_RD))
{
/*Open a file for read and write*/
f = NULL; /*Add your code here*/
}
return f;
}
/**
* Close an opened file
* @param drv pointer to a driver where this function belongs
* @param file_p pointer to a file_t variable. (opened with fs_open)
* @return LV_FS_RES_OK: no error or any error from @lv_fs_res_t enum
*/
static lv_fs_res_t fs_close(lv_fs_drv_t *drv, void *file_p)
{
lv_fs_res_t res = LV_FS_RES_NOT_IMP;
/*Add your code here*/
res = close((int)file_p);
return res;
}
/**
* Read data from an opened file
* @param drv pointer to a driver where this function belongs
* @param file_p pointer to a file_t variable.
* @param buf pointer to a memory block where to store the read data
* @param btr number of Bytes To Read
* @param br the real number of read bytes (Byte Read)
* @return LV_FS_RES_OK: no error or any error from @lv_fs_res_t enum
*/
static lv_fs_res_t fs_read(lv_fs_drv_t *drv, void *file_p, void *buf, uint32_t btr, uint32_t *br)
{
lv_fs_res_t res = LV_FS_RES_OK;
/*Add your code here*/
*br = read((int)file_p, buf, btr);
return res;
}
ROMFS 文件系统镜像
HoneyGUI 提供 ROMFS 文件系统镜像的打包支持:
工作路径为
your HoneyGUI dir/realgui/example/screen_lvgl/
,执行打包过程需要有 python 环境支持,工程用到的外部文件资源将打包为文件系统镜像最终作为 User Data 下载。打开工作路径,将需要打包的文件放置于
root/
文件夹下,双击脚本mkromfs_0x4600000.bat
生成文件系统镜像root(0x4600000).bin
和资源映射地址resource.h
。文件的默认 base address 为 0x4600000,resource.h
中记录了打包文件的映射地址,由于 ROMFS 支持物理地址直接访问,开发者可通过映射地址直接访问资源文件。请使用 MP Tool 的 User Data 功能下载烧录文件系统镜像到 flash,烧录地址需与 base address 保持一致。若需要修改 base address, 修改脚本
mkromfs_0x4600000.bat
中的 “–addr <number>” 参数即可,如下示例为修改 base address 从 0x4600000 改为 0x4000000。
# before - base address: 0x4600000, image: root(0x4600000).bin
python _bin_mkromfs.py --binary --addr 0x4600000 root root(0x4600000).bin
# after - base address: 0x4000000, image: root(0x4000000).bin
python _bin_mkromfs.py --binary --addr 0x4000000 root root(0x4000000).bin
备注
该打包工具仅适用于 ROMFS 的文件系统镜像打包。
打包过程并非简单的文件拼接,同时也记录了文件系统的目录信息和文件的信息。
LittleFS 文件系统镜像
LittleFS 文件系统支持读写操作,且具有掉电保护的特点,HoneyGUI 提供 LittleFS 文件系统镜像的打包支持:
工作路径为
your HoneyGUI dir/realgui/example/screen_lvgl/root_lfs
,工程用到的外部文件资源将打包为文件系统镜像最终作为 User Data 下载。打开工作路径,将需要打包的文件放置于
root/
文件夹下,双击脚本mklittlefs_img.bat
生成文件系统镜像root.bin
。请使用 MP Tool 的 User Data 功能下载烧录文件系统镜像到 flash。若需要修改文件系统的大小, 修改脚本
mklittlefs_img.bat
中的 “-s <number>” 参数即可。当使用rtk_fs.c
中的接口进行文件操作时,其中的RTK_FS_MNT_ADDR
需与烧录地址一致,MAX_LFS_SIZE
需与文件系统大小一致。如需解包文件系统镜像,双击脚本
unpack_littlefs_img.bat
将root.bin
解包到root_up/
文件夹下。
# pack image:
# -c <pack_dir>, --create <pack_dir>
# create littlefs image from a directory
#
# -b <number>, --block <number>
# fs block size, in bytes
#
# -p <number>, --page <number>
# fs page size, in bytes
#
# -s <number>, --size <number>
# fs image size, in bytes
mklittlefs.exe -c root/ root.bin -b 4096 -s 512000 -p 16
# unpack image:
# -l, --list
# list files in littlefs image
#
# -u <dest_dir>, --unpack <dest_dir>
# unpack littlefs image to a directory
mklittlefs.exe root.bin -l
mklittlefs.exe root.bin -u root_up/
备注
该打包工具仅适用于 LittleFS 的文件系统镜像打包。
LVGL Benchmark 测试
LVGL 的 Benchmark 是一个性能测试工具,用于评估 LVGL 库在各种硬件和软件环境下的图形显示性能。通过运行 Benchmark,用户可以获取帧率、渲染速度和内存使用情况等数据,从而帮助优化显示配置和调试性能问题。Benchmark 包括多种测试场景,如图形绘制、动画和文本渲染,每个场景模拟实际应用中的常见操作。用户可以通过这些测试来比较不同配置和平台的性能表现,从而做出针对性的优化调整。
LVGL 基准测试的官方文档位于 your HoneyGUI dir/lvgl/demos/benchmark/README.md
。
参考 Benchmark
芯片型号 |
处理器主频 |
加速器 |
显示面积 |
缓冲区配置 |
结果 |
---|---|---|---|---|---|
RTL8762E |
40MHz |
SW |
240*280 |
Double buffing |
Weighted FPS:15; Opa. speed: 100% |
RTL8762E |
40MHz |
SW |
80*160 |
Double buffing |
Weighted FPS:34; Opa. speed: 95% |
RTL8762D |
90MHz |
SW |
240*280 |
Double buffing |
Weighted FPS:161; Opa. speed: 77% |
RTL8762D |
90MHz |
SW |
80*160 |
Double buffing |
Weighted FPS:337; Opa. speed: 95% |
RTL8772G |
125MHz |
PPE1.0 |
480*480 |
Two buffer |
Weighted FPS:20; Opa. speed: 100% |
RTL8772G |
125MHz |
PPE1.0 |
240*280 |
Double buffing |
Weighted FPS:721; Opa. speed: 77% |
RTL8773E |
100MHz |
PPE2.0 |
390*450 |
Double buffing |
Weighted FPS:159; Opa. speed: 86% |
芯片型号 |
处理器主频 |
硬件加速器 |
图片绘制 |
图片透明度 |
图片缩放 |
图片旋转 |
圆角矩形 |
矩形填充 |
RLE 解码 |
字符 |
线条 |
---|---|---|---|---|---|---|---|---|---|---|---|
RTL8772G |
125MHz |
PPE1.0 |
HW |
HW |
HW |
SW |
SW+HW |
HW |
HW |
SW |
SW |
RTL8773E |
100MHz |
PPE2.0 |
HW |
HW |
HW |
HW |
SW+HW |
HW |
HW |
SW |
SW |
备注
涉及 LVGL Mask 的效果均需要 SW 处理
RTL8772G 支持 Helium 硬件加速器
从 Demo 入门开发
建议开发者开发前先行阅读理解 LVGL Overview 和 LVGL Widgets - Base object 部分以了解 LVGL 的设计概念和设计逻辑。
LVGL 提供了丰富的 demo 和 example 来帮助开发者了解熟悉各个控件和特性的使用。
LVGL Demo 中展示了综合性比较强的 Demo ,其源码保存在目录
your HoneyGUI dir/lvgl/src/demo
下,开发者可直接调用对应的lv_demo_xxx()
函数来熟悉了解。在线文档 LVGL Example 中展示了各个 example 的运行效果,其源码保存在目录
your HoneyGUI dir/lvgl/src/example
下,开发者可直接调用对应的lv_example_xxx()
函数来熟悉控件和理解特性。
资源转换器
LVGL 的图片和字库需要借助工具转换为 LVGL 可以识别的格式,才能在 UI 中使用。LVGL 支持转换为 C 数组格式和 bin 二进制文件的资源,其中 C 数组格式的资源将会参与编译过程,每当程序逻辑发生变化时,都会参与编译,资源大小计入 APP image(OTA 时需要更大空间),bin 二进制文件格式的资源不参与编译,单独存储,需要文件系统等来支持访问。在路径 your HoneyGUI dir/realgui/example/screen_lvgl/assets/
下已提供 example lvgl_example_assets.c
示例如何为控件配置不同格式的资源。
图片转换器
LVGL 在线转换工具
在线转换工具: LVGL Image Converter
文档说明: LVGL Overview Images
使用步骤请参考 LVGL Overview Images - Online Converter:
选择 LVGL 版本
选取图片文件
选择输出文件的颜色格式
颜色格式的说明请参考 LVGL Overview Images - color format
选择输出图片的类型 (C array/binary file)
点击 Convert 获取输出文件
在文档 LVGL Overview Images 中详细介绍了如何在 LVGL 中使用图片资源和图片转换工具,并提供了简单的使用范例。以 C array 生成的图片资源置于 your HoneyGUI dir/realgui/example/screen_lvgl/assets/
下即可被自动构建到工程中。
值得一提的是,使用 bin 文件的图片资源时,bin 文件中数据的格式为 4 Byte lv_img_header_t + data
, 其中 lv_img_header_t
中包含有 Color format
, width
和 height
,此时利用 lv_img_header_t
信息来计算出 data_size
即可构建一个完整的 lv_img_dsc_t
来描述图片。
typedef struct {
uint32_t cf : 5; /*Color format: See `lv_img_color_format_t`*/
uint32_t always_zero : 3; /*It the upper bits of the first byte. Always zero to look like a
non-printable character*/
uint32_t reserved : 2; /*Reserved to be used later*/
uint32_t w : 11; /*Width of the image map*/
uint32_t h : 11; /*Height of the image map*/
} lv_img_header_t;
/** Image header it is compatible with
* the result from image converter utility*/
typedef struct {
lv_img_header_t header; /**< A header describing the basics of the image*/
uint32_t data_size; /**< Size of the image in bytes*/
const uint8_t * data; /**< Pointer to the data of the image*/
} lv_img_dsc_t;
HoneyGUI 图像转换工具
转换工具下载链接: HoneyGUI Image Convert Tool
当需要进一步压缩图片资源占用空间时,HoneyGUI 图像转换工具支持对图片进行压缩转换,IC 支持软硬件解码。HoneyGUI 图像转换工具采用 RLE(Run-length Encoding) 压缩,该压缩算法是一种简单的无损算法,通过编码连续重复的像素值和重复次数来减少存储空间,计算复杂度低且压缩率较高,非常适合用于压缩 GUI 资源。
压缩图片
用户可利用 HoneyGUI 图像转换工具将图片资源转换为 RLE 压缩的二进制文件格式,具体使用步骤请参考 HoneyGUI Image Converter - Doc:
选择需要压缩的图片文件(支持 PNG、JPEG 等格式)
配置图片的转换参数:启用 Compress,Compress Mode 选择 RLE, 启用 Color Head,Color Space 按需选择
点击 Convert 生成压缩的二进制文件
导入 LVGL
HoneyGUI 图像转换工具生成的二进制文件可导入 LVGL 使用:
若作为文件导入
注意: 修改文件扩展名为 .rle , 即可放入文件系统使用
your HoneyGUI dir/realgui/example/screen_lvgl/root
// file: lvgl_example_assets.c void load_img_rle_file(void) { lv_obj_t *icon = lv_img_create(lv_scr_act()); lv_img_set_src(icon, "F:/logo_lvgl.rle"); lv_obj_set_pos(icon, 0, 0); }
备注:使用 RLE 解码器 + ROMFS 时,解码器将会直接从文件系统即 FLASH 上获取图片,不做额外缓存,需要做缓存处理的情况请使用文件系统接口将文件读到内存中,作为数组方式使用。
若作为 C 数组格式导入
打开LVGL图片转换在线工具并上传要转换的压缩文件,请参考 LVGL 在线转换工具
在 Color format 选项中,务必选择 CF_RAW
将转换后的图片文件导出为C文件格式,例如
logo_lvgl_rle.c
注意1:转换结果文件的存放路径: 将转换后的 C 文件存放在以下参考路径:
your HoneyGUI dir/realgui/example/screen_lvgl/assets
注意2:修改图像描述符中的色彩格式 cf: 导出的C文件,例如
logo_lvgl_rle.c
,需要对其中的图像描述符进行修改,保证 cf 设置为LV_IMG_CF_RAW
:// file:logo_lvgl_rle.c const lv_img_dsc_t logo_lvgl_rle = { .header.cf = LV_IMG_CF_RAW, .header.always_zero = 0, .header.reserved = 0, .header.w = 0, .header.h = 0, .data_size = 1889, .data = logo_lvgl_rle_map, };
在项目中声明图片后即可作为图片源使用
// file:lvgl_example_assets.c void load_img_rle_c_file(void) { LV_IMG_DECLARE(logo_lvgl_rle); lv_obj_t *icon = lv_img_create(lv_scr_act()); lv_img_set_src(icon, &logo_lvgl_rle); lv_obj_set_pos(icon, 0, 0); }
若作为文件导入,以文件地址的方式访问图片资源
构建
lv_img_dsc_t
,例如:// file:lvgl_example_assets.c #include "resource.h" const lv_img_dsc_t lvgl_test_img_rle = { .header.cf = LV_IMG_CF_RAW, .header.always_zero = 0, .header.reserved = 0, .header.w = 0, .header.h = 0, .data_size = 0, .data = LOGO_LVGL_RLE, };注意:图像描述符中的色彩格式设置为 cf = LV_IMG_CF_RAW
图片资源访问,控件创建:
// file: lvgl_example_assets.c void load_img_rle_dataAddr_file(void) { lv_obj_t *icon = lv_img_create(lv_scr_act()); lv_img_set_src(icon, &lvgl_test_img_rle); lv_obj_set_pos(icon, 0, 0); }
LVGL 启用 RLE 解码器
为了在 LVGL 中解码 RLE 压缩的图片资源,需要配置启用 RLE 解码器,并为其分配缓存空间。
启用 RLE 解码器:在配置文件
lv_conf.h
中找到LV_USE_RTK_IDU
宏定义,并将其设置为启用(1)- 分配解码缓存:在
lv_conf.h
文件中配置以下参数: LV_PSRAM_START
:缓存的起始地址LV_PSRAM_SIZE
:缓存空间大小,确保此大小足够容纳所使用的最大整张图片的解码数据
- 分配解码缓存:在
// file: lv_conf.h
/*RTK_IDU decoder library*/
#define LV_USE_RTK_IDU 1
#ifdef LV_USE_RTK_IDU
#define LV_MEM_PSRAM_ADR 0x08000000
#define LV_PSRAM_SIZE (MY_DISP_HOR_RES * MY_DISP_VER_RES * 4)
#define LV_PSRAM_START (LV_MEM_PSRAM_ADR + 2 * MY_DISP_HOR_RES * MY_DISP_VER_RES * LV_COLOR_DEPTH / 8)
#ifndef LV_MEM_ADR
#define LV_MEM_ADR LV_PSRAM_START
#endif
#endif
备注
使用 RLE 解码器 + ROMFS 时,解码器将会直接从文件系统即 FLASH 上获取图片,不做额外缓存;
字库转换器
在线转换工具:LVGL Font Converter
文档说明:LVGL Overview Fonts
使用步骤请参考 LVGL Overview Font - Add a new font :
设定输出字库的名字
设定字体的高度 height,像素单位
设定字体的 bpp(bit-per-piel)
表示采用多少个 bit 来描述一个像素,当数值越大时,字符的抗锯齿效果越好,边缘越平滑,字库占用空间越大
选择输出字库的类型 (C array/bin file)
选择字体文件 (TTF/WOFF)
设定需要转换的字符 Unicode 范围,也可直接列出需要转换的字符
在文档 LVGL Overview Fonts 中详细介绍了如何在 LVGL 中使用字库资源和字库转换工具,并提供了简单的使用范例。在 example 中 lv_example_label_3()
示例了如何为 label 控件配置指定的字库。以 C array 生成的字库资源置于 your HoneyGUI dir/realgui/example/screen_lvgl/assets/
下即可被自动构建到工程中。
在 LVGL 中提供了内置的字库,以数组的形式保存在目录 your HoneyGUI dir/lvgl/src/font/
下,每份字库所包含的字符均注明在文件开头。内置字库中包含有一份汉字字库 lv_font_simsun_16_cjk.c
cjk 16 号字库,但为单一字号,字符数有限。
开发资源支持
在线文档
LVGL 的 在线文档 提供了全面的技术文档和教程,帮助开发者更好地了解和使用 LVGL 图形库。该文档包含以下内容: - 概述和特性:文档介绍了 LVGL 的基本概念和特性,包括图形对象、屏幕管理、事件处理、主题样式等。用户可以通过阅读文档了解 LVGL 的核心功能和优势。
应用开发指南:文档提供了详细的应用开发指南,包括如何初始化和配置 LVGL 、如何创建和管理图形对象、如何处理用户输入和事件、如何添加主题和样式等。这些指南可以帮助用户快速上手使用LVGL并开发自己的应用程序。
API 文档:文档详细列举了 LVGL 的 API 接口和函数,以及它们的参数和用法。用户可以根据需要查阅API文档来了解具体的函数和接口的功能和用法,以便进行更高级的自定义和扩展。
示例代码:文档中提供了众多的示例代码,涵盖了常见的应用场景和功能。用户可以借鉴这些示例代码,加快开发速度,并快速实现特定功能的需求。
使用 LVGL 的在线文档可以帮助用户更好地理解和掌握 LVGL 的使用方法和技巧,提高开发效率。用户可以通过逐步学习文档中的内容,从简单的界面构建到复杂的应用开发,逐步掌握 LVGL 的各种功能和特性。同时,文档还提供了示例和代码片段,方便用户更快地开发出具有丰富界面和功能的应用程序。
用户可以通过在网页浏览器中打开 LVGL 的在线文档,并浏览各个章节和内容,根据自己的需要查找和学习相关的知识。另外,用户还可以通过搜索功能来快速查找文档中的具体信息。总之,LVGL 的在线文档是用户理解和使用 LVGL 图形库的重要资源,可以提供全面而详细的指导,帮助用户快速上手和开发出更好的应用程序。
基于文档开发能够完成大部分的 UI 效果,值得注意的是,文档内容并不一定齐全,当文档内容存在疏漏时,最终还是以代码为准。
Github 仓库
LVGL 的 GitHub 仓库是开发者使用和贡献 LVGL 的重要平台:
获取最新版本:LVGL 的 GitHub 仓库可以获得最新的 LVGL 版本和更新。开发者可以及时获取最新的功能更新、修复和改进,保持应用程序与 LVGL 的同步。
参与社区和贡献代码:通过 GitHub 仓库,开发者可以积极参与 LVGL 社区的讨论和交流,了解其他开发者的问题和解决方案。同时,开发者也可以贡献自己的代码和改进,让 LVGL 更加完善和强大。
提交问题和 bug 报告:GitHub 仓库提供了问题和bug报告的平台,开发者可以提交他们在使用 LVGL 过程中遇到的问题和 bug。这有助于 LVGL 开发团队及时发现和解决问题,提高 LVGL 的稳定性和可靠性。
学习示例和文档:GitHub 仓库中还包含示例代码和文档,帮助开发者更好地理解和学习 LVGL 的使用。开发者可以通过浏览仓库中的示例代码和文档,学习 LVGL 的各个功能和特性,提高开发技能。
设计器
GUI Guider: 免费
Squareline: Squareline Studio,付费
LVGL的设计器(LVGL Designer)是一个为 LVGL 图形库设计和开发界面的可视化工具。它提供了一个直观且用户友好的界面,使开发者能够快速创建和编辑 LVGL 的 GUI 界面。
LVGL Designer 具有以下特点和功能:
可视化界面设计:设计器提供了直观的可视化界面,开发者可以使用鼠标和简单的拖放操作来创建和编辑GUI界面。它允许添加和调整各种图形对象、标签、按钮、文本框、图像等元素,并设置它们的大小、位置、样式等属性。
实时预览和调试:设计器支持实时预览,开发者可以随时查看他们所设计的界面的外观和行为。这有助于开发者快速调整和优化界面,确保其满足预期效果。
事件和交互管理:设计器使开发者能够方便地添加和管理事件和交互行为。开发者可以为图形对象添加点击、滚动、拖动等事件,并通过简单的配置设置它们的响应行为。
主题和样式定制:设计器支持主题和样式的定制,开发者可以轻松地选择和应用不同的主题和样式,使界面更具个性化和美观。
导出代码:设计器允许将设计的界面导出为 LVGL 代码,并提供所需的初始化和配置。这样,开发者可以直接将导出的代码用于 LVGL 应用程序的开发,省去手动编写代码的步骤。
使用 LVGL 的设计器可以极大地加速 GUI 界面的设计和开发过程,尤其适用于非专业的 UI 设计师或开发者。通过简单的拖放和配置操作,开发者可以快速创建出具有吸引力和交互性的界面,提高开发效率和用户体验。同时,设计器还提供一个便捷的方法来导出设计的界面为可用的 LVGL 代码,使开发者能够直接将其集成到他们的应用程序中。
论坛
LVGL 的官方论坛是一个开发者社区,致力于讨论和分享有关 LVGL 图形库的话题和资源。它提供了一个平台,供开发者之间交流、寻求帮助和分享他们的经验和项目。
LVGL 论坛的一些特点和功能包括:
提问和回答:开发者可以在论坛上提出他们在使用 LVGL 时遇到的问题,并获得其他开发者的帮助和回答。这使得论坛成为一个宝贵的知识库,提供了解决问题的经验和技巧。
教程和示例:论坛上有许多有用的教程和示例代码,展示了如何使用 LVGL 的不同功能和特性。这些资源对于新手开发者学习和掌握 LVGL 非常有帮助。
开发者贡献和项目展示:论坛上的开发者可以分享他们的项目和定制的 LVGL 界面,以及其他开发者可以共享、讨论和参考的贡献。
更新和发布通告:LVGL 的开发团队在论坛上发布关于新版本发布和更新的通告和说明。这使得开发者可以及时了解最新功能和改进。
社区互动:论坛提供了一个社区互动的平台,开发者可以互相交流、分享和建立联系,加强 LVGL 开发社区的合作和发展。
LVGL 论坛对于使用 LVGL 的开发者来说,是获取支持、解决问题、学习和分享经验的重要资源。
博客
LVGL 的官方博客是一个定期更新的平台,提供关于 LVGL 图形库的最新信息、教程、案例研究和开发者见解。LVGL 的开发团队和社区成员经常在博客上发布有关 LVGL 的各种内容,这些内容可以使开发者更好地了解和使用 LVGL。
LVGL 的博客包含以下内容:
更新和新功能介绍:博客上会发布关于 LVGL 最新版本的更新和改进的文章,这些文章介绍了新功能、修复了的问题和性能提升,使开发者可以了解和利用最新的 LVGL 特性。
教程和使用指南:博客会提供有关 LVGL 的实用教程和使用指南,涵盖从入门到高级的各种主题。这些教程通常包括示例代码和详细说明,帮助开发者掌握 LVGL 的使用和最佳实践。
案例研究和项目展示:博客上会分享一些使用 LVGL 实现的案例研究和项目展示。这些文章介绍了如何使用 LVGL 构建实际应用程序和界面,让开发者从实践中获取灵感和经验。
技术深入解析和开发者见解:博客还会涵盖对LVGL的深入分析和开发者的见解。这些文章可能探讨 LVGL 的内部工作原理、性能优化技巧、优秀设计实践等方面的主题,给开发者提供更深入的了解和思考。
LVGL 的博客是一个重要的资源,对于 LVGL 的开发者来说是了解和掌握 LVGL 的宝贵来源。通过阅读博客,开发者可以获取到关于 LVGL 最新动态、学习材料和技术见解,帮助他们更好地使用 LVGL 构建出优秀的图形界面。
常见问题
HoneyGUI vs LVGL 绘制图片帧率
GRAM 屏幕 (280x456)SRAM 分块绘制
背景:RTL8772G 平台,RGB565,非压缩图片,测试单张图片的显示绘制性能。
测试类型 |
HoneyGUI 帧率(FPS) SW |
HoneyGUI 帧率(FPS) PPE |
LVGL 帧率(FPS) SW |
LVGL 帧率(FPS) PPE |
---|---|---|---|---|
绘制图片 |
73 |
74 |
70 |
73 |
普通填充矩形 |
3 |
85 |
74 |
74 |
图像旋转 45° |
3 |
3 |
4 |
4 |
图像放大 1.5 倍 |
3 |
31 |
3 |
25 |
图像缩小 0.5 倍 |
9 |
73 |
12 |
59 |
Section |
HoneyGUI 帧率(FPS) |
LVGL 帧率(FPS) |
---|---|---|
10 |
70 |
45 |
20 |
73 |
73 |
30 |
74 |
73 |
PSRAM 整帧 buffer 绘制(800x480)
背景:RTL8772G 平台,rgb565,图片尺寸 315x316,非压缩图片,RGB 屏幕,测试单张图片的显示绘制性能。
测试类型 |
HoneyGUI SW (FPS) |
HoneyGUI PPE (FPS) |
LVGL SW (FPS) |
LVGL PPE (FPS) |
---|---|---|---|---|
绘制图片 |
76 |
76 |
17 |
25 |
普通填充矩形 |
4 |
78 |
25 |
26 |
图像旋转 45° |
3 |
3 |
6 |
4 |
图像放大 1.5 倍 |
2 |
23 |
3 |
13 |
图像缩小 0.5 倍 |
10 |
82 |
13 |
50 |
分析
对于 RGB 屏幕需要额外的 psram 作为缓存 buffer,LVGL完全使用psram作为图像缓存buffer,相比于 HoneyGUI 采用 ram 与 sram 结合的方式,LVGL 各方面性能表现较差;
HoneyGUI vs LVGL RAM消耗
测试类型 |
HoneyGUI (Bytes) |
LVGL 控件消耗 (Bytes) |
---|---|---|
绘制图片 |
156 |
176 |
普通填充矩形 |
64 |
200 |
图像旋转 |
156 |
208 |
图像放大 1.5 倍 |
156 |
208 |
图像缩小 0.5 倍 |
156 |
176 |
测试类型 |
HoneyGUI (Bytes) |
LVGL 控件消耗 (Bytes) |
---|---|---|
绘制图片 |
41892(40KB) |
55300(54KB) |
普通填充矩形 |
41892(40KB) |
55300(54KB) |
图像旋转 |
41892(40KB) |
55300(54KB) |
图像放大 1.5 倍 |
41892(40KB) |
55300(54KB) |
图像缩小 0.5 倍 |
41892(40KB) |
55300(54KB) |
结论
适用场景: 需要推动大尺寸的屏幕(例如 800x480),并且整帧绘制的情况,推荐选择 HoneyGUI,对于需要频繁刷新脏块的项目,推荐使用 LVGL;分块绘制场景,在ram资源紧张的情况下,推荐使用 HoneyGUI,section 推荐参数 10。
旋转,放大缩小:LVGL 在图像旋转方面由于采用 2x2 的矩阵,在二维图渲染方面,相比于 HoneyGUI 的 3x3 矩阵,运算方面数据量更少,因此表现更快,而对于显示 2.5D,仿三维效果时,HoneyGUI将表现更好。
在实际项目中,可以根据具体的帧率需求、系统资源情况以及其他功能需求,选择合适的显示框架。如果可行,进行具体的性能测试和评估是最为理想的做法。
通过以上分析,可以为项目选择显示框架时提供参考,帮助决策人员根据实际需要做出最佳选择。