嵌入式文件系统

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));

}
posted @ 2026-01-14 16:03  博客侦探  阅读(33)  评论(0)    收藏  举报