35. SDIO 您所在的位置:网站首页 stm32向sd卡中写文件偶尔会出错 35. SDIO

35. SDIO

2024-04-20 16:00| 来源: 网络整理| 查看: 265

35. SDIO—SD卡读写测试¶

本章参考资料:《STM32F10X-中文参考手册》、《STM32F103增强型系列数据手册》以及SD简易规格文件《Physical Layer Simplified Specification V2.0》(版本号:2.00)。

阅读本章内容之前,建议先阅读SD简易规格文件。

35.1. SDIO简介¶

SD卡(Secure Digital Memory Card)在我们生活中已经非常普遍了,控制器对SD卡进行读写通信操作一般有两种通信接口可选,一种是SPI接口,另外一种就是SDIO接口。SDIO全称是安全数字输入/输出接口,多媒体卡(MMC)、SD卡、SD I/O卡都有SDIO接口。STM32F10x系列控制器有一个SDIO主机接口,它可以与MMC卡、SD卡、SD I/O卡以及CE-ATA设备进行数据传输。MMC卡可以说是SD卡的前身,现阶段已经用得很少。SD I/O卡本身不是用于存储的卡,它是指利用SDIO传输协议的一种外设。比如Wi-Fi Card,它主要是提供Wi-Fi功能,有些Wi-Fi模块是使用串口或者SPI接口进行通信的,但Wi-Fi SDIO Card是使用SDIO接口进行通信的。并且一般设计SD I/O卡是可以插入到SD的插槽。CE-ATA是专为轻薄笔记本硬盘设计的硬盘高速通讯接口。

多媒体卡协会网站www.mmca.org中提供了有MMCA技术委员会发布的多媒体卡系统规范。

SD卡协会网站www.sdcard.org中提供了SD存储卡和SDIO卡系统规范。

CE-ATA工作组网站www.ce-ata.org中提供了CE_ATA系统规范。

随之科技发展,SD卡容量需求越来越大,SD卡发展到现在也是有几个版本的,关于SDIO接口的设备整体概括见 图35_1。

图 35‑1 SDIO接口的设备

关于SD卡和SD I/O部分内容可以在SD协会网站获取到详细的介绍,比如各种SD卡尺寸规则、读写速度标示方法、应用扩展等等信息。

本章内容针对SD卡使用讲解,对于其他类型卡的应用可以参考相关系统规范实现,所以对于控制器中针对其他类型卡的内容可能在本章中简单提及或者被忽略,本章内容不区分SDIO和SD卡这两个概念。即使目前SD协议提供的SD卡规范版本最新是4.01版本,但STM32F10x系列控制器只支持SD卡规范版本2.0,即只支持标准容量SD和高容量SDHC标准卡,不支持超大容量SDXC标准卡,所以可以支持的最高卡容量是32GB。

35.2. SD卡物理结构¶

一张SD卡包括有存储单元、存储单元接口、电源检测、卡及接口控制器和接口驱动器5个部分,见图 35‑2。存储单元是存储数据部件,存储单元通过存储单元接口与卡控制单元进行数据传输;电源检测单元保证SD卡工作在合适的电压下,如出现掉电或上状态时,它会使控制单元和存储单元接口复位;卡及接口控制单元控制SD卡的运行状态,它包括有8个寄存器;接口驱动器控制SD卡引脚的输入输出。

图 35‑2 SD卡物理结构

SD卡总共有8个寄存器,用于设定或表示SD卡信息,参考表 35‑1。这些寄存器只能通过对应的命令访问,对SD卡进行控制操作并不是像操作控制器GPIO相关寄存器那样一次读写一个寄存器的,它是通过命令来控制,SDIO定义了64个命令,每个命令都有特殊意义,可以实现某一特定功能,SD卡接收到命令后,根据命令要求对SD卡内部寄存器进行修改,程序控制中只需要发送组合命令就可以实现SD卡的控制以及读写操作。

表 35‑1 SD卡寄存器

名称

bit宽度

描述

CID

128

卡识别号(Card identification number):用来识别的卡的个体号码(唯一的)

RCA

16

相对地址(Relative card address):卡的本地系统地址,初始化时,动态地由卡建议,主机核准。

DSR

16

驱动级寄存器(Driver Stage Register):配置卡的输出驱动

CSD

128

卡的特定数据(Card Specific Data):卡的操作条件信息

SCR

64

SD配置寄存器(SD Configuration Register):SD 卡特殊特性信息

OCR

32

操作条件寄存器(Operation conditions register)

SSR

512

SD状态(SD Status):SD卡专有特征的信息

CSR

32

卡状态(Card Status):卡状态信息

每个寄存器位的含义可以参考SD简易规格文件《Physical Layer Simplified Specification V2.0》第5章内容。

35.3. SDIO总线¶ 35.3.1. 总线拓扑¶

SD卡一般都支持SDIO和SPI这两种接口,本章内容只介绍SDIO接口操作方式,如果需要使用SPI操作方式可以参考SPI相关章节。另外,STM32F10x系列控制器的SDIO是不支持SPI通信模式的,如果需要用到SPI通信只能使用SPI外设。

SD卡总线拓扑参考图 35‑3。虽然可以共用总线,但不推荐多卡槽共用总线信号,要求一个单独SD总线应该连接一个单独的SD卡。

图 35‑3 SD卡总线拓扑

SD卡使用9-pin接口通信,其中3根电源线、1根时钟线、1根命令线和4根数据线,具体说明如下:

CLK:时钟线,由SDIO主机产生,即由STM32控制器输出;

CMD:命令控制线,SDIO主机通过该线发送命令控制SD卡,如果命令要求SD卡提供应答(响应),SD卡也是通过该线传输应答信息;

D0-3:数据线,传输读写数据;SD卡可将D0拉低表示忙状态;

VDD、VSS1、VSS2:电源和地信号。

在之前的I2C以及SPI章节都有详细讲解了对应的通信时序,实际上,SDIO的通信时序简单许多,SDIO不管是从主机控制器 向SD卡传输,还是SD卡向主机控制器传输都只以CLK时钟线的上升沿为有效。SD卡操作过程会使用两种不同频率 的时钟同步数据,一个是识别卡阶段时钟频率FOD,最高为400kHz,另外一个是数据传输模式下时钟频率FPP,默认最高 为25MHz,如果通过相关寄存器配置使SDIO工作在高速模式,此时数据传输模式最高频率为50MHz。

对于STM32控制器只有一个SDIO主机,所以只能连接一个SDIO设备,开发板上集成了一个Micro SD卡槽和SDIO接口的WiFi模块,要求只能使用其中一个设备。SDIO接口的WiFi模块一般集成有使能线,如果需要用到SD卡需要先控制该使能线禁用WiFi模块。

35.3.2. 总线协议¶

SD总线通信是基于命令和数据传输的。通讯由一个起始位(“0”),由一个停止位(“1”)终止。SD通信一般是主机发送一个命令(Command),从设备在接收到命令后作出响应(Response),如有需要会有数据(Data)传输参与。

SD总线的基本交互是命令与响应交互,见图 35‑4。

图 35‑4 命令与响应交互

SD数据是以块(Black)形式传输的,SDHC卡数据块长度一般为512字节,数据可以从主机到卡,也可以是从卡到主机。数据块需要CRC位来 保证数据传输成功。CRC位由SD卡系统硬件生成。STM32控制器可以控制使用单线或4线传输,本开发板设计使用4线传输。 图35_5 为主机向SD卡写入数据块操作示意。

图 35‑5 多块写入操作

SD数据传输支持单块和多块读写,它们分别对应不同的操作命令,多块写入还需要使用命令来停止整个写入操作。数据写入前需要检测SD卡忙状态,因为SD卡在接收到数据后编程到存储区过程需要一定操作时间。SD卡忙状态通过把D0线拉低表示。

数据块读操作与之类似,只是无需忙状态检测。

使用4数据线传输时,每次传输4bit数据,每根数据线都必须有起始位、终止位以及CRC位,CRC位每根数据线都要分别检查,并把检查结果汇总然后在数据传输完后通过D0线反馈给主机。

SD卡数据包有两种格式,一种是常规数据(8bit宽),它先发低字节再发高字节,而每个字节则是先发高位再发低位,4线传输示意如 图35_6。

图 35‑6 8位宽数据包传输

4线同步发送,每根线发送一个字节的其中两个位,数据位在四线顺序排列发送,DAT3数据线发较高位,DAT0数据线发较低位。

另外一种数据包发送格式是宽位数据包格式,对SD卡而言宽位数据包发送方式是针对SD卡SSR(SD状态)寄存器内容发送的,SSR寄存器总共有512bit,在 主机发出ACMD13命令后SD卡将SSR寄存器内容通过DAT线发送给主机。宽位数据包格式示意见 图35_7。

图 35‑7 宽位数据包传输

35.3.3. 命令¶

SD命令由主机发出,以广播命令和寻址命令为例,广播命令是针对与SD主机总线连接的所有从设备发送的,寻址命令是指定某个地址设备进行命令传输。

35.3.3.1. 命令格式¶

SD命令格式固定为48bit,都是通过CMD线连续传输的(数据线不参与),见 图35_8。

图 35‑8 SD命令格式

SD命令的组成如下:

起始位和终止位:命令的主体包含在起始位与终止位之间,它们都只包含一个数据位,起始位为0,终止位为1。

传输标志:用于区分传输方向,该位为1时表示命令,方向为主机传输到SD卡,该位为0时表示响应,方向为SD卡传输到主机。

命令主体内容包括命令、地址信息/参数和CRC校验三个部分。

命令号:它固定占用6bit,所以总共有64个命令(代号:CMD0~CMD63),每个命令都有特 定的用途,部分命令不适用于SD卡操作,只是专门用于MMC卡或者SD I/O卡。

地址/参数:每个命令有32bit地址信息/参数用于命令附加内容,例如,广播命令没有地址 信息,这32bit用于指定参数,而寻址命令这32bit用于指定目标SD卡的地址。

CRC7校验:长度为7bit的校验位用于验证命令传输内容正确性,如果发生外部干扰导致传输 数据个别位状态改变将导致校准失败,也意味着命令传输失败,SD卡不执行命令。

35.3.3.2. 命令类型¶

SD命令有4种类型:

无响应广播命令(bc),发送到所有卡,不返回任务响应;

带响应广播命令(bcr),发送到所有卡,同时接收来自所有卡响应;

寻址命令(ac),发送到选定卡,DAT线无数据传输;

寻址数据传输命令(adtc),发送到选定卡,DAT线有数据传输。

另外,SD卡主机模块系统旨在为各种应用程序类型提供一个标准接口。在此环境中,需要有特定的客户/应用程序功能。为实现这些功能,在标准中定义了两种类型的通用命令:特定应用命令(ACMD)和常规命令(GEN_CMD)。要使用SD卡制造商特定的ACMD命令如ACMD6,需要在发送该命令之前无发送CMD55命令,告知SD卡接下来的命令为特定应用命令。CMD55命令只对紧接的第一个命令有效,SD卡如果检测到CMD55之后的第一条命令为ACMD则执行其特定应用功能,如果检测发现不是ACMD命令,则执行标准命令。

35.3.3.3. 命令描述¶

SD卡系统的命令被分为多个类,每个类支持一种“卡的功能设置”。表 35‑2列举了SD卡部分命令信息,更多详细信息可以参考SD简易规格文件说明,表中填充位和保留位都必须被设置为0。

虽然没有必须完全记住每个命令详细信息,但越熟悉命令对后面编程理解非常有帮助。

表 35‑2 SD部分命令描述

命令序号

类型

参数

响应

缩写

描述

基本命令(Class

CMD0

bc

[31:0]填充位

GO_IDLE_STATE

复位所有的卡到idle状态。

CMD2

bcr

[31:0]填充位

R2

ALL_SEND_CID

通知所有卡通过CMD线返回CID值。

CMD3

bcr

[31:0]填充位

R6

SEND_RELATIVE_ADDR

通知所有卡发布新RCA。

CMD4

bc

[31:16]DSR[15:0]填充位

SET_DSR

编程所有卡的DSR。

CMD7

ac

[31:16]RCA[15:0]填充位

R1b

SELECT/DESELECT_CARD

选择/取消选择RCA地址卡。

CMD8

bcr

[31:12]保留位[11:8]VHS[7:0]检查模式

R7

SEND_IF_COND

发送SD卡接口条件,包含主机支持的电压信息,并询问卡是否支持。

CMD9

ac

[31:16]RCA[15:0]填充位

R2

SEND_CSD

选定卡通过CMD线发送CSD内容

CMD10

ac

[31:16]RCA[15:0]填充位

R2

SEND_CID

选定卡通过CMD线发送CID内容

CMD12

ac

[31:0]填充位

R1b

STOP_TRANSMISSION

强制卡停止传输

CMD13

ac

[31:16]RCA[15:0]填充位

R1

SEND_STATUS

选定卡通过CMD线发送它状态寄存器

CMD15

ac

[31:16]RCA[15:0]填充位

GO_INACTIVE_STATE

使选定卡进入“inactive”状态

面向块的读操作(Class

CMD16

ac

[31:0]块长度

R1

SET_BLOCK_LEN

对于标准SD卡,设置块命令的长度,对于SDHC卡块命令长度固定为512字节。

CMD17

adtc

[31:0]数据地址

R1

READ_SINGLE_BLOCK

对于标准卡,读取SEL_BLOCK_LEN长度字节的块;对于SDHC卡,读取512字节的块。

CMD18

adtc

[31:0]数据地址

R1

READ_MULTIPLE_BLOCK

连续从SD卡读取数据块,直到被CMD12中断。块长度同CMD17。

面向块的写操作(Class

CMD24

adtc

[31:0]数据地址

R1

WRITE_BLOCK

对于标准卡,写入SEL_BLOCK_LEN长度字节的块;对于SDHC卡,写入512字节的块。

CMD25

adtc

[31:0]数据地址

R1

WRITE_MILTIPLE_BLOCK

连续向SD卡写入数据块,直到被CMD12中断。每块长度同CMD17。

CMD27

adtc

[31:0]填充位

R1

PROGRAM_CSD

对CSD的可编程位进行编程

擦除命令(Class

CMD32

ac

[31:0]数据地址

R1

ERASE_WR_BLK_START

设置擦除的起始块地址

CMD33

ac

[31:0]数据地址

R1

ERASE_WR_BLK_END

设置擦除的结束块地址

CMD38

ac

[31:0]填充位

R1b

ERASE

擦除预先选定的块

加锁命令(Class

CMD42

adtc

[31:0]保留

R1

LOCK_UNLOCK

加锁/解锁SD卡

特定应用命令(Class

CMD55

ac

[31:16]RCA[15:0]填充位

R1

APP_CMD

指定下个命令为特定应用命令,不是标准命令

CMD56

adtc

[31:1]填充位[0]读/写

R1

GEN_CMD

通用命令,或者特定应用命令中,用于传输一个数据块,最低位为1表示读数据,为0表示写数据

SD卡特定应用命令

ACMD6

ac

[31:2]填充位[1:0]总线宽度

R1

SET_BUS_WIDTH

定义数据总线宽度(‘00’=1bit,’10’=4bit)。

ACMD13

adtc

[31:0]填充位

R1

SD_STATUS

发送SD状态

ACMD41

Bcr

[32]保留位[30]HCS(OCR[30])

[29:24]保留位[23:0]VDD电压(OCR[23:0])

R3

SD_SEND_OP_COND

主机要求卡发送它的支持信息(HCS)和OCR寄存器内容。

ACMD51

adtc

[31:0]填充位

R1

SEND_SCR

读取配置寄存器SCR

35.3.4. 响应¶

响应由SD卡向主机发出,部分命令要求SD卡作出响应,这些响应多用于反馈SD卡的状态。SDIO总共有7个响应类型(代号:R1~R7),其中SD卡没有R4、R5类型响应。特定的命令对应有特定的响应类型,比如当主机发送CMD3命令时,可以得到响应R6。与命令一样,SD卡的响应也是通过CMD线连续传输的。根据响应内容大小可以分为短响应和长响应。短响应是48bit长度,只有R2类型是长响应,其长度为136bit。各个类型响应具体情况如表 35‑3。

除了R3类型之外,其他响应都使用CRC7校验来校验,对于R2类型是使用CID和CSD寄存器内部CRC7。

表 35‑3 SD卡响应类型

R1(正常响应命令)

描述

起始位

传输位

命令号

卡状态

CRC7

终止位

Bit

47

46

[45:40]

[39:8]

[7:1]

0

位宽

1

1

6

32

7

1

“0”

“0”

x

x

x

“1”

备注

如果有传输到卡的数据,那么在数据线可能有busy信号

R2(CID,CSD寄存器)

描述

起始位

传输位

保留

[127:1]

终止位

Bit

135

134

[133:128]

127

0

位宽

1

1

6

x

1

“0”

“0”

“111111”

CID或者CSD寄存器[127:1]位的值

“1”

备注

CID寄存器内容作为CMD2和CMD10响应,CSD寄存器内容作为CMD9响应。

R3(OCR寄存器)

描述

起始位

传输位

保留

OCR寄存器

保留

终止位

Bit

47

46

[45:40]

[39:8]

[7:1]

0

位宽

1

1

6

32

7

1

“0”

“0”

“111111”

x

“1111111”

“1”

备注

OCR寄存器的值作为ACMD41的响应

R6(发布的RCA寄存器响应)

描述

起始位

传输位

CMD3

RCA寄存器

卡状态位

CRC7

终止位

Bit

47

46

[45:40]

[39:8]

[7:1]

0

位宽

1

1

6

16

16

7

1

“0”

“0”

“000011”

x

x

x

“1”

备注

专用于命令CMD3的响应

R7(发布的RCA寄存器响应)

描述

起始位

传输位

CMD8

保留

接收电压

检测模式

CRC7

终止位

Bit

47

46

[45:40]

[39:20]

[19:16]

[15:8]

[7:1]

0

位宽

1

1

6

20

4

8

7

1

“0”

“0”

“001000”

“00000h”

x

x

x

“1”

备注

专用于命令CMD8的响应,返回卡支持电压范围和检测模式

35.4. SD卡的操作模式及切换¶ 35.4.1. SD卡的操作模式¶

SD卡有多个版本,STM32控制器目前最高支持《Physical Layer Simplified Specification V2.0》定义的SD卡,STM32控制器对SD卡进行数据读写之前需要识别卡的种类:V1.0标准卡、V2.0标准卡、V2.0高容量卡或者不被识别卡。

SD卡系统(包括主机和SD卡)定义了两种操作模式:卡识别模式和数据传输模式。在系统复位后,主机处于卡识别模式,寻找总线上可用的SDIO设备;同时,SD卡也处于卡识别模式,直到被主机识别到,即当SD卡接收到SEND_RCA(CMD3)命令后,SD卡就会进入数据传输模式,而主机在总线上所有卡被识别后也进入数据传输模式。在每个操作模式下,SD卡都有几种状态,参考表 35‑4,通过命令控制实现卡状态的切换。

表 35‑4 SD卡状态与操作模式

操作模式

SD卡状态

无效模式(Inactive)

无效状态(Inactive State)

卡识别模式(Card identification mode)

空闲状态(Idle State)

准备状态(Ready State)

识别状态(Identification State)

数据传输模式(Data transfer mode)

待机状态(Stand-by State)

传输状态(Transfer State)

发送数据状态(Sending-data State)

接收数据状态(Receive-data State)

编程状态(Programming State)

断开连接状态(Disconnect State)

35.4.2. 卡识别模式¶

在卡识别模式下,主机会复位所有处于“卡识别模式”的SD卡,确认其工作电压范围,识别SD卡类型,并且获取SD卡的相对地址(卡相对地址较短,便于寻址)。 在卡识别过程中,要求SD卡工作在识别时钟频率FOD的状态下。卡识别模式下SD卡状态转换如 图35_9。

图 35‑9 卡识别模式状态转换图

主机上电后,所有卡处于空闲状态,包括当前处于无效状态的卡。主机也可以发送GO_IDLE_STATE(CMD0)让所有卡软复位从而进入空闲状态,但当前处于无效状态的卡并不会复位。

主机在开始与卡通信前,需要先确定双方在互相支持的电压范围内。SD卡有一个电压支持范围,主机当前电压必须在该范围可能才能与卡正常通信。SEND_IF_COND(CMD8)命令就是用于验证卡接口操作条件的(主要是电压支持)。卡会根据命令的参数来检测操作条件匹配性,如果卡支持主机电压就产生响应,否则不响应。而主机则根据响应内容确定卡的电压匹配性。CMD8是SD卡标准V2.0版本才有的新命令,所以如果主机有接收到响应,可以判断卡为V2.0或更高版本SD卡。

SD_SEND_OP_COND(ACMD41)命令可以识别或拒绝不匹配它的电压范围的卡。ACMD41命令的VDD电压参数用于设置主机支持电压范围,卡响应会返回卡支持的电压范围。对于对CMD8有响应的卡,把ACMD41命令的HCS位设置为1,可以测试卡的容量类型,如果卡响应的CCS位为1说明为高容量SD卡,否则为标准卡。卡在响应ACMD41之后进入准备状态,不响应ACMD41的卡为不可用卡,进入无效状态。ACMD41是应用特定命令,发送该命令之前必须先发CMD55。

ALL_SEND_CID(CMD2)用来控制所有卡返回它们的卡识别号(CID),处于准备状态的卡在发送CID之后就进入识别状态。之后主机就发送SEND_RELATIVE_ADDR(CMD3)命令,让卡自己推荐一个相对地址(RCA)并响应命令。这个RCA是16bit地址,而CID是128bit地址,使用RCA简化通信。卡在接收到CMD3并发出响应后就进入数据传输模式,并处于待机状态,主机在获取所有卡RCA之后也进入数据传输模式。

35.4.3. 数据传输模式¶

只有SD卡系统处于数据传输模式下才可以进行数据读写操作。数据传输模式下可以将主机SD时钟频率设置为FPP,默认最高为25MHz,频率切换可以通 过CMD4命令来实现。数据传输模式下,SD卡状态转换过程见 图35_10。

图 35‑10 数据传输模式卡状态转换

CMD7用来选定和取消指定的卡,卡在待机状态下还不能进行数据通信,因为总线上可能有多个卡都是出于待机状态,必须选择一个RCA地址目标卡使其进入传输状态才可以进行数据通信。同时通过CMD7命令也可以让已经被选择的目标卡返回到待机状态。

数据传输模式下的数据通信都是主机和目标卡之间通过寻址命令点对点进行的。卡处于传输状态下可以使用表 35‑2中面向块的读写以及擦除命令对卡进行数据读写、擦除。CMD12可以中断正在进行的数据通信,让卡返回到传输状态。CMD0和CMD15会中止任何数据编程操作,返回卡识别模式,这可能导致卡数据被损坏。

35.5. STM32的SDIO功能框图¶

STM32控制器有一个SDIO,由两部分组成:SDIO适配器和AHB接口,见图 35‑11。SDIO适配器提供SDIO主机功能,可以提供SD时钟、发送命令和进行数据传输。AHB接口用于控制器访问SDIO适配器寄存器并且可以产生中断和DMA请求信号。

图 35‑11 SDIO功能框图

SDIO使用两个时钟信号,一个是SDIO适配器时钟(SDIOCLK=HCLK=72MHz),另外一个是AHB总线时钟 的二分频(HCLK/2,一般为36MHz)。适配器 寄存器和FIFO使用AHB总线一侧的时钟(HCLK/2),控制单元、命令通道和数据通道使用SDIO适配器一侧的时钟(SDIOCLK)。

SDIO_CK是SDIO接口与SD卡用于同步的时钟信号。它使用SDIOCLK作为SDIO_CK的时 钟来源,可以通过设置BYPASS模式直接 得到,这时SDIO_CK = SDIOCLK=HCLK。若禁止BYPASS模式,可以通过配置时钟寄存器的CLKDIV位控制分 频因子,即SDIO_CK=SDIOCLK/(2+CLKDIV)= HCLK/(2+CLKDIV)。配置时钟时要注意,SD卡普遍要求SDIO_CK时钟频率不能超过25MHz。

STM32控制器的SDIO是针对MMC卡和SD卡的主设备,所以预留有8根数据线,对于SD卡最多用四根数据线。

SDIO适配器是SD卡系统主机部分,是STM32控制器与SD卡数据通信中间设备。SDIO适配器由五个单元组成,分别是控制单元、命令路径单元、数据路径单元、寄存器单元以及FIFO,见图 35‑12。

图 35‑12 SDIO适配器框图

35.5.1. 控制单元¶

控制单元包含电源管理和时钟管理功能,结构如图 35‑13。电源管理部件会在系统断电和上电阶段禁止SD卡总线输出信号。时钟管理部件控制CLK线时钟信号生成。一般使用SDIOCLK分频得到。

图 35‑13 SDIO适配器控制单元

35.5.2. 命令路径¶

命令路径控制命令发送,并接收卡的响应,结构见 图35_14。

图 35‑14 SDIO适配器命令路径

关于SDIO适配器状态转换流程可以参考 图35_9,当SD卡处于某一状态时,SDIO适配器必然处于特定状态与之对应。STM32控制器以命令路径状态机(CPSM)来描述SDIO适配器的状态变化,并加入了等待 超时检测功能,以便退出永久等待的情况。CPSM的描述见 图35_15。

图 35‑15 CPSM状态机描述图

35.5.3. 数据路径¶

数据路径部件负责与SD卡相互数据传输,内部结构见 图35_16。

图 35‑16 SDIO适配器数据路径

SD卡系统数据传输状态转换参考 图35_10,SDIO适配器以数据路 径状态机(DPSM)来描述SDIO适配器状态变化情况。并加入了等待超时检测功能,以便退出永久 等待情况。发送数据时,DPSM处于等待发送(Wait_S)状态,如果数据FIFO不为空,DPSM变成发送 状态并且数据路径部件启动向卡发送数据。接收数据时,DPSM处于等待接收状态,当DPSM收到起始位 时变成接收状态,并且数据路径部件开始从卡接收数据。DPSM状态机描述见 图35_17。

图 35‑17 DPSM状态机描述图

35.5.4. 数据FIFO¶

数据FIFO(先进先出)部件是一个数据缓冲器,带发送和接收单元。控制器的FIFO包含宽度为32bit、深度为32字的数据缓冲器和发送/接收逻辑。其中SDIO状态寄存器(SDIO_STA)的TXACT位用于指示当前正在发送数据,RXACT位指示当前正在接收数据,这两个位不可能同时为1。

当TXACT为1时,可以通过AHB接口将数据写入到传输FIFO。

当RXACT为1时,接收FIFO存放从数据路径部件接收到的数据。

根据FIFO空或满状态会把SDIO_STA寄存器位值1,并可以产生中断和DMA请求。

35.5.5. 适配器寄存器¶

适配器寄存器包含了控制SDIO外设的各种控制寄存器及状态寄存器,内容较多,可以通过SDIO提供的各种结构体来了解,这些寄存器的功能都被整合到了结构体或STM32HAL库之中。

35.6. SDIO初始化结构体¶

HAL库函数对SDIO外设建立了三个初始化结构体,分别为SDIO初始化结构体SDIO_InitTypeDef、SDIO命令初始化结构体SDIO_CmdInitTypeDef和SDIO数据初始化结构体SDIO_DataInitTypeDef。这些结构体成员用于设置SDIO工作环境参数,并由SDIO相应初始化配置函数或功能函数调用,这些参数将会被写入到SDIO相应的寄存器,达到配置SDIO工作环境的目的。

初始化结构体和初始化库函数配合使用是HAL库精髓所在,理解了初始化结构体每个成员意义基本上就可以对该外设运用自如了。初始化结构体定义在stm32f1xx_hal_sd.h文件中,初始化库函数定义在stm32f1xx_hal_sdio.c文件中,编程时我们可以结合这两个文件内注释使用。

SDIO初始化结构体用于配置SDIO基本工作环境,比如时钟分频、时钟沿、数据宽度等等。它被SDIO_Init函数使用。

代码清单 35‑1 SDIO初始化结构体¶ typedef struct { uint32_t ClockEdge; // 时钟沿 uint32_t ClockBypass; // 旁路时钟 uint32_t ClockPowerSave; // 节能模式 uint32_t BusWide; // 数据宽度 uint32_t HardwareFlowControl; // 硬件流控制 uint32_t ClockDiv; // 时钟分频 } SDIO_InitTypeDef;

各结构体成员的作用介绍如下:

ClockEdge:主时钟SDIOCLK产生CLK引脚时钟有效沿选择,可选上升沿或下降沿,它设定SDIO时钟控 制寄存器(SDIO_CLKCR)的NEGEDGE位的值,一般选择设置为高电平。

ClockBypass:时钟分频旁路使用,可选使能或禁用,它设定SDIO_CLKCR寄存器的BYPASS位。如果使能 旁路,SDIOCLK直接驱动CLK线输出时钟;如果禁用,使用SDIO_CLKCR寄存器的CLKDIV位值分频SDIOCLK,然后输出到CLK线。一般选择禁用时钟分频旁路。

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

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

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

ClockDiv:时钟分频系数,它设定SDIO_CLKCR寄存器的CLKDIV位的值,设置SDIOCLK与CLK线输出时钟分频系数:

CLK线时钟频率=SDIOCLK/([CLKDIV+2])。

35.7. SDIO命令初始化结构体¶

SDIO命令初始化结构体用于设置命令相关内容,比如命令号、命令参数、响应类型等等。

代码清单 35‑2 SDIO命令初始化接口¶ typedef struct { uint32_t Argument; // 命令参数 uint32_t CmdIndex; // 命令号 uint32_t Response; // 响应类型 uint32_t WaitForInterrupt; // 等待使能 uint32_t CPSM; // 命令路径状态机 } SDIO_CmdInitTypeDef;

各个结构体成员介绍如下:

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

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

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

Wait:等待类型选择,有三种状态可选,一种是无等待状态,超时检测功能启动;一种是等待中断,另外一种 是等待传输完成。它设定SDIO_CMD寄存器的WAITPEND位和WAITINT位的值。

CPSM:命令路径状态机控制,可选使能或禁用CPSM。它设定SDIO_CMD寄存器的CPSMEN位的值。

35.8. SDIO数据初始化结构体¶

SDIO数据初始化结构体用于配置数据发送和接收参数,比如传输超时、数据长度、传输模式等等。

代码清单 35‑3 SDIO数据初始化结构体¶ typedef struct { uint32_t DataTimeOut; // 数据传输超时 uint32_t DataLength; // 数据长度 uint32_t DataBlockSize; // 数据块大小 uint32_t TransferDir; // 数据传输方向 uint32_t TransferMode; // 数据传输模式 uint32_t DPSM; // 数据路径状态机 } SDIO_DataInitTypeDef;

各结构体成员介绍如下:

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

DataLength:设置传输数据长度,它设定SDIO数据长度寄存器(SDIO_DLEN)的值。

DataBlockSize:设置数据块大小,有多种尺寸可选,不同命令要求的数据块可能不同。它设定SDIO数据 控制寄存器(SDIO_DCTRL)寄存器的DBLOCKSIZE位的值。

TransferDir:数据传输方向,可选从主机到卡的写操作,或从卡到主机的读操作。它设定SDIO_DCTRL寄存器的DTDIR位的值。

TransferMode:数据传输模式,可选数据块或数据流模式。对于SD卡操作使用数据块类型。它设定SDIO_DCTRL寄存器的DTMODE位的值。

DPSM:数据路径状态机控制,可选使能或禁用DPSM。它设定SDIO_DCTRL寄存器的DTEN位的值。要实现数据传输都必须使能SDIO_DPSM。

35.9. SD卡读写测试实验¶

SD卡广泛用于便携式设备上,比如数码相机、手机、多媒体播放器等。对于嵌入式设备来说是一种重要的存储数据部件。类似于SPI Flash芯片数据操作,可以直接进行读写,也可以写入文件系统,然后使用文件系统读写函数,使用文件系统操作。本实验是进行SD卡最底层的数据读写操作,直接使用SDIO对SD卡进行读写,会损坏SD卡原本内容,导致数据丢失,实验前请注意备份SD卡的原内容。由于SD卡容量很大,我们平时使用的SD卡都是已经包含有文件系统的,一般不会使用本章的操作方式编写SD卡的应用,但它是SD卡操作的基础,对于原理学习是非常有必要的,在它的基础上移植文件系统到SD卡的应用将在下一章讲解。

35.9.1. 硬件设计¶

STM32控制器的SDIO引脚是被设计固定不变的,开发板设计采用四根数据线模式。对于命令线和数据线须需要加一个上拉电阻。

图 35‑18a 指南者和霸道开发板的SD卡硬件设计(SDIO接口)

图 35‑18b MINI开发板的SD卡硬件设计(SPI接口)

35.9.2. 软件设计¶

这里只讲解核心的部分代码,有些变量的设置,头文件的包含等没有全部罗列出来,完整的代码请参考本章配套的工程。有了之前相关SDIO知识基础,我们就可以着手开始编写SD卡驱动程序了,根据之前内容,可了解操作的大概流程:

初始化相关GPIO及SDIO外设;

配置SDIO基本通信环境进入卡识别模式,通过几个命令处理后得到卡类型;

如果是可用卡就进入数据传输模式,接下来就可以进行读、写、擦除的操作。

虽然看起来只有三步,但它们有非常多的细节需要处理。实际上,SD卡是非常常用外设部件,ST公司在其测试板上也有板子SD卡卡槽,并提供了完整的驱动程序,我们直接参考移植使用即可。类似SDIO、USB这些复杂的外设,它们的通信协议相当庞大,要自行编写完整、严谨的驱动不是一件轻松的事情,这时我们就可以利用ST官方例程的驱动文件,根据自己硬件移植到自己开发平台即可。

在“初识STM32 HAL库”章节我们重点讲解了HAL库的源代码及启动文件和库使用帮助文档这两部分内容,实际上“Utilities”文件夹内容是非常有参考价值的,该文件夹包含了基于ST官方实验板的驱动文件,比如LCD、SDRAM、SD卡、音频解码IC等等底层驱动程序, 另外还有第三方软件库,如emWin图像软件库和FatFs文件系统。虽然,我们的开发平台跟ST官方实验平台硬件设计略有差别,但移植程序方法是完全可行的。学会移植程序可以减少很多工作量,加快项目进程,更何况ST官方的驱动代码是经过严格验证的。

在“STM32Cube_FW_F4_V1.19.0DriversBSP”文件路径下可以知道SD卡驱动文件,见图 35‑19。我们需要stm32746g_discovery_sd.c和stm32746g_discovery_sd.h两个文件的完整内容。另外还需要stm32746g_discovery.c和stm32746g_discovery.h两个文件的部分代码内容,为简化工程,本章配置工程代码是将这两个文件需要用到的内容移植到stm32746g_discovery_sd.c文件中,具体可以参考工程文件。

图 35‑19 ST官方实验板SD卡驱动文件

我们把stm32746g_discovery_sd.c和stm32746g_discovery_sd.h两个文件拷贝到我们的工程文件夹中,并将其对应改名为bsp_sdio_sd.c和bsp_sdio_sd.h,见 图35_20。另外,sdio_test.c和sdio_test.h文件包含了SD卡读、写、擦除测试代码。

图 35‑20 SD卡驱动文件

35.9.2.1. GPIO初始化和DMA配置¶

SDIO用到CLK线、CMD线和4根DAT线,使用之前必须初始化相关的GPIO,并设置复用模式为SDIO的类型。而SDIO外设又支持生成DMA请求,使用DMA传输可以提高数据传输效率,因此在SDIO的控制代码中,可以把它设置为DMA传输模式或轮询模式,STM32HAL库提供SDIO示例中针对这两个模式做了区分处理。由于应用中一般都使用DMA传输模式,所以接下来代码分析都以DMA传输模式介绍。

35.9.2.1.1. SDIO模式、地址及时钟分频配置相关的宏定义¶ 代码清单 35‑4 SDIO模式、地址及时钟分频配置相关的宏定义(bsp_sdio_sdcard.h文件)¶ #define BSP_SD_CardInfo HAL_SD_CardInfoTypeDef #define MSD_OK ((uint8_t)0x00) #define MSD_ERROR ((uint8_t)0x01) /** * @brief SD transfer state definition */ #define SD_TRANSFER_OK ((uint8_t)0x00) #define SD_TRANSFER_BUSY ((uint8_t)0x01) #define SD_DETECT_PIN GPIO_PIN_11 #define SD_DETECT_GPIO_PORT GPIOF #define __SD_DETECT_GPIO_CLK_ENABLE() __HAL_RCC_GPIOF_CLK_ENABLE() #define SD_DETECT_IRQn EXTI15_10_IRQn #define SD_DATATIMEOUT 100000000U #define SD_PRESENT ((uint8_t)0x01) #define SD_NOT_PRESENT ((uint8_t)0x00) /* DMA definitions for SD DMA transfer */ #define __DMAx_TxRx_CLK_ENABLE __HAL_RCC_DMA2_CLK_ENABLE #define SD_DMAx_Tx_INSTANCE DMA2_Channel4 #define SD_DMAx_Rx_INSTANCE DMA2_Channel4 #define SD_DMAx_Tx_IRQn DMA2_Channel4_5_IRQn #define SD_DMAx_Rx_IRQn DMA2_Channel4_5_IRQn #define BSP_SD_IRQHandler SDIO_IRQHandler #define SD_DMAx_Tx_IRQHandler DMA2_Channel4_5_IRQHandler #define SD_DMAx_Rx_IRQHandler DMA2_Channel4_5_IRQHandler 35.9.2.1.2. DMA传输配置¶ 代码清单 35‑5 DMA传输配置(bsp_sdio_sdcard.c文件)¶ uint8_t SD_DMAConfigRx(SD_HandleTypeDef *hsd) { static DMA_HandleTypeDef hdma_rx; HAL_StatusTypeDef status = HAL_ERROR; if (hsd->hdmarx == NULL) { /* Configure DMA Rx parameters */ hdma_rx.Init.Direction = DMA_PERIPH_TO_MEMORY; hdma_rx.Init.PeriphInc = DMA_PINC_DISABLE; hdma_rx.Init.MemInc = DMA_MINC_ENABLE; hdma_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_WORD; hdma_rx.Init.MemDataAlignment = DMA_MDATAALIGN_WORD; hdma_rx.Init.Priority = DMA_PRIORITY_VERY_HIGH; hdma_rx.Instance = SD_DMAx_Rx_INSTANCE; /* Associate the DMA handle */ __HAL_LINKDMA(hsd, hdmarx, hdma_rx); /* Stop any ongoing transfer and reset the state*/ HAL_DMA_Abort(&hdma_rx); /* Deinitialize the Channel for new transfer */ HAL_DMA_DeInit(&hdma_rx); /* Configure the DMA Channel */ status = HAL_DMA_Init(&hdma_rx); /* NVIC configuration for DMA transfer complete interrupt */ HAL_NVIC_SetPriority(SD_DMAx_Rx_IRQn, 0xD, 0); HAL_NVIC_EnableIRQ(SD_DMAx_Rx_IRQn); } else { status = HAL_OK; } return (status != HAL_OK? MSD_ERROR : MMSD_OK); } /** * @brief SD_DMAConfigTx * @par Function Description * This function configure the DMA to transmit data to the SD card * @retval * SD_ERROR or MSD_OK */ uint8_t SD_DMAConfigTx(SD_HandleTypeDef *hsd) { static DMA_HandleTypeDef hdma_tx; HAL_StatusTypeDef status; if (hsd->hdmatx == NULL) { /* Configure DMA Tx parameters */ hdma_tx.Init.Direction = DMA_MEMORY_TO_PERIPH; hdma_tx.Init.PeriphInc = DMA_PINC_DISABLE; hdma_tx.Init.MemInc = DMA_MINC_ENABLE; hdma_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_WORD; hdma_tx.Init.MemDataAlignment = DMA_MDATAALIGN_WORD; hdma_tx.Init.Priority = DMA_PRIORITY_VERY_HIGH; hdma_tx.Instance = SD_DMAx_Tx_INSTANCE; /* Associate the DMA handle */ __HAL_LINKDMA(hsd, hdmatx, hdma_tx); /* Stop any ongoing transfer and reset the state*/ HAL_DMA_Abort(&hdma_tx); /* Deinitialize the Channel for new transfer */ HAL_DMA_DeInit(&hdma_tx); /* Configure the DMA Channel */ status = HAL_DMA_Init(&hdma_tx); /* NVIC configuration for DMA transfer complete interrupt */ HAL_NVIC_SetPriority(SD_DMAx_Tx_IRQn, 0xD, 0); HAL_NVIC_EnableIRQ(SD_DMAx_Tx_IRQn); } else { status = HAL_OK; } return (status != HAL_OK? MSD_ERROR : MMSD_OK); }

SD_DMA_RxConfig函数用于配置DMA的SDIO接收请求参数,并指定接收存储器地址和大小。SD_DMA_TxConfig函数用于配置DMA的SDIO发送请求参数,并指定发送存储器地址和大小。这两个函数在SDIO数据传输时会被调用来对DMA进行配置,使得传输过程采用DMA搬运数据。函数有两个输入参数,BufferSRC在接收时用于指定接收到的数据存储的内存地址,发送时用于指定要对外发送的数据的内存地址;而BufferSize用于指定数据的大小,它是32个字节的,所以在配置DMA传输的数据大小时,要把它由字的单位转换成字节,也就是1/4大小;另外,DMA配置中的外设地址在接收和发送时都是SDIO的FIFO,在代码中使用了宏SDIO_FIFO_ADDRESS来定义。接收和发送函数非常类似,只是数据的方向和来源不一样而已,对于DMA相关配置的详细解释可以参考DMA章节内容。

35.9.2.2. 相关类型定义¶

打开bsp_sdio_sdcard.h文件可以发现有非常多的枚举类型定义、结构体类型定义以及宏定义,把所有的定义在这里罗列出来不太现实,这部分代码内容请直接打开工程查看,针对这些内容在此处简要介绍如下:

枚举类型定义:有MSD_ERROR、SDTransferState和SDCardState三个。MSD_ERROR是列举了控制器可能出 现的错误、比如CRC校验错误、CRC校验错误、通信等待超时、FIFO上溢或下溢、擦除命令错误等等。这些错误类型部分是控制器系统寄存器的标志位,部分是通过命令的响应内容得到的。SDTransferState定义了SDIO传输状态,有传输正常状态、传输忙状态和传输错误状态。SDCardState定义卡的当前状态,比如准备状态、识别状态、待机状态、传输状态等等,具体状态转换过程参考图 35‑9和图 35‑10。

结构体类型定义:有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和卡类型成员。

宏定义内容:包含有命令号定义、SDIO传输方式、SD卡插入状态以及SD卡类型定义。参考表 35‑2列举了描述了部分命令,文件中为每个命令号定义一个宏,比如将复位CMD0定义为SD_CMD_GO_IDLE_STATE,这与表 35‑2中缩写部分是类似的,所以熟悉命名用途可以更好理解SD卡操作过程。SDIO数据传输可以选择是否使用DMA传输,前面提到的SD_DMA_MODE和SD_POLLING_MODE就定义在这里,两种方式只能二选一使用,为提高系统性能,一般使用DMA传输模式。接下来还定义了检测SD卡是否正确插入的宏SD_PRESENT和SD_NOT_PRESENT,ST官方的原SD卡驱动是以一个输入引脚电平判断SD卡是否正确插入,由于我们的硬件没有使用该引脚,所以我们的程序里把ST驱动中原来的引脚检测部分代码删除掉了,但保留了SD_PRESENT和SD_NOT_PRESENT两个宏定义。最后定义SD卡具体的类型,有V1.1版本标准卡、V2.0版本标准卡、高容量SD卡以及其他类型卡,前面三个是常用的类型。

在bsp_sdio_sdcard.c文件也有部分宏定义,这部分宏定义只能在该文件中使用。这部分宏定义包括命令超时时间定义、OCR寄存器位掩码、R6响应位掩码等等,这些定义更多是为提取特定响应位内容而设计的掩码。

因为类型定义和宏定义内容没有在本文中列举出来,读者有必要使用KEIL工具打开本章配套例程理解清楚。同时了解bsp_sdio_sdcard.c文件中定义的多个不同类型变量。

接下来我们就开始根据SD卡识别过程和数据传输过程理解SD卡驱动函数代码。这部分代码内容也是非常庞大,不可能全部在文档中全部列出,对于部分函数只介绍其功能。

35.9.2.3. SD卡初始化¶

SD卡初始化过程主要是卡识别和相关SD卡状态获取。整个初始化函数可以实现 图35_21 中的功能。

35.9.2.3.1. SD卡初始化函数¶ 代码清单 35‑6 SD_Init函数¶ uint8_t BSP_SD_Init(void) { uint8_t state = MMSD_OK; /*SD设备接口配置 */ uSdHandle.Instance = SDIO; uSdHandle.Init.ClockEdge = SDIO_CLOCK_EDGE_RISING; uSdHandle.Init.ClockBypass = SDIO_CLOCK_BYPASS_DISABLE; uSdHandle.Init.ClockPowerSave = SDIO_CLOCK_POWER_SAVE_DISABLE; uSdHandle.Init.BusWide = SDIO_BUS_WIDE_1B; uSdHandle.Init.HardwareFlowControl = SDIO_HARDWARE_FLOW_CONTROL_DISABLE; uSdHandle.Init.ClockDiv = SDIO_TRANSFER_CLK_DIV; /* SD初始化 */ BSP_SD_MspInit(NULL); if (HAL_SD_Init(&uSdHandle) != HAL_OK) { state = MSD_ERROR; } /* 配置SD总线宽度 */ if (state == MMSD_OK) { if (HAL_SD_ConfigWideBusOperation(&uSdHandle, SDIO_BUS_WIDE_4B) != HAL_OK) { state = MSD_ERROR; } else { state = MMSD_OK; } } return state; }

该函数的部分执行流程如下:

配置NVIC,SD卡通信用到SDIO中断,如果用到DMA传输还需要配置DMA中断。中断服务函数SDIO_IRQHandler定义 在stm32f1xx_it.c文件的,为了移植方便,您也可以把它直接定义在bsp_sdio_sdcard.c文件中,中断服务函数定义在哪个文件问题都不大,只要定义正确就可以的,编译器会自动寻找,只是在移植的时候,要注意别漏掉该函数。

执行GPIO_Configuration函数,其功能是对底层SDIO引脚进行初始化以及开启相关时钟,该函数在之前已经讲解。

SDIO_DeInit函数用于解除初始化SDIO接口,它是与GPIO_Configuration函数相反功能,关闭相关时钟,关闭SDIO电源, 让SDIO接近上电复位状态。恢复复位状态后再进行相关配置,可以防止部分没有配置的参数采用非默认值而导致错误,这是ST官方驱动常用的一种初始化方式。

调用SD_PowerON函数,它用于查询卡的工作电压和时钟控制配置,并返回MSD_ERROR类型错误,该函数是整个SD识别精髓,有必要详细分析。

35.9.2.3.2. SD_POWERON函数¶ 代码清单 35‑7 SD_POWERON函数¶ /* * 函数名:SD_PowerON * 描述 :确保SD卡的工作电压和配置控制时钟 * 输入 :无 * 输出 :-MSD_ERROR SD卡错误代码 * 成功时则为 MSD_OK * 调用 :在 SD_Init() 调用 */ MSD_ERROR SD_PowerON(void) { MSD_ERROR errorstatus = MMSD_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 != MSD_OK) { /* CMD 响应超时 */ 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 == MSD_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 为 MSD_OK ,说明是SD card: SD 卡 2.0 (电压范围不匹配) * 或 SD 卡 1.x */ if (errorstatus == MSD_OK) { //响应了cmd55,是sd卡,可能为1.x,可能为2.0 /*下面开始循环地发送sdio支持的电压范围,循环一定次数*/ /* SD CARD * 发送 ACMD41 SD_APP_OP_COND ,带参数 0x80100000 */ while ((!validvoltage) && (count > 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); }

SD_POWERON函数执行流程如下:

配置SDIO_InitStructure结构体变量成员并调用SDIO_Init库函数完成SDIO外设的基 本配置,注意此处的SDIO时钟分频,由于处于卡识别阶段,其时钟不能超过400KHz。

调用SDIO_SetPowerState库函数控制SDIO的电源状态,给SDIO提供电源,并调用SDIO_ClockCmd库函数使能SDIO时钟。

发送命令给SD卡,首先发送CMD0,复位所有SD卡, CMD0命令无需响应,所以调用CmdError函数检测错误即可。CmdError函数用于无需响应的命令发送检测,带有等待超时检测功能,它通过不断检测SDIO_STA寄存器的CMDSENT位即可知道命令发送成功与否。如果遇到超时错误则直接退出SD_PowerON函数。如果无错误则执行下面程序。

发送CMD8命令,检测SD卡支持的操作条件,主要就是电压匹配,CMD8的响应类型是R7,使用 CmdResp7Error函数可获取得到R7响应结果,它是通过检测SDIO_STA寄存器相关位完成的,并具有等待超时检测功能。如果CmdResp7Error函数返回值为MSD_OK,即CMD8有响应,可以判定SD卡为V2.0及以上的高容量SD卡,如果没有响应可能是V1.1版本卡或者是不可用卡。

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

(6) 执行SD_PowerON函数无错误后就已经确定了SD卡类型,并说明卡和主机电压是匹配的, SD卡处于卡识别模式下的准备状态。退出SD_PowerON函数返回SD_Init函数,执行接下来代码。 判断执行SD_PowerON函数无错误后,执行下面的SD_InitializeCards函数进行与SD卡相关的初始化, 使得卡进入数据传输模式下的待机模式。

35.9.2.3.3. SD_InitializeCards函数¶ 代码清单 35‑8 SD_InitializeCards函数¶ /* * 函数名:SD_InitializeCards * 描述 :初始化所有的卡或者单个卡进入就绪状态 * 输入 :无 * 输出 :-MSD_ERROR SD卡错误代码 * 成功时则为 MSD_OK *调用:在 SD_Init() 调用,在调用power_on()上电卡识别完毕后,调用此函数进行卡初始化 */ MSD_ERROR SD_InitializeCards(void) { MSD_ERROR errorstatus = MSD_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 (MSD_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的卡 */ /* 发送 CMD3 SET_REL_ADDR ,带参数 0 * 要求各个SD卡返回自身的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 (MSD_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 20) & SD_CCCC_ERASE) == 0) { errorstatus = SD_REQUEST_NOT_APPLICABLE; return (errorstatus); } //延时,根据时钟分频设置来计算 maxdelay = 120000 / ((SDIO->CLKCR & 0xFF) + 2); if (SDIO_GetResponse(SDIO_RESP1) & SD_CARD_LOCKED) { //卡已上锁 errorstatus = SD_LOCK_UNLOCK_FAILED; return (errorstatus); } if (CardType == SDIO_HIGH_CAPACITY_SD_CARD) { //sdhc卡 //在sdhc卡中,地址参数为块地址,每块512字节,而sdsc卡地址为字节地址 //所以若是sdhc卡要对地址/512进行转换 startaddr /= 512; endaddr /= 512; } /*!< ERASE_GROUP_START (CMD32)设置擦除的起始地址, erase_group_end(CMD33)设置擦除的结束地址, */ if ((SDIO_STD_CAPACITY_SD_CARD_V1_1 == CardType) || (SDIO_STD_CAPACITY_SD_CARD_V2_0 == CardType) || (SDIO_HIGH_CAPACITY_SD_CARD == CardType)) { /*!< 发送命令 CMD32 SD_ERASE_GRP_START ,带参数startaddr */ SDIO_CmdInitStructure.SDIO_Argument = startaddr; SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_SD_ERASE_GRP_START; SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short; //R1 SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No; SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable; SDIO_SendCommand(&SDIO_CmdInitStructure); errorstatus = CmdResp1Error(SD_CMD_SD_ERASE_GRP_START); if (errorstatus != MSD_OK) { return (errorstatus); } /*!< 发送命令 CMD33 SD_ERASE_GRP_END ,带参数endaddr */ SDIO_CmdInitStructure.SDIO_Argument = endaddr; SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_SD_ERASE_GRP_END; 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_SD_ERASE_GRP_END); if (errorstatus != MSD_OK) { return (errorstatus); } } /*!< 发送 CMD38 ERASE 命令,开始擦除*/ SDIO_CmdInitStructure.SDIO_Argument = 0; SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_ERASE; 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_ERASE); if (errorstatus != MSD_OK) { return (errorstatus); } for (delay = 0; delay CMD33->CMD38。如果发送顺序不对,SD卡会设置ERASE_SEQ_ERROR位到状态寄存器。

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

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

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

35.9.2.4.2. 数据写入操作¶

数据写入可分为单块数据写入和多块数据写入,这里只分析单块数据写入,多块的与之类似。SD卡数据写入之前并没有硬性要求擦除写入块,这与SPI Flash芯片写入是不同的。ST官方的SD卡写入函数包括扫描查询方式和DMA传输方式,我们这里只介绍DMA传输模式。

代码清单 35‑10 SD_WriteBlock函数¶ /** * @brief 向sd卡写入一个BLOCK的数据(512字节) * @note 本函数使用后需要调用如下两个函数来等待数据传输完成 * - SD_WaitWriteOperation(): 确认DMA已把数据传输到SDIO接口 * - SD_GetStatus(): 确认SD卡内部已经把数据写入完毕 * @param writebuff: 指向要写入的数据 * @param WriteAddr: 要把数据写入到sd卡的地址 * @param BlockSize: 块大小,sdhc卡为512字节 * @retval MSD_ERROR: 返回的sd错误代码 */ MSD_ERROR SD_WriteBlock(uint8_t *writebuff, uint32_t WriteAddr, uint16_t BlockSize) { MSD_ERROR errorstatus = MSD_OK; TransferError = MSD_OK; TransferEnd = 0; StopCondition = 0; SDIO->DCTRL = 0x0; if (CardType == SDIO_HIGH_CAPACITY_SD_CARD) { BlockSize = 512; WriteAddr /= 512; } /*----以下这段是在st驱动库上添加的 , 没有这一段容易卡死在DMA检测中 ---*/ /* 设置块BLOCK的大小,cmd16, * 若是sdsc卡,可以用来设置块大小, * 若是sdhc卡,块大小为512字节,不受cmd16影响 */ SDIO_CmdInitStructure.SDIO_Argument = (uint32_t) BlockSize; SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_SET_BLOCKLEN; 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_SET_BLOCKLEN); if (MSD_OK != errorstatus) { return (errorstatus); } /********************************************************/ /*!< 发送 CMD24 WRITE_SINGLE_BLOCK 写入 */ SDIO_CmdInitStructure.SDIO_Argument = WriteAddr; //写入地址 SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_WRITE_SINGLE_BLOCK; SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short; //r1 SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No; SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable; SDIO_SendCommand(&SDIO_CmdInitStructure); errorstatus = CmdResp1Error(SD_CMD_WRITE_SINGLE_BLOCK); if (errorstatus != MSD_OK) { return (errorstatus); } //配置sdio的写数据寄存器 SDIO_DataInitStructure.SDIO_DataTimeOut = SD_DATATIMEOUT; SDIO_DataInitStructure.SDIO_DataLength = BlockSize; SDIO_DataInitStructure.SDIO_DataBlockSize = (uint32_t) 9


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有