STM32F103 SPI驱动SD卡:从初始化到FatFS文件系统
2026/9/16 19:10:32 网站建设 项目流程

简介:这是一份使用STM32F103作为基于SPI的SD卡读卡器的工程示例,面向嵌入式入门开发者与单片机学习者,目标是在常见的Blue Pill开发板上通过SPI接口驱动Micro SD卡,并借助USB实现与电脑通信的读卡功能。工程在STM32 System Workbench下搭建和测试,源码基于HAL库组织,涵盖SPI、SD、TIM、USART及USB等外设驱动文件;附件同时给出简洁的接线说明,包括SD卡CS/SCK/MISO/MOSI对应PA4-PA7,以及USB D+需外接上拉电阻等关键细节,便于快速复现和移植。资源包整体组织清晰,压缩包共171个文件,以C源文件与头文件为主体,各有60个和96个,另含工程配置、链接脚本、启动汇编文件、说明文档等辅助内容,整体仅1.87MB。已有1188人学习下载,适合具备一定单片机基础、希望深入理解SPI协议与SD卡底层交互的开发者作为参考,也可作为课堂实验或DIY读卡器项目的起点。

1. 为什么用 STM32F103 给 SD 卡加一个 SPI 读卡器

一块 STM32F103 最小系统板、四根 SPI 线,再加一张 SD 卡,就能把单片机的存储空间扩展成以 GB 计的文件池。这在数据记录器、Bootloader、配置存储这些场景里几乎成了标配,主要原因倒不是因为 F103 性能多强,而是 SPI 协议简单到 GPIO 翻转就能模拟,SD 卡借助 SPI 模式又只需要 4 根线,比 SDIO 接口省掉一半以上的引脚和中断管理成本。标题里提到的“读卡器”,落到工程上其实是三个动作:让 SD 卡进入 SPI 模式、按扇区读写、再把文件系统挂到这些扇区上。

下面把这条链路完整拆一遍,从 F103 的 SPI 外设配置开始,走完 CMD0 到 ACMD41 的初始化,最后做一个能用串口命令读扇区的读卡器。新手按步骤能在最小系统板上跑通,老手也能对照着检查时钟分频和片选时序这些容易出问题的边界。

2. STM32F103 的 SPI 硬件配置与接线模型

2.1 SPI 模式下的 SD 卡接线:CS、SCK、MOSI、MISO 怎么接

SPI 模式下 SD 卡对外暴露的只有 4 根信号线外加电源地。CS 对应卡座的片选,SCK、MOSI、MISO 分别接卡座的 5、6、2、7 脚。这里最容易犯的错是把卡座的引脚号和信号名记反,初始化时 CMD0 会一直返回 0xFF,因为数据根本没有进到卡里。以 F103 的 SPI1 为例,常见接法用 PA5 做 SCK、PA6 做 MISO、PA7 做 MOSI、PA4 做软件片选,这个组合能直接照搬到绝大多数 F103 最小系统板上。

电源和电平是另一个容易忽略的点。SD 卡需要 2.7V 到 3.6V 供电,和 F103 的 3.3V IO 正好匹配,但不能直接从 5V 引脚取电,要经过板上的 3.3V 稳压器。卡座的 VSS 要单独走地线,别和 SPI 信号线缠在同一条杜邦线束里。SCK、MOSI、CS 三条从 MCU 到卡座的线上串 22 到 33 欧姆电阻,能明显减少时钟边沿的过冲,杜邦线长度超过 10 cm 时这个做法尤其有效。

2.2 软件片选与硬件片选:SD 卡为什么不建议用 SPI 的 NSS 自动拉低

片选有两种做法。硬件片选由 SPI 外设的 NSS 引脚自动控制,在每帧数据传输前自动拉低;软件片选则把 CS 当普通 GPIO,在操作前手动拉低、结束再拉高。SD 卡在 SPI 模式下对 CS 的要求比较特殊:读一个扇区时,从发送 CMD17 到读完 512 字节数据令牌,CS 必须一直保持低电平,中间不能出现高电平脉冲。硬件 NSS 的自动翻转是按帧为单位控的,做不到这种跨多帧的连续片选,所以实际项目里基本都用软件片选。

片选方式常用引脚优点代价
软件片选PA4 或任意 GPIOCS 时序完全可控,适合多帧连续操作每条 SPI 操作前后要多写 GPIO 语句
硬件 NSSPA4 复用功能少占用 GPIO,代码量少NSS 在帧间自动拉高,SD 多块读会被强行打断

F103 的 SPI1 默认 NSS 功能也在 PA4 上,用软件片选时要注意别把 PA4 配置成复用功能。

2.3 CUBEMX 参数怎么设:CPOL、CPHA、预分频与 NSS 模式

SD 卡 SPI 模式支持 mode 0 和 mode 3,CUBEMX 里默认的 mode 0 就能用。参数设置大致是:Mode 选 Full-Duplex Master,Data Size 选 8 Bits,CPOL 选 Low,CPHA 选 1 Edge,NSS 选 Software,BaudRatePrescaler 初始化阶段选 32。按 F103 的 APB2 时钟 72 MHz 计算,预分频 32 得到 2.25 MHz 的 SCK,这是 SD 卡上电初始化阶段的安全值。

HAL 初始化代码对应如下:

hspi1.Instance = SPI1; hspi1.Init.Mode = SPI_MODE_MASTER; hspi1.Init.Direction = SPI_DIRECTION_2LINES; hspi1.Init.DataSize = SPI_DATASIZE_8BIT; hspi1.Init.CLKPolarity = SPI_POLARITY_LOW; hspi1.Init.CLKPhase = SPI_PHASE_1EDGE; hspi1.Init.NSS = SPI_NSS_SOFT; hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_32; hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB; HAL_SPI_Init(&hspi1);

代码里每个字段都能在 CUBEMX 界面上找到对应项。CPOL 为 Low 表示 SCK 空闲为低电平,CPHA 为 1 Edge 表示在第一个边沿采样,两者组合就是 SPI mode 0。NSS 必须选 Software,因为片选的拉低拉高时机由我们自己控制。

如果你还在用 STM32F103 标准外设库 v3.5.0,也就是网上很多老工程里 stm32f103 库 v3.50 的版本,对应写法是 SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low、SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge、SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_32,初始化之后还要补一句 SPI_Cmd(SPI1, ENABLE)。两种方式时钟相位完全等价,区别只在 HAL 对外设时钟的管理更隐蔽。

注意 BaudRatePrescaler 不是定死不变的。SD 卡初始化完成后可以把分频从 32 切到 8 甚至 4,让 SCK 从 2.25 MHz 升到 9 MHz 或 18 MHz。F103 的 SPI1 最高只能到 18 MHz,分频 4 已经是上限,不要尝试用 2 分频跑 36 MHz,SCK 波形会严重变形。切换时机放在 SD_InitCard 返回成功之后、第一次挂载文件系统之前。

3. SPI 协议下 SD 卡初始化:CMD0 到 ACMD41 的完整时序

3.1 上电后为什么必须给 74 个时钟脉冲

SD 卡只有在 CS 高电平状态下收到足够多的 SCK 时钟,才会进入 SPI 模式。这是数据手册明确写的门槛,规范要求至少 74 个时钟,常见做法是发 80 个,也就是 10 个 0xFF 字节。这 80 个时钟有两个作用:一是让卡内部的供电检测逻辑完成上电判定,二是让卡在切到 SPI 模式前先看到完整的时钟序列。

static void SD_PowerUp(void) { uint8_t i; SD_CS_HIGH(); // 先保持 CS 高,让卡不选中 for (i = 0; i < 10; i++) { // 10 字节等于 80 个 SCK SD_SPI_Byte(0xFF); } }

这段代码必须放在所有命令之前。CS 在上电阶段必须是高电平,如果上电时 CS 被拉低,卡会把后续时钟当作 SD 模式处理,CMD0 就不会进入 SPI 模式分支。

3.2 命令帧结构与 R1、R3、R7 响应怎么读

每条命令是 6 个字节:第一个字节是 0x40 和命令号的位或,中间四个字节是参数,最后一个字节是 CRC7。SPI 模式下 CRC 检查默认关闭,但 CMD0 和 CMD8 必须填正确 CRC,因为部分卡在初始化阶段强制校验这两个命令。后面 CMD55、ACMD41 的 CRC 字节填 0x01 即可。

命令帧首字节参数预期响应作用
CMD00x400x000000000x01进入 SPI 模式
CMD80x480x000001AA0x01 后跟 0x1AA探测 SD v2 与电压范围
CMD550x770x000000000x01告诉卡下一命令是 ACMD
ACMD410x690x400000000x00初始化完成,HCS=1
CMD580x7A0x000000000x00 后跟 OCR读 OCR 判断 SDHC/SDXC

R1 是一个字节,最高位为 0 表示命令被接受;0x01 表示卡处于空闲状态,0x00 表示初始化完成。CMD8 返回的是 R7,前一个字节是 R1,后面跟 4 字节,低 12 位应该回显 0x1AA。CMD58 返回 R3,后面 4 字节里 bit30 是 CCS 位,CCS=1 代表 SDHC/SDXC 的块寻址,CCS=0 代表 SDSC 的字节寻址。

3.3 初始化代码与 v1.x、v2.0 的分支

uint8_t SD_SendCommand(uint8_t cmd, uint32_t arg, uint8_t crc) { uint8_t frame[6], resp; frame[0] = 0x40 | cmd; frame[1] = (arg >> 24) & 0xFF; frame[2] = (arg >> 16) & 0xFF; frame[3] = (arg >> 8) & 0xFF; frame[4] = arg & 0xFF; frame[5] = crc; SD_SPI_Transmit(frame, 6); resp = SD_SPI_Byte(0xFF); // 丢弃卡从接收到响应的填充 for (int i = 0; i < 8; i++) { resp = SD_SPI_Byte(0xFF); if ((resp & 0x80) == 0) break; // 最高位为 0 才是有效 R1 } return resp; }

逻辑上要注意发送完 6 字节后,卡需要几个时钟周期才把 R1 放到 MISO 上,所以先读一个无效字节,再在后续字节里找最高位为 0 的响应。循环上限是 8 次读取,覆盖大多数卡的响应延迟。

uint8_t SD_InitCard(void) { uint8_t r; SD_PowerUp(); r = SD_SendCommand(0, 0x00000000, 0x95); // CMD0 if (r != 0x01) return 1; r = SD_SendCommand(8, 0x000001AA, 0x87); // CMD8 if (r == 0x01) { for (int i = 0; i < 4; i++) SD_SPI_Byte(0xFF); // 丢弃 R7 的 4 字节 do { SD_SendCommand(55, 0x00000000, 0x01); // CMD55 r = SD_SendCommand(41, 0x40000000, 0x01); // ACMD41,HCS=1 } while (r == 0x01); // 初始化未完成,继续轮询 if (r != 0x00) return 2; r = SD_SendCommand(58, 0x00000000, 0x01); // CMD58 读 OCR if (r != 0x00) return 3; uint32_t ocr = 0; for (int i = 0; i < 4; i++) ocr = (ocr << 8) | SD_SPI_Byte(0xFF); card_capacity = (ocr & 0x40000000) ? 1 : 0; // CCS 位 } else { // 老卡不支持 CMD8,走 SD v1.x 流程 do { SD_SendCommand(55, 0x00000000, 0x01); r = SD_SendCommand(41, 0x00000000, 0x01); // HCS 必须为 0 } while (r == 0x01); if (r != 0x00) return 4; card_capacity = 0; // v1.x 按字节寻址 } return 0; }

这段代码里隐藏了一个常见坑:CMD8 返回 0x01 说明是 SD v2.0 及以上卡,ACMD41 参数必须带 HCS=1,也就是 0x40000000;如果 CMD8 返回 0x00 或者没有响应,说明是 SD v1.x,ACMD41 的 HCS 必须为 0,否则卡永远不握手成功。CMD58 读回来的 OCR 如果 bit30 为 1,后面访问扇区时传入的就是块编号而不是字节地址。

4. 读卡器应用:扇区读写与 FatFS 挂载

4.1 用 CMD17 读一个扇区的完整流程

初始化完成后卡进入数据传输状态。读单块用 CMD17,参数是 32 位地址;SDHC/SDXC 卡直接填扇区号,SDSC 卡要把字节地址除以 512。发送 CMD17 后,R1 返回 0x00,之后卡会输出 0xFE 数据起始令牌,接着是 512 字节数据,最后 2 字节 CRC 在 SPI 模式下可以不校验。

uint8_t SD_ReadSector(uint32_t lba, uint8_t *buf) { uint8_t r, token; r = SD_SendCommand(17, lba, 0x01); if (r != 0x00) return 1; for (int i = 0; i < 500; i++) { token = SD_SPI_Byte(0xFF); if (token == 0xFE) break; } if (token != 0xFE) return 2; for (int i = 0; i < 512; i++) buf[i] = SD_SPI_Byte(0xFF); SD_SPI_Byte(0xFF); // CRC 高字节 SD_SPI_Byte(0xFF); // CRC 低字节 return 0; }

关键在等待令牌的循环。卡收到 CMD17 后,内部闪存读取需要时间,期间 MISO 输出 0xFF,直到数据准备好才输出 0xFE。如果 500 次轮询都没等到 0xFE,先降时钟再重试,多半不是卡坏了,而是 SCK 太快。

4.2 FatFS 的磁盘 IO 层:disk_initialize、disk_read、disk_write 怎么填

只操作裸扇区还不够,读卡器最终要暴露文件系统。FatFS 是这套方案里最常见的文件系统实现,网上搜 fatfs 文件系统 sd 卡 stm32 能找到大量例程。FatFS 需要的底层接口有 disk_initialize、disk_status、disk_read、disk_write 和 disk_ioctl,其中关键的是读写两个。

DRESULT disk_read(BYTE pdrv, BYTE *buf, LBA_t sector, UINT count) { while (count--) { if (SD_ReadSector(sector++, buf) != 0) return RES_ERROR; buf += 512; } return RES_OK; } DRESULT disk_write(BYTE pdrv, const BYTE *buf, LBA_t sector, UINT count) { while (count--) { if (SD_WriteSector(sector++, (uint8_t*)buf) != 0) return RES_ERROR; buf += 512; } return RES_OK; }

写扇区用 CMD24,和读的差异在令牌顺序:发送 CMD24 后先等 R1=0x00,再连续发 0xFE、512 字节、2 个 0xFF,最后读卡的状态响应字节。这个状态字节有几种情况,写的时候要按返回值判断:

返回值含义处理建议
0x00写数据被接受正常,继续下一步
0x05CRC 错误检查 SCK 时钟频率,降频
0x0D数据被拒绝检查块长度是否设为 512,以及媒体是否有写保护

ffconf.h 里要把 FF_USE_MKFS 打开,才能用 f_mkfs 格式化空卡;64GB 的卡出厂可能是 exFAT 格式,F103 要识别它就打开 FF_FS_EXFAT 支持。注意 FatFS 默认按 512 字节一扇区访问,如果你的卡设置过非标准扇区大小,必须先发 CMD16 改回 512。

4.3 用 UART 做一个极简扇区读卡器

调试或者配合上位机使用时,不用把 USB 协议栈搬进来。常见做法是走串口命令,把 F103 变成一台能响应扇区读写的读卡器。协议设计成 8 字节请求头,上位机发完请求后等数据回包,适合在串口助手里直接调试。

typedef struct { uint8_t head0; // 0xA5 uint8_t head1; // 0x5A uint8_t cmd; // 0x10 读扇区,0x20 写扇区 uint8_t lba[4]; // 小端 LBA uint8_t chk; // 校验和 } SDCARD_REQ; void UART_RxHandler(uint8_t *p) { uint32_t lba; if (p[0] != 0xA5 || p[1] != 0x5A) return; lba = p[3] | (p[4] << 8) | (p[5] << 16) | ((uint32_t)p[6] << 24); if (p[2] == 0x10) { uint8_t buf[512]; SD_ReadSector(lba, buf); UART_Send(buf, 512); uint32_t crc = CRC32_Calc(buf, 512); UART_Send((uint8_t*)&crc, 4); } }

两条关键约束。第一,115200 波特率下传 512 字节要 45 ms 左右,而 SPI 读一扇区只要几毫秒,速度差接近一个数量级,所以 UART 发送要做成中断或 DMA,不能阻塞在 SPI 读卡之后。第二,中断优先级要分清楚,SPI 中断优先级调高、UART 接收优先级调低,否则上位机连续发包时会出现丢字节。

5. 验证与排错:时钟分频、写保护与长线干扰

5.1 时钟分频从 2.25 MHz 切到 18 MHz 的正确时刻

初始化阶段用预分频 32 是为了兼容老卡和长走线。ACMD41 返回 0x00 之后,卡已经完成内部上电流程,这时才可以把预分频从 32 改到 4。改预分频先关 SPI、再改参数、再开 SPI:

__HAL_SPI_DISABLE(&hspi1); hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_4; HAL_SPI_Init(&hspi1); __HAL_SPI_ENABLE(&hspi1);

改完如果读扇区偶尔超时,把分频退回到 8,9 MHz 是一个取舍后的稳妥值。

5.2 卡没锁却报写保护怎么办

观察点落在 SD 卡状态区分上。很多卡座有机械写保护开关,但 F103 走 SPI 读卡时根本读不到那个开关,写保护状态是从卡内部状态寄存器返回的。写法是在 CMD24 前发 CMD13 读状态,返回值第二位为 1 才说明卡真的置了写保护位。另一个常见现象是写入返回 0x05 或 0x0D,这通常不是保护开关的问题,而是块长度没设置对,先发 CMD16 设为 512。

提示:如果卡之前在某台相机或电脑上被格式化成 exFAT,F103 端没有打开 FF_FS_EXFAT 时,f_mount 可能返回 FR_NO_FILESYSTEM,磁盘格式化和文件系统开关要放一起排查。

5.3 用逻辑分析仪抓 SPI 时序定位初始化失败

第一步抓 CS、SCK、MOSI 三个通道。观察 CS 低电平是否覆盖了 CMD0 的完整 6 字节,如果 CS 提前拉高,命令没发完;如果 CS 始终为低但 CMD0 返回 0xFF,检查 MOSI 是否真的接到卡座的 DI,以及卡座供电。第二步看在用 2.25 MHz 时的 SCK 周期是否符合预期,波形上升沿如果圆得像正弦波,说明信号线上过冲被压得太厉害,去掉串联电阻或用杜邦线单独走地。

提示:从 SDIO 例程移植到 SPI 读卡时,别把 disk_read 写成中断或 DMA 异步版本。SPI 读卡是严格的一问一答,保持同步阻塞最可靠,FatFS 的轮询机制本身就容忍扇区级阻塞。IO 引脚配置用推挽输出加不上拉也可以,但 MISO 那根线要配置成浮空输入,卡输出高阻时靠内部弱上拉兜底。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询