s苦瓜大王

GD32 SDIO + FatFs 文件系统使用指南

GD32 SDIO + FatFs 文件系统使用指南

适用场景:基于 GD32 系列 MCU(GD32F1/F2/F3/F4/F7 等)通过 SDIO 接口外接 SD 卡,并移植 FatFs(R0.13 及以上)实现 FAT 文件系统读写。
文档版本:v1.0
适用读者:嵌入式软件开发工程师


目录

  1. 概述
  2. 硬件设计
  3. CubeMX/标准库工程配置
  4. SDIO 驱动实现
  5. FatFs 移植
  6. 应用层 API 使用示例
  7. ffconf.h 关键配置
  8. 性能优化
  9. 常见问题与排查
  10. 进阶:USB MSC 复合设备
  11. 进阶:抗掉电文件系统方案
  12. 参考资源

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.csdio.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_syncf_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 调试小技巧

  1. 逻辑分析仪抓 CLK/CMD/DAT0~3,对照 SD 协议时序定位问题。
  2. 先跑通 sdio_card_init,能拿到 CID/OCR/CSD 就说明硬件基本正常。
  3. 格式化用 f_mkfs("0:", FM_FAT, 0) 显式指定 FAT 类型。
  4. 大文件用 f_lseek + 块读写,避免频繁单字节 f_write
  5. 多任务访问加 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 官方资源

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 型号调整寄存器定义与库函数,建议结合本工程模板与官方例程交叉验证。

posted on 2026-07-15 15:45  s苦瓜大王  阅读(5)  评论(0)    收藏  举报