> 文档中心 > 【学习笔记】XR872 GUI Littlevgl 8.0 移植(文件系统)

【学习笔记】XR872 GUI Littlevgl 8.0 移植(文件系统)


不得不提

 在移植的过程中,发现 LVGL 的文件操作接口并不十分完善,在我看来, LVGL 的文件操作接口,应该更多的是为了 LVGL 内部接口方便读取资源文件而设立的,例如读取图像文件,加载字库文件等等。

遍历目录也形同鸡肋,特别是读取目录的接口定义,简直要吐血,最后一个参数居然没有附带缓冲区长度,在使用的过程中,程序员如果对缓冲区长度未把握好,极容易出现内存溢出的问题。

例如缓冲区定义为 64 个字节,如果在调用 lv_fs_dir_read() 接口读取目录时,目录路径深度超过 64 字节,就会出现缓冲区溢出的问题,这很有可能在研发前期没有复现,而在用户使用的时候,才会出现。这对一个产品来说,是一个非常严重的问题。

/** * Read the next filename form a directory. * The name of the directories will begin with '/' * @param rddir_p pointer to an initialized 'fs_dir_t' variable * @param fn pointer to a buffer to store the filename * @return LV_FS_RES_OK or any error from lv_fs_res_t enum */lv_fs_res_t lv_fs_dir_read(lv_fs_dir_t * rddir_p, char * fn);对应的实现:lv_fs_res_t (*dir_read_cb)(struct _lv_fs_drv_t * drv, void * rddir_p, char * fn);可以看到并没有字段可以指出,承担结果返回的 fn 缓冲区有多大。

接口分析

LGVL 对文件操作的接口进行统一重新定义,通过结构体 lv_fs_drv_t 对这些操作接口进行管理,LVGL 内部涉及到文件操作相关的接口,均调用封装过的接口,这样一来就得以脱离具体的文件系统接口,便于移植到不同的平台上使用。

一个 lv_fs_drv_t 表示为一种文件系统驱动程序,LVGL 通过链表来保存多个文件系统驱动程序,当调用打开文件或者目录时,会根据路径的第一个字符,遍历该链表,匹配指定的文件系统驱动程序。

如下图所示,可以实现三种存储类型不同的文件操作,SD卡文件读写实现(S),Flash 文件读写实现(F),Samba 网络文件读写实现(N),并通过 lv_fs_drv_register() 注册到 LVGL 系统中,LVGL 会以链表的方式组织这三种操作接口。

当时需要打开某些文件的时候,如果该文件存储在 SD 卡中,那么路径以 S:/ 开头即可,如果该文件存储在 Flash 中,那么路径以 F:/ 开头,同理,Samba 则以 N:/ 开头,LVGL 会通过路径的第一个字符,来遍历整个链表,只有匹配成功,才会调用相应的文件操作接口。

网上有看到通过区分 letter 的方式来 switch 不同的操作方法,这就意味着不同的文件读写实现会耦合到一个源文件里面,其实大可不必,LVGL 已经考虑到很周全了,按照 LVGL 的方式,完全可以实现不同的文件读写实现分属不同的源文件中,彼此互不影响,新增新的文件读写实现,增加新的源文件即可,不需要修改原来的源文件。(是不是很像设计模式中的开闭原则)

 移植方法

通过上面分析,只需要按照要求实现 lv_fs_drv_t 中定义的回调函数,然后通过 lv_fs_drv_register() 注册到链表中即可。

LVGL 8.0 版本的 lv_port_fs_template.c 模板文件的接口定义与 lv_fs_drv_t 的定义并不完全匹配,不能直接使用。

这是原来错误的接口定义:

static void fs_init(void);static lv_fs_res_t fs_open (lv_fs_drv_t * drv, void * file_p, const char * path, lv_fs_mode_t mode);static lv_fs_res_t fs_close (lv_fs_drv_t * drv, void * file_p);static lv_fs_res_t fs_read (lv_fs_drv_t * drv, void * file_p, void * buf, uint32_t btr, uint32_t * br);static lv_fs_res_t fs_write(lv_fs_drv_t * drv, void * file_p, const void * buf, uint32_t btw, uint32_t * bw);static lv_fs_res_t fs_seek (lv_fs_drv_t * drv, void * file_p, uint32_t pos);static lv_fs_res_t fs_size (lv_fs_drv_t * drv, void * file_p, uint32_t * size_p);static lv_fs_res_t fs_tell (lv_fs_drv_t * drv, void * file_p, uint32_t * pos_p);static lv_fs_res_t fs_remove (lv_fs_drv_t * drv, const char *path);static lv_fs_res_t fs_trunc (lv_fs_drv_t * drv, void * file_p);static lv_fs_res_t fs_rename (lv_fs_drv_t * drv, const char * oldname, const char * newname);static lv_fs_res_t fs_free (lv_fs_drv_t * drv, uint32_t * total_p, uint32_t * free_p);static lv_fs_res_t fs_dir_open (lv_fs_drv_t * drv, void * rddir_p, const char *path);static lv_fs_res_t fs_dir_read (lv_fs_drv_t * drv, void * rddir_p, char *fn);static lv_fs_res_t fs_dir_close (lv_fs_drv_t * drv, void * rddir_p);

这是正确的接口定义:

static void fs_init(fs_private_t *fs_private);static bool fs_ready(struct _lv_fs_drv_t * drv);static void *fs_open(struct _lv_fs_drv_t * drv, const char * path, lv_fs_mode_t mode);static lv_fs_res_t fs_close(struct _lv_fs_drv_t * drv, void * file_p);static lv_fs_res_t fs_read(struct _lv_fs_drv_t * drv, void * file_p, void * buf, uint32_t btr, uint32_t * br);static lv_fs_res_t fs_write(struct _lv_fs_drv_t * drv, void * file_p, const void * buf, uint32_t btw, uint32_t * bw);static lv_fs_res_t fs_seek(struct _lv_fs_drv_t * drv, void * file_p, uint32_t pos, lv_fs_whence_t whence);static lv_fs_res_t fs_tell(struct _lv_fs_drv_t * drv, void * file_p, uint32_t * pos_p);static void * fs_dir_open(struct _lv_fs_drv_t * drv, const char * path);static lv_fs_res_t fs_dir_read(struct _lv_fs_drv_t * drv, void * rddir_p, char * fn);static lv_fs_res_t fs_dir_close(struct _lv_fs_drv_t * drv, void * rddir_p);

只要按照要求实现 lv_fs_drv_t 中定义的回调函数,并注册就行,所以实际上不需要参考  lv_port_fs_template.c 写,自己实现就行。

以下是基于 XR872 ,xradio-skylark-sdk v1.1.1 ,fatfs 实现的移植成功的代码:

/** * @file lv_port_fs_templ.c * */ /*Copy this file as "lv_port_fs.c" and set this value to "1" to enable content*/#if 1/********************* *      INCLUDES *********************/#include "fs/fatfs/ff.h"#include "common/framework/fs_ctrl.h"#include "lv_port_fs.h"#include #define iprintf(fmt, arg...)printf("[lv_port_fs] "fmt, ##arg)/********************* *      DEFINES *********************//********************** *      TYPEDEFS **********************/typedef struct{bool is_ready;}fs_private_t;/********************** *  STATIC PROTOTYPES **********************/static void fs_init(fs_private_t *fs_private);static bool fs_ready(struct _lv_fs_drv_t * drv);static void *fs_open(struct _lv_fs_drv_t * drv, const char * path, lv_fs_mode_t mode);static lv_fs_res_t fs_close(struct _lv_fs_drv_t * drv, void * file_p);static lv_fs_res_t fs_read(struct _lv_fs_drv_t * drv, void * file_p, void * buf, uint32_t btr, uint32_t * br);static lv_fs_res_t fs_write(struct _lv_fs_drv_t * drv, void * file_p, const void * buf, uint32_t btw, uint32_t * bw);static lv_fs_res_t fs_seek(struct _lv_fs_drv_t * drv, void * file_p, uint32_t pos, lv_fs_whence_t whence);static lv_fs_res_t fs_tell(struct _lv_fs_drv_t * drv, void * file_p, uint32_t * pos_p);static void * fs_dir_open(struct _lv_fs_drv_t * drv, const char * path);static lv_fs_res_t fs_dir_read(struct _lv_fs_drv_t * drv, void * rddir_p, char * fn);static lv_fs_res_t fs_dir_close(struct _lv_fs_drv_t * drv, void * rddir_p);/********************** *  STATIC VARIABLES **********************//********************** * GLOBAL PROTOTYPES **********************//********************** *      MACROS **********************//********************** *   GLOBAL FUNCTIONS **********************/void lv_port_fs_init(void){    /*----------------------------------------------------     * Initialize your storage device and File System     * -------------------------------------------------*/static fs_private_t fs_private;lv_memset(&fs_private, 0x00, sizeof(fs_private_t));    fs_init(&fs_private);    /*---------------------------------------------------     * Register the file system interface in LVGL     *--------------------------------------------------*/    /*Add a simple drive to open images*/    static lv_fs_drv_t fs_drv;    lv_fs_drv_init(&fs_drv);    /*Set up fields...*/    fs_drv.letter = 'S';fs_drv.ready_cb = fs_ready;fs_drv.user_data = (void *)&fs_private;    fs_drv.open_cb = fs_open;    fs_drv.close_cb = fs_close;    fs_drv.read_cb = fs_read;    fs_drv.write_cb = fs_write;    fs_drv.seek_cb = fs_seek;    fs_drv.tell_cb = fs_tell; fs_drv.dir_open_cb = fs_dir_open;    fs_drv.dir_read_cb = fs_dir_read;fs_drv.dir_close_cb = fs_dir_close;    lv_fs_drv_register(&fs_drv);}/********************** *   STATIC FUNCTIONS **********************/ //LV_FS_RES_OK=0,//LV_FS_RES_HW_ERR,/*低级硬件错误*///LV_FS_RES_FS_ERR,/*文件系统结构中的错误*///LV_FS_RES_NOT_EX,/*驱动程序、文件或目录不存在*///LV_FS_RES_FULL,/*磁盘已满*///LV_FS_RES_LOCKED,/*文件已打开*///LV_FS_RES_DENIED,/*访问被拒绝。检查“fs_打开”模式和写保护*///LV_FS_RES_BUSY,/*文件系统现在无法处理它,请稍后再试*///LV_FS_RES_TOUT,/*过程时间路由*///LV_FS_RES_NOT_IMP,/*请求的函数未实现*///LV_FS_RES_OUT_OF_MEM,/*内存不足,无法执行内部操作*///LV_FS_RES_INV_PARAM,/*参数中的参数无效*///LV_FS_RES_UNKNOWN,/*其他未知错误*/static lv_fs_res_t to_lvfs_res(FRESULT res){switch(res){case FR_OK: return LV_FS_RES_OK;/*(0)成功*/case FR_DISK_ERR: return LV_FS_RES_HW_ERR;/*(1)在低级别磁盘I/O层中发生了一个硬错误*/case FR_INT_ERR: return LV_FS_RES_FS_ERR;/*(2)断言失败*/case FR_NOT_READY: return LV_FS_RES_HW_ERR;/*(3)物理驱动器无法工作*/case FR_NO_FILE: return LV_FS_RES_NOT_EX;/*(4)找不到该文件*/case FR_NO_PATH: return LV_FS_RES_NOT_EX;/*(5)找不到路径*/case FR_INVALID_NAME: return LV_FS_RES_INV_PARAM;/*(6)路径名格式无效*/case FR_DENIED: return LV_FS_RES_DENIED;/*(7)拒绝访问:磁盘以满\使用写模式打开只读文件\删除只读文件...等等*/case FR_EXIST : return LV_FS_RES_DENIED;/*(8)已经存在同名的文件或目录*/case FR_INVALID_OBJECT:  return LV_FS_RES_INV_PARAM;/*(9)文件/目录对象无效*/case FR_WRITE_PROTECTED:  return LV_FS_RES_DENIED;/*(10)物理驱动器是写保护的*/case FR_INVALID_DRIVE:  return LV_FS_RES_INV_PARAM;/*(11)逻辑驱动器号无效*/case FR_NOT_ENABLED:  return LV_FS_RES_HW_ERR;/*(12)当前卷没有工作区*/case FR_NO_FILESYSTEM:  return LV_FS_RES_HW_ERR;/*(13)没有有效的FAT卷*/case FR_MKFS_ABORTED:  return LV_FS_RES_INV_PARAM;/*(14)f_MKFS()在格式化开始前终止*/case FR_TIMEOUT:  return LV_FS_RES_BUSY;/*(15)无法在定义的时间段内获得访问卷的授权*/case FR_LOCKED:  return LV_FS_RES_LOCKED;/*(16)根据文件共享策略拒绝该操作*/case FR_NOT_ENOUGH_CORE:  return LV_FS_RES_OUT_OF_MEM;/*(17)无法分配LFN工作缓冲区*/case FR_TOO_MANY_OPEN_FILES:  return LV_FS_RES_BUSY;/*(18)打开的文件数目大于 FF_FS_LOCK*/case FR_INVALID_PARAMETER:  return LV_FS_RES_INV_PARAM;/*(19)给定参数无效*/}return LV_FS_RES_UNKNOWN;}/*Initialize your Storage device and File system.*/static void fs_init(fs_private_t *fs_private){    /*E.g. for FatFS initialize the SD card and FatFS itself*/if (fs_ctrl_mount(FS_MNT_DEV_TYPE_SDCARD, 0) != 0) {fs_private->is_ready = false;iprintf("fs_ctrl_mount failed...\n");return ;}iprintf("fs_ctrl_mount success...\n");fs_private->is_ready = true;return ;}/************************************************************************************函数: fs_ready*功能:LVGL 会通过此函数判断文件系统是否就绪*参数:*drv:上下文, 我的理解, 便于取 letter、user_data 成员变量*返回值:*true:已就绪   *false:未就绪***********************************************************************************/static bool fs_ready(struct _lv_fs_drv_t * drv){fs_private_t *fs_private = (fs_private_t *)drv->user_data;if(false == fs_private->is_ready){fs_private->is_ready = fs_ctrl_mount(FS_MNT_DEV_TYPE_SDCARD, 0) != 0 ? false : true;if(false == fs_private->is_ready){iprintf("fs_ctrl_mount failed...\n");}}return fs_private->is_ready;}/************************************************************************************函数: fs_open*功能:LVGL 会通过此函数打开指定的文件*参数:*drv:上下文, 我的理解, 便于取 letter、user_data 成员变量*path:要打开的文件路径*mode:打开模式 LV_FS_MODE_WR\LV_FS_MODE_RD*返回值:*>0:返回打开文件的句柄,后续操作皆以此句柄为核心(void * file_p)*NULL:打开文件失败***********************************************************************************/static void *fs_open(struct _lv_fs_drv_t * drv, const char * path, lv_fs_mode_t mode){FRESULT fr_res;unsigned char f_mode = 0x00;FIL *fil = (FIL *)lv_mem_alloc(sizeof(FIL));if(NULL == fil){iprintf("fs_open: lv_mem_alloc failed %d Byte...\n", sizeof(FIL));return NULL;}if(mode & LV_FS_MODE_WR){f_mode |= FA_WRITE | FA_CREATE_ALWAYS;}if(mode & LV_FS_MODE_RD){f_mode |= FA_READ;}    if ( (fr_res = f_open(fil, path, f_mode)) != FR_OK) {lv_mem_free(fil);iprintf("f_open failed:[0x%.2X][%d][%s]...\n", f_mode, fr_res, path);return NULL;}return (void *)fil;}/************************************************************************************函数: fs_close*功能:LVGL 会通过此函数关闭指定的文件*参数:*drv:上下文, 我的理解, 便于取 letter、user_data 成员变量*file_p: 文件对应的句柄, 由 fs_open() 成功时取得*返回值:参考 lv_fs_res_t***********************************************************************************/static lv_fs_res_t fs_close(struct _lv_fs_drv_t * drv, void * file_p){if(file_p){f_close((FIL *)file_p);lv_mem_free(file_p);return LV_FS_RES_OK;}return LV_FS_RES_INV_PARAM;}/************************************************************************************函数: fs_close*功能:LVGL 会通过此函数读取指定的文件*参数:*drv:上下文, 我的理解, 便于取 letter、user_data 成员变量*file_p: 文件对应的句柄, 由 fs_open() 成功时取得*buf:存储文件数据的缓冲区*btr:存储文件数据的缓冲区大小*br:实际读取文件的数据长度*返回值:参考 lv_fs_res_t***********************************************************************************/static lv_fs_res_t fs_read(struct _lv_fs_drv_t * drv, void * file_p, void * buf, uint32_t btr, uint32_t * br){FRESULT fr_res = FR_OK;if ( (fr_res = f_read((FIL *)file_p, buf, btr, br)) != FR_OK) {iprintf("f_read failed:[%d]...\n", fr_res);}else{iprintf("f_read success:btr:[%d] br:[%d]...\n", btr, *br);}return to_lvfs_res(fr_res);}/************************************************************************************函数: fs_write*功能:LVGL 会通过此函数写入指定的文件*参数:*drv:上下文, 我的理解, 便于取 letter、user_data 成员变量*file_p: 文件对应的句柄, 由 fs_open() 成功时取得*buf:存储要写入文件的数据*btw:写于文件数据的长度*bw:实际写入文件的数据长度*返回值:参考 lv_fs_res_t***********************************************************************************/static lv_fs_res_t fs_write(struct _lv_fs_drv_t * drv, void * file_p, const void * buf, uint32_t btw, uint32_t * bw){FRESULT fr_res = f_write((FIL *)file_p, buf, btw, bw);if(FR_OK == fr_res && *bw < btw){iprintf("fs_write failed disk full: %d 0:返回打开目录的句柄,后续操作皆以此句柄为核心(void * rddir_p)*NULL:打开目录失败***********************************************************************************/static void * fs_dir_open(struct _lv_fs_drv_t * drv, const char * path){FRESULT fr_res = FR_OK;DIR *dir = (DIR *)lv_mem_alloc(sizeof(DIR));if(NULL == dir){iprintf("f_opendir: lv_mem_alloc %d byte failed...\n", sizeof(DIR));return NULL;}    if ( (fr_res = f_opendir(dir, path)) != FR_OK) {lv_mem_free(dir);iprintf("f_opendir failed:[%d][%s]...\n", fr_res, path);return NULL;}return (void *)dir;}/************************************************************************************函数: fs_dir_read*功能:LVGL 会通过此函数获取当前目录的条目(即该目录下有哪些文件夹和文件)*参数:*drv:上下文, 我的理解, 便于取 letter、user_data 成员变量*rddir_p: 目录对应的句柄, 由 fs_dir_open() 成功时取得*fn:通过此参数返回目录内容, 一次只返回一个条目(如有十个文件, 只返回一个)*返回值:参考 lv_fs_res_t*备注说明:*1. 没有条目或读取完条目则返回空文本内容到 fn (即 \0)*2. LGVL 对上述似乎并没有明确要求, 并且参数也显得不合理(缺少缓冲区长度信息)*3. 使用时务必注意 fn 越界问题, fn 缓冲区大小必须大于 FILINFO->fname+1 大小***********************************************************************************/static lv_fs_res_t fs_dir_read(struct _lv_fs_drv_t * drv, void * rddir_p, char * fn){FRESULT fr_res = FR_OK;FILINFO fno;fr_res = f_readdir((DIR *)rddir_p, &fno);if (fr_res != FR_OK){iprintf("f_readdir failed:[%d]...\n", fr_res);return to_lvfs_res(fr_res);}if('\0' == fno.fname[0]){fn[0] = '\0';return LV_FS_RES_OK;}if (fno.fattrib & AM_DIR){fn[0] = '/';fn++;}// 注意, 这里会存在内存溢出的风险, LVGL 的接口并未提供缓冲区长度信息strcpy(fn, fno.fname); // strncpy(fn, fno.fname, fn_size);return LV_FS_RES_OK;}/************************************************************************************函数: fs_dir_close*功能:LVGL 会通过此函数关闭指定目录*参数:*drv:上下文, 我的理解, 便于取 letter、user_data 成员变量*rddir_p: 目录对应的句柄, 由 fs_dir_open() 成功时取得*返回值:参考 lv_fs_res_t***********************************************************************************/static lv_fs_res_t fs_dir_close(struct _lv_fs_drv_t * drv, void * rddir_p){if(rddir_p){f_closedir((DIR *)rddir_p);lv_mem_free(rddir_p);return LV_FS_RES_OK;}return LV_FS_RES_INV_PARAM;}#else /*Enable this file at the top*//*This dummy typedef exists purely to silence -Wpedantic.*/typedef int keep_pedantic_happy;#endif

测试代码

#include "common/framework/platform_init.h"#include #include #include "prj_config.h"#include "kernel/os/os.h"#include "lvgl/lvgl.h"#include "lvgl_driver/lv_port_disp.h"#include "lvgl_driver/lv_port_fs.h"void lv_example_fs_file(void){#define TEST_FILE_PATH"S:/test.txt"#define TEST_TEXT"lvgl fs write read test..."lv_fs_file_t file;lv_fs_res_t fsres = LV_FS_RES_OK;    fsres = lv_fs_open(&file, TEST_FILE_PATH, LV_FS_MODE_RD | LV_FS_MODE_WR);    if(fsres != LV_FS_RES_OK){ printf("lv_fs_open failed:[%d][%s]\n", fsres, TEST_FILE_PATH);return ;}printf("lv_fs_open success:[%s]\n", TEST_FILE_PATH);uint32_t wbyte = 0x00, rbyte = 0x00;fsres = lv_fs_write(&file, TEST_TEXT, strlen(TEST_TEXT), &wbyte);if(fsres != LV_FS_RES_OK){ printf("lv_fs_write failed:[%d][%s]\n", fsres, TEST_FILE_PATH);lv_fs_close(&file);return ;}printf("lv_fs_write success:[%s]\n", TEST_FILE_PATH);lv_fs_seek(&file, 0x00, LV_FS_SEEK_SET);char buff[100] = {0};fsres = lv_fs_read(&file, buff, sizeof(buff), &rbyte);if(fsres != LV_FS_RES_OK){ printf("lv_fs_read failed:[%d][%s]\n", fsres, TEST_FILE_PATH);lv_fs_close(&file);return ;}printf("lv_fs_read success:[%s]\n", TEST_FILE_PATH);lv_fs_close(&file);printf("lv_fs_close success:[%s]\n", TEST_FILE_PATH);printf("write %d byte, read %d byte\n", wbyte, rbyte);printf("read buff:[%s]\n", buff);return ;}void lv_example_fs_dir(void){#define TEST_DIR_PATH"S:/"lv_fs_dir_t rddir;lv_fs_res_t fsres = LV_FS_RES_OK;fsres = lv_fs_dir_open(&rddir, TEST_DIR_PATH);if(fsres != LV_FS_RES_OK){printf("lv_fs_dir_open failed:[%d][%s]\n", fsres, TEST_DIR_PATH);return ;}printf("lv_fs_dir_open success:[%s]\n", TEST_DIR_PATH);char buff[257] = {0};while( ( fsres = lv_fs_dir_read(&rddir, buff) ) == LV_FS_RES_OK){if('\0' == buff[0]){printf("read dir done\n");lv_fs_dir_close(&rddir);printf("lv_fs_dir_close success:[%s]\n", TEST_DIR_PATH);return ;}printf("[%s]\n", buff);}printf("lv_fs_dir_read failed:[%d][%s]\n", fsres, TEST_DIR_PATH);return ;}int main(void){platform_init();lv_init(); lv_port_fs_init();lv_tick_handle_init();lv_example_fs_file();lv_example_fs_dir();while(1){OS_Sleep(3);}return 0;}