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卡2.0协议.pdf》这个文档。
从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指令说明
4.3、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 两个文件中移植而 来。
DMA 及相关配置宏定义
相关类型定义
打开 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_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) (3) SDIO_DeInit 函数用于解除初始化 SDIO 接口,它只是简单调用 SD_LowLevel_DeInit 函数。 而 SD_LowLevel_DeInit 函数是与 SD_LowLevel_Init 函数相反功能,关闭相关时钟,关闭 SDIO 电 源,让 SDIO 接近上电复位状态。恢复复位状态后再进行相关配置,可以防止部分没有配置的参 数采用非默认值而导致错误,这是 ST 官方驱动常用的一种初始化方式。 (4) 调用 SD_PowerON 函数,它用于查询卡的工作电压和时钟控制配置,并返回 SD_Error 类型错 误,该函数是整个 SD 识别精髓,有必要详细分析。
SD_POWERON 函数
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 卡数据操作一般包括数据读取、数据写入以及存储区擦除。数据读取和写入都可以分为单块 操作和多块操作。
擦除函数