lvgl静态文件系统
1. 前言
在lvgl中经常需要显示大量图片,但是MCU的内部flash不足以保存大量的图片,因此需要将图片保存在外部flash上,通过SPI或者QSPI方式读取。
适用于嵌入式的文件系统较多,例如fatfs,但是较为占用资源,并且在文件较多的情况下,读取文件需要查目录,导致速度慢,fatfs的增删改功能我们也用不上,因此想到写一个精简版的静态文件系统。
2.实现过程
lvgl静态文件系统就是将文件保存在系统的flash上,应用层只负责读取,通常不修改文件,总共有以下几个步骤:
- 生成bin文件组
- 打包bin文件,并且生成索引文件
- 将文件透传给MCU的外部flash
- 代码中生成文件对象
- lvgl接口绑定
- 读取使用
2.1 将图片转换为bin文件
使用lvgl自带的工具或者第三方工具将一堆图片生成一堆bin文件,互联网上的资源较多,生成方式在此不再赘述,示例图片:
2.2 打包bin文件,并且生成索引文件
使用python脚本将这堆bin文件打包成一个文件,并且记录每个文件的名称、大小、位置偏移。
使用下面的python脚本可以自动生成包含单文件信息的头文件
import os
import argparse
import datetime as dt
parser = argparse.ArgumentParser()
parser.add_argument('--path', type=str, default = "../../Sources/Img_bin")
args = parser.parse_args()
img_bin_path = args.path
saved_img_bin_name = r"img_compress.bin"
saved_img_head_h = r"img_info.h"
saved_img_c = r"lvgl_img_bin_instance.c"
if os.path.exists(saved_img_bin_name):
print("del :",saved_img_bin_name)
os.remove(saved_img_bin_name)
if os.path.exists(saved_img_head_h):
print("del :",saved_img_head_h)
os.remove(saved_img_head_h)
def get_img_bin(path):
filenames=os.listdir(path)
for filename in filenames:
if filename[-4:] not in '.bin':
filenames.remove(filename)
return filenames
img_bin_name = get_img_bin(img_bin_path)
print("files nums:",len(img_bin_name))
out_img_bin = open(saved_img_bin_name,'wb')
head_h = open(saved_img_head_h,'w')
head_h.writelines("#ifndef MR200_IMG_BIN_INFO_H\r\n")
head_h.writelines("#define MR200_IMG_BIN_INFO_H\r\n")
head_h.writelines("#include "+ "<stdint.h>"+"\r\n")
head_h.writelines("//packet time :"+dt.datetime.now().strftime('%F %T')+"\r\n")
# head_h.writelines("typedef struct img_bin_instance{\n")
# head_h.writelines(" uint32_t addr;\n uint32_t lenth;\n char *name\n}img_bin_instance_t;\r\n")
addr_index = 0
for filename in img_bin_name:
f = open(img_bin_path+'/'+filename,'rb')
data = f.read()
out_img_bin.write(data)
name = filename
size = f.seek(0, os.SEEK_END)
filename = filename.replace(' ','_')
filename = filename.replace('.bin','')
head_h.writelines("#define "+(filename+"_name ").upper()+"\""+name+"\""+"\n")
head_h.writelines("#define "+(filename+"_addr ").upper()+str(hex(addr_index))+"\n")
head_h.writelines("#define "+(filename+"_len ").upper()+str(hex(size))+"\r\n")
addr_index = addr_index + size
f.close()
head_h.writelines("#endif\r\n")
head_h.close()
out_img_bin.close()
print("out_bin_size:",addr_index)
print("end..")
生成的文件信息截图示例:
2.3 将文件透传给MCU的外部flash
写一个脚本,将图片文件传输给MCU,MCU再将图片文件保存在FLASH上,传输之前记得擦除相关FLASH区域,这块比较复杂,需要自己实现,在此不赘述。
2.4 代码中生成文件对象
定义一个结构体,将图片信息保存在代码中,以供lvgl查询读取
typedef struct img_instance
{
const char *name;
uint32_t addr;
uint32_t len;
uint32_t pos;
}img_instance_t;
img_instance_t * get_img_instance_by_name(char *name);
get_img_instance_by_name函数就是通过文件名获取图片在文件中的起始地址与长度:
img_instance_t * get_img_instance_by_name(char *name)
{
for(int i = 0 ; i < sizeof(mr100_img_table)/sizeof(img_instance_t *); i++)
{
img_instance_t *obj = mr100_img_table[i];
if(0 == strcmp(name,obj->name))
{
return obj;
}
}
return NULL;
}
mr200_img_table就是一个指针数组,保存了每个图片的对象地址:
img_instance_t *mr200_img_table[]=
{
&g_img_charge_0,
&g_img_charge_10,
&g_img_charge_20,
&g_img_charge_30,
&g_img_charge_40,
&g_img_charge_50,
&g_img_charge_60,
&g_img_charge_70,
&g_img_charge_80,
&g_img_charge_90,
&g_img_charge_100,
}
每个图片的对象需要手动定义,例如:
static img_instance_t g_img_charge_0 =
{
.name = CHARGE_0_P_NAME,
.addr = CHARGE_0_P_ADDR,
.len = CHARGE_0_P_LEN,
};
2.5 lvgl接口绑定
lvgl文件接口主要绑定以下几个接口:
fs_open,本质上就是告诉lvgl所需图片文件的起始地址:
static void * fs_open(lv_fs_drv_t * drv, const char * path, lv_fs_mode_t mode)
{
static img_instance_t *fs_img = NULL;
// log_println("open img :%s",path);
if(mode == LV_FS_MODE_WR) {
}
else if(mode == LV_FS_MODE_RD) {
fs_img = get_img_instance_by_name((char *)path);
fs_img ->pos = 0 ;
// log_println("obj name :%s",fs_img->name);
// log_println("obj addr :%d",fs_img->addr);
// log_println("obj len :%d",fs_img->len);
}
else if(mode == (LV_FS_MODE_WR | LV_FS_MODE_RD)) {
}
return fs_img;
}
fs_close,将文件句柄赋值为NULL,没啥好说的
static lv_fs_res_t fs_close(lv_fs_drv_t * drv, void * file_p)
{
lv_fs_res_t res = LV_FS_RES_OK;
if(file_p == NULL)
{
return LV_FS_RES_NOT_IMP;
}
img_instance_t *fs_img = (img_instance_t *)file_p;
fs_img = NULL;
(void) fs_img ;
/*Add your code here*/
return res;
}
fs_read,从flash中读取所需文件内容,并且更新pos:
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_NOT_IMP;
if(file_p == NULL)
{
// log_println("fs_read error!");
return LV_FS_RES_NOT_IMP;
}
img_instance_t *fs_img = (img_instance_t *)file_p;
uint32_t size = (btr > (fs_img->len - fs_img->pos)) ? (fs_img->len - fs_img->pos) : btr;
// log_println("read :%s addr:%d,len:%d",fs_img->name,fs_img->addr + fs_img->pos,size);
mod_param_read_img(fs_img->addr + fs_img->pos,buf,size);
*br = size;
fs_img->pos += size;
/*Add your code here*/
res = LV_FS_RES_OK;
return res;
}
fs_seek,lvgl内部进行pos切换:
static lv_fs_res_t fs_seek(lv_fs_drv_t * drv, void * file_p, uint32_t pos, lv_fs_whence_t whence)
{
lv_fs_res_t res = LV_FS_RES_OK;
if(file_p == NULL)
{
return LV_FS_RES_NOT_IMP;
}
img_instance_t *fs_img = (img_instance_t *)file_p;
switch (whence)
{
case LV_FS_SEEK_SET:
fs_img->pos = pos;
break;
case LV_FS_SEEK_CUR:
fs_img->pos += pos;
break;
case LV_FS_SEEK_END:
fs_img->pos = fs_img->len;
break;
default:
break;
}
/*Add your code here*/
return res;
}
fs_tell,lvgl通过这个接口获取当前文件的指针所在位置:
static lv_fs_res_t fs_tell(lv_fs_drv_t * drv, void * file_p, uint32_t * pos_p)
{
lv_fs_res_t res = LV_FS_RES_OK;
if(file_p == NULL)
{
return LV_FS_RES_NOT_IMP;
}
img_instance_t *fs_img = (img_instance_t *)file_p;
*pos_p = fs_img->pos;
/*Add your code here*/
return res;
}
2.6 读取使用
就是通用的lvgl文件调用方式,例如:
lv_obj_t * img_logo = lv_img_create(background);
lv_img_set_src(img_logo, "P:logo.bin");
lv_obj_set_style_img_opa(img_logo,0,0);
lv_obj_align(img_logo, LV_ALIGN_CENTER, 0, 0);
3. 总结
原理很简单,但是步骤上比较复杂,各位大佬有什么好的意见可以在下面留言。 ❤
本作品采用 知识共享署名-相同方式共享 4.0 国际许可协议 进行许可。
你好,这些接口如fs_seek在lvgl内部怎么调用呢?方便话想加个微信交流一下,无限感谢,手机号同微13764371695
在port里面,将fs_seek绑定回调后,并且注册为存储设备,lvgl内部会自动调用