作者是初学者,水平有限,本文仅作为个人学习笔记使用,不能保证内容的正确性。部分资料来源于网络,参考了SD卡协议2.0手册等资料,如果有歧义,可以与作者联系。
目录
SD卡 SPI模式操作(1)初始化SD卡
SD卡 SPI模式操作(2)读操作
SD卡 SPI模式操作(3)写操作
省流直接看图,文章下面有比较详细的初始化流程
常见问题
1、 为什么发送初始化命令之后,SD卡一直返回0xFF?
作者亲自遇到过这个问题,我的工具是某宝上十几块钱的逻辑分析仪,芯片是CH32V203C8T6,开发板长这样子的
首先检查线路的连接,MCU的MISO连接SD卡的DO,MOSI连接SD卡的DI,如果MCU有多个SPI外设,一定要看清楚SD卡连接的是哪个SPI。
最有效的方法,就是用逻辑分析仪连接到SD卡所连接的线路上,看有没有信号输出。
确定线路连接正常之后,再检查上电需要的时钟信号是否正确,必须在100k~400kHz的频率之间,如果你的MCU主频设置过高,如144MHz,就算SPI用256级的分频,依然是非常大的。144MHz分频256次:
M
C
U
主频
S
P
I
分频数
=
144
M
H
z
256
=
562.5
K
b
i
t
/
s
\frac{MCU主频}{SPI分频数}=\frac{144MHz}{256}=562.5Kbit/s
SPI分频数MCU主频=256144MHz=562.5Kbit/s
所以在合适的主频下,选择合适的SPI分频数。初始化完成之后,再调高SPI的主频,用于数据通信。
最后一个办法,可以检查初始化步骤是否在上电时操作。
有些开发板上的SD卡槽的电源,是直接连接开发板电源,所以要求SD卡初始化的操作必须在开发板刚上电时完成。但是调试时,一般只会让MCU复位而不会让开发板断电,从而SD卡无法进入初始化模式。
在这种情况下,我建议每次下载完程序之后,让开发板重新上电,或者先购买一个SD卡模块,让GPIO控制SD卡的上电,直到写完初始化部分的功能,代码能正常初始化SD卡之后,再换回开发板的卡槽。
2、关于TF卡
有些TF卡不支持SPI模式操作,这个需要注意,可以多准备几张卡。目前我的8G金士顿TF卡,这个没问题,但是我的32G闪迪没法进入SPI模式。
3、 MCU应该在什么时候发送命令?
SD卡的DO线,空闲的情况下应该是高电平,当SD卡忙的时候,DO线会被SD卡拉低,所以应该在DO线为高电平的时候,发送命令。
简介
SPI模式是MCU在没有SDIO的情况下控制MMC和SD卡的备用操作模式。比起SDIO模式,SPI模式的传输协议稍微简单一些,代价是传输速度也会变慢。
引脚说明
(图片来源于网络)
引脚设置
在SPI模式下使用SD卡,按照SD卡的要求正确接线之后,需要将MCU的引脚进行如下设置:
SD引脚 | MCU引脚 | MCU引脚模式 |
---|---|---|
CS | NSS | 复用推挽输出 |
SCLK | SCK | 复用推挽输出 |
DI | MOSI | 复用推挽输出 |
DO | MISO | 浮空输入 |
VDD | (任意引脚) | 复用推挽输出 |
VSS | GND | – |
其中,SD卡的VDD可以使用一个引脚去控制SD卡的电源,从而使用MCU控制SD卡上电。
注意,有些SD卡的DO引脚需要一个上拉电阻,否则会在初始化的过程中失败。
SPI模式设置
SD卡使用的是SPI模式0(CPHA=0,CPOL=0)和SPI模式3(CPHA=1,CPOL=1),一般情况下选择SPI模式3。命令发送模式为高位优先(MSB-first)。
报文格式
从主机到卡的命令帧的长度是下图所示的固定长度的报文。
在SPI通信模式下,数据通信由主机控制,形式为:主机拉低NSS信号,发送命令令牌,SD卡发送响应令牌,如果需要返回数据,那么数据将跟在响应令牌后面。
发送完命令之后,应该继续发送0xFF,并且检查是否收到可用的应答。如果在设定时间内没有收到回应,那么视作超时处理。在收到应答之前,应该将NSS保持低电平。
命令令牌格式
SPI模式的命令令牌固定为6个字节,命令的传输从最高位开始。
位号 | 47 | 46 | [45:40] | [39:8] | [7:1] | 0 |
---|---|---|---|---|---|---|
位宽 | 1 | 1 | 6 | 32 | 7 | 1 |
值 | “0” | “1” | x | x | x | “1” |
描述 | 开始位 | 数据传输位 | 命令号(CMDx) | 参数 | CRC校验 | 结束位 |
其中,在SPI模式下,CRC仅在初始化卡时有效,如CMD0、CMD8等。不使用CRC时,可将CRC所有位置1。
当命令为CMD0时,低8位应该为0x95,
当命令为CMD8时,低8位应该为0x87。
命令令牌描述
命令令牌分为CMD和ACMD,ACMD是应用程序命令,在发送ACMD之前,应该先发送CMD55(APP_CMD)。填充位和保留位均为"0"。
命令号 | 参数 | 响应 | 名称 | 描述 |
---|---|---|---|---|
CMD0 | [31:0]:填充位 | R1 | GO_IDLE_STATE | 将卡复位到IDLE状态 |
CMD8 | [31:12]:保留位,[11:8]:支持的电压(VHS),[7:0]:需要回显的参数(echo-back) | R7 | SEND_IF_COND | 发送包含主机电源电压信息,询问被访问的卡是否能在供电电压范围内工作 |
CMD12 | [31:0]:填充位 | R1b | STOP_TRANSMISSION | 强制停止当前SD卡的数据传输,用于多块数据操作,结束传输 |
CMD16 | [31:0]:块长度 | R1 | SET_BLOCKLEN | 设置SD卡的块大小(字节),后续所有块操作命令(读和写)都是以此设置的大小为准,对于高容量卡,块大小固定为512字节 |
CMD17 | [31:0]:数据地址,单位:字节(SDSC),单位:512字节(SDHC) | R1 | READ_SINGLE_BLOCK | 读取一个块的数据,参数为块的首地址。块的长度由CMD16设置,对于高容量卡,块大小固定为512字节 |
CMD18 | [31:0]:数据地址,单位:字节(SDSC),单位:512字节(SDHC) | R1 | READ_MULTIPLE_BLOCK | 连续读取多块数据,直到主机发送CMD12命令,参数为块的首地址。块的长度由CMD16设置,对于高容量卡,块大小固定为512字节 |
CMD24 | [31:0]:数据地址,单位:字节(SDSC),单位:512字节(SDHC) | R1 | WRITE_BLOCK | 写入一个块的数据,参数为块的首地址。块的长度由CMD16设置,对于高容量卡,块大小固定为512字节 |
CMD25 | [31:0]:数据地址,单位:字节(SDSC),单位:512字节(SDHC) | R1 | WRITE_MULTIPLE_BLOCK | 连续写入多块数据,直到主机发送CMD12命令,参数为块的首地址。块的长度由CMD16设置,对于高容量卡,块大小固定为512字节 |
CMD55 | [31:0]:填充位 | R1 | APP_CMD | 通知SD卡,接下来发送的命令是特定于应用程序的命令,而不是标准命令 |
CMD58 | [31:0]:填充位 | R3 | READ_OCR | 读取OCR寄存器 |
ACMD23 | [31:23]:保留位,[22:0]:数据块数 | R1 | SET_WR_BLK_ERASE_COUNT | 需要预擦除的数据块个数,以提升SD卡写入的性能 |
ACMD41 | [31]:保留位,[30]:HCS, [29:0]:保留位 | R1 | SD_SEND_OP_COND | 发送主机容量支持信息,并将卡初始化,HCS一般置“1” |
响应令牌格式
有几种类型的响应令牌,所有数据都是先传输高位。所有令牌的高8位都是R1令牌。
R1令牌
除SEND_STATUS命令外,SD卡在每次收到命令后都会发送此响应令牌。长度为1字节,MSB始终设置为"0"。[0:6]是错误指示,"1"为发生错误。
位号 | 描述 |
---|---|
0 | 处于空闲状态:SD卡处于空闲状态,且正在运行初始化程序 |
1 | 擦除重置:在执行擦除前,一个擦除指令序列被清除,因为收到了一个超出擦除序列的指令。 |
2 | 非法指令 |
3 | CRC错误:上一条命令的CRC检查失败 |
4 | 擦除序列错误:在擦除指令序列中发现错误 |
5 | 地址错误:命令中使用了与块长度不匹配的未对齐地址。 |
6 | 参数错误:命令的参数(例如地址、块长度)超出了此卡的允许范围。 |
7 | 固定为0 |
R1b令牌
该响应令牌与R1格式相同,但可选地添加了忙信号。忙信号令牌可以是任意数量的字节。零值表示卡正忙。非零值表示卡已准备好下一个命令。
R2令牌
通常情况下只会用到R2令牌的最低位,也就是卡被写保护。该令牌是CMD13(SEND_STATUS)的响应令牌。高8位为R1令牌。
R3令牌
该令牌是CMD58(READ_OCR)的响应令牌,共5个字节,用于获取OCR寄存器的数据。高8位为R1令牌,低4字节为OCR寄存器的数据。
R7令牌
该令牌是CMD8(SEND_IF_COND)的响应令牌,共5字节,主要用于获取SD卡工作电压信息,高8位为R1令牌。
位号 | 描述 |
---|---|
[0:7] | 发送CMD8时会发送一个1字节的参数echo-back,这里将返回这个参数,一般用于检查通讯是否正常。一般在CMD8使用10101010b作为参数 |
[8:11] | SD卡能接受的电压值(见电压值表,通常为0001b) |
[12:27] | 保留位,固定为“00000h” |
[28:31] | 命令号,固定为“001000b” |
电压值如下:
值 | 描述 |
---|---|
0000b | 未定义 |
0001b | 2.7-3.6V |
0010b | 保留为更加低的电压值 |
0100b | 保留 |
1000b | 保留 |
初始化SD卡
由于SD卡进入SPI模式后,只能通过断电退出,为了方便调试,建议使用GPIO控制SD卡的上电。
上电复位
SD卡上电之后,需要往sclk线发送至少74个时钟信号,注意频率在100k~400kHz之间,建议直接使用低速SPI发送10个0xFF,在此过程中,即使是为了进入SPI模式,CS线也需要保持高电平。
当SD卡上电完毕(可以延迟1ms)之后,需要发送CMD0进行复位操作。为了使SD卡进入SPI模式,需要将CS线拉低,然后发送CMD0,注意CMD0的格式,命令长度固定为6个字节。
对于CMD0命令,应该发送0x40、0x00、0x00、0x00、0x00、0x95,发送完毕之后还需要发送若干个0xFF等待SD卡响应。
当SD卡响应时,应该返回一个R1令牌,且内容为0x01。
接收完毕之后,应该拉高CS,然后再发送一个0xFF。
如果在此期间,SD卡一直没有返回R1令牌,MISO一直为0xFF,那么有以下几个点需要检查:
1、 检查当前复位操作是否为上电操作,该操作应该是SD卡刚上电时的操作。如果当前是个debug程序,应该将SD卡的VDD引脚接到MCU,由MCU控制SD卡的上电。
2、 检查SD卡的连接是否正确,MOSI和MISO是不是接反了?正确的接法是MOSI接DI,MISO接DO。
3、 检查上电时,SCLK的时钟是否发送正确,必须大于74个时钟,而且频率必须在100k~400kHz之间。
检查卡版本是否为Ver 1.X
通过发送一个CMD8命令,如果SD卡响应该命令无效,说明该卡的版本为Ver1.X。如果有响应,说明卡版本为Ver2.X或者更高的版本。
CMD8的参数一般为0x000001AA,SD卡收到响应之后,应该首先检查高8位,也就是R1令牌部分。
当R1的第二位,也就是 R1 & 0x0004为真时,该命令无效,说明卡版本为Ver1.X。
当CMD8有效时,应该抛弃剩余的4字节数据。
检查支持电压范围(可选)
主要通过CMD58命令获取OCR寄存器的信息,以获得SD卡的支持电压范围。
开始初始化SD卡
通过发送ACMD41命令去初始化SD卡。注意,在发送ACMD41之前,应该先发送CMD55命令,而且CMD55命令会响应一个R1令牌,此处的R1令牌可以直接抛弃。
对于Ver1.X版本的卡,ACMD41的参数应该为0x00000000;
对于Ver2.X以及更高版本的卡,ACMD41的参数一般使用0x40000000。
这时需要不断发送0xFF同时检查接收R1令牌(其实这里的等待接收步骤和CMD0一样),直到R1为0,表示SD卡初始化成功。这里的循环建议添加超时计数。
获取卡的容量状态
通过CMD58命令获取OCR寄存器的信息,其中CCS位位于OCR寄存器的第30位。
1为高容量卡(SDHC)
0为标准容量卡(SDSC)
设置块大小
SD卡的所有读写操作,都是以块为单位去读写,因此需要预先设置一个块的大小。
通过CMD16命令设置SD卡的块大小(字节),后续所有块操作命令(读和写)都是以此设置的大小为准,对于高容量卡,块大小固定为512字节。
对于低容量的卡,为了统一,一般建议将块大小设置为512字节。
结尾
SD卡初始化完成之后,可以将SPI总线切换成高速总线,将在下一章继续介绍SD卡的读操作。