ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

STM32F103 SPI驱动SD卡:从初始化到FatFS文件系统

STM32F103 SPI驱动SD卡:从初始化到FatFS文件系统 简介这是一份使用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 3CUBEMX 里默认的 mode 0 就能用。参数设置大致是Mode 选 Full-Duplex MasterData Size 选 8 BitsCPOL 选 LowCPHA 选 1 EdgeNSS 选 SoftwareBaudRatePrescaler 初始化阶段选 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 MHzSCK 波形会严重变形。切换时机放在 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告诉卡下一命令是 ACMDACMD410x690x400000000x00初始化完成HCS1CMD580x7A0x000000000x00 后跟 OCR读 OCR 判断 SDHC/SDXCR1 是一个字节最高位为 0 表示命令被接受0x01 表示卡处于空闲状态0x00 表示初始化完成。CMD8 返回的是 R7前一个字节是 R1后面跟 4 字节低 12 位应该回显 0x1AA。CMD58 返回 R3后面 4 字节里 bit30 是 CCS 位CCS1 代表 SDHC/SDXC 的块寻址CCS0 代表 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); // ACMD41HCS1 } 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 参数必须带 HCS1也就是 0x40000000如果 CMD8 返回 0x00 或者没有响应说明是 SD v1.xACMD41 的 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 后先等 R10x00再连续发 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);改完如果读扇区偶尔超时把分频退回到 89 MHz 是一个取舍后的稳妥值。5.2 卡没锁却报写保护怎么办观察点落在 SD 卡状态区分上。很多卡座有机械写保护开关但 F103 走 SPI 读卡时根本读不到那个开关写保护状态是从卡内部状态寄存器返回的。写法是在 CMD24 前发 CMD13 读状态返回值第二位为 1 才说明卡真的置了写保护位。另一个常见现象是写入返回 0x05 或 0x0D这通常不是保护开关的问题而是块长度没设置对先发 CMD16 设为 512。提示如果卡之前在某台相机或电脑上被格式化成 exFATF103 端没有打开 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 那根线要配置成浮空输入卡输出高阻时靠内部弱上拉兜底。本文还有配套的精品资源点击获取
返回列表