GD32 SDIO + FatFs 文件系统使用指南
GD32 SDIO + FatFs 文件系统使用指南
适用场景:基于 GD32 系列 MCU(GD32F1/F2/F3/F4/F7 等)通过 SDIO 接口外接 SD 卡,并移植 FatFs(R0.13 及以上)实现 FAT 文件系统读写。
文档版本:v1.0
适用读者:嵌入式软件开发工程师
目录
- 概述
- 硬件设计
- CubeMX/标准库工程配置
- SDIO 驱动实现
- FatFs 移植
- 应用层 API 使用示例
- ffconf.h 关键配置
- 性能优化
- 常见问题与排查
- 进阶:USB MSC 复合设备
- 进阶:抗掉电文件系统方案
- 参考资源
1. 概述
FatFs 是一个通用的 FAT 文件系统模块,由 ChaN 编写,采用标准 C 语言编写,完全与磁盘 I/O 层解耦,可方便地移植到各种 MCU 上。
1.1 系统架构
┌──────────────────────────────┐
│ Application Layer │ f_open / f_read / f_write
├──────────────────────────────┤
│ FatFs Module │ FAT12/16/32 / exFAT
├──────────────────────────────┤
│ diskio.c (移植接口层) │ disk_initialize/read/write/ioctl
├──────────────────────────────┤
│ SDIO Driver + HAL/SPL │ GD32 SDIO 外设驱动
├──────────────────────────────┤
│ SD Card │ 物理存储介质
└──────────────────────────────┘
1.2 核心特性
- 支持 FAT12 / FAT16 / FAT32 / exFAT
- 多卷管理(同时管理 SD 卡 + SPI Flash 等)
- 支持长文件名(LFN)
- 支持多种代码页(含简体中文 GBK)
- 支持 RTOS(需自行加互斥锁)
- 资源占用:ROM ≈ 10~30 KB,RAM ≈ 数 KB(取决于配置)
2. 硬件设计
2.1 引脚连接
以 GD32F450 + SDIO 4 位总线为例:
| SD 卡引脚 | GD32 引脚 | 功能说明 |
|---|---|---|
| DAT0 | PC8 | SDIO D0 |
| DAT1 | PC9 | SDIO D1 |
| DAT2 | PC10 | SDIO D2 |
| DAT3 | PC11 | SDIO D3 |
| CMD | PD2 | SDIO CMD |
| CLK | PC12 | SDIO CK |
| CD | PA8(可选) | 卡检测 |
| WP | PA15(可选) | 写保护检测 |
| VCC | 3.3V | 必须稳压 |
| GND | GND | 共地 |
2.2 原理图设计要点
- 上拉电阻:CMD、DATA0~3 各加 10K 上拉至 3.3V,防止上电期间浮动误触发。
- 去耦电容:SD 卡座 VCC 引脚加 100μF + 0.1μF 去耦。
- 电平兼容:普通 SD 卡工作电压 2.7~3.6V,GD32 IO 3.3V 直接驱动即可。若使用 UHS-I 高速卡(1.8V),需增加电平转换器。
- ESD 防护:卡座信号线建议加 TVS 二极管阵列(如 USBLC6)。
- 卡座选择:推荐自弹式卡座(push-push),可靠性高于翻盖式。
- 布线:CLK / CMD / DATA 走线等长,避开高频信号线。
2.3 供电要求
SD 卡在写入时峰值电流可达 100~200mA,建议:
- 单独 LDO 供电(如 RT9013-3.3),避免与电机、LED 共享电源。
- 电源走线宽度 ≥ 0.5mm,必要时铺铜。
- 大容量卡(>32GB)建议使用 SDXC 标准,文件系统默认 exFAT(需在 ffconf.h 启用
FF_FS_EXFAT)。
3. CubeMX/标准库工程配置
3.1 使用 GD32 标准库
工程目录结构:
Project/
├── Libraries/
│ ├── GD32F4xx_Standard_Peripherals/
│ ├── FatFs/ ← FatFs 源码
│ │ ├── source/
│ │ │ ├── ff.c
│ │ │ ├── ffunicode.c
│ │ │ └── ffsystem.c
│ │ └── option/
│ │ ├── cc936.c ← 中文支持
│ │ └── syscall.c ← RTOS 钩子
│ ├── diskio.c ← 移植层(自行实现)
│ ├── sdio_sdcard.c ← SDIO 驱动(自行实现)
├── User/
│ ├── main.c
│ ├── app_fatfs.c
3.2 使用 HAL 库(GD32E50x 等)
- 在工程管理器中启用 SDIO 外设。
- 启用 FatFs 中间件,配置为 SD Card 模式。
- 勾选
Use DMA,可显著提升读写速度。 - CubeMX 会自动生成
fatfs.c、sdio.c模板,用户只需补充应用层调用。
4. SDIO 驱动实现
4.1 SDIO 外设初始化
void sdio_lowlevel_init(void)
{
/* GPIO 配置 */
rcu_periph_clock_enable(RCU_GPIOC);
rcu_periph_clock_enable(RCU_GPIOD);
gpio_af_set(GPIOC, GPIO_AF_12,
GPIO_PIN_8 | GPIO_PIN_9 | GPIO_PIN_10 | GPIO_PIN_11 | GPIO_PIN_12);
gpio_mode_set(GPIOC, GPIO_MODE_AF, GPIO_PUPD_PULLUP,
GPIO_PIN_8 | GPIO_PIN_9 | GPIO_PIN_10 | GPIO_PIN_11 | GPIO_PIN_12);
gpio_output_options_set(GPIOC, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ,
GPIO_PIN_8 | GPIO_PIN_9 | GPIO_PIN_10 | GPIO_PIN_11 | GPIO_PIN_12);
gpio_af_set(GPIOD, GPIO_AF_12, GPIO_PIN_2);
gpio_mode_set(GPIOD, GPIO_MODE_AF, GPIO_PUPD_PULLUP, GPIO_PIN_2);
gpio_output_options_set(GPIOD, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_2);
/* SDIO 时钟 */
rcu_periph_clock_enable(RCU_SDIO);
}
4.2 SD 卡识别与初始化
sd_error_enum sdio_card_init(void)
{
sd_error_enum status;
uint32_t retry = 0;
/* 1. 配置 SDIO 为低速 400KHz(识别阶段必须) */
sdio_clock_config(SDIO_SDIO_SCK_400K);
sdio_bus_mode_set(SDIO_BUSMODE_1BIT);
sdio_hardware_clock_disable();
sdio_power_on();
/* 2. 卡识别(CMD0 + CMD8 + ACMD41) */
sdio_power_state_wait(SDIO_POWER_ON);
sdio_clock_enable();
do {
status = sdio_card_inserted_check();
if (SD_OK == status) break;
} while (++retry < 10);
if (SD_OK != status) return SD_ERROR;
/* 3. 卡类型识别 */
status = sdio_card_init();
if (SD_OK != status) return SD_ERROR;
/* 4. 切换到 4 位总线 + 高速时钟 */
sdio_clock_config(SDIO_SDIO_SCK_25M);
sdio_bus_mode_set(SDIO_BUSMODE_4BIT);
sdio_bus_width_set(SDIO_BUSMODE_4BIT);
return SD_OK;
}
4.3 块读写函数
sd_error_enum sdio_block_read(uint8_t *buf, uint32_t sector, uint32_t count)
{
/* 设置 DMA */
sdio_dma_config((uint32_t)buf, count * 512, SDIO_DMA_DIR_RX);
sdio_dma_enable();
/* 发起 CMD17(单块)或 CMD18(多块) */
if (count == 1) {
sdio_command_start(SD_CMD_READ_SINGLE_BLOCK, sector, SDIO_RESP_48);
} else {
sdio_command_start(SD_CMD_READ_MULTI_BLOCK, sector, SDIO_RESP_48);
}
/* 等待传输完成 */
while (RESET == sdio_flag_get(SDIO_FLAG_RXOVERR |
SDIO_FLAG_DTCRCERR |
SDIO_FLAG_DTTIMEOUT |
SDIO_FLAG_STBITERR));
while (RESET == sdio_flag_get(SDIO_FLAG_RXFINI));
sdio_flag_clear(SDIO_FLAG_RXFINI);
sdio_dma_disable();
return SD_OK;
}
sd_error_enum sdio_block_write(const uint8_t *buf, uint32_t sector, uint32_t count)
{
sdio_dma_config((uint32_t)buf, count * 512, SDIO_DMA_DIR_TX);
sdio_dma_enable();
if (count == 1) {
sdio_command_start(SD_CMD_WRITE_SINGLE_BLOCK, sector, SDIO_RESP_48);
} else {
sdio_command_start(SD_CMD_WRITE_MULTI_BLOCK, sector, SDIO_RESP_48);
}
while (RESET == sdio_flag_get(SDIO_FLAG_TXUNDERR |
SDIO_FLAG_DTCRCERR |
SDIO_FLAG_DTTIMEOUT |
SDIO_FLAG_STBITERR));
while (RESET == sdio_flag_get(SDIO_FLAG_TXFINI));
sdio_flag_clear(SDIO_FLAG_TXFINI);
/* 等待卡完成内部写入 */
if (SD_OK != sdio_wait_write_finish()) {
return SD_ERROR;
}
sdio_dma_disable();
return SD_OK;
}
5. FatFs 移植
5.1 diskio.c 完整实现
#include "ff.h"
#include "diskio.h"
#include "sdio_sdcard.h"
DSTATUS disk_status(BYTE pdrv)
{
if (pdrv != 0) return STA_NOINIT;
return 0;
}
DSTATUS disk_initialize(BYTE pdrv)
{
if (pdrv != 0) return STA_NOINIT;
if (SD_OK != sdio_card_init()) return STA_NOINIT;
return 0;
}
DRESULT disk_read(BYTE pdrv, BYTE *buff, DWORD sector, UINT count)
{
if (pdrv != 0 || count == 0) return RES_PARERR;
if (SD_OK != sdio_block_read(buff, sector, count)) return RES_ERROR;
return RES_OK;
}
DRESULT disk_write(BYTE pdrv, const BYTE *buff, DWORD sector, UINT count)
{
if (pdrv != 0 || count == 0) return RES_PARERR;
if (SD_OK != sdio_block_write(buff, sector, count)) return RES_ERROR;
return RES_OK;
}
DRESULT disk_ioctl(BYTE pdrv, BYTE cmd, void *buff)
{
if (pdrv != 0) return RES_PARERR;
switch (cmd) {
case CTRL_SYNC:
return RES_OK;
case GET_SECTOR_COUNT:
*(DWORD *)buff = sd_card_capacity() / 512;
return RES_OK;
case GET_SECTOR_SIZE:
*(WORD *)buff = 512;
return RES_OK;
case GET_BLOCK_SIZE:
*(DWORD *)buff = 1;
return RES_OK;
}
return RES_PARERR;
}
DWORD get_fattime(void)
{
/* 返回时间戳:2026-07-15 10:30:00 */
return ((2026UL - 1980) << 25) | // 年
(7UL << 21) | // 月
(15UL << 16) | // 日
(10U << 11) | // 时
(30U << 5) | // 分
(0U / 2U); // 秒
}
5.2 多卷扩展
若需同时管理 SD 卡和 SPI Flash,需扩展 FF_VOLUMES 并在 disk_* 中通过 pdrv 区分:
DRESULT disk_read(BYTE pdrv, BYTE *buff, DWORD sector, UINT count)
{
switch (pdrv) {
case 0: /* SD 卡 */
return sd_read(buff, sector, count) ? RES_ERROR : RES_OK;
case 1: /* SPI Flash */
return spi_flash_read(buff, sector, count) ? RES_ERROR : RES_OK;
default:
return RES_PARERR;
}
}
6. 应用层 API 使用示例
6.1 挂载与卸载
FATFS fs;
FRESULT res;
void mount_sd(void)
{
res = f_mount(&fs, "0:", 1);
if (res == FR_NO_FILESYSTEM) {
printf("No filesystem, formatting...\r\n");
res = f_mkfs("0:", 0, 0); /* 简单格式化 */
if (res != FR_OK) {
printf("Format failed: %d\r\n", res);
return;
}
f_mount(&fs, "0:", 1);
}
printf("SD card mounted OK\r\n");
}
void unmount_sd(void)
{
f_mount(NULL, "0:", 0);
}
6.2 文件读写
void file_demo(void)
{
FIL file;
UINT br, bw;
char buf[128];
/* 写入 */
if (f_open(&file, "0:/log.txt", FA_OPEN_ALWAYS | FA_WRITE) == FR_OK) {
f_lseek(&file, f_size(&file)); /* 追加模式 */
f_printf(&file, "[%u] System boot.\r\n", (unsigned)sys_tick);
f_close(&file);
}
/* 读取 */
if (f_open(&file, "0:/log.txt", FA_READ) == FR_OK) {
while (f_gets(buf, sizeof(buf), &file)) {
printf("%s", buf);
}
f_close(&file);
}
}
6.3 目录操作
void list_files(const char *path)
{
DIR dir;
FILINFO fno;
if (f_opendir(&dir, path) != FR_OK) return;
while (f_readdir(&dir, &fno) == FR_OK && fno.fname[0]) {
if (fno.fattrib & AM_DIR) {
printf("[DIR] %s/\r\n", fno.fname);
} else {
printf("[FILE] %s %lu bytes\r\n", fno.fname, fno.fsize);
}
}
f_closedir(&dir);
}
6.4 错误码速查
| 返回值 | 宏定义 | 含义 |
|---|---|---|
| 0 | FR_OK |
成功 |
| 1 | FR_DISK_ERR |
底层硬件错误 |
| 2 | FR_INT_ERR |
内部断言失败 |
| 3 | FR_NOT_READY |
磁盘未就绪 |
| 4 | FR_NO_FILE |
文件不存在 |
| 5 | FR_NO_PATH |
路径不存在 |
| 6 | FR_INVALID_NAME |
非法文件名 |
| 7 | FR_DENIED |
访问被拒绝 |
| 8 | FR_EXIST |
文件已存在 |
| 9 | FR_INVALID_OBJECT |
无效对象 |
| 10 | FR_WRITE_PROTECTED |
写保护 |
| 11 | FR_INVALID_DRIVE |
非法驱动器 |
| 12 | FR_NOT_ENABLED |
工作区未启用 |
| 13 | FR_NO_FILESYSTEM |
无有效文件系统 |
| 14 | FR_MKFS_ABORTED |
格式化中止 |
| 15 | FR_TIMEOUT |
超时 |
| 16 | FR_LOCKED |
文件被锁定 |
| 17 | FR_NOT_ENOUGH_CORE |
内存不足 |
| 18 | FR_TOO_MANY_OPEN_FILES |
打开文件过多 |
| 19 | FR_INVALID_PARAMETER |
参数无效 |
7. ffconf.h 关键配置
/* ffconf.h 重要配置项 */
#define FF_FS_READONLY 0 /* 0: 读写 / 1: 只读 */
#define FF_FS_NORTC 0 /* 0: 用 get_fattime / 1: 不需要 */
#define FF_FS_RPATH 2 /* 0~2,相对路径支持级别 */
#define FF_FS_EXFAT 1 /* 0: 禁用 / 1: 启用 exFAT(SDXC 必备) */
#define FF_FS_LABEL 1 /* 启用卷标读写 */
#define FF_CODE_PAGE 936 /* 437=英文 / 936=GBK简中 / 950=BIG5 */
#define FF_USE_LFN 3 /* 0: 不支持 / 1/2/3: 长文件名支持 */
#define FF_MAX_LFN 255 /* LFN 最大长度 */
#define FF_LFN_BUF 255
#define FF_SFN_BUF 12
#define FF_VOLUMES 2 /* 逻辑卷数量 */
#define FF_MIN_GPT 0 /* 0: MBR / 1: GPT */
#define FF_MAX_SS 512 /* 最大扇区大小 */
#define FF_FS_TINY 0 /* 0: 独立 buf / 1: 栈上小 buf */
#define FF_FS_NORTC 0
#define FF_FS_NOFSINFO 0
#define FF_FS_LOCK 1 /* 文件锁数量(单任务=0, RTOS≥2) */
7.1 资源占用参考
| 配置 | ROM | RAM |
|---|---|---|
| 最小(只读FAT12/16) | ~10 KB | ~0.5 KB |
| 标准(读写FAT32+LFN) | ~25 KB | ~2 KB |
| 完整(含 exFAT) | ~50 KB | ~4 KB |
8. 性能优化
8.1 SDIO 时钟与总线宽度
| 模式 | 时钟 | 总线宽度 | 理论带宽 | 实测速度 |
|---|---|---|---|---|
| 默认 | 25 MHz | 1 bit | 3.125 MB/s | 1~2 MB/s |
| 4-bit | 25 MHz | 4 bit | 12.5 MB/s | 8~10 MB/s |
| SDR50 | 50 MHz | 4 bit | 25 MB/s | 20+ MB/s |
| SDR104 | 208 MHz | 4 bit | 104 MB/s | 需 UHS-I 卡 |
8.2 DMA 优化
- 启用 SDIO DMA,连续读写时 CPU 占用率 < 5%。
- 多块读写优于单块循环(减少命令开销)。
- 写操作后及时调用
f_sync或f_close触发刷盘。
8.3 FatFs 层优化
- 使用
f_write单次写入 ≥ 512 字节(与扇区对齐)。 - 大文件追加用
f_lseek+f_write,避免全文件扫描。 - 高频小数据可启用
FF_FS_TINY = 1减小 buf。 - 多任务访问务必加互斥锁(
FF_FS_LOCK >= 1+ff_req_grant/ff_rel_grant)。
8.4 实测性能参考(GD32F450 + 25MHz + 4-bit + DMA)
| 操作 | 速度 |
|---|---|
| 顺序读 1MB | ~9.5 MB/s |
| 顺序写 1MB | ~5 MB/s |
| 4KB 随机读 | ~2 MB/s |
| 4KB 随机写 | ~0.5 MB/s |
9. 常见问题与排查
9.1 初始化失败
| 现象 | 原因 | 解决方案 |
|---|---|---|
sdio_card_init 返回超时 |
时钟 > 400KHz、缺上拉、电源不稳 | 降低初始化时钟,加 10K 上拉,加 100μF 电容 |
| 卡识别为 SDHC 但容量错误 | CMD58 顺序错 | 检查 ACMD41 后必须 CMD58 读 OCR |
| 4-bit 切换失败 | 卡不支持 4-bit 或时序不稳 | 退回 1-bit 模式,检查信号质量 |
| 写数据时偶尔 CRC 错 | 时钟过高 | 降至 12~25 MHz |
9.2 挂载失败
| 现象 | 原因 | 解决方案 |
|---|---|---|
FR_NO_FILESYSTEM |
卡未格式化或非 FAT | 调用 f_mkfs 重新格式化 |
FR_DISK_ERR |
SDIO DMA 未配好 | 检查 DMA 通道、中断优先级 |
| 反复 mount 失败 | 引脚复用配置错 | 确认所有信号线都配置为 AF 模式 |
9.3 文件操作异常
| 现象 | 原因 | 解决方案 |
|---|---|---|
| 中文文件名乱码 | FF_CODE_PAGE 未设 936 |
改为 936,并加入 cc936.c |
| 写文件偶尔失败 | 未等写入完成、突然掉电 | 调 f_sync,加掉电保护 |
| 长时间运行后文件系统损坏 | 突然掉电、簇链破坏 | 启用日志系统或换用 littlefs |
f_lseek 越界 |
超过 4GB(FAT32) | 改用 exFAT 或拆分文件 |
9.4 性能问题
| 现象 | 原因 | 解决方案 |
|---|---|---|
| 读写速度很慢 | 仍用 1-bit 模式 | 切 4-bit + DMA + 提高时钟 |
| CPU 占用率高 | 未启用 DMA | 配置 SDIO DMA |
| 小文件写入慢 | 每字节都同步 | 启用写缓冲,批量写入 |
9.5 调试小技巧
- 逻辑分析仪抓 CLK/CMD/DAT0~3,对照 SD 协议时序定位问题。
- 先跑通
sdio_card_init,能拿到 CID/OCR/CSD 就说明硬件基本正常。 - 格式化用
f_mkfs("0:", FM_FAT, 0)显式指定 FAT 类型。 - 大文件用
f_lseek+ 块读写,避免频繁单字节f_write。 - 多任务访问加 Mutex,FatFs 非线程安全。
10. 进阶:USB MSC 复合设备
把 SD 卡模拟为 U 盘是嵌入式产品常用需求。GD32 内置 USB FS PHY,可与 SDIO + FatFs 组合实现 USB Mass Storage + SD 读卡器:
/* usbd_msc.c 中实现 SCSI 读写回调 */
int8_t msc_read(uint8_t lun, uint8_t *buf, uint32_t blk_addr, uint16_t blk_len)
{
if (SD_OK != sdio_block_read(buf, blk_addr, blk_len)) return -1;
return 0;
}
int8_t msc_write(uint8_t lun, uint8_t *buf, uint32_t blk_addr, uint16_t blk_len)
{
if (SD_OK != sdio_block_write(buf, blk_addr, blk_len)) return -1;
return 0;
}
注意事项:
- USB MSC 操作期间禁用 FatFs 访问,避免文件系统不一致。
- 主机弹出 U 盘前需同步写缓冲(
f_sync)。 - 启用 SCSI
START_STOP_UNIT命令支持安全弹出。
11. 进阶:抗掉电文件系统方案
FAT 文件系统天然不抗掉电,意外断电可能导致:
- 簇链损坏(文件丢失)
- FAT 表损坏(整盘不可用)
- 目录项丢失
11.1 掉电保护策略
| 方案 | 复杂度 | 效果 |
|---|---|---|
周期性 f_sync |
低 | 一般 |
| 超级电容 + 完整刷盘时间评估 | 中 | 良好 |
| 双 FAT 备份(手动维护) | 中 | 良好 |
| 改用 littlefs / SafeFAT | 高 | 优秀 |
11.2 littlefs 移植要点
littlefs 是 ARM 推出的抗掉电嵌入式文件系统,同样与底层解耦:
/* littlefs 移植示例 */
const struct lfs_config cfg = {
.read = lfs_sd_read,
.prog = lfs_sd_prog,
.erase = lfs_sd_erase,
.sync = lfs_sd_sync,
.read_size = 512,
.prog_size = 512,
.block_size = 4096,
.block_count = 0, /* 运行时计算 */
.block_cycles = 500,
.cache_size = 512,
.lookahead_size = 512,
};
12. 参考资源
12.1 官方资源
- FatFs 官方网址:http://elm-chan.org/fsw/ff/
- FatFs 文档:
doc/目录下00index.html/appnote.html - GD32 官方固件库与例程:https://www.gd32mcu.com/
12.2 SD 协议标准
- SD 2.0 / 3.0 规范(SD Association 会员可下载)
- SDIO 规范:简化版 SD 卡协议
12.3 推荐阅读
- 《嵌入式 FAT 文件系统 FatFs 原理与应用》
- ChaN 应用笔记:
doc/appnote.html - GD32F4xx 用户手册:SDIO 章节
附录 A:完整工程模板
/* main.c 示例 */
#include "gd32f4xx.h"
#include "ff.h"
#include <stdio.h>
FATFS fs;
FIL file;
int main(void)
{
SystemInit();
sdio_lowlevel_init();
delay_init();
printf("System boot.\r\n");
if (f_mount(&fs, "0:", 1) != FR_OK) {
if (f_mkfs("0:", 0, 0) != FR_OK) {
printf("Format failed.\r\n");
while (1);
}
f_mount(&fs, "0:", 1);
}
if (f_open(&file, "0:/boot.log", FA_OPEN_ALWAYS | FA_WRITE) == FR_OK) {
f_lseek(&file, f_size(&file));
f_printf(&file, "Boot at %u ms\n", (unsigned)sys_tick);
f_close(&file);
}
while (1) {
/* 主循环任务 */
}
}
附录 B:版本历史
| 版本 | 日期 | 修改人 | 说明 |
|---|---|---|---|
| v1.0 | 2026-07-15 | - | 初版发布 |
提示:实际项目中请根据具体 GD32 型号调整寄存器定义与库函数,建议结合本工程模板与官方例程交叉验证。
浙公网安备 33010602011771号