笨笨熊 https://bbs.21ic.com/?644857 [收藏] [复制] [RSS] 付出不一定有收获,但是不付出肯定没有收获     ——笨笨熊

日志

SD卡底层驱动 SPI模式(转载)二

已有 4139 次阅读2011-5-6 11:14 |个人分类:SD卡|系统分类:单片机

硬件平台:stm32
编译环境:MDK401
驱动方式:SPI总线
S.jpg


编写的函数如下:
1、 u8 SD_Init()                                                         SD卡初始化(复位和激活)
2、 u8 SD_SendCommand(u8 cmd, u32 arg, u8 crc)      向SD卡发送一个命令
3、 u32 SD_GetCapacity(void)                                     获取SD卡的容量
4、 u8 SD_GetCID(u8 *cid_data)                                 获取SD卡的CID信息,包括制造商信息
5、 u8 SD_GetCSD(u8 *csd_data)                               获取SD卡的CSD信息,包括容量和速度信息
6、 u8 SD_ReadMultiBlock(u32 sector, u8 *buffer, u8 count)   读SD卡的多个block
7、 u8 SD_ReadSingleBlock(u32 sector, u8 *buffer)       读SD卡的一个block
8、 u8 SD_ReceiveData(u8 *data, u16 len, u8 release)    从SD卡中读回指定长度的数据,放置在给定位置
9、 u8 SD_WaitReady(void)                                          等待SD卡Ready
10、u8 SD_WriteMultiBlock(u32 sector, const u8 *data, u8 count)  写入SD卡的N个block
11、u8 SD_WriteSingleBlock(u32 sector, const u8 *data)  写入SD卡的一个block

SD卡命令   推荐SD卡命令共分为12类,分别为class0到class11,不同的SDd卡,主控根据其功能,支持不同的命令集 如下:
Class0 (卡的识别、初始化等基本命令集)
CMD0:复位SD 卡.
CMD1:读OCR寄存器.
CMD9:读CSD寄存器.
CMD10:读CID寄存器.
CMD12:停止读多块时的数据传输
CMD13:读 Card_Status 寄存器
Class2 (读卡命令集):
CMD16:设置块的长度
CMD17:读单块.
CMD18:读多块,直至主机发送CMD12为止 .
Class4(写卡命令集) :
CMD24:写单块.
CMD25:写多块.
CMD27:写CSD寄存器 .
Class5 (擦除卡命令集):
CMD32:设置擦除块的起始地址.
CMD33:设置擦除块的终止地址.
CMD38: 擦除所选择的块.
Class6(写保护命令集):
CMD28:设置写保护块的地址.
CMD29:擦除写保护块的地址.
CMD30: Ask the card for the status of the write protection bits
class7:卡的锁定,解锁功能命令集  
class8:申请特定命令集 。
class10 -11 :保留
其中 class1,    class3,class9:SPI模式不支持
SD卡命令格式如下:
SD命令.JPG

解释:6个字节,第一个字节最高两个BIT为01,2-5BYTE为参数,还有一个字节的CRC校验

SD卡SPI模式硬件连接:(典型连接)
FD.JPG

SD卡两种模式,引角使用情况注意:本帖所用SPI模式
SD引角.JPG


SPI时序如下:
SPI读时序.jpg

SPI写时序.jpg

SD卡时序如下:
复位.JPG

初始化.JPG

设置长度.JPG

读.JPG

写.JPG


时序分析(这里我举一个例子)

SD卡读扇区时序
1、拉低CS,选中SD卡
2、发送命令17(从本帖的上面查)
3、命令发送完毕后不断的读取输出线,读到0xFE,表示命令成功
4、准备接收数据,0xFE是数据的开始,此后就可以接收512个字节了   (当然在初始化的时候有用命令16设置块的大小为512
5、数据接收完毕,拉高CS线
6、最后等8个时钟,你可以随便发一个BYTE就可以了(严格按照时序,大家别偷懒哦)

SD卡读扇区时序




  1. u8 SD_ReceiveData(u8 *data, u16 len, u8 release)

  2. {

  3. u16 retry;

  4. u8 r1;

  5. SD_CS_Reset();  // 启动一次传输

  6. //等待SD卡发回数据起始令牌0xFE

  7. retry = 0;

  8. do

  9. {

  10. r1 = SPI_ReadWriteByte(0xFF);

  11. retry++;

  12. if(retry>200) //200次等待后没有应答,退出报错

  13. {

  14. SD_CS_Set();

  15. return 1;

  16. }

  17. }while(r1 != 0xFE);

  18. //开始接收数据

  19. while(len--)

  20. {

  21. *data = SPI_ReadWriteByte(0xFF);

  22. data++;

  23. }

  24. //下面是2个伪CRC(dummy CRC)

  25. SPI_ReadWriteByte(0xFF);

  26. SPI_ReadWriteByte(0xFF);

  27. //按需释放总线,将CS置高

  28. if(release == RELEASE)

  29. {

  30. //传输结束

  31. SD_CS_Set();

  32. SPI_ReadWriteByte(0xFF);

  33. }

  34. return 0;

  35. }
复制代码


SD_SendCommand




  1. u8 SD_SendCommand(u8 cmd, u32 arg, u8 crc)

  2. {

  3. unsigned char r1;

  4. unsigned char Retry = 0;

  5. // SPI_ReadWriteByte(0xff);

  6. //片选端置低,选中SD卡

  7. SD_CS_Reset();

  8. //发送

  9. SPI_ReadWriteByte(cmd | 0x40); //分别写入命令

  10. SPI_ReadWriteByte(arg >> 24);

  11. SPI_ReadWriteByte(arg >> 16);

  12. SPI_ReadWriteByte(arg >> 8);

  13. SPI_ReadWriteByte(arg);

  14. SPI_ReadWriteByte(crc);

  15. //等待响应,或超时退出

  16. while((r1 = SPI_ReadWriteByte(0xFF))==0xFF)

  17. {

  18. Retry++;

  19. if(Retry > 200)

  20. {

  21. break;

  22. }

  23. }

  24. //关闭片选

  25. //在总线上额外增加8个时钟,让SD卡完成剩下的工作

  26. SPI_ReadWriteByte(0xFF);

  27. SD_CS_Set();

  28. //返回状态值

  29. return r1;

  30. }
复制代码


SD卡初始化代码:
这里我不敢保证大家的卡都能初始化成功,我自己的是可以,我发送CMD0和CMD1就可以初始化啦
网上有更兼容的初始化代码,大家可以看看哦!
调试的时候大家看返回值就可以了,哪里没成功就修改哪里哦!




  1. u8 SD_Init()

  2. {

  3. unsigned char time,temp,i;

  4. SD_CS_Set(); //关闭片选

  5. for(i=0;i<0x0A;i++) //初始时,首先要发送最少74个时钟信号,这是必须的!!!

  6. {

  7. SPI_ReadWriteByte(0xff); //120个时钟

  8. }

  9. SD_CS_Reset(); //打开片选

  10. time=0;

  11. do

  12. {

  13. temp=SD_SendCommand(CMD0, 0 ,0x95);//写入CMD0  复位SD卡

  14. time++;

  15. if(time==200)

  16. {

  17. SD_CS_Set(); //关闭片选

  18. }

  19. }while(temp!=0x01);



  20. time=0;

  21. do

  22. {

  23. temp=SD_SendCommand(CMD1, 0 , 0xff); //写入CMD1 激活SD卡

  24. time++;

  25. if(time==200)

  26. {

  27. SD_CS_Set(); //关闭片选

  28. }

  29. }while(temp!=0);



  30. SPI_SetSpeed(1);

  31. temp = SD_SendCommand(CMD59, 0, 0x01);

  32. if(temp != 0x00)

  33. {

  34. return temp; //命令错误,返回r1

  35. }

  36. temp=SD_SendCommand(CMD16,512,0xff);

  37. if(temp!=0x00)

  38. {

  39. return temp ;

  40. //命令错误,返回r1

  41. }

  42. SD_CS_Set(); //关闭片选

  43. SPI_ReadWriteByte(0xff); //按照SD卡的操作时序在这里补8个时钟

  44. return 0;

  45. }
复制代码


写入SD卡的N个block




  1. u8 SD_WriteMultiBlock(u32 sector, const u8 *data, u8 count)

  2. {

  3. u8 r1;

  4. u16 i;

  5. //设置为高速模式

  6. SPI_SetSpeed(SPI_SPEED_HIGH);

  7. //如果不是SDHC,给定的是sector地址,将其转换成byte地址

  8. if(SD_Type != SD_TYPE_V2HC)

  9. {

  10. sector = sector<<9;

  11. }

  12. //如果目标卡不是MMC卡,启用ACMD23指令使能预擦除

  13. if(SD_Type != SD_TYPE_MMC)

  14. {

  15. r1 = SD_SendCommand(ACMD23, count, 0x00);

  16. }

  17. //发多块写入指令

  18. r1 = SD_SendCommand(CMD25, sector, 0x00);

  19. if(r1 != 0x00)

  20. {

  21. return r1; //应答不正确,直接返回

  22. }

  23. //开始准备数据传输

  24. SD_CS_Reset();

  25. //先放3个空数据,等待SD卡准备好

  26. SPI_ReadWriteByte(0xff);

  27. SPI_ReadWriteByte(0xff);

  28. //--------下面是N个sector写入的循环部分

  29. do

  30. {

  31. //放起始令牌0xFC 表明是多块写入

  32. SPI_ReadWriteByte(0xFC);

  33. //放一个sector的数据

  34. for(i=0;i<512;i++)

  35. {

  36. SPI_ReadWriteByte(*data++);

  37. }

  38. //发2个Byte的dummy CRC

  39. SPI_ReadWriteByte(0xff);

  40. SPI_ReadWriteByte(0xff);

  41. //等待SD卡应答

  42. r1 = SPI_ReadWriteByte(0xff);

  43. if((r1&0x1F)!=0x05)

  44. {

  45. SD_CS_Set(); //如果应答为报错,则带错误代码直接退出

  46. return r1;

  47. }

  48. //等待SD卡写入完成

  49. if(SD_WaitReady()==1)

  50. {

  51. SD_CS_Set(); //等待SD卡写入完成超时,直接退出报错

  52. return 1;

  53. }

  54. //本sector数据传输完成

  55. }while(--count);

  56. //发结束传输令牌0xFD

  57. r1 = SPI_ReadWriteByte(0xFD);

  58. if(r1==0x00)

  59. {

  60. count = 0xfe;

  61. }

  62. if(SD_WaitReady())

  63. {

  64. while(1)

  65. {

  66. }

  67. }

  68. //写入完成,片选置1

  69. SD_CS_Set();

  70. SPI_ReadWriteByte(0xff);

  71. return count; //返回count值,如果写完则count=0,否则count=1

  72. }
复制代码



后记:
关于SPI读写,我就不发了,大家自己写就是了,当然读扇区,写扇区都是差不多的,只是命令不同,所以我只列举了其中一个,大家把函数都写好。IO设置好,初始化成功后,就可以读一个扇区了,大家取出来,可以用WINHEX对比一下数据是否一致,一般命令成功后就没有什么大问题的。

祝大家成功!如果本帖对你有任何帮助,你要支持我哦!
如果有任何不懂的地方,请联系我   E-mail:lovelyboy_zjb@163.com   QQ:331799388


路过

鸡蛋

鲜花

握手

雷人

评论 (0 个评论)