欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

SD卡实验_STM32F1开发指南_第四十三章

程序员文章站 2022-07-04 16:03:14
...
                                                       第四十三章 SD卡实验

序言:

    战舰STM32F103自带了标准的SD卡接口,使用STM32F1自带的SDIO接口驱动,4位模式,
最高通信速率可达24Mhz,最高每秒可传输数据12M字节,对于一般应用足够了。
本章分为如下几部分:
43.1 SDIO接口简介
43.2 硬件设计
43.3 软件设计
43.4 下载验证

43.1 SDIO简介

   STM32F103自带SDIO接口,本节,我们将简单介绍STM32F1的SDIO接口,包括:主要功能
及框图、时钟、命令与响应和相关寄存器简介等,最后,我们将介绍SD卡的初始化流程。

43.1.1 SDIO主要功能及框图

    stm32f1的SDIO控制器支持多媒体卡(MMC卡)、SD卡、SD I/O卡和CE-ATA设备等。
SDIO的组要功能如下:
① 与多媒体卡系统规格书版本4.2全兼容。支持三种不同的数据总线模式:1位(默认)、4位和8位。
② 与较早的多媒体卡系统规格版本全兼容(向前兼容)。
③ 与SD卡规格版本2.0全兼容。
④ 与SD I/O卡规格版本2.0全兼容:支持不同的数据总线模式:1位(默认)和4位。
⑤ 完全支持CE-ATA功能(与CE-ATA数字协议版本1.1全兼容)。8位总线模式下数据传输速率可达48Mhz。
⑥ 数据和命令输出使能信号,用于控制外部双向驱动器。
    stm32f1的SDIO控制器包含2部分:SDIO适配器和AHB总线接口,其功能框图如下所示:
SD卡实验_STM32F1开发指南_第四十三章
SD卡实验_STM32F1开发指南_第四十三章
   复位后,默认情况下SDIO_D0用于数据传输。
   初始化后主机可以改变数据总线宽度(通过ACMD6命令设置)。
   如果一个多媒体卡接到了总线上,则SDIO_D0、SDIO_D[3:0]或SDIO_D[7:0]可以用于
数据传输。
   MMC版本V3.31和之前版本的协议只支持1位数据线,所以只能用SDIO_D0(为了通用
考虑,在程序里面我们只要检测到MMC卡就设置为1位总线数据)。
    如果一个SD卡或SD I/O卡接到了总线上,可以通过主机配置数据传输使用SDIO_D0或
SDIO_D[3:0]。所有的数据线都工作在推挽模式。
    SDIO_CMD有两种操作模式:
① 用于初始化时的开路模式(仅用于MMC版本V3.31或之前版本);
② 用于命令传输的推挽模式(SD/SD IO卡和MMC V4.2在初始化时也使用推挽模式)。

43.1.2 SDIO的时钟

    从上图可以看出,SDIO共有3个时钟,分别是:
    卡时钟(SDIO_CK):每个时钟周期在命令和数据线上传输1位命令或数据。
对于多媒体卡V3.31协议,时钟频率可以在0MHz~20MHz间变化;
对于多媒体卡V4.0/4.2协议,时钟频率可以在0MHz~48MHz之间变化;
对于SD或SD I/O卡,时钟频率可以在0MHz~25MHz间变化。
    SDIO适配器时钟(SDIOCLK):用于驱动SDIO适配器。
其频率等于AHB总线频率(HCLK),并且用于产生SDIO_CK时钟。
    AHB总线接口时钟(HCLK/2):用于驱动SDIO的AHB总线接口。
其频率为HCLK/2。

    前面提到,SD卡时钟(SDIO_CK),根据卡的不同,可能有好几个区间。这就需要设置
时钟频率。公式为:
    SDIO_CK = SDIOCLK / (2 + CLKDIV)
    其中,SDIOCLK为HCLK,一般为72Mhz;CLKDIV是分频系数,可以通过寄存器SDIO_CLKCR
进行设置(确保SDIO_CK不超过卡的最大操作频率)。
    注意:SD卡刚刚初始化时,SDIO_CK不能超过400khz,否则无法初始化成功。初始化成功后,
可以设置时钟频率到最大了。

43.1.3 SDIO的命令与响应

SDIO的命令分类:应用相关命令(ACMD)、通用命令(CMD);
ACMD的发送方式:必须先发送通用命令(CMD55),然后才能发送ACMD;
SDIO的所有命令和响应传输通道:SDIO_CMD引脚;
命令和响应长度:48位。
   SDIO命令格式如下:
位的位置 宽度 说明 备注
bit 47 1 0 起始位 由SDIO硬件控制
bit 46 1 1 传输位 由SDIO硬件控制
bit [45:40] 6 - 命令索引 用户控制:CMD0,CMD1之类的在SDIO_CMD寄存器里面设置
bit [39:8] 32 - 参数 用户控制:由寄存器SDIO_ARG设置
bit [7:1] 7 - CRC7 由SDIO硬件控制
bit 0 1 1 结束位 由SDIO硬件控制
    所有的命令都是stm32f1发出的。
    一般情况下,选中的SD卡接收到命令后,都会回复一个应答(注意,CMD0是没有应答的)这个应答
我们称之为响应,响应也是在CMD线上串行传输的。
    stm32f1的SDIO控制器支持2种类型的响应:短响应(48位)、长响应(136位),这两种响应类型都带有
CRC校验(注:不带CRC的响应应该忽略CRC错误标志,比如CMD1的响应)。
    短响应格式
位的位置 宽度 说明 备注
bit 47 1 0 起始位 由SDIO硬件控制
bit 46 1 0 传输位 由SDIO硬件控制
bit[45:40] 6 - 命令索引 控制用户:存放在SDIO_RESPCMD寄存器
bit[39:8] 32 - 参数 控制用户:存放在SDIO_RESP1寄存器中
bit[7:1] 7 - CRC7(或11111111) 由SDIO硬件控制
bit 0 1 1 结束位 由SDIO硬件控制
    长响应格式
位的位置 宽度 说明 备注
bit 135 1 0 起始位 由SDIO硬件控制
bit 134 1 0 传输位 由SDIO硬件控制
bit[133:128] 6 111111 保留 由SDIO硬件控制
bit[127:1] 127 - CID或CSD(包括内部CRC7) 存放在SDIO_RESP1~RESP4等4个寄存器
bit 0 1 1 结束位 由SDIO硬件控制
    SD存储卡共有5类响应(R1、R2、R3、R6、R7),我们这里以R1为例简单介绍一下使用方法
    R1(普通响应命令)响应输入短响应,其长度为48位。格式同上面短响应格式。
    在收到R1响应后,可以从SDIO_RESPCMD寄存器和SDIO_RESP1寄存器分别读出:命令索引和卡状态信息。
关于其他响应的介绍,参考光盘《SD卡2.0协议.pdf》或《stm32中文手册》第20章。

    数据在SDIO控制器与SD卡之间的传输方式
    对于SDI/SDIO存储器,数据是以数据块方式传输的,对于MMC卡,数据是以数据块或数据流方式
传输的。本节只考虑数据块形式。
    SDIO(多)数据块读操作流程:
SD卡实验_STM32F1开发指南_第四十三章
SD卡实验_STM32F1开发指南_第四十三章
    从机在收到主机相关命令后,开始发送数据块给主机,所有数据块都带有CRC校验。
单个数据块读的时候,在收到1个数据块以后既可以停止了,不需要发送停止命令(CMD12);
多个数据块读的时候,SD卡将一直发发送数据给主机,直到接收到主机发送STOP命令(CMD12)。

    SDIO(多)数据块写操作流程:
SD卡实验_STM32F1开发指南_第四十三章
SD卡实验_STM32F1开发指南_第四十三章
     写操作同读操作基本类似,只是多了一个繁忙判断,新的数据块必须在SD卡非繁忙时才能发送。
这里的繁忙信号由SD卡拉低SDIO_D0,以表示繁忙,SDIO硬件自动控制,不需要软件处理。

43.1.4 SDIO相关寄存器介绍

1)电源控制(SDIO_POWER)
SD卡实验_STM32F1开发指南_第四十三章
SD卡实验_STM32F1开发指南_第四十三章
    该寄存器复位值为0,所以SDIO的电源是关闭的。要使用SDIO,第一步就是设置最低2位为1,让SDIO上电,开启卡时钟。
2)时钟控制寄存器(SDIO_CLKCR)
SD卡实验_STM32F1开发指南_第四十三章    SD卡实验_STM32F1开发指南_第四十三章
WIDBUS:设置SDIO总线位宽,正常使用时,设置为1,即4位宽度;
BYPASS:设置分频器是否旁路,一般要使用分频器,所以这里设置为0,禁止旁路;
CLKEN  :设置是否使能SDIO_CK,这里设置为1;
CLKDIV :设置SDIO_CK的分频,这里设置为1,即可得到24MHz的SDIO_CK频率。
3)参数制寄存器(SDIO_ARG)
    用于存储命令参数,一个简单的32位寄存器注意:写命令前先写这个就寄存器。
4)命令响应寄存器(SDIO_RESPCMD)
    用于存储最后收到的命令响应中的命令索引。一个简单的32位寄存器,只有低6位有效。
如果传输的命令响应不包含索引,则该寄存器内容不可知。
5)响应寄存器组(SDIO_RESP1~4)
SD卡实验_STM32F1开发指南_第四十三章
SD卡实验_STM32F1开发指南_第四十三章
    用于存储收到的卡响应部分信息。
如果收到短响应,则数据存放在SDIO_RESP1寄存器里,其他三个没有用到;
如果收到长响应,则依次存放在1~4里面。 
7)命令寄存器(SDIO_CMD)
SD卡实验_STM32F1开发指南_第四十三章
SD卡实验_STM32F1开发指南_第四十三章
   CMDINDEX:命令索引,要发送的命令索引号(比如发送CMD1,其值为1,此处为1);
    WAITRESP:设置等待响应位,指示CPSM是否需要等待,以及等待类型等。
    CPSMEN   : 这里的CPSM,就是命令通道状态机(详见《STM32中文参考手册》v10 p368)。一般开启。设置1。
8)数据定时器寄存器(SDIO_DTIMER)
    用于存储以卡总线时钟(SDIO_CK)为周期的数据超时时间。
一个计数器将从SDIO_DTIMER中加载数值,并在数据通道状态机(DPSM)(详见《STM32中文参考手
册》v10 p372)进入Wait_R或繁忙状态时进行递减计数,当DPSM处在这些状态时,如果计数器减为
0,则设置超时标志。
    注意:在写入数据控制寄存器,进行数据传输之前,必须先写入本寄存器和数据长度寄存器(SDIO_DLEN)。
9)数据长度寄存器(SDIO_DLEN)
    用于设置需要传输的数据字节长度。低25位有效。
注意:对于块数据传输,该寄存器的值必须是数据块长度(通过SDIO_DCTRL设置)的倍数。
10)数据控制寄存器(SDIO_DCTRL)
SD卡实验_STM32F1开发指南_第四十三章
SD卡实验_STM32F1开发指南_第四十三章
     用于设置数据通道状态机(DPSM),需要根据实际情况,配置该寄存器,才可正常实现数据收发。
包括:数据传输使能、传输方向、传输模式、DMA使能、数据块长度等信息。   
11)状态寄存器(SDIO_STA )、清除中断寄存器(SDIO_ICR)和中断屏蔽寄存器(SDIO_MASK)
SD卡实验_STM32F1开发指南_第四十三章
    这三个寄存器每个位的定义都相同,只是功能不同。下面是状态寄存器(SDIO_STA)各位定义:
SD卡实验_STM32F1开发指南_第四十三章
     状态寄存器可以用来查询SDIO控制器的当前状态,以便处理各种事务。比如,
SDIO_STA的bit2表示命令响应超时,说明SDIO的命令响应出了问题;
SDIO_ICR的bit2可以用来清除SDIO_STA的bit2超时标志;
SDIO_MASK的bit2,可以开启或关闭命令响应超时中断。
12)数据FIFO寄存器(SDIO_FIFO)
    包括接收和发送FIFO,他们由一组连续的32个地址上的32个寄存器组成,CPU可以
使用FIFO读写多个操作数。
    例如,我们要从SD卡上读数据,必须读SDIO_FIFO寄存器,要写数据到SD卡,则要
写SDIO_FIFO寄存器。SDIO将这32个地址分为16个一组,发送和接收各占一半。我们每
次读写的时候,最多就是读取和发送FIFO或写入接收FIFO的一半大小的数据,即8个字
(32个字节)。注:读写SDIO_FIFO必须以4字节对齐的内存进行操作

43.1.5 SD初始化流程

    要实现SDIO驱动SD卡,最重要的步骤就是SD卡的初始化,只要SD卡初始化完成了,剩下
的读写操作就简单了。
    从SD卡2.0协议文档,可以得到下图所示初始化流程图:
SD卡实验_STM32F1开发指南_第四十三章
SD卡实验_STM32F1开发指南_第四十三章
   从图中看到,不管是什么卡,
首先要给卡上电:设置SDIO_POWER[1:0] = 11;
上电后对卡进行软复位:发送CMD0; 
之后区分SD卡2.0:发送CMD8(只有2.0及以后的卡才支持CMD8命令,MMC卡和V1.x的卡是不支持该命令的)。
    CMD8的格式如下:
位的位置 宽度 说明
bit 47 1 0b 起始位
bit 46 1 1b 传输位
bit [45:40] 6 001000b 命令索引
bit [39:20] 20 00000h 参数:Reserved bits
bit [19:16] 4 x 参数:volatage supplied(VHS)
bit [15:8] 8 x 参数:check pattern
bit [7:1] 7 x CRC7
bit 0 1 1b 结束位
    这里我们需要在发送CMD8时,通过其带的参数我们可以设置VHS位,以告诉SD卡,主机
的供电情况。VHS位定义如下:
序号 Voltage Supplied Value Definition
1 0000b Not Defined
2 0001b 2.7~3.6V
3 0010b Reserved for Low Voltage Range
4 0100b Reserved
5 1000b Reserved
6 Others Not Defined
    这里我们使用参数:0x1AA(0001b + 10101010b),即告诉SD卡,主机供电为2.7~3.6V。
如果SD卡支持CMD8,且支持该电压范围,则会通过CMD8的相应(R7)将参数部分原本返回
给主机,如果不支持CMD8,或者不支持这个电压范围,则不响应。
    区分SD卡后,发送ACMD41(注意发送前先发送CMD55),来进一步确认卡的操作电压
范围,并通过HCS位来告诉SD卡,主机是不是支持高容量卡(SDHC)。
    ACMD41的命令如下表所示:
ACMD INDEX type argument resp abbreviation command description
ACMD41 bcr [31]reserved bit
[30]HCS(OCR[30])
[29:24]reserved bits
[23:0]Vdd Voltage
Window(OCR[23:0])
R3 SD_SEND_OP_COND Sends host capacity support information(HCS) and asks the accessed card to send its operating condition register(OCR)content in the response on the CMD line.
HCS is effective when card receives
SEND_IF_COND command. Reserved
bit shall be set to '0'. CCS bit is assigned to OCR[30].

    ACMD41得到的响应(R3)包含SD卡OCR寄存器内容,OCR寄存器内容如下表所示:
序号 OCR bit position OCR Fields Definition
1 0~6 reserved
2 7 Reserved for Low Voltage Range
3 8~14 reserved
4 15 2.7~2.8
5 16 2.8~2.9
6 17 2.9~3.0
7 18 3.0~3.1
8 19 3.1~3.2
9 20 3.2~3.3
10 21 3.3~3.4
11 22 3.4~3.5
12 23 3.5~3.6
13 24~29 reserved
14 30 Card Capacity Status(CCS)1
15 31 Card power up status bit (busy)2
1)This bit is valid only when the card power up status bit is set.
2)This bit is set to Low if the card has not finished the power up routine.
    对于支持CMD8指令的卡,主机通过ACMD41的参数设置HCS位为1,来告诉SD卡
主机支持SDHC卡,如果设置为0,则表示主机不支持SDHC卡,SDHC卡如果接收到
HCS为0,则永远不会返回卡就绪状态。
   对于不支持CMD8的卡,HCS位设置为0即可。
    SD卡在接收到ACMD41后,返回OCR寄存器内容,如果是2.0的卡,主机可以通过
判断OCR的CCS位来判断是SDHC还是SDSC;如果是1.x的卡,则忽略该位。
   OCR寄存器的最后一个位用于告诉主机SD卡是否上电完成,如果上电完成,则该位被置1。
    MMC卡则不支持ACMD41,不响应CMD55。对于MMC卡,我们只需要发送CMD0后,
再发送CMD1(作用同ACMD41),检查MMC卡的OCR寄存器,实现MMC卡的初始化。
    至此,我们便实现了对SD卡的类型区分。
    最后,获取卡CID寄存器数据和卡相对地址(RCA):发送了CMD2和CMD3命令。
   CMD2,用于获取CID寄存器的数据,CID寄存器数据各位定义如下:
序号 Name Field Width CID-slice
1 Manufacturer ID MID 8 [127:120]
2 OEM/Application ID OID 16 [119:104]
3 Product name PNM 40 [103:64]
4 Product revision PRV 8 [63:56]
5 Product serial number PSN 32 [55:24]
6 reserved -- 4 [23:20]
7 Manufacturing date MDT 12 [19:8]
8 CRC7 checksum CRC 7 [7:1]
9 not used, always 1 - 1 [0:0]
    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详细信息参见《SD卡2.0协议.pdf》)。
    至此,SD卡初始化基本结束了。
    最后,选中要操作的SD卡:发送CMD7命令。即可开始对SD卡的读写操作了,SD卡的其他
命令和参数,参考《SD卡2.0协议.pdf》,里面有详细的介绍。
    通过对SDIO相关寄存器的讲解,大家对SDIO的整个工作流程有个比较清晰的理解了。
下面讲解stm32f1操作SDIO相关固件库函数。他们分布在stm32f10x_sdio.c和stm32f10x_sdio.h中。
    1)时钟初始化:SDIO_Init。
    void SDIO_Init(SDIO_InitTypeDef * SDIO_InitStruct);
    实现方法主要通过设置结构体类型SDIO_InitTypeDef参数成员变量的值达到设置SDIO时钟控制
寄存器SDIO_CLKCR的目的,参数包括旁路时钟分频器、时钟分频系数等。
    关于这个函数的详细配置,详见前面的CLKCR寄存器。
    2)发送命令:SDIO_SendCommand。
    void SDIO_SendCommand(SDIO_CmdInitTypeDef * SDIO_CmdInitStruct);
    实现方法设置SDIO的命令寄存器SDIO_CMD和命令参数寄存器SDIO_ARG。前面讲过,在发送
命令之前,必须先设置命令参数。所以这个函数同时设置参数和发生命令两个功能。
    详细配置,参见前面的SDIO_CMD和SDIO_ARG寄存器。
    3)配置数据通道:SDIO_DataConfig。
    void SDIO_DataConfig(SDIO_DataInitTypeDef * SDIO_DataInitStruct);
    实现方法通过设置结构体类型SDIO_DataInitTypeDef参数成员变量的值来配置SDIO的数据通道状态机,
包括数据传输使能、传输方向、传输模式、DMA使能、数据块长度等信息。
    具体参考寄存器SDIO_DCTRL。
    4)读写数据FIFO寄存器:SDIO_ReadData和SDIO_WriteData。
    这两个函数比较简单,就是设置和读取SDIO_FIFO寄存器,前面已经详细讲过了。
    5)其他常用函数
    时钟使能函数SDIO_ClockCmd();
    电源状态控制SDIO_SetPowerState();
    DMA使能SDIO_DMACmd();
    状态获取SDIO_GetFlagStatus()等。
这些函数使用非常简单。

43.2 硬件设计

    本章实验功能简单:开机先初始化SD卡,如果初始化完成,则提示LCD初始化成功。
按下KEY0,读取SD卡扇区0的数据,然后通过串口发送到电脑。如果没有初始化通过,
则在LCD上提示初始化失败。用DS0提示程序正在运行。

43.3 软件设计

    在工程中添加sdio_sdcard.c和sdio_sdcard.h文件。在FWLib分组中添加stm3f10x_sdio.c。

    sdio_sdcard.c中几个重要函数

第一个SD_Init函数。

   ① 该函数先实现SDIO时钟,及相关IO口的初始化;
   ② 然后清零SDIO部分寄存器;
   ③ 然后初始化SD卡:
   首先通过函数SD_PowerON完成SD卡上电,并获得SD卡类型(SDHC\SDSC\SDV1.x\MMC),然后调用
SD_InitializeCard函数,完成SD卡的初始化。
//初始化SD卡
//返回值:错误代码;(0,无错误)
SD_Error SD_Init(void)
{
    NVIC_InitTypeDef NVIC_InitStructure;
    GPIO_InitTypeDef GPIO_InitStructure;
    u8 clkdiv = 0;
    SD_Error errorstatus = SD_OK;
    //SDIO IO口初始化
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC | RCC_APB2Periph_GPIOD, ENABLE);  //使能PORTC和PORTD时钟
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_SDIO | RCC_AHBPeriph_DMA2, ENABLE);       //使能SDIO和DMA2时钟
    
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_9 | GPIO_Pin_10 | GPIO_Pin_11 | GPIO_Pin_12; //PC.8~12复用输出
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;    //复用推挽输出
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //IO口速度为50MHz
    GPIO_Init(GPIOC, &GPIO_InitStructure);             //初始化PC8~12
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;          //PD2复用输出
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;    //复用推挽输出
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;  //IO 50MHz
    GPIO_Init(GPIOD, &GPIO_InitStructure);             // 初始化PD2
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;          //PD7
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;      //上拉输入
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;  //IO 50MHz
    GPIO_Init(GPIOD, &GPIO_InitStructure);             //初始化PD7
    //SDIO外设寄存器设置为默认值
    SDIO_DeInit();
    NVIC_InitStructure.NVIC_IRQChannel = SDIO_IRQn;           //SDIO中断配置
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; //抢占优先级0
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;        //子优先级0
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;           //使能外部中断通道
    NVIC_Init(&NVIC_InitStructure);                           // 初始化NVIC寄存器 
    errorstatus = SD_PowerON();    //SD卡上电
    if(errorstatus == SD_OK)
    {
        errorstatus = SD_InitializeCards();           //初始化SD卡
    }
    if(errorstatus == SD_OK)
    {
        errorstatus = SD_GetCardInfo(&SDCardInfo);    //获取SD卡信息
    }
    if(errorstatus == SD_OK)
    {
        errorstatus = SD_SelectDeselect((u32)(SDCardInfo.RCA << 16));    //选中SD卡
    }
    if(errorstatus == SD_OK)
    {
        errorstatus = SD_EnableWideBusOperation(1);    //4位宽度
    }
    if((errorstatus == SD_OK) || (SDIO_MULTIMEDIA_CARD == CardType))
    {
        if(SDCardInfo.CardType == SDIO_STD_CAPACITY_SD_CARD_V1_1) ||
           SDCardInfo.CardType == SDIO_STD_CAPACITY_SD_CARD_V2_0)
        {
              clkdiv = SDIO_TRANSFER_CLK_DIV + 6;    //V1.1和V2.0卡,设置最高71/12 = 6MHz
        }
        else
        {
            clkdiv = SDIO_TRANSFER_CLK_DIV;          //SDHC等其他卡设置最高12MHz
        }
        SDIO_Clock_Set(clkdiv);                            //设置时钟频率
        errorstatus = SD_SetDeviceMode(SD_POLLING_MODE);   //设置为查询模式
    }
    return errorstatus;
}

    SD_InitializeCards函数主要内容:

   ① 发送CMD2和CMD3,获得CID寄存器内容和SD卡相对地址(RCA);
   ② 通过CMD9,获取CSD寄存器内容。
    到这里SD卡初始化完成了。
    ③ 随后,SD_Init函数通过调用SD_GetCardInfo函数,获取SD卡相关信息,
    ④ 之后调用SD_SelectDeselect函数,选择要操作的卡(CMD7+RCA)。
    ⑤ 通过函数SD_EnableWideBusOperation设置SDIO的数据位宽为4位(但MMC卡只支持1未模式)。
    ⑥ 最后,设置SDIO_CK时钟的频率,并设置工作模式(DMA/轮询)。
函数如下:
//初始化所有的卡,并让卡进入就绪状态
//返回值:错误代码
SD_Error SD_InitializeCards(void)
{
    SD_Error errorstatus = SD_OK;
    u16 rca = 0x01;
    if(0 == SDIO_GetPowerState())
    {
        return SD_REQUEST_NOT_APPLICABLE;    //检查电源状态,确保为上电状态
    }
    if(SDIO_SECURE_DIGITAL_IO_CARD != CardType)
    {
        SDIO_CmdInitStructure.SDIO_Argument = 0x00; 
        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);      //发送CMD2,取得CID,长响应
        
        errorstatus = CmdResp2Error();    //等待R2响应
        if(errorstatus != SD_OK)
        {
            return errorstatus;            //响应错误
        }
        
        CID_Tab[0] = SDIO->RESP1;
        CID_Tab[1] = SDIO->RESP2;
        CID_Tab[2] = SDIO->RESP3;
        CID_Tab[3] = 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))    //判断卡类型
     {
         SDIO_CmdInitStructure.SDIO_Argument = 0x00;
         SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_SET_REL_ADDR;    //cmd3
         SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short;    //r6
         SDIO_CmdInitStructure.SDIO_Wait     = SDIO_Wait_No;
         SDIO_CmdInitStructure.SDIO_CPSM     = SDIO_CPSM_Enable;
         SDIO_SendCommand(&SDIO_CmdInitStructure);    //发送CMD3,短响应
 
        
         errorstatus = CmdResp6Error(SD_CMD_SET_REL_ADDR, &rca);    //等待R6响应
         if(errorstatus != SD_OK)
         {
            return errorstatus;            //响应错误
         }
     }
 
    if(SDIO_MULTIMEDIA_CARD == CardType)//判断卡类型
     {
         SDIO_CmdInitStructure.SDIO_Argument = (u32)(rca << 6);        //发送CMD3,短响应
         SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_SET_REL_ADDR;    //cmd3
         SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short;    //r6
         SDIO_CmdInitStructure.SDIO_Wait     = SDIO_Wait_No;
         SDIO_CmdInitStructure.SDIO_CPSM     = SDIO_CPSM_Enable;
         SDIO_SendCommand(&SDIO_CmdInitStructure);    //发送CMD3,短响应
 
        
         errorstatus = CmdResp2Error();    //等待R2响应
         if(errorstatus != SD_OK)
         {
            return errorstatus;            //响应错误
         }
     }
 
    if(SDIO_SECURE_DIGITAL_IO_CARD != CardType)
    {
        RCA = rca;
        SDIO_CmdInitStructure.SDIO_Argument = (u32)(rca << 16); 
        SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_ALL_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);      //发送CMD9+卡RCA
        
        errorstatus = CmdResp2Error();    //等待R2响应
        if(errorstatus != SD_OK)
        {
            return errorstatus;            //响应错误
        }
        
        CID_Tab[0] = SDIO->RESP1;
        CID_Tab[1] = SDIO->RESP2;
        CID_Tab[2] = SDIO->RESP3;
        CID_Tab[3] = SDIO->RESP4;
    }
    return SD_OK;    //卡初始化成功
}

    SD卡读块函数:SD_ReadBlock。用于从SD卡指定地址读出一块(扇区)数据。

    函数 ① 先发送CMD16,用于设置块大小;
             ② 然后配置SDIO控制器读数据的长度(用函数convert_from_bytes_to_power_of_two求出blksize以2为底的指数);
             ③ 然后发送CMD17(带地址参数addr),从指定地址读取一块数据;
             ④ 最后,根据所设置的模式(查询/DMA),从SDIO_FIFO读出数据。
代码如下:
//SD卡读取一个块 
//buf:读数据缓存区(必须4字节对齐!!)
//addr:读取地址
//blksize:块大小
SD_Error SD_ReadBlock(u8 *buf,long long addr,u16 blksize)
{	  
	SD_Error errorstatus=SD_OK;
	u8 power;
   	u32 count=0,*tempbuff=(u32*)buf;//转换为u32指针 
	u32 timeout=SDIO_DATATIMEOUT;   
   	if(NULL==buf)return SD_INVALID_PARAMETER; 
   	SDIO->DCTRL=0x0;	//数据控制寄存器清零(关DMA)   
	if(CardType==SDIO_HIGH_CAPACITY_SD_CARD)//大容量卡
	{
		blksize=512;
		addr>>=9;
	}   
 
  	SDIO_DataInitStructure.SDIO_DataBlockSize= SDIO_DataBlockSize_1b ;//清除DPSM状态机配置
	SDIO_DataInitStructure.SDIO_DataLength= 0 ;
	SDIO_DataInitStructure.SDIO_DataTimeOut=SD_DATATIMEOUT ;
	SDIO_DataInitStructure.SDIO_DPSM=SDIO_DPSM_Enable;
    SDIO_DataInitStructure.SDIO_TransferDir=SDIO_TransferDir_ToCard;
	SDIO_DataInitStructure.SDIO_TransferMode=SDIO_TransferMode_Block;
    SDIO_DataConfig(&SDIO_DataInitStructure);
	
	
	if(SDIO->RESP1&SD_CARD_LOCKED)return SD_LOCK_UNLOCK_FAILED;//卡锁了
	
	if((blksize>0)&&(blksize<=2048)&&((blksize&(blksize-1))==0))
	{
		power=convert_from_bytes_to_power_of_two(blksize);	    
 
		SDIO_CmdInitStructure.SDIO_Argument =  blksize;
        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);           //发送CMD16+设置数据长度为blksize,短响应
		
		errorstatus=CmdResp1Error(SD_CMD_SET_BLOCKLEN);	    //等待R1响应  
		
		if(errorstatus!=SD_OK)return errorstatus;           	//响应错误	 
	}
    else 
        return SD_INVALID_PARAMETER;	  	  
									    
	SDIO_DataInitStructure.SDIO_DataBlockSize= power<<4 ;    //清除DPSM状态机配置
	SDIO_DataInitStructure.SDIO_DataLength= blksize ;
	SDIO_DataInitStructure.SDIO_DataTimeOut=SD_DATATIMEOUT ;
	SDIO_DataInitStructure.SDIO_DPSM=SDIO_DPSM_Enable;
	SDIO_DataInitStructure.SDIO_TransferDir=SDIO_TransferDir_ToSDIO;
	SDIO_DataInitStructure.SDIO_TransferMode=SDIO_TransferMode_Block;
    SDIO_DataConfig(&SDIO_DataInitStructure);
	
	SDIO_CmdInitStructure.SDIO_Argument =  addr;
    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);                //发送CMD17+从addr地址出读取数据,短响应 
	
	errorstatus=CmdResp1Error(SD_CMD_READ_SINGLE_BLOCK);     //等待R1响应   
	if(errorstatus!=SD_OK)
        return errorstatus;   		                         //响应错误	 

 	if(DeviceMode==SD_POLLING_MODE)						     //查询模式,轮询数据	 
	{
 		INTX_DISABLE();                                      //关闭总中断(POLLING模式,严禁中断打断SDIO读写操作!!!)
		while(!(SDIO->STA&((1<<5)|(1<<1)|(1<<3)|(1<<10)|(1<<9))))//无上溢/CRC/超时/完成(标志)/起始位错误
		{
			if(SDIO_GetFlagStatus(SDIO_FLAG_RXFIFOHF) != RESET)	 //接收区半满,表示至少存了8个字
			{
				for(count=0;count<8;count++)			//循环读取数据
				{
					*(tempbuff+count)=SDIO->FIFO;
				}
				tempbuff+=8;	 
				timeout=0X7FFFFF; 	                //读数据溢出时间
			}
            else 	                                //处理超时
			{
				if(timeout==0)
                    return SD_DATA_TIMEOUT;
				timeout--;
			}
		} 

		if(SDIO_GetFlagStatus(SDIO_FLAG_DTIMEOUT) != RESET)		//数据超时错误
		{										   
	 		SDIO_ClearFlag(SDIO_FLAG_DTIMEOUT); 	//清错误标志
			return SD_DATA_TIMEOUT;
	 	}
        else if(SDIO_GetFlagStatus(SDIO_FLAG_DCRCFAIL) != RESET)	//数据块CRC错误
		{
	 		SDIO_ClearFlag(SDIO_FLAG_DCRCFAIL);  //清错误标志
			return SD_DATA_CRC_FAIL;		   
		}
        else if(SDIO_GetFlagStatus(SDIO_FLAG_RXOVERR) != RESET) 	//接收fifo上溢错误
		{
	 		SDIO_ClearFlag(SDIO_FLAG_RXOVERR);	 //清错误标志
			return SD_RX_OVERRUN;		 
		}
        else if(SDIO_GetFlagStatus(SDIO_FLAG_STBITERR) != RESET) //接收起始位错误
		{
	 		SDIO_ClearFlag(SDIO_FLAG_STBITERR);  //清错误标志
			return SD_START_BIT_ERR;		 
		}   
		while(SDIO_GetFlagStatus(SDIO_FLAG_RXDAVL) != RESET)	     //FIFO里面,还存在可用数据
		{
			*tempbuff=SDIO_ReadData();	         //循环读取数据
			tempbuff++;
		}
		INTX_ENABLE();                        //开启总中断
		SDIO_ClearFlag(SDIO_STATIC_FLAGS);    //清除所有标记
	}
    else if(DeviceMode==SD_DMA_MODE)
	{
  	    SD_DMA_Config((u32*)buf,blksize,DMA_DIR_PeripheralSRC); 
		TransferError=SD_OK;
		StopCondition=0;			        //单块读,不需要发送停止传输指令
		TransferEnd=0;				    //传输结束标置位,在中断服务置1
		SDIO->MASK|=(1<<1)|(1<<3)|(1<<8)|(1<<5)|(1<<9);	//配置需要的中断 
		SDIO_DMACmd(ENABLE);
 		while(((DMA2->ISR&0X2000)==RESET)&&(TransferEnd==0)&&(TransferError==SD_OK)&&timeout)
            timeout--;                    //等待传输完成 
		if(timeout==0)
            return SD_DATA_TIMEOUT;       //超时
		if(TransferError!=SD_OK)
            errorstatus=TransferError;  
    }   
 	return errorstatus; 
}
    该函数有两个注意的地方:
    ① addr参数类型为long long,以支持大于4G的卡,否则操作大于4G的卡可能会出错。
    ② 轮询方式,读写FIFO时,禁止任何中断打断,所以使用了INTX_DSIABLE函数关闭总中断,
在FIFO读写结束后,才打开总中断(INTX_ENABLE函数设置)。

    还有另外三个底层读写函数:

SD_ReadMultiBlocks:用于多块读;SD_WriteBlock:用于单块写;SD_ReadMultiBlocks:用于多块写。具体代码参考源代码。
    关于控制命令,详见《SD卡2.0协议.pdf》。

    SDIO与文件系统的两个接口函数:SD_ReadDiskSDIO_WriteDisk
    这两个函数在FATS实验将会用到。
代码如下:
//读SD卡
//buf:读数据缓存区
//sector:扇区地址
//cnt:扇区个数
//返回值:错误状态;0:正常;其他:错误代码。
u8 SD_ReadDisk(u8 * buf, u32 sector, u8 cnt)
{
    u8 sta = SD_OK;
    long long lsector = sector;
    u8 n;
    if((u32)buf%4 != 0) 
    {
        for(n = 0; n < cnt; n++)
        {
            //FATS提供给SD_ReadDisk或SD_WriteDisk的数据缓存区地址不一定是4字节对齐,所以通过一个
            //4字节对齐缓存(SDIO_DATA_BUFFER)作为过渡,以确保传递给底层读写函数的buf是4字节对齐的。
            //__align(4) u8 SDIO_DATA_BUFFER[512];		
            sta = SD_ReadBlock(SDIO_DATA_BUFFER, lsector+512*n; 512);    //单扇区读操作
            memcpy(buf, SDIO_DATA_BUFFER, 512);
            buf += 512;    
        }
    }
    else
    {
        if(cnt == 1)
            sta = SD_ReadBlock(buf, lsector, 512);                //单个扇区的读操作
        else
            sta = SD_ReadMultiBlocks(buf, lsector, 512, cnt);     //多个扇区
    }
}
//写SD卡
//buf:写数据缓存区
//sector:扇区地址
//cnt:扇区个数
//返回值:错误状态:0:正常;其他:错误代码
u8 SD_WriteDisk(u8*buf,u32 sector,u8 cnt)
{
    u8 sta=SD_OK;
    u8 n;
    long long lsector=sector;
    lsector<<=9;
    if((u32)buf%4!=0)
    {
        for(n=0;n<cnt;n++)
        {
            memcpy(SDIO_DATA_BUFFER,buf,512);
            sta=SD_WriteBlock(SDIO_DATA_BUFFER,lsector+512*n,512);//单扇区写
            buf+=512;
        }
    }
    else
    {
        if(cnt==1)
            sta=SD_WriteBlock(buf,lsector,512);                    //单个扇区写
        else 
            sta=SD_WriteMultiBlocks(buf,lsector,512,cnt);          //多个扇区写
    }
    return sta;
}
main.c文件:
    这里总共有2个函数,show_sdcard_info函数用于从串口输出SD卡信心;main函数先初始化SD卡,
初始化成功后,调用show_sdcard_info函数,输出SD卡信息,并在LCD上显示SD容量。
    然后进入死循环,如果按键KEY0按下,则通过SD_ReadDisk函数读取SD卡的扇区0(物理磁盘,扇区0),
并将数据通过串口打印出来。
//通过串口打印SD卡相关信息
void show_sdcard_info(void)
{
switch(SDCardInfo.CardType)
{
case SDIO_STD_CAPACITY_SD_CARD_V1_1:
            printf("Card Type:SDSC V1.1\r\n");
            break;
        case SDIO_STD_CAPACITY_SD_CARD_V2_0:
            printf("Card Type:SDSC V2.0\r\n");
            break;
        case SDIO_HIGH_CAPACITY_SD_CARD:
            printf("Card Type:SDHC V2.0\r\n");
            break;
        case SDIO_MULTIMEDIA_CARD:
            printf("Card Type:MMC Card\r\n");
            break; 
}
printf("Card ManufacturerID:%d\r\n",SDCardInfo.SD_cid.ManufacturerID);//制造商ID
printf("Card RCA:%d\r\n",SDCardInfo.RCA);                             //卡相对地址
printf("Card Capacity:%d MB\r\n",(u32)(SDCardInfo.CardCapacity>>20)); //显示容量
printf("Card BlockSize:%d\r\n\r\n",SDCardInfo.CardBlockSize);         //显示块大小
} 
int main(void)
{
    u8 Key; u8 t = 0; u8 * buf = 0;
    u32 sd_size;
    delay_init();    //延时函数初始化
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);    //分组2
    uart_init(115200);    //串口初始化115200
    LED_Init();           //初始化与LED连接的硬件接口
    KEY_Init();           //初始化按键
    LCD_Init();           //初始化LCD
    my_mem_init(SRAMIN);  //初始化内部内存池
    
    POINT_COLOR = RED;    //设置字体为红色
    LCD_ShowString(30, 50, 200, 16, 16, "WarShip STM32");  
    LCD_ShowString(30, 70, 200, 16, 16, "SD CARD TEST");  
    LCD_ShowString(30, 90, 200, 16, 16, "aaa@qq.com");  
    LCD_ShowString(30, 110, 200, 16, 16, "2015/1/20");  
    LCD_ShowString(30, 130, 200, 16, 16, "KEY0:Read Sector 0");  
 
    while(SD_Init())        //检测不到SD卡
    {
       LCD_ShowString(30, 150, 200, 16, 16, "SD_Card Error");
       delay_ms(500);  
       LCD_ShowString(30, 150, 200, 16, 16, "Please Check");
       delay_ms(500);  
       LED0 != LED0;        //DS0闪烁
    }
 
    show_sdcard_info();     //打印SD卡信息
    POINT_COLOR=BLUE;       //设置字体为蓝色
    //检测SD成功
    LCD_ShowString(30,150,200,16,16,"SD Card OK ");
    LCD_ShowString(30,170,200,16,16,"SD Card Size: MB");
    LCD_ShowNum(30+13*8,170,SDCardInfo.CardCapacity>>20,5,16);//显示SD卡容量
    while(1)
    {
        key=KEY_Scan(0);
        if(key==KEY0_PRES)         //KEY0按下了
        {
            buf=mymalloc(0,512);   //申请内存
            if(buf==0)
            { 
                printf("failed\r\n");
                continue;
            }
            if(SD_ReadDisk(buf,0,1)==0)                 //读取扇区0内容
            {
                LCD_ShowString(30,190,200,16,16,"USART1 Sending Data...");
                printf("SECTOR 0 DATA:\r\n");
                for(sd_size=0;sd_size<512;sd_size++)
                    printf("%x ",buf[sd_size]);         //打印数据
                printf("\r\nDATA ENDED\r\n");
                LCD_ShowString(30,190,200,16,16,"USART1 Send Data Over!");
            }
            myfree(0,buf); //释放内存
        }
        t++;
        delay_ms(10);
        if(t==20)
        {
            LED0=!LED0;
            t=0;
        }
    } 
}

43.4下载验证

    在代码编译成功后,下载到F103开发板上,可以看到LCD显示如下内容(SD卡已插上):
SD卡实验_STM32F1开发指南_第四十三章
SD卡实验_STM32F1开发指南_第四十三章
    打开串口调试助手,按下KEY0可以看到开发板返回来的数据:
SD卡实验_STM32F1开发指南_第四十三章
SD卡实验_STM32F1开发指南_第四十三章
     注意,不同的SD卡读出的扇区0内容是不相同的,不用惊讶。

相关标签: stm32 SD卡