1、前言
在上一篇LVGL专题文章中已经讲解了如何将LVGL与FatFs链接起来,实现LVGL对SD卡中的数据进行读写操作。本文在上一文的基础上,将实现LVGL读取文件系统中的图片文件(BMP、PNG、JPG),并显示到MCU设备中的LCD屏中。
LVGL配置FatFs文件系统,实现对STM32的SD卡数据读写-CSDN博客
不得不吐槽一下,在MCU这类资源受限的设备上,需要刷一张图片,可谓是一波三折,属实头大。在试错,踩坑好几回后,总计归纳了这篇文章所呈现的笔记内容。如果本文讲解的方法有不合适的地方,欢迎在评论区或者私信交流探讨。
2、LVGL图片类型
(1)、解码器
在LVGL中,支持如下4种格式的图片类型,分别是BMP、PNG、JPG和GIF。
打开LVGL官网的文档,根据使用的LVGL选择了对应的文档版本后,点击3rd party libraries。可以看到官方对于使用这些第三方库资源内容的说明。
BMP decoder — LVGL documentation
除了使用本地图片解码器,LVGL官网也提供了在线图像转换器:Image Converter — LVGL
(2)、BMP
BMP(Bitmap Image File)是一种图像文件格式,广泛应用于Windows操作系统中,用于存储数字图像。BMP图片是没有经过压缩的RGB图像数据。在使用的时候,不需要特定的解码器,直接读取文件的内容,简单处理后,就能进行显示。但因为其没有压缩,所以占用的内存空间较大。接下来所说的LVGL BMP解码器是指读取BMP文件处理像素的操作。
而较多的MCU设备LCD显示屏仅支持16位RGB565格式的BMP图像显示。因此,对于目标设备LCD仅支持RGB565时,需要将24位BMP位图进行格式转换后,才能正常显示到LCD中。于是在这一篇文章中,将BMP的原理进行了深度的解析,同时也提供了位图格式转换的参考代码,要是有这个问题的困扰,可以参考一下。
BMP位图原理深度解析及编程实现RGB565图片格式转换-CSDN博客
在LVGL的BMP解码器中,像素是按需读取的(不是整个图像被加载),因此使用 BMP 图像需要很少的 RAM。
在 lv_conf.h 中通过 LV_USE_BMP 可启用BMP解码器,并将 BMP 文件直接用作图像源。
(3)、PNG
PNG(Portable Network Graphics)是一种广泛使用的图像文件格式,它采用无损压缩算法,旨在提供高质量的图像显示,同时保持文件大小相对较小。
PNG图像解码时,其解码期间的RAM为: 图像宽度 x 图像高度 x 4字节。
在 lv_conf.h 中通过 LV_USE_PNG 可启用PNG解码器,并将 PNG 文件直接用作图像源。
(4)、JPG
JPG文件,全称为JPEG(Joint Photographic Experts Group),是一种广泛使用的图像文件格式,它基于JPEG标准,该标准定义了一种有损压缩算法,用于减少图像文件的大小,同时尽可能保持图像质量。
对于JPG图片的处理,LVGL提供的解码JPG库的实际上是SJPG,这是基于“普通”JPG 的自定义格式,专门用于LVGL。
在LVGL中,解码普通 JPG 会消耗整个未压缩图像大小的 RAM,因此LVGL官方仅推荐用于具有更多 RAM 的设备。并且LVGL提供的JPG解码器,仅解码 JPG 和 SJPG 图像所需的部分,因此在使用过程中,无法对图片进行缩放或旋转。
默认未修改的情况下,sjpg 图像占用的缓存空间为:图像宽度 * 2 * 16 字节。
在 lv_conf.h 中通过 LV_USE_SJPG 可启用JPG解码器,并将 JPG 文件直接用作图像源。
(5)、GIF
GIF文件,全称Graphics Interchange Format,是一种无损压缩的图像文件格式,主要用于创建简单且颜色较少的图像,尤其是具有动画效果的图像。
要解码和显示 GIF 动画,需要以下 RAM 量:
LV_COLOR_DEPTH 8: 3 x 图像宽度 x 图像高度
LV_COLOR_DEPTH 16:4 x 图像宽度 x 图像高度
LV_COLOR_DEPTH 32:5 x 图像宽度 x 图像高度
在 lv_conf.h 中通过 LV_USE_GIF 中启用GIF解码器,并且可将GIF文件直接作为图像源。
3、LVGL显示图片
(1)、图片显示方案
在LVGL中,将图片显示到LCD设备中,主要如下3种方法:
1、将图片转码成源代码,编译时,一起编译到程序代码中
2、将图片转码成二进制bin文件,将图片拷贝到SD卡中,程序运行时进行读取显示
3、将图片直接拷贝到SD卡中,通过LVGL的解码库解析显示图片
在本文中,讲解的是,如何配置MCU工程及LVGL,实现利用LVGL的解码库,将各类图片解析出来,显示到LCD屏幕中。
提醒:在显示BMP图片时,如果是直接将BMP原图片拷贝到SD卡中去显示时。需要对图片进行转码操作,否则BMP图片颜色深度位数不一致会无法显示。
[Warn] (0.484, +484) decoder_open: LV_COLOR_DEPTH == 16 but bpp is 24 (should be 16) (in lv_bmp.c line #160)
[Warn] (0.495, +11) _lv_img_cache_open: Image draw cannot open the image resource (in lv_img_cache.c line #125)
[Warn] (0.506, +11) lv_draw_img: Image draw error (in lv_draw_img.c line #84)
[Warn] (0.515, +9) decoder_open: LV_COLOR_DEPTH == 16 but bpp is 24 (should be 16) (in lv_bmp.c line #160)
[Warn] (0.525, +10) _lv_img_cache_open: Image draw cannot open the image resource (in lv_img_cache.c line #125)
[Warn] (0.536, +11) lv_draw_img: Image draw error (in lv_draw_img.c line #84)
BMP位图原理深度解析及编程实现RGB565图片格式转换-CSDN博客
(2)、MCU工程配置
①、修改内存大小
根据自己所用开发的芯片,找到其启动文件,修改其堆、栈空间。如果堆栈空间不足,运行LVGL程序时,会出现各种莫名其妙的问题,因此,在能芯片容量范围内的情况下,能多大就多大。
打开lv_conf.h文件,需要为LVGL分配多一些的内存空间,找到LV_MEM_SIZE这个宏,将其值设置的大一些。
如下所示为在程序代码调试过程中,堆栈空间值过小,打开图片失败时,产生的错误提示,将启动文件的堆栈空间和LV_MEM_SIZE设置到合适值时,如下所示的错误消失,图片正常显示。
[Warn] (0.693, +693) _lv_img_cache_open: Image draw cannot open the image resource (in lv_img_cache.c line #125)
[Warn] (0.704, +11) lv_draw_img: Image draw error (in lv_draw_img.c line #84)
[Warn] (0.715, +11) _lv_img_cache_open: Image draw cannot open the image resource (in lv_img_cache.c line #125)
[Warn] (0.726, +11) lv_draw_img: Image draw error (in lv_draw_img.c line #84)
[Warn] (0.737, +11) _lv_img_cache_open: Image draw cannot open the image resource (in lv_img_cache.c line #125)
[Warn] (0.748, +11) lv_draw_img: Image draw error (in lv_draw_img.c line #84)
如果MCU设备的内存空间告急严重,可以尝试使用微库,这也能节省一定的内存空间。如下所示是开启微库前后,编译出来的程序内存变化情况。
②、修改LVGL配置文件
在LVGL中,如果需要读取SD卡中的原始图片文件,并显示到LCD中,需要开启宏定义配置。
打开lv_conf.h文件,找到文件系统部分的配置,检查LV_USE_FS_FATFS是否开启,如果还未配置LVGL的文件系统,请查看本文开始部分的内容,进行文件系统的适配。
找到如下图所示的LV_USE_PNG、LV_USE_BMP、LV_USE_SJPG、LV_USE_GIF,根据实际的使用需要,对这些宏定义开关进行配置。
(3)、参考程序源码
#include "lvgl.h"
#include "lv_port_disp.h"
#include "lv_port_indev.h"
#include "lv_conf.h"
#include "diskio.h"
#include "ff.h"
#include "ffconf.h"
void FatFs_Init(void)
{
FATFS fs;
FRESULT res_sd;
while(SD_Init())
{
LED_RED_ON;
delay_ms(500);
LED_RED_OFF;
delay_ms(500);
}
res_sd = f_mount(&fs,"0:", 1);
if (res_sd!=FR_OK)
{
printf("SD Mount FatFs Failed! %d\r\n",res_sd);
while (1);
} else {
printf("SD Mount FatFs Success!\r\n");
}
}
void LVGL_Show_Images(void)
{
FatFs_Init();//初始化文件系统,并且将SD卡挂载
lv_init();
lv_port_disp_init();
lv_port_indev_init();
lv_obj_t *label_1 = lv_label_create(lv_scr_act());
lv_obj_t *label_2 = lv_label_create(lv_scr_act());
lv_obj_t *label_3 = lv_label_create(lv_scr_act());
const lv_font_t *font_txt;
font_txt = &lv_font_montserrat_14;
lv_obj_set_style_text_font(label_1, font_txt, LV_PART_MAIN);
lv_obj_set_style_text_font(label_2, font_txt, LV_PART_MAIN);
lv_obj_set_style_text_font(label_3, font_txt, LV_PART_MAIN);
lv_label_set_text(label_1, "PNG");
lv_label_set_text(label_2, "BMP");
lv_label_set_text(label_3, "JPG");
lv_obj_t *img1 = lv_img_create(lv_scr_act());
lv_img_set_src(img1, "0:/images/1.png");//jpg 11.2K以内,png 11.2K以内
lv_obj_center(img1);
lv_obj_align_to(label_1 ,img1, LV_ALIGN_OUT_LEFT_MID, -10, 0);
lv_obj_t *img2 = lv_img_create(lv_scr_act());
lv_img_set_src(img2, "0:/images/2.bmp");
lv_obj_align_to(img2, img1, LV_ALIGN_OUT_TOP_MID, 0, -40);
lv_obj_align_to(label_2 ,img2, LV_ALIGN_OUT_LEFT_MID, -10, 0);
lv_obj_t *img3 = lv_img_create(lv_scr_act());
lv_img_set_src(img3, "0:/images/3.jpg");
lv_obj_align_to(img3, img1, LV_ALIGN_OUT_BOTTOM_MID, 0, 40);
lv_obj_align_to(label_3 ,img3, LV_ALIGN_OUT_LEFT_MID, -10, 0);
}