SPI模式下SD卡调试的最主要工作是熟悉SD的相关规范,特别是理解关于SD卡的的读写时序。以下为调试过程中的一些要点:
SD卡的SPI通信接口使其可以通过SPI通道进行数据读写。从应用的角度来看,采用SPI接口的好处在于,很多CPU内部自带SPI控制器,不光给开发上带来方便,同时也见降低了开发成本。然而,它也有不好的地方,如失去了SD卡的性能优势,要解决这一问题,就要用SD方式,因为它提供更大的总线数据带宽。SPI接口的选用是在上电初始时向其写入第一个命令时进行的。以下介绍SD卡的驱动方法,只实现简单的扇区读写。
SD卡自身有完备的命令系统,以实现各项操作。命令格式如下:
命令的传输过程采用发送应答机制,过程如下:
每一个命令都有自己命令应答格式。在SPI模式中定义了三种应答格式,如下表所示:
字节 |
位 |
含义 |
||
1 |
7 |
开始位,始终为0 |
||
6 |
参数错误 |
|||
5 |
地址错误 |
|||
4 |
擦除序列错误 |
|||
3 |
CRC错误 |
|||
2 |
非法命令 |
|||
1 |
擦除复位 |
|||
0 |
闲置状态 |
|||
字节 |
位 |
含义 |
||
1 |
7 |
开始位,始终为0 |
||
6 |
参数错误 |
|||
5 |
地址错误 |
|||
4 |
擦除序列错误 |
|||
3 |
CRC错误 |
|||
2 |
非法命令 |
|||
1 |
擦除复位 |
|||
0 |
闲置状态 |
|||
2 |
7 |
溢出,CSD覆盖 |
||
6 |
擦除参数 |
|||
5 |
写保护非法 |
|||
4 |
卡ECC失败 |
|||
3 |
卡控制器错误 |
|||
2 |
未知错误 |
|||
1 |
写保护擦除跳过,锁/解锁失败 |
|||
0 |
锁卡 |
|||
字节 |
位 |
含义 |
||
1 |
7 |
开始位,始终为0 |
||
6 |
参数错误 |
|||
5 |
地址错误 |
|||
4 |
擦除序列错误 |
|||
3 |
CRC错误 |
|||
2 |
非法命令 |
|||
1 |
擦除复位 |
|||
0 |
闲置状态 |
|||
2~5 |
全部 |
操作条件寄存器,高位在前 |
1. 复位及其初始化。
SD卡的复位很简单,规范里写的也很详细,个人认为基本不存在太大问题。首先是74个clk,然后CS_LOW;发送CMD0,收到的应答是0x01;接着发送CMD1,收到的应答应该是0x00;最后CS_HIGH。至此,复位完成。需要注意的问题:此时的时钟不宜太快,可以在SD卡初始化完成后可提高数据读写速度;在发送命令之前和收到应答位之后,主控制器应该发送8个时钟完成相应操作。具体的时序图如下:
初始化是通过发送CMD1来进行的,具体时序图如下:
需要注意的是:SD卡的初始化是非常重要的,只有进行了正确的初始化,才能进行后面的各项操作。在初始化过程中,SPI的时钟不能太快,否则会造初始化失败。在初始化成功后,应尽量提高SPI的速率。在刚开始要先发送至少74个时钟信号,这是必须的。在以前的实验中,很多是因为疏忽了这一点,而使初始化不成功。随后就是写入两个命令CMD0与CMD1,使SD卡进入SPI模式。
2. 读取CID及其CSD
CID寄存器存储了SD卡的标识码。每一个卡都有唯一的标识码。
CID寄存器长度为128位。它的寄存器结构如下:
名称 |
域 |
数据宽度 |
CID划分 |
生产标识号 |
MID |
8 |
[127:120] |
OEM/应用标识 |
OID |
16 |
[119:104] |
产品名称 |
PNM |
40 |
[103:64] |
产品版本 |
PRV |
8 |
[63:56] |
产品序列号 |
PSN |
32 |
[55:24] |
保留 |
- |
4 |
[23:20] |
生产日期 |
MDT |
12 |
[19:8] |
CRC7校验合 |
CRC |
7 |
[7:1] |
未使用,始终为1 |
- |
1 |
[0:0] |
读取CID 的时序图如下:
CSD(Card-Specific Data)寄存器提供了读写SD卡的一些信息。其中的一些单元可以由用户重新编程。具体的CSD结构如下:
名称 |
域 |
数据宽度 |
单元类型 |
CSD划分 |
CSD结构 |
CSD_STRUCTURE |
2 |
R |
[127:126] |
保留 |
- |
6 |
R |
[125:120] |
数据读取时间1 |
TAAC |
8 |
R |
[119:112] |
数据在CLK周期内读取时间2(NSAC*100) |
NSAC |
8 |
R |
[111:104] |
最大数据传输率 |
TRAN_SPEED |
8 |
R |
[103:96] |
卡命令集合 |
CCC |
12 |
R |
[95:84] |
最大读取数据块长 |
READ_BL_LEN |
4 |
R |
[83:80] |
允许读的部分块 |
READ_BL_PARTIAL |
1 |
R |
[79:79] |
非线写块 |
WRITE_BLK_MISALIGN |
1 |
R |
[78:78] |
非线读块 |
READ_BLK_MISALIGN |
1 |
R |
[77:77] |
DSR条件 |
DSR_IMP |
1 |
R |
[76:76] |
保留 |
- |
2 |
R |
[75:74] |
设备容量 |
C_SIZE |
12 |
R |
[73:62] |
最大读取电流@VDD min |
VDD_R_CURR_MIN |
3 |
R |
[61:59] |
最大读取电流@VDD max |
VDD_R_CURR_MAX |
3 |
R |
[58:56] |
最大写电流@VDD min |
VDD_W_CURR_MIN |
3 |
R |
[55:53] |
最大写电流@VDD max |
VDD_W_CURR_MAX |
3 |
R |
[52:50] |
设备容量乘子 |
C_SIZE_MULT |
3 |
R |
[49:47] |
擦除单块使能 |
ERASE_BLK_EN |
1 |
R |
[46:46] |
擦除扇区大小 |
SECTOR_SIZE |
7 |
R |
[45:39] |
写保护群大小 |
WP_GRP_SIZE |
7 |
R |
[38:32] |
写保护群使能 |
WP_GRP_ENABLE |
1 |
R |
[31:31] |
保留 |
- |
2 |
R |
[30:29] |
写速度因子 |
R2W_FACTOR |
3 |
R |
[28:26] |
最大写数据块长度 |
WRITE_BL_LEN |
4 |
R |
[25:22] |
允许写的部分部 |
WRITE_BL_PARTIAL |
1 |
R |
[21:21] |
保留 |
- |
5 |
R |
[20:16] |
文件系统群 |
FILE_OFRMAT_GRP |
1 |
R/W |
[15:15] |
拷贝标志 |
COPY |
1 |
R/W |
[14:14] |
永久写保护 |
PERM_WRITE_PROTECT |
1 |
R/W |
[13:13] |
暂时写保护 |
TMP_WRITE_PROTECT |
1 |
R/W |
[12:12] |
文件系统 |
FIL_FORMAT |
2 |
R/W |
[11:10] |
保留 |
- |
2 |
R/W |
[9:8] |
CRC |
CRC |
7 |
R/W |
[7:1] |
未用,始终为1 |
- |
1 |
|
[0:0] |
读取CSD 的时序图如下:
综合上面对CID与CSD寄存器的读取,可以知道很多关于SD卡的信息,如卡的容量等信息。
3. 扇区数据读取
扇区读是对SD卡驱动的目的之一。SD卡的每一个扇区中有512个字节,一次扇区读操作将把某一个扇区内的512个字节全部读出。过程很简单,先写入命令,在得到相应的回应后,开始数据读取。
读取单块数据流程:CS_LOW-->8个clk-->发送CMD17-->接收响应R1-->接收读数据起始令牌0xFE-->接收数据-->接收CRC-->8个clk-->CS_HIGH。
数据读取的时序图如下:
4. 扇区数据写入
扇区写是SD卡驱动的另一目的。每次扇区写操作将向SD卡的某个扇区中写入512个字节。过程与扇区读相似,只是数据的方向相反与写入命令不同而已。
写入单块数据流程:CS_LOW-->8个clk-->发送CMD24-->接收响应R1-->写入读数据起始令牌0xFE-->写入数据-->接收CRC-->8个clk-->CS_HIGH。
数据写入的时序图如下:
总之,进行单块数据读写的流程图如下:
5. 文件系统
个人认为相对比较困难的是FAT32文件系统的理解和掌握。文件以只写方式打开文件的流程如下图所示。
FAT32分为几个区域,这里将简单对它们的结构与在文件存储中的功能要点进行描述。
Ø DBR(DOS BOOT RECORD 操作系统引导记录区)
DBR是我们进军FAT32的首道防线。其实DBR中的BPB部分才是这一区域的核心部分(第12~90字节为BPB),只有深入详实的理解了BPB的意义,才能够更好的实现和操控FAT32。其实对我们有用的数据只不过90个字节。仅仅是这90个字节就可以告诉我们关于磁盘的很多信息,比如每扇区字节数、每簇扇区数、磁道扇区数等等。对于这些信息的读取,只要遵循DBR中的字段定义即可。
Ø FAT(文件分配表)
FAT表是FAT32文件系统中用于磁盘数据(文件)索引和定位引进的一种链式结构。可以说FAT表是FAT32文件系统最有特色的一部分,它的链式存储机制也是FAT32的精华所在,也正因为有了它才使得数据的存储可以不连续,使磁盘的功能发挥得更为出色。
Ø 根目录区
在FAT32中其实已经把文件的概念进行扩展,目录同样也是文件,从根目录的地位与其它目录是相同的,因此根目录也被看作是文件。既然是文件就会有文件名,根目录的名称就是磁盘的卷标。
每一个文件都对应一个描述它属性的结构,定义如下:
FAT32文件目录项32个字节的定义 |
|||
字节偏移量 |
字数量 |
定义 |
|
0~7 |
8 |
文件名 |
|
8~10 |
3 |
扩展名 |
|
11 |
1 |
属性字节 |
0x00 (读写) |
0x01 (只读) |
|||
0x02 (隐藏) |
|||
0x04 (系统) |
|||
0x08 (卷标) |
|||
0x10 (子目录) |
|||
0x20 (归档) |
|||
12 |
1 |
系统保留 |
|
13 |
1 |
创建时间的10毫秒位 |
|
14~15 |
2 |
文件创建时间 |
|
16~17 |
2 |
文件创建日期 |
|
18~19 |
2 |
文件最后访问日期 |
|
20~21 |
2 |
文件起始簇号的高16 位 |
|
22~23 |
2 |
文件的最近修改时间 |
|
24~25 |
2 |
文件的最近修改日期 |
|
26~27 |
2 |
文件起始簇号的低16 位 |
|
28~31 |
4 |
表示文件的长度 |
至此对于FAT32文件系统根目录下的文件读取就已经实现了,至于多级子目录结构可以像查找文件的首簇一样查找某一级目录名的首簇,然后再到此簇下去找下一级目录的首簇,直到最终的文件。