STM32 SD 卡读写:FatFS 文件系统移植实战指南
👁 2 阅读 · 2026-08-18 · STM32 存储
在嵌入式系统中,SD 卡是常用的存储介质,而 FatFS 文件系统为数据管理提供了标准化接口。本文以 STM32F407 为例,详细讲解 FatFS 的移植步骤,包括底层 SPI/SDIO 驱动适配、磁盘 I/O 接口实现、配置选项解析,并给出完整的读写示例代码。通过本文,你将掌握在 STM32 上快速集成 FatFS 的方法,实现文件的创建、写入、读取与删除,为数据记录、固件升级等应用奠定基础。
## 引言
在嵌入式产品中,数据存储需求日益增长,如日志记录、参数保存、音频文件存储等。SD 卡凭借大容量、低成本、可插拔等优势成为主流选择。然而,直接操作 SD 卡扇区繁琐且易出错,引入 FatFS 文件系统可让开发者像操作 PC 文件一样管理数据。本文将带你从零开始,在 STM32 上移植 FatFS,实现高效的 SD 卡读写。
## FatFS 与 STM32 存储架构
FatFS 是一个开源的 FAT/exFAT 文件系统模块,专为小型嵌入式系统设计。它独立于底层硬件,通过统一的接口(disk_initialize、disk_read、disk_write 等)与存储介质通信。在 STM32 上,SD 卡可通过两种接口连接:
- **SDIO 接口**:4 位并行传输,速度更快,适合高性能应用。
- **SPI 接口**:引脚占用少,速度较慢,但兼容性广。
本文以 SDIO 模式为例(STM32F407 自带 SDIO 外设),但移植思路同样适用于 SPI 模式。
## 移植步骤
### 1. 获取 FatFS 源码
从 [FatFS 官网](http://elm-chan.org/fsw/ff/) 下载最新版本(如 R0.15)。解压后,核心文件包括:
- `ff.h` / `ff.c`:文件系统实现
- `diskio.h` / `diskio.c`:底层接口(需修改)
- `ffconf.h`:配置文件
将源码添加到工程,并确保头文件路径正确。
### 2. 配置 ffconf.h
根据需求修改关键宏:
```c
#define FF_USE_LFN 1 // 支持长文件名
#define FF_FS_MINIMIZE 0 // 完整功能
#define FF_USE_STRFUNC 1 // 支持 f_printf
#define FF_USE_MKFS 1 // 支持格式化
#define FF_USE_FASTSEEK 1 // 快速定位
#define FF_VOLUMES 1 // 卷数量
```
若使用 SDIO,还需定义 `FF_USE_LFN` 的缓冲区大小,建议设为 255。
### 3. 实现底层磁盘 I/O 接口
在 `diskio.c` 中,需要实现 5 个函数:`disk_initialize`、`disk_status`、`disk_read`、`disk_write`、`disk_ioctl`。以下为基于 STM32 HAL 库的示例(简化版):
```c
#include "ff.h"
#include "diskio.h"
#include "sdio.h"
// 全局 SD 卡句柄(由 HAL 库管理)
extern SD_HandleTypeDef hsd;
// 初始化磁盘
DSTATUS disk_initialize(BYTE pdrv) {
if (pdrv != 0) return STA_NOINIT;
if (HAL_SD_Init(&hsd) != HAL_OK) return STA_NOINIT;
// 配置 SDIO 时钟等(略)
return 0;
}
// 读取扇区
DRESULT disk_read(BYTE pdrv, BYTE *buff, LBA_t sector, UINT count) {
if (pdrv != 0) return RES_PARERR;
if (HAL_SD_ReadBlocks(&hsd, buff, sector, count, HAL_MAX_DELAY) != HAL_OK)
return RES_ERROR;
// 等待操作完成
while (HAL_SD_GetCardState(&hsd) != HAL_SD_CARD_TRANSFER);
return RES_OK;
}
// 写入扇区
DRESULT disk_write(BYTE pdrv, const BYTE *buff, LBA_t sector, UINT count) {
if (pdrv != 0) return RES_PARERR;
if (HAL_SD_WriteBlocks(&hsd, (BYTE*)buff, sector, count, HAL_MAX_DELAY) != HAL_OK)
return RES_ERROR;
while (HAL_SD_GetCardState(&hsd) != HAL_SD_CARD_TRANSFER);
return RES_OK;
}
// 获取磁盘状态
DSTATUS disk_status(BYTE pdrv) {
if (pdrv != 0) return STA_NOINIT;
return 0; // 简单返回就绪
}
// 控制命令(如获取扇区大小)
DRESULT disk_ioctl(BYTE pdrv, BYTE cmd, void *buff) {
if (pdrv != 0) return RES_PARERR;
switch (cmd) {
case GET_SECTOR_SIZE:
*(WORD*)buff = 512;
return RES_OK;
case GET_BLOCK_SIZE:
*(DWORD*)buff = 1;
return RES_OK;
case GET_SECTOR_COUNT:
*(DWORD*)buff = hsd.SdCard.BlockNbr;
return RES_OK;
default:
return RES_PARERR;
}
}
```
注意:`LBA_t` 类型在 FatFS R0.15 中定义为 64 位,需确保 HAL 库函数参数匹配。
### 4. 挂载文件系统
在应用代码中,使用 `f_mount` 挂载 SD 卡。若卡未格式化,可调用 `f_mkfs` 创建 FAT 文件系统。
```c
FATFS fs;
FRESULT res;
// 挂载
res = f_mount(&fs, "", 1);
if (res == FR_NO_FILESYSTEM) {
// 格式化 SD 卡(需先卸载)
f_mount(NULL, "", 0);
res = f_mkfs("", FM_FAT32, 0, work, sizeof(work));
if (res != FR_OK) error_handler();
f_mount(&fs, "", 1);
}
```
### 5. 文件读写示例
以下代码演示创建文件、写入数据、读取数据并删除文件:
```c
FIL file;
UINT bytes_written, bytes_read;
char write_buf[] = "Hello STM32 FatFS!\r\n";
char read_buf[64];
// 写入文件
if (f_open(&file, "test.txt", FA_CREATE_ALWAYS | FA_WRITE) == FR_OK) {
f_write(&file, write_buf, sizeof(write_buf)-1, &bytes_written);
f_close(&file);
}
// 读取文件
if (f_open(&file, "test.txt", FA_READ) == FR_OK) {
f_read(&file, read_buf, sizeof(read_buf)-1, &bytes_read);
read_buf[bytes_read] = '\0';
f_close(&file);
printf("Read: %s", read_buf);
}
// 删除文件
f_unlink("test.txt");
```
## 注意事项
- **DMA 与中断**:使用 SDIO 时,建议启用 DMA 传输,并在中断回调中处理完成标志,避免阻塞 CPU。
- **电源稳定**:SD 卡对供电敏感,需在 VCC 引脚并联 10μF 和 0.1μF 电容,并确保 3.3V 稳压输出。
- **长文件名支持**:启用 `FF_USE_LFN` 后,需提供工作缓冲区(如 `FF_LFN_BUF`),否则会占用较多栈空间。
- **多卷管理**:若同时使用多个存储设备(如 SPI Flash),需在 `diskio.c` 中根据 `pdrv` 区分设备。
- **错误处理**:每次文件操作后检查返回值,避免因卡未插入或损坏导致死循环。
## 总结
通过以上步骤,你已成功在 STM32 上移植 FatFS,实现了 SD 卡的文件读写。实际项目中,可根据需求扩展功能,如目录遍历、文件追加、日志轮转等。FatFS 的移植核心在于底层接口的适配,一旦完成,上层应用开发将变得高效便捷。希望本文能为你后续的存储应用开发提供有力支持。