2、SD卡基础知识:SD卡与SPI通信协议简介、SD卡模块接线(CS/SCK/MOSI/MISO)、SD卡初始化与卡类型检测、常见问题排查(电压不匹配、卡格式化)
好,咱们正式开始折腾SD卡。说实话,在嵌入式系统里做数据存储,SD卡绝对是最亲民的方案之一。便宜、容量大、随处都能买到。但很多新手一上来就被各种协议和接线搞懵了。别急,我带你一步步捋清楚。
2.1 SD卡与SPI通信协议简介
SD卡内部其实是个复杂的闪存芯片,但它对外提供了两种通信模式:SDIO模式和SPI模式。咱们做单片机开发,绝大多数情况下用的是SPI模式。为什么?因为大部分单片机都自带SPI硬件外设,接线少,驱动也简单。
SPI,全称是Serial Peripheral Interface,串行外设接口。它是个主从通信协议,咱们的单片机是主机,SD卡是从机。通信需要四根线:
- SCK(时钟线)—— 由主机产生,决定通信速率
- MOSI(主机输出,从机输入)—— 主机发数据给SD卡
- MISO(主机输入,从机输出)—— SD卡发数据给主机
- CS(片选线)—— 选中SD卡,低电平有效
我个人习惯把SPI通信想象成两个人打电话:SCK是通话的节奏,MOSI是你说的话,MISO是对方回的话,CS就是“喂,你听着啊”这个信号。嗯,这样就好理解了。
关键点:SPI通信是同步的,所有数据交换都在时钟边沿完成。SD卡在SPI模式下,命令和响应都是字节流,每个命令固定6个字节。
2.2 SD卡模块接线(CS/SCK/MOSI/MISO)
接线这块,我踩过不少坑。先给你一个标准的接线表,以STM32F103为例:
| SD卡模块引脚 | STM32引脚 | 说明 |
|---|---|---|
| CS | PA4 | 片选,任意GPIO即可 |
| SCK | PA5 | SPI时钟,必须用SPI硬件引脚 |
| MOSI | PA7 | 主机输出,接SD卡DI |
| MISO | PA6 | 主机输入,接SD卡DO |
| VCC | 3.3V | 注意!不是5V! |
| GND | GND | 共地 |
这里我要特别强调一下:SD卡必须工作在3.3V。我曾经有一次手快,直接把5V接到了SD卡模块上,结果冒烟了……嗯,那模块直接报废。所以接线前一定用万用表量一下电压。
另外,有些SD卡模块自带电平转换芯片,可以兼容5V单片机。但如果你用的是3.3V的单片机(比如STM32),直接接就行,不用额外转换。
警告:MISO线是SD卡的输出,如果单片机引脚是5V容忍的,最好加个电阻分压或者电平转换。否则长期使用可能损坏SD卡或单片机。
2.3 SD卡初始化与卡类型检测
初始化SD卡,说白了就是让卡进入SPI模式,然后识别它是哪种卡。标准SD卡、SDHC卡、SDXC卡,它们的初始化流程略有不同。
我一般把初始化分成三步:
- 上电延时—— 给SD卡供电后,至少等待1ms,让它内部稳定
- 发送80个时钟脉冲—— 让SD卡进入SPI模式,这步不能省
- 发送CMD0 + CMD8 + CMD58—— 获取卡类型和电压范围
代码示例(伪代码,实际用C写):
// 1. 上电延时
delay_ms(10);
// 2. 发送80个时钟脉冲(CS拉高)
for(i=0; i<10; i++) {
SPI_Write(0xFF);
}
// 3. 发送CMD0,让卡进入SPI模式
CS_LOW();
SPI_Write(0x40); // CMD0命令索引
SPI_Write(0x00);
SPI_Write(0x00);
SPI_Write(0x00);
SPI_Write(0x00);
SPI_Write(0x95); // CRC校验,CMD0必须带有效CRC
CS_HIGH();
// 4. 等待响应0x01(进入空闲状态)
// 如果一直收不到,说明卡没响应
// 5. 发送CMD8,检测卡是否支持SDHC
// CMD8参数:0x000001AA,CRC=0x87
// 如果响应0x01,说明是SDSC卡
// 如果响应0x05,说明是SDHC/SDXC卡
你想想看,为什么CMD0后面要跟CMD8?说白了,CMD8是用来探测卡是否支持1.8V或2.7V~3.6V电压范围的。如果卡不支持CMD8,那它就是老式的SDSC卡。如果支持,再看CMD58返回的OCR寄存器,就能区分SDHC和SDXC了。
小技巧:初始化时SPI时钟频率要慢,一般设到400kHz以下。等初始化完成后再切换到高速模式。我刚开始不知道这个,直接用10MHz去初始化,结果卡死活不响应。
2.4 常见问题排查(电压不匹配、卡格式化)
做SD卡驱动,十有八九会遇到问题。我把自己踩过的坑总结一下:
电压不匹配
这是最常见的问题。SD卡的工作电压是2.7V~3.6V,但很多开发板上的SPI引脚是5V电平。我曾经用一块5V的Arduino直接接SD卡模块,结果读出来的数据全是0xFF。后来用示波器一看,MISO线上的高电平只有2.8V,但单片机认为3V以下就是低电平……嗯,通信全乱了。
解决办法:用3.3V供电,或者加电平转换芯片(比如74LVC4245)。
卡格式化问题
很多SD卡出厂是FAT32格式,但有些卡是exFAT或者RAW格式。咱们做嵌入式,一般用FAT32或者裸扇区读写。如果卡没格式化,初始化时读MBR会失败。
我曾经遇到过一张64GB的卡,怎么初始化都不成功。折腾了半天,才发现它是exFAT格式,而我的FAT32驱动根本不认识。后来用电脑重新格式化成FAT32,问题就解决了。
其他常见问题
- 卡没插好 —— 检查卡座弹片是否接触良好
- 时钟太快 —— 初始化时降到100kHz试试
- CRC校验错误 —— 有些卡对CRC要求严格,CMD0必须带有效CRC
- 电源纹波太大 —— SD卡对电源质量敏感,加个10μF电容
避坑指南:如果你用的是淘宝上几块钱的SD卡模块,注意看上面的电平转换芯片。有些模块用的是3.3V LDO,但输入电压不能超过5.5V。我建议统一用3.3V供电,省心。
好了,SD卡的基础知识就这些。下一章咱们开始写真正的驱动代码,让卡读写起来。记住,硬件调试时别急,先确认电压、时钟、片选这三样都对,再谈软件问题。