嵌入式文件系统
0 参考资料
-
视频教程
-
文章教程
1 基础介绍
1.1 FatFs和FAT32的联系和区别
FatFs 和 FAT32 是两个在概念和应用层面有本质区别的术语。FAT32 是一种具体的文件系统格式,而 FatFs 是一个用于在嵌入式系统中实现对 FAT32 等格式进行读写操作的软件库。
核心区别在于: FAT32 是存储设备上数据的组织规则,定义了如何存储文件和目录信息;而 FatFs 是运行在微控制器上的软件模块,它充当了操作系统与物理存储设备之间的桥梁,使得嵌入式设备能够理解和操作 FAT32 格式的存储介质。
1.2 文件系统介绍
下图是一个文件系统的基本框架:

在一个操作系统里面,经常会遇到不同的文件系统,比如说这个在windows的平台上面插入了一个U盘口,把它格式化成fat32格式,在这个U盘上就能进行一系列操作:比如新建文件、新建文件夹、移动、删除文件。但是windows上面它默认的文件系统是NTFS(是微软为Windows NT系列操作系统设计的文件系统,旨在替代FAT系列文件系统)。但是我们可以发现不同的文件系统都可以在Windows平台上正常操作,那是因为windows平台提供了VFS(虚拟文件系统)功能。
VFS是为了干什么呢?就是为了匹配不同的文件系统,统一用户的操作接口。以上图中常见的两个嵌入式系统为例,一个是FATFS,另一个是LittleFS。这两个文件系统他们操作的方法文件名称不同,如图所示,一个打开文件的方法是f_open()、另一个是lfs_file_open()。我们希望我们在编写代码的时候是用统一的函数来操作,所以虚拟文件系统的作用就是把这些方法统一为fopen()函数,我们编写代码的时候就不用关心它到底使用的是f_open()还是lfs_file_open()。还有一个就是虚拟出文件系统以后,可以增加一些盘符位置(比如说当前是在D盘,那么他的所有的文件呢都是挂载在D盘底下,我们要操作D盘的文件,它的路径必须是D:\...\...,这个盘符D:\又被称作基本路径、挂载路径)。那么通过挂载路径就能分析出当前是执行的是哪个方法,是NTFS里面的方法呢还是FAT32里面的方法。简而言之,通过挂载路径就可以去确认到底使用的是哪个文件系统。
不同的文件系统它都需要移植,都需要适配!不同的文件系统都会向下提供一个标准接口,比如说FATFS是diskio.c。我们如果要移植不同的硬件,首先一定是要编写驱动或者是协议,以便能去正常的访问这个介质。然后我们在他们已经定义好的这个硬件接口(图中倒数第二层文本框)里面去调用我们自己的驱动或者是协议。这样我们在上层就可以通过一个公共的fopen()方法按照图示一层层传递下来,最终访问到对应的硬件介质。
如上图所示,FATFS硬件接口下面是SD/MMC协议,本来我们需要自己编写代码访问下面的SD内存卡(比如如果存储介质使用SPI通信,那么就需要编写SPI初始化和通信相关代码),但是依赖于ESP-IDF的强大的组件系统,里面呢已经帮我们移植好了,如下图:

MMC卡是最早的闪存卡标准,SD卡在其基础上增加了写保护开关和加密功能,尺寸略厚;microSD卡(原称TF卡)是SD卡的迷你版本,体积最小但功能兼容,可通过适配器用于SD卡槽。
三者都采用闪存技术,SD兼容MMC接口,microSD兼容SD标准,目前MMC已基本淘汰,SD用于相机等设备,microSD广泛用于手机、运动相机等小型设备。
2 文件系统移植
2.1 ESP32移植FATFS示例
2.1.1 基本函数使用
-
挂载文件系统
- 使用函数:
esp_err_t esp_vfs_fat_sdmmc_mount(const char* base_path, const sdmmc_host_t* host_config, const void* slot_config, const esp_vfs_fat_mount_config_t* mount_config, sdmmc_card_t** out_card) - 该函数需要设置的结构体参数比较多,可参考下面的示例代码。
- 可以说,配置好前四个输入参数就挂载好了SD卡,之后就可以直接使用
fopen()、fread()、fwrite()、fclose()等标准文件操作函数。 - 最后一个参数属于输出参数,保存的信息是挂载SD卡完成后的SD卡信息结构的指针。
- 使用函数:
-
取消挂载
- 使用函数:
esp_err_t esp_vfs_fat_sdcard_unmount(const char *base_path, sdmmc_card_t *card) - 第一个参数是挂载路径,第二个参数是SD卡信息结构体
- 使用函数:
-
获取卡的容量信息
- 使用函数:
esp_err_t esp_vfs_fat_info(const char* base_path, uint64_t* out_total_bytes, uint64_t* out_free_bytes) - 第一个参数是挂载路径,第二个参数是总的可用字节数,第三个参数剩余字节数
- 使用函数:
-
打开文件
- 使用函数:
fopen()
- 使用函数:
-
读写文件
- 使用函数:
fread()、fwrite()
- 使用函数:
-
关闭文件
- 使用函数:
fclose()
- 使用函数:
2.2.2 使用示例
#include <locale.h>
#include "esp_err.h"
#include "esp_log.h"
#include "esp_vfs_fat.h" // ESP-IDF的FAT文件系统VFS接口
#include "driver/sdmmc_host.h" // SDMMC主机驱动
#include "bsp_tf.h"
#define TF_TAG "TF"
// SD卡挂载点路径
// 重点:后续访问SD文件时需要加上此前缀
#define MOUNT_POINT "/tf"
// SD卡信息结构体指针
static sdmmc_card_t*_tf_card_info = NULL;
/**
* @brief 判断TF卡是否已经挂载
*/
uint8_t isTfMounted(void)
{
return _tf_card_info == NULL ? 0 : 1;
}
/**
* @brief 挂载TF卡
*/
esp_err_t mountTf(void)
{
esp_err_t ret = ESP_OK;
if (isTfMounted() == 1) // 如果TF卡已经挂载
return ret;
// 1. 配置 Locale 为中文(中国)UTF-8,覆盖所有类别
setlocale(LC_ALL, "zh_CN.UTF-8");
// 2. MMC主机配置
sdmmc_host_t host = SDMMC_HOST_DEFAULT(); // 获取SDMMC主机默认配置
host.flags = SDMMC_HOST_FLAG_1BIT; // 设置为1线模式
// 3. MMC接口配置
sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT(); // 获取SDMMC槽默认配置
slot_config.width = 1; // 1线模式
slot_config.clk = GPIO_NUM_47; // CLK引脚(根据原理图)
slot_config.cmd = GPIO_NUM_48; // CMD引脚(根据原理图)
slot_config.d0 = GPIO_NUM_21; // D0引脚(根据原理图)
slot_config.gpio_cd = GPIO_NUM_NC; // 不使用卡检测引脚(根据原理图)
slot_config.gpio_wp = GPIO_NUM_NC; // 不使用写保护引脚(根据原理图)
// 4. 文件系统挂载配置
esp_vfs_fat_sdmmc_mount_config_t mount_config = {
.format_if_mount_failed = false, // 挂载失败时不格式化
.max_files = 5, // 最大同时打开文件数
.allocation_unit_size = 16 * 1024 // 分配单元大小
};
// 5. 挂载SD卡到文件系统
ret = esp_vfs_fat_sdmmc_mount(MOUNT_POINT, &host, &slot_config, &mount_config, &_tf_card_info);
if (ret == ESP_OK)
{
ESP_LOGI(TF_TAG, "mount success"); // 挂载成功,标记为已挂载
}
else
{
_tf_card_info = NULL;
ESP_LOGW(TF_TAG, "mount failure %s", esp_err_to_name(ret));
}
return ret;
}
/**
* @brief 取消挂载
*/
esp_err_t unMountTf(void)
{
esp_err_t ret = ESP_OK;
if (isTfMounted() == 0) // 如果TF卡没有挂载
return ret;
// 取消挂载
ret = esp_vfs_fat_sdcard_unmount(MOUNT_POINT, _tf_card_info);
if (ret == ESP_OK)
{
ESP_LOGI(TF_TAG, "unmount success");
_tf_card_info = NULL;
}
else
ESP_LOGW(TF_TAG, "mount failure %d", ret);
return ret;
}
/**
* @brief 打印TF卡的容量信息
*/
void printTfSizeInfo(void)
{
uint64_t card_total_bytes = 0;//TF 总大小
uint64_t total_bytes;
uint64_t free_bytes;
uint64_t used_bytes;
if (isTfMounted() == 0) // 如果TF卡没有挂载
return;
// 1. 获取SD卡硬件总容量(物理容量)
card_total_bytes = (uint64_t)_tf_card_info->csd.capacity * _tf_card_info->csd.sector_size;
// 获取TF卡信息
esp_vfs_fat_info(TF_TAG, &total_bytes, &free_bytes);
used_bytes = total_bytes - free_bytes;
// 打印信息 转换单位 Byte > GB
ESP_LOGI(TF_TAG, "SD卡物理总容量: %.2f GB", (double)card_total_bytes / (1024 * 1024 * 1024));
ESP_LOGI(TF_TAG, "FAT分区总空间: %.2f GB", (double)total_bytes / (1024 * 1024 * 1024));
ESP_LOGI(TF_TAG, "已使用空间: %.6f GB", (double)used_bytes / (1024 * 1024 * 1024));
ESP_LOGI(TF_TAG, "剩余空间: %.6f GB", (double)free_bytes / (1024 * 1024 * 1024));
}

专门针对嵌入式文件系统的入门学习,包括基本函数的使用和移植
浙公网安备 33010602011771号