引言
LVGL是一个跨平台、轻量级、易于移植的图形库。也因其支持大量特性和其易于裁剪,配置开关众多,且版本升级较快,不同版本之间存在一定的差异性,相关的使用教程有一定的滞后性,由于缺少最新版本的中文教程,加上大量的教程中未注明对应的版本,初始接触的开发者往往容易中招,花费大量时间爬坑,本文特对自己的使用经验进行记录和总结,以期对初始接触LVGL的开发者有所帮助。
简介
什么是Lvgl?LVGL(轻巧而多功能的图形库)是一个免费的开放源代码图形库,它提供创建具有易于使用的图形元素,精美的视觉效果和低内存占用的嵌入式GUI所需的一切。
主要特性
l 功能强大的构建块,例如按钮,图表,列表,滑块,图像等。
l 带有动画,抗锯齿,不透明,平滑滚动的高级图形
l 各种输入设备,例如触摸板,鼠标,键盘,编码器等
l 支持UTF-8编码的多语言
l 多显示器支持,如TFT,单色显示器
l 完全可定制的图形元素
l 独立于任何微控制器或显示器使用的硬件
l 可扩展以使用很少的内存(64 kB闪存,16 kB RAM)进行操作
l 操作系统,支持外部存储器和GPU,但不是必需的
l 单帧缓冲区操作,即使具有高级图形效果
l 用C语言编写,以实现最大的兼容性(与C ++兼容)
l 模拟器可在没有嵌入式硬件的PC上进行嵌入式GUI设计
l 可移植到MicroPython
l 可快速上手的教程、示例、主题
l 丰富的文档教程
l 在MIT许可下免费和开源
LVGL硬件要求
- 16、32或64位微控制器或处理器
- 最低 16 MHz 时钟频率
- Flash/ROM::对于非常重要的组件要求 >64 kB(建议 > 180 kB)
- RAM
- 静态 RAM 使用量:~2 kB,取决于所使用的功能和对象类型
- 堆栈: > 2kB(建议 > 8 kB)
- 动态数据(堆):> 2 KB(如果使用多个对象,则建议 > 16 kB)。由 lv_conf.h 中的 LV_MEM_SIZE 宏进行设置。
- 显示缓冲区:> “水平分辨率”像素(建议 > 10× “水平分辨率” )
- MCU 或外部显示控制器中的一帧缓冲区
- C99或更高版本的编译器
- 具备基本的C(或C ++)知识:指针,结构,回调...
请注意,内存使用情况可能会因具体的体系结构、编译器和构建选项而异。
官方网址
LVGL - Light and Versatile Embedded Graphics Library
源码地址 https://github.com/lvgl/lvgl
部分中文资料网址
LVGL教程|极客笔记 (deepinout.com)
欢迎阅读百问网LVGL中文开发手册! — 百问网LVGL中文教程文档 文档 (100ask.net)
环境搭建
本文主要针对在windows10下,使用visual stdio开发环境编译运行模块器对LVGL8.3开发测试的一些配置使用进行介绍。
VS2019或VS022均可,Visual Stdio和Git工具的安装使用,在此不做介绍。
Visual Studio simulator模拟器为开源项目,源码地址如下:
https://github.com/lvgl/lv_port_win_visual_studio
模拟器源码项目下载
模拟器项目源码内部包含了引用特定版本代码库模块,通过文件下载方式下载,会存在引用模块为空的问题。
Github站点的英文说明文档中介绍了推荐的下载方式,采用git下载,但与我们通常下载单个源码库有所不同。
此存储库包含其他必要的 LVGL 软件存储库作为 git 子模块。这些子模块不会使用正常的 git clone 命令拉入,它们将被需要。有几种技术可以拉入子模块。
此命令将在单个步骤中克隆lv_sim_visual_studio存储库和所有子模块。
git clone --recurse-submodules https://github.com/lvgl/
lv_sim_visual_studio.git
众所周知,国内的网络环境,访问github比较抽风,这种方式,通常会失败。
因此,推荐使用下面的方式下载
git clone https://github.com/lvgl/lv_sim_visual_studio.git
cd lv_sim_visual_studio
git submodule update --init –recursive
先单独下载模拟器主项目,再切换到模拟器项目目录,使用模块拉取命令下载,这样,即使用失败了,可以通过反复尝试,可以将模块代码拉取回来。
模拟器使用
模块器项目代码成功拉取后,使用VS2019或VS2022成功打开即可运行。
模拟器项目中,lvgl的配置文件中,大部分可用宏已默认开启,这个与MCU项目下配置模板中的有所不同,毕竟电脑上模拟器可以有充足的硬件资源可供分配。
lv_conf.h 中的LV_MEM_SIZE宏应为 128KB 或更大,因为在使用 64 位模拟器时可能会遇到内存不足问题。 注意:在此项目中,大小设置为 1024KB。 用户需要检查在Visual Studio中选择的目标,因为模拟器项目支持ARM64,Visual Studio会因为字母顺序在第一时间选择ARM64。
自带例程使用
LVGL.Simulator.cpp文件中,main函数内,默认使用的lv_demo_widgets()演示例程,其后有各类其他演示例程,只需注释掉lv_demo_widgets()演示例程调用,分别启用其他例程即可。
文件系统使用
LVGL8.3版本的fsdrv目录中,已自带lv_fs_win32的文件系统调用接口,在模拟器中使用无需另外移植lv_fs_port文件,但需要在相关的配置文件中进行相关宏定义开启和相关配置。
Lv_conf.h配置文件中,文件系统宏默认已开启
#define LV_USE_FS_WIN32 '/'
//#define LV_FS_WIN32_PATH "C:\\Users\\john\\"
文件系统的访问路径配置为注释状态,可启用该宏定义并指向所需访问的磁盘文件目录,否则,文件系统默认访问项目的当前路径。
修改配置如下
#define LV_USE_FS_WIN32 '/'
#define LV_FS_WIN32_PATH "D:\\SD"//此处可使用你自己的目录
然后,开启main函数中的如下代码
lv_fs_dir_t d;
if (lv_fs_dir_open(&d, "/") == LV_FS_RES_OK)
{
char b[MAX_PATH];
memset(b, 0, MAX_PATH);
while (lv_fs_dir_read(&d, b) == LV_FS_RES_OK)
{
printf("%s\n", b);
}
lv_fs_dir_close(&d);
}
运行程序,查看控制台窗口,是不是应该出现指定目录下的所有目录和文件呢?
然而,并没有。
这一步也是LVGL默认模拟器项目比较坑的地方,没有默认给出需要的配置项,这一处也折腾我跟踪代码,分析了好久,说起来都是泪啊。
到底应该怎么办呢?
运行并跟踪代码,在lv_init()中会调用lv_fs_win32_init(),在这个函数中,对文件驱动器进行了初始化
static lv_fs_drv_t fs_drv; /*A driver descriptor*/
lv_fs_drv_init(&fs_drv);
/*Set up fields...*/
fs_drv.letter = LV_FS_WIN32_LETTER;
fs_drv.cache_size = LV_FS_WIN32_CACHE_SIZE;
定位查看LV_FS_WIN32_LETTER宏,发现在lv_conf_internal.h中,
如果外部未定义,会被定义为'\0',其后注释说明,需设置为设置驱动器可访问的大写字母。
如此处不进行定义,当lv_fs_dir_open函数中调用lv_fs_get_drv(letter)方法时,其内部的判断 (*drv)->letter == letter会无法匹配而导致无法返回可用的驱动器对象。
我们可以修改lv_conf_internal.h文件中(2035行)处的LV_FS_WIN32_LETTER宏定义,或者在lv_conf.h文件中,在宏 LV_USE_FS_WIN32 的下面,增加一行如下定义即可
#define LV_FS_WIN32_LETTER '/'
再次运行模拟器,bingo,指定目标下的目录和文件,在控制台中被打印输出出来了
使用矢量字体
模拟器项目中,默认已提供了矢量字体示例,打开注释开关启用,同时注释掉后面的演示Demo调用即可。
// ----------------------------------
// my freetype application
// ----------------------------------
///*Init freetype library
// *Cache max 64 faces and 1 size*/
lv_freetype_init(64, 1, 0);
///*Create a font*/
static lv_ft_info_t info;
info.name = "./lvgl/src/extra/libs/freetype/arial.ttf";
info.weight = 36;
info.style = FT_FONT_STYLE_NORMAL;
lv_ft_font_init(&info);
/*Create style with the new font*/
static lv_style_t style;
lv_style_init(&style);
lv_style_set_text_font(&style, info.font);
/*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, "FreeType Arial Test");
默认矢量字体例程是英文字体,我们要使用中文矢量字体怎么处理呢?
在我们之前建立的文件系统目录中,放置对应的中文字体
//info.name = "./lvgl/src/extra/libs/freetype/arial.ttf";
info.name = "D:/SD/TTF/simhei.ttf";
此处须使用完整路径或相对路径
lv_label_set_text(label, "This is 思源黑体!");
测试,中文显示成功。
使用点阵字体
矢量字体使用方便,但在资源紧张的MCU中使用,需要消耗一定的内存开销和存储开销,至少需要增加200KB左右的Flash和16KB左右的内存开销。
为了节约内存资源 ,在无需多种字号的情况下,我们通常选择点阵字体。点阵字体可以通过专门的转换工具软件从矢量字库提取。
我们使用里飞网阿里兄提供的免费转换软件实现点阵字体库的转换。
字体转换工具和用法,大家在里飞网论坛自行查阅即可,如不需发贴,无需注册。
生成点阵字库文件的同时,会生成一个C文件,将文件添加到项目,将修改以下方法,增加在Windows系统下的文字字阵数据获取方法实现。
static uint8_t __g_font_buf[512];//如bin文件存在SPI FLASH可使用此buff
static uint8_t* __user_font_getdata(int offset, int size) {
//如字模保存在SPI FLASH, SPIFLASH_Read(__g_font_buf,offset,size);
//如字模已加载到SDRAM,直接返回偏移地址即可如:return (uint8_t*)(sdram_fontddr+offset);
lv_fs_file_t file;
lv_fs_res_t result;
static uint32_t icount = 0;
result = lv_fs_open(&file, "/Font/Bin/SourceHanSans_16.bin", LV_FS_MODE_RD);
printf("lv_fs_open res:%d,count:%d\r\n", result, ++icount);
if (result != LV_FS_RES_OK)
return NULL;
lv_fs_seek(&file, offset, LV_FS_SEEK_CUR);
uint32_t len;
lv_fs_read(&file, __g_font_buf, size, &len);
lv_fs_close(&file);
return __g_font_buf;
}
注:此方法仅适用于windows系统中测试使用,如果MCU中使用,速度一定会十分感人^_^,因为lvgl获取点阵字体的每一个字节,都会调用一次这个函数。
大家可对此方法进行优化,如增加读取缓存处理,减少文件访问。如有好的实现,也请告诉我呵。
增加点阵字体测试代码:
void lv_showFont(void)
{
LV_FONT_DECLARE(SourceHanSans_16);//引入字库
lv_obj_t* zh_label = lv_label_create(lv_scr_act());//获取默认屏幕
lv_obj_set_style_text_font(zh_label, &SourceHanSans_16, LV_STATE_DEFAULT);// 设置风格的字体
lv_label_set_text(zh_label, "你好,这是思源黑体!");//显示文字
lv_obj_align(zh_label, LV_ALIGN_TOP_MID, 0, 0);
lv_obj_t* label = lv_label_create(lv_scr_act());//获取默认屏幕
lv_obj_set_style_text_font(label, &SourceHanSans_16, LV_STATE_DEFAULT);// 设置风格的字体
lv_label_set_text(label, "山不在高,有仙则名,水不在深,有龙则灵。");//显示文字
lv_obj_align(label, LV_ALIGN_CENTER, 0, 0);
//lv_obj_t* style_txt = lv_label_create(lv_scr_act());//获取默认屏幕
//lv_obj_set_style_text_font(style_txt, &SourceHanSans_16, LV_STATE_DEFAULT);// 设置风格的字体
//lv_label_set_text(style_txt, "\uF00C确定");//显示文字
//lv_obj_align(style_txt, LV_ALIGN_BOTTOM_MID, 0, 0);
}
运行测试代码,效果如下。
使用图片文件
我们通常希望能够直接使用存储在文件系统中的图片,最好是图片无需预先经过专门的处理,这样的话,图片比较通用,也无需专门的软件工具处理。
LVGL8.3已经准备好了常用的BMP、PNG、JPG格式图像解码器。
考虑解码的性能和速度,我们直接使用BMP位图。
为了降低内存开销和存储开销,我打算使用256色位图,这样的话,一张普通图片,使用windows自带的画图程序,可以直接转存为256色位图。
添加加载图形对象代码,如下
void lv_showBmp(void)
{
lv_obj_t* bmp = lv_img_create(lv_scr_act());
if (bmp == NULL)
{
printf("[%s:%d] create bmp failed\n", __FUNCTION__, __LINE__);
return;
}
char* bmp_path;
#if LV_USE_FS_WIN32
bmp_path = "/Images/Lena_320_256.bmp";
#else
bmp_path = "C:/Images/Lena_320_256.bmp";
#endif
lv_img_set_src(bmp, bmp_path); // 设置图片
//lv_img_set_zoom(bmp, 128);
lv_obj_align(bmp, LV_ALIGN_CENTER, 0, 0);
lv_obj_set_size(bmp, 160, 160); // 设置大小
}
此处加载图片的路径应设置成相对路径,且需以’/’开头,文件系统处理函数中,会定位到配置的文件目录里面,否则,会出现找不到文件的情况。
为了节省资源 ,使用256色位图,lv_conf.h中,颜色深度宏参数如下定义
/*Color depth: 1 (1 byte per pixel), 8 (RGB332), 16 (RGB565), 32 (ARGB8888)*/
#define LV_COLOR_DEPTH 8
实际测试,发现存在如上图中的颜色显示不正常问题。
如将色深改为32,#define LV_COLOR_DEPTH 32
使用24位真彩或32位真彩位图,则一切正常,除了lvgl需要的内存大幅飙升(相对于资源紧缺的MCU来说)。
如果想使用16位色深的图片,windows自带的画图工具并不支持,需要专门的工具进行转换,并且需将色深宏参数定义为16。
256色(8位)位图为什么不能正常显示,本人目前也还未找到原因,可能是lvgl的bug,也可能是我打开的姿势不对,如有解决此问题的朋友,请不吝告知,十分感谢。