文档库 最新最全的文档下载
当前位置:文档库 › C8051F020 SPI读写SD卡全攻略

C8051F020 SPI读写SD卡全攻略

C8051F020 SPI读写SD卡全攻略
C8051F020, SD卡, FAT16
我从一个月前刚放暑假开始弄单片机读写SD卡,八月初完成FAT16,已经可以写入TXT文件,并可在windows上读出。由于网上资料比较散,所以一开始走了不少弯路,现在写一篇总结,将我遇到的问题详细地列出来,希望能帮助和我一样的菜鸟们少走弯路。文中说到的一些问题对高手而言只是常识性的,还请包涵。 第一步:搭电路
我买了一小块蜂窝板和一个SD插槽,按照标准电路焊接上,由于用的是SPI模式,所以选电路图的时候要看好,SD卡上的引脚顺序不要看错,912345678,最后两根挨得很紧,焊接时不要连上了,相关引脚一定要按照要求接上47K的上拉电阻。电路虽然简单,但一定要确保无误。还有一点,SD卡座种类不一样,有位学长买的是弹簧式的,焊完以后刚开始初始化都能成功,放了几天突然不行了,检查各引脚都没问题,最后发现是卡座的问题,这样的硬件问题很难发现,还浪费时间,所以卡座还是直接买简单的好。
第二步:设置硬件SPI
我用的是sililab的C8051F020,自带硬件SPI,如果不带可以用软件模拟,关于软件模拟SPI这块网上有很多现成的程序。这一歩首先按照020手册写了一段程序,当然是将其设为主模式,这时候CONFIG(这个软件可以视窗化操作C8051F的大多数寄存器并自动生成代码)会分配4个引脚,CLK时钟位,MISO和MOSI两个数据传输位,还有一个NSS位,这个脚用不上,不要将其当作CS片选位,CS位选一个普通IO既可。所有从单片机上输出的引脚都设为推挽输出。设置完成后最好弄个示波器看一下输出波形是否和你想象中的一样,这样就能确保你的SPI工作没问题了,这一步也是关键,SPI是底层通信的基础。
第三步:SD卡初始化
这一步正式进入单片机调试SD部分,了解SD卡的时序后(后面我会上传这部分资料),网上众说纷纭,还有说要看完178页英文PDF,我都晕了,这个看完估计我都是专家了。关于初始化命令有很多说法,我发的是CMD0和CMD1就可以成功初始化。
解释一下这个命令格式的含义:这是个一个字节的命令格式为01xx xxxx 后六位是CMD后面的数字的二进制值,如CMD1=0100 0001=0x41 程序中写为CMD | 0x40 CMD代表后面的数字。
需要注意初始化时SPI速率不能超过400K,我设的是100K,初始化没问题,还有发CMD0之前要向SD卡发送至少74个时钟周期,只有CMD0需要这样特殊。
下面是发送CMD0的程序段:
retry=0;
CSH;
do{
for(i=0;i<10;i++) SPI_WriteByte(0xFF); // 发送 至少 74个时钟周期 注意片选线此时为高
r1=mmcSendCommand(MMC_GO_IDLE_STATE, 0); // 发送CMD0,注意此时片选

线才为低
retry++;
if(retry>0xfe) return -1; //尝试的发送次数可以适当多一些
} while(r1 != MMC_R1_IDLE_STATE); //正确应答为1

尝试发送的次数至少为200,有人建议2000的,随便,如果不稳定,比如有时候收的到有时候收不到,就可以适当增大发送次数
这是紧随其后发送的CMD1程序段:
retry=0;
do{
r1=mmcSendCommand(MMC_SEND_OP_COND, 0); // 发送CMD1
retry++;
if(retry>100) return -1;
} while(r1!=0); //正确应答为0
初始化有这两个就可以完成了,有的程序中还会加上
mmcSendCommand(MMC_CRC_ON_OFF, 0); //关CRC校验
mmcSendCommand(MMC_SET_BLOCKLEN, 512);//设置块长度为512字节
这个无关紧要,SPI模式下默认没有CRC校验的,而且每块字节数就是512,这个块大小就别改了,你要设置成别的大小,后面加FAT会出麻烦的。
下面解释一下上面程序中的uint8_t mmcCommand(uint8_t cmd, uint32_t arg)函数
uint8_t mmcCommand(uint8_t cmd, uint32_t arg)
{
uint8_t r1,retry=0;
SPI_WriteByte(cmd|0x40); // send command
SPI_WriteByte(arg>>24);
SPI_WriteByte(arg>>16);
SPI_WriteByte(arg>>8);
SPI_WriteByte(arg);
SPI_WriteByte(0x95); // 讲解标记(1)
SPI_WriteByte(0xFF);// 讲解标记(2)

while((r1=SPI_WriteByte(0xFF))==0xFF)if(retry++>8)break;
return r1;
}
arg这个参数看一下SD的SPI命令格式就知道这个字段是命令的属性,一般为0
讲解标记(1)
CRC位这个0x95只对CMD0有意义,发送其他命令时这个位可为任意值,所以不必修改
讲解标记(2)
这个容易忽略,不忽略第一个字节你就可能收不到正确的响应,许多程序中这个叫做dummy values。特别注意看时序图,后面写命令的程序中是要发送两个字节的,不要和这个搞混了。
写命令程序段:
uint8_t mmcWrite(uint32_t sector, uint8_t* buffer){
uint8_t r1;
uint16_t i;
CSL; // assert chip select
r1 = mmcCommand(MMC_WRITE_BLOCK,sector<<9); // issue command
if(r1 != 0)return r1;
SPI_WriteByte(0xFF); // send dummy
SPI_WriteByte(MMC_STARTBLOCK_WRITE); // send data start token

for(i=0; i<512; i++){
SPI_WriteByte(*buffer++); // write data
}

SPI_WriteByte(0xFF); // write 16-bit CRC (dummy values)看清楚!两个字节哦!
SPI_WriteByte(0xFF);

r1 = SPI_WriteByte(0xFF); // read data response token
if((r1&MMC_DR_MASK)!=MMC_DR_ACCEPT)return r1; //讲解标记(1)
while(!SPI_WriteByte(0xFF)); // wait until card not busy
CSH; // release chip select
return 0;
}
讲解标记(1)
这个很重要!!!我在这浪费了一个星期!!!
许多程序包括网上的大多资料都说这个回应为0x05,可是我每次都收不到这个回应,收到的是0xE5,本来我以为是程序有问题,其实不然,我查了资料,找到

了这个响应令牌的8位的含义,发现高三位是保留位,而0xE5和0x05低五位是一样的说明响应是正确的,这个高三位可能由于厂家不同值不一样。
这个程序是比较完善的,响应r1与上个MMC_DR_MASK(宏定义值为0x0001 1111)就把高三位与成0了,网上有的程序是没有这个过程的。

如果你想验证只能是否能正常读写,可以将值赋进数组写入到SD卡的一个扇区里(这里的扇区是指物理扇区,这个概念在FAT文件中再说)在用数组读出来,在仿真器里看是否一样,这个过程可能用不了winhex这款软件,因为你写入的那个扇区可能是引导区,造成你将卡插到电脑中会提示你格式化。
下面是CMD0的波形图


原来以为这个波形图有问题,因为时序图上片选线在数据传送过程中是一直低的,还在网上问了一阵子,可惜没人理我,其实是正确的,中间的电平跳变是由于SPI发送函数开头和末位有把片选拉低和拉高,片选线一旦拉高,数据线就会跟着变高,所以出现了跳变,我尝试着把SPI发送函数的开头末位片选去掉,发现这样也是可以的。

第四步:加FAT
如果上面测试都没问题,那么底层通信就没有问题了,到目前为止我们一直是把SD当成一个大的FLASH来操作的,但是要想在电脑上把用单片机写的程序读出来就要按照一定规则往里面写,这个规则就是FAT。我用的是2G的金士顿SD卡,正好可以用FAT16,FAT16最大支持2G。
这一块的内容可以参照https://www.wendangku.net/doc/2616616077.html,/document/fat/#索引
里面的讲解很详细,会帮助你理解文件系统
需要把握的思路是:先用电脑把SD卡格式化成FAT16的(即FAT),然后读写的规则是:找到MBR(主引导区)读相关字节得到逻辑引导区的地址,在逻辑扇区里读出BPB数据,再对FAT表,根目录和数据区进行对应操作
这一块可以用winhex看SD卡的物理扇区和逻辑扇区,以便对照

这一块我讲几个我遇到的问题
1>>如果是VISTA操作系统,你要以管理员身份进入,不然无法看到物理扇区,即鼠标停在winhex的图标上点右键选取以管理身份运行即可(不要笑,我刚开始就不知道应该这样操作,呵呵)
2>>有些SD卡是没有主引导区即MBR的,这样更好,逻辑扇区就和物理扇区一样了,那么怎样判断有没有MBR呢?最简单的你用winhex看一下物理和逻辑扇区,如果数据一样就是没有MBR了。再有严谨一点的方法:注意看PDF中对逻辑引导区的解释,逻辑引导区基本是以E9和EB开头的,单凭这一点就可以用函数轻松判断了。所以写文件之前先弄清你的SD卡有没有MBR,想了解更多请参照https://www.wendangku.net/doc/2616616077.html,/bg4uvr/blog/item/b59f2fde196efd5fcdbf1aee.html
有没有MBR是可以转换的,具体请看:https://www.wendangku.net/doc/2616616077.html,/bg4uvr/blog/it

em/9489a6295f7bcff998250a48.html
3>>这里说一些关于编译的问题,加入FAT部分的程序后,工程中程序文件会比较多,这里要注意重复包含的问题,这一块网上很多,不再重复。有时候错误并不在指针提示的那一行
比如有时候指的那一行只有int a;这样的语句,这时候注意往上面看,是不是定义函数时漏了末尾的分号,造成编译器将a也当作其形参了,这种错误有时候很隐蔽,比如int a;上面只有#include "b.h",这时候就要去b中看看,是不是文件末尾定义的那个函数后面忘了分号
还有编译器报出"segment too large"这时候须把编译器选项中的Memory Model 中的Variable 设成XDATA这是对sililab IDE开发环境而言的,或者放入xdata数组里也行Project——>Tool Chain Intergration——>Compiler——>Custmize——>Memory Model ——>Variable——>Large: XDATA
这个IDE官方下载的会限制代码大小,因为里面用的编译器是限制版的,这时候你如果装了正版的KEIL就可以用KEIL的编译器从而不受代码限制。具体做法:Project——>Tool Chain Intergration将Compiler和Linker中的路径修改到KEIL的相应路径即可。
还有就是有的程序是不支持文件夹嵌套的

现在我说一个最最重要的问题,也是我遇到的最后一个问题,字节序问题,请先参考https://www.wendangku.net/doc/2616616077.html,/sunshine1314/archive/2008/04/20/2309655.aspx
了解了字节序,当然这里不用管比特序,如果你用的是AVR单片机,恭喜你,和SD卡还有电脑的字序是一样的,不用转换字序的,网上大多数程序你都能用,我用的C8051F020则需要,每次和卡交换大于一个字节的数据时都需要做一次转换,由于程序中只用到了8 16 32这类数据,所以我只加入两字节转换函数和四字节转换函数,函数体如下:
uint16_t two_byte_exchange(uint16_t h)
{
if(!Big_Small_ending_Switch) //如果没有使能字节转换则返回原值
return h;

return (h >> 8) + (h << 8);

}
uint32_t four_byte_exchange(uint32_t h)
{

if(!Big_Small_ending_Switch) //如果没有使能字节转换则返回原值
return h;

return (h >> 24) + ((h >> 16) << 8)+ ((h >> 8) << 16)+ (h << 24);


}

以上就是我遇到的大部分问题了,希望对大家有所帮助,由于水平有限,不足之处还请前辈们指教!
最后还要感谢我的辅导员猴哥,还有东海学长,青蓝冰和风无影学长。

相关文档