当前位置: 首页 > java >正文

SD卡及FATFS文件系统

SD卡及FATFS文件系统

  • 一,SD卡简介
  • 二,SD卡基础
    • 2.1 SD卡类型与规格
  • 测试历程
    • 1,测试开始
    • 2,挂载文件系统
    • 3,创建测试目录
    • 4,创建并写入测试文件
    • 5,读取测试文件

一,SD卡简介

SD卡以其容量大、成本适中、接口相对简单等优点,成为了扩展存储的常用选择
直接操作SD卡的物理扇区非常复杂且低效。为了方便管理SD卡上的数据,我们需要文件系统
本节我们将通过SDIO接口(或SPI模式,但本教程重点关注SDIO)使用FATFS文件系统来读写SD卡
这里的SDIO模式怎么和SPI模式平起平坐了?
SDIO模式只是局限于SD卡,SDIO 和 SPI 都是“物理总线协议”(physical bus protocols),也就是通信接口,在使用SD卡时我们可以选择这两种模式去驱动SD卡,就像前面使用iic,spi去驱动从机一样

二,SD卡基础

2.1 SD卡类型与规格

物理尺寸: 主要有标准SD卡、miniSD卡和microSD卡。嵌入式应用中microSD卡因其小巧而最为常见。
容量标准:
SDSC (Standard Capacity): 容量最高至2GB。
SDHC (High Capacity): 容量从2GB到32GB。
SDXC (Extended Capacity): 容量从32GB到2TB。
SDUC (Ultra Capacity): 容量从2TB到128TB (较新标准)。

MCU对不同容量标准的支持可能有限,务必查阅MCU和FATFS库的兼容性说明。

速度等级 (Speed Class): 表示最低写入速度,如Class 2 (2MB/s), Class 4 (4MB/s), Class 6 (6MB/s), Class 10 (10MB/s)。还有UHS (Ultra High Speed) 等级如U1 (10MB/s), U3 (30MB/s),以及视频速度等级V6, V10, V30等。选择合适的SD卡需考虑应用对读写性能的要求。

这里的容量标准是怎么来的?有什么作用?我是小白,对于速度等级完全不理解

容量标准(SDSC/SDHC/SDXC/SDUC)其实是由 SD 协会(SD Association)在不同时间制定的一系列规范

标准容量范围支持的文件系统主流用途
SDSCup to 2 GB 字节寻址FAT16早期数码相机、旧设备
SDHC> 2 GB – 32 GB 扇区寻址FAT32现在最常见,用于手机、树莓派、通用存储
SDXC> 32 GB – 2 TB 扇区寻址exFAT高清视频录制、大文件存储(4 K/8 K)
SDUC> 2 TB – 128 TB 扩展寻址exFAT专业视频、数据中心、嵌入式大容量存储(新兴)

不同的容量标准与它自己的寻址方式绑定,什么是寻址方式呢?
当主机(MCU、相机、电脑)要读写 SD 卡时,会通过 SD 命令(CMD17、CMD24 等)告诉卡“从地址 X 开始读 N 个字节”或“把 N 个字节写到地址 X”。这个“地址 X”到底表示字节偏移(byte offset)还是块/扇区编号(block number),就是“寻址方式”的区别:
1,当X表示字节时,SD卡就是字节寻址(与前面的iic中的列高地址和列低地址一样,X表示命令的同时也表示地址位置/偏移),在SD卡的命令中,命令为32位,所以可以访问取值范围 0…2³²–1(≈4 GiB)内的任意字节,但是SDSC最大只有2 GiB,所以可以访问SDSC的所有内容

2,当X表示扇区号时,SD卡就是扇区寻址,X加1就是访问一个扇区512B,X同样是32位,这样 32 位扇区号可覆盖 2³² × 512 B ≈ 2 TiB 的容量,正好对应 SDXC 卡上限

3,扩展寻址,SDUC(>2 TiB–128 TiB)卡

所以我们可以发现不同的寻址方式解决的是容量问题

除此之外,我们还可以发现不同的容量标准与不同的文件系统绑定,为什么呢?
我们先把文件系统比喻成索引系统,小容量的SD卡只需要简单的索引,而大容量的SD卡,就需要更大的索引系统
那么到底什么是文件系统呢?
文件系统就是管理文件的规则
一个成品设备如相机、手机、电脑、车载系统、嵌入式设备……它们的固件(或操作系统)里只内置了对某些文件系统/规则的“翻译器”。
如果你给它插一个它“不认识”的文件系统(比如用 NTFS、ext4、APFS 等),它就“听不懂”卡里的目录结构,看不到任何文件
SD 协会为了存储卡容量与设备兼容,就对各代 SD 卡推荐了上面的文件系统:他们不是把文件系统像固件一样“写死”在卡片里,而是将SD卡在出厂时预先格式化(format) 成了推荐的文件系统,我们用户可以自行重格式化,修改文件系统

就算是SD协会制定了SD标准,成品设备是怎么和SD标准达成一致的呢?
成品设备之所以能读取不同容量的 SD 卡,本质上在于它的软件/固件里包含了多个文件系统的支持(以及对应的 SD 协议驱动),这样才能对应 SDSC、SDHC、SDXC/SDUC 等卡型在出厂时常用的文件系统

我们平时使用成品设备时直接插入SD卡就能读取里面数据,但是对于半成品开发板STM32来说,我们还需要在使用之前先初始化 SDIO 驱动,再装FatFS 驱动

为什么会这样呢?这就是裸机和操作系统的区别了:
操作系统 vs. 裸机(Bare-metal)
相机、手机、电脑都运行着操作系统(OS)
手机常见的有 Android、iOS;电脑是 Windows、macOS、Linux。
一,操作系统里已经集成了
1,SD 卡驱动
2,文件系统模块(FAT/exFAT)
二,自动挂载:插卡后,系统就自动识别、挂载(mount)出一个盘符,你直接读写文件就好。
然而STM32 裸机没有操作系统(除非你另行移植一个 RTOS)
一,出厂出来只有最底层的寄存器、外设,需要我们用 CubeMX 配置 SDIO/SPI、引脚、时钟、DMA实现硬件初始化
二,没有SD 卡驱动,要自己去写并调用 HAL_SD_Init()
三,没有现成的文件系统,需要把 FatFS(或其他文件系统)集成进来
四,手动挂载:调用 f_mount()(FatFS)把底层驱动和文件系统连起来
五,最后 再去用 f_open()、f_read()、f_write() 这类 API 读写文件

相机/手机/电脑 跟 STM32 这类裸机 MCU 在使用 SD 卡时,有两个关键区别
文件系统本身的容量/文件数限制

FAT12/16(SDSC 卡,≤2 GiB)

FAT12 的聚簇数最多 4 096 个,簇大小最小 512 B,所以最大只到 ~2 MiB;

FAT16 的聚簇表项 16 位,最多 65 536 个簇,即使用最大 32 KiB 聚簇,也只能支持到 2 GiB 左右。

FAT32(SDHC 卡,>2 GiB–32 GiB)

FAT32 用 28 位表项,支持最多 ~268 435 456 个簇,配合最小 512 B 聚簇即可覆盖 ~128 TiB,但 Windows 默认只在 32 GiB 以下格式化为 FAT32,主要为了性能和卷标兼容性。

exFAT(SDXC、SDUC 卡,>32 GiB)

exFAT 设计时就面向超大卷(最高可到数百 PB),目录表和簇号都用了更宽的字段,支持更大的文件和更多的文件。

兼容性与规范要求

SD 规范里对不同卡型(SDSC/SDHC/SDXC/SDUC)都有“推荐使用”的文件系统:

SDSC 卡通常用 FAT16;

SDHC 卡上较小容量(≤32 GiB)依然用 FAT32;

SDXC/SDUC 卡(>32 GiB)强制或推荐用 exFAT。

这样做能保证:

主机设备(如相机、智能手机、电脑)在出厂时就能识别、读写,不用额外安装驱动;

跨平台(Windows、macOS、Linux、嵌入式系统)有一致的最低支持。

性能和资源消耗

较小的卡如果用 exFAT,会带来更多的文件系统元数据开销;

大卡若用 FAT32,目录/聚簇表过大,查找和维护速度会急剧下降,且容易产生大量碎片。

专利与授权

exFAT 是微软的专利文件系统,SDXC/SDUC 标准中包含了 exFAT 的授权条款;

对于低容量的 SDSC/SDHC 卡,使用开源的 FAT16/FAT32,无需支付专利费用,更经济。

测试历程

1,测试开始

my_printf(&huart1, "\r\n--- SD卡FATFS文件系统测试开始 ---\r\n");

2,挂载文件系统

挂载是建立硬件(SD卡)与软件(文件系统API)之间的联系
挂载前,系统无法读写SD卡中的任何文件
挂载后,系统可以通过FATFS提供的函数(如f_open、f_read等)访问SD卡中的内容
代码中使用f_mount函数完成挂载操作

if (f_mount(&SDFatFS, SDPath, 1) != FR_OK){
my_printf(&huart1, "SD卡挂载失败,请检查SD卡连接或是否初始化\r\n");return;}
my_printf(&huart1, "SD卡挂载成功\r\n");

函数解析:

FRESULT f_mount(FATFS* fs, const TCHAR* path, BYTE opt)

参数fs:指向要注册/注销的文件系统对象的指针
参数path:驱动器号,最常见的格式是 “0:”、“1:”,后面可以跟一个斜杠 /,比如 “0:/”

如果传入空字符串 “”,则表示使用默认驱动器,一般就是 0:

参数opt: 挂载选项。0 表示延迟挂载 (仅注册工作区,实际的卷初始化在首次访问时进行);1 表示立即挂载 (执行卷初始化并检查卷状态)。

这里的驱动器是什么东西?
是对底层每个存储介质(比如 SD 卡、内部 Flash、USB 盘……)的一个逻辑编号或“盘符”,它不是物理设备,他是物理设备通过diskio.c 里的读写函数注册给 FatFS时分给物理设备的编号

以上两个参数在fatfs.c 和 fatfs.h中有定义

下载调试后串口提示挂载失败:
— SD卡FATFS文件系统测试开始 —
SD卡挂载失败,请检查SD卡连接或是否初始化

检查res值:

if (f_mount(&SDFatFS, SDPath, 1) != FR_OK){my_printf(&huart1,"FR_OK = %d\n", (int)FR_OK);my_printf(&huart1,"f_mount returned: %d\n", (int)res);my_printf(&huart1, "SD卡挂载失败,请检查SD卡连接或是否初始化\r\n");return;}

调试结果:
— SD卡FATFS文件系统测试开始 —
FR_OK = 0
f_mount returned: 134432800
SD卡挂载失败,请检查SD卡连接或是否初始化

开启keil调试:

3,创建测试目录

使用函数 f_mkdir 创建一个新目录

    res = f_mkdir("测试目录");if (res == FR_OK){my_printf(&huart1, "创建测试目录成功\r\n");}else if (res == FR_EXIST){my_printf(&huart1, "测试目录已存在\r\n");}else{my_printf(&huart1, "创建目录失败,错误码: %d\r\n", res);}

我们可以ff.c文件中看到FRESULT f_mkdir (
const TCHAR* path /* Pointer to the directory path */
)

这个函数的形参是const TCHAR* path,TCHAR等同于char,代表字符串常量:
单级目录名,比如 “TESTDIR” 或者 “测试目录”。
相对路径,比如 “测试目录/sub1”。
绝对路径,如果你在多驱动器环境下,可以写
“0:/DATA”、“1:/LOGS/2025”,其中前缀的 0:、1: 表示不同的挂载号

4,创建并写入测试文件

这一步涉及两个函数:
FRESULT f_open(FIL* fp, const TCHAR* path, BYTE mode)
这个函数第一个形参是FIL SDFile; 是指向文件对象的指针
形参const TCHAR *path 代表文件名或路径字符串
如果我们传入TestFileName
它会在当前挂载盘的根目录下创建/打开名为 SD_TEST.TXT 的文件。
如果你想放在子目录里,可以传 “测试目录/SD_TEST.TXT”,或者加上驱动号 “0:/SD_TEST.TXT”,前提是你已正确挂载。
形参BYTE mode 用于指定「打开方式」和「访问权限」,FatFs 里定义了一系列宏,你可以通过「按位或」来组合。常用的几个有:

宏名含义
FA_READ0x01以读模式打开文件
FA_WRITE0x02以写模式打开文件
FA_OPEN_EXISTING0x00仅当文件已存在时打开,否则失败(默认行为)
FA_CREATE_NEW0x04创建新文件;如果文件已存在,则失败返回 FR_EXIST
FA_CREATE_ALWAYS0x08创建新文件;若已存在则先删除旧文件,再新建一个空文件
FA_OPEN_ALWAYS0x10如果文件已存在就打开它,否则创建新文件
FA_OPEN_APPEND0x30FA_OPEN_ALWAYS,但文件指针定位到文件尾以追加写入

第二个函数是f_write

FRESULT f_write (FIL* fp,           /* [in]  指向已打开文件的文件对象指针 */const void* buff,  /* [in]  指向要写入数据缓冲区的指针 */UINT btw,          /* [in]  要写入的字节数 */UINT* bw           /* [out] 实际写入的字节数 */
);

这个函数有四个参数:
FIL* fp:指向刚才f_open打开的文件对象,文件必须已用 FA_WRITE打开
const void* buff:指向存放要写入内容的缓冲区。可以是字符串、二进制数据、结构体数组
UINT btw :要写入的最大字节数

如果 *bw < btw,说明写入过程中发生了中断或错误。

    my_printf(&huart1, "创建并写入测试文件...\r\n");res = f_open(&SDFile, TestFileName, FA_CREATE_ALWAYS | FA_WRITE);if (res == FR_OK){// 写入数据res = f_write(&SDFile, WriteBuffer, strlen(WriteBuffer), &bw);if (res == FR_OK && bw == strlen(WriteBuffer)){my_printf(&huart1, "写入文件成功: %u 字节\r\n", bw);}else{my_printf(&huart1, "写入文件失败,错误码: %d\r\n", res);}// 关闭文件f_close(&SDFile);}else{my_printf(&huart1, "创建文件失败,错误码: %d\r\n", res);}

5,读取测试文件

    my_printf(&huart1, "读取测试文件...\r\n");memset(ReadBuffer, 0, sizeof(ReadBuffer));res = f_open(&SDFile, TestFileName, FA_READ);if (res == FR_OK){// 读取数据res = f_read(&SDFile, ReadBuffer, sizeof(ReadBuffer) - 1, &br);if (res == FR_OK){ReadBuffer[br] = '\0'; // 确保字符串结束符my_printf(&huart1, "读取文件成功: %u 字节\r\n", br);my_printf(&huart1, "文件内容: %s\r\n", ReadBuffer);// 验证数据一致性if (strcmp(ReadBuffer, WriteBuffer) == 0){my_printf(&huart1, "数据验证成功: 读写数据一致\r\n");}else{my_printf(&huart1, "数据验证失败: 读写数据不一致\r\n");}}else{my_printf(&huart1, "读取文件失败,错误码: %d\r\n", res);}// 关闭文件f_close(&SDFile);}else{my_printf(&huart1, "打开文件失败,错误码: %d\r\n", res);}
http://www.xdnf.cn/news/13466.html

相关文章:

  • 马里兰大学:LLM过度思考降低性能
  • 打卡第42天:简单CNN
  • PyTorch 中torch.einsum函数的使用详解和工程应用示例
  • QML显示图片问题解决办法
  • IDEA的git提交代码提交失败,有错误0 个文件已提交,1 个文件提交失败:
  • 双路 CPU 物理服务器租用服务
  • 鹰盾视频加密器Windows播放器禁止虚拟机运行的技术实现解析
  • 青藏高原ASTER_GDEM数据集(2011)
  • Linux C学习路线全概括及知识点笔记3-网络编程
  • AI 视频创作技术全解析:从环境搭建到实战落地​
  • 2025年的WWDC所更新的内容
  • JS 原型与原型链详解
  • mac redis以守护进程重新启动
  • MySQL之事务与视图
  • 【笔记】Kubernetes 中手动及自动化证书更换步骤及注意事项
  • 如何开启自己计算机远程桌面连接功能? 给别人或异地访问
  • 8.Vue的watch监视
  • 从sdp开始到webrtc的通信过程
  • 第二十六课:手搓梯度增强
  • 深入浅出:C++深拷贝与浅拷贝
  • Jadx(开源AVA反编译工具) v1.5.0
  • 编译线程安全的HDF5库
  • Python环境搭建竞赛技术
  • 代码训练LeetCode(29)最后一个单词的长度
  • Github月度新锐热门工具 - 202506
  • PyTorch:让深度学习像搭积木一样简单!!!
  • 邮件限流器
  • 《Redis》持久化
  • 国产linux系统(银河麒麟,统信uos)使用 PageOffice实现word 文档中的table插入新行并赋值
  • 论文略读:RegMix: Data Mixture as Regression for Language Model Pre-training