STM32在LVGL上实现移植FatFs文件系统(保姆级详细教程)
一、移植背景
硬件平台:STM32H743
软件平台:已经移植好了LVGL_v8.3.11和FatFs_v0.16的工程

本文结尾有彩蛋哦!!!~

二、移植前准备
1.确保你的LVGL已经移植好并且正常显示GUI。
下面是STM32的LVGL保姆级移植教程:
STM32 在Keil 5 下移植LVGL_v8.3.11(步骤堪称保姆级,致力打造史上最详细lvgl移植教程,小白上手即可实现100%成功移植!!!)-CSDN博客
https://blog.csdn.net/qq_34885669/article/details/149667437?spm=1001.2014.3001.5502
2.确保你的FatFs已经移植好并且正常读写SD卡。
下面是STM32的FatFs保姆级移植教程:
保姆级移植教程待更新。。。。(这个教程资料比较成熟,建议网上找)
由于本文重点是实现LVGL上移植FatFs,所以LVGL和FatFs各自的移植就不在这里去介绍了,请自行查看上面的教程,把LVGL和FatFs移植好之后再来看本文章后面的内容。
备注:
LVGL和FatFs各自的移植意思就是:你要先把LVGL移植到你带屏幕的工程里面并且实现LVGL显示GUI正常,然后要把FatFs和你的SD卡驱动移植对接好,确保你移植的FatFs可以正常打开读写SD卡里面的文件。
实现LVGL上移植FatFs意思就是:把LVGL 自己的文件系统抽象模块 lv_fs和FatFs的标准 文件系统APIf_open, f_read, f_close, f_opendir, f_readdir, f_stat 移植在一起,实现LVGL可以通过自己的文件系统API来操作SD卡里面的内容,比如使用LVGL显示SD卡里面的图片。
三、LVGL 文件系统抽象层API移植
所谓的LVGL 文件系统抽象层API移植,简单来讲就是把FatFs的文件系统操作API和LVGL 文件系统抽象层API对接在一起,实现LVGL可以通过自己的文件系统API来操作SD卡里面的内容。和之前LVGL移植适配屏幕驱动一样,LVGL文件系统一样是有一个lv_port_fs.c这样的文件系统适配层文件,接下来我们就是要对这个文件做移植适配。
备注:在LVGL源码的examples\porting路径下就是LVGL官方给出的显示,输入,文件系统移植接口适配文件示例,记得要添加到你的工程里面。

1.启用lv_port_fs.c
在使用#if 1启用lv_port_fs.c后,我们还需要添加好FatFs文件系统头文件和MCU一下你用到的头文件:
2.注册FatFs文件系统文件操作API
找到lv_port_fs.c的lv_port_fs_init函数,如图:

lv_port_fs_init它的作用简单来说就是把LVGL自己的文件操作回调接口和FatFs的文件操作API做一下一一对应,然后再将对应好的接口注册到LVGL里面,这样LVGL在打开SD卡里面的文件的时候就会调用到我们对应好的FatFs文件系统操作API去读写SD卡里面的文件。
这个函数我们需要改动的其实很少,只需要设置一下fs_drv.letter:
注册文件系统接口时设置的fs_drv.letter这个所谓的盘符,就是在LVGL层面设置文件路径时用到的盘符,但是实际上FatFs在对接SD卡时也有一个盘符,严格来说是FatFs的驱动器号。这个盘符也是移植中最容易出问题的地方,我下面举一个例子你们便一目了然了:
我在移植FatFs时给SD卡定义的驱动器号为1:

假设:在SD卡的根目录里面有个BMP文件夹,里面有个test.bmp图片,fs_drv.letter = 'S'。diskio.c文件里SD卡驱动器号为1。
那么:
在LVGL层面显示照片时调用lv_img_set_src设置图片路径应该使用:S:/BMP/test.bmp
在FatFs层面的盘符却一般都是0~9这样的数字,在FatFs里面叫驱动器号,假设SD卡驱动器号是1,即在FatFs层面这个图片的路径应该是:1:/BMP/test.bmp
总结来说:
当你使用LVGL层面的API去操作SD卡里面的文件,那就得使用LVGL的fs_drv.letter开头的路径,比如S:/BMP/test.bmp
当你使用FatFs层面的API去操作SD卡里面的文件,那就得使用FatFs的驱动器号开头的路径,比如1:/BMP/test.bmp
3.fs_init文件系统文件操作API移植
fs_init里面其实主要就是调用FatFs层面的f_mount去挂载SD卡,我们要注意的就是f_mount的第二个参数要填的是SD卡对应的驱动器号,不是LVGL层面的fs_drv.letter。

static void fs_init(void)
{/*E.g. for FatFS initialize the SD card and FatFS itself*//*You code here*/static FATFS fs; // 文件系统对象// 挂载 FATFSFRESULT res = f_mount(&fs, "1:", 1);if(res != FR_OK) {printf("LVGL fatfs mount failed: %d\r\n", res);}else{printf("LVGL fatfs mount succeed: %d\r\n", res);}
}
4.fs_open文件系统文件操作API移植
移植fs_open,我们主要要注意的就是2点:文件操作模式和文件路径 的适配移植。
需要注意的是, LVGL 传进来的path已经没有盘符,比如 "BMP/test.bmp" ,我们需要把盘符补充上去,变成"1:/BMP/test.bmp"然后才能给f_open使用:
static void * fs_open(lv_fs_drv_t * drv, const char * path, lv_fs_mode_t mode)
{lv_fs_res_t res = LV_FS_RES_NOT_IMP;BYTE fatfs_mode = 0;/*把LVGL的打开文件的模式和FatFs一一对应好*/if(mode == LV_FS_MODE_WR) fatfs_mode = FA_WRITE | FA_OPEN_ALWAYS;else if(mode == LV_FS_MODE_RD) fatfs_mode = FA_READ;else if(mode == (LV_FS_MODE_WR | LV_FS_MODE_RD)) fatfs_mode = FA_READ | FA_WRITE | FA_OPEN_ALWAYS;/* LVGL 传进来的path已经没有盘符,比如 "test.txt" ,我们需要把盘符补充上去,然后才能给f_open使用*/char *full_path = lv_mem_alloc(256);if(full_path == NULL) return NULL;lv_snprintf(full_path, 256, "1:%s", path); // 注意 path 已经是 "/xxx"//printf("fs_open full_path=%s \n",full_path);FIL * f = lv_mem_alloc(sizeof(FIL)); // 为每个文件分配空间if(f == NULL){printf("lv_mem_alloc failed ! (f == NULL)\r\n");return NULL;}FRESULT fr = f_open(f, full_path, fatfs_mode);if(fr == FR_OK) {printf("fs_open full_path=%s OK !\n",full_path);return f;}else {printf("f_open failed: %d, full_path=%s\n", fr, full_path);lv_mem_free(f);return NULL;}
}
5.fs_close文件系统文件操作API移植
static lv_fs_res_t fs_close(lv_fs_drv_t * drv, void * file_p)
{LV_UNUSED(drv);if(FR_OK == f_close((FIL*)file_p)) {lv_mem_free(file_p);return LV_FS_RES_OK;}else{lv_mem_free(file_p);return LV_FS_RES_UNKNOWN;}
}
6.fs_read文件系统文件操作API移植
static lv_fs_res_t fs_read(lv_fs_drv_t * drv, void * file_p, void * buf, uint32_t btr, uint32_t * br)
{/*Add your code here*/LV_UNUSED(drv);FRESULT res = f_read(file_p, buf, btr, (UINT *)br);if(res == FR_OK) return LV_FS_RES_OK;else return LV_FS_RES_UNKNOWN;
}
7.fs_write文件系统文件操作API移植
static lv_fs_res_t fs_write(lv_fs_drv_t * drv, void * file_p, const void * buf, uint32_t btw, uint32_t * bw)
{/*Add your code here*/LV_UNUSED(drv);FRESULT res = f_write(file_p, buf, btw, (UINT *)bw);if(res == FR_OK) return LV_FS_RES_OK;else return LV_FS_RES_UNKNOWN;
}
8.fs_seek文件系统文件操作API移植
static lv_fs_res_t fs_seek(lv_fs_drv_t * drv, void * file_p, uint32_t pos, lv_fs_whence_t whence)
{/*Add your code here*/LV_UNUSED(drv);switch(whence) {case LV_FS_SEEK_SET:f_lseek(file_p, pos);break;case LV_FS_SEEK_CUR:f_lseek(file_p, f_tell((FIL *)file_p) + pos);break;case LV_FS_SEEK_END:f_lseek(file_p, f_size((FIL *)file_p) + pos);break;default:break;}return LV_FS_RES_OK;
}
9.fs_tell文件系统文件操作API移植
static lv_fs_res_t fs_tell(lv_fs_drv_t * drv, void * file_p, uint32_t * pos_p)
{/*Add your code here*/LV_UNUSED(drv);*pos_p = f_tell((FIL *)file_p);return LV_FS_RES_OK;
}
10.fs_dir_open文件系统文件操作API移植
static void * fs_dir_open(lv_fs_drv_t * drv, const char * path)
{LV_UNUSED(drv);DIR * d = lv_mem_alloc(sizeof(DIR));if(d == NULL) return NULL;/*Make the path relative to the current directory (the projects root folder)*/char *real_path = lv_mem_alloc(256);if(real_path == NULL) return NULL;lv_snprintf(real_path, 256, "1:%s", path);FRESULT res = f_opendir(d, real_path);if(res != FR_OK) {lv_mem_free(d);d = NULL;}return d;
}
10.fs_dir_close文件系统文件操作API移植
static lv_fs_res_t fs_dir_close(lv_fs_drv_t * drv, void * rddir_p)
{LV_UNUSED(drv);if(FR_OK == f_closedir(rddir_p)) {lv_mem_free(rddir_p);return LV_FS_RES_OK;}else{lv_mem_free(rddir_p);return LV_FS_RES_UNKNOWN;}
}
11.fs_dir_read文件系统文件操作API移植
static lv_fs_res_t fs_dir_read(lv_fs_drv_t * drv, void * rddir_p, char * fn)
{LV_UNUSED(drv);FRESULT res;FILINFO fno;fn[0] = '\0';do {res = f_readdir(rddir_p, &fno);if(res != FR_OK) return LV_FS_RES_UNKNOWN;if(fno.fattrib & AM_DIR) {fn[0] = '/';strcpy(&fn[1], fno.fname);}else strcpy(fn, fno.fname);} while(strcmp(fn, "/.") == 0 || strcmp(fn, "/..") == 0);return LV_FS_RES_OK;
}
三、初始化LVGL的文件系统
最后,我们需要在main函数调用lv_port_fs_init();来初始化我们移植好的FatFs:

四、验证测试
最简单的我们就是可以使用显示SD卡里面的图片来测试移植是否成功:
/* show image */lv_obj_t * img1 = lv_img_create(lv_scr_act());lv_img_set_src(img1, "S:/JPEG/girl.jpg");lv_obj_center(img1);
在main.c里面调用lv_port_fs_init(); 初始化FatFs文件系统:


需要注意的是:如果你想测试显示SD卡里面的图片,必须先在lv_conf.h里面启用对应的图片编解码器

到此为止,移植结束!!!
彩蛋:
实际上,LVGL8.3.11它自带的源码里面就已经移植好了FatFs!!!我们只需要去lv_conf.h里面配置一下相关的宏定义然后简单修改下源码就可以了。哈哈哈哈!!!
在源码包里面的\src\extra\libs\fsdrv\lv_fs_fatfs.c就是LVGL官方已经适配好的FatFs适配层。

使用方法:
1.把lv_fs_fatfs.c添加到你的工程里面
2.打开lv_conf.h找到:
同理,开启LV_USE_FS_FATFS,然后设置好LV_FS_FATFS_LETTER,LV_FS_FATFS_PATH 如图:

然后打开LVGL源码自带的lv_fs_fatfs.c,找到fs_init,添加一下挂载文件系统的代码:

只需要在lv_fs_fatfs.c的 fs_open 接口里面添加一下补充盘符代码就可以了:

同理在lv_fs_fatfs.c的 fs_dir_open 接口里面添加一下补充盘符代码
main.c里面的 初始化文件系统改为使用lv_fs_fatfs_init();
