经过一周左右时间的摸索,终于明白了如何用msp30在SD卡实现FAT32文件系统,很开心~在学习的过程中,也发现一个问题,就是网上系统地讲SD卡的资料很少,而讲SDHC卡的资料则更少,所以决定写一篇博客与大家分享,由于SDHC卡大部分内容都与SD卡一样,所以下文除非是特别介绍SDHC卡,其余都会以SD卡代替SDHC卡。如果发现文中存在问题,欢迎指正,谢谢。
首先,我们先说明一下本文的主要内容,本文的主要侧重点在于利用msp430(其它单片机应该类似)驱动SD卡。驱动方式选用SPI方式,驱动成功之后,将FAT32文件系统移植过来。所以如果想要仔细学习FAT32文件系统的,可以忽略本文了,想要快速地利用单片机在SD卡上实现FAT32文件系统的,可以看一下。大家可以交流一下。
一、开发之前的准备
1、准备WinHex工具
工欲善其事,必先利其器。在开发之前,我们必须要先准备好需要的工具,除了相应的单片机开发平台,我们还需要一个很重要的工具,WinHex。WinHex可以直接查看磁盘内部的16进制数据。我们把SD卡用卡槽接到电脑上之后,打开WinHex,点击Tools--Open Disk,然后在Physical Media下选择自己的SD卡,即可打开自己的SD卡。如下图所示。这里需要注意的是,一定要在Physical Media下选自己的SD卡,这样看到才是物理地址,否则看到的是逻辑地址,可能会跟你的实际操作不一致,如你在地址为1024的地方写了一段数据,用WinHex在这里却看不到你写的数据。
2、SD卡和SDHC卡
目前大家口头上经常说的是SD卡,但实际上,目前所用的大容量的卡其实均是SDHC卡。SD的容量最大只能到2G,而SDHC卡的容量最小2G,最大32G。所以,如果你的“SD卡”的容量超过2G了,那其实那是SDHC卡。SD卡和SDHC卡在用户使用上,除了容量大了,几乎体会不到别的区别,但是在开发过程中,却是存在一些区别的,SD卡是按字节寻址,SDHC卡是按块寻址。这一点一定要记住。什么是按字节寻址,什么是按块寻址,我们在下文会简单讲解。
3、寻址方式及“块”和“簇”的简单介绍
首先,我们介绍一下“块”的概念,在SD卡或者SDHC卡里,一个块是由512个字节组成,每次读写的时候,数据都是以块为单位进行读写的。SD卡读写是按字节寻址,这是指其读写地址的单位为字节,而我们前面提到,读写的时候数据是以块为单位进行读写的,所以读写SD卡的时候,其地址应该为512的倍数,即读写SD卡时,其地址参数为0,512,1024……而SDHC卡则是以块为单位寻址的,即读写SDHC卡时,地址参数为1时,读写的地址实际上用512字节开始,到1023字节出结束。从WinHex中,我们也可以看到,在地址为512倍数的上一行,有一条线分割了上下两行,这其实也就是把块分割开了,看起来更清楚了。
在这里,我们再简单介绍一下“簇”。我们在格式化SD卡的时候,可能会忽略一个选项,分配单元大小,如下图所示,这个其实就是我们常说的“簇”。在文件系统中,存储文件是以“簇”为单位进行存储的。就比如说一栋楼,将它划分为若干个房间,每个房间的大小一样,同时给每个房间一个房间号.这时,每个房间的大小,就是分配单元. 在建立分区时,会出现分配单元大小的选项。每个簇只能存放一个文件。文件就是按照这个簇的大小被分成若干块存储在磁盘上的。比如一个512字节大的文件,当分配单元为512字节时,它占用512字节的存储空间;一个513字节大的文件,当分配单元为512字节时,它占用1024字节的存储空间,但当分配单元为4096时,它就会占用4096字节的存储空间。所以,这也就解释了为什么有时候我们明明看SD卡或者U盘上还有一定容量的存储空间,却存不下文件的问题。
二、SD卡命令介绍
SD卡里,用到的所有命令都是6个字节,第一个字节为命令代号,紧接着的4个字节为该命令所需的参数,最后一个字节为校验。命令里,第一个字节可以通过命令数+0x40得到。如cmd8的第一个字节为0x48。[page]
在SPI模式下,我们需要用到底命令如下表所示:
关于这些命令,这里有几个注意事项:
1)CMD0的最后一个字节必须为0x95,因为SD卡刚上电还没有处于SPI模式,还需要这样一个校验的字节来校验,进入SPI模式之后,则不需要校验了,最后一个字节可以随意;
2)CMD55和CMD41(该命令也常称为ACMD41)必须一起使用,这里所说的两个一起使用是指两个命令必须连续发送,而不是先一直发送CMD55,收到正确的回复之后,再一直发送CMD41,,这样做的后果是一直收到0x05,不是收到0x00;此外,发送完CMD41之后,会先收到一个0x01,然后才是0x00;
3)读写SD卡时,SD卡和SDHC卡的地址信息是不同的,一个是以字节为单位,一个是以块为单位;读SD卡时,写入CDM17之后,要一直读,直道读到0xfe,说明后续的512字节是数据;写SD卡时,写进数据之后,回复为0x05,说明成功写入。
这些命令对应的回复整理的还不太全面,有些命令的回复是好几个字节的,这里没有详细写出。
三、开发过程
1、硬件方面
由于我们是采用SPI方式驱动SD卡,所以只需要用到4跟线,分别为片选信号线(CS)、数据输入线(DIN)、数据输出线(DOUT)和时钟线(CLK)。这里,我的这四根线都采用外部上拉的方式上拉了。设计的原理图如下图所示。这里需要注意的是,SD卡的数据输入应该接单片机的数据输出,SD的数据输出应该接单片机的数据输入。
在SPI模式下,我们需要用到的4个脚的信息可以从下表中获取,多于的一些脚在SPI模式下是用不到,这里所有的脚我都上拉了,其实有一些脚不上拉也没关系,而且MSP430有些型号内部可以设置上拉的。此外,SD卡一般有9个引脚,原理图里有11个引脚,主要是因为原理图里画的是卡槽,多出来的几个引脚是检测是否有卡,以及卡是否写保护的。
2、软件方面
软件方面,我们可以分为四个步骤:
- 先编写出利用SPI接口读写一个字节的函数
- 然后利用这些函数编写出写命令函数
- 利用写命令函数写出读写数据块以及初始化函数
- 移植FAT32文件系统
(1)读写一个字节的函数
该函数有两种方式实现,一个是利用单片机自带的SPI模块来实现,这种方式的好处是读写速度快,但是单片机上的SPI模块数量有限,这样不太灵活,另一个是利用普通IO实现,这样的好处是使用十分灵活,但是速度不够快,MSP430的IO的极限翻转速度大概在320K左右。
(2)写命令函数
写命令函数其实是调用写一个字节的函数,实现发送6个字节命令的效果,只是这里有一点需要注意,在写命令函数里,为了提高兼容性,应该先拉高片选信号,给8个周期的时钟信号之后,再拉低片选信号,开始发命令的操作。
(3)读写块函数
读写块函数其实是调用写命令函数和读一个字节的函数,这里需要注意的有两点,一个就是上文提到的地址信息的问题,另一个就是在读写完块数据之后,需要拉高片选信号,并且再给8个周期的时钟信号,以提高稳定性。
(4)初始化函数
初始化函数其实是整个SD卡驱动里,最为重要的一个环节,在SD卡的初始化阶段里,为了能够识别出多种类型的SD卡(MMC卡、SD卡、SDHC卡等),需要调用多条命令,并且根据应答数据来判断是哪种类型的卡。在初始化的时候,需要注意的是要放慢SPI的速度,这样初始化的成功率会高一些。具体初始化的过程,后面我会再整理一下,做成一张图上传的~
(5)移植FAT32文件系统
有了上述的基础函数之后,就可以把FAT32文件系统移植过来了,只需要把底层的驱动函数改成自己写的驱动函数就可以了~
四、总结
前段时间,想用MSP430在SD上实现FAT32文件系统,由于时间有限,自己只完成了SD卡的底层驱动这一部分。文件系统的实现,则要感谢znFAT的团队,因为上层的文件系统是移植的他们的znFAT。感觉znFAT的可移植性真的很强,而且很稳定,移植起来还是比较顺利的~大家如果想对FAT32文件系统有深入的了解的话,可以买znFAT代码编写者写的一本书看下,目前简单翻了一遍,感觉思路很清晰的~
最后要感谢网上众多技术达人对于SD卡驱动的总结,之前也遇到不少奇奇怪怪的问题,也是看很多他们的总结,才能顺利完成这个任务~