ESP32 LVGL FATFS 文件系统移植
临时工的林
编辑于 2022年06月09日 20:35
收录于文集
共3篇

大过年的干什么呢?写写博客吧。。。

玩UI设计,许多时候需要用到图片资源,或者需要存储其它什么音视频等,都需要很大的存储空间来储存数据,仅靠ESP32板载的几M Flash是远远不够的,所以需要外扩存储器,比如SD卡、eMMC等大容量存储器。而想要轻松地操作这些存储器,我们就需要借助文件系统,就像在电脑上点点鼠标打开、保存、关闭文件那样简单,至于文件系统具体是何物,大伙百度上搜吧!文件系统又有许多种,这里移植嵌入式系统中常用的FATFS文件系统。


那么文件系统在整个嵌入式软硬件系统当中处于什么位置呢?从最底层的硬件层开始,就是我们的SD卡这些存储器电路,与我们的MCU微控制器相连,SD卡和MCU之间如何通信,就是更上一层的通信协议层,实现通信协议层,SD卡实际上就能和MCU说上话了,但数据存储以及管理起来十分困难,所以就搞了一样东西出来帮助我们管理这些存储空间,使我们能够十分方便地使用这些存储空间,这样东西就是我们更上一层的文件系统层。有了文件系统层,我们的应用层也就可以借助文件系统提供给我们的接口来使用SD卡了,想要创建一个文件,就直接创建一个文件,完全不用管这个文件被存储在SD卡的什么位置。


所以,想用文件系统管理SD卡,大致要实现三个方面。第一,实现SD卡和MCU之间以某种通信方式进行通信,比如SPI、SDIO。第二,实现以通信方式为基础,MCU能在SD卡存储空间中读取和写入任一个扇区的数据。第三,把MCU能够访问SD卡扇区的这种能力对接到文件系统接口当中,让文件系统也能够访问SD卡的任一扇区。


我们无妨来简单快速看一下FATFS源码。打开FATFS官网。拉到最底下资源下载源码。

解压缩后进入source文件夹。

其中源码文件就那么几个,diskio.c是底层磁盘IO对接模块,ff.c是文件系统内核功能模块,ffconf.h是文件系统配置相关,ffsystem.c是FatFs 的操作系统相关函数示例代码,ffunicode.c是FatFs R0.13+ 的编码处理相关函数。我们实际移植时只需关心diskio.c就够了,这就是文件系统与底层SD卡等磁盘关联的接口模块,里面有几个函数接口是需要我们实现的。

看名字大家应该就知道这些函数是干什么的了,就是把SD卡等磁盘设备的初始化接口、读取扇区接口和写入数据到扇区的接口分别对接进去就可以了。最后把这些源码添加到自己的工程目录中即可。OK,移植过程大概就是这么个搞法,至于细节在这里就不再啰嗦啦,因为我们这次移植的设备平台是ESP32,有意思的是ESP IDF已经把FATFS文件系统源码作为它IDF工程的一个组件来使用,那么这个组件是如何在我们自己构建的ESP32工程中使用起来的呢?他与SD卡是如何关联的呢?又是如何对接到LVGL系统中的呢?


FATFS刚好在这里起到承上启下的作用,接下来我们需要做两件事。第一,FATFS与SD卡驱动关联。第二,FATFS与LVGL关联。

先看SD卡怎样驱动。先来看看我自制的ESP32扩展板,先忽略右边的编码器,它是用来作为系统输入的,下一期再说。那么使用的存储设备是这种迷你SD卡,又叫TF卡。

其电路原理图如下。

驱动SD卡有两种通信方式,一种是SPI模式,另外一种是SDIO模式。这里我使用SPI模式。那么它与ESP32的接线如下。

可以看出,我使用的是ESP32的SPI3(VSPI)接口,因为SPI2(HSPI)我给了显示屏用了嘛。电路图需要的注意的地方就是信号线要加上上拉电阻。OK,了解硬件资源后,开始做驱动吧。


那么要读写SD卡,先要初始化SD卡,初始化SD卡就要先配置好ESP32的SPI,才能与其进行通信。所以先来配置ESP32的SPI。

先添加几个与SPI接口相关的头文件。

通信接口定义。

配置SPI主机属性,用接口提供的默认属性,但默认属性中的slot属性默认为HSPI_HOST,由于我用的是VSPI(SPI3),所以需要重新赋值为VSPI_HOST,这个属性代表使用哪个SPI。

配置SPI总线。

总线初始化。

配置SPI设备。

到此,SPI总线以及片选设备已经配置完成。之后ESP32可以向SD卡发送命令和数据了。按道理来说,下一步就应该向SD卡发送一系列初始化的命令,然后挂载文件系统,最后可以开始各种文件操作了。但在IDF工程中我们不用这么麻烦,他又搞了个虚拟文件系统,这个虚拟文件系统把FATFS与SD SPI设备关联起来了,我们用他给的一个接口就能一步把文件系统给挂载了。

最后一键挂载。就是这个接口把FATFS和SD SPI设备关联起来了,之后就可以使用FATFS的原生接口或者通过C标准库和POSIX函数对SD卡进行文件读写操作了,在这里就不展示了,大家在调试时可以操作一下。

到此,FATFS已经挂载起来了,完成了我们的第一步操作。整个过程,我把它整合成一个组件中。

这是CMakeLists.txt文件,引用fatfs组件。前面说过,fatfs被集成在IDF组件中了,要使用包含即可。

sd_fat.h头文件。

sd_fat.c源文件。


接下来就是把FATFS与LVGL的文件系统接入口对接起来。按道理来说只需把FATFS的那些文件操作的API丢进LVGL文件系统的接入口就OK了的。但是,LVGL官方专门为ESP32的文件系统移植做了一个移植用的组件lv_fs_if。在lvgl的github上可以下载源码。

下载下来后,当做我们工程的一个组件。需要自行添加一个CMakeLists.txt文件。内容如下。

那么lv_fs_if这个组件是干什么的呢?我们刚才有提到,FATFS与LVGL对接,就要把FATFS的API移植到LVGL文件系统的接入口处。那么LVGL文件系统的接入口在哪里呢?就在components/lvgl/src/misc/lv_fs.c里面。下面这些就是LVGL文件系统的接入口,通过这些接口就可在LVGL系统中间接使用FATFS访问SD卡了。

搞到这里,我们搞出了三个对接相关的组件,分别是lvgl、lv_fs_if和fatfs。以及三个重要的源文件lv_fs.c、lv_fs_fatfs.c和ff.c,分别存在于lvg、lv_fs_if和fatfs这三个组件中。他们之间的关系很显然是调用与被调用的关系。很显然lv_fs.c是处于最上层,ff.c是处于最底层。

LVGL文件操作接口通过回调函数实现对文件的操作。比如lv_fs_open接口中通过回调函数open_cb。

而又在lv_fs_fatfs.c中,实现了LVGL文件操作接口的回调。

其通过回调函数指针与lv_fs.c中的接口关联。

在lv_fs_fatfs.c中注册了这些回调。

而在这些回调当中,正是调用了ff.c中的FATFS原生文件操作接口。比如这个fs_open函数中就调用了ff.c中的f_open函数。

ff.c中的f_open函数。

就这样将lvgl的文件系统接口与lv_fs_if组件的接口关联起来,从而可以间接操作ff.c中原生接口了。

到此整条逻辑线就摸清楚了!是不是很开心!不急,还没完!!

还要在lv_fs_if.h文件中添加以下几行代码。

然后在lv_fs_fatfs.c中的static void fs_init(void)函数中添加SD卡的初始化代码。记得包含头文件。

以及fs_dir_open函数中的DIR类型定义需要改成FF_DIR类型定义,否则会报错。

到此,才算完成了移植工作。


最后,让我们写段代码测试一下吧。

主函数,要先调用lv_init()函数再调用lv_fs_if_init()函数。

调试打印信息。

OK!