# 1.初始化SD卡

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

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

![Pasted image 20230128214632.png](https://mirror.simgor.cn/typecho_img/2023/01/1052810562.png)

# 常见问题

## Q1、 为什么发送初始化命令之后，SD卡一直返回0xFF？

作者亲自遇到过这个问题，我的工具是某宝上十几块钱的逻辑分析仪，芯片是CH32V203C8T6，开发板长这样子的
![Pasted image 20230203214636.png](https://mirror.simgor.cn/typecho_img/2023/02/4080687956.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卡之后，再换回开发板的卡槽。

## Q2、关于TF卡

有些TF卡不支持SPI模式操作，这个需要注意，可以多准备几张卡。目前我的8G金士顿TF卡，这个没问题，但是我的32G闪迪没法进入SPI模式。

## Q3、 MCU应该在什么时候发送命令？

SD卡的DO线，空闲的情况下应该是高电平，当SD卡忙的时候，DO线会被SD卡拉低，所以应该在DO线为高电平的时候，发送命令。

# 简介

SPI模式是MCU在没有SDIO的情况下控制MMC和SD卡的备用操作模式。比起SDIO模式，SPI模式的传输协议稍微简单一些，代价是传输速度也会变慢。

# 引脚说明

![Pasted image 20230118202918.png](https://mirror.simgor.cn/typecho_img/2023/01/1515819948.png)
![Pasted image 20230118202939.png](https://mirror.simgor.cn/typecho_img/2023/01/3533747864.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](https://mirror.simgor.cn/typecho_img/2023/01/358165483.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令牌。

### R１令牌

除SEND_STATUS命令外，SD卡在每次收到命令后都会发送此响应令牌。长度为１字节，MSB始终设置为"0"。\[0:6\]是错误指示，"1"为发生错误。

![Pasted image 20230128211046.png](https://mirror.simgor.cn/typecho_img/2023/01/1522903460.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](https://mirror.simgor.cn/typecho_img/2023/01/498398950.png)

### R3令牌

该令牌是CMD58（READ_OCR）的响应令牌，共5个字节，用于获取OCR寄存器的数据。高8位为R1令牌，低4字节为OCR寄存器的数据。
![Pasted image 20230128212417.png](https://mirror.simgor.cn/typecho_img/2023/01/4079843275.png)

### R7令牌

该令牌是CMD8（SEND_IF_COND）的响应令牌，共5字节，主要用于获取SD卡工作电压信息，高8位为R1令牌。
![Pasted image 20230128213548.png](https://mirror.simgor.cn/typecho_img/2023/01/2649515220.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](https://mirror.simgor.cn/typecho_img/2023/01/1052810562.png)

## 上电复位

![Pasted image 20230129042952.png](https://mirror.simgor.cn/typecho_img/2023/01/1323167921.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](https://mirror.simgor.cn/typecho_img/2023/01/4151156777.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卡的读操作。

# 参考文章

- [【翻译】如何使用MMC/SD卡](https://www.0xaa55.com/thread-25692-1-1.html)
- [SD卡寄存器及对应的CMD命令描述](https://blog.csdn.net/weixin_45640197/article/details/126618265)
- [SD卡小结](https://blog.csdn.net/weixin_39489470/article/details/103527313)

# 如果需要评论

因为是个人博客，因备案要求不能使用交互服务，如果需要评论，可以前往本文的CSDN页：
[CSDN-SD卡 SPI模式操作（1）初始化SD卡](https://blog.csdn.net/Simgo/article/details/128825962)
