目录
[2.1 cubemx](#2.1 cubemx)
[2.2 rt-thread setting](#2.2 rt-thread setting)
[2.3 其他配置](#2.3 其他配置)
[3.1 sdio_sd.c](#3.1 sdio_sd.c)
[3.2 sdio_sd.h](#3.2 sdio_sd.h)
前言
在十二讲flsh篇中我们介绍了使用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适配器,来自OLL48CK,一般是48MHz,并分频后产生卡时钟(SDIO_CK)。
SDIO_CK与SDIOCLK关系为:
其中CLKDIV是分频系数。
二、工程创建
2.1 cubemx
stm32f4系列芯片具有SDIO模式的驱动引脚

总共有五种模式
SD 1 bit
- 含义 :SD 卡工作在 1 位数据总线 模式。
- 特点 :
- 只使用
D0这一根数据线来传输数据。 - 兼容性最好,所有 SD 卡都支持,但传输速度最慢。
- 适合对速度要求不高、只需要基本读写的场景。
- 只使用
SD 4 bits Wide bus
- 含义 :SD 卡工作在 4 位宽总线 模式。
- 特点 :
- 使用
D0~D3共 4 根数据线并行传输数据。 - 理论速度是 1 位模式的 4 倍,是 SD 卡最常用的高速模式。
- 需要硬件和 SD 卡都支持 4 位总线模式,大部分现代 SD 卡都支持。
- 使用
MMC 1 bit
- 含义 :MMC 卡(MultiMediaCard)工作在 1 位数据总线 模式。
- 特点 :
- 仅使用
D0数据线,是 MMC 卡的基础模式。 - MMC 卡是比 SD 卡更早的存储卡标准,现在使用较少。
- 速度和兼容性都和 SD 1 bit 类似,但仅针对 MMC 卡。
- 仅使用
MMC 4 bits Wide bus
- 含义 :MMC 卡工作在 4 位宽总线 模式。
- 特点 :
- 使用
D0~D3共 4 根数据线,是 MMC 卡的高速模式。 - 速度比 1 位模式快,但仍慢于 SD 4 bits 模式。
- 仅适用于支持 4 位总线的 MMC 卡。
- 使用
MMC 8 bits Wide bus
- 含义 :MMC 卡工作在 8 位宽总线 模式。
- 特点 :
- 使用
D0~D7共 8 根数据线,是 MMC 卡的最快模式。 - 这种模式需要专门的 8 位 MMC 卡,且硬件上要引出全部 8 根数据线,在普通开发板上很少见。
- 主要用于对存储带宽要求极高的工业或专业设备。
- 使用
SDIO除了支持SD卡也支持MMC卡。我们这里用的是SD卡,所以选择SD 4 bits Wide bus最好。
工作设置如下
Clock transition on which the bit capture is made
- 含义:SDIO 在时钟的哪个边沿采样数据。
- 当前设置 :
Rising transition(上升沿) - 说明 :
- SDIO 总线的时钟和数据同步方式,标准协议规定在时钟的上升沿捕获数据,因此这个值一般不需要修改。
- 下降沿捕获仅用于某些特殊兼容场景,通常保持默认即可。
SDIO Clock divider bypass
- 含义:是否绕过 SDIO 时钟分频器。
- 当前设置 :
Disable(关闭) - 说明 :
- 开启后,SDIO 直接使用
SDIOCLK作为时钟源,不再分频。 - 关闭时,时钟频率 =
SDIOCLK / (divide factor + 2),这是更常见的做法,可灵活调整时钟速度。
- 开启后,SDIO 直接使用
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(禁用) - 说明 :
- 硬件流控通过
DAT3引脚来控制数据传输的启停,防止数据溢出。 - 大多数普通场景(如 SD 卡读写)不需要启用,只有在高速、高吞吐的专业应用中才会用到。
- 硬件流控通过
SDIOCLK clock divide factor
- 含义:SDIO 时钟的分频系数。
- 当前设置 :
0 - 说明 :
- 这个值决定了最终的 SDIO 时钟频率,公式为:
SDIO 时钟频率 = SDIOCLK / (divide factor + 2)。 - 当值为
0时,时钟频率 =SDIOCLK / 2。此时SD 卡的时钟频率为24MHz,低于25MHz,满足要求
- 这个值决定了最终的 SDIO 时钟频率,公式为:
DMA具有SDIO、SDIO_RX和SDIO_TX可以选择。
SDIO
- 含义 :这是 SDIO 外设的 双向 DMA 通道,既可以用于接收(RX)也可以用于发送(TX)。
- 特点 :
- 当 SDIO 需要在单次传输中同时进行读写时,或硬件上只分配了一个 DMA 通道给 SDIO 时,会使用这个模式。
- 实际传输方向由 SDIO 控制器的操作命令决定,灵活性高,但需要软件在传输前后切换方向。
- 适合对传输方向不固定、或资源受限的场景。
资源足够下我们选择SDIO_RX和SDIO_TX双通道模式。
在时钟配置下,注意要保证红框内时钟频率为48MHz,这个就是提供给SDIO的适配器时钟。

2.2 rt-thread setting

DFS的配置内容就不用多说了,上文中有详细介绍。在这里需要注意的是**Enable RT_DFS_ELM_USE_EXFAT 开关**
这个开关的作用是:让 RT-Thread 的 FatFs 组件支持 exFAT 文件系统,突破传统 FAT32 的容量和性能限制。
经过前面的SD卡容量介绍可知,当SD卡容量大于64GB时,使用的是exFAT文件系统。此时该开关就必须开,低于这个容量可以不开。
其次是最大扇区大小设置如下
-
标准容量卡 (SDSC,≤2GB):
- 最大支持块大小:1024 字节(由 CSD 寄存器的 READ_BL_LEN 决定,READ_BL_LEN<12,最大值为 11,2^11=1024)
- 但 CMD16 命令设置的块长度最大为512 字节
-
高容量卡 (SDHC,2-32GB)、扩展容量卡 (SDXC,32GB-2TB)、超大容量卡 (SDUC,2TB-128TB):
- 块长度固定为 512 字节,无论 CMD16 如何设置
- 默认扇区大小均为512 字节,这是 SD 卡协议和文件系统(如 FAT16/FAT32、exFAT)普遍采用的标准大小

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

最后一个开关是使用SDHCI模式。
SDHCI 全称是 SD Host Controller Interface,是一种标准化的 SD 卡主机控制器接口规范。它比传统的 SDIO 外设更高效,支持更高的传输速率(如 UHS-I 高速模式),并且硬件集成了 DMA、中断优化等功能,性能更好。
如果芯片内置了SDHCI 控制器,则可以开启,没有的话必须关闭。STM32F4系列没有配置这个模式。
2.3 其他配置
其他配置就和普通外设一样,去board.h中开启BSP_USING_SDIO宏定义,在board.c末尾加入cubemx生成的初始化函数HAL_SD_MspInit和HAL_SD_MspDeInit。
三、代码编写
在qpplications创建下面两个文件
3.1 sdio_sd.c
cpp
/*
* 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给系统。
SDka设备的名称得设为"sd0",换其他名称会找不到。
下面得文件操作和文件夹操作finsh函数和上文一样,不多解释。
3.2 sdio_sd.h
cpp
/*
* 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即可。
cpp
const struct dfs_mount_tbl mount_table[] =
{
{"sd0", "/", "elm", 0, 0},
{0}
};
四、结果演示

操作内容和上差不多,有时会失败,应该是我访问速度设置太高的问题。
