SD卡讲解

SD卡

SD 卡 (Secure Digital Memory Card) 在我们生活中已经非常普遍了,控制器对 SD 卡进行读写通信 操作一般有两种通信接口可选,一种是 SPI 接口,另外一种就是 SDIO 接口。

SDIO 全称是安全数 字输入/输出接口,多媒体卡 (MMC)、SD 卡、SD I/O 卡都有 SDIO 接口。stm32f4xx 系列控制器 有一个 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 是专为轻薄笔记本硬盘设计的硬盘高速通讯 接口。

SD卡按容量分类,可以分为3类:SD卡、SDHC卡、SDXC卡,如下表所示:

SD 卡物理结构

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

SD 卡总共有 8 个寄存器,用于设定或表示 SD 卡信息,参考表 SD 卡寄存器。

这些寄存器只能通 过对应的命令访问,对 SD 卡进行控制操作并不是像操作控制器 GPIO 相关寄存器那样一次读写 一个寄存器的,它是通过命令来控制,

SDIO 定义了 64 个命令,每个命令都有特殊意义,可以实 现某一特定功能,SD 卡接收到命令后,根据命令要求对 SD 卡内部寄存器进行修改,程序控制 中只需要发送组合命令就可以实现 SD 卡的控制以及读写操作。

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

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

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

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

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

SDIO 的通信时序简单许多,SDIO 不管是从主机控制器向 SD 卡传输,还是 SD 卡向主机控制器传输都只以 CLK 时钟线 的上升沿为有效。

SD 卡操作过程会使用两种不同频率的时钟同步数据,一个是识别卡阶段时钟 频率 FOD,最高为 400kHz,另外一个是数据传输模式下时钟频率 FPP,默认最高为 25MHz,如 果通过相关寄存器配置使 SDIO 工作在高速模式,此时数据传输模式最高频率为 50MHz。

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

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

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

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

SD 卡忙状态通过把 D0 线拉低表示。

数据块读操作与之类似,只是无需忙状态检测。 使用 4 数据线传输时,每次传输 4bit 数据,每根数据线都必须有起始位、终止位以及 CRC 位, CRC 位每根数据线都要分别检查,并把检查结果汇总然后在数据传输完后通过 D0 线反馈给主机。

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

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

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


命令

SD 命令由主机发出,以广播命令和寻址命令为例,广播命令是针对与 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 卡不执行命令。


命令类型

SD 命令有 4 种类型:

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

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

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

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

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


命令描述

SD 卡系统的命令被分为多个类,每个类支持一种“卡的功能设置”。表 SD 部分命令描述 列举了 SD 卡部分命令信息,更多详细信息可以参考 SD 简易规格文件说明,表中填充位和保留位都必 须被设置为 0。 虽然没有必须完全记住每个命令详细信息,但越熟悉命令对后面编程理解非常有帮助.


响应

响应由 SD 卡向主机发出,部分命令要求 SD 卡作出响应,这些响应多用于反馈 SD 卡的状态。

SDIO 总共有 7 个响应类型 (代号:R1~R7),其中 SD 卡没有 R4、R5 类型响应。

特定的命令对应 有特定的响应类型,比如当主机发送 CMD3 命令时,可以得到响应 R6。与命令一样,SD 卡的 响应也是通过 CMD 线连续传输的。

根据响应内容大小可以分为短响应和长响应。短响应是 48bit 长度,只有 R2 类型是长响应,其长度为 136bit。各个类型响应具体情况如表 SD 卡响应类型。 除了 R3 类型之外,其他响应都使用 CRC7 校验来校验,对于 R2 类型是使用 CID 和 CSD 寄存器 内部 CRC7。

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 卡都有几种状态,

SD的初始化

从SD卡初始化流程可知,不管什么卡(这里我们将卡分为4类:SD2.0高容量卡(SDHC,最大32G),SD2.0标准容量卡(SDSC,最大2G),SD1.x卡和MMC卡)。

首先我们要执行的是卡上电(设置SDIO_POWER[1:0]=11),上电后发送CMD0,对卡进行软复位,主机上电后, 所有卡处于空闲状态,包括当前处于无效状态的卡。

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

之后发送CMD8命令,用于区分SD卡2.0,只有2.0及以后的卡才支持CMD8命令,MMC卡和V1.x的卡,是不支持该命令的。

SEND_IF_COND(CMD8) 命令就是用于验证 卡接口操作条件的 (主要是电压支持)。卡会根据命令的参数来检测操作条件匹配性,如果卡支持 主机电压就产生响应,否则不响应。

而主机则根据响应内容确定卡的电压匹配性。CMD8 是 SD 卡标准 V2.0 版本才有的新命令,所以如果主机有接收到响应,可以判断卡为 V2.0 或更高版本 SD 卡。

CMD8命令格式如下表:

在发送CMD8的时候,通过其带的参数我们可以设置VHS位,以告诉SD卡,主机的供电情况,让SD卡知道主机的供电范围。

VHS位定义如下表所示:

这里我们使用参数0X1AA,即告诉SD卡,主机供电为2.7~3.6V之间,如果SD卡支持CMD8,且支持该电压范围,则会通过CMD8的响应(R7,关于SD卡响应,请参考《SD卡2.0协议.pdf》第4.9节)将参数部分原本返回给主机,如果不支持CMD8,或者不支持这个电压范围,则不响应。

在发送CMD8后,发送ACMD41(注意:发送ACMD41之前,要先发送CMD55),来进一步确认卡的操作电压范围,并通过HCS位来告诉SD卡,主机是不是支持高容量卡(SDHC)

SD_SEND_OP_COND(ACMD41) 命令可以识别或拒绝不匹配它的电压范围的卡。ACMD41 命令 的 VDD 电压参数用于设置主机支持电压范围,卡响应会返回卡支持的电压范围。

对于对 CMD8 有响应的卡,把 ACMD41 命令的 HCS 位设置为 1,可以测试卡的容量类型,如果卡响应的 CCS 位 为 1 说明为高容量 SD 卡,否则为标准卡。卡在响应 ACMD41 之后进入准备状态,不响应 ACMD41 的卡为不可用卡,进入无效状态。ACMD41 是应用特定命令,发送该命令之前必须先发 CMD55。

ACMD41命令格式如下表所示:

ACMD41指令响应(R3),包含了SD卡OCR寄存器内容,其定义如下表所示:

对于支持CMD8的卡,主机设置ACMD41的参数HCS=1,告诉SD卡,主机支持SDHC卡。

对2.0的卡,OCR的CCS位用于表示SDHC还是SDSC;对1.x的卡,则忽略该位;

对MMC卡,则不支持ACMD41,MMC卡只需要发送:CMD0和CMD1即可完成初始化。

ALL_SEND_CID(CMD2) 用来控制所有卡返回它们的卡识别号 (CID),处于准备状态的卡在发送 CID 之后就进入识别状态。

之后主机就发送 SEND_RELATIVE_ADDR(CMD3) 命令,让卡自己推荐一个相对地址 (RCA) 并响应命令。这个 RCA 是 16bit 地址,而 CID 是 128bit 地址,使用 RCA 简化通信。

卡在接收到 CMD3 并发出响应后就进入数据传输模式,并处于待机状态,主机在获取所有卡 RCA 之后也进入数据传输模式。


CMD2用于获取CID寄存器数据,CID寄存器各位定义如下表:

SD卡在收到CMD2后,将返回R2长响应(136位),其中包含128位有效数据(CID寄存器内容),存放在SDIO_RESP1~4等4个寄存器里面。通过读取这四个寄存器,就可以获得SD卡的CID信息。

CMD3用于设置卡相对地址(RCA,必须为非0)对于SD卡(非MMC卡),在收到CMD3后,将返回一个新的RCA给主机,方便主机寻址。

RCA的存在允许一个SDIO接口挂多个SD卡,通过RCA来区分主机要操作的是哪个卡。

对于MMC卡,则不是由SD卡自动返回RCA,而是主机主动设置MMC卡的RCA,即通过CMD3带参数(高16位用于RCA设置),实现RCA设置。同样MMC卡也支持一个SDIO接口挂多个MMC卡,不同于SD卡的是所有的RCA都是由主机主动设置的,而SD卡的RCA则是SD卡发给主机的。

在获得卡RCA之后,我们便可以发送CMD9(带RCA参数),获得SD卡的CSD寄存器内容,从CSD寄存器,我们可以得到SD卡的容量和扇区大小等十分重要的信息。CSD寄存器我们在这里就不详细介绍了,关于CSD寄存器的详细介绍,请大家参考《SD卡2.0协议.pdf》。

至此,我们的SD卡初始化基本就结束了,最后通过CMD7命令,选中我们要操作的SD卡,

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

数据传输模式

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

数据传输模式下的数据通信都是主机和目标卡之间通过寻址命令点对点进行的。

卡处于传输状态 下可以使用表 SD 部分命令描述 中面向块的读写以及擦除命令对卡进行数据读写、擦除。CMD12 可以中断正在进行的数据通信,让卡返回到传输状态。

CMD0 和 CMD15 会中止任何数据编程操 作,返回卡识别模式,这可能导致卡数据被损坏。


SD卡读操作

SD卡多块数据块读取流程


CMD16指令说明

CMD17指令说明

CMD18指令说明

CMD12指令说明


SD卡写操作


SD卡单块数据块写入流程

SD卡多块数据块写入流程


CMD13指令说明

R1响应:

SD卡状态,请参考《SD卡2.0协议.pdf》第63页-Table4-35


CMD24指令说明


ACMD23指令说明

注意:发送ACMD之前,必须先发送CMD55,通知SD卡,接下来要发送的是应用命令(APPCMD),而非标准命令


CMD55指令说明


CMD25指令说明


STM32 的 SDIO 功能框图

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

SDIO 使用两个时钟信号,一个是 SDIO 适配器时钟 (SDIOCLK=48MHz),另外一个是 APB2 总线 时钟 (PCLK2,一般为 84MHz)。

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,见 图 SDIO 适配器框图。


控制单元

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


命令路径

命令路径控制命令发送,并接收卡的响应,结构见图 SDIO 适配器命令路径。


关于 SDIO 适配器状态转换流程可以参考图卡识别模式状态转换图 ,当 SD 卡处于某一状态时, SDIO 适配器必然处于特定状态与之对应。

STM32 控制器以命令路径状态机 (CPSM) 来描述 SDIO 适配器的状态变化,并加入了等待超时检测功能,以便退出永久等待的情况。CPSM 的描述见图 CPSM 状态机描述图。


数据路径

数据路径部件负责与 SD 卡相互数据传输,内部结构见图 SDIO 适配器数据路径。

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


数据 FIFO

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

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

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

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


适配器寄存器

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


SDIO 初始化结构体

标准库函数对 SDIO 外设建立了三个初始化结构体,分别为 SDIO 初始化结构体 SDIO_InitTypeDef、 SDIO 命令初始化结构体 SDIO_CmdInitTypeDef 和 SDIO 数据初始化结构体 SDIO_DataInitTypeDef。

这些结构体成员用于设置 SDIO 工作环境参数,并由 SDIO 相应初始化配置函数或功能函数调用, 这些参数将会被写入到 SDIO 相应的寄存器,达到配置 SDIO 工作环境的目的。

初始化结构体和初始化库函数配合使用是标准库精髓所在,理解了初始化结构体每个成员意义 基本上就可以对该外设运用自如了。

初始化结构体定义在 stm32f4xx_sdio.h 文件中,初始化库函 数定义在 stm32f4xx_sdio.c 文件中,编程时我们可以结合这两个文件内注释使用。

SDIO 初始化结构体用于配置 SDIO 基本工作环境,

比如时钟分频、时钟沿、数据宽度等等。它被 SDIO_Init 函数使用。

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

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

(2)SDIO_ClockBypass:时钟分频旁路使用,可选使能或禁用,它设定 SDIO_CLKCR 寄存器的 BYPASS 位。如果使能旁路,SDIOCLK 直接驱动 CLK 线输出时钟;如果禁用,使用 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 位的值,设置 SDIO-CLK 与 CLK 线输出时钟分频系数: CLK 线时钟频率 =SDIOCLK/([CLKDIV+2])。


SDIO 命令初始化结构体

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

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

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

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


SDIO 数据初始化结构体

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

各结构体成员介绍如下:

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

(2) SDIO_DataLength:设置传输数据长度,它设定 SDIO 数据长度寄存器 (SDIO_DLEN) 的值。 (3) SDIO_DataBlockSize:设置数据块大小,有多种尺寸可选,不同命令要求的数据块可能不同。 它设定 SDIO 数据控制寄存器 (SDIO_DCTRL) 寄存器的 DBLOCKSIZE 位的值。

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

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

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


SD 卡读写测试实验

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


软件设计

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

  • 初始化相关 GPIO 及 SDIO 外设;
  • 配置 SDIO 基本通信环境进入卡识别模式,通过几个命令处理后得到卡类型;
  • 如果是可用卡就进入数据传输模式,接下来就可以进行读、写、擦除的操作。

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

在“初识 STM32 标准库”章节我们重点讲解了标准库的源代码及启动文件和库使用帮助文档这 两部分内容,实际上“Utilities”文件夹内容是非常有参考价值的,该文件夹包含了基于 ST 官方 实验板的驱动文件,比如 LCD、SRAM、SD 卡、音频解码 IC 等等底层驱动程序,另外还有第三 方软件库,如 emWin 图像软件库和 FatFs 文件系统。

虽然,我们的开发平台跟 ST 官方实验平台 硬件设计略有差别,但移植程序方法是完全可行的。学会移植程序可以减少很多工作量,加快项 目进程,更何况 ST 官方的驱动代码是经过严格验证的。 在“STM32F4xx_DSP_StdPeriph_Lib_V1.8.0”文件下可以知道 SD 卡驱动文件,见图 ST 官方实验 板 SD 卡驱动文件。

我们需要stm324x7i_eval_sdio_sd.c 和 stm324x7i_eval_sdio_sd.h 两个文件的完 整内容。另外还需要 stm324x7i_eval.c 和 stm324x7i_eval.h 两个文件的部分代码内容,为简化工程, 本章配置工程代码是将这两个文件需要用到的内容移植到 stm324x7i_eval_sdio_sd.c 文件中,具体 可以参考工程文件。

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


GPIO 初始化和 DMA 配置

SDIO 用到 CLK 线、CMD 线和 4 根 DAT 线,使用之前必须初始化相关 GPIO,并设置为复用模 式。SDIO 可以生成 DMA 请求,使用 DMA 传输可以提高数据传输效率。SDIO 可以设置为轮询 模式或 DMA 传输模式,SD 卡驱动代码针对这两个模式做了区分处理,一般使用 DMA 传输模 式,使用接下来代码分析都以 DMA 传输模式介绍。 GPIO 初始化和 DMA 配置这部分代码从 stm324x7i_eval.c 和 stm324x7i_eval.h 两个文件中移植而来。

void SD_LowLevel_DeInit(void)
{
  GPIO_InitTypeDef  GPIO_InitStructure;
  
  /*!< Disable SDIO Clock */
  SDIO_ClockCmd(DISABLE);
  
  /*!< Set Power State to OFF */
  SDIO_SetPowerState(SDIO_PowerState_OFF);

  /*!< DeInitializes the SDIO peripheral */
  SDIO_DeInit();
  
  /* Disable the SDIO APB2 Clock */
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_SDIO, DISABLE);

  GPIO_PinAFConfig(GPIOC, GPIO_PinSource8, GPIO_AF_MCO);
  GPIO_PinAFConfig(GPIOC, GPIO_PinSource9, GPIO_AF_MCO);
  GPIO_PinAFConfig(GPIOC, GPIO_PinSource10, GPIO_AF_MCO);
  GPIO_PinAFConfig(GPIOC, GPIO_PinSource11, GPIO_AF_MCO);
  GPIO_PinAFConfig(GPIOC, GPIO_PinSource12, GPIO_AF_MCO);
  GPIO_PinAFConfig(GPIOD, GPIO_PinSource2, GPIO_AF_MCO);

  /* Configure PC.08, PC.09, PC.10, PC.11 pins: D0, D1, D2, D3 pins */
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_9 | GPIO_Pin_10 | GPIO_Pin_11;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN;
  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
  GPIO_Init(GPIOC, &GPIO_InitStructure);

  /* Configure PD.02 CMD line */
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
  GPIO_Init(GPIOD, &GPIO_InitStructure);

  /* Configure PC.12 pin: CLK pin */
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;
  GPIO_Init(GPIOC, &GPIO_InitStructure);
}

/**
  * @brief  Initializes the SD Card and put it into StandBy State (Ready for 
  *         data transfer).
  * @param  None
  * @retval None
  */
void SD_LowLevel_Init(void)
{
  GPIO_InitTypeDef  GPIO_InitStructure;

  /* GPIOC and GPIOD Periph clock enable */
  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC | RCC_AHB1Periph_GPIOD, ENABLE);

  GPIO_PinAFConfig(GPIOC, GPIO_PinSource8, GPIO_AF_SDIO);
  GPIO_PinAFConfig(GPIOC, GPIO_PinSource9, GPIO_AF_SDIO);
  GPIO_PinAFConfig(GPIOC, GPIO_PinSource10, GPIO_AF_SDIO);
  GPIO_PinAFConfig(GPIOC, GPIO_PinSource11, GPIO_AF_SDIO);
  GPIO_PinAFConfig(GPIOC, GPIO_PinSource12, GPIO_AF_SDIO);
  GPIO_PinAFConfig(GPIOD, GPIO_PinSource2, GPIO_AF_SDIO);

  /* Configure PC.08, PC.09, PC.10, PC.11 pins: D0, D1, D2, D3 pins */
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_9 | GPIO_Pin_10 | GPIO_Pin_11;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
  GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
  GPIO_Init(GPIOC, &GPIO_InitStructure);

  /* Configure PD.02 CMD line */
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
  GPIO_Init(GPIOD, &GPIO_InitStructure);

  /* Configure PC.12 pin: CLK pin */
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;
  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
  GPIO_Init(GPIOC, &GPIO_InitStructure);

  /* Enable the SDIO APB2 Clock */
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_SDIO, ENABLE);

  /* Enable the DMA2 Clock */
  RCC_AHB1PeriphClockCmd(SD_SDIO_DMA_CLK, ENABLE);
}


DMA 及相关配置宏定义

#define SDIO_FIFO_ADDRESS                ((uint32_t)0x40012C80)
/** 
  * @brief  SDIO Intialization Frequency (400KHz max)
  */
#define SDIO_INIT_CLK_DIV                ((uint8_t)0x76)
/** 
  * @brief  SDIO Data Transfer Frequency (25MHz max) 
  */
#define SDIO_TRANSFER_CLK_DIV            ((uint8_t)0x0) 

#define SD_SDIO_DMA                   DMA2
#define SD_SDIO_DMA_CLK               RCC_AHB1Periph_DMA2
 
#define SD_SDIO_DMA_STREAM3	          3

#ifdef SD_SDIO_DMA_STREAM3
 #define SD_SDIO_DMA_STREAM            DMA2_Stream3
 #define SD_SDIO_DMA_CHANNEL           DMA_Channel_4
 #define SD_SDIO_DMA_FLAG_FEIF         DMA_FLAG_FEIF3
 #define SD_SDIO_DMA_FLAG_DMEIF        DMA_FLAG_DMEIF3
 #define SD_SDIO_DMA_FLAG_TEIF         DMA_FLAG_TEIF3
 #define SD_SDIO_DMA_FLAG_HTIF         DMA_FLAG_HTIF3
 #define SD_SDIO_DMA_FLAG_TCIF         DMA_FLAG_TCIF3 
 #define SD_SDIO_DMA_IRQn              DMA2_Stream3_IRQn
 #define SD_SDIO_DMA_IRQHANDLER        DMA2_Stream3_IRQHandler 
#elif defined SD_SDIO_DMA_STREAM6
 #define SD_SDIO_DMA_STREAM            DMA2_Stream6
 #define SD_SDIO_DMA_CHANNEL           DMA_Channel_4
 #define SD_SDIO_DMA_FLAG_FEIF         DMA_FLAG_FEIF6
 #define SD_SDIO_DMA_FLAG_DMEIF        DMA_FLAG_DMEIF6
 #define SD_SDIO_DMA_FLAG_TEIF         DMA_FLAG_TEIF6
 #define SD_SDIO_DMA_FLAG_HTIF         DMA_FLAG_HTIF6
 #define SD_SDIO_DMA_FLAG_TCIF         DMA_FLAG_TCIF6 
 #define SD_SDIO_DMA_IRQn              DMA2_Stream6_IRQn
 #define SD_SDIO_DMA_IRQHANDLER        DMA2_Stream6_IRQHandler
#endif /* SD_SDIO_DMA_STREAM3 */
void SD_LowLevel_DMA_TxConfig(uint32_t *BufferSRC, uint32_t BufferSize)
{
  DMA_InitTypeDef SDDMA_InitStructure;

  DMA_ClearFlag(SD_SDIO_DMA_STREAM, SD_SDIO_DMA_FLAG_FEIF | SD_SDIO_DMA_FLAG_DMEIF | SD_SDIO_DMA_FLAG_TEIF | SD_SDIO_DMA_FLAG_HTIF | SD_SDIO_DMA_FLAG_TCIF);

  /* DMA2 Stream3  or Stream6 disable */
  DMA_Cmd(SD_SDIO_DMA_STREAM, DISABLE);

  /* DMA2 Stream3  or Stream6 Config */
  DMA_DeInit(SD_SDIO_DMA_STREAM);

  SDDMA_InitStructure.DMA_Channel = SD_SDIO_DMA_CHANNEL;
  SDDMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)SDIO_FIFO_ADDRESS;
  SDDMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)BufferSRC;
  SDDMA_InitStructure.DMA_DIR = DMA_DIR_MemoryToPeripheral;
  SDDMA_InitStructure.DMA_BufferSize = 1;
  SDDMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
  SDDMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
  SDDMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word;
  SDDMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Word;
  SDDMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
  SDDMA_InitStructure.DMA_Priority = DMA_Priority_VeryHigh;
  SDDMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Enable;
  SDDMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_Full;
  SDDMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_INC4;
  SDDMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_INC4;
  DMA_Init(SD_SDIO_DMA_STREAM, &SDDMA_InitStructure);
  DMA_ITConfig(SD_SDIO_DMA_STREAM, DMA_IT_TC, ENABLE);
  DMA_FlowControllerConfig(SD_SDIO_DMA_STREAM, DMA_FlowCtrl_Peripheral);

  /* DMA2 Stream3  or Stream6 enable */
  DMA_Cmd(SD_SDIO_DMA_STREAM, ENABLE);
    
}

/**
  * @brief  Configures the DMA2 Channel4 for SDIO Rx request.
  * @param  BufferDST: pointer to the destination buffer
  * @param  BufferSize: buffer size
  * @retval None
  */
void SD_LowLevel_DMA_RxConfig(uint32_t *BufferDST, uint32_t BufferSize)
{
  DMA_InitTypeDef SDDMA_InitStructure;

  DMA_ClearFlag(SD_SDIO_DMA_STREAM, SD_SDIO_DMA_FLAG_FEIF | SD_SDIO_DMA_FLAG_DMEIF | SD_SDIO_DMA_FLAG_TEIF | SD_SDIO_DMA_FLAG_HTIF | SD_SDIO_DMA_FLAG_TCIF);

  /* DMA2 Stream3  or Stream6 disable */
  DMA_Cmd(SD_SDIO_DMA_STREAM, DISABLE);

  /* DMA2 Stream3 or Stream6 Config */
  DMA_DeInit(SD_SDIO_DMA_STREAM);

  SDDMA_InitStructure.DMA_Channel = SD_SDIO_DMA_CHANNEL;
  SDDMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)SDIO_FIFO_ADDRESS;
  SDDMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)BufferDST;
  SDDMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory;
  SDDMA_InitStructure.DMA_BufferSize = 1;
  SDDMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
  SDDMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
  SDDMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word;
  SDDMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Word;
  SDDMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
  SDDMA_InitStructure.DMA_Priority = DMA_Priority_VeryHigh;
  SDDMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Enable;
  SDDMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_Full;
  SDDMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_INC4;
  SDDMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_INC4;
  DMA_Init(SD_SDIO_DMA_STREAM, &SDDMA_InitStructure);
  DMA_ITConfig(SD_SDIO_DMA_STREAM, DMA_IT_TC, ENABLE);
  DMA_FlowControllerConfig(SD_SDIO_DMA_STREAM, DMA_FlowCtrl_Peripheral);

  /* DMA2 Stream3 or Stream6 enable */
  DMA_Cmd(SD_SDIO_DMA_STREAM, ENABLE);
}

相关类型定义

打开 bsp_sdio_sd.h 文件可以发现有非常多的枚举类型定义、结构体类型定义以及宏定义,把所有 的定义在这里罗列出来肯定是不现实的,

此处简要介绍如下:

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

• 结构体类型定义:有 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 卡类型定义。参 考表 SD 部分命令描述 列举了描述了部分命令,文件中为每个命令号定义一个宏,比如将 复位 CMD0 定义为 SD_CMD_GO_IDLE_STATE,这与表 SD 部分命令描述 中缩写部分是类 似的,所以熟悉命名用途可以更好理解 SD 卡操作过程。SDIO 数据传输可以选择是否使用 DMA 传输,SD_DMA_MODE 宏定义选择 DMA 传输,SD_POLLING_MODE 使用普通扫描 和传输,只能二选一使用。为提高系统性能,一般使用 DMA 传输模式,ST 官方的 SD 卡驱动 对这两个方式做了区分出来,下面对 SD 卡操作都是以 DMA 传输模式为例讲解。接下来还 定义了检测 SD 卡是否正确插入的宏,ST 官方的 SD 卡驱动是以一个输入引脚电平判断 SD 卡是否正确插入,这里我们不使用,把引脚定义去掉 (不然编译出错),保留 SD_PRESENT 和 SD_NOT_PRESENT 两个宏定义。最后定义 SD 卡具体的类型,有 V1.1 版本标准卡、V2.0 版本标准卡、高容量 SD 卡以及其他类型卡,前面三个是常用的类型。

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

因为类型定义和宏定义内容没有在本文中列举出来,读者有必要使用 KEIL 工具打开本章配套例 程理解清楚。同时了解 bsp_sdio_sd.c 文件中定义的多个不同类型变量。 接下来我们就开始根据 SD 卡识别过程和数据传输过程理解 SD 卡驱动函数代码。这部分代码内容也是非常庞大,不可能全部在文档中全部列出,对于部分函数只介绍其功能。


SD 卡初始化(软件部分)

SD_Error SD_Init(void)
{
  __IO SD_Error errorstatus = SD_OK;
  
	/**************配置SDIO中断 DMA中断**********************/
	NVIC_InitTypeDef NVIC_InitStructure;
	
	// Configure the NVIC Preemption Priority Bits 
	NVIC_PriorityGroupConfig (NVIC_PriorityGroup_1);
	NVIC_InitStructure.NVIC_IRQChannel = SDIO_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
	NVIC_Init (&NVIC_InitStructure);
	NVIC_InitStructure.NVIC_IRQChannel = SD_SDIO_DMA_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
	NVIC_Init (&NVIC_InitStructure);
	/**********************************************************/
	
  /* SDIO Peripheral Low Level Init */
  SD_LowLevel_Init();

  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);
  }

  /*!< Configure the SDIO peripheral */
  /*!< SDIO_CK = SDIOCLK / (SDIO_TRANSFER_CLK_DIV + 2) */
  /*!< on STM32F4xx devices, SDIOCLK is fixed to 48MHz */
  SDIO_InitStructure.SDIO_ClockDiv = SDIO_TRANSFER_CLK_DIV;
  SDIO_InitStructure.SDIO_ClockEdge = SDIO_ClockEdge_Rising;
  SDIO_InitStructure.SDIO_ClockBypass = SDIO_ClockBypass_Disable;
  SDIO_InitStructure.SDIO_ClockPowerSave = SDIO_ClockPowerSave_Disable;
  SDIO_InitStructure.SDIO_BusWide = SDIO_BusWide_1b;
  SDIO_InitStructure.SDIO_HardwareFlowControl = SDIO_HardwareFlowControl_Disable;
  SDIO_Init(&SDIO_InitStructure);

  /*----------------- Read CSD/CID MSD registers ------------------*/
  errorstatus = SD_GetCardInfo(&SDCardInfo);

  if (errorstatus == SD_OK)
  {
    /*----------------- Select Card --------------------------------*/
    errorstatus = SD_SelectDeselect((uint32_t) (SDCardInfo.RCA << 16));
  }

  if (errorstatus == SD_OK)
  {
    errorstatus = SD_EnableWideBusOperation(SDIO_BusWide_4b);
  }  

  return(errorstatus);
}

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

(1) 配置 NVIC,SD 卡通信用到 SDIO 中断,如果用到 DMA 传输还需要配置 DMA 中断。注意中 断服务函数不是定义在 stm32f4xx_it.c 文件的,是直接定义在 bsp_sdio_sd.c 文件中,中断服务函 数定义在个文件问题都不大,只要定义正确就可以的,编译器会自动寻找。

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

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

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


SD_POWERON 函数

/** 
  * @brief SDIO Commands  Index 
  */
#define SD_CMD_GO_IDLE_STATE                       ((uint8_t)0)
#define SD_CMD_SEND_OP_COND                        ((uint8_t)1)
#define SD_CMD_ALL_SEND_CID                        ((uint8_t)2)
#define SD_CMD_SET_REL_ADDR                        ((uint8_t)3) /*!< SDIO_SEND_REL_ADDR for SD Card */
#define SD_CMD_SET_DSR                             ((uint8_t)4)
#define SD_CMD_SDIO_SEN_OP_COND                    ((uint8_t)5)
#define SD_CMD_HS_SWITCH                           ((uint8_t)6)
#define SD_CMD_SEL_DESEL_CARD                      ((uint8_t)7)
#define SD_CMD_HS_SEND_EXT_CSD                     ((uint8_t)8)
#define SD_CMD_SEND_CSD                            ((uint8_t)9)
#define SD_CMD_SEND_CID                            ((uint8_t)10)
#define SD_CMD_READ_DAT_UNTIL_STOP                 ((uint8_t)11) /*!< SD Card doesn't support it */
#define SD_CMD_STOP_TRANSMISSION                   ((uint8_t)12)
#define SD_CMD_SEND_STATUS                         ((uint8_t)13)
#define SD_CMD_HS_BUSTEST_READ                     ((uint8_t)14)
#define SD_CMD_GO_INACTIVE_STATE                   ((uint8_t)15)
#define SD_CMD_SET_BLOCKLEN                        ((uint8_t)16)
#define SD_CMD_READ_SINGLE_BLOCK                   ((uint8_t)17)
#define SD_CMD_READ_MULT_BLOCK                     ((uint8_t)18)
#define SD_CMD_HS_BUSTEST_WRITE                    ((uint8_t)19)
#define SD_CMD_WRITE_DAT_UNTIL_STOP                ((uint8_t)20) /*!< SD Card doesn't support it */
#define SD_CMD_SET_BLOCK_COUNT                     ((uint8_t)23) /*!< SD Card doesn't support it */
#define SD_CMD_WRITE_SINGLE_BLOCK                  ((uint8_t)24)
#define SD_CMD_WRITE_MULT_BLOCK                    ((uint8_t)25)
#define SD_CMD_PROG_CID                            ((uint8_t)26) /*!< reserved for manufacturers */
#define SD_CMD_PROG_CSD                            ((uint8_t)27)
#define SD_CMD_SET_WRITE_PROT                      ((uint8_t)28)
#define SD_CMD_CLR_WRITE_PROT                      ((uint8_t)29)
#define SD_CMD_SEND_WRITE_PROT                     ((uint8_t)30)
#define SD_CMD_SD_ERASE_GRP_START                  ((uint8_t)32) /*!< To set the address of the first write
                                                                  block to be erased. (For SD card only) */
#define SD_CMD_SD_ERASE_GRP_END                    ((uint8_t)33) /*!< To set the address of the last write block of the
                                                                  continuous range to be erased. (For SD card only) */
#define SD_CMD_ERASE_GRP_START                     ((uint8_t)35) /*!< To set the address of the first write block to be erased.
                                                                  (For MMC card only spec 3.31) */

#define SD_CMD_ERASE_GRP_END                       ((uint8_t)36) /*!< To set the address of the last write block of the
                                                                  continuous range to be erased. (For MMC card only spec 3.31) */

#define SD_CMD_ERASE                               ((uint8_t)38)
#define SD_CMD_FAST_IO                             ((uint8_t)39) /*!< SD Card doesn't support it */
#define SD_CMD_GO_IRQ_STATE                        ((uint8_t)40) /*!< SD Card doesn't support it */
#define SD_CMD_LOCK_UNLOCK                         ((uint8_t)42)
#define SD_CMD_APP_CMD                             ((uint8_t)55)
#define SD_CMD_GEN_CMD                             ((uint8_t)56)
#define SD_CMD_NO_CMD                              ((uint8_t)64)
SD_Error SD_PowerON(void)
{
  __IO SD_Error errorstatus = SD_OK;
  uint32_t response = 0, count = 0, validvoltage = 0;
  uint32_t SDType = SD_STD_CAPACITY;

  /*!< Power ON Sequence -----------------------------------------------------*/
  /*!< Configure the SDIO peripheral */
  /*!< SDIO_CK = SDIOCLK / (SDIO_INIT_CLK_DIV + 2) */
  /*!< on STM32F4xx devices, SDIOCLK is fixed to 48MHz */
  /*!< SDIO_CK for initialization should not exceed 400 KHz */  
  SDIO_InitStructure.SDIO_ClockDiv = SDIO_INIT_CLK_DIV;
  SDIO_InitStructure.SDIO_ClockEdge = SDIO_ClockEdge_Rising;
  SDIO_InitStructure.SDIO_ClockBypass = SDIO_ClockBypass_Disable;
  SDIO_InitStructure.SDIO_ClockPowerSave = SDIO_ClockPowerSave_Disable;
  SDIO_InitStructure.SDIO_BusWide = SDIO_BusWide_1b;
  SDIO_InitStructure.SDIO_HardwareFlowControl = SDIO_HardwareFlowControl_Disable;
  SDIO_Init(&SDIO_InitStructure);

  /*!< Set Power State to ON */
  SDIO_SetPowerState(SDIO_PowerState_ON);

  /*!< Enable SDIO Clock */
  SDIO_ClockCmd(ENABLE);

  /*!< CMD0: GO_IDLE_STATE ---------------------------------------------------*/
  /*!< No CMD response required */
  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;
  SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable;
  SDIO_SendCommand(&SDIO_CmdInitStructure);

  errorstatus = CmdError();

  if (errorstatus != SD_OK)
  {
    /*!< CMD Response TimeOut (wait for CMDSENT flag) */
    return(errorstatus);
  }

  /*!< CMD8: SEND_IF_COND ----------------------------------------------------*/
  /*!< Send CMD8 to verify SD card interface operating condition */
  /*!< Argument: - [31:12]: Reserved (shall be set to '0')
               - [11:8]: Supply Voltage (VHS) 0x1 (Range: 2.7-3.6 V)
               - [7:0]: Check Pattern (recommended 0xAA) */
  /*!< CMD Response: R7 */
  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();

  if (errorstatus == SD_OK)
  {
    CardType = SDIO_STD_CAPACITY_SD_CARD_V2_0; /*!< SD Card 2.0 */
    SDType = SD_HIGH_CAPACITY;
  }
  else
  {
    /*!< 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 */
  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 is Command TimeOut, it is a MMC card */
  /*!< If errorstatus is SD_OK it is a SD card: SD card 2.0 (voltage range mismatch)
     or SD card 1.x */
  if (errorstatus == SD_OK)
  {
    /*!< SD CARD */
    /*!< Send ACMD41 SD_APP_OP_COND with Argument 0x80100000 */
    while ((!validvoltage) && (count < SD_MAX_VOLT_TRIAL))
    {

      /*!< SEND CMD55 APP_CMD with RCA as 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);
      }
      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);
      }

      response = SDIO_GetResponse(SDIO_RESP1);
      validvoltage = (((response >> 31) == 1) ? 1 : 0);
      count++;
    }
    if (count >= SD_MAX_VOLT_TRIAL)
    {
      errorstatus = SD_INVALID_VOLTRANGE;
      return(errorstatus);
    }

    if (response &= SD_HIGH_CAPACITY)
    {
      CardType = SDIO_HIGH_CAPACITY_SD_CARD;
    }

  }/*!< else MMC Card */

  return(errorstatus);
}

SD_POWERON 函数执行流程如下:

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

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

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

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

(5) 使用 ACMD41 命令判断卡的具体类型。在发送 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 卡相关的初 始化,使得卡进入数据传输模式下的待机模式。


SD_InitializeCards 函数

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 */
    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_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))
  {
    /*!< Send CMD3 SET_REL_ADDR with argument 0 */
    /*!< SD Card publishes its 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 */
    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; /*!< All cards get intialized */

  return(errorstatus);
}

SD_InitializeCards 函数执行流程如下:

(1) 判断 SDIO 电源是否启动,如果没有启动电源返回错误。

(2) SD 卡不是 SD I/O 卡时会进入 if 判断,执行发送 CMD2,CMD2 是用于通知所有卡通过 CMD 线返回 CID 值,执行 CMD2 发送之后就可以使用 CmdResp2Error 函数获取 CMD2 命令发送情况,发送无错误后即可以使用 SDIO_GetResponse 函数获取响应内容,它是个长响应,我们把 CMD2 响应内容存放在 CID_Tab 数组内。

(3) 发送 CMD2 之后紧接着就发送 CMD3,用于指示 SD 卡自行推荐 RCA 地址,CMD3 的响应 为 R6 类型,CmdResp6Error 函数用于检查 R6 响应错误,它有两个形参,一个是命令号,这里为 CMD3,另外一个是 RCA 数据指针,这里使用 rca 变量的地址赋值给它,使得在 CMD3 正确响应 之后 rca 变量即存放 SD 卡的 RCA。R6 响应还有一部分位用于指示卡的状态,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 卡的很多状态信息, 这在之后应用中使用频率是很高的。

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

(4) 扩展数据线宽度,之前的所有操作都是使用一根数据线传输完成的,使用 4 根数据线可 以提高传输性能,调用可以设置数据线宽度,函数只有一个形参,用于指定数据线宽度。在 SD_EnableWideBusOperation 函数中,调用了 SDEnWideBus 函数使能使用宽数据线,然后传输 SDIO_InitTypeDef 类型变量并使用 SDIO_Init 函数完成使用 4 根数据线配置。

至此,SD_Init 函数已经全部执行完成。如果程序可以正确执行,接下来就可以进行 SD 卡读写以 及擦除等操作。虽然 bsp_sdio_sd.c 文件看起来非常长,但在 SD_Init 函数分析过程就已经涉及到 它差不多一半内容了,另外一半内容主要就是读、写或擦除相关函数。


SD 卡数据操作

SD 卡数据操作一般包括数据读取、数据写入以及存储区擦除。数据读取和写入都可以分为单块 操作和多块操作。


擦除函数

SD_Error SD_Erase(uint64_t startaddr, uint64_t endaddr)
{
  SD_Error errorstatus = SD_OK;
  uint32_t delay = 0;
  __IO uint32_t maxdelay = 0;
  uint8_t cardstate = 0;

  /*!< Check if the card coomnd class supports erase command */
  if (((CSD_Tab[1] >> 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)
  {
    startaddr /= 512;
    endaddr /= 512;
  }
  
  /*!< According to sd-card spec 1.0 ERASE_GROUP_START (CMD32) and 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))
  {
    /*!< Send CMD32 SD_ERASE_GRP_START with argument as addr  */
    SDIO_CmdInitStructure.SDIO_Argument =(uint32_t)startaddr;
    SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_SD_ERASE_GRP_START;
    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_START);
    if (errorstatus != SD_OK)
    {
      return(errorstatus);
    }

    /*!< Send CMD33 SD_ERASE_GRP_END with argument as addr  */
    SDIO_CmdInitStructure.SDIO_Argument = (uint32_t)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 != SD_OK)
    {
      return(errorstatus);
    }
  }

  /*!< Send 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 != SD_OK)
  {
    return(errorstatus);
  }

  for (delay = 0; delay < maxdelay; delay++)
  {}

  /*!< Wait till the card is in programming state */
  errorstatus = IsCardProgramming(&cardstate);
  delay = SD_DATATIMEOUT;
  while ((delay > 0) && (errorstatus == SD_OK) && ((SD_CARD_PROGRAMMING == cardstate) || (SD_CARD_RECEIVING == cardstate)))
  {
    errorstatus = IsCardProgramming(&cardstate);
    delay--;
  }

  return(errorstatus);
}

SD_Erase 函数用于擦除 SD 卡指定地址范围内的数据。该函数接收两个参数,一个是擦除的起始 地址,另外一个是擦除的结束地址。对于高容量 SD 卡都是以块大小为 512 字节进行擦除的,所 以保证字节对齐是程序员的责任。

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 卡数据写入之前并没有硬性要求擦除写入块,这与 SPI Flash 芯片写入是不同的。ST 官方的 SD 卡写入函数包括扫描查询方式和 DMA 传输方式,我们这里只介绍 DMA 传输模式

SD_Error SD_WriteBlock(uint8_t *writebuff, uint64_t WriteAddr, uint16_t BlockSize)
{
  SD_Error errorstatus = SD_OK;

#if defined (SD_POLLING_MODE)
  uint32_t bytestransferred = 0, count = 0, restwords = 0;
  uint32_t *tempbuff = (uint32_t *)writebuff;
#endif

  TransferError = SD_OK;
  TransferEnd = 0;
  StopCondition = 0;
  
  SDIO->DCTRL = 0x0;

#if defined (SD_DMA_MODE)
  SDIO_ITConfig(SDIO_IT_DCRCFAIL | SDIO_IT_DTIMEOUT | SDIO_IT_DATAEND | SDIO_IT_RXOVERR | SDIO_IT_STBITERR, ENABLE);
  SD_LowLevel_DMA_TxConfig((uint32_t *)writebuff, BlockSize);
  SDIO_DMACmd(ENABLE);
#endif

  if (CardType == SDIO_HIGH_CAPACITY_SD_CARD)
  {
    BlockSize = 512;
    WriteAddr /= 512;
  }

  /* Set Block Size for Card */ 
  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 (SD_OK != errorstatus)
  {
    return(errorstatus);
  }
    
  /*!< Send CMD24 WRITE_SINGLE_BLOCK */
  SDIO_CmdInitStructure.SDIO_Argument = (uint32_t)WriteAddr;
  SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_WRITE_SINGLE_BLOCK;
  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_WRITE_SINGLE_BLOCK);

  if (errorstatus != SD_OK)
  {
    return(errorstatus);
  }

  SDIO_DataInitStructure.SDIO_DataTimeOut = SD_DATATIMEOUT;
  SDIO_DataInitStructure.SDIO_DataLength = BlockSize;
  SDIO_DataInitStructure.SDIO_DataBlockSize = (uint32_t) 9 << 4;
  SDIO_DataInitStructure.SDIO_TransferDir = SDIO_TransferDir_ToCard;
  SDIO_DataInitStructure.SDIO_TransferMode = SDIO_TransferMode_Block;
  SDIO_DataInitStructure.SDIO_DPSM = SDIO_DPSM_Enable;
  SDIO_DataConfig(&SDIO_DataInitStructure);

  /*!< In case of single data block transfer no need of stop command at all */
#if defined (SD_POLLING_MODE) 
  while (!(SDIO->STA & (SDIO_FLAG_DBCKEND | SDIO_FLAG_TXUNDERR | SDIO_FLAG_DCRCFAIL | SDIO_FLAG_DTIMEOUT | SDIO_FLAG_STBITERR)))
  {
    if (SDIO_GetFlagStatus(SDIO_FLAG_TXFIFOHE) != RESET)
    {
      if ((512 - bytestransferred) < 32)
      {
        restwords = ((512 - bytestransferred) % 4 == 0) ? ((512 - bytestransferred) / 4) : (( 512 -  bytestransferred) / 4 + 1);
        for (count = 0; count < restwords; count++, tempbuff++, bytestransferred += 4)
        {
          SDIO_WriteData(*tempbuff);
        }
      }
      else
      {
        for (count = 0; count < 8; count++)
        {
          SDIO_WriteData(*(tempbuff + count));
        }
        tempbuff += 8;
        bytestransferred += 32;
      }
    }
  }
  if (SDIO_GetFlagStatus(SDIO_FLAG_DTIMEOUT) != RESET)
  {
    SDIO_ClearFlag(SDIO_FLAG_DTIMEOUT);
    errorstatus = SD_DATA_TIMEOUT;
    return(errorstatus);
  }
  else if (SDIO_GetFlagStatus(SDIO_FLAG_DCRCFAIL) != RESET)
  {
    SDIO_ClearFlag(SDIO_FLAG_DCRCFAIL);
    errorstatus = SD_DATA_CRC_FAIL;
    return(errorstatus);
  }
  else if (SDIO_GetFlagStatus(SDIO_FLAG_TXUNDERR) != RESET)
  {
    SDIO_ClearFlag(SDIO_FLAG_TXUNDERR);
    errorstatus = SD_TX_UNDERRUN;
    return(errorstatus);
  }
  else if (SDIO_GetFlagStatus(SDIO_FLAG_STBITERR) != RESET)
  {
    SDIO_ClearFlag(SDIO_FLAG_STBITERR);
    errorstatus = SD_START_BIT_ERR;
    return(errorstatus);
  }

#endif

  return(errorstatus);
}

写入函数的执行流程如下:

(1) SD_WriteBlock 函数开始将 SDIO 数据控制寄存器 (SDIO_DCTRL) 清理,复位之前的传输设置。

(2) 来调用 SDIO_ITConfig 函数使能相关中断,包括数据 CRC 失败中断、数据超时中断、数据结 束中断等等。

(3) 调用 SD_LowLevel_DMA_TxConfig 函数,配置使能 SDIO 数据向 SD 卡的数据传输的 DMA 请 求,该函数可以参考代码清单 37 6。为使 SDIO 发送 DMA 请求,需要调用 SDIO_DMACmd 函数 使能。对于高容量的 SD 卡要求块大小必须为 512 字节,程序员有责任保证数据写入地址与块大 小的字节对齐问题。

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

(5) 利用 SDIO_DataInitTypeDef 结构体类型变量配置数据传输的超时、块数量、数据块大小、数 据传输方向等参数并使用 SDIO_DataConfig 函数完成数据传输环境配置。 执行完以上代码后,SDIO 外设会自动生成 DMA 发送请求,将指定数据使用 DMA 传输写入到 SD 卡内。


写入操作等待函数

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

SD_Error SD_WaitWriteOperation(void)
{
  SD_Error errorstatus = SD_OK;
  uint32_t timeout;

  timeout = SD_DATATIMEOUT;
  
  while ((DMAEndOfTransfer == 0x00) && (TransferEnd == 0) && (TransferError == SD_OK) && (timeout > 0))
  {
    timeout--;
  }
  
  DMAEndOfTransfer = 0x00;

  timeout = SD_DATATIMEOUT;
  
  while(((SDIO->STA & SDIO_FLAG_TXACT)) && (timeout > 0))
  {
    timeout--;  
  }

  if (StopCondition == 1)
  {
    errorstatus = SD_StopTransfer();
    StopCondition = 0;
  }
  
  if ((timeout == 0) && (errorstatus == SD_OK))
  {
    errorstatus = SD_DATA_TIMEOUT;
  }
  
  /*!< Clear all the static flags */
  SDIO_ClearFlag(SDIO_STATIC_FLAGS);
  
  if (TransferError != SD_OK)
  {
    return(TransferError);
  }
  else
  {
    return(errorstatus);
  }
}

该函数开始等待当前块数据正确传输完成,并添加了超时检测功能。然后不停监测 SDIO_STA 寄 存器的 TXACT 位,以等待数据写入完成。对于多块数据写入操作需要调用 SD_StopTransfer 函数 停止数据传输,而单块写入则不需要。SD_StopTransfer 函数实际是向 SD 卡发送 CMD12,该命令 专门用于停止数据传输,SD 卡系统保证在主机发送 CMD12 之后整块传输完后才停止数据传输。 SD_WaitWriteOperation 函数最后是清除相关标志位并返回错误。


数据读取操作

同向 SD 卡写入数据类似,从 SD 卡读取数据可分为单块读取和多块读取。这里这介绍单块读操 作函数,多块读操作类似理解即可。

SD_Error SD_ReadBlock(uint8_t *readbuff, uint64_t ReadAddr, uint16_t BlockSize)
{
  SD_Error errorstatus = SD_OK;
#if defined (SD_POLLING_MODE) 
  uint32_t count = 0, *tempbuff = (uint32_t *)readbuff;
#endif

  TransferError = SD_OK;
  TransferEnd = 0;
  StopCondition = 0;
  
  SDIO->DCTRL = 0x0;

#if defined (SD_DMA_MODE)
  SDIO_ITConfig(SDIO_IT_DCRCFAIL | SDIO_IT_DTIMEOUT | SDIO_IT_DATAEND | SDIO_IT_RXOVERR | SDIO_IT_STBITERR, ENABLE);
  SDIO_DMACmd(ENABLE);
  SD_LowLevel_DMA_RxConfig((uint32_t *)readbuff, BlockSize);
#endif
  
  if (CardType == SDIO_HIGH_CAPACITY_SD_CARD)
  {
    BlockSize = 512;
    ReadAddr /= 512;
  }

  /* Set Block Size for Card */ 
  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 (SD_OK != errorstatus)
  {
    return(errorstatus);
  }
  
  SDIO_DataInitStructure.SDIO_DataTimeOut = SD_DATATIMEOUT;
  SDIO_DataInitStructure.SDIO_DataLength = BlockSize;
  SDIO_DataInitStructure.SDIO_DataBlockSize = (uint32_t) 9 << 4;
  SDIO_DataInitStructure.SDIO_TransferDir = SDIO_TransferDir_ToSDIO;
  SDIO_DataInitStructure.SDIO_TransferMode = SDIO_TransferMode_Block;
  SDIO_DataInitStructure.SDIO_DPSM = SDIO_DPSM_Enable;
  SDIO_DataConfig(&SDIO_DataInitStructure);

  /*!< Send CMD17 READ_SINGLE_BLOCK */
  SDIO_CmdInitStructure.SDIO_Argument = (uint32_t)ReadAddr;
  SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_READ_SINGLE_BLOCK;
  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_READ_SINGLE_BLOCK);

  if (errorstatus != SD_OK)
  {
    return(errorstatus);
  }

#if defined (SD_POLLING_MODE)  
  /*!< In case of single block transfer, no need of stop transfer at all.*/
  /*!< Polling mode */
  while (!(SDIO->STA &(SDIO_FLAG_RXOVERR | SDIO_FLAG_DCRCFAIL | SDIO_FLAG_DTIMEOUT | SDIO_FLAG_DBCKEND | SDIO_FLAG_STBITERR)))
  {
    if (SDIO_GetFlagStatus(SDIO_FLAG_RXFIFOHF) != RESET)
    {
      for (count = 0; count < 8; count++)
      {
        *(tempbuff + count) = SDIO_ReadData();
      }
      tempbuff += 8;
    }
  }

  if (SDIO_GetFlagStatus(SDIO_FLAG_DTIMEOUT) != RESET)
  {
    SDIO_ClearFlag(SDIO_FLAG_DTIMEOUT);
    errorstatus = SD_DATA_TIMEOUT;
    return(errorstatus);
  }
  else if (SDIO_GetFlagStatus(SDIO_FLAG_DCRCFAIL) != RESET)
  {
    SDIO_ClearFlag(SDIO_FLAG_DCRCFAIL);
    errorstatus = SD_DATA_CRC_FAIL;
    return(errorstatus);
  }
  else if (SDIO_GetFlagStatus(SDIO_FLAG_RXOVERR) != RESET)
  {
    SDIO_ClearFlag(SDIO_FLAG_RXOVERR);
    errorstatus = SD_RX_OVERRUN;
    return(errorstatus);
  }
  else if (SDIO_GetFlagStatus(SDIO_FLAG_STBITERR) != RESET)
  {
    SDIO_ClearFlag(SDIO_FLAG_STBITERR);
    errorstatus = SD_START_BIT_ERR;
    return(errorstatus);
  }
  count = SD_DATATIMEOUT;
  while ((SDIO_GetFlagStatus(SDIO_FLAG_RXDAVL) != RESET) && (count > 0))
  {
    *tempbuff = SDIO_ReadData();
    tempbuff++;
    count--;
  }
  
  /*!< Clear all the static flags */
  SDIO_ClearFlag(SDIO_STATIC_FLAGS);

#endif

  return(errorstatus);
}

数据读取操作与数据写入操作编程流程是类似,只是数据传输方向改变,使用到的 SD 命令号也 有所不同而已。SD_ReadBlock 函数有三个形参,分别为数据读取存储器的指针、数据读取起始 目标地址和单块长度。

SD_ReadBlock 函数执行流程如下:

(1) 将 SDIO 外设的数据控制寄存器 (SDIO_DCTRL) 清零,复位之前的传输设置。

(2) 调用 SDIO_ITConfig 函数使能相关中断,包括数据 CRC 失败中断、数据超时中断、数据结束 中断等等。然后调用 SD_LowLevel_DMA_RxConfig 函数,配置使能 SDIO 从 SD 卡的读取数据的 DMA 请求,该函数可以参考代码清单 37 6。为使 SDIO 发送 DMA 请求,需要调用 SDIO_DMACmd 函数使能。对于高容量的 SD 卡要求块大小必须为 512 字节,程序员有责任保证目标读取地址与 块大小的字节对齐问题。

(3) 对 SD 卡进行数据读写之前,都必须发送 CMD16 指定块的大小,对于标准卡,读取 BlockSize长度字节的块;对于 SDHC 卡,读取 512 字节的块。

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

(5) 最后控制器向 SD 卡发送单块读数据命令 CMD17,SD 卡在接收到命令后就会通过数据线把 数据传输到控制器数据 FIFO 内,并自动生成 DMA 传输请求。


读取操作等待函数

SD_WaitReadOperation 函数用于等待数据读取操作完成,只有在确保数据读取完成了我们才可以 放心使用数据。SD_WaitReadOperation 函数也适用于单块读取函数和多块读取函数的。

SD_Error SD_WaitReadOperation(void)
{
  SD_Error errorstatus = SD_OK;
  uint32_t timeout;

  timeout = SD_DATATIMEOUT;
  
  while ((DMAEndOfTransfer == 0x00) && (TransferEnd == 0) && (TransferError == SD_OK) && (timeout > 0))
  {
    timeout--;
  }
  
  DMAEndOfTransfer = 0x00;

  timeout = SD_DATATIMEOUT;
  
  while(((SDIO->STA & SDIO_FLAG_RXACT)) && (timeout > 0))
  {
    timeout--;  
  }

  if (StopCondition == 1)
  {
    errorstatus = SD_StopTransfer();
    StopCondition = 0;
  }
  
  if ((timeout == 0) && (errorstatus == SD_OK))
  {
    errorstatus = SD_DATA_TIMEOUT;
  }
  
  /*!< Clear all the static flags */
  SDIO_ClearFlag(SDIO_STATIC_FLAGS);

  if (TransferError != SD_OK)
  {
    return(TransferError);
  }
  else
  {
    return(errorstatus);  
  }
}

该函数开始等待当前块数据正确传输完成,并添加了超时检测功能。然后不停监测 SDIO_STA 寄 存器的 RXACT 位,以等待数据读取完成。对于多块数据读取操作需要调用 SD_StopTransfer 函数 停止数据传输,而单块写入则不需要。该函数最后是清除相关标志位并返回错误。


SDIO 中断服务函数

在进行数据传输操作时都会使能相关标志中断,用于跟踪传输进程和错误检测。如果是使用 DMA 传输,也会使能 DMA 相关中断。为简化代码,加之 SDIO 中断服务函数内容一般不会修改,将 中断服务函数放在 bsp_sdio_sd.c 文件中,而不是放在常用于存放中断服务函数的 stm32f4xx_it.c 文件。

void SDIO_IRQHandler(void)
{
  /* Process All SDIO Interrupt Sources */
  SD_ProcessIRQSrc();
}


SD_Error SD_ProcessIRQSrc(void)
{ 
  if (SDIO_GetITStatus(SDIO_IT_DATAEND) != RESET)
  {
    TransferError = SD_OK;
    SDIO_ClearITPendingBit(SDIO_IT_DATAEND);
    TransferEnd = 1;
  }  
  else if (SDIO_GetITStatus(SDIO_IT_DCRCFAIL) != RESET)
  {
    SDIO_ClearITPendingBit(SDIO_IT_DCRCFAIL);
    TransferError = SD_DATA_CRC_FAIL;
  }
  else if (SDIO_GetITStatus(SDIO_IT_DTIMEOUT) != RESET)
  {
    SDIO_ClearITPendingBit(SDIO_IT_DTIMEOUT);
    TransferError = SD_DATA_TIMEOUT;
  }
  else if (SDIO_GetITStatus(SDIO_IT_RXOVERR) != RESET)
  {
    SDIO_ClearITPendingBit(SDIO_IT_RXOVERR);
    TransferError = SD_RX_OVERRUN;
  }
  else if (SDIO_GetITStatus(SDIO_IT_TXUNDERR) != RESET)
  {
    SDIO_ClearITPendingBit(SDIO_IT_TXUNDERR);
    TransferError = SD_TX_UNDERRUN;
  }
  else if (SDIO_GetITStatus(SDIO_IT_STBITERR) != RESET)
  {
    SDIO_ClearITPendingBit(SDIO_IT_STBITERR);
    TransferError = SD_START_BIT_ERR;
  }

  SDIO_ITConfig(SDIO_IT_DCRCFAIL | SDIO_IT_DTIMEOUT | SDIO_IT_DATAEND |
                SDIO_IT_TXFIFOHE | SDIO_IT_RXFIFOHF | SDIO_IT_TXUNDERR |
                SDIO_IT_RXOVERR | SDIO_IT_STBITERR, DISABLE);
  return(TransferError);
}

SDIO 中断服务函数 SDIO_IRQHandler 会直接调用 SD_ProcessIRQSrc 函数执行。SD_ProcessIRQSrc 函数通过多个 if 判断语句分辨中断源,并对传输错误标志变量 TransferError 赋值以指示当前传输 状态。最后禁用 SDIO 中断。

void SD_SDIO_DMA_IRQHANDLER(void)
{
  /* Process DMA2 Stream3 or DMA2 Stream6 Interrupt Sources */
  SD_ProcessDMAIRQ();
}


void SD_ProcessDMAIRQ(void)
{
  if(DMA2->LISR & SD_SDIO_DMA_FLAG_TCIF)
  {
    DMAEndOfTransfer = 0x01;
    DMA_ClearFlag(SD_SDIO_DMA_STREAM, SD_SDIO_DMA_FLAG_TCIF|SD_SDIO_DMA_FLAG_FEIF);
  }
}

SD_SDIO_DMA_IRQHANDLER 函 数 是 DMA 传 输 中 断 服 务 函 数, 它直接调用 SD_ProcessDMAIRQ 函数执行。SD_ProcessDMAIRQ 函数主要是判断 DMA 的传输完成标 志位。

至此,我们已经介绍了 SD 卡初始化、SD 卡数据操作的基础功能函数以及 SDIO 相关中断服务函 数内容,很多时候这些函数已经足够我们使用了。

接下来我们就编写一些简单的测试程序验证移 植的正确性。


知识点真多啊这个SD卡,总的来说就是STM32F4通过SDIO外设来与 SD卡进行连接,通过发送命令来对SD卡进行操作,相关配置函数都是ST官方写好的,在发送一些列命令配置好之后,再封装好读和写的函数,就实现了对SD卡的初始化和读写操作。


SD 卡测试函数

野火官方的测试例程

测试 SD 卡部分的函数是我们自己编写的,存放在 sdio_test.c 文件中。

void SD_Test(void)
{

	LED_BLUE;
  /*------------------------------ SD Init ---------------------------------- */
	/* SD卡使用SDIO中断及DMA中断接收数据,中断服务程序位于bsp_sdio_sd.c文件尾*/
  if((Status = SD_Init()) != SD_OK)
  {    
		LED_RED;
    printf("SD卡初始化失败,请确保SD卡已正确接入开发板,或换一张SD卡测试!\n");
  }
  else
  {
    printf("SD卡初始化成功!\n");		 
  }
        
  if(Status == SD_OK) 
  {
		LED_BLUE;
    /*擦除测试*/
    SD_EraseTest();
    
		LED_BLUE;
    /*single block 读写测试*/
    SD_SingleBlockTest();
    
		//暂不支持直接多块读写,多块读写可用多个单块读写流程代替
		LED_BLUE;
    /*muti block 读写测试*/
    SD_MultiBlockTest();
  }
 
}

SD 卡擦除测试

/**
  * @brief  Tests the SD card erase operation.
  * @param  None
  * @retval None
  */
void SD_EraseTest(void)
{  
  /*------------------- Block Erase ------------------------------------------*/
  if (Status == SD_OK)
  {
    /* Erase NumberOfBlocks Blocks of WRITE_BL_LEN(512 Bytes) */
    Status = SD_Erase(0x00, (BLOCK_SIZE * NUMBER_OF_BLOCKS));
  }

  if (Status == SD_OK)
  {
    Status = SD_ReadMultiBlocks(Buffer_MultiBlock_Rx, 0x00, BLOCK_SIZE, NUMBER_OF_BLOCKS);

    /* Check if the Transfer is finished */
    Status = SD_WaitReadOperation();

    /* Wait until end of DMA transfer */
    while(SD_GetStatus() != SD_TRANSFER_OK);
  }

  /* Check the correctness of erased blocks */
  if (Status == SD_OK)
  {
    EraseStatus = eBuffercmp(Buffer_MultiBlock_Rx, MULTI_BUFFER_SIZE);
  }
  
  if(EraseStatus == PASSED)
  {    
		LED_GREEN;
    printf("SD卡擦除测试成功!\n");
  }
  else
  {
		LED_BLUE;
    printf("SD卡擦除测试失败!\n");
    printf("温馨提示:部分SD卡不支持擦除测试,若SD卡能通过下面的single读写测试,即表示SD卡能够正常使用。\n");
  }
}

SD_EraseTest 函数主要编程思路是擦除一定数量的数据块,接着读取已擦除块的数据,把读取到 的数据与 0xff 或者 0x00 比较,得出擦除结果。

SD_Erase 函数用于擦除指定地址空间,源代码参考代码清单:SDIO-10 ,它接收两个参数指定擦 除空间的起始地址和终止地址。

如果 SD_Erase 函数返回正确,表示擦除成功则执行数据块读取; 如果 SD_Erase 函数返回错误,表示 SD 卡擦除失败,并不是所有卡都能擦除成功的,部分卡虽 然擦除失败,但数据读写操作也是可以正常执行的。这里使用多块读取函数 SD_ReadMultiBlocks, 它有四个形参,分别为读取数据存储器、读取数据目标地址、块大小以及块数量,函数后面都会 跟随等待数据传输完成相关处理代码。接下来会调用 eBuffercmp 函数判断擦除结果,它有两个形 参,分别为数据指针和数据字节长度,它实际上是把数据存储器内所有数据都与 0xff 或 0x00 做 比较,只有出现这两个数之外就报错退出。


单块读写测试

/**
  * @brief  Tests the SD card Single Blocks operations.
  * @param  None
  * @retval None
  */
void SD_SingleBlockTest(void)
{  
  /*------------------- Block Read/Write --------------------------*/
  /* Fill the buffer to send */
  Fill_Buffer(Buffer_Block_Tx, BLOCK_SIZE, 0x320F);

  if (Status == SD_OK)
  {
    /* Write block of 512 bytes on address 0 */
    Status = SD_WriteBlock(Buffer_Block_Tx, 0x00, BLOCK_SIZE);
    /* Check if the Transfer is finished */
    Status = SD_WaitWriteOperation();
    while(SD_GetStatus() != SD_TRANSFER_OK);
  }

  if (Status == SD_OK)
  {
    /* Read block of 512 bytes from address 0 */
    Status = SD_ReadBlock(Buffer_Block_Rx, 0x00, BLOCK_SIZE);
    /* Check if the Transfer is finished */
    Status = SD_WaitReadOperation();
    while(SD_GetStatus() != SD_TRANSFER_OK);
  }

  /* Check the correctness of written data */
  if (Status == SD_OK)
  {
    TransferStatus1 = Buffercmp(Buffer_Block_Tx, Buffer_Block_Rx, BLOCK_SIZE);
  }
  
  if(TransferStatus1 == PASSED)
  {
    LED_GREEN;
    printf("Single block 测试成功!\n");

  }
  else
  {
		LED_RED;
    printf("Single block 测试失败,请确保SD卡正确接入开发板,或换一张SD卡测试!\n");
    
  }
}

SD_SingleBlockTest 函数主要编程思想是首先填充一个块大小的存储器,通过写入操作把数据写 入到 SD 卡内,然后通过读取操作读取数据到另外的存储器,然后在对比存储器内容得出读写操 作是否正确。

SD_SingleBlockTest 函数一开始调用 Fill_Buffer 函数用于填充存储器内容,它只是简单实用 for 循 环赋值方法给存储区填充数据,它有三个形参,分别为存储区指针、填充字节数和起始数选择, 这里的起始数选择参数对本测试没有实际意义。

SD_WriteBlock 函数和 SD_ReadBlock 函数分别执 行数据写入和读取操作,具体可以参考代码清单:SDIO-11 和代码清单:SDIO-13。

Buffercmp 函数 用于比较两个存储区内容是否完全相等,它有三个形参,分别为第一个存储区指针、第二个存储区指针和存储器长度,该函数只是循环比较两个存储区对应位置的两个数据是否相等,只有发现 存在不相等就报错退出。

SD_MultiBlockTest 函数与 SD_SingleBlockTest 函数执行过程类似,这里就不做详细分析。


多块读写测试

/**
  * @brief  Tests the SD card Multiple Blocks operations.
  * @param  None
  * @retval None
  */
void SD_MultiBlockTest(void)
{  
  /*--------------- Multiple Block Read/Write ---------------------*/
  /* Fill the buffer to send */
  Fill_Buffer(Buffer_MultiBlock_Tx, MULTI_BUFFER_SIZE, 0x0);

  if (Status == SD_OK)
  {
    /* Write multiple block of many bytes on address 0 */
    Status = SD_WriteMultiBlocks(Buffer_MultiBlock_Tx, 0x00, BLOCK_SIZE, NUMBER_OF_BLOCKS);
    /* Check if the Transfer is finished */
    Status = SD_WaitWriteOperation();
    while(SD_GetStatus() != SD_TRANSFER_OK);
  }

  if (Status == SD_OK)
  {
    /* Read block of many bytes from address 0 */
    Status = SD_ReadMultiBlocks(Buffer_MultiBlock_Rx, 0x00, BLOCK_SIZE, NUMBER_OF_BLOCKS);
    /* Check if the Transfer is finished */
    Status = SD_WaitReadOperation();
    while(SD_GetStatus() != SD_TRANSFER_OK);
  }

  /* Check the correctness of written data */
  if (Status == SD_OK)
  {
    TransferStatus2 = Buffercmp(Buffer_MultiBlock_Tx, Buffer_MultiBlock_Rx, MULTI_BUFFER_SIZE);
  }
  
  if(TransferStatus2 == PASSED)
  {
		LED_GREEN;
    printf("Multi block 测试成功!");

  }
  else
  {
		LED_RED;
    printf("Multi block 测试失败,请确保SD卡正确接入开发板,或换一张SD卡测试!");
  }
}

以上就是有关于SD卡的全部内容。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值