引言
在嵌入式产品中,数据存储需求日益增长,如日志记录、参数保存、音频文件存储等。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 官网 下载最新版本(如 R0.15)。解压后,核心文件包括:
-
ff.h/ff.c:文件系统实现 -
diskio.h/diskio.c:底层接口(需修改) -
ffconf.h:配置文件
将源码添加到工程,并确保头文件路径正确。
2. 配置 ffconf.h
根据需求修改关键宏:
#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 库的示例(简化版):
#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 文件系统。
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. 文件读写示例
以下代码演示创建文件、写入数据、读取数据并删除文件:
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 的移植核心在于底层接口的适配,一旦完成,上层应用开发将变得高效便捷。希望本文能为你后续的存储应用开发提供有力支持。