作者是初学者,水平有限,本文仅作为个人学习笔记使用,不能保证内容的正确性。部分资料来源于网络,参考了SD卡协议2.0手册等资料,如果有歧义,可以与作者联系。

目录

SD卡 SPI模式操作(1)初始化SD卡
SD卡 SPI模式操作(2)读操作
SD卡 SPI模式操作(3)写操作

省流直接看图,文章下面有比较详细的初始化流程

Pasted image 20230128214632.png

常见问题

1、 为什么发送初始化命令之后,SD卡一直返回0xFF?

作者亲自遇到过这个问题,我的工具是某宝上十几块钱的逻辑分析仪,芯片是CH32V203C8T6,开发板长这样子的
Pasted image 20230203214636.png

首先检查线路的连接,MCU的MISO连接SD卡的DO,MOSI连接SD卡的DI,如果MCU有多个SPI外设,一定要看清楚SD卡连接的是哪个SPI。
最有效的方法,就是用逻辑分析仪连接到SD卡所连接的线路上,看有没有信号输出。
确定线路连接正常之后,再检查上电需要的时钟信号是否正确,必须在100k~400kHz的频率之间,如果你的MCU主频设置过高,如144MHz,就算SPI用256级的分频,依然是非常大的。144MHz分频256次:
\frac{MCU主频}{SPI分频数}=\frac{144MHz}{256}=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模式的传输协议稍微简单一些,代价是传输速度也会变慢。

引脚说明

Pasted image 20230118202918.png
Pasted image 20230118202939.png
(图片来源于网络)

引脚设置

在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)。

报文格式

从主机到卡的命令帧的长度是下图所示的固定长度的报文。
命令报文 1.png
在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"为发生错误。

Pasted image 20230128211046.png

位号 描述
0 处于空闲状态:SD卡处于空闲状态,且正在运行初始化程序
1 擦除重置:在执行擦除前,一个擦除指令序列被清除,因为收到了一个超出擦除序列的指令。
2 非法指令
3 CRC错误:上一条命令的CRC检查失败
4 擦除序列错误:在擦除指令序列中发现错误
5 地址错误:命令中使用了与块长度不匹配的未对齐地址。
6 参数错误:命令的参数(例如地址、块长度)超出了此卡的允许范围。
7 固定为0

R1b令牌

该响应令牌与R1格式相同,但可选地添加了忙信号。忙信号令牌可以是任意数量的字节。零值表示卡正忙。非零值表示卡已准备好下一个命令。

R2令牌

通常情况下只会用到R2令牌的最低位,也就是卡被写保护。该令牌是CMD13(SEND_STATUS)的响应令牌。高8位为R1令牌。
Pasted image 20230128212104.png

R3令牌

该令牌是CMD58(READ_OCR)的响应令牌,共5个字节,用于获取OCR寄存器的数据。高8位为R1令牌,低4字节为OCR寄存器的数据。
Pasted image 20230128212417.png

R7令牌

该令牌是CMD8(SEND_IF_COND)的响应令牌,共5字节,主要用于获取SD卡工作电压信息,高8位为R1令牌。
Pasted image 20230128213548.png

位号 描述
[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卡的上电。
Pasted image 20230128214632.png

上电复位

Pasted image 20230129042952.png

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。
Pasted image 20230129042629.png

如果在此期间,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卡的读操作。

参考文章

如果需要评论

因为是个人博客,因备案要求不能使用交互服务,如果需要评论,可以前往本文的CSDN页:
CSDN-SD卡 SPI模式操作(1)初始化SD卡