前言
在之前的 Flash 篇中介绍了使用 DFS 文件系统管理外置 Flash 芯片的存储空间,除此之外还有一种常见的外置存储设备即 SD 卡。本文基于 DFS 文件系统来管理 SD 卡。以 STM32F4 探索板为例,使用的 RT-Thread 驱动版本为 5.2.2。 关于 DFS 文件系统的详细介绍可参考相关文档,本文不再重复。
一、SD 卡概述
SD 卡(Secure Digital Memory Card)是一种基于闪存技术的便携式存储设备。主要分为以下两种规格:
- 标准 SD 卡:尺寸为 32mm×24mm×2.1mm,多用于相机等设备。
- microSD 卡:尺寸为 15mm×11mm×1mm,常用于手机、无人机等小型设备。
容量分类
- SDSC(标准容量):最大支持 2GB,使用 FAT16 文件系统。
- SDHC(高容量):容量范围为 4GB~32GB,使用 FAT32 文件系统。
- SDXC(扩展容量):容量范围为 64GB~2TB,使用 exFAT 文件系统。

SD 卡的接口主要有 SDIO 模式和 SPI 模式。下图是标准 SD 卡的两种模式的引脚定义。microSD 卡相比下少一个 VSS 引脚。

因为我们使用的是操作系统,可以不用太关注底层硬件架构。简单说明一下 SDIO 模式的通讯。
SDIO 模式使用 SD 总线协议,支持 4 位数据线(DAT0-DAT3)和 1 位命令线(CMD),理论传输速率更高(UHS-I 可达 104MB/s)。SPI 模式仅使用 1 位数据线(MOSI/MISO)和 1 位时钟线(SCK),协议更简单但速率较低(通常低于 25MB/s)。
SDIO 模式需要两个时钟:
- 卡时钟(SDIO_CK):每个时钟周期在命令和数据线上传输一位命令和数据。对于 SD 卡,这个频率可以在 0-25MHz 之间。
- SDIO 适配时钟(SDIOCLK):该时钟用于驱动 SDIO 适配器,来自 PLL48CK,一般是 48MHz,并分频后产生卡时钟(SDIO_CK)。
SDIO_CK 与 SDIOCLK 关系为:
其中 CLKDIV 是分频系数。
二、工程创建
2.1 CubeMX 配置
STM32F4 系列芯片具有 SDIO 模式的驱动引脚。
总共有五种模式:
- SD 1 bit:只使用 D0 这一根数据线,兼容性最好,速度最慢。
- SD 4 bits Wide bus:使用 D0~D3 共 4 根数据线并行传输,理论速度是 1 位模式的 4 倍,最常用的高速模式。
- MMC 1 bit:仅使用 D0 数据线,针对 MMC 卡。
- MMC 4 bits Wide bus:使用 D0~D3 共 4 根数据线,适用于支持 4 位总线的 MMC 卡。
- MMC 8 bits Wide bus:使用 D0~D7 共 8 根数据线,主要用于对存储带宽要求极高的工业或专业设备。
SDIO 除了支持 SD 卡也支持 MMC 卡。我们这里用的是 SD 卡,所以选择SD 4 bits Wide bus最好。
工作设置如下:
- Clock transition on which the bit capture is made:当前设置为
Rising transition(上升沿)。SDIO 总线的时钟和数据同步方式,标准协议规定在时钟的上升沿捕获数据。 - SDIO Clock divider bypass:当前设置为
Disable(关闭)。开启后直接使用 SDIOCLK 作为时钟源,关闭时通过分频调整速度。 - SDIO Clock output enable when the bus is idle:当前设置为
Disable the power save for the clock(关闭时钟省电模式)。空闲时仍会输出时钟,兼容性更好。 - SDIO hardware flow control:当前设置为
The hardware control flow is disabled(禁用)。大多数普通场景不需要启用。 - SDIOCLK clock divide factor:当前设置为
0。此时 SD 卡的时钟频率为 24MHz,低于 25MHz,满足要求。

DMA 具有 SDIO、SDIO_RX 和 SDIO_TX 可以选择。资源足够下我们选择 SDIO_RX 和 SDIO_TX 双通道模式。
在时钟配置下,注意要保证红框内时钟频率为 48MHz,这个就是提供给 SDIO 的适配器时钟。

2.2 RT-Thread 配置
DFS 的配置内容无需过多赘述,在此需要注意的是**Enable RT_DFS_ELM_USE_EXFAT 开关**。
这个开关的作用是:让 RT-Thread 的 FatFs 组件支持 exFAT 文件系统,突破传统 FAT32 的容量和性能限制。当 SD 卡容量大于 64GB 时,使用的是 exFAT 文件系统,此时该开关必须打开。
其次是最大扇区大小设置:
- 标准容量卡 (SDSC,≤2GB):最大支持块大小 1024 字节,但 CMD16 命令设置的块长度最大为 512 字节。
- 高容量卡 (SDHC)、扩展容量卡 (SDXC)、超大容量卡 (SDUC):块长度固定为 512 字节,默认扇区大小均为 512 字节。

注意内核对象名称的最大长度默认为 8,需改为 32。因为 SD 卡设备初始化时,系统会自动创建的对象名 mmcsdhotplugmb 长度超过了这个限制,会导致系统初始化失败。
将两个 SD 卡的线程栈可以适当增大,按原先大小发现会栈溢出,导致错误。

最后一个开关是使用 SDHCI 模式。如果芯片内置了 SDHCI 控制器,则可以开启,没有的话必须关闭。STM32F4 系列没有配置这个模式。
2.3 其他配置
去 board.h 中开启 BSP_USING_SDIO 宏定义,在 board.c 末尾加入 cubemx 生成的初始化函数 HAL_SD_MspInit 和 HAL_SD_MspDeInit。
此时编译文件会发生报错,原因是 'cache_buf' undeclared (first use in this function),经排查为 ALIGN 未定义。我们需要加上 ALIGN 定义。
进入文件 drivers\drv_sdio.c,在文件开头加入如下定义:
#define ALIGN(align) __attribute__((aligned(align)))
此时再编译发现报错消失,工程配置完毕。
三、代码编写
在 applications 创建下面两个文件。
3.1 sdio_sd.c
/*
* Copyright (c) 2006-2021, RT-Thread Development Team
*
* SPDX-License-Identifier: Apache-2.0
*
* Change Logs:
* Date Author Notes
* 2026-01-20 H1567 the first version
*/
#include "sdio_sd.h"
#define DBG_TAG "SDIO"
#define DBG_LVL DBG_LOG
#include <rtdbg.h>
#define SD_NAME "sd0"
#define SD_path "/"
void sdio_sd_init(void)
{
rt_thread_mdelay(500);
uint8_t dfs_flag;
dfs_flag = dfs_mount(SD_NAME, SD_path, "elm", 0, 0);
if(dfs_flag != 0){
LOG_D("enable dfs mkfs");
dfs_mkfs("elm", SD_NAME);
dfs_flag = dfs_mount(SD_NAME, SD_path, "elm", 0, 0);
if(dfs_flag != 0){
LOG_D("failed to SD device dfs mount");
}
}
}
int fd;
char readBuf[32] = {0};
char writeBuf[32] = "Hello rt-thread!\n";
void file(int argc, char **argv)
{
if(!rt_strcmp(argv[1], "open")){
if(argv[2] != 0) fd = open(argv[2], O_RDWR|O_CREAT|O_APPEND);
else {
rt_kprintf("error\n");
}
} else if (!rt_strcmp(argv[1], "close")) {
close(fd);
} else if (!rt_strcmp(argv[1], "read")) {
read(fd, readBuf, sizeof(readBuf));
} else if (!rt_strcmp(argv[1], "write")) {
write(fd, writeBuf, sizeof(writeBuf));
} else if (!rt_strcmp(argv[1], "rename")) {
if(argv[2] != 0) rename(argv[2], argv[3]);
else {
rt_kprintf("error\n");
}
} else if (!rt_strcmp(argv[1], "unlink")) {
if(argv[2] != 0) unlink(argv[2]);
else {
rt_kprintf("error\n");
}
}
}
MSH_CMD_EXPORT(file, dfs file management);
struct dirent *d;
DIR *dirp;
void directory(int argc, char **argv)
{
if(!rt_strcmp(argv[1], "mkdir")){
if(argv[2] != 0) mkdir(argv[2], 0x777);
else {
rt_kprintf("error\n");
}
} else if (!rt_strcmp(argv[1], "rmdir")) {
if(argv[2] != 0) rmdir(argv[2]);
else {
rt_kprintf("error\n");
}
} else if (!rt_strcmp(argv[1], "opendir")) {
if(argv[2] != 0) dirp = opendir(argv[2]);
else {
rt_kprintf("error\n");
}
} else if (!rt_strcmp(argv[1], "closedir")) {
closedir(dirp);
} else if (!rt_strcmp(argv[1], "readdir")) {
d = readdir(dirp);
rt_kprintf("found %s\n",d->d_name);
}
}
MSH_CMD_EXPORT(directory, dfs directory management);
注意初始化函数 sdio_sd_init 开始的 500ms 延时不可少。用户不需要配置 SDIO 的各种初始化,这些系统自动完成,但只有初始化完成后才能进行设备绑定,所以要预留 500ms 给系统。
SD 卡设备的名称得设为'sd0',换其他名称会找不到。
下面的文件操作和文件夹操作 FinSH 函数逻辑同上。
3.2 sdio_sd.h
/*
* Copyright (c) 2006-2021, RT-Thread Development Team
*
* SPDX-License-Identifier: Apache-2.0
*
* Change Logs:
* Date Author Notes
* 2026-01-20 H1567 the first version
*/
#ifndef APPLICATIONS_SDIO_SD_H_
#define APPLICATIONS_SDIO_SD_H_
#include <board.h>
#include <dfs_fs.h>
#include <unistd.h>
void sdio_sd_init(void);
#endif /* APPLICATIONS_SDIO_SD_H_ */
特别说明:
DFS 具有自动挂载模式,但这个模式有时会挂载失败,因此本例不使用。若使用的话在 setting 打开开关,用下面常量结构体代替初始化函数 sdio_sd_init 即可。
const struct dfs_mount_tbl mount_table[] = {
{"sd0", "/", "elm", 0, 0},
{0}
};
四、结果演示

操作流程与前述 Flash 篇类似,若遇异常可尝试降低时钟频率以确保稳定性。

