关于SD卡CRC7计算,求助大神这是什么歌

SD卡的SPI模式总结_专业论文_资源天下
当前位置: >>
>> SD卡的SPI模式总结
SD卡的SPI模式总结
资源天下  8:39:10
  最初接触SD卡,看了一大推民间高手自己总结的资料,感觉很杂,而且不详细,忽略了很多像我这种菜鸟不明白的问题。  
例如SD卡的命令格式,CRC校验码(这个东西困惑了好久)等等。后来我索性找了SD卡的官方数据手册,对着英文一点一点看起来。下面让我来一一的总结一下。  
首先、SD卡具有SD和SPI两种通信协议。上电之后,其默认通信模式为SD模式,相较SPI模式而言,SD模式主要的优势在于  
速度,也就是说它比SPI通信更快,多用于一些对速度要求较快的地方。而SPI模式相较SD模式的优势则在于线路简单,而且很多  
MCU自带了SPI外设,所以SPI模式用起来会相对简单,当然SPI模式的速度比SD模式的速度要慢。  
这里我们主要讨论SD卡的SPI模式的应用!  
SD卡应用SPI模式与MCU相接时只需六根线,时钟线(SCK)、数据输入线(SI)、数据输出线(SO)、片选(CS)(低电平有效),当然还有两根电源线(注意:电源供电为3.3V,SD卡的供电范围为2.7至3.6V)。上电后,SD卡默认为SD模式,所以我们就得发送命令让SD卡进入SPI模式。既然要发送命令,那就必然得知道这个发命令的程序怎么写,以及SD卡的读写时序。  
读写的时序倒是容易明白,既然是SPI模式,那就是选中片选CS后往SD卡里面发送或是接收就是了。当然SD卡还是对SPI的时钟频率有要求的。SPI的时钟频率最快不得超过25MHZ,在发送命令的时候,为了兼容MMC卡,SPI时钟频率最快不得超过400KH  
Z,最慢不得低于100KHZ(因为MMC卡的时钟频率为100K至400KHZ)。所以我们在收发命令的时候应将SPI的时钟频率设置在100K至400KHZ之间的慢速模式,在读写卡内数据的时候则可以设置为不超过25MHZ的高速模式。还有就是SD卡收发的数据(命令)都是高位在前低位在后的。  
现在我们来说说命令格式。SD卡对命令格式是有要求的,规定一个命令包含六个字节,共分为三部分,如图1所示。  
图1:SPI模式下SD卡的命令格式  
第一部分是首字节,也就是BYTE1,它由三部分组成,首先是最高位,它表示起始位且总是0;然后是第六位,如果是主机发给SD卡的命令,则该位为1,若是SD卡返回给主机回应,则该位为0,也就是说这一位代表着这个命令是从主机到SD卡的还是从SD卡到主机的;最后是低六位的命令,它代表着命令序号,如果你发命令0给SD卡,则这六位就为0,如果你发命令32,则这六位的值就为32(一开始我看一些资料的时候很纳闷,说发命令0就是复位SD卡,那命令0到底是什么呢,还有就是有人说命令X的数值就是0X40+X,那为什么是0X40呢?后来才知道0X40就是第六位为1)。  
第二部分就是BYTE2―5,就是命令参数,那什么是命令参数呢?比如说你要发读SD卡第1个扇区数据的命令,那命令的内容(也就是byte1的0至5位)就是读SD卡的数据。命令参数(byte2-5)就是第一个扇区的地址,也就是你要读SD卡的什么地方。有些命令是没有参数的,那就给0;有些命令是有参数的,参数的具体内容请参照SD卡的官方数据手册,那里有列表说明。  
第三部分是BYTE6,它也包含两部分。最低位是结束位,其值总为1;其他7为即为CRC7校验码。校验码是最令我费解的地方,我看的所有民间资料都没有说CRC校验码的值是多少,那我这个命令怎么写呢?苦思不得其所!干脆看人家的程序,有些程序校验码给的都是0X95,有些给的是0XFF,有些是0X01,但有一个特点就是发命令0的时候,CRC码都是0X95,没有人说明这个CRC校验码是怎么计算出来的。于是乎上网搜索,看到了很多复杂而专业的东西,教你怎么计算,都很复杂也看不明白。我想每个命令的校验码应该是固定的而且肯定都不相同,所以根本就不需要你自己去计算!后来仔细看SD卡的官方英文手册的时候才发现,在SPI模式下,根本就不需要CRC码进行数据的校验,CRC码只是用来SD模式用的,到这才终于明白为什么会有各种CRC码了。但是在发送命令0(复位命令)时,一定要加上0X95的CRC码。因为上电后SD卡默认是在SD模式的,你发命令0将其转换为SPI模式时,SD卡还是在SD模式,所以要加CRC码,至于为什么是0X95呢?这就是芯片开发商通过CRC码的计算公式算出来的,至于怎么算的,这就不是我们关心的范围了,你只要知道怎么用就行!  
到这里总算是搞明白了SD卡的命令格式了。
  附件下载:
相关专业论文
推荐专业论文
设为首页&&&&&&&&&&&&&&&&&&&&&
资源天下() 版权所有第36章 &&&&SDIO&SD卡读写测试
全套集视频教程和页教程请到秉火论坛下载:
野火视频教程优酷观看网址:
本章参考资料:《参考手册》、《规格书》、库帮助文档《》以及简易规格文件《》版本号:。
特别说明,本书内容是以系列控制器资源讲解。
阅读本章内容之前,建议先阅读简易规格文件。
36.1 SDIO简介
卡在我们生活中已经非常普遍了,控制器对卡进行读写通信操作一般有两种通信接口可选,一种是接口,另外一种就是接口。全称是安全数字输入输出接口,多媒体卡、卡、卡都有接口。系列控制器有一个主机接口,它可以与卡、卡、卡以及设备进行数据传输。卡可以说是卡的前身,现阶段已经用得很少。卡本身不是用于存储的卡,它是指利用传输协议的一种外设。比如,它主要是提供功能,有些模块是使用串口或者接口进行通信的,但是使用接口进行通信的。并且一般设计卡是可以插入到的插槽。是专为轻薄笔记本硬盘设计的硬盘高速通讯接口。
多媒体卡协会网站中提供了有技术委员会发布的多媒体卡系统规范。
卡协会网站中提供了存储卡和卡系统规范。
工作组网站中提供了系统规范。
随之科技发展,卡容量需求越来越大,卡发展到现在也是有几个版本的,关于接口的设备整体概括见图。
图 361 SDIO接口的设备
关于卡和部分内容可以在协会网站获取到详细的介绍,比如各种卡尺寸规则、读写速度标示方法、应用扩展等等信息。
本章内容针对卡使用讲解,对于其他类型卡的应用可以参考相关系统规范实现,所以对于控制器中针对其他类型卡的内容可能在本章中简单提及或者被忽略,本章内容不区分和卡这两个概念。即使目前协议提供的卡规范版本最新是版本,但系列控制器只支持卡规范版本,即只支持标准容量和高容量标准卡,不支持超大容量标准卡,所以可以支持的最高卡容量是。
36.2 SD卡物理结构
一张卡包括有存储单元、存储单元接口、电源检测、卡及接口控制器和接口驱动器个部分,见图。存储单元是存储数据部件,存储单元通过存储单元接口与卡控制单元进行数据传输;电源检测单元保证卡工作在合适的电压下,如出现掉电或上状态时,它会使控制单元和存储单元接口复位;卡及接口控制单元控制卡的运行状态,它包括有个寄存器;接口驱动器控制卡引脚的输入输出。
图 362 SD卡物理结构
卡总共有个寄存器,用于设定或表示卡信息,参考表。这些寄存器只能通过对应的命令访问,对卡进行控制操作并不是像操作控制器相关寄存器那样一次读写一个寄存器的,它是通过命令来控制,定义了个命令,每个命令都有特殊意义,可以实现某一特定功能,卡接收到命令后,根据命令要求对卡内部寄存器进行修改,程序控制中只需要发送组合命令就可以实现卡的控制以及读写操作。
表 361 SD卡寄存器
卡识别号用来识别的卡的个体号码唯一的
相对地址卡的本地系统地址,初始化时,动态地由卡建议,主机核准。
驱动级寄存器配置卡的输出驱动
卡的特定数据卡的操作条件信息
配置寄存器卡特殊特性信息
操作条件寄存器
状态卡专有特征的信息
卡状态卡状态信息
每个寄存器位的含义可以参考简易规格文件《》第章内容。
36.3 SDIO总线
36.3.1 总线拓扑
卡一般都支持和这两种接口,本章内容只介绍接口操作方式,如果需要使用操作方式可以参考相关章节。另外,系列控制器的是不支持通信模式的,如果需要用到通信只能使用外设。
卡总线拓扑参考图。虽然可以共用总线,但不推荐多卡槽共用总线信号,要求一个单独总线应该连接一个单独的卡。
图 363 SD卡总线拓扑
卡使用接口通信,其中根电源线、根时钟线、根命令线和根数据线,具体说明如下:
?&&&&:时钟线,由主机产生,即由控制器输出;
?&&&&:命令控制线,主机通过该线发送命令控制卡,如果命令要求卡提供应答响应,卡也是通过该线传输应答信息;
?&&&&:数据线,传输读写数据;卡可将拉低表示忙状态;
?&&&&、、:电源和地信号。
在之前的以及章节都有详细讲解了对应的通信时序,实际上,的通信时序简单许多,不管是从主机控制器向卡传输,还是卡向主机控制器传输都只以时钟线的上升沿为有效。卡操作过程会使用两种不同频率的时钟同步数据,一个是识别卡阶段时钟频率,最高为,另外一个是数据传输模式下时钟频率,默认最高为,如果通过相关寄存器配置使工作在高速模式,此时数据传输模式最高频率为。
对于控制器只有一个主机,所以只能连接一个设备,开发板上集成了一个卡槽和接口的模块,要求只能使用其中一个设备。接口的模块一般集成有使能线,如果需要用到卡需要先控制该使能线禁用模块。
36.3.2 总线协议
总线通信是基于命令和数据传输的。通讯由一个起始位"",由一个停止位""终止。通信一般是主机发送一个命令,从设备在接收到命令后作出响应,如有需要会有数据传输参与。
总线的基本交互是命令与响应交互,见图。
图 364 命令与响应交互
数据是以块形式传输的,卡数据块长度一般为字节,数据可以从主机到卡,也可以是从卡到主机。数据块需要位来保证数据传输成功。位由卡系统硬件生成。控制器可以控制使用单线或线传输,本开发板设计使用线传输。图为主机向卡写入数据块操作示意。
图 365 多块写入操作
数据传输支持单块和多块读写,它们分别对应不同的操作命令,多块写入还需要使用命令来停止整个写入操作。数据写入前需要检测卡忙状态,因为卡在接收到数据后编程到存储区过程需要一定操作时间。卡忙状态通过把线拉低表示。
数据块读操作与之类似,只是无需忙状态检测。
使用数据线传输时,每次传输数据,每根数据线都必须有起始位、终止位以及位,位每根数据线都要分别检查,并把检查结果汇总然后在数据传输完后通过线反馈给主机。
卡数据包有两种格式,一种是常规数据宽,它先发低字节再发高字节,而每个字节则是先发高位再发低位,线传输示意如图。
图 366 8位宽数据包传输
线同步发送,每根线发送一个字节的其中两个位,数据位在四线顺序排列发送,数据线发较高位,数据线发较低位。
另外一种数据包发送格式是宽位数据包格式,对卡而言宽位数据包发送方式是针对卡状态寄存器内容发送的,寄存器总共有,在主机发出命令后卡将寄存器内容通过线发送给主机。宽位数据包格式示意见图。
图 367 宽位数据包传输
36.3.3 命令
命令由主机发出,以广播命令和寻址命令为例,广播命令是针对与主机总线连接的所有从设备发送的,寻址命令是指定某个地址设备进行命令传输。
1.&&&&命令格式
命令格式固定为,都是通过线连续传输的(数据线不参与),见图。
图 368 SD命令格式
命令的组成如下:
?&&&&起始位和终止位:命令的主体包含在起始位与终止位之间,它们都只包含一个数据位,起始位为,终止位为。
?&&&&传输标志:用于区分传输方向,该位为时表示命令,方向为主机传输到卡,该位为时表示响应,方向为卡传输到主机。
命令主体内容包括命令、地址信息参数和校验三个部分。
?&&&&命令号:它固定占用,所以总共有个命令代号:,每个命令都有特定的用途,部分命令不适用于卡操作,只是专门用于卡或者卡。
?&&&&地址参数:每个命令有地址信息参数用于命令附加内容,例如,广播命令没有地址信息,这用于指定参数,而寻址命令这用于指定目标卡的地址。
?&&&&校验:长度为的校验位用于验证命令传输内容正确性,如果发生外部干扰导致传输数据个别位状态改变将导致校准失败,也意味着命令传输失败,卡不执行命令。
2.&&&&命令类型
命令有种类型:
?&&&&无响应广播命令,发送到所有卡,不返回任务响应;
?&&&&带响应广播命令,发送到所有卡,同时接收来自所有卡响应;
?&&&&寻址命令,发送到选定卡,线无数据传输;
?&&&&寻址数据传输命令,发送到选定卡,线有数据传输。
另外,卡主机模块系统旨在为各种应用程序类型提供一个标准接口。在此环境中,需要有特定的客户应用程序功能。为实现这些功能,在标准中定义了两种类型的通用命令:特定应用命令和常规命令。要使用卡制造商特定的命令如,需要在发送该命令之前无发送命令,告知卡接下来的命令为特定应用命令。命令只对紧接的第一个命令有效,卡如果检测到之后的第一条命令为则执行其特定应用功能,如果检测发现不是命令,则执行标准命令。
3.&&&&命令描述
卡系统的命令被分为多个类,每个类支持一种"卡的功能设置"。表列举了卡部分命令信息,更多详细信息可以参考简易规格文件说明,表中填充位和保留位都必须被设置为。
虽然没有必须完全记住每个命令详细信息,但越熟悉命令对后面编程理解非常有帮助。
表 362 SD部分命令描述
基本命令(Class 0)
[31:0]填充位
GO_IDLE_STATE
复位所有的卡到idle状态。
[31:0]填充位
ALL_SEND_CID
通知所有卡通过CMD线返回CID值。
[31:0]填充位
SEND_RELATIVE_ADDR
通知所有卡发布新RCA。
[31:16]DSR[15:0]填充位
编程所有卡的DSR。
[31:16]RCA[15:0]填充位
SELECT/DESELECT_CARD
选择/取消选择RCA地址卡。
[31:12]保留位[11:8]VHS[7:0]检查模式
SEND_IF_COND
发送SD卡接口条件,包含主机支持的电压信息,并询问卡是否支持。
[31:16]RCA[15:0]填充位
选定卡通过CMD线发送CSD内容
[31:16]RCA[15:0]填充位
选定卡通过CMD线发送CID内容
[31:0]填充位
STOP_TRANSMISSION
强制卡停止传输
[31:16]RCA[15:0]填充位
SEND_STATUS
选定卡通过CMD线发送它状态寄存器
[31:16]RCA[15:0]填充位
GO_INACTIVE_STATE
使选定卡进入"inactive"状态
面向块的读操作(Class 2)
[31:0]块长度
SET_BLOCK_LEN
对于标准SD卡,设置块命令的长度,对于SDHC卡块命令长度固定为512字节。
[31:0]数据地址
READ_SINGLE_BLOCK
对于标准卡,读取SEL_BLOCK_LEN长度字节的块;对于SDHC卡,读取512字节的块。
[31:0]数据地址
READ_MULTIPLE_BLOCK
连续从SD卡读取数据块,直到被CMD12中断。块长度同CMD17。
面向块的写操作(Class 4)
[31:0]数据地址
WRITE_BLOCK
对于标准卡,写入SEL_BLOCK_LEN长度字节的块;对于SDHC卡,写入512字节的块。
[31:0]数据地址
WRITE_MILTIPLE_BLOCK
连续向SD卡写入数据块,直到被CMD12中断。每块长度同CMD17。
[31:0]填充位
PROGRAM_CSD
对CSD的可编程位进行编程
擦除命令(Class 5)
[31:0]数据地址
ERASE_WR_BLK_START
设置擦除的起始块地址
[31:0]数据地址
ERASE_WR_BLK_END
设置擦除的结束块地址
[31:0]填充位
擦除预先选定的块
加锁命令(Class 7)
[31:0]保留
LOCK_UNLOCK
加锁/解锁SD卡
特定应用命令(Class 8)
[31:16]RCA[15:0]填充位
指定下个命令为特定应用命令,不是标准命令
[31:1]填充位[0]读/写
通用命令,或者特定应用命令中,用于传输一个数据块,最低位为1表示读数据,为0表示写数据
SD卡特定应用命令
[31:2]填充位[1:0]总线宽度
SET_BUS_WIDTH
定义数据总线宽度('00'=1bit,'10'=4bit)。
[31:0]填充位
发送SD状态
[32]保留位[30]HCS(OCR[30]) [29:24]保留位[23:0]VDD电压(OCR[23:0])
SD_SEND_OP_COND
主机要求卡发送它的支持信息(HCS)和OCR寄存器内容。
[31:0]填充位
读取配置寄存器SCR
36.3.4 响应
响应由卡向主机发出,部分命令要求卡作出响应,这些响应多用于反馈卡的状态。总共有个响应类型代号:,其中卡没有、类型响应。特定的命令对应有特定的响应类型,比如当主机发送命令时,可以得到响应。与命令一样,卡的响应也是通过线连续传输的。根据响应内容大小可以分为短响应和长响应。短响应是长度,只有类型是长响应,其长度为。各个类型响应具体情况如表。
除了类型之外,其他响应都使用校验来校验,对于类型是使用和寄存器内部。
表 363 SD卡响应类型
R1(正常响应命令)
如果有传输到卡的数据,那么在数据线可能有busy信号
R2(CID,CSD寄存器)
CID或者CSD寄存器[127:1]位的值
CID寄存器内容作为CMD2和CMD10响应,CSD寄存器内容作为CMD9响应。
R3(OCR寄存器)
OCR寄存器的值作为ACMD41的响应
R6(发布的RCA寄存器响应)
专用于命令CMD3的响应
R7(发布的RCA寄存器响应)
专用于命令CMD8的响应,返回卡支持电压范围和检测模式
36.4 SD卡的操作模式及切换
36.4.1 SD卡的操作模式
卡有多个版本,控制器目前最高支持《》定义的卡,控制器对卡进行数据读写之前需要识别卡的种类:标准卡、标准卡、高容量卡或者不被识别卡。
卡系统包括主机和卡定义了两种操作模式:卡识别模式和数据传输模式。在系统复位后,主机处于卡识别模式,寻找总线上可用的设备;同时,卡也处于卡识别模式,直到被主机识别到,即当卡接收到命令后,卡就会进入数据传输模式,而主机在总线上所有卡被识别后也进入数据传输模式。在每个操作模式下,卡都有几种状态,参考表,通过命令控制实现卡状态的切换。
表 364 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)
36.4.2 卡识别模式
在卡识别模式下,主机会复位所有处于"卡识别模式"的卡,确认其工作电压范围,识别卡类型,并且获取卡的相对地址卡相对地址较短,便于寻址。在卡识别过程中,要求卡工作在识别时钟频率的状态下。卡识别模式下卡状态转换如图。
图 369 卡识别模式状态转换图
主机上电后,所有卡处于空闲状态,包括当前处于无效状态的卡。主机也可以发送让所有卡软复位从而进入空闲状态,但当前处于无效状态的卡并不会复位。
主机在开始与卡通信前,需要先确定双方在互相支持的电压范围内。卡有一个电压支持范围,主机当前电压必须在该范围可能才能与卡正常通信。命令就是用于验证卡接口操作条件的主要是电压支持。卡会根据命令的参数来检测操作条件匹配性,如果卡支持主机电压就产生响应,否则不响应。而主机则根据响应内容确定卡的电压匹配性。是卡标准版本才有的新命令,所以如果主机有接收到响应,可以判断卡为或更高版本卡。
命令可以识别或拒绝不匹配它的电压范围的卡。命令的电压参数用于设置主机支持电压范围,卡响应会返回卡支持的电压范围。对于对有响应的卡,把命令的位设置为,可以测试卡的容量类型,如果卡响应的位为说明为高容量卡,否则为标准卡。卡在响应之后进入准备状态,不响应的卡为不可用卡,进入无效状态。是应用特定命令,发送该命令之前必须先发。
用来控制所有卡返回它们的卡识别号,处于准备状态的卡在发送之后就进入识别状态。之后主机就发送命令,让卡自己推荐一个相对地址并响应命令。这个是地址,而是地址,使用简化通信。卡在接收到并发出响应后就进入数据传输模式,并处于待机状态,主机在获取所有卡之后也进入数据传输模式。
36.4.3 数据传输模式
只有卡系统处于数据传输模式下才可以进行数据读写操作。数据传输模式下可以将主机时钟频率设置为,默认最高为,频率切换可以通过命令来实现。数据传输模式下,卡状态转换过程见图。
图 3610 数据传输模式卡状态转换
用来选定和取消指定的卡,卡在待机状态下还不能进行数据通信,因为总线上可能有多个卡都是出于待机状态,必须选择一个地址目标卡使其进入传输状态才可以进行数据通信。同时通过命令也可以让已经被选择的目标卡返回到待机状态。
数据传输模式下的数据通信都是主机和目标卡之间通过寻址命令点对点进行的。卡处于传输状态下可以使用表中面向块的读写以及擦除命令对卡进行数据读写、擦除。可以中断正在进行的数据通信,让卡返回到传输状态。和会中止任何数据编程操作,返回卡识别模式,这可能导致卡数据被损坏。
36.5 STM32的SDIO功能框图
控制器有一个,由两部分组成:适配器和接口,见图。适配器提供主机功能,可以提供时钟、发送命令和进行数据传输。接口用于控制器访问适配器寄存器并且可以产生中断和请求信号。
图 3611 SDIO功能框图
使用两个时钟信号,一个是适配器时钟,另外一个是总线时钟,一般为。
控制器的是针对卡和卡的主设备,所以预留有根数据线,对于卡最多用四根数据线。
适配器是卡系统主机部分,是控制器与卡数据通信中间设备。适配器由五个单元组成,分别是控制单元、命令路径单元、数据路径单元、寄存器单元以及,见图。
图 3612 SDIO适配器框图
1.&&&&控制单元
控制单元包含电源管理和时钟管理功能,结构如图。电源管理部件会在系统断电和上电阶段禁止卡总线输出信号。时钟管理部件控制线时钟信号生成。一般使用分频得到。
图 3613 SDIO适配器控制单元
2.&&&&命令路径
命令路径控制命令发送,并接收卡的响应,结构见图。
图 3614 SDIO适配器命令路径
关于适配器状态转换流程可以参考图,当卡处于某一状态时,适配器必然处于特定状态与之对应。控制器以命令路径状态机来描述适配器的状态变化,并加入了等待超时检测功能,以便退出永久等待的情况。的描述见图。
图 3615 CPSM状态机描述图
3.&&&&数据路径
数据路径部件负责与卡相互数据传输,内部结构见图。
图 3616 SDIO适配器数据路径
卡系统数据传输状态转换参考图,适配器以数据路径状态机来描述适配器状态变化情况。并加入了等待超时检测功能,以便退出永久等待情况。发送数据时,处于等待发送状态,如果数据不为空,变成发送状态并且数据路径部件启动向卡发送数据。接收数据时,处于等待接收状态,当收到起始位时变成接收状态,并且数据路径部件开始从卡接收数据。状态机描述见图。
图 3617 DPSM状态机描述图
4.&&&&数据FIFO
数据先进先出部件是一个数据缓冲器,带发送和接收单元。控制器的包含宽度为、深度为字的数据缓冲器和发送接收逻辑。其中状态寄存器的位用于指示当前正在发送数据,位指示当前正在接收数据,这两个位不可能同时为。
?&&&&当为时,可以通过接口将数据写入到传输。
?&&&&当为时,接收存放从数据路径部件接收到的数据。
根据空或满状态会把寄存器位值,并可以产生中断和请求。
5.&&&&适配器寄存器
适配器寄存器包含了控制外设的各种控制寄存器及状态寄存器,内容较多,可以通过提供的各种结构体来了解,这些寄存器的功能都被整合到了结构体或标准库之中。
36.6 SDIO初始化结构体
标准库函数对外设建立了三个初始化结构体,分别为初始化结构体、命令初始化结构体和数据初始化结构体。这些结构体成员用于设置工作环境参数,并由相应初始化配置函数或功能函数调用,这些参数将会被写入到相应的寄存器,达到配置工作环境的目的。
初始化结构体和初始化库函数配合使用是标准库精髓所在,理解了初始化结构体每个成员意义基本上就可以对该外设运用自如了。初始化结构体定义在文件中,初始化库函数定义在文件中,编程时我们可以结合这两个文件内注释使用。
初始化结构体用于配置基本工作环境,比如时钟分频、时钟沿、数据宽度等等。它被函数使用。
代码清单 361 SDIO初始化结构体
1 typedef struct {
uint32_t SDIO_ClockE // 时钟沿
uint32_t SDIO_ClockB // 旁路时钟
uint32_t SDIO_ClockPowerS // 节能模式
uint32_t SDIO_BusW // 数据宽度
uint32_t SDIO_HardwareFlowC // 硬件流控制
uint8_t SDIO_ClockD // 时钟分频
8 } SDIO_InitTypeD
各结构体成员的作用介绍如下:
(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位的值,设置SDIOCLK与CLK线输出时钟分频系数:
线时钟频率。
36.7 SDIO命令初始化结构体
命令初始化结构体用于设置命令相关内容,比如命令号、命令参数、响应类型等等。它被函数使用。
代码清单 362 SDIO命令初始化接口
1 typedef struct {
uint32_t SDIO_A // 命令参数
uint32_t SDIO_CmdI // 命令号
uint32_t SDIO_R // 响应类型
uint32_t SDIO_W // 等待使能
uint32_t SDIO_CPSM; // 命令路径状态机
7 } SDIO_CmdInitTypeD
各个结构体成员介绍如下:
(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位的值。
36.8 SDIO数据初始化结构体
数据初始化结构体用于配置数据发送和接收参数,比如传输超时、数据长度、传输模式等等。它被函数使用。
代码清单 363 SDIO数据初始化结构体
1 typedef struct {
uint32_t SDIO_DataTimeO // 数据传输超时
uint32_t SDIO_DataL // 数据长度
uint32_t SDIO_DataBlockS // 数据块大小
uint32_t SDIO_TransferD // 数据传输方向
uint32_t SDIO_TransferM // 数据传输模式
uint32_t SDIO_DPSM; // 数据路径状态机
8 } SDIO_DataInitTypeD
各结构体成员介绍如下:
(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。
36.9 SD卡读写测试实验
卡广泛用于便携式设备上,比如数码相机、手机、多媒体播放器等。对于嵌入式设备来说是一种重要的存储数据部件。类似与芯片数据操作,可以直接进行读写,也可以写入文件系统,然后使用文件系统读写函数,使用文件系统操作。本实验是进行卡最底层的数据读写操作,直接使用对卡进行读写,会损坏卡原本内容,导致数据丢失,实验前请注意备份卡的原内容。由于卡容量很大,我们平时使用的卡都是已经包含有文件系统的,一般不会使用本章的操作方式编写卡的应用,但它是卡操作的基础,对于原理学习是非常有必要的,在它的基础上移植文件系统到卡的应用将在下一章讲解。
36.9.1 硬件设计
控制器的引脚是被设计固定不变的,开发板设计采用四根数据线模式。对于命令线和数据线须需要加一个上拉电阻。
图 3618 SD卡硬件设计
36.9.2 软件设计
这里只讲解核心的部分代码,有些变量的设置,头文件的包含等没有全部罗列出来,完整的代码请参考本章配套的工程。有了之前相关知识基础,我们就可以着手开始编写卡驱动程序了,根据之前内容,可了解操作的大概流程:
?&&&&初始化相关及外设;
?&&&&配置基本通信环境进入卡识别模式,通过几个命令处理后得到卡类型;
?&&&&如果是可用卡就进入数据传输模式,接下来就可以进行读、写、擦除的操作。
虽然看起来只有三步,但它们有非常多的细节需要处理。实际上,卡是非常常用外设部件,公司在其测试板上也有板子卡卡槽,并提供了完整的驱动程序,我们直接参考移植使用即可。类似、这些复杂的外设,它们的通信协议相当庞大,要自行编写完整、严谨的驱动不是一件轻松的事情,这时我们就可以利用官方例程的驱动文件,根据自己硬件移植到自己开发平台即可。
在"初始标准库"章节我们重点讲解了标准库的源代码及启动文件和库使用帮助文档这两部分内容,实际上""文件夹内容是非常有参考价值的,该文件夹包含了基于官方实验板的驱动文件,比如、、卡、音频解码等等底层驱动程序,另外还有第三方软件库,如图像软件库和文件系统。虽然,我们的开发平台跟官方实验平台硬件设计略有差别,但移植程序方法是完全可行的。学会移植程序可以减少很多工作量,加快项目进程,更何况官方的驱动代码是经过严格验证的。
在""文件下可以知道卡驱动文件,见图。我们需要和两个文件的完整内容。另外还需要和两个文件的部分代码内容,为简化工程,本章配置工程代码是将这两个文件需要用到的内容移植到文件中,具体可以参考工程文件。
图 3619 ST官方实验板SD卡驱动文件
我们把和两个文件拷贝到我们的工程文件夹中,并将其对应改名为和,见图。另外,和文件包含了卡读、写、擦除测试代码。
图 3620 SD卡驱动文件
1.&&&&GPIO初始化和DMA配置
用到线、线和根线,使用之前必须传输相关,并设置为复用模式。可以生成请求,使用传输可以提高数据传输效率。可以设置为轮询模式或传输模式,卡驱动代码针对这两个模式做了区分处理,一般使用传输模式,使用接下来代码分析都以传输模式介绍。
初始化和配置这部分代码从和两个文件中移植而来。
DMA及相关配置宏定义
代码清单 364 DMA及相关配置宏定义
1 #define SDIO_FIFO_ADDRESS ((uint32_t)0x40012C80)
* @brief SDIO Intialization Frequency (400KHz max)
5 #define SDIO_INIT_CLK_DIV ((uint8_t)0x76)
* @brief SDIO Data Transfer Frequency (25MHz max)
9 #define SDIO_TRANSFER_CLK_DIV ((uint8_t)0x0)
11 #define SD_SDIO_DMA DMA2
12 #define SD_SDIO_DMA_CLK RCC_AHB1Periph_DMA2
14 #define SD_SDIO_DMA_STREAM3 3
16 #ifdef SD_SDIO_DMA_STREAM3
17 #define SD_SDIO_DMA_STREAM DMA2_Stream3
18 #define SD_SDIO_DMA_CHANNEL DMA_Channel_4
19 #define SD_SDIO_DMA_FLAG_FEIF DMA_FLAG_FEIF3
20 #define SD_SDIO_DMA_FLAG_DMEIF DMA_FLAG_DMEIF3
21 #define SD_SDIO_DMA_FLAG_TEIF DMA_FLAG_TEIF3
22 #define SD_SDIO_DMA_FLAG_HTIF DMA_FLAG_HTIF3
23 #define SD_SDIO_DMA_FLAG_TCIF DMA_FLAG_TCIF3
24 #define SD_SDIO_DMA_IRQn DMA2_Stream3_IRQn
25 #define SD_SDIO_DMA_IRQHANDLER DMA2_Stream3_IRQHandler
26 #elif defined SD_SDIO_DMA_STREAM6
27 #define SD_SDIO_DMA_STREAM DMA2_Stream6
28 #define SD_SDIO_DMA_CHANNEL DMA_Channel_4
29 #define SD_SDIO_DMA_FLAG_FEIF DMA_FLAG_FEIF6
30 #define SD_SDIO_DMA_FLAG_DMEIF DMA_FLAG_DMEIF6
31 #define SD_SDIO_DMA_FLAG_TEIF DMA_FLAG_TEIF6
32 #define SD_SDIO_DMA_FLAG_HTIF DMA_FLAG_HTIF6
33 #define SD_SDIO_DMA_FLAG_TCIF DMA_FLAG_TCIF6
34 #define SD_SDIO_DMA_IRQn DMA2_Stream6_IRQn
35 #define SD_SDIO_DMA_IRQHANDLER DMA2_Stream6_IRQHandler
36 #endif /* SD_SDIO_DMA_STREAM3 */
使用宏定义编程对程序在同系列而不同型号主控芯片移植起到很好的帮助,同时简化程序代码。数据起始地址可用于传输地址;在卡识别模式和数据传输模式下一般是不同的,使用不同分频系数控制。使用外设,可选择和。
GPIO初始化
代码清单 365 GPIO初始化
1 void SD_LowLevel_Init(void)
GPIO_InitTypeDef GPIO_InitS
/* GPIOC and GPIOD Periph clock enable */
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC | RCC_AHB1Periph_GPIOD,
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传输配置
代码清单 366 DMA传输配置
1 /* 配置使用DMA发送 */
2 void SD_LowLevel_DMA_TxConfig(uint32_t *BufferSRC, uint32_t BufferSize)
DMA_InitTypeDef SDDMA_InitS
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_MemoryToP
SDDMA_InitStructure.DMA_BufferSize = 1;
SDDMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_D
SDDMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_E
SDDMA_InitStructure.DMA_PeripheralDataSize =DMA_PeripheralDataSize_W
SDDMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_W
SDDMA_InitStructure.DMA_Mode = DMA_Mode_N
SDDMA_InitStructure.DMA_Priority = DMA_Priority_VeryH
SDDMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_E
SDDMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_F
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);
42 /* 配置使用DMA接收 */
43 void SD_LowLevel_DMA_RxConfig(uint32_t *BufferDST, uint32_t BufferSize)
DMA_InitTypeDef SDDMA_InitS
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;
58 SDDMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)SDIO_FIFO_ADDRESS;
SDDMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)BufferDST;
SDDMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToM
SDDMA_InitStructure.DMA_BufferSize = 1;
SDDMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_D
SDDMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_E
65 SDDMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_W
SDDMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_W
SDDMA_InitStructure.DMA_Mode = DMA_Mode_N
SDDMA_InitStructure.DMA_Priority = DMA_Priority_VeryH
SDDMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_E
SDDMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_F
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);
函数用于配置的发送请求参数,并指定发送存储器地址和大小。函数用于配置的接收请求参数,并指定接收存储器地址和大小。实际上,仔细分析代码可以发现参数在这里是没有被用到,一般在使用参数时都是指定传输的数量,控制器在传输指定的数量后自动停止,但对于卡来说,可以生成硬件控制流,在传输完目标数量数据后即控制传输停止,所以这里调用函数使能卡作为传输停止的控制,这样参数无需用到也可以正确传输。对于相关配置可以参考章节内容。
2.&&&&相关类型定义
打开文件可以发现有非常多的枚举类型定义、结构体类型定义以及宏定义,把所有的定义在这里罗列出来肯定是不现实的,此处简要介绍如下:
?&&&&枚举类型定义:有、和三个。是列举了控制器可能出现的错误、比如校验错误、校验错误、通信等待超时、上溢或下溢、擦除命令错误等等。这些错误类型部分是控制器系统寄存器的标志位,部分是通过命令的响应内容得到的。定义了传输状态,有传输正常状态、传输忙状态和传输错误状态。定义卡的当前状态,比如准备状态、识别状态、待机状态、传输状态等等,具体状态转换过程参考图和图。
?&&&&结构体类型定义:有、、以及。定义了卡的特定数据寄存器位,一般提供类型的响应可以获取得到寄存器内容。结构体类似结构体,它定义卡寄存器内容,也是通过响应类型获取得到。结构体定义了卡状态,有数据宽度、卡类型、速度等级、擦除宽度、传输偏移地址等等卡状态。结构体定义了卡信息,包括了类型和类型成员,还有定义了卡容量、卡块大小、卡相对地址和卡类型成员。
?&&&&宏定义内容:包含有命令号定义、传输方式、卡插入状态以及卡类型定义。参考表列举了描述了部分命令,文件中为每个命令号定义一个宏,比如将复位定义为,这与表中缩写部分是类似的,所以熟悉命名用途可以更好理解卡操作过程。数据传输可以选择是否使用传输,宏定义选择传输,使用普通扫描和传输,只能二选一使用。为提高系统性能,一般使用传输模式,官方的卡驱动对这两个方式做了区分出来,下面对卡操作都是以传输模式为例讲解。接下来还定义了检测卡是否正确插入的宏,官方的卡驱动是以一个输入引脚电平判断卡是否正确插入,这里我们不使用,把引脚定义去掉不然编译出错,保留和两个宏定义。最后定义卡具体的类型,有版本标准卡、版本标准卡、高容量卡以及其他类型卡,前面三个是常用的类型。
在文件也有部分宏定义,这部分宏定义只能在该文件中使用。这部分宏定义包括命令超时时间定义、寄存器位掩码、响应位掩码等等,这些定义更多是为提取特定响应位内容而设计的掩码。
因为类型定义和宏定义内容没有在本文中列举出来,读者有必要使用工具打开本章配套例程理解清楚。同时了解文件中定义的多个不同类型变量。
接下来我们就开始根据卡识别过程和数据传输过程理解卡驱动函数代码。这部分代码内容也是非常庞大,不可能全部在文档中全部列出,对于部分函数只介绍其功能。
3.&&&&SD卡初始化
卡初始化过程主要是卡识别和相关卡状态获取。整个初始化函数可以实现图中的功能。
图 3621 SD卡初始化和识别流程
SD卡初始化函数
代码清单 367 SD_Init函数
1 SD_Error SD_Init(void)
__IO SD_Error errorstatus = SD_OK;
/**************配置SDIO中断 DMA中断**********************/
NVIC_InitTypeDef NVIC_InitS
// 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_R
SDIO_InitStructure.SDIO_ClockBypass = SDIO_ClockBypass_D
SDIO_InitStructure.SDIO_ClockPowerSave = SDIO_ClockPowerSave_D
SDIO_InitStructure.SDIO_BusWide = SDIO_BusWide_1b;
47 SDIO_InitStructure.SDIO_HardwareFlowControl = SDIO_HardwareFlowControl_D
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);
该函数的部分执行流程如下:
配置,卡通信用到中断,如果用到传输还需要配置中断。注意中断服务函数不是定义在文件的,是直接定义在文件中,中断服务函数定义在个文件问题都不大,只要定义正确就可以的,编译器会自动寻找。
执行函数,其功能是对底层引脚进行初始化以及开启相关时钟,该函数在之前已经讲解。
函数用于解除初始化接口,它只是简单调用函数。而函数是与函数相反功能,关闭相关时钟,关闭电源,让接近上电复位状态。恢复复位状态后再进行相关配置,可以防止部分没有配置的参数采用非默认值而导致错误,这是官方驱动常用的一种初始化方式。
调用函数,它用于查询卡的工作电压和时钟控制配置,并返回类型错误,该函数是整个识别精髓,有必要详细分析。
SD_POWERON函数
代码清单 368 SD_POWERON函数
1 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 --------------------------------------------*/
SDIO_InitStructure.SDIO_ClockDiv = SDIO_INIT_CLK_DIV;
SDIO_InitStructure.SDIO_ClockEdge = SDIO_ClockEdge_R
SDIO_InitStructure.SDIO_ClockBypass = SDIO_ClockBypass_D
SDIO_InitStructure.SDIO_ClockPowerSave = SDIO_ClockPowerSave_D
SDIO_InitStructure.SDIO_BusWide = SDIO_BusWide_1b;
SDIO_InitStructure.SDIO_HardwareFlowControl =SDIO_HardwareFlowControl_D
SDIO_Init(&SDIO_InitStructure);
/*!& Set Power State to ON */
SDIO_SetPowerState(SDIO_PowerState_ON);
/*!& Enable SDIO Clock */
SDIO_ClockCmd(ENABLE);
/*!& CMD0: GO_IDLE_STATE -----------------------------------------*/
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_E
SDIO_SendCommand(&SDIO_CmdInitStructure);
errorstatus = CmdError();
if (errorstatus != SD_OK) {
return (errorstatus);
/*!& CMD8: SEND_IF_COND -------------------------------------------*/
SDIO_CmdInitStructure.SDIO_Argument = SD_CHECK_PATTERN;
SDIO_CmdInitStructure.SDIO_CmdIndex = SDIO_SEND_IF_COND;
SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_S
SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No;
SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_E
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;
/*!& CMD55 */
SDIO_CmdInitStructure.SDIO_Argument = 0x00;
SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_APP_CMD;
SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_S
SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No;
SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_E
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_S
SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No;
SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_E
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 0x */
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_S
SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No;
SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_E
SDIO_SendCommand(&SDIO_CmdInitStructure);
errorstatus = CmdResp1Error(SD_CMD_APP_CMD);
if (errorstatus != SD_OK) {
return (errorstatus);
SDIO_CmdInitStructure.SDIO_Argument = SD_VOLTAGE_WINDOW_SD|SDT
SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_SD_APP_OP_COND;
SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_S
SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No;
SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_E
SDIO_SendCommand(&SDIO_CmdInitStructure);
errorstatus = CmdResp3Error();
if (errorstatus != SD_OK) {
return (errorstatus);
response = SDIO_GetResponse(SDIO_RESP1);
validvoltage = (((response && 31) == 1) ? 1 : 0);
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_InitializeCards函数
代码清单 369 SD_InitializeCards函数
1 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_L
SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No;
SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_E
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_S
SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No;
SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_E
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) {
/*!& 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_L
SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No;
SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_E
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);
函数执行流程如下:
判断电源是否启动,如果没有启动电源返回错误。
卡不是卡时会进入判断,执行发送,是用于通知所有卡通过线返回值,执行发送之后就可以使用函数获取命令发送情况,发送无错误后即可以使用函数获取响应内容,它是个长响应,我们把响应内容存放在数组内。
发送之后紧接着就发送,用于指示卡自行推荐地址,的响应为类型,函数用于检查响应错误,它有两个形参,一个是命令号,这里为,另外一个是数据指针,这里使用变量的地址赋值给它,使得在正确响应之后变量即存放卡的。响应还有一部分位用于指示卡的状态,函数通用会对每个错误位进行必要的检测,如果发现有错误存在则直接返回对应错误类型。执行完函数之后返回到函数中,如果判断无错误说明此刻卡已经处于数据传输模式。
发送给指定的卡使其发送返回其寄存器内容,这里的就是在函数获取得到的。最后把响应内容存放在数组中。
执行函数无错误后卡就已经处于数据传输模式下的待机状态,退出后会返回前面的函数,执行接下来代码,以下是函数的后续执行过程:
重新配置外设,提高时钟频率,之前的卡识别模式都设定线时钟为小于,进入数据传输模式可以把时钟设置为小于,以便提高数据传输速率。
调用函数获取卡信息,它需要一个指向类型变量地址的指针形参,这里赋值为变量的地址。卡信息主要是和寄存器内容,这两个寄存器内容在函数中都完成读取过程并将其分别存放在数组和数组中,所以函数只是简单的把这两个数组内容整合复制到变量对应成员内。正确执行函数后,变量就存放了卡的很多状态信息,这在之后应用中使用频率是很高的。
调用函数用于选择特定的卡,它实际是向卡发送。执行之后,卡就从待机状态转变为传输模式,可以说数据传输已经是万事俱备了。
扩展数据线宽度,之前的所有操作都是使用一根数据线传输完成的,使用根数据线可以提高传输性能,调用可以设置数据线宽度,函数只有一个形参,用于指定数据线宽度。在函数中,调用了函数使能使用宽数据线,然后传输类型变量并使用函数完成使用根数据线配置。
至此,函数已经全部执行完成。如果程序可以正确执行,接下来就可以进行卡读写以及擦除等操作。虽然文件看起来非常长,但在函数分析过程就已经涉及到它差不多一半内容了,另外一半内容主要就是读、写或擦除相关函数。
4.&&&&SD卡数据操作
卡数据操作一般包括数据读取、数据写入以及存储区擦除。数据读取和写入都可以分为单块操作和多块操作。
代码清单 3610 SD_Erase函数
1 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;
/*!& 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)
SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_SD_ERASE_GRP_START;
SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_S
SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No;
SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_E
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)
SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_SD_ERASE_GRP_END;
SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_S
SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No;
SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_E
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_S
SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No;
SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_E
SDIO_SendCommand(&SDIO_CmdInitStructure);
errorstatus = CmdResp1Error(SD_CMD_ERASE);
if (errorstatus != SD_OK) {
return (errorstatus);
for (delay = 0; delay & 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);
return (errorstatus);
函数用于擦除卡指定地址范围内的数据。该函数接收两个参数,一个是擦除的起始地址,另外一个是擦除的结束地址。对于高容量卡都是以块大小为字节进行擦除的,所以保证字节对齐是程序员的责任。函数的执行流程如下:
检查卡是否支持擦除功能,如果不支持则直接返回错误。为保证擦除指令正常进行,要求主机一个遵循下面的命令序列发送指令:。如果发送顺序不对,卡会设置位到状态寄存器。
函数发送指令用于设定擦除块开始地址,在执行无错误后发送设置擦除块的结束地址。
发送擦除命令,使得卡进行擦除操作。卡擦除操作由卡内部控制完成,不同卡擦除后是还是由厂家决定。擦除操作需要花费一定时间,这段时间不能对卡进行其他操作。
通过函数可以检测卡是否处于编程状态即卡内部的擦写状态,需要确保卡擦除完成才退出函数。函数先通过发送命令卡发送它的状态寄存器内容,并对响应内容进行分析得出当前卡的状态以及可能发送的错误。
数据写入操作
数据写入可分为单块数据写入和多块数据写入,这里只分析单块数据写入,多块的与之类似。卡数据写入之前并没有硬性要求擦除写入块,这与芯片写入是不同的。官方的卡写入函数包括扫描查询方式和传输方式,我们这里只介绍传输模式。
代码清单 3611 SD_WriteBlock函数
1 SD_Error SD_WriteBlock(uint8_t *writebuff, uint64_t WriteAddr,
uint16_t BlockSize)
SD_Error errorstatus = SD_OK;
TransferError = SD_OK;
TransferEnd = 0;
StopCondition = 0;
SDIO-&DCTRL = 0x0;
9 #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);
if (CardType == SDIO_HIGH_CAPACITY_SD_CARD) {
BlockSize = 512;
WriteAddr /= 512;
/* Set Block Size for Card */
SDIO_CmdInitStructure.SDIO_Argument = (uint32_t) BlockS
SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_SET_BLOCKLEN;
SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_S
SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No;
SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_E
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)WriteA
SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_WRITE_SINGLE_BLOCK;
SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_S
SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No;
SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_E
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 = BlockS
SDIO_DataInitStructure.SDIO_DataBlockSize = (uint32_t) 9 && 4;
SDIO_DataInitStructure.SDIO_TransferDir = SDIO_TransferDir_ToC
SDIO_DataInitStructure.SDIO_TransferMode = SDIO_TransferMode_B
SDIO_DataInitStructure.SDIO_DPSM = SDIO_DPSM_E
SDIO_DataConfig(&SDIO_DataInitStructure);
return (errorstatus);
函数用于向指定的目标地址写入一个块的数据,它有三个形参,分别为指向待写入数据的首地址的指针变量、目标写入地址和块大小。块大小一般都设置为字节。写入函数的执行流程如下:
函数开始将数据控制寄存器清理,复位之前的传输设置。
来调用函数使能相关中断,包括数据失败中断、数据超时中断、数据结束中断等等。
调用函数,配置使能数据向卡的数据传输的请求,该函数可以参考代码清单。为使发送请求,需要调用函数使能。对于高容量的卡要求块大小必须为字节,程序员有责任保证数据写入地址与块大小的字节对齐问题。
对卡进行数据读写之前,都必须发送指定块的大小,对于标准卡,要写入长度字节的块;对于卡,写入字节的块。接下来就可以发送块写入命令通知卡要进行数据写入操作,并指定待写入数据的目标地址。
利用结构体类型变量配置数据传输的超时、块数量、数据块大小、数据传输方向等参数并使用函数完成数据传输环境配置。
执行完以上代码后,外设会自动生成发送请求,将指定数据使用传输写入到卡内。
写入操作等待函数
函数用于检测和等待数据写入完成,在调用数据写入函数之后一般都需要调用,函数不仅使用于单块写入函数也适用于多块写入函数。
代码清单 3612 SD_WaitWriteOperation函数
1 SD_Error SD_WaitWriteOperation(void)
SD_Error errorstatus = SD_OK;
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);
return (errorstatus);
该函数开始等待当前块数据正确传输完成,并添加了超时检测功能。然后不停监测寄存器的位,以等待数据写入完成。对于多块数据写入操作需要调用函数停止数据传输,而单块写入则不需要。函数实际是向卡发送,该命令专门用于停止数据传输,卡系统保证在主机发送之后整块传输完后才停止数据传输。函数最后是清除相关标志位并返回错误。
数据读取操作
同向卡写入数据类似,从卡读取数据可分为单块读取和多块读取。这里这介绍单块读操作函数,多块读操作类似理解即可。
代码清单 3613 SD_ReadBlock函数
1 SD_Error SD_ReadBlock(uint8_t *readbuff, uint64_t ReadAddr,
uint16_t BlockSize)
SD_Error errorstatus = SD_OK;
TransferError = SD_OK;
TransferEnd = 0;
StopCondition = 0;
SDIO-&DCTRL = 0x0;
10 #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);
if (CardType == SDIO_HIGH_CAPACITY_SD_CARD) {
BlockSize = 512;
ReadAddr /= 512;
/* Set Block Size for Card */
SDIO_CmdInitStructure.SDIO_Argument = (uint32_t) BlockS
SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_SET_BLOCKLEN;
SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_S
SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No;
SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_E
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 = BlockS
SDIO_DataInitStructure.SDIO_DataBlockSize = (uint32_t) 9 && 4;
SDIO_DataInitStructure.SDIO_TransferDir = SDIO_TransferDir_ToSDIO;
SDIO_DataInitStructure.SDIO_TransferMode = SDIO_TransferMode_B
SDIO_DataInitStructure.SDIO_DPSM = SDIO_DPSM_E
SDIO_DataConfig(&SDIO_DataInitStructure);
/*!& Send CMD17 READ_SINGLE_BLOCK */
SDIO_CmdInitStructure.SDIO_Argument = (uint32_t)ReadA
SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_READ_SINGLE_BLOCK;
SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_S
SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No;
SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_E
SDIO_SendCommand(&SDIO_CmdInitStructure);
errorstatus = CmdResp1Error(SD_CMD_READ_SINGLE_BLOCK);
if (errorstatus != SD_OK) {
return (errorstatus);
return (errorstatus);
数据读取操作与数据写入操作编程流程是类似,只是数据传输方向改变,使用到的命令号也有所不同而已。函数有三个形参,分别为数据读取存储器的指针、数据读取起始目标地址和单块长度。函数执行流程如下:
将外设的数据控制寄存器清理,复位之前的传输设置。
调用函数使能相关中断,包括数据失败中断、数据超时中断、数据结束中断等等。然后调用函数,配置使能从卡的读取数据的请求,该函数可以参考代码清单。为使发送请求,需要调用函数使能。对于高容量的卡要求块大小必须为字节,程序员有责任保证目标读取地址与块大小的字节对齐问题。
对卡进行数据读写之前,都必须发送指定块的大小,对于标准卡,写入长度字节的块;对于卡,写入字节的块。
利用结构体类型变量配置数据传输的超时、块数量、数据块大小、数据传输方向等参数并使用函数完成数据传输环境配置。
最后控制器向卡发送单块读数据命令,卡在接收到命令后就会通过数据线把数据传输到控制器数据内,并自动生成传输请求。
读取操作等待函数
函数用于等待数据读取操作完成,只有在确保数据读取完成了我们才可以放心使用数据。函数也是适用于单块读取函数和多块读取函数的。
代码清单 3614 SD_WaitReadOperation函数
1 SD_Error SD_WaitReadOperation(void)
SD_Error errorstatus = SD_OK;
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);
return (errorstatus);
该函数开始等待当前块数据正确传输完成,并添加了超时检测功能。然后不停监测寄存器的位,以等待数据读取完成。对于多块数据读取操作需要调用函数停止数据传输,而单块写入则不需要。该函数最后是清除相关标志位并返回错误。
5.&&&&SDIO中断服务函数
在进行数据传输操作时都会使能相关标志中断,用于跟踪传输进程和错误检测。如果是使用传输,也会使能相关中断。为简化代码,加之中断服务函数内容一般不会修改,将中断服务函数放在文件中,而不是放在常用于存放中断服务函数的文件。
代码清单 3615 SDIO中断服务函数
1 void SDIO_IRQHandler(void)
/* Process All SDIO Interrupt Sources */
SD_ProcessIRQSrc();
7 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);
中断服务函数会直接调用函数执行。函数通过多个判断语句分辨中断源,并对传输错误标志变量赋值以指示当前传输状态。最后禁用中断。
代码清单 3616 DMA请求中断
1 void SD_SDIO_DMA_IRQHANDLER(void)
/* Process DMA2 Stream3 or DMA2 Stream6 Interrupt Sources */
SD_ProcessDMAIRQ();
7 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);
函数是传输中断服务函数,它直接调用函数执行。函数主要是判断的传输完成标志位。
至此,我们已经介绍了卡初始化、卡数据操作的基础功能函数以及相关中断服务函数内容,很多时候这些函数已经足够我们使用了。接下来我们就编写一些简单的测试程序验证移植的正确性。
6.&&&&测试函数
测试卡部分的函数是我们自己编写的,存放在文件中。
SD卡测试函数
代码清单 3617 SD_Test
1 void SD_Test(void)
/*---------------------------- SD Init -------------------------- */
/* SD卡使用SDIO中断及DMA中断接收数据,中断服务程序位于bsp_sdio_sd.c文件尾*/
if ((Status = SD_Init()) != SD_OK) {
printf("SD卡初始化失败,请确保SD卡已正确接入开发板,或换一张SD卡测试!\n");
printf("SD卡初始化成功!\n");
if (Status == SD_OK) {
/*擦除测试*/
SD_EraseTest();
/*single block 读写测试*/
SD_SingleBlockTest();
//暂不支持直接多块读写,多块读写可用多个单块读写流程代替
/*muti block 读写测试*/
SD_MultiBlockTest();
测试程序以开发板上灯指示测试结果,同时打印相关测试结果到串口调试助手。测试程序先调用函数完成卡初始化,该函数具体代码参考代码清单,如果初始化成功就可以进行数据操作测试。
SD卡擦除测试
代码清单 3618 SD_EraseTest
1 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");
printf("SD卡擦除测试失败!\n");
printf("温馨提示:部分SD卡不支持擦除测试,若SD卡能通过下面的single \
32 读写测试,即表示SD卡能够正常使用。\n");
函数主要编程思路是擦除一定数量的数据块,接着读取已擦除块的数据,把读取到的数据与或者比较,得出擦除结果。
函数用于擦除指定地址空间,源代码参考代码清单,它接收两个参数指定擦除空间的起始地址和终止地址。如果函数返回正确,表示擦除成功则执行数据块读取;如果函数返回错误,表示卡擦除失败,并不是所有卡都能擦除成功的,部分卡虽然擦除失败,但数据读写操作也是可以正常执行的。这里使用多块读取函数,它有四个形参,分别为读取数据存储器、读取数据目标地址、块大小以及块数量,函数后面都会跟随等待数据传输完成相关处理代码。接下来会调用函数判断擦除结果,它有两个形参,分别为数据指针和数据字节长度,它实际上是把数据存储器内所有数据都与或做比较,只有出现这两个数之外就报错退出。
单块读写测试
代码清单 3619 SD_SingleBlockTest函数
1 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");
printf("Single block 测试失败,请确保SD卡正确接入开发板,或换一张SD卡测试!\n");
函数主要编程思想是首先填充一个块大小的存储器,通过写入操作把数据写入到卡内,然后通过读取操作读取数据到另外的存储器,然后在对比存储器内容得出读写操作是否正确。
函数一开始调用函数用于填充存储器内容,它只是简单实用循环赋值方法给存储区填充数据,它有三个形参,分别为存储区指针、填充字节数和起始数选择,这里的起始数选择参数对本测试没有实际意义。函数和函数分别执行数据写入和读取操作,具体可以参考代码清单和代码清单。函数用于比较两个存储区内容是否完全相等,它有三个形参,分别为第一个存储区指针、第二个存储区指针和存储器长度,该函数只是循环比较两个存储区对应位置的两个数据是否相等,只有发现存在不相等就报错退出。
函数与函数执行过程类似,这里就不做详细分析。
代码清单 3620 main函数
1 int main(void)
/* 禁用WiFi模块 */
BL8782_PDN_INIT();
/* 初始化LED灯 */
LED_GPIO_Config();
/* 初始化独立按键 */
Key_GPIO_Config();
/*初始化USART1*/
Debug_USART_Config();
printf("\r\n欢迎使用秉火 STM32 F429 开发板。\r\n");
printf("在开始进行SD卡基本测试前,请给开发板插入32G以内的SD卡\r\n");
printf("本程序会对SD卡进行非文件系统方式读写,会删除SD卡的文件系统\r\n");
printf("实验后可通过电脑格式化或使用SD卡文件系统的例程恢复SD卡文件系统\r\n");
printf("\r\n但sd卡内的原文件不可恢复,实验前务必备份SD卡内的原文件!!!\r\n");
printf("\r\n若已确认,请按开发板的KEY1按键,开始SD卡测试实验....\r\n");
/* Infinite loop */
while (1) {
/*按下按键开始进行SD卡读写实验,会损坏SD卡原文件*/
if ( Key_Scan(KEY1_GPIO_PORT,KEY1_PIN) == KEY_ON) {
printf("\r\n开始进行SD卡读写实验\r\n");
SD_Test();
开发板板载了接口的模块,可以认为是个卡,因为系统控制器只有一个,为使用卡,需要把模块的使能端拉低,禁用模块,函数就是实现该功能。测试过程中有用到灯、独立按键和调试串口,所以需要对这些模块进行初始化配置。在无限循环中不断检测按键状态,如果有被按下就执行卡测试函数。
36.9.3 下载验证
把卡插入到开发板右侧的卡槽内,使用线连接开发板上的""接口到电脑,电脑端配置好串口调试助手参数。编译实验程序并下载到开发板上,程序运行后在串口调试助手可接收到开发板发过来的提示信息,按下开发板左下边沿的按键,开始执行卡测试,测试结果在串口调试助手可观察到,板子上灯也可以指示测试结果。
36.10 每课一问
2.&&&&SD系统集成硬件CRC校验,查阅相关资料了解CRC校验原理。
阅读(...) 评论()

我要回帖

更多关于 法律求助网 的文章

 

随机推荐