SD卡已经看了两天了,主要是因为测试出来的卡容量不对,所以一直找原因,最终还是发现了,总比不过是单位上面出现了问题,或许是之前没有接触到SD的缘故吧,所以对其中的一些寄存器很不了解,一切都是重新开始,对照这寄存器手册,理解程序,修改程序。一步步还是总结一下!
首先关于SD卡的协议是有必要了解的,我今天花了一上午的课堂时间来理解这个SD卡的协议,就是基于这个文档的,这个文档很适合入门SD协议的(个人认为)。http://download.****.net/detail/king_bingge/5218183
初识SD之后,就可以开始正式学习SD卡了!
一、要使用SD卡,那么首先肯定得对SD卡进行初始化,那么如何进行初始化呢?(命令的参数暂且不提)
1、这里涉及到很多指令了。协议规定了在给SD卡上电之后需要给出至少74个时钟脉冲后,才能进行相关的SD初始化工作,虽然是这么说,但是我不给74个时钟,他照样能初始化,看看。
;i<;i++)SD_SPI_ReadWriteByte(0XF);
但是,或许为了能够更加成功的初始化吧,所以有这个规定所以,我们还是规规矩矩的好,给它74个时钟,没关系的嘛!
2、然后就是协议中说到当我们复位或者上电的时候,SD卡的SD控制寄存器处于卡识别模式中的空闲模式的,暂且这样称吧。本来我们是不需要发送复位命令了的,但是我们不知道我们的SD所支持的电压范围。所以,我们最好还是先给出一条复位指令,然后紧接着一条获取工作电压的指令,这样也是比较保险,如果多SD卡工作电压有疑问的,那么就得去看芯片手册了。有了这个知识,那下面的代码就不成问题了
retry=; do { r1=SD_SendCmd(CMD0,,0x95);//进入IDLE状态 }while((r1!=0X01) && retry--); SD_Type=; //默认无卡 if(r1==0X01) { ) //SD V2.0 { ;i<;i++)buf[i]=SD_SPI_ReadWriteByte(0XFF); //Get trailing return value of R7 resp ]==]==0XAA) //卡是否支持2.7~3.6V
3、协议上还提到ACMD41命令的目的是给予 SD卡控制器一个识别 SD卡是否可以在所给Vdd 范围下工作的机制,如果 SD 卡无法在指定 Vdd 范围内工作,则它会进入非活动状态(Inactive state ),所以我们接下来需要发送这个命令,但是在发送这个命令之前,要知道这是一个应用型的命令,所以要加上CMD55命令,所以有了下面的代码。
]==]==0XAA)//卡是否支持2.7~3.6V { retry=0XFFFE; do { SD_SendCmd(CMD55,,0X01); //发送CMD55 r1=SD_SendCmd(CMD41,0x40000000,0X01);//发送CMD41 }while(r1&&retry--); ,)//鉴别SD2.0卡版本开始 //获取供电状态 { ;i<;i++)buf[i]=SD_SPI_ReadWriteByte(0XFF);//得到OCR值 ]&0x40)SD_Type=SD_TYPE_V2HC; //检查CCS else SD_Type=SD_TYPE_V2; }
这样就获取了卡的类型了,至此卡的初始化基本完成,当然根据协议上,我们还可以在这里修改相对地址之类的。如果有必要的话,可以这样做!
二、初始化完SD卡,接下来如果你想查看我们SD的容量,可以这样做!
之前就是因为卡容量的问题,所以郁闷了好久,理解了个大概!注意这里函数名是读取扇区数,实际上返回的值是我们卡的容量,这里得注意了。
1、首先看代码
u32 SD_GetSectorCount(void) { u8 csd[]; u32 Capacity_KB,Capacity_MB ; u8 n; u16 csize; //取CSD信息,如果期间出错,返回0 ) ; n = (csd[] & ) + ((csd[] & ) >> ) + ((csd[] & ) << ) + ; csize = (csd[] >> ) + ((u16)csd[] << ) + ((u16)(csd[] & ) << ) + ; Capacity_KB= (u32)csize << (n - );//得到扇区数 ,这里的单位是KB Capacity_MB = Capacity_KB/; return Capacity_MB; }
这个计算的问题必须得看SD卡的手册,也就是128位的CSD寄存器。这里我把我分析的过程贴出来,我不得不说比较乱,或许只有我自己能看懂了,懒得整理了,仅供参考!
//My SD_Card //CSD寄存器中的值如下: 7f ff bit(-) csd0 - csd3 5f cb bit(--) csd4 - csd7 db df ff bit(--) csd8 - csd11 bit(---) csd12 -csd15 csize {,} csize_muti{,} read {,} csize = = csize_muti = = read = = //计算公式: //blocknr = (csize+1)*mult = //mult = (csize_muti < 8)*(2^(csize_muti + 2)) //block_len = (read < 12)*(2^(read)) //capacity = blocknr * block_len = 13*4*3516*98304 //依据下面代码来计算我的容量: n = (csd[] & ) + ((csd[] & ) >> ) + ((csd[] & ) << ) + ; csize = (csd[] >> ) + ((u16)csd[] << ) + ((u16)(csd[] & ) << ) + ; Capacity_KB= (u32)csize << (n - );//得到扇区数 ,这里的单位是KB // 00 7f ff 32 5f 59 83 cb 76 db df ff 96 40 00 97 Capacity_MB = Capacity_KB/; //1、(csd[8] >> 6) 得到的是 bit62和bit63的值 去掉2位 //2、((u16)csd[7] << 2)得到的是bit64--bit69的值 去掉6位 //3、((u16)(csd[6] & 3) << 10)得到的是bit70--bit73的值
其实我的问题还是出现在单位上面!
这样我们就能看到显示的容量值了,我的是1G的。打印出来是971M,和windows下面的是一致的。其实我们可以通过读SD卡的引导扇区,从而把相关的信息读取出来,而不需要使用那些个寄存器。那么现在我们的计算公式就是(这只是我自己信手写的,如果想要理解,你必须得看扇区的内容咯,我就是对照着那个MBR来写的)
x=(((buf_read[])**+(buf_read[])*+(buf_read[])))*//; //打印大小 printf("\n SD Sector Size:%d Mb\n",x);
虽然不怎么雅观,但是能用就是了。
2、接下来看如何用SPI读一个扇区吧,先看代码
u8 SD_ReadDisk(u8*buf,u32 sector,u8 cnt) { u8 r1; ;//转换为字节地址 ) { r1=SD_SendCmd(CMD17,sector,0X01);//读命令 )//指令发送成功 { r1=SD_RecvData(buf,);//接收512个字节 } }else { r1=SD_SendCmd(CMD18,sector,0X01);//连续读命令 do { r1=SD_RecvData(buf,);//接收512个字节 buf+=; }); SD_SendCmd(CMD12,,0X01); //发送停止命令 } SD_DisSelect();//取消片选 return r1;// }
这几行代码能实现单个和多个扇区的读写,跟踪进去可以能够看到这个函数
//SPIx 读写一个字节 //TxData:要写入的字节 //返回值:读取到的字节 u8 SPIx_ReadWriteByte(u8 TxData) { u8 retry=; while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET) //检查指定的SPI标志位设置与否:发送缓存空标志位 { retry++; ); } SPI_I2S_SendData(SPI1, TxData); //通过外设SPIx发送一个数据 retry=; while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET); //检查指定的SPI标志位设置与否:接受缓存非空标志位 { retry++; ); } return SPI_I2S_ReceiveData(SPI1); //返回通过SPIx最近接收的数据 }
这个函数的功能实现了既可以进行发送又可以进行读取数据,现在来总结一下!
读一个扇区的过程
1、先发命令r1=SD_SendCmd(CMD17,sector,0X01);//读单个扇区的命令
2、然后将接收到得数据存在临时数组里面
u8 SD_RecvData(u8*buf,u16 len) { ;//等待SD卡发回数据起始令牌0xFE while(len--)//开始接收数据 { *buf=SPIx_ReadWriteByte(0xFF); buf++; } //下面是2个伪CRC(dummy CRC) SD_SPI_ReadWriteByte(0xFF); SD_SPI_ReadWriteByte(0xFF); ;//读取成功 }
那么对应的写扇区也类似的
1、先发写单个扇区的命令 r1=SD_SendCmd(CMD24,sector,0X01);//写命令
2、将Buffer里面的内容写到对应的扇区里面去
u8 SD_SendBlock(u8*buf,u8 cmd) { u16 t; ;//等待准备失效 SD_SPI_ReadWriteByte(cmd); if(cmd!=0XFD)//不是结束指令 { ;t<;t++)SPIx_ReadWriteByte(buf[t]);//提高速度,减少函数传参时间 SD_SPI_ReadWriteByte(0xFF);//忽略crc SD_SPI_ReadWriteByte(0xFF); t=SD_SPI_ReadWriteByte(0xFF);//接收响应 ;//响应错误 } ;//写入成功 }
到这里,读写扇区就完成了,下一步就是,使用文件系统来进行操作了。