目录
1. SD卡识别和卡启动时序(CMD2/ACMD41命令时序)
SD卡(SecureDigital MemoryCard)即:安全数码卡,它是在MMC的基础上发展而来,是一种基于半导体快闪记忆器的新一代记忆设备,它被广泛地于便携式装置上使用,例如数码相机、个人数码助理(PDA)和多媒体播放器等。SD卡由日本松下、东芝及美国SanDisk公司于1999年8月共同开发研制。SD卡按容量分类,可以分为以下几类。
一、SD卡内部结构-内部寄存器
名称 | bit宽度 | 描述 |
OCR | 32 | OCR记录了电压范围、卡的工作模式(如是否支持高容量模式)以及卡是否准备好进行数据传输。 |
CID | 128 | 通常用于获取卡的制造商ID和产品型号等。 |
RCA | 16 | 用于标识SD卡的相对地址。在初始化阶段,SD卡会通过RCA与主机系统建立连接。 |
DSR | 16 | 用于SD卡驱动阶段的控制,主要控制SD卡的电气特性和速率。 |
CSD | 128 | 包含有关SD卡具体规格的详细信息,包括卡的容量、访问模式、速度等级等。 |
SCR | 64 | 包含SD卡的配置和特性,如支持的功能、卡的速度等级等。 |
SSR | 512 | 它包含卡的工作状态、错误状态、命令结果等信息。 |
CSR | 32 | 显示SD卡当前的状态,包括是否准备好进行读写操作、卡的健康状态等。 |
1.CSD寄存器
2.OCR寄存器
3.CID寄存器
4.SCR寄存器
二、SD卡拓扑结构
SD卡总线拓扑参考图 SD卡总线拓扑。虽然可以共用总线, 但不推荐多卡槽共用总线信号,要求一个单独SD总线应该连接一个单独的SD卡。当一个主机SD总线上有多个SD卡时,就会通过SD卡的RCA地址来区分每个卡。
三、SD卡硬件接线
为了驱动SD卡,首先的第一步就是观察SD卡引脚接线情况,通常我们在画原理图时,都是使用的SD卡卡槽与MCU进行接线。SD卡可以使用SDIO协议和SPI协议进行通信,这里我们主要介绍SDIO协议。下图中的TF卡只是容量小一点的SD卡。
引脚号 | SD卡 | TF卡 |
1 | data3 | data2 |
2 | cmd | data3 |
3 | vss | cmd |
4 | vdd | vdd |
5 | clk | clk |
6 | vss | vss |
7 | data0 | data0 |
8 | data1 | data1 |
9 | data2 |
SD卡硬件接线图:
其中
![]()
TF卡硬件接线图:
![]()
以F1C100S为例,接线图如下所示:
SDIO总线和USB总线类似,SDIO总线也有两端,其中一端是主机(HOST)端,另一端是设备端(DEVICE),采用HOST- DEVICE这样的设计是为了简化DEVICE的设计,所有的通信都是由HOST端发出命令开始的。在DEVICE端只要能解析HOST的命令,就可以同HOST进行通信了,SDIO的HOST可以连接多个DEVICE。
我们发现上图中的TF卡使用的协议为SDIO协议,主要有4根数据线,1根命令线,1根时钟线组合而成。为了驱动这个SD/TF卡我们就需要去了解SDIO协议的时序。
四、SD卡命令及其配置流程
1. 常用CMD命令
命令类型 | 命令 | 命令号 | 响应类型 | 功能描述 |
基本命令(class0) | CMD0 | 0x00 | - | 复位所有卡的状态为空闲状态。 |
CMD2 | 0x02 | R2 | 获取卡的CID。 | |
CMD3 | 0x03 | R6 | 获取卡的相对地址(RCA)。 | |
CMD7 | 0x07 | R1b | 选择卡并切换到传输状态。(OCR) | |
CMD8 | 0x08 | R7 | 询问卡是否支持主机支持电压。 | |
CMD9 | 0x09 | R2 | 获取卡的CSD。 | |
CMD12 | 0x0C | R1b | 停止传输命令(用于终止数据传输)。 | |
CMD13 | 0x0D | R1 | 读取 SD 卡的 Card Status Register(卡状态寄存器)。 | |
CMD15 | 0x0F | - | 释放卡的访问控制,解除卡锁定。 | |
CMD16 | 0x10 | R1 | 用于指定卡的数据块大小,通常是512字节或者更大的块。 | |
读取命令(class2) | CMD17 | 0x11 | R1 | 读取单个数据块。标准 SD读取数据块长度受 CMD16 控制。对于 SDHC、SDXC卡,读取数据块为固定的 512字节,不受CMD16 控制 |
CMD18 | 0x12 | R1 | 读取多个数据块。块大小由 CMD16 控制。 | |
擦除命令(class3) | CMD32 | 0x20 | R1 | 设置要擦除的第一个块的地址(擦除起始地址)。 |
CMD33 | 0x21 | R1 | 设置要擦除的最后一个块的地址(擦除结束地址)。 | |
CMD38 | 0x26 | R1b | 从块的起始位置到结束位置执行擦除操作的命令。 | |
写入命令(class4) | CMD24 | 0x18 | R1 | 写入单块数据。块大小由 CMD16 控制。 |
CMD25 | 0x19 | R1 | 写入多个数据块。块大小由 CMD16 控制。 | |
CMD27 | 0x1B | R1 | 对 CSD 的可编程位进行编程 | |
特殊命令(class8) | CMD55 | 0x37 | R1 | 发送前置命令,告知卡后续要执行ACMD命令。 |
ACMD6 | 0x06 | R1b | 配置卡的总线宽度。 | |
ACMD23 | 0x17 | R1 | 告诉SD卡后续要写入多少个数据块。 | |
ACMD41 | 0x29 | R3 | 启动卡并查询OCR响应,卡上电完成。 | |
ACMD51 | 0x33 | R1 | 读取SD卡的SCR寄存器。 |
2. 响应类型
在SD卡命令中,响应类型是指SD卡在接收到命令后返回的响应格式,所有响应都是通过CMD信号线发送。不同命令会有不同的响应类型,这些响应类型决定了卡返回数据的结构。响应类型的关键是根据命令和数据位数来判断的。根据响应类型,可以知道要解析多少个字节,以及如何解读响应内容。
SDIO总共有7个响应类型(代号:R1~R7),其中SD卡没有R4、R5类型响应。特定的命令对应有特定的响应类型,比如当主机发送CMD3命令时,可以得到响应R6。
根据响应内容大小可以分为短响应和长响应。短响应是48bit长度,只有R2类型是长响应,其长度为136bit。
而有些响应后面会带有 ' b' 标志,表示 " 忙 "标志。用于命令执行期间卡可能需要一些时间来完成操作。响应中会有一个“忙”位,表示卡是否仍在执行某些操作。
(1)短响应
R1响应(正常响应命令):R1响应会返回32位的卡状态。
Bit位 47 46 [45:40] [39:8] [7:1] 0 位宽 1 1 6 32 7 1 值 "0" "0" x x x "1" 描述 开始位 传输位 命令号 卡状态 CRC7 结束位 R1b响应:和R1响应差不多,但是R1b响应在数据线上会有busy信号,主机在发送完数据后,应该检查 busy 信号。
Bit位 47 46 [45:40] [39:8] [7:1] 0 位宽 1 1 6 32 7 1 值 "0" "0" x x x "1" 描述 开始位 传输位 命令号 卡状态 CRC7 结束位 R3响应(OCR 寄存器):R3响应会返回32位的OCR寄存器的值作为ACMD41的响应。
Bit位 47 46 [45:40] [39:8] [7:1] 0 位宽 1 1 6 32 7 1 值 "0" "0" "111111" x "111111" "1" 描述 开始位 传输位 保留位 OCR寄存器 保留位 结束位 R6响应(CMD3专用响应):
Bit位 47 46 [45:40] [39:8] [7:1] 0 位宽 1 1 6 16 16 7 1 值 "0" "0" "000011" x x x "1" 描述 开始位 传输位 命令号CMD3 [31:16]卡的RCA [15:0]卡的状态 CRC7 结束位 R7响应(CMD8专用响应):Bit[19:16]表明卡支持的电压范围。卡接受提供的电压范围就返回 R7 响应。卡会在响应的参数中返回电压范围和检查模式。
Bit位 47 46 [45:40] [39:20] [19:16] [15:8] [7:1] 0 位宽 1 1 6 20 4 8 7 1 值 "0" "0" "001000" "00000h" x x x "1" 描述 开始位 传输位 命令号CMD8 保留位 接受电压 检查模式 CRC7 结束位
(2)长响应
R2响应(CID,CSD 寄存器):R2响应会返回SD卡中CID或者CSD寄存器的值。其中CID寄存器的值用于CMD2和CMD10响应,CSD寄存器的值用于CMD9响应。
Bit位 135 134 [133:128] [127:1] 0 位宽 1 1 6 127 1 值 "0" "0" "111111" x "1" 描述 开始位 传输位 保留位 CID/CSD寄存器的值(内部CRC7) 结束位
(2)命令和响应使用举例
以F1C100s举例,该芯片中有4个响应寄存器,如下所示。当我们发送命令后需要接收响应时,要根据不同的响应类型来获取响应寄存器的值。
例如短响应通常只需要 1 个响应寄存器,响应数据会被放入响应寄存器0(通常是
SD0_RESP0
),因此,获取响应时只需要读取SD0_RESP0
即可。// 假设响应数据保存在 sd_command.response 数组中 sd_command.cmdidx = 3; //CMD3 sd_command.cmdarg = 0; // 根据具体命令的参数设置 sd_command.resp_type = SD_RESPONSE_CRC | SD_RESPONSE_PRESENT; //SD_RESPONSE_PRESENT:设置需要响应。 SD_RESPONSE_CRC :CRC校验。 result = sd_card_send_command(&sd_command, 0); // 读取四个响应寄存器的值 uint32 response= *SD0_RESP0; //获取响应寄存器0的值
对于长响应,通常需要读取 4 个响应寄存器(如
SD0_RESP0
,SD0_RESP1
,SD0_RESP2
,SD0_RESP3
),SD0_RESP0
存储低位部分[31:0],依次类推。// 假设响应数据保存在 sd_command.response 数组中 sd_command.cmdidx = 2; //CMD2 sd_command.cmdarg = 0; // 根据具体命令的参数设置 sd_command.resp_type = SD_RESPONSE_CRC | SD_RESPONSE_PRESENT | SD_RESPONSE_136; //SD_RESPONSE_PRESENT:设置需要响应。 SD_RESPONSE_CRC :CRC校验。SD_RESPONSE_136:长响应。 result = sd_card_send_command(&sd_command, 0); // 读取四个响应寄存器的值 uint32 response0 = *SD0_RESP0; //获取响应寄存器0的值 uint32 response1 = *SD0_RESP1; //获取响应寄存器1的值 uint32 response2 = *SD0_RESP2; //获取响应寄存器2的值 uint32 response3 = *SD0_RESP3; //获取响应寄存器3的值
3. SD卡初始化流程
(1)SD 卡初始化时,SDIO_CK时钟不可超过 400KHz;初始化完成后,可设为最大(不可超过 SD 卡最大操作频率 25MHz))并可更改数据总线宽度(默认只用 SDIO_D0数据线进行初始化)。
(2)开启SDIO电源,为卡提供时钟,并延时两毫秒。
(3)发送CMD0复位命令,让卡到空闲状态。
(4)发送CMD8命令,询问卡是否支持主机支持电压。根据电压值判断SD卡支持版本。
(5)反复发送ACMD41(先发送CMD55,再发送ACMD41),等待OCR回应,卡上电完成和卡容量类型。如果正常则进入准备状态。
(6)发送CMD2,进入识别状态,获得卡信息(CID)。包含卡的制造商、产品ID等信息。
(7)发送CMD3,获得卡的相对地址(RCA)。这个地址用于区分不同的卡,并在后续的操作中标识当前使用的卡。进入待机状态(下一步就要进行数据传输)。
(8)发送CMD9,将获取到的SD卡的RCA地址作为参数传入SD参数寄存器中,获得卡的数据(CSD)。包含卡的规格、容量、访问模式等信息。
(9)发送CMD7,将获取到的SD卡的RCA地址作为参数传入SD参数寄存器中,用于选择该卡,切换到传输状态。此时,SD卡已经准备好进行数据的读写操作。
(10)检查卡是否被锁定,卡被锁定时不能进行写操作,通常用于保护卡内的数据不被修改。我们在写操作时,SD卡要处于非锁定模式。
(11)通过读取SD卡的SCR寄存器,判断卡是否支持宽总线(4位数据线宽度)。如果支持宽总线,则可以修改时钟为25 MHz,提高数据传输速率。
(12)发送ACMD6(先发送CMD55,再发送ACMD6),修改总线宽度,(如从1位宽设置为4位宽)。
补充知识点:如何设置SDIO的频率呢?以F1C100s为例。
答:通过配置F1C100S的CCU寄存器。CCU 是 Clock Control Unit(时钟控制单元) 的简称,它是一个用于管理和控制芯片内部时钟源的硬件模块。通过 CCU 寄存器,系统可以动态地设置各个模块的时钟频率、启用或禁用特定的时钟,以及选择时钟源等。
假设我们需要设置SDIO的时钟频率为400khz,我们可以选择OSC24M的时钟源,然后进行选择预分频和主分频比。
答:可以设置预分频为4,主分频为14。频率=24M /(4*(14+1))= 400khz。即:
*CCU_SDMMC0_CLK |= ( 0x80000000 | (0x00 << 24)| (0x00 << 20) | (0x4 << 16) | (0x00 << 8) | (15 - 1));
4. SD卡写操作流程
(1)发送
CMD7
命令,将初始化获取到的SD卡的RCA地址作为参数传入SD参数寄存器中,用于选择对应的SD卡,确保卡处于“传输状态”。(2)设置块大小,通过
CMD16
命令设置块大小(通常是512字节),如果不设置,则默认为512字节。(3)配置SDIO的数据路径状态机DPSM(这个通常是硬件配置步骤,不需要通过命令发送)。在配置块大小后,需要配置SDIO(SD卡接口)的数据路径状态机(DPSM)来处理数据的传输。DPSM 控制数据传输的流动,它会确保数据能够被正确地写入到SD卡中。这个步骤通常是在硬件层面配置的,与命令流程关系不大,但非常重要。
(3)SD卡使用
CMD24
(单块写命令)或CMD25
(多块写命令)来执行数据写操作。命令需要包含块地址和要写入的数据。(4)轮询读取SDIO状态寄存器(查看F1C100S手册):监控传输过程,等待数据完成写入FIFO。
TXFIFO_EMPTY
:表示发送 FIFO(数据缓冲区)为空,意味着数据已经传输完成。
RXFIFO_READY
:表示接收 FIFO 准备好接收数据。
CMD_DONE
:表示命令已经执行完成。
ERROR
:表示传输过程中出现了错误。(5)发送停止传输命令(CMD12):当数据传输完成后,在需要停止传输时使用。单块写命令时不需要发送停止命令,多块写时需要发送。
单块写:
多块写:
5. SD卡读操作流程
(1)发送
CMD7
命令,将初始化获取到的SD卡的RCA地址作为参数传入SD参数寄存器中,用于选择对应的SD卡,确保卡处于“传输状态”。(2)发送
CMD16
设置块大小,并配置 SDIO 的数据路径状态机。(3)发送
CMD18
连续读取命令,读取多个数据块直到完成。如果是单块读取,发送CMD17
单块读取命令。(4)轮询读取SDIO状态寄存器(查看F1C100S手册),读取 FIFO 中的数据,直到数据接收完毕。
(5)数据传输结束后,发送
CMD12
强制停止传输命令。单块读时不需要发送,多块读时需要发送。(6)检查 FIFO 是否为空,如果不为空,则继续读取剩余数据。
(7)检查错误状态,确保数据读取无误,如果出现错误则处理。
单块读:
多块读:
6. SD卡擦除流程
(1)SD状态检查,参数检査、命令支持检查、检查卡类型:在进行任何写入操作之前,需要检查 SD 卡的状态,以确保卡处于正常工作状态。这通常包括检查卡是否处于准备状态、卡是否支持擦除操作等。在擦除操作前,需要确保所选参数(如要擦除的起始块和结束块的地址)是有效的。确认 SD 卡是否支持擦除命令。
(2)发送
CMD32
设置要擦除的起始块的地址:在执行该命令后,卡会将起始地址加载到擦除缓冲区,并准备好擦除操作。(3)发送
CMD33
设置要擦除的最后一个块的地址:该命令告诉卡擦除的结束地址,范围包括从起始块到结束块。(4)发送 CMD38 擦除选定的块:
CMD38
命令会要求卡擦除从起始块到结束块之间的所有数据块。(5)擦除完成后的检查:轮询读取SDIO状态寄存器(查看F1C100S手册),检查是否存在错误。如果没有错误,擦除操作成功;如果有错误,系统需要根据错误码采取适当的恢复措施。
五、SDIO协议数据包格式
1. 单线传输模式
单线模式下,只有一根数据线
DAT0
传输数据。所以必须要有传输方向位来明确数据的流向。
传输方向位:1:主机传输到SD卡, 0:SD卡传输到主机。
2. 多线传输模式
在多线传输模式下,有4 条数据线(
DAT0
到DAT3
)并行完成的,传输方向由 命令类型 和 数据线的硬件状态 决定,不需要额外的传输方向位。主机通过命令明确告诉 SD 卡当前的操作是读还是写(例如CMD17
是读单块,CMD24
是写单块),因此数据的方向是由命令预先确定的。多线模式强调高效并行传输,省去了传输方向位这一单线模式中的冗余信息。
六、SD卡相关时序分析
1. SD卡识别和卡启动时序(CMD2/ACMD41命令时序)
CMD2 和 ACMD41 的时序如下。命令后跟着两个 Z bit(允许总线切换方向的时间),接着是响应卡发出的 P bit。卡响应主机命令的起始在 Nid 时钟周期后。
2. SD卡发送相对地址时序(CMD3命令时序)
3. SD卡两个命令发送间隔时序(命令时序)
SD卡两个命令发送之间的时序,通常称为命令间隔时序,用来确保主机和卡之间的通信稳定,同时为卡提供充足的时间完成当前命令的处理。
主机在接收到卡的响应后,必须等待 至少 8 个时钟周期,确保 CMD 线为高电平空闲状态,才可发送下一条命令。
4. SD卡读时序
在SD卡的操作中,数据读取(尤其是单块读取)需要通过特定的命令来控制和配置。
(1)单块读
主机通过 CMD7 来选择一个卡进行数据读取操作,通过 CMD16 来设置需要传送数据的有效块长度。读操作的基本总线时序见图4-18。序列以单块读命令(CMD17)开始,CMD17 在参数中指出了起始地址。响应也通过CMD 线发送。
(2)多块读
多块读模式中,在主机读命令之后,卡发送一个连续的数据块流。通过CMD12来中止。
5. 停止命令时序
数据传输在停止命令后的两个时钟周期停止。
七、实践一F1C100s使用SD卡
F1C100s芯片中有很多个专属SD卡的寄存器(详情查看手册即可),在发送命令或者数据时,我们只需要将命令或数据存放到寄存器即可,不需要手动发送。例如命令存放 F1c100的SD命令寄存器中,SD卡的控制器会自动通过CMD线将命令发送到SD卡。也就是说,不需要额外的手动操作通过CMD线发送命令。控制器本身会根据命令寄存器中的数据,控制数据总线(CMD线)来发送命令。
1. 定义相关结构体
struct tagSD_CARD_COMMAND
{
uint16 cmdidx; //命令
uint32 resp_type; //响应类型
uint32 cmdarg; //命令参数
uint32 response[4]; //响应
};
struct tagSD_CARD_DATA
{
uint8 *data; //数据
uint32 flags; //标志
uint32 blocks; //块数
uint32 blocksize; //大小
};
typedef struct tagSD_CARD_COMMAND SD_CARD_COMMAND;
typedef struct tagSD_CARD_DATA SD_CARD_DATA;
SD_CARD_COMMAND sd_command;
SD_CARD_DATA sd_data;
2. 通过寄存器设置基础配置
例如将端口设置为SDIO模式、打开始终、设置频率等等。
3. 发送命令
#define SD_RESPONSE_NONE 0x00000000
#define SD_RESPONSE_PRESENT 0x00000001
#define SD_RESPONSE_136 0x00000002
#define SD_RESPONSE_CRC 0x00000004
#define SD_RESPONSE_BUSY 0x00000008
//Send reset command to the card
sd_command.cmdidx = 0; //发送cmd0
sd_command.cmdarg = 0;
sd_command.resp_type = SD_RESPONSE_NONE;
sd_card_send_command(&sd_command, 0);
//Wait a while for the card to reset
sd_card_delay(50);
//Send card interface condition command to the card
sd_command.cmdidx = 8; //发送CMD8
sd_command.cmdarg = 0x00000155; //31:12 reserved (0x00000), 11:8 supply voltage (0x1 = 2.7 - 3.6V), 7:0 check pattern (0x55)
sd_command.resp_type = SD_RESPONSE_CRC | SD_RESPONSE_PRESENT;
result = sd_card_send_command(&sd_command, 0);
#define SD_CMD_START 0x80000000
#define SD_CMD_SEND_INIT_SEQ 0x00008000
#define SD_CMD_RESP_EXPIRE 0x00000040
#define SD_CMD_LONG_RESPONSE 0x00000080
#define SD_CMD_CHK_RESPONSE_CRC 0x00000100
#define SD_CMD_DATA_EXPIRE 0x00000200
#define SD_CMD_WRITE 0x00000400
#define SD_CMD_AUTO_STOP 0x00001000
#define SD_CMD_WAIT_PRE_OVER 0x00002000
#define SD_CMD_UPCLK_ONLY 0x00200000
#define SD_STATUS_CARD_DATA_BUSY 0x00000200
#define SD_STATUS_FIFO_EMPTY 0x00000004
#define SD_STATUS_FIFO_FULL 0x00000008
int32 sd_card_send_command(PSD_CARD_COMMAND command, PSD_CARD_DATA data)
{
uint32 cmdval = SD_CMD_START; //用于合成命令值
int32 error = SD_OK; //错误类型
uint32 timeout; //超时时间
//判断命令是否为空
if(command == 0) return(SD_ERROR);
//判断命令号
if(command->cmdidx == 12) return(SD_OK);
if(command->cmdidx == 0) cmdval |= SD_CMD_SEND_INIT_SEQ;
//判断响应类型
if(command->resp_type & SD_RESPONSE_PRESENT) cmdval |= SD_CMD_RESP_EXPIRE; //
if(command->resp_type & SD_RESPONSE_136) cmdval |= SD_CMD_LONG_RESPONSE; //长响应
if(command->resp_type & SD_RESPONSE_CRC) cmdval |= SD_CMD_CHK_RESPONSE_CRC; //CRC校验
//如果有数据需要传递
if(data){
cmdval |=(SD_CMD_DATA_EXPIRE | SD_CMD_WAIT_PRE_OVER);
if(data->flags & SD_DATA_WRITE) cmdval |= SD_CMD_WRITE;
if(data->blocks > 1) cmdval |= SD_CMD_AUTO_STOP;
*SD0_BKSR = data->blocksize;
*SD0_BYCR = data->blocks * data->blocksize;
}
//将命令参数加载到命令参数寄存器,将合成命令值加载到命令寄存器。
*SD0_CAGR = command->cmdarg;
*SD0_CMDR = command->cmdidx | cmdval;
//如果有数据
if(data){
if( (error = sd_send_data(data)) ) goto out;
}
//等待命令完成。
if((error = sd_rint_wait(1000, SD_RINT_COMMAND_DONE))) goto out;
//如果有数据
if(data){
if(data->blocks > 1) cmdval = SD_RINT_AUTO_COMMAND_DONE;
else cmdval = SD_RINT_DATA_OVER;
//等待命令完成
if((error = sd_rint_wait(120, cmdval))) goto out;
}
//检查是否允许卡发送忙
if(command->resp_type & SD_RESPONSE_BUSY){
timeout = timer0_get_ticks() + 2000; //设置2超时时间
while(*SD0_STAR & SD_STATUS_CARD_DATA_BUSY){ //等待sd卡发送完毕
if(timer0_get_ticks() > timeout){ //检查是否超时
error = SD_ERROR_TIMEOUT;
goto out;
}
}
}
//获取短响应
command->response[0] = *SD0_RESP0;
//获取长响应
if(command->resp_type & SD_RESPONSE_136){
command->response[1] = *SD0_RESP1;
command->response[2] = *SD0_RESP2;
command->response[3] = *SD0_RESP3;
}
out:
if(error < 0){
//重置 DMA、FIFO 和控制器
*SD0_GCTL |= (SD_GCTL_DMA_RST | SD_GCTL_FIFO_RST | SD_GCTL_SOFT_RST);
sd_card_update_clock(); //更新时钟
}
*SD0_RISR = 0xFFFFFFFF; //清除所有原始中断
*SD0_GCTL |= SD_GCTL_FIFO_RST; //重置FIFO
return(error);
}
4. 完整代码(大概逻辑)
int32 sd_card_init(void)
{
int32 result;
//清空数据结构
memset(&sd_command, 0, sizeof(SD_CARD_COMMAND));
memset(&sd_data, 0, sizeof(SD_CARD_DATA));
*PORT_F_CFG_REG = 0x222222; //配置SDIO引脚
*CCU_BUS_SOFT_RST0 |= CCU_BSRR0_SD0_RST; //De-assert SPI0 reset
*CCU_BUS_CLK_GATE0 |= CCU_BCGR0_SD0_EN; //Open the SPI0 bus gate
sd_card_delay(10); //Wait a short wile for the device to become active
sd_card_clk_init(400000); //Setup the SD card clock at 400KHz
*SD0_HWRST &= ~SD_HWRST_ACTIVE; //Reset the SD card interface
sd_card_delay(50); //Wait a while for the device to be reset
*SD0_HWRST |= SD_HWRST_ACTIVE; //Make the SD card interface active again
sd_card_delay(50); //Wait a while for the device to become active again
*SD0_GCTL |= (SD_GCTL_DMA_RST | SD_GCTL_FIFO_RST | SD_GCTL_SOFT_RST); //Reset the DMA, FIFO and controller
sd_card_delay(50); //Wait a while for the system to be done resetting
*SD0_GCTL &= ~SD_GCTL_CD_DBC_ENB; //Disable card detect de-bounce
*SD0_CKCR |= SD_CKCR_CCLK_CTRL; //Turn card clock of when FSM in idle state
*SD0_BWDR = SD_BWDR_1_BIT_WIDTH; //Set the hardware to use only a single bit for data transfer
*SD0_TMOR = 0xFFFFFFFF; //Set max timeout for data and response.
*SD0_CKCR |= SD_CKCR_CCLK_ENB; //Turn the card clock on
sd_card_update_clock(); //Update the card clock
sd_card_delay(50); //Wait a while for the clock to stabilize
//发送命令0
sd_command.cmdidx = 0;
sd_command.cmdarg = 0;
sd_command.resp_type = SD_RESPONSE_NONE;
sd_card_send_command(&sd_command, 0);
sd_card_delay(50);
//发送命令8
sd_command.cmdidx = 8;
sd_command.cmdarg = 0x00000155; //31:12 reserved (0x00000), 11:8 supply voltage (0x1 = 2.7 - 3.6V), 7:0 check pattern (0x55)
sd_command.resp_type = SD_RESPONSE_CRC | SD_RESPONSE_PRESENT;
result = sd_card_send_command(&sd_command, 0);
rt_kprintf("result:%d\n",result);
sd_card_delay(100);
// rt_kprintf("CMD8_reg:0x%08x\n",*SD0_CMDR);
rt_kprintf("CMD8_val:0x%08x\n",*SD0_RESP0);
if(result == SD_OK)
{
do
{
//发送特殊命令
sd_command.cmdidx = 55;
sd_command.cmdarg = 0;
sd_command.resp_type = SD_RESPONSE_CRC | SD_RESPONSE_PRESENT;
sd_card_send_command(&sd_command, 0);
sd_card_delay(10);
//Send host capacity support information command
sd_command.cmdidx = 41;
sd_command.cmdarg = 0x40FF8000; //Need to figure out these settings
sd_command.resp_type = SD_RESPONSE_CRC | SD_RESPONSE_PRESENT;
result = sd_card_send_command(&sd_command, 0);
sd_card_delay(500);
// rt_kprintf("CMD41_reg:0x%08x\n",*SD0_CMDR);
rt_kprintf("CMD41_val:0x%08x\n",*SD0_RESP0);
//0 means still initializing
} while((sd_command.response[0] & 0x80000000) == 0);
//Check card capacity status. 1 means SDHC or SDXC. 0 means SDSC
if(sd_command.response[0] & 0x40000000){
cardtype = SD_CARD_TYPE_SDHC;
}else{
cardtype = SD_CARD_TYPE_SDSC; //Signal TYPE_SD_LOW...
}
rt_kprintf("cardtype:%d\n",cardtype);
}
sd_card_delay(50);
//Only if a card has been detected
if(cardtype)
{
//Send get CID numbers command to the card
sd_command.cmdidx = 2;
sd_command.cmdarg = 0;
sd_command.resp_type = SD_RESPONSE_CRC|SD_RESPONSE_136 | SD_RESPONSE_PRESENT;
sd_card_send_command(&sd_command, 0);
sd_card_delay(50);
rt_kprintf("CMD2_reg:0x%08x\n",*SD0_CMDR);
//Save the numbers
cardcid[0] = sd_command.response[3];
cardcid[1] = sd_command.response[2];
cardcid[2] = sd_command.response[1];
cardcid[3] = sd_command.response[0];
sd_card_delay(50);
//Send publish RCA command to the card
sd_command.cmdidx = 3;
sd_command.cmdarg = 0;
sd_command.resp_type = SD_RESPONSE_CRC | SD_RESPONSE_136 | SD_RESPONSE_PRESENT;
result = sd_card_send_command(&sd_command, 0);
if(result){
return(SD_ERROR);
}
cardrca= *SD0_RESP0 >>16;
rt_kprintf("cardrca:0x%08x\n",cardrca);
}
//Send get card specific data command to the card
sd_command.cmdidx = 9;
sd_command.cmdarg = cardrca;
sd_command.resp_type = SD_RESPONSE_CRC | SD_RESPONSE_136 | SD_RESPONSE_PRESENT;
result = sd_card_send_command(&sd_command, 0);
if(result){
return(result);
}
//Save the CSD
cardcsd[0] = sd_command.response[3];
cardcsd[1] = sd_command.response[2];
cardcsd[2] = sd_command.response[1];
cardcsd[3] = sd_command.response[0];
//Check the version of the CSD_STRUCTURE
if(((cardcsd[0] & 0xC0000000) == 0) || (cardtype == SD_CARD_TYPE_MMC)){
cardsectors = ((((cardcsd[1] & 0x03FF) << 2) | (cardcsd[2] >> 30)) + 1) << (((cardcsd[2] >> 15) & 0x07) + 2) << ((cardcsd[1] >> 16) & 0x0F) >> 9;
cardsize = cardsectors >> 2;
}
else{
cardsize = ((((cardcsd[1] & 0x03F) << 16) | (cardcsd[2] >> 16)) + 1) * 512;
cardsectors = cardsize * 2;
}
rt_kprintf("cardsectors:0x%08x\n",cardsectors); //0x00ed8000 正确
//Send get card identification data command to the card
sd_command.cmdidx = 10;
sd_command.cmdarg = cardrca;
sd_command.resp_type = SD_RESPONSE_CRC | SD_RESPONSE_136 | SD_RESPONSE_PRESENT;
result = sd_card_send_command(&sd_command, 0);
if(result){
return(result);
}
//Save the CID
cardcid[0] = sd_command.response[3];
cardcid[1] = sd_command.response[2];
cardcid[2] = sd_command.response[1];
cardcid[3] = sd_command.response[0];
//Decode the CID
//Get the manufacturer ID
cardmid = cardcid[0] >> 24;
//Get the product name
cardpnm[0] = cardcid[0] & 0xFF;
cardpnm[1] = (cardcid[1] >> 24) & 0xFF;
cardpnm[2] = (cardcid[1] >> 16) & 0xFF;
cardpnm[3] = (cardcid[1] >> 8) & 0xFF;
cardpnm[4] = cardcid[1] & 0xFF;
//Get the product serial number
cardpsn = (cardcid[2] << 8) | ((cardcid[3] >> 24) & 0xFF);
//Set the card clock to 48MHz and use the 4 bit bus if supported
if(sd_card_set_clock_and_bus(1)){
//Signal error on failure
return(SD_ERROR);
}
return(SD_OK);
}