【工具使用】STM32CubeMX-FATFS文件系统

一、概述

    无论是新手还是大佬,基于STM32单片机的开发,使用STM32CubeMX都是可以极大提升开发效率的,并且其界面化的开发,也大大降低了新手对STM32单片机的开发门槛。

    本文主要讲述STM32芯片FATFS文件系统功能的配置及其相关知识。

二、软件说明

    STM32CubeMX是ST官方出的一款针对ST的MCU/MPU跨平台的图形化工具,支持在Linux、MacOS、Window系统下开发,其对接的底层接口是HAL库,另外习惯于寄存器开发的同学们,也可以使用LL库。STM32CubeMX除了集成MCU/MPU的硬件抽象层,另外还集成了像RTOS,文件系统,USB,网络,显示,嵌入式AI等中间件,这样开发者就能够很轻松的完成MCU/MPU的底层驱动的配置,留出更多精力开发上层功能逻辑,能够更进一步提高了嵌入式开发效率。

    演示版本 6.9.0

三、FATFS功能简介

    FATFS文件系统其实不涉及单片机底层外设的操作,但因为STM32CubeMX支持该第三方库的配置,且该功能算是比较常用的,所以也更新到这系列文章中。

    FATFS 是一个面向小型嵌入式系统的通用 FAT/ExFAT 文件系统模块,由 Elm Chan 开发并持续维护更新。它完全采用 ANSI C(C89)编写,与底层磁盘 I/O 层完全分离,具备高度的硬件平台独立性,可轻松移植到 8051、PIC、AVR、ARM 等多种微控制器上。其版本不断演进(如常见的 R0.11、R0.11a 等),通过配置宏定义可灵活裁剪功能,满足不同嵌入式场景的需求。

    文件系统支持:兼容 FAT12、FAT16、FAT32 格式,支持多卷(物理驱动器或分区,最多 10 个卷)及两种分区规则(fdisk 和 super - floppy)。

    丰富的 API 接口:提供文件操作函数(如 f_open 打开 / 创建文件、f_read 读文件、f_write 写文件)、目录操作函数(如 f_opendir 打开目录、f_readdir 读取目录条目)、磁盘管理函数(如 f_mkfs 创建文件系统)等,覆盖文件读写、目录管理、磁盘格式化等核心功能。

    灵活的配置选项:支持长文件名(ANSI/OEM 或 Unicode 编码)、多种编码页(包括双字节字符系统 DBCS)、多任务环境(需配合操作系统)、只读模式、最小化 API 及缓冲区配置,可根据资源限制优化系统。

    广泛的存储媒介支持:如 SD 卡、USB 闪存、NAND/NOR 闪存等,通过底层磁盘 I/O 接口(disk_read、disk_write等函数)实现对不同硬件的适配。

优点:
开源免费 :无版权费用,适合成本敏感的嵌入式项目。
轻量级易移植 :代码量少,对资源要求低,且与平台无关,移植时只需实现底层磁盘 I/O 接口。
兼容性高 :与 Windows 的 FAT 文件系统兼容,便于数据交换(如 U 盘中的文件可直接在 PC 上读写)。
可裁剪性强:通过修改 ffconf.h 中的宏定义,可灵活开启或关闭功能(如长文件名、多语言支持),优化内存占用。

缺点:
安全性与可靠性 :相比 NTFS(支持日志、磁盘配额、加密等),FATFS 缺乏数据日志功能,异常断电时可能导致文件系统损坏,数据恢复能力较弱。
大文件与闪存优化 :ExFAT 专为闪存设计,支持更大文件(单个文件>4GB)和更大卷容量,而 FAT32 对文件大小(最大 4GB)和分区容量(最大 32GB)有限制,尽管 FATFS 支持 ExFAT,但整体对闪存的优化不如 ExFAT 原生。
功能丰富度:NTFS 等文件系统具备更复杂的权限管理、压缩等功能,FATFS 作为轻量级文件系统,功能相对基础,不适用于对文件管理要求极高的场景。

四、FATFS配置及应用

4.1 FATFS配置

    这些是FATFS 文件系统的配置参数,这些配置参数决定了 FATFS 文件系统的功能特性、编码方式及存储设备管理模式,适用于对存储设备进行基本读写且无需复杂分区或长文件名支持的场景。

  1. Version(版本)
    FATFS version:显示当前 FATFS 版本为 R0.11。
  2. Function Parameters(功能参数)
    FS_READONLY :设为 Disabled,表示不禁用只读模式,即允许文件系统写操作。
    FS_MINIMIZE :设为 Disabled,未启用最小化级别。
    USE_STRFUNC :设为 Enabled with LF -> CRLF conversion,启用字符串函数,并支持将换行符(LF)转换为回车换行符(CRLF)。
    USE_FIND :设为 Disabled,未启用文件查找功能。
    USE_MKFS :设为 Enabled,允许创建文件系统(如格式化存储设备)。
    USE_FASTSEEK :设为 Enabled,启用快速查找功能,提升文件访问效率。
    USE_LABEL :设为 Disabled,未启用卷标(Volume Label)功能。
    USE_FORWARD:设为 Disabled,未启用向前查找功能。
  3. Locale and Namespace Parameters(区域和命名空间参数)
    CODE_PAGE :设为 Multilingual Latin 1 (OEM),采用多语言拉丁 1(OEM)编码。
    USE_LFN :设为 Disabled,未启用长文件名(Long Filename)支持。
    MAX_LFN :设为 255,定义长文件名的最大长度(因 USE_LFN 禁用,此参数暂不生效)。
    LFN_UNICODE :设为 Disabled,未启用 Unicode 格式的长文件名。
    STRF_ENCODE :设为 UTF - 8,字符串编码采用 UTF - 8 格式。
    FS_RPATH:设为 Disabled,未启用相对路径(Relative Path)功能。
  4. Physical Drive Parameters(物理驱动器参数)
    VOLUMES :设为 1,表示支持 1 个逻辑驱动器。
    MAX_SS 和 MIN_SS :均设为 512,定义存储设备的最大和最小扇区大小为 512 字节(符合常见 U 盘、SD 卡等存储设备的扇区标准)。
    MULT_PARTITION :设为 Disabled,未启用多分区功能,即不支持同时管理多个存储分区。
    USE_TRIM :设为 Disabled,未启用 TRIM(擦除)功能。
    FS_NOFSSINFO:设为 0,表示强制完全 FAT 扫描的模式为 0。
  5. System Parameters(系统参数)
    FS_TINY :设为 Disabled,未启用微小模式。
    FS_NORTC :设为 Dynamic timestamp,采用动态时间戳功能。
    WORD_ACCESS :设为 Byte access,表示按字节访问(平台相关的访问选项)。
    FS_REENTRANT :设为 Disabled,不支持重入功能。
    FS_TIMEOUT :设为 1000,定义超时时间为 1000 个 tick。
    FS_LOCK:设为 2,表示同时打开文件的最大数量为 2。

    这里面我们只需要关注MAX_SS 和 MIN_SS,最好是设置成跟物理扇区一样大的,以方便驱动接口的实现,如果不行,按默认512设置也行,其他功能有需要可以做调整,完整的配置如下:

4.2 QSPI配置

    具体配置及对应的NorFlash读写接口参考《【工具使用】STM32CubeMX-QSPI配置-实现NorFlash读写》,这里不再赘述。

4.3 驱动接口实现

    完成FATFS文件系统的配置后,我们需要在生成的工程里实现FATFS的底层驱动,这里我们使用STM32F103芯片内部自带的Flash来作为存储介质。这里需要实现user_diskio.c文件中的USER_writeUSER_readUSER_ioctl接口。如果使用外部Flash作为存储介质,注意多实现一个USER_initialize的接口,在里面实现外部Flash芯片的初始化程序。

c 复制代码
/* Includes ------------------------------------------------------------------*/
#include <string.h>
#include "ff_gen_drv.h"
#include "w25qxx.h"

/* Private typedef -----------------------------------------------------------*/
/* Private define ------------------------------------------------------------*/
#define FATFS_SECTOR_SIZE     512U        /* FATFS 扇区大小 (字节)           */
#define W25Q_SECTOR_SIZE      0x1000U     /* W25Qxx 物理扇区 (4KB)           */
#define W25Q_SECTORS_PER_FAT  8U          /* 4096/512 = 8个FATFS扇区        */

/* Private variables ---------------------------------------------------------*/
/* Disk status */
static volatile DSTATUS Stat = STA_NOINIT;

/* 读-改-写缓冲区 (4KB),用于部分扇区写入 */
static uint8_t rwbuffer[W25Q_SECTOR_SIZE];

/* USER CODE END DECL */

/* Private function prototypes -----------------------------------------------*/
DSTATUS USER_initialize (BYTE pdrv);
DSTATUS USER_status (BYTE pdrv);
DRESULT USER_read (BYTE pdrv, BYTE *buff, DWORD sector, UINT count);
#if _USE_WRITE == 1
  DRESULT USER_write (BYTE pdrv, const BYTE *buff, DWORD sector, UINT count);
#endif /* _USE_WRITE == 1 */
#if _USE_IOCTL == 1
  DRESULT USER_ioctl (BYTE pdrv, BYTE cmd, void *buff);
#endif /* _USE_IOCTL == 1 */

Diskio_drvTypeDef  USER_Driver =
{
  USER_initialize,
  USER_status,
  USER_read,
#if  _USE_WRITE
  USER_write,
#endif  /* _USE_WRITE == 1 */
#if  _USE_IOCTL == 1
  USER_ioctl,
#endif /* _USE_IOCTL == 1 */
};

/* Private functions ---------------------------------------------------------*/

/**
  * @brief  Initializes a Drive
  * @param  pdrv: Physical drive number (0..)
  * @retval DSTATUS: Operation status
  */
DSTATUS USER_initialize (
	BYTE pdrv           /* Physical drive nmuber to identify the drive */
)
{
  /* USER CODE BEGIN INIT */
    uint8_t ret;

    ret = W25Qxx_Init();
    if (ret == 0)
    {
        Stat &= ~STA_NOINIT;   /* 清除NOINIT标志,初始化成功 */
    }
    else
    {
        Stat = STA_NOINIT;     /* 初始化失败 */
    }
    return Stat;
  /* USER CODE END INIT */
}

/**
  * @brief  Gets Disk Status
  * @param  pdrv: Physical drive number (0..)
  * @retval DSTATUS: Operation status
  */
DSTATUS USER_status (
	BYTE pdrv       /* Physical drive number to identify the drive */
)
{
  /* USER CODE BEGIN STATUS */
    return Stat;
  /* USER CODE END STATUS */
}

/**
  * @brief  Reads Sector(s)
  * @param  pdrv: Physical drive number (0..)
  * @param  *buff: Data buffer to store read data
  * @param  sector: Sector address (LBA)
  * @param  count: Number of sectors to read (1..128)
  * @retval DRESULT: Operation result
  */
DRESULT USER_read (
	BYTE pdrv,      /* Physical drive nmuber to identify the drive */
	BYTE *buff,     /* Data buffer to store read data */
	DWORD sector,   /* Sector address in LBA */
	UINT count      /* Number of sectors to read */
)
{
  /* USER CODE BEGIN READ */
    uint32_t addr;
    uint32_t size;

    addr = (uint32_t)sector * FATFS_SECTOR_SIZE;
    size = (uint32_t)count * FATFS_SECTOR_SIZE;

    W25Qxx_ReadData(buff, addr, size);

    return RES_OK;
  /* USER CODE END READ */
}

/**
  * @brief  Writes Sector(s)
  * @param  pdrv: Physical drive number (0..)
  * @param  *buff: Data to be written
  * @param  sector: Sector address (LBA)
  * @param  count: Number of sectors to write (1..128)
  * @retval DRESULT: Operation result
  */
#if _USE_WRITE == 1
DRESULT USER_write (
	BYTE pdrv,          /* Physical drive nmuber to identify the drive */
	const BYTE *buff,   /* Data to be written */
	DWORD sector,       /* Sector address in LBA */
	UINT count          /* Number of sectors to write */
)
{
  /* USER CODE BEGIN WRITE */
    uint32_t   addr;
    uint32_t   remain;
    uint32_t   w25_sector;
    uint32_t   w25_offset;
    uint32_t   chunk;
    const BYTE *p;

    addr   = (uint32_t)sector * FATFS_SECTOR_SIZE;
    remain = (uint32_t)count * FATFS_SECTOR_SIZE;
    p      = buff;

    while (remain > 0)
    {
        w25_sector = addr / W25Q_SECTOR_SIZE;
        w25_offset = addr % W25Q_SECTOR_SIZE;
        chunk      = (remain < (W25Q_SECTOR_SIZE - w25_offset))
                     ? remain : (W25Q_SECTOR_SIZE - w25_offset);

        /* 写入整4KB扇区:先擦除再直接写入 */
        if (w25_offset == 0 && chunk == W25Q_SECTOR_SIZE)
        {
            W25Qxx_EraseSector(w25_sector);
            if (W25Qxx_WriteSector((uint8_t *)p, w25_sector, 0, W25Q_SECTOR_SIZE) != 0)
                return RES_ERROR;
        }
        else
        {
            /* 部分扇区:读-改-擦-写 */
            W25Qxx_ReadData(rwbuffer, w25_sector * W25Q_SECTOR_SIZE, W25Q_SECTOR_SIZE);
            memcpy(rwbuffer + w25_offset, p, chunk);

            W25Qxx_EraseSector(w25_sector);
            if (W25Qxx_WriteSector(rwbuffer, w25_sector, 0, W25Q_SECTOR_SIZE) != 0)
                return RES_ERROR;
        }

        p      += chunk;
        addr   += chunk;
        remain -= chunk;
    }

    return RES_OK;
  /* USER CODE END WRITE */
}
#endif /* _USE_WRITE == 1 */

/**
  * @brief  I/O control operation
  * @param  pdrv: Physical drive number (0..)
  * @param  cmd: Control code
  * @param  *buff: Buffer to send/receive control data
  * @retval DRESULT: Operation result
  */
#if _USE_IOCTL == 1
DRESULT USER_ioctl (
	BYTE pdrv,      /* Physical drive nmuber (0..) */
	BYTE cmd,       /* Control code */
	void *buff      /* Buffer to send/receive control data */
)
{
  /* USER CODE BEGIN IOCTL */
    DRESULT res = RES_ERROR;

    switch (cmd)
    {
    case CTRL_SYNC:
        /* 确保所有挂起的写操作完成 */
        W25Qxx_WaitForWriteEnd();
        res = RES_OK;
        break;

    case GET_SECTOR_COUNT:
        /* 512字节扇区总数 */
        *(DWORD *)buff = W25QXX_FLASH_SIZE / FATFS_SECTOR_SIZE;
        res = RES_OK;
        break;

    case GET_SECTOR_SIZE:
        /* FATFS 扇区大小 */
        *(WORD *)buff = FATFS_SECTOR_SIZE;
        res = RES_OK;
        break;

    case GET_BLOCK_SIZE:
        /* 擦除块大小 (扇区数),W25Qxx 4KB = 8个FATFS扇区 */
        *(DWORD *)buff = W25Q_SECTOR_SIZE / FATFS_SECTOR_SIZE;
        res = RES_OK;
        break;

    default:
        res = RES_PARERR;
        break;
    }

    return res;
  /* USER CODE END IOCTL */
}
#endif /* _USE_IOCTL == 1 */

4.4 应用代码实现

c 复制代码
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "fatfs.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "string.h"
/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */

/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */

/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/

/* USER CODE BEGIN PV */

/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
/* USER CODE BEGIN PFP */

/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
FATFS fs;           // 文件系统对象
FIL file;         // 文件对象
FRESULT res;      // 函数返回值
UINT bytesRead;   // 读取的字节数
UINT bytesWritten; // 写入的字节数
char buffer[32];

/* USER CODE END 0 */

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_FATFS_Init();
  /* USER CODE BEGIN 2 */
  /* 1. 挂载文件系统 */
  res = f_mount(&fs, "", 1);
  if (res != FR_OK)
  {
      // 挂载失败,可能需要格式化
      res = f_mkfs("", 0, 0);
      if (res == FR_OK)
      {
          res = f_mount(&fs, "", 1); // 重新挂载
      }
      else
      {
          // 格式化失败,处理错误
      }
  }

  /* 2. 创建并写入文件 */
  res = f_open(&file, "test.txt", FA_CREATE_ALWAYS | FA_WRITE);
  if (res == FR_OK)
  {
      const char *message = "Hello, FATFS!\r\n";
      res = f_write(&file, message, strlen(message), &bytesWritten);
      if (res == FR_OK)
      {
          // 写入成功,bytesWritten包含实际写入的字节数
      }
      f_sync(&file);  // 确保数据写入 Flash
      f_close(&file); // 关闭文件
  }

  /* 3. 读取文件内容 */
  res = f_open(&file, "test.txt", FA_READ);
  if (res == FR_OK)
  {
      res = f_read(&file, buffer, sizeof(buffer)-1, &bytesRead);
      if (res == FR_OK)
      {
          buffer[bytesRead] = '\0'; // 添加字符串结束符
          // 处理读取的数据,例如通过串口打印
      }
      f_close(&file);
  }

  /* 4. 卸载文件系统(可选) */
  f_mount(NULL, "", 0);

  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

4.4 效果演示

五、注意事项

1、对于一些时效性要求比较高的场景,则不适合使用文件系统,自己实现存储芯片的读写驱动速度会更快,同时可能会丧失一部分的稳定性。

2、使用这个文件系统,存储空间至少要大于128K,否则文件系统会格式化失败。

3、使用文件系统前,请先保证设备的读写接口可以正常使用,方便在出问题时定位问题点。

4、格式化成FAT32时,需要注意对存储设备的大小有限制,要求不低于32M,小空间的建议用格式化成FAT。

5、f_open接口不像C语言标准库里的,当使用文件写打开时,必须存在此文件才能打开(C语言标准库里是无此文件时会创建文件)。

6、使用文件系统的接口时,尽量都判断下返回值,方便后面查找问题。

7、使用写接口写入文件后,如果不是在文件末尾添加数据,在文件中间添加完数据后,需要把文件指针移到文件末尾再关闭文件,否则关闭文件后,文件会以当前写入的位置进行保存,导致文件后面的数据丢失。

六、相关链接

对于刚入门的小伙伴可以先看下STM32CubeMX的基础使用及Keil的基础使用。
【工具使用】STM32CubeMX-基础使用篇
【工具使用】Keil5软件使用-基础使用篇
【工具使用】STM32CubeMX-QSPI配置-实现NorFlash读写

相关推荐
✎ ﹏梦醒͜ღ҉繁华落℘4 天前
单片机基础知识---stm32单片机的优先级
stm32·单片机·mongodb
u152109648494 天前
S.S.Audio PRO A2音频隔离器
嵌入式硬件·音视频·实时音视频·视频编解码·视频
zd8451015004 天前
RS485 总线详解
单片机·嵌入式硬件
半条-咸鱼4 天前
【STM32】I2C协议原理、HAL读写与OLED显示操作
嵌入式硬件·c·信息与通信
牛根生同志4 天前
SPI数据收发的时候 TXE与RXNE标志位置位的时机
stm32·spi·transfer
wohoo_wangzi4 天前
苏州晟雅泰电子:关于W25Q128JVSIQ这个芯片物料的参数,规格及应用领域
嵌入式硬件
goldenrolan4 天前
学习型红外控制系统稳定性挂测工装专项总结
软件测试·python·stm32·嵌入式·红外
✎ ﹏梦醒͜ღ҉繁华落℘4 天前
编程基础 --高内聚,低耦合
c语言·单片机
科芯创展4 天前
1A,1MHz,30VIN,XZ4115,降压恒流LED驱动芯片
单片机·嵌入式硬件
集芯微电科技有限公司4 天前
四通道2A输出集成功率电感降压模块专为紧凑型方案设计
人工智能·单片机·嵌入式硬件·生成对抗网络·计算机外设