1. 前言

在lvgl中经常需要显示大量图片,但是MCU的内部flash不足以保存大量的图片,因此需要将图片保存在外部flash上,通过SPI或者QSPI方式读取。

适用于嵌入式的文件系统较多,例如fatfs,但是较为占用资源,并且在文件较多的情况下,读取文件需要查目录,导致速度慢,fatfs的增删改功能我们也用不上,因此想到写一个精简版的静态文件系统。

2.实现过程

lvgl静态文件系统就是将文件保存在系统的flash上,应用层只负责读取,通常不修改文件,总共有以下几个步骤:

  • 生成bin文件组
  • 打包bin文件,并且生成索引文件
  • 将文件透传给MCU的外部flash
  • 代码中生成文件对象
  • lvgl接口绑定
  • 读取使用

2.1 将图片转换为bin文件

使用lvgl自带的工具或者第三方工具将一堆图片生成一堆bin文件,互联网上的资源较多,生成方式在此不再赘述,示例图片:

step1.png

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..")

生成的文件信息截图示例:

img_info.png

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. 总结

原理很简单,但是步骤上比较复杂,各位大佬有什么好的意见可以在下面留言。 ❤

文章目录