✅作者简介:嵌入式入坑者,与大家一起加油,希望文章能够帮助各位!!!!
📃个人主页:@rivencode的个人主页
🔥系列专栏:玩转STM32
SDIO (Secure Digital Input and Output)全称安全数字输入/输出接口,在AHB外设总线和多媒体卡(MMC)、SD存储卡、SDIO卡和CE-ATA设备间提供了操作接口。
MMC(MultiMedia Card)卡由西门子公司和首推CF的SanDisk于1997年推出多媒体卡(MMC)是一种小型(24x32或18x1.4mm)可擦除固态存储卡,其全称为Multi-Media Card,特别应用于移动电话和数字影像及其他移动终端中。
SD卡是一种基于半导体快闪记忆器的新一代记忆设备。SD卡由日本松下、东芝及美国SanDisk公司于1999年8月共同开发研制。
SD卡数据传送和物理规范由MMC发展而来,大小和MMC差不多,但是容纳更大容量的存贮单元。SD卡与MMC卡保持着向上兼容,也就是说,MMC可以被新的SD设备存取,兼容性则取决于应用软件,但SD卡却不可以被MMC设备存取。**
应用:SD卡的结构能保证数字文件传送的安全性,也很容易重新格式化,所以有着广泛的应用领域,音乐、电影、新闻等多媒体文件都可以方便地保存到SD卡中。因此不少数码相机也开始支持SD卡。
本身不是用于存储的卡,它是指利用 SDIO 传输协议的一种外设。比如 Wi-Fi Card,它主要是提供 Wi-Fi 功能,有些 Wi-Fi 模块是使用串口或者 SPI 接口进行通信的,但 Wi-Fi SDIO Card 是使用 SDIO 接口进行通信的,一般设计 SD I/O 卡是可以插入到 SD 的插槽
CE-ATA 一种基于为了节省接口IO数量,专为轻薄 笔记本硬盘 设计的笔记本硬盘高速通讯接口。 一种使用MMC接口界面,ATA指令集的笔记本电脑 硬盘接口

结论:MMC卡是SD卡的前身,也就是说SD将取代MMC卡的地位,MMC卡也用的越来越少,所以我们后面只讲SD卡.
SDIO的主要功能:
(关于不是SD卡的就不介绍了)
1.与SD存储卡规格版本2.0全兼容(向前兼容)
2.数据和命令输出使能信号,用于控制外部双向驱动器。
SDIO外设挂载在STM32的AHB总线。

这样一来,STM32(主机),就可以通过AHB总线,控制SDIO外设的寄存器进而控制SDIO外设读/写SD卡,SDIO外设向SD卡传输数据,或者向SD卡接收数据可以使用DMA进行传输(这样可以省去很多繁琐的步骤后面再讲)
当前版本的SDIO在同一时间里只能支持一个SD卡(这是ST公司限制的)
SD卡是一种基于半导体快闪记忆器的新一代记忆设备。SD卡由日本松下、东芝及美国SanDisk公司于1999年8月共同开发研制。
SD卡数据传送和物理规范由MMC发展而来,大小和MMC差不多,但是容纳更大容量的存贮单元。SD卡与MMC卡保持着向上兼容,也就是说,MMC可以被新的SD设备存取,兼容性则取决于应用软件,但SD卡却不可以被MMC设备存取。**
应用:SD卡的结构能保证数字文件传送的安全性,也很容易重新格式化,所以有着广泛的应用领域,音乐、电影、新闻等多媒体文件都可以方便地保存到SD卡中。因此不少数码相机也开始支持SD卡。
SD卡采用的是采用了NandFlash存储器,稍后解释NandFlash与NorFlash的区别。



1.卡容量
STM32F10x 系列控制器只支持 SD 卡规范版本 2.0,即只支持标准容量 SD 和高容量SDHC 标准卡,不支持超大容量 (大于32GB)SDXC 标准卡,所以STM32可以支持的最高卡容量是 32GB。
2.速度等级
我们定义了 4 个速度等级,来表示卡的最小速率:(实际上目前最高 Class10)
● Class 0 – 这种卡不定义具体性能,代表了这个规范出来之前的所有卡
● Class 2 – 最小 2MB/s 的性能
● Class 4 – 最小 4MB/s 的性能
● Class 6 – 最小 6MB/s 的性能
● Class 8 – 最小 8MB/s 的性能
● Class 10 – 最小 10MB/s 的性
SD卡的内部构造:

电源检测单元保证 SD 卡工作在合适的电压下,如出现掉电或上状态时,它会使控制单元和存储单元接口复位;
1)SD卡引脚


SD 卡使用 9-pin 接口通信,其中 3 根电源线、1 根时钟线、1 根命令线和 4 根数据线,具体说明如下:
卡控制单元控制 SD 卡的运行状态,它包括有 8 个寄存器,接口驱动器控制 SD 卡引脚的输入输出
SD 卡总共有 8 个寄存器,用于设定或表示 SD 卡信息,这些寄存器只能通过对应的命令访问(STM32主机通过驱动SDIO外设通过CMD命令线向从机SD卡发送命令),SDIO 定义了 64 个命令,每个命令都有特殊意义,可以实现某一特定功能,SD 卡接收到命令后,根据命令要求对 SD 卡内部寄存器进行修改,程序控制中只需要发送组合命令就可以实现 SD 卡的控制以及读写操作。

1.CID 寄存器(重点了解)
卡识别(CID)寄存器是一个 128 位的寄存器。包含了卡的识别信息,用于卡识别解析(具有唯一性)。每个读/写卡都有一个特殊的识别号。CID 寄存器的结构如下:

2.RCA 寄存器(重点了解)
可写的 16 位SD卡相对地址寄存器,在SD卡的初始化期间,由SD卡向外发布的卡地址。这个地址用于卡初始化进程之后,主机同卡之间的交互寻址。(在主机向SD卡读写数据时,就是发送CMD7选择/取消选择 RCA 地址卡,就是靠RCA来确定主机与哪张卡通信)
为啥不用上面的CID呢 ?
答:因为它太长了128位呢,仅此而已
3.CSD 寄存器(2.0)
CSD V2.0 只适用于高容量

4.SCR 寄存器
作为 CSD 寄存器的补充,另一个配置寄存器称为 SD 卡配置寄存器(SCR)。SCR 提供了 SD 卡的特殊功能的信息。SCR 是一个 64bit 的寄存器,这个寄存器应该由 SD 厂家设置。

5.OCR 寄存器(重点了解)
32 位的操作条件寄存器(OCR)存储了卡的 VDD 电压描述。另外,还包括了状态信息位。如果卡的上电程序完成,一个状态位会被设置。寄存器还包括另一个状态位,在设置上电状态位后,用来表明卡的容量状态。
Bit31 – 卡上电状态位(busy),这个状态位在卡的上电流程完成后设置。
Bit30 – 卡容量状态位(CCS),如果是高容量卡,设置为 1,如果是标准卡,设置为 0。
卡容量状态位只有在上电流程完成,且 Bit31 设置为 1 之后才有效。主机可以读取这个状态位来判断卡的种类
上面这些寄存器的只要了解即可,后面用的不多,等后面用到再讲这样就能有个更深刻的认识。
1)FLASH简介
FLSAH 存储器又称闪存,它与 EEPROM 都是掉电后数据不丢失的存储器,但 FLASH存储器容量普遍大于 EEPROM,现在基本取代了它的地位。我们生活中常用的 U 盘、SD卡、SSD 固态硬盘以及我们STM32 芯片内部用于存储程序的设备,都是 FLASH 类型的存储器。在存储控制上,最主要的区别是 FLASH 芯片只能一大片一大片地擦写,而在“I2C章节”中我们了解到 EEPROM 可以单个字节擦写。
2)NOR FLASH 和 NAND FLASH的区别
FLASH 存储器又分为 NOR FLASH 和 NAND FLASH
NOR 与 NAND 的共性是在数据写入前都需要有擦除操作,而擦除操作一般是以“扇区/块”为单位的。
而 NOR 与 NAND 特性的差别,主要是由于其内部“地址/数据线”是否分开导致的。由于 NOR 的地址线和数据线分开,它可以按“字节”读写数据,符合 CPU 的指令译码执行要求,而由于 NAND 的数据和地址线共用,只能按“块”来读写数据,所以不符合CPU指令译码要求。
所以NOR FLASH 一般应用在代码存储的场合,如嵌入式控制器内部的程序存储空间。
而 NAND FLASH 一般应用在大数据量存储的场合,包括 SD 卡、U 盘以及固态硬盘等,都是 NAND FLASH 类型的。
总结一句话:NOR FLASH贵而精,NAND FLASH大而便宜
所以我们的SD卡用 NAND FLASH 作为存储单元
存储单元是存储数据部件,存储单元通过存储单元接口与卡控制
单元进行数据传输
总线上的通信是通过传送命令和数据实现。
在SD卡总线上的基本操作是命令/响应结构(主机发送命令给SD卡,SD卡是否进行响应,主机发生命令和SD卡响应都是通过命令线(CMD)传输的(有一个响应例外等下说)),这样的总线操作在命令或总线机制下实现信息交换。
在SD/SDIO存储器卡上传送的数据是以数据块的形式传输;在MMC上传送的数据是以数据块或数据流的形式传输;

SD 卡总线有一个主机,多个SD卡,时钟线(CLK),电源(VDD)和地信号(VSS)是所以卡共用。命令线(CMD)和数据线(DAT0-3)信号是根据每张卡的。
理论上主机可以与多张卡进行数据交互,虽然可以共用总线,但不推荐多卡槽共用总线信号,要求一个单独 SD 总线应该连接一个单独的 SD 卡。
即STM32只能接一张SD卡
SDIO 不管是从主机控制器向 SD 卡传输,还是 SD 卡向主机控制器传输都只以 CLK 时钟线的上升沿为有效。SD 卡操作过程会使用两种不同频率的时钟同步数据:
识别卡阶段:时钟频率 FOD,最高为 400kHz
数据传输模式:时钟频率FPP,默认最高为 25MHz
如果通过相关寄存器配置使 SDIO 工作在高速模式,此时数据
传输模式最高频率为 50MHz
为什么卡识别阶段的频率低?
卡识别的时候为了兼容识别不同版本的SD卡
SD 总线通信是基于命令和数据传输的。通讯由一个起始位(“0”),由一个停止位(“1”)终止,SD 通信一般是主机发送一个命令(Command),从设备在接收到命令后作出响应(Response),如有需要会有数据(Data)传输参与。

SD 数据是以块形式传输的,SDHC 卡数据块长度一般为 512 字节,数据可以从主机到卡,也可以是从卡到主机。数据块需要 CRC 位来校验数据。CRC 位由 SD卡系统硬件生成。STM32 控制器可以控制使用单线或 4 线传输(D0-D3)。


SD 数据传输支持单块和多块读写,它们分别对应不同的操作命令,多块写入还需要使用命令来停止整个写入操作。数据写入前需要检测 SD 卡忙状态,因为 SD 卡在接收到数据后存储到存储区(NandFlash)过程需要一定操作时间。SD 卡忙状态通过把 D0 线拉低表示。
使用 4 数据线(D0~D3)传输时,每次传输 4bit 数据,每根数据线都必须有起始位、终止位以及CRC 位,CRC 位每根数据线都要分别检查,并把检查结果汇总然后在数据传输完后通过D0 线反馈给主机。
SD 卡有两种数据包格式。

对 SD 卡而言宽位数据包发送方式是针对 SD 卡 SSR(SD 状态)寄存器内容发送的,SSR 寄存器总共有 512bit,在主机发出ACMD13 命令后(当然在发送ACMD13 之前需要发送CMD55 ) SD 卡将 SSR 寄存器内容通过数据线发送给主机。


SDIO包含2个部分:
● SDIO适配器模块:实现所有MMC/SD/SD I/O卡的相关功能,如时钟的产生、命令和数据的传送。
● AHB总线接口:操作SDIO适配器模块中的寄存器(由STM32控制SDIO外设),并产生中断和DMA请求信号

复位后默认情况下SDIO_D0用于数据传输,初始化后主机可以改变数据总线的宽度(SD卡1根或4根数据线)。
如果一个SD卡接到了总线上,可以通过主机配置数据传输使用SDIO_D0或SDIO_D[3:0] (使用一根或四根数据线)。所有的数据线都工作在复用推挽模式。
命令线也都工作在复用推挽模式
SDIO使用两个时钟信号:
● SDIO适配器时钟(SDIOCLK=HCLK)
● AHB总线时钟(HCLK/2)经过了分频
SDIO_CK是卡的时钟线:每个时钟周期上升沿在命令和数据线上传输1位命令或数据。对于SD卡,时钟频率可以在0MHz至25MHz间变化。

接下来就是将SDIO框图拆分研究各个模块:
● SDIO适配器模块:实现所有SD卡的相关功能,如时钟的产生、命令和数据的传送,而STM32可以通过AHB接口读写适配器寄存器从而控制SDIO外设读写SD卡(发送命令,接收响应,数据传输)。

适配器寄存器和FIFO使用AHB总线一侧的时钟(HCLK/2),控制单元、命令通道和数据通道使用SDIO适配器一侧的时钟(SDIOCLK)
SDIO适配器包含以下5个部分
● 适配器寄存器模块
● 控制单元
● 命令通道
● 数据通道
● 数据FIFO
1.适配器寄存器模块
适配器寄存器模块包含所有系统寄存器。
控制单元包含电源管理功能和为存储器卡提供的时钟分频

时钟管理子单元产生和控制SDIO_CK信号。SDIO_CK输出可以使用时钟分频或时钟旁路模式。
在电源关闭和电源启动阶段,电源管理子单元会关闭卡总线上的输出信号则下述情况下没有时钟输出:
● 复位后
● 在电源关闭和电源启动阶段
● 当启动了省电模式并且卡总线处于空闲状态(命令通道和数据通道子单元进入空闲阶段后的8个时钟周期)
命令通道单元向SD卡发送命令,并接收SD卡的响应,命令与响应都使用SDIO_CMD(命令线)进行传输。

1)命令参数寄存器

2)命令寄存器(命令索引(6位),命令使能位ENCMDcompl)

SD 命令格式固定为 48bit,都是通过 CMD 线连续传输的(数据线不参与)。
命令的主体包括:命令号,命令参数/地址信息,其他的(起始位,传输标志,CRC校验,终止位)不管,后面再讲。

如果命令不带参数,那SDIO参数寄存器默认为全1就好了
主机发送命令的流程:

1)命令响应寄存器

2)SDIO相应寄存器1~4

SDIO 总共有 7 个响应类型(代号:R1~R7),其中 SD 卡没有 R4、R5 类型响应,SD 卡的响应也是通过 CMD 线连续传输的。根据响应内容大小可以分为短响应和长响应。短响应是 48bit 长度,只有 R2 类型是长响应,其长度为 136bit。(关于响应与命令后面马上讲)

现在只需要知道,响应由SD卡也是通过CMD(命令线)传输到SDIO(响应中的命令号存放在SDIO的命令响应寄存器中(如果响应不带命令号则不用管就好了),SD卡的状态则存放在SDIO 响应寄存器1~4)
主机接收SD卡响应的流程:

当写入命令寄存器并设置了使能位,开始发送命令。命令发送完成时,命令通道状态机(CPSM)设置状态标志并在不需要响应时进入空闲状态(见下图)。

一般发送命令接收响应过程

如果命令寄存器中设置挂起位,CPSM进入挂起(Pend)状态并等待数据通道子单元发出的CmdPend信号,在检测到CmdPend信号时,CPSM进入发送(Send)状态,这将触发数据计数器发送停止命令的功能


当进入等待(Wait)状态时,命令定时器开始运行,当CPSM进入接收(Receive)状态之前,产生了超时,则设置超时标志并进入空闲(Idle)状态。
注: 命令超时固定为64个SDIO_CK时钟周期。



然后那些状态机CPSM被关闭,CRC校验失败等都会回到空闲状态
最后这个发送命令等待响应的过程,有很多状态感觉会很复杂,其实我们只有启用了CPSM状态机可以不用管这些状态转化,我们只管发送命令,CPSM状态机会帮我们处理好这些状态。



CRC反正不用管,硬件会帮我们自动校验

在时钟控制寄存器中可以配置卡的数据总线宽度。如果选择了4位总线模式,则每个时钟周期四条数据信号线(SDIO_D[3:0])上将传输4位数据;如果没有选择宽总线模式,则每个时钟周期只在SDIO_D0上传输1位数据。(数据传输可以选择1根或4根数据线关于SD卡)

数据FIFO(先进先出)子单元是一个具有发送和接收单元的数据缓冲区。
FIFO包含一个每字32位宽、共32个字的数据缓冲区,和发送与接收电路。因为数据FIFO工作在AHB时钟区域(HCLK/2),所有与SDIO时钟区域(SDIOCLK)连接的信号都进行了重新同步。依据TXACT和RXACT标志,可以关闭FIFO、使能发送或使能接收。TXACT和RXACT由数据通道子单元设置而且是互斥的:
─ 当TXACT有效时,发送FIFO代表发送电路和数据缓冲区
─ 当RXACT有效时,接收FIFO代表接收电路和数据缓冲区

发送FIFO有32个连续的地址。发送FIFO中有一个数据输出寄存器,包含读指针指向的数据字。当数据通道子单元装填了移位寄存器后,它移动读指针至下个数据并传输出数据。如果未使能发送FIFO,所有的状态标志均处于无效状态。当发送数据时,数据通道子单元设置TXACT为有效。
写操作结束后,写指针自动加一;在另一端,有一个读指针始终指向FIFO中的当前数据。如果关闭了接收FIFO,所有的状态标志会被清除,读写指针也被复位。在接收到数据时数据通道子单元设置RXACT。
下表列出了接收FIFO的状态标志。通过32个连续的地址可以访问接收FIFO。
2.数据通道状态机(DPSM)
根据传输的方向(发送或接收),使能时数据通道状态机(DPSM)将进入Wait_S或Wait_R状态:
● 发送:DPSM进入Wait_S状态。如果发送FIFO中有数据,则DPSM进入发送状态,同时数据通道子单元开始向卡发送数据。

● 接收:DPSM进入Wait_R状态并等待开始位;当收到开始位时,DPSM进入接收状态,同时数据通道子单元开始从卡接收数据

DPSM工作在SDIO_CK频率,卡总线信号与SDIO_CK的上升沿同步。DPSM有6个状态(这下有的看了),如下图所示:

数据控制寄存器

● 空闲(Idle):数据通道不工作,SDIO_D[7:0]输出处于高阻状态。当写入数据控制寄存器并设置使能位时,DPSM为数据计数器加载新的数值,并依据数据方向位进入Wait_S或Wait_R状态。

● Wait_R:如果数据计数器等于0,当接收FIFO为空时DPSM进入到空闲(Idle)状态。如果数据计数器不等于0,DPSM等待SDIO_D上的开始位。如果DPSM在超时之前接收到一个开始位,它会进入接收(Receive)状态并加载数据块计数器。如果DPSM在检测到一个开始位前出现超时,或发生开始位错误,DPSM将进入空闲状态并设置超时状态标志。
下面数据长度寄存器与数据计数寄存器详情看图就不在赘述了,关于这些寄存器会在最后的结构章节详细讲解。

● 接收(Receive):接收到的串行数据被组合为字节并写入数FIFO。根据数据控制寄存器中传输模式位的设置,数据传输模式可以是块传输或流传输:
─ 在数据块传输模式下,当数据块计数器达到0时,DPSM等待接收CRC码,如果接收到的代码与内部产生的CRC码匹配,则DPSM进入Wait_R状态,否则设置CRC失败状态标志同时DPSM进入到空闲状态。


● Wait_S:如果数据计数器为0,DPSM进入空闲状态;否则DPSM等待数据FIFO空标志消失后,进入发送状态
● 发送(Send):DPSM开始发送数据到卡设备。根据数据控制寄存器中传输模式位的设置,数据传输模式可以是块传输或流传输:
─ 在块模式下,当数据块计数器达到0时,DPSM发送内部产生的CRC码,然后是结束位,并进入繁忙状态

● 繁忙(Busy):DPSM等待CRC状态标志:
─ 如果没有接收到正确的CRC状态,则DPSM进入空闲状态并设置CRC失败状态标志。
─ 如果接收到正确的CRC状态,则当SDIO_D0不为低时(SD卡不繁忙)DPSM进入Wait_S状态。(一般情况)

当DPSM处于繁忙状态时发生了超时,DPSM则设置数据超时标志并进入空闲状态。
当DPSM处于Wait_R或繁忙状态时,数据定时器被使能,并能够产生数据超时错误:
─ 发送数据时,如果DPSM处于繁忙状态超过程序设置的超时间隔,则产生超时。
─ 接收数据时,如果未收完所有数据,并且DPSM处于Wait_R状态超过程序设置的超时间隔,则产生超时

数据可以从主机传送到卡,也可以反向传输。数据在数据线上传输。数据存储在一个32字的FIFO中,每个字为32位宽,SD卡可以拉低SDIO_D0数据线表示卡正在忙碌.
最后反正数据传输中的状态怎么复杂,其实数据通道状态机(DPSM)会帮我们处理好这些状态的转换,我们只需要准备好发送和接收数据就好了
实在理不清清楚就了解一下,大概知道用那些寄存器位控制。
SD 命令由主机发出,以广播命令和寻址命令为例,广播命令是针对与 SD 主机总线连接的所有从设备(SD卡)发送的,寻址命令是指定某个地址设备进行命令传输。
SD 命令格式固定为 48bit,都是通过 CMD 线连续传输的(数据线不参与)
1.起始位和终止位:命令的主体包含在起始位与终止位之间,它们都只包含一个数据位,起始位为 0,终止位为 1。
2.传输标志:用于区分传输方向,该位为 1 时表示命令,方向为主机传输到 SD 卡,该位为 0 时表示响应,方向为 SD 卡传输到主机。
3.命令主体内容包括命令、地址信息/参数和 CRC 校验三个部分。
命令号:它固定占用 6bit,所以总共有 64 个命令(代号:CMD0~CMD63),每个命令都有特定的用途,部分命令不适用于 SD 卡操作,只是专门用于 MMC 卡或者SD I/O 卡。(有些命令具有参数有些命令没有参数)
地址/参数:每个命令有 32bit 地址信息/参数用于命令附加内容,例如,广播命令没有地址信息,这 32bit 用于指定参数,而寻址命令这 32bit 用于指定目标 SD 卡的地址。
CRC7 校验:长度为 7bit 的校验位用于验证命令传输内容正确性,如果发生外部干扰导致传输数据个别位状态改变将导致校准失败,也意味着命令传输失败,SD卡不执行命令。
SD 命令有 4 种类型:
所有命令和响应都是通过 SD 卡的 CMD 线发送的。命令的发送总是从最左边的那一位开始
另外,SD 卡主机模块系统旨在为各种应用程序类型提供一个标准接口。在此环境中,需要有特定的客户/应用程序功能。为实现这些功能,在标准中定义了两种类型的通用命令:
特定应用命令(ACMD)和常规命令(GEN_CMD)。要使用 SD 卡制造商特定的 ACMD 命令如ACMD6,需要在发送该命令之前无发送 CMD55 命令,告知 SD 卡接下来的命令为特定应用命令(因为特定应用命令ACMD6与CMD6都表示为 000110 所以要区别的话发送特定命令之前先发送CMD55表示后面的 000110 表示ACMD6 )。
CMD55 命令只对紧接的第一个命令有效,SD 卡如果检测到 CMD55 之后的第一条命令为 ACMD 则执行其特定应用功能,如果检测发现不是 ACMD 命令,则执行标准命令。
反正下面的命名基本上用的到,这里记也记不住,先熟悉一下,后面讲SD卡识别模式,SD传输模式,以及后面写代码的时候,用到的命令在提及。

命令寄存器包含命令索引(发至卡的6位)和命令类型;命令本身决定了是否需要响应和响应的类型、48位还是136位。

应由 SD 卡向主机发出,部分命令要求 SD 卡作出响应,这些响应多用于反馈 SD 卡的状态。SDIO 总共有 7 个响应类型(代号:R1~R7),其中 SD 卡没有 R4、R5 类型响应。特定的命令对应有特定的响应类型,比如当主机发送 CMD3 命令时,可以得到响应 R6。与命令一样,SD 卡的响应也是通过 CMD 线连续传输的。根据响应内容大小可以分为短响应和长响应。短响应是 48bit 长度,只有 R2 类型是长响应,其长度为 136bit。
长度 48bit。bit [45:40]代表响应的命令号(什么命令造成的响应)。卡的状态存储在 bit [39:8]。很多命令基本是R1类型的响应。

R1b响应:
R1b 就是 R1 响应命令,同时数据线上有busy信号(SDIO_D0被拉低)。卡在收到这些命令后可能会变为busy。主机应该在响应中检查 busy。

长度为 136bit。CID 寄存器的内容作为 CMD2 和 CMD10 的响应发送。CSD 寄存器的内容作为 CMD9 的响应发送。只传输 CID 和 CSD 寄存器的[127:1]位,这些寄存器的第[0]位被响应的结束位替代了

长度 48bit,OCR 寄存器的值作为 ACMD41 的响应发送


长度 48bit。[45:40]是响应的命令号,这里就是‘000011’,即 CMD3。参数中的 16 位MSB 用于产生 RCA 号


总结:
1.命令本身决定了是否需要响应和响应的类型
2.响应中除了 R3 类型之外,其他响应都使用 CRC7 校验来校验,对于 R2 类型是使用 CID 和CSD 寄存器内部 CRC7(如果响应不包含CRC(如CMD1的响应),设备驱动应该忽略CRC失败状态)。
3.有些响应不一定返回命令号
SD 卡系统定义了两种操作模式:
● 卡识别模式
在复位后,查找总线上的新卡的时候,主机会处于“卡识别模式”。卡在复位后会处于识别模式,直到收到 SEND_RCA(CMD3)命令.
● 数据传输模式
当 RCA (相当于SD卡的ID号)第一次发布后,卡会处于“数据传输模式”。主机会在总线上所有的卡都被识别后进入这个模式

上面看不懂不要紧,接着往下看
在主机和卡交互之初,主机可能不知道卡支持的电压,卡也可能不知道是否支持当前的电压。主机会先假设 SD 卡支持某个电压,并以这个电压发送一个复位命令 CMD0。为了验证电压,“Physical Layer Specification V2.0”又定义了一个新的命令 CMD8。

CMD8(发送接口条件命令)是用来依照 SD2.0 标准初始化 SD 卡的。CMD8 要在 SD 卡处于“idle”状态下使用。
此命令有两个功能:
检测卡是否能在主机提供的电压下工作。
恢复 CMD8 就能够通过从定义之前预留的 bit,为一些已存在的命令扩展新的功能。

当卡在“idle”状态时,主机应该先发送 CMD8,再发送 ACMD41。参数中“电压支持”bit 被设置为主机支持的电压,而“检测模式”bit 被设置为任意的 8bit 模式(推荐使用‘10101010b) b代表位的意思。

①:这里是卡实际返回的内容(响应传输中不包含错误)
②:匹配意思是同时满足下面的 a)和 b)。不匹配是其他情况。
a) VHS(支持的电压)中只有 1 个 bit 被设置了
b)卡支持主机提供的电压
CMD8用于验证 SD 卡接口操作条件。SD卡会通过分析 CMD8 的参数来检测操作条件的正确性,而主机会通过分析 CMD8 的响应来检查正确性,支持的电压是由参数里的 VHS 区指定的。SD卡会假设 VHS 里面指定的电压是当前支持的电压。每一次命令VHS 里面只有 1 位能被设置为 1。主机会通过 CRC 和检查模式来确认通信的有效性。
强制要求:
在发送第一个 ACMD41 之前要先发送 CMD8,以便初始化高容量 SD 卡。SD 卡如果收到 CMD8,就会知道主机支持 V2.0,就可以使能新的功能。
总结一句话:CMD8用来识别不同版本的卡和检测卡是否能在主机提供的电压下工作。
1.电压不匹配的 2.0 以上 SD 卡
2.1.0 的 SD 卡
3.不是 SD 卡
ACMD41是用来提供给主机一种机制来识别和拒绝那些不匹配它期望的 VDD 范围的卡,主机通过发送需求的 VDD 电压范围来完成这个命令,这个范围作为命令的参数。不能支持指定电压范围的卡应该自动放弃后续的总线操作,并且进入无效状态。OCR 寄存器里面的标准应该响应的定义。
注意:ACMD41 是应用特定命令,在发送 ACMD41 之前需要发送CMD55。


在卡识别模式,主机复位所有的卡、检测操作电压范围、识别卡并为总线上每个卡设置相对地址(RCA(Relative Card Address):靠这个地址确认与主机与哪个SD卡通信)。在卡识别模式下,所有数据通信只使用命令信号线(CMD)。

在卡识别过程中,卡应该在识别时钟频率 FOD (400KHZ)下的 SD 时钟频率中工作,在数据传输模式时钟频率为Fpp(最大25MHZ),所以在卡识别模式完成之后进入数据传输模式需要转换频率。
在卡识别模式期间,主机应该保持在 Fod 频率,因为某些卡可能在卡识别模式中有频率限制。在数据传输模式,主机可以在 Fpp 频率范围操作卡
卡会检查自己能不能在主机提供的电压下工作。如果能够,就会返回 R7 响应。响应参数中卡反馈了电压范围和检测模式设置。

SD_SEND_OP_COND(ACMD41)作为开始,通过设置操作条件和 OCR 的 HCS 位来进行。

HCS 会被不回应 CMD8 的卡忽视掉。然而,如果卡不回应 CMD8,主机应该设置 HCS 为 0。标准容量卡会忽略 HCS。如果 HCS 设置为 0,那么高容量 SD 卡永远都不会返回 ready 状态(保持 busy 位为 0)。
卡通过 OCR 的 busy 位来通知主机 ACMD41 的初始化完成了。
设置 busy 位为 0 表示卡仍然在初始化。
设置 busy 位为 1,表示已经完成初始化。主机会重复发送 ACMD41,直到 busy 为被设置
R3作为ACMD41命令的响应返回,OCR寄存器的值

OCR 寄存器
32 位的操作条件寄存器(OCR)存储了卡的 VDD 电压描述。另外,还包括了状态信息位。如果卡的上电程序完成,一个状态位会被设置。寄存器还包括另一个状态位,在设置上电状态位后,用来表明卡的容量状态。
卡容量状态位只有在上电流程完成,且 Bit31 设置为 1 之后才有效。
主机可以读取这个状态位来判断卡的种类。

如果卡响应了 CMD8,那么 ACMD41 的响应就包括了 CCS 字段信息。当卡返回“ready”的时候,CCS 是有效的(busy 位设置为 1)。
CCS=1 表示卡是高容量 SD 卡;CCS=0 表示卡是普通 SD 卡
总结:ACMD41命令,是用来SD卡的工作电压是否匹配,确定SD的容量(是标准卡还是高容量的卡)。
在系统中,主机遵照相同的初始化顺序来初始化所有的新卡。不兼容的卡会进入“Inactive(无效)”状态。
主机接着就会发送命令 CMD2给每一个卡,来得到他们的 CID 号,未识别的卡(处于 Ready 状态的)发送自己的 CID 作为响应。当卡发送了 CID 之后,它就进入“Identification(识别状态)”状态。

之后主机发送CMD3命令,通知卡发布一个新的相对地址(RCA),这个地址比 CID 短,用于作为将来数据传输模式的地址。一旦收到 RCA,卡就会变为“Stand-by(待机状态)”状态。
这时,如果主机想要分配另一个 RCA 号,它可以再发送一个 CMD3,通知卡重新发布一个 RCA 号。最后一个产生的 RCA 才是有效的。
卡识别模式流程图(重点重点)
一定一定要结合图去理解上面的文字叙述,卡的识别过程就豁然开朗了


在卡识别模式期间,主机应该保持在 Fod (400KHZ)频率,因为某些卡可能在卡识别模式中有频率限制。在数据传输模式,主机可以在 Fpp (最高25MHZ)频率范围操作卡
主机发送命令CMD9来获得“SD卡具体数据(Card Specific Data)”,比如“块长度”,“存储容量”数据传输模式所有状态等(这些数据会用于后面对SD卡的读写操作)。
广播命令 CMD4会配置所有已识别卡的驱动范围(电压)。它会根据应用总线布局(长度)、总线上卡的数量以及数据传输频率来配置它们的 DSR 寄存器。
CMD7 的作用是选择一张SD卡,然后把它从卡识别模式切换到数据传输模式,每次只能有一张卡处于传输模式,如果一张处于传输模式的SD卡同主机的连接被释放,那么它会回到“Stand-by(待机)”状态。当 CMD7 带着参数RCA=0x0000 发送的时候,所有的卡都会回到“Stand-by(待机)状态。


所有数据读命令可以在任何情况下通过停止命令(CMD12)来中止。数据传输会中止,卡会回到传输状态。读命令有:块读(CMD17),多块读(CMD18),发送写保护(CMD30),发送SCR(ACMD51)以及读模式的通用命令(CMD56)
一般就用CMD17块读,多块读(CMD18)


所有数据写命令同样也可以通过 CMD12 来中止。在发送 CMD7 取消选定卡之前,应该先停止写命令。写命令有:块写(CMD24,CMD25),编程 CSD(CMD27),锁定/解锁命令(CMD42)。
CMD24:
1.对于标准卡写入写入 SEL_BLOCK_LEN长度字节的块。
2.对于 SDHC卡(高容量SD卡>2GB),写入512 字节的块。
CMD25:
连续写入数据,直到被CMD12命令打断。

CMD12:

● 一旦数据传输完成,卡会退出写状态,并且进入编程状态(传输成功)或者传输状态(传输失败)

卡可能会提供缓存给“块写”:这就意味着当前一个块正在处理的时候,就可以发送后一个块了。如果缓存都慢了,那么卡就会处于编程状态则SDIO_D0会被拉低(表示busy)

● 将另一张卡从 Stand-by (待机)模式转换到 Transfer (传输)模式(CMD7)不会终止擦除和编程操作。正在操作的SD卡会切换到 Disconnect 状态并且释放 SDIO_D0线。
● 处于 Disconnect 状态的卡可以通过 CMD7 的命令重新被选定。这时,卡会进入Programming 模式,并且重新使能 busy 。

复位卡(CMD0 或者 CMD15)会中止任何等候或者执行的编程操作。这可能会损坏卡的内容(但是尽量不要怎么做)。

总结
至此整个数据传输流程就全部讲完,到这里肯定要心中有点数了,其实后面的代码:卡的识别模式与数据传输模式 代码实现完完全全是按照上述流程来编写的。
其实前面关于SDIO寄存器的讲解已经比较详细了,这里再借助于关于SDIO结构体再进行总结一遍。
标准库函数对 SDIO 外设建立了三个初始化结构体,分别为 SDIO 初始化结构体SDIO_InitTypeDef、SDIO 命令初始化结构体 SDIO_CmdInitTypeDef 和 SDIO 数据初始化结
构体 SDIO_DataInitTypeDef。这些结构体成员用于设置 SDIO 工作环境参数,并由 SDIO 相应初始化配置函数或功能函数调用,这些参数将会被写入到 SDIO 相应的寄存器,达到配置 SDIO 工作环境的目的。
至于为什么需要一个命令结构体与数据结构体,就是为了方便我们配置SDIO关于寄存器位,因为发送命令或者数据需要很多参数配置。
SDIO 初始化结构体用于配置 SDIO 基本工作环境,比如时钟分频、时钟沿、数据宽度等等。它被 SDIO_Init 函数使用。

1) SDIO_ClockEdge:主时钟 SDIOCLK 产生 CLK 引脚时钟有效沿选择,可选上升沿或下降沿。

2) SDIO_ClockBypass:时钟分频旁路使用,可选使能或禁用,如果使能旁路,SDIOCLK (72MHZ )直接驱动 CLK 线输出时钟(不满足最高25HZ的要求),如果禁用,使用 SDIO_CLKCR 寄存器的 CLKDIV 位值分频 SDIOCLK,然后输出到 CLK 线。一般选择禁用时钟分频旁路。

3) SDIO_ClockPowerSave:节能模式选择,可选使能或禁用,它设定 SDIO_CLKCR 寄存器的 PWRSAV 位的值。如果使能节能模式,CLK 线只有在总线激活时才有时钟输出;如果禁用节能模式,始终使能 CLK 线输出时钟。

4) SDIO_BusWide:数据线宽度选择,可选 1 位数据总线、4 位数据总线或 8 为数据总线,系统默认使用 1 位数据总线,操作 SD 卡时在数据传输模式下一般选择 4 位数据总线。它设定 SDIO_CLKCR 寄存器的 WIDBUS 位的值。

5) SDIO_HardwareFlowControl:硬件流控制选择,可选使能或禁用,它设定SDIO_CLKCR 寄存器的 HWFC_EN 位的值。硬件流控制功能可以避免 FIFO 发送上溢和下溢错误。

6) SDIO_ClockDiv:时钟分频系数,它设定 SDIO_CLKCR 寄存器的 CLKDIV 位的值,设置 SDIOCLK 与 CLK 线输出时钟分频系数:
CLK 线时钟频率=SDIOCLK/([CLKDIV+2])。


1) SDIO_Argument:作为命令的一部分发送到卡的命令参数,它设定 SDIO 参数寄存器(SDIO_ARG)的值。

(2) SDIO_CmdIndex:命令号选择,它设定 SDIO 命令寄存器(SDIO_CMD)的 CMDINDEX位的值。

(3) SDIO_Response:响应类型,SDIO 定义两个响应类型:长响应和短响应。根据命令号选择对应的响应类型。SDIO 定义了四个 32 位的 SDIO 响应寄存器(SDIO_RESPx,x=1…4),短响应只用SDIO_RESP1,长响应使用4个(SDIO_RESPx,x=1…4)。

1)命令响应寄存器

2)SDIO响应寄存器1~4

4) SDIO_Wait:等待类型选择,有三种状态可选,一种是无等待状态,超时检测功能启动,一种是等待中断,另外一种是等待传输完成。

5) SDIO_CPSM:命令路径状态机控制,可选使能或禁用 CPSM。它设定 SDIO_CMD 寄存器的 CPSMEN 位的值
只要我们使能的了命令状态机,则下面发送命令和接收响应的过程中的状态转换就不用我们管了

当我们要发送命令,我们只需要配置这个命令初始化结构体的成员,然后调用下图这个函数,则我们配置的参数写入对应的寄存器位中。


1) SDIO_DataTimeOut:设置数据传输以卡总线时钟周期表示的超时周期,它设定 SDIO数据定时器寄存器(SDIO_DTIMER)的值。在 DPSM 进入 Wait_R 或繁忙状态后开始递减,直到 0 还处于以上两种状态则将超时状态标志置 1(详情前面的数据通道小节)。

2) SDIO_DataLength:设置传输数据长度。

3) SDIO_DataBlockSize:设置数据块大小,有多种尺寸可选,不同命令要求的数据块可能不同。

4) SDIO_TransferDir:数据传输方向,可选从主机到卡的写操作,或从卡到主机的读操作。

5) SDIO_TransferMode:数据传输模式,可选数据块或数据流模式。对于 SD 卡操作使用数据块类型。

6) SDIO_DPSM:数据路径状态机控制,可选使能或禁用 DPSM。它设定 SDIO_DCTRL寄存器的 DTEN 位的值。要实现数据传输都必须使能 SDIO_DPSM。
与命令一样使能了数据路径状态机,就不用高那么多麻烦的状态转换了
我们平时使用的SD 卡都是已经包含有文件系统的,一般不会使用本实验的操作方式读写 SD 卡,但是对学习SD卡的驱动原理非常重要!!!
本实验是进行 SD卡最底层的数据读写操作,直接使用 SDIO 对 SD 卡进行读写,会损坏 SD 卡的文件系统,导致数据丢失,所以做这个实验之前需要备份SD卡数据。
主要是学习SD卡的卡识别过程,以及数据传输工过程,其实就是完全依照前面的两个流程图来实现代码的。
卡识别模式流程图
数据传输流程图

原理图:

实物图:

我这里用的是CS创世的贴片式SD卡,也称之为SD NAND , 内部存储单元架构为SLC,适合存代码。直接上板时相比于拔插式SD卡在抗震和抗PIN氧化方面更有优势,对于缩小整板体积也有一定帮助。

详情请参考:雷龙官网

先看主函数:


SD_Terst函数:

我们主要讲解的就是SD卡的初始化

SD_Init()函数:
/**
* 函数名:SD_Init
* 描述 :初始化SD卡,使卡处于就绪状态(准备传输数据)
* 输入 :无
* 输出 :-SD_Error SD卡错误代码
* 成功时则为 SD_OK
* 调用 :外部调用
*/
SD_Error SD_Init(void)
{
/*重置SD_Error状态*/
SD_Error errorstatus = SD_OK;
NVIC_Configuration();
/* SDIO 外设底层引脚初始化 */
GPIO_Configuration();
/*对SDIO的所有寄存器进行复位*/
SDIO_DeInit();
/*上电并进行卡识别流程,确认卡的操作电压 */
errorstatus = SD_PowerON();
/*如果上电,识别不成功,返回“响应超时”错误 */
if (errorstatus != SD_OK)
{
/*!< CMD Response TimeOut (wait for CMDSENT flag) */
return(errorstatus);
}
/*卡识别成功,进行卡初始化 */
errorstatus = SD_InitializeCards();
if (errorstatus != SD_OK) //失败返回
{
/*!< CMD Response TimeOut (wait for CMDSENT flag) */
return(errorstatus);
}
/* 配置SDIO外设
* 上电识别,卡初始化都完成后,进入数据传输模式,提高读写速度
*/
/* SDIOCLK = HCLK, SDIO_CK = HCLK/(2 + SDIO_TRANSFER_CLK_DIV) */
SDIO_InitStructure.SDIO_ClockDiv = SDIO_TRANSFER_CLK_DIV;
/*上升沿采集数据 */
SDIO_InitStructure.SDIO_ClockEdge = SDIO_ClockEdge_Rising;
/* Bypass模式使能的话,SDIO_CK不经过SDIO_ClockDiv分频 */
SDIO_InitStructure.SDIO_ClockBypass = SDIO_ClockBypass_Disable;
/* 若开启此功能,在总线空闲时关闭sd_clk时钟 */
SDIO_InitStructure.SDIO_ClockPowerSave = SDIO_ClockPowerSave_Disable;
/* 暂时配置成1bit模式 */
SDIO_InitStructure.SDIO_BusWide = SDIO_BusWide_1b;
/* 硬件流,若开启,在FIFO不能进行发送和接收数据时,数据传输暂停 */
SDIO_InitStructure.SDIO_HardwareFlowControl = SDIO_HardwareFlowControl_Disable;
SDIO_Init(&SDIO_InitStructure);
if (errorstatus == SD_OK)
{
/* 用来读取csd/cid寄存器 */
errorstatus = SD_GetCardInfo(&SDCardInfo);
}
if (errorstatus == SD_OK)
{
/* 通过cmd7 ,rca选择要操作的卡 */
errorstatus = SD_SelectDeselect((uint32_t) (SDCardInfo.RCA << 16));
}
if (errorstatus == SD_OK)
{
/* 最后为了提高读写,开启4bits模式 */
errorstatus = SD_EnableWideBusOperation(SDIO_BusWide_4b);
}
return(errorstatus);
}
接下来逐段代码来分析一下:

errorstatus其实是一个SD_Error类型的枚举变量,SD_Error 是
列举了控制器可能出现的错误、比如 CRC 校验错误、CRC 校验错误、通信等待超时、FIFO 上溢或下溢、擦除命令错误等等。这些错误类型部分是控制器系统寄存器的标志位,部分是通过命令的响应内容得到的,如果是SD_OK则代表没有发送错误,

配置SDIO中断:


SDIO 外设底层引脚初始化


复位所有SDIO寄存器


重点来了:调用SD_PowerON()进入卡识别模式

/*
* 函数名:SD_PowerON
* 描述 :确保SD卡的工作电压和配置控制时钟
* 输入 :无
* 输出 :-SD_Error SD卡错误代码
* 成功时则为 SD_OK
* 调用 :在 SD_Init() 调用
*/
SD_Error SD_PowerON(void)
{
SD_Error errorstatus = SD_OK;
uint32_t response = 0, count = 0, validvoltage = 0;
uint32_t SDType = SD_STD_CAPACITY;
/********************************************************************************************************/
/* 上电初始化
* 配置SDIO的外设
* SDIOCLK = HCLK, SDIO_CK = HCLK/(2 + SDIO_INIT_CLK_DIV)
* 初始化时的时钟不能大于400KHz
*/
/* HCLK = 72MHz, SDIOCLK = 72MHz, SDIO_CK = HCLK/(178 + 2) = 400 KHz */
SDIO_InitStructure.SDIO_ClockDiv = SDIO_INIT_CLK_DIV;
SDIO_InitStructure.SDIO_ClockEdge = SDIO_ClockEdge_Rising;
/* 不使用bypass模式,直接用HCLK进行分频得到SDIO_CK */
SDIO_InitStructure.SDIO_ClockBypass = SDIO_ClockBypass_Disable;
/* 空闲时不关闭时钟电源 */
SDIO_InitStructure.SDIO_ClockPowerSave = SDIO_ClockPowerSave_Disable;
/* 初始化的时候暂时先把数据线配置成1根 */
SDIO_InitStructure.SDIO_BusWide = SDIO_BusWide_1b;
/* 失能硬件流控制 */
SDIO_InitStructure.SDIO_HardwareFlowControl = SDIO_HardwareFlowControl_Disable;
SDIO_Init(&SDIO_InitStructure);
/* 开启SDIO外设的电源 */
SDIO_SetPowerState(SDIO_PowerState_ON);
/* 使能 SDIO 时钟 */
SDIO_ClockCmd(ENABLE);
/********************************************************************************************************/
/* 下面发送一系列命令,开始卡识别流程
* CMD0: GO_IDLE_STATE(复位所以SD卡进入空闲状态)
* 没有响应
*/
SDIO_CmdInitStructure.SDIO_Argument = 0x0;
SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_GO_IDLE_STATE;
/* 没有响应 */
SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_No;
/* 关闭等待中断 */
SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No;
/* CPSM在开始发送命令之前等待数据传输结束 */
SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable;
SDIO_SendCommand(&SDIO_CmdInitStructure);
/* 检测是否正确接收到cmd0 */
errorstatus = CmdError();
/* 命令发送出错,返回 */
if (errorstatus != SD_OK)
{
/* CMD Response TimeOut (wait for CMDSENT flag) */
return(errorstatus);
}
/********************************************************************************************************/
/* CMD8: SEND_IF_COND
* 发送 CMD8 检查SD卡的电压操作条件
*
* 参数: - [31:12]: 保留 (要被设置为 '0')
* - [11:8] : 支持的电压 (VHS) 0x1 (范围: 2.7-3.6 V)
* - [7:0] : 校验模式 (推荐 0xAA)
* 响应类型: R7
*/
/* 接收到命令sd会返回这个参数 */
SDIO_CmdInitStructure.SDIO_Argument = SD_CHECK_PATTERN;
SDIO_CmdInitStructure.SDIO_CmdIndex = SDIO_SEND_IF_COND;
SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short;
SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No;
SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable;
SDIO_SendCommand(&SDIO_CmdInitStructure);
/*检查是否接收到命令*/
errorstatus = CmdResp7Error();
/* 有响应则card遵循sd协议2.0版本 */
if (errorstatus == SD_OK)
{
/* SD Card 2.0 ,先把它定义会sdsc类型的卡 */
CardType = SDIO_STD_CAPACITY_SD_CARD_V2_0;
/* 这个变量用作ACMD41的参数,用来询问是sdsc卡还是sdhc卡 */
SDType = SD_HIGH_CAPACITY;
}
else /* 无响应,说明是1.x的或mmc的卡 */
{
/* 发命令 CMD55 */
SDIO_CmdInitStructure.SDIO_Argument = 0x00;
SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_APP_CMD;
SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short;
SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No;
SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable;
SDIO_SendCommand(&SDIO_CmdInitStructure);
errorstatus = CmdResp1Error(SD_CMD_APP_CMD);
}
/* CMD55
* 发送cmd55,用于检测是sd卡还是mmc卡,或是不支持的卡
* CMD 响应: R1
*/
SDIO_CmdInitStructure.SDIO_Argument = 0x00;
SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_APP_CMD;
SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short;
SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No;
SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable;
SDIO_SendCommand(&SDIO_CmdInitStructure);
/* 是否响应,没响应的是mmc或不支持的卡 */
errorstatus = CmdResp1Error(SD_CMD_APP_CMD);
/********************************************************************************************************/
/* 若 errorstatus 为 Command TimeOut, 说明是MMC 卡
* 若 errorstatus 为 SD_OK ,说明是SD card: SD 卡 2.0 (电压范围不匹配)
* 或 SD 卡 1.x
*/
if (errorstatus == SD_OK) //响应了cmd55,是sd卡,可能为1.x,可能为2.0
{
/*下面开始循环地发送sdio支持的电压范围,循环一定次数*/
/* SD CARD
* 发送 ACMD41 SD_APP_OP_COND ,带参数 0x80100000
*/
while ((!validvoltage) && (count < SD_MAX_VOLT_TRIAL))
{
/* 在发送ACMD命令前都要先向卡发送CMD55
* 发送 CMD55 APP_CMD , RCA 为 0
*/
SDIO_CmdInitStructure.SDIO_Argument = 0x00;
SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_APP_CMD;
SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short;
SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No;
SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable;
SDIO_SendCommand(&SDIO_CmdInitStructure);
errorstatus = CmdResp1Error(SD_CMD_APP_CMD);
if (errorstatus != SD_OK)
{
return(errorstatus);
}
/* ACMD41
* 命令参数由支持的电压范围及HCS位组成,HCS位置一来区分卡是SDSC还是SDHC
* 0:SDSC
* 1:SDHC
* 响应:R3,对应的是OCR寄存器
*/
SDIO_CmdInitStructure.SDIO_Argument = SD_VOLTAGE_WINDOW_SD | SDType;
SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_SD_APP_OP_COND;
SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short;
SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No;
SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable;
SDIO_SendCommand(&SDIO_CmdInitStructure);
errorstatus = CmdResp3Error();
if (errorstatus != SD_OK)
{
return(errorstatus);
}
/* 若卡需求电压在SDIO的供电电压范围内,会自动上电并标志pwr_up位
* 读取卡寄存器,卡状态
*/
response = SDIO_GetResponse(SDIO_RESP1);
/* 读取卡的ocr寄存器的pwr_up位,看是否已工作在正常电压 */
validvoltage = (((response >> 31) == 1) ? 1 : 0);
count++; /* 计算循环次数 */
}
if (count >= SD_MAX_VOLT_TRIAL) /* 循环检测超过一定次数还没上电 */
{
errorstatus = SD_INVALID_VOLTRANGE; /* SDIO不支持card的供电电压 */
return(errorstatus);
}
/*检查卡返回信息中的HCS位*/
/* 判断ocr中的ccs位 ,如果是sdsc卡则不执行下面的语句 */
if (response &= SD_HIGH_CAPACITY)
{
CardType = SDIO_HIGH_CAPACITY_SD_CARD; /* 把卡类型从初始化的sdsc型改为sdhc型 */
}
}/* else MMC Card */
return(errorstatus);
}
1.配置SDIO初始化结构体**

配置 SDIO_InitStructure 结构体变量成员并调用 SDIO_Init 库函数完成 SDIO 外设的基本配置,注意此处的 SDIO 时钟分频,由于处于卡识别阶段,其时钟不能超过 400KHz。
2.发送CMD0命令:要SD卡回到空闲状态


那些检测标志全是来源与下图:

3.发送CMD8: 用来识别不同版本的卡和检测卡是否能在主机提供的电压下工作。
1.电压不匹配的 2.0 以上 SD 卡
2.1.0 的 SD 卡
3.不是 SD 卡

4.使用 ACMD41 命令判断卡的具体类型。因为是 A 类命令,所以在发送 ACMD41之前必须先发送 CMD55,CMD55 命令的响应类型的 R1。如果 CMD55 命令都没有响应说明是 MMC 卡或不可用卡。在正确发送 CMD55 之后就可以送ACMD41,并根据响应判断卡类型,ACMD41 的响应号为 R3,CmdResp3Error 函数用于检测命令正确发送并带有超时检测功能,但并不具备响应内容接收功能,需要在判定命令正确发送之后调用 SDIO_GetResponse 函数才能获取响应的内容。
实际上,在有响应时,SDIO 外设会自动把响应存放在 SDIO_RESPx 寄存器中,SDIO_GetResponse 函数只是根据形参返回对应响应寄存器的值。通过判定响应内容值即可确定 SD 卡类型。


总结:执行 SD_PowerON 函数无错误后就已经确定了 SD 卡类型,并说明卡和主机电压是匹配的,SD 卡处于卡识别模式下的准备状态。退出 SD_PowerON 函数返回SD_Init 函数,执行接下来代码。
执行 SD_PowerON 函数没有错误后:SD 卡处于卡识别模式下的准备状态


SD_InitializeCards()函数:
/*
* 函数名:SD_InitializeCards
* 描述 :初始化所有的卡或者单个卡进入就绪状态
* 输入 :无
* 输出 :-SD_Error SD卡错误代码
* 成功时则为 SD_OK
* 调用 :在 SD_Init() 调用,在调用power_on()上电卡识别完毕后,调用此函数进行卡初始化
*/
SD_Error SD_InitializeCards(void)
{
SD_Error errorstatus = SD_OK;
uint16_t rca = 0x01;
if (SDIO_GetPowerState() == SDIO_PowerState_OFF)
{
errorstatus = SD_REQUEST_NOT_APPLICABLE;
return(errorstatus);
}
/* 判断卡的类型 */
if (SDIO_SECURE_DIGITAL_IO_CARD != CardType)
{
/* Send CMD2 ALL_SEND_CID
* 响应:R2,对应CID寄存器
*/
SDIO_CmdInitStructure.SDIO_Argument = 0x0;
SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_ALL_SEND_CID;
SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Long;
SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No;
SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable;
SDIO_SendCommand(&SDIO_CmdInitStructure);
errorstatus = CmdResp2Error();
if (SD_OK != errorstatus)
{
return(errorstatus);
}
/* 将返回的CID信息存储起来 */
CID_Tab[0] = SDIO_GetResponse(SDIO_RESP1);
CID_Tab[1] = SDIO_GetResponse(SDIO_RESP2);
CID_Tab[2] = SDIO_GetResponse(SDIO_RESP3);
CID_Tab[3] = SDIO_GetResponse(SDIO_RESP4);
}
/********************************************************************************************************/
if ( (SDIO_STD_CAPACITY_SD_CARD_V1_1 == CardType)
||(SDIO_STD_CAPACITY_SD_CARD_V2_0 == CardType)
||(SDIO_SECURE_DIGITAL_IO_COMBO_CARD == CardType)
||(SDIO_HIGH_CAPACITY_SD_CARD == CardType) ) /* 使用的是2.0的卡 */
{
/* Send CMD3 SET_REL_ADDR with argument 0
* SD Card publishes its RCA.
* 响应:R6,对应RCA寄存器
*/
SDIO_CmdInitStructure.SDIO_Argument = 0x00;
SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_SET_REL_ADDR;
SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short;
SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No;
SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable;
SDIO_SendCommand(&SDIO_CmdInitStructure);
/* 把接收到的卡相对地址存起来 */
errorstatus = CmdResp6Error(SD_CMD_SET_REL_ADDR, &rca);
if (SD_OK != errorstatus)
{
return(errorstatus);
}
}
/********************************************************************************************************/
if (SDIO_SECURE_DIGITAL_IO_CARD != CardType)
{
RCA = rca;
/* Send CMD9 SEND_CSD with argument as card's RCA
* 响应:R2 对应寄存器CSD(Card-Specific Data)
*/
SDIO_CmdInitStructure.SDIO_Argument = (uint32_t)(rca << 16);
SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_SEND_CSD;
SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Long;
SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No;
SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable;
SDIO_SendCommand(&SDIO_CmdInitStructure);
errorstatus = CmdResp2Error();
if (SD_OK != errorstatus)
{
return(errorstatus);
}
CSD_Tab[0] = SDIO_GetResponse(SDIO_RESP1);
CSD_Tab[1] = SDIO_GetResponse(SDIO_RESP2);
CSD_Tab[2] = SDIO_GetResponse(SDIO_RESP3);
CSD_Tab[3] = SDIO_GetResponse(SDIO_RESP4);
}
/********************************************************************************************************/
/*全部卡初始化成功 */
errorstatus = SD_OK;
return(errorstatus);
}
1.判断 SDIO 电源是否启动,如果没有启动电源返回错误。

2.发送CMD2命令 :是用于通知所有卡通过 CMD 线返回 CID 值,执行 CMD2 发送之后就可以使用 CmdResp2Error 函数获取 CMD2 命令发送情况,发送无错误后即可以使用 SDIO_GetResponse 函数获取响应内容,它是个长响应,我们把 CMD2 响应内容存放在 CID_Tab 数组内。
3.发送CMD3命令,用于指示 SD 卡自行推荐 RCA 地址,CMD3 的响应为 R6 类型,CmdResp6Error 函数用于检查 R6 响应错误,它有两个形参,一个是命令号,这里为 CMD3,另外一个是 RCA 数据指针,这里使用 rca变量的地址赋值给它,使得在 CMD3 正确响应之后 rca 变量即存放 SD 卡的 RCA。
CmdResp6Error 函数通用会对每个错误位进行必要的检测,如果发现有错误存在则直接返回对应错误类型。

执行完CmdResp6Error 函数之后返回到 SD_InitializeCards 函数中,如果判断无错误说明此刻 SD 卡已经处于数据传输模式。

4.发送 CMD9 给指定 RCA 的 SD 卡使其发送返回其 CSD 寄存器内容,这里的 RCA就是在 CmdResp6Error 函数获取得到的 rca。最后把响应内容存放在 CSD_Tab 数组中。

执行 SD_InitializeCards 函数无错误后 SD 卡就已经处于数据传输模式下的待机状态,退出 SD_InitializeCards 后会返回前面的 SD_Init 函数,执行接下来代码,以下是 SD_Init 函数的后续执行过程:

1) 重新配置 SDIO 外设,提高时钟频率,之前的卡识别模式都设定 CMD 线时钟为小于 400KHz,进入数据传输模式可以把时钟设置为小于 25MHz,以便提高数据传输速率。

(2) 调用 SD_GetCardInfo 函数获取 SD 卡信息,它需要一个指向 SD_CardInfo 类型变量地址的指针形参,这里赋值为 SDCardInfo 变量的地址。SD 卡信息主要是 CID和 CSD 寄存器内容,这两个寄存器内容在 SD_InitializeCards 函数中都完成读取过程并将其分别存放在 CID_Tab 数组和 CSD_Tab 数组中,SD_GetCardInfo 函数只是简单的把这两个数组内容整合复制到 SDCardInfo 变量对应成员内。正确执行 SD_GetCardInfo 函数后,SDCardInfo 变量就存放了 SD 卡的很多状态信息,这在之后应用中使用频率是很高的。

结构体类型定义:有 SD_CSD、SD_CID、SD_CardStatus 以及 SD_CardInfo。SD_CSD 定义了 SD 卡的特定数据(CSD)寄存器位,一般提供 R2 类型的响应可以获取得到 CSD 寄存器内容。SD_CID 结构体类似 SD_CSD 结构体,它定义 SD 卡CID 寄存器内容,也是通过 R2 响应类型获取得到。SD_CardStatus 结构体定义了SD 卡状态,有数据宽度、卡类型、速度等级、擦除宽度、传输偏移地址等等 SD卡状态。SD_CardInfo 结构体定义了 SD 卡信息,包括了 SD_CSD 类型和 SD_CID类型成员,还有定义了卡容量、卡块大小、卡相对地址 RCA 和卡类型成员。

主要是存储卡的容量,卡的大小,RCA地址,卡的类型(这些是关键信息,由命令响应返回然后存入这个结构体中)



3) 调用 SD_SelectDeselect 函数用于选择特定 RCA 的 SD 卡,它实际是向 SD 卡发送CMD7。执行之后,卡就从待机状态转变为传输模式,可以说数据传输已经是万事俱备了



4) 扩展数据线宽度,之前的所有操作都是使用一根数据线传输完成的,使用 4 根数据线可以提高传输性能,调用可以设置数据线宽度,函数只有一个形参,用于指定数据线宽度。

至此,SD_Init 函数已经全部执行完成。如果程序可以正确执行,接下来就可以进行SD 卡读写以及擦除等操作。

SD_EraseTest()函数

SD_Erase()函数:


1) 检查 SD 卡是否支持擦除功能,如果不支持则直接返回错误。为保证擦除指令正常进行,要求主机一个遵循下面的命令序列发送指令:CMD32->CMD33->CMD38。如果发送顺序不对,SD 卡会设置 ERASE_SEQ_ERROR 位到状态寄存器


2) SD_Erase 函数发送 CMD32 指令用于设定擦除块开始地址,在执行无错误后发送CMD33 设置擦除块的结束地址。

3) 发送擦除命令 CMD38,使得 SD 卡进行擦除操作。SD 卡擦除操作由 SD 卡内部控制完成,不同卡擦除后是 0xff 还是 0x00 由厂家决定。擦除操作需要花费一定时间,这段时间不能对 SD 卡进行其他操作。

4) 通过 IsCardProgramming 函数可以检测 SD 卡是否处于编程状态(即卡内部的擦写状态),需要确保 SD 卡擦除完成才退出 SD_Erase 函数。IsCardProgramming 函数先通过发送CMD13 命令 SD 卡发送它的状态寄存器内容,并对响应内容进行分析得出当前 SD 卡的状态以及可能发送的错误。



SD_WriteBlock 函数用于向指定的目标地址写入一个块的数据,它有三个形参,分别为指向待写入数据的首地址的指针变量、目标写入地址和块大小。块大小一般都设置为512 字节。SD_WriteBlock 写入函数的执行流程如下:
1) SD_WriteBlock 函数开始时将 SDIO 数据控制寄存器 (SDIO_DCTRL)清零,复位之前的传输设置。

2) 对 SD 卡进行数据读写之前,都必须发送 CMD16 指定块的大小,对于标准卡,要写入BlockSize 长度字节的块;对于 SDHC 卡,写入固定为 512 字节的块。接下来就可以发送块写入命令 CMD24 通知 SD 卡要进行数据写入操作,并指定待写入数据的目标地址。


3) 利用 SDIO_DataInitTypeDef 结构体类型变量配置数据传输的超时、块数量、数据块大小、数据传输方向等参数并使用 SDIO_DataConfig 函数完成数据传输环境配置。

4) 调用 SDIO_ITConfig 函数使能 SDIO 数据结束传输结束中断,传输结束时,会跳转到SDIO 的中断服务函数运行。
5)SD_DMA_TxConfig 函数,配置使能 SDIO 数据向 SD 卡的数据传输的DMA 请求,为使 SDIO 发送 DMA 请求,需要调用
SDIO_DMACmd 函数使能。对于高容量的 SD 卡要求块大小必须为 512 字节,SDIO 外设会自动生成 DMA 发送请求,将指定数据使用 DMA 传输写入到 SD 卡内。
普通模式需要自己去处理那些溢出什么的太麻烦了,用DMA传输数据就好了

DMA外设配置(不清楚的参考:DMA外设详解):


写入操作等待函数
SD_WaitWriteOperation 函数用于检测和等待数据写入完成,在调用数据写入函数之后一般都需要调用,SD_WaitWriteOperation 函数适用于单块及多块写入函数。

SDIO 中断服务函数
在进行数据传输操作时都会使能相关标志中断,用于跟踪传输进程和错误检测。
SD_ProcessIRQSrc 函数首先判断全局变量 StopCondition 变量是否为 1,该全局变量在SDIO 的多块读写函数中被置 1(前面分析的单块读写函数中 StopCondition 均为 0),因为根据 SD 卡的要求,多块读写命令由 CMD12 结束,SD 卡在接收到该命令时才停止多块的传输,此处正是根据 StopCondition 的情况控制是否发送 CMD12 命令,它发送命令时直接采用往寄存器写入命令和参数的方式。



调用库函数 SD_DMAEndOfTransferStatus 一直检测 DMA 的传输完成标志,当 DMA 传输结束时,该函数会返回 SET 值。另外,while 循环中的判断条件使用的TransferEnd 和 TransferError 是全局变量,它们会在 SDIO 的中断服务函数根据传输情况被设置,传输结束后,根据 TransferError 的值来确认是否正确传输,若不正确则直接返回错
误代码。SD_WaitWriteOperation 函数最后是清除相关标志位并返回错误。
数据读取操作
同向 SD 卡写入数据类似,从 SD 卡读取数据可分为单块读取和多块读取。这里仅介绍单块读操作函数,多块读操作类似。
这一部分自己看代码吧,操作差不多,已经人麻了太多了。


还有多块读取与多块写入,其实是一样的,只不过传输结束需要发送CMD12来结束传输。
总结:代码太多了,但是核心的东西已经讲完了,自己去看代码悟一下,其实前面的理论部分懂了,代码部分是完全按照理论来走的,只不过多了一点点细节,就这样咯,那些边边角角留给你们。
最后来炫一下富


