在Windows模拟器中使用LVGL8.3


引言

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官方文档网站(https://docs.lvgl.io)
  • lvgl官方博客博客站点(https://blog.lvgl.io)
  • sim在线模拟器网站(https://sim.lvgl.io)

部分中文资料网址

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,也可能是我打开的姿势不对,如有解决此问题的朋友,请不吝告知,十分感谢。