SD卡驱动分析

SD卡驱动分析重点强调一遍,SD卡的最高工作电压是3.6V,如果采用5V单片机一定要加电平转换芯片,建议还是用3.3V单片机进行操作。首先我们来了解一下SD卡的发展过程。到目前为止(2016年7月)SD卡一共有4个版本,我们直接看一下这个来自SD卡官网(www.sdcard.org)的表格:当然高版本是向下兼容的。接下来了解一下SD卡的引脚,SD卡的引脚和MMC卡是兼容,目前看来在生活中很难见到MMC卡,因此我…

重点强调一遍,SD卡的最高工作电压是3.6V,如果采用5V单片机一定要加电平转换芯片,建议还是用3.3V单片机进行操作。




首先我们来了解一下SD卡的发展过程。


到目前为止(2016年7月)SD卡一共有4个版本,我们直接看一下这个来自SD卡官网(
www.sdcard.org
)的表格:






当然高版本是向下兼容的。




接下来了解一下SD卡的引脚,SD卡的引脚和MMC卡是兼容,目前看来在生活中很难见到MMC卡,因此我们主要讨论SD卡,MMC卡有兴趣的可以自己了解一下。




我们再来看一张来自SD卡官网的图:






右图中第二排引脚是设计给UHS-II总线使用的,第一排的引脚和原来的引脚是一样的。




下面给出SD卡引脚的定义:






这张图来自网上,上面给出了标准SD卡和MicroSD卡在SD模式及SPI模式下的引脚定义,至于MMC卡的引脚定义就忽略吧。特别说明一句,CS线是片选线,低电平有效。




SD卡的通信方式有两种,一种是SPI模式,它的传输方式是单比特传输,另一种是SDIO模式(也叫SD模式),它的传输方式是4比特传输。SDIO模式传输时需要加CRC校验,如果单片机不自带CRC校验功能就需要自己写一下CRC校验,这是有点困难的;而在SPI方式工作时,除了初始化的指令需要CRC,发送其余的指令以及传输数据时都可以关闭CRC,当然如果需要的话可以使用指令打开CRC。SDIO模式传输的速度肯定比SPI模式快,但程序较为复杂,SPI模式相对较为简单,大多数单片机都有硬件SPI,即使没有也可以用软件模拟SPI,因此我们主要讨论如何使用SPI模式驱动SD卡。下文中的内容如果没有特殊说明,都是指SPI模式。




SD卡上电后默认工作在SD模式,如果在发送复位指令(CMD0)时将CS拉低,SD卡将进入SPI模式,否则SD卡会保持在SD模式不变。想要从SPI模式切换为SD模式的唯一方式就是重新上电,电源断开的时间必须大于1ms。




这里给出以SPI方式初始化SD卡的流程图:


这个图是我按照官方手册中给出的流程图翻译的,对部分内容进行了适当取舍。








上电后在发送CMD0之前要先发送至少74个时钟。其中前64个时钟是让SD卡达到正常工作电压,后10个时钟是为了同步。




在初始化阶段时钟频率不要超过400KHz,初始化完成后可以提高频率以加快读写速度。




接下来我们再来说一下SD卡的通信时序。




我们知道SPI一般有以下几种设置:


高位最先发送、低位最先发送;时钟空闲为高电平、时钟空闲为低电平;上升沿采样、下降沿采样等。这里采取的设置是高位优先发送、时钟空闲为低电平、上升沿采样。




SD卡发送指令的格式如下:






参数一般为unsigned int,高字节优先发送。




SD卡的指令分两种,一种是CMD,另一种是ACMD。按照手册中的说法,CMD是标准指令,ACMD是特殊应用指令。下面我将手册中的指令翻译出来,共大家参考。这里只翻译了能用的指令,保留指令还有预留指令都没有翻译。个人能力有限,我尽量翻译得准确一些,有不恰当的地方欢迎大家指出。














在这里说明一点,SD模式和SPI模式下的指令数量是相同的,但是指令的格式有一定不同,网上许多文章将这两者混为一谈。




SD卡在每接收到一条指令后都会有一个应答,具体是哪种应答可以从上面的表中查到,下面给出应答的格式。




R1


长度是一字节,最高位永远为0,其余位是错误指示位,出错时被置一,具体含义如下:


第0位,处于空闲状态:SD卡处于空闲状态,且正在运行初始化程序


第1位,擦除重置:在执行擦除前,一个擦除指令序列被清除,因为收到了一个超出擦除序列的指令。(这个我实在翻译不好,大家凑合看)


第2位,非法指令:就是字面意思


第3位,CRC错误:就是字面意思


第4位,擦除序列错误:在擦除指令序列中发现错误


第5位,地址错误:就是字面意思


第6位,特定错误:指令的参数超出范围


第7位,永远为0








R1b


R1b是在R1的基础上增加了一个忙碌状态指示,当R1的值为0时SD卡处于忙碌状态,而当R1为任何不为0的值时,SD卡才能开始接收下一条指令。




R2


一共两字节,格式如下:


第15位:永远为0


第14位:参数错误


第13位:地址错误


第12位:擦除序列错误


第11位:CRC错误


第10位:非法指令


第9位:擦除重置


第8位:处于空闲状态


第7位:超出范围或CSD寄存器被覆盖


第6位:擦除参数


第5位:写入预擦除违规


第4位:ECC错误


第3位:CC错位


第2位:错误


第1位:跳过写入预擦除或锁定、解锁指令失败


第0位:SD卡锁定




R3


1字节的R1+4字节的OCR寄存器值




R7


1字节的R1+4字节的应答,详细定义这里就不说了,只要知道这个值一般是0x000001AA就行,有兴趣的自己去看手册。




除此之外,SD卡还有控制标志,下面给出这些控制标志。




数据应答标志


每向SD卡写入一个扇区的数据后,SD卡都会返回一个字节的应答,格式如下:






状态位


010:接受本次传输的数据


101:因为CRC错误拒绝本次传输的数据


110:因为写入错误拒绝本次传输的数据


如果在多扇区写入时发生错误,主机应当使用CMD12停止传输。如果错误代码是110,主机可以使用CMD13查看错误原因,使用ACMD22找到写入成功的扇区。




开始传送标志和停止传送标志


单扇区读写和多扇区读的格式是:1字节的开始传输标志+512字节的数据+2字节CRC。




单扇区读写和多扇区读的开始传送标志是:






即0xFE




多扇区写的开始传送标志是:






即0xFC




多扇区写的停止传送标志是:






即0xFD




数据错误标志


如果读取操作失败SD卡不能发送数据,SD卡就会发出这个标志,格式如下:






错误类型


0001:错误


0010:卡片内部控制器错误


0100:ECC错误


1000:超出范围






终于说完了这些数据格式,下面结合代码来看一看,SD卡的初始化、读写单个数据块(扇区)是如何操作的。



  1. #include “spi.h”//这是SPI的驱动
  2. #define WAIT_COUNT 10//等待SD卡进行某些操作的计数器,超过这个次数认为等待超时,高速单片机(如STM32)可以将这个数值适当扩大
  3. sbit SPI_SS = P1^6;//SPI从机选择口, 连接到其它MCU的SS口
  4. //SD卡读写扇区的缓存,小容量卡的扇区大小设置成512字节,大容量卡的扇区就是512字节不可更改
  5. unsigned char xdata SDBlockBuffer[512] = {0};
  6. //初始化完成后可获得SD卡的类型,方便其他程序使用
  7. unsigned char SD_TYPE = 0;
  8. //返回值
  9. //0,初始化失败,电压不正确,SD卡无法使用,或是MMC卡
  10. //1,初始化成功,SD1.0的卡
  11. //2,初始化成功,SD2.0的标准卡(2GB以内)
  12. //3,初始化成功,SD2.0的大容量卡(32GB以内)
  13. unsigned char SDInit()
  14. {
  15.     unsigned char i = 0, r = 0;//i是计数器,r用于保存其他函数的返回值
  16.     char sd_type;
  17.     unsigned char buf[4] = {0};
  18.     
  19.     //用低速初始化SPI,初始化时SPI总线时钟频率不要超过400KHz
  20.     InitSPI(3);
  21.     //使能
  22.     SPI_SS = 0;
  23.     //至少74个时钟信号
  24.     for (i=0; i<10; i++)
  25.     {
  26.         SPI_RW(0xFF);
  27.     }
  28.     //一定次数之内如果没有得到SD卡的返回值,说明通信失败,这个次数可以进行适当更改
  29.     i = WAIT_COUNT;
  30.     do
  31.     {
  32.         r = SDSendCmd(0, 0, 0x95);
  33.     }while(i– && (r & 0x80));
  34.     //注意:在SPI模式下CRC是默认关闭的,但是要保证CMD0的CRC正确,之后的指令的CRC可以是任意值,CMD0的CRC是0x95,已经计算好了,直接用就行。
  35.     //如有需要可以使用CMD59打开或关闭CRC,详情自行查看手册
  36.     
  37.     //r=0x01说明SD卡正常,可以继续进行下一步操作
  38.     if(r == 0x01)
  39.     {
  40.         //CMD8是SD 2.0新添加的指令,如果响应说明是SD2.0或更高版本
  41.         r = SDSendCmd(8, 0x1AA, 0x87);
  42.         //返回0x01是SD2.0
  43.         if (r == 0x01)
  44.         {
  45.             //对于SPI模式的R7,只有5个字节,首先返回的是R1,然后的4个字节,一般情况下是0x0000 01AA
  46.             SPI_SS = 0;
  47.             for (i=0; i<4; i++)
  48.             {
  49.                 buf[i] = SPI_RW(0xFF);
  50.             }
  51.             SPI_SS = 1;
  52.             if (buf[2] == 0x01 && buf[3] == 0xAA)
  53.             {
  54.                 //支持电压范围2.7-3.6
  55.                 //备注[1]
  56.                 //等待SD卡准备好,这个阶段时间可能较长,需要多等一段时间,但不应该超过一秒,超过一秒应重新初始化
  57.                 i = 254;
  58.                 do
  59.                 {
  60.                     SDSendCmd(55, 0, 0);//这是发送CMD55
  61.                     r = SDSendCmd(41, 0x40000000, 0);//发送ACMD41
  62.                     i–;
  63.                 }while(r && i);//如果r的最低位为0说明SD卡准备好了
  64.                 if (i)
  65.                 {
  66.                     //使用CMD58检查SD卡版本和电压,这里只检查版本,不检查电压
  67.                     r = SDSendCmd(58, 0, 0);
  68.                     SPI_SS = 0;
  69.                     for (i=0; i<4; i++)
  70.                     {
  71.                         buf[i] = SPI_RW(0xFF);
  72.                     }
  73.                     SPI_SS = 1;
  74.                     
  75.                     if (buf[0] & 0x40)
  76.                     {
  77.                         //是2.0或更高版本的大容量卡,即SDHC
  78.                         sd_type = 3;
  79.                     }
  80.                     else
  81.                     {
  82.                         //是2.0或更高版本的标准卡
  83.                         sd_type = 2;
  84.                     }
  85.                 }
  86.                 else
  87.                 {
  88.                     //SD卡准备时间过长
  89.                     sd_type = 0;
  90.                 }
  91.             }
  92.             else
  93.             {
  94.                 //失败,电压不正常或此卡无法使用
  95.                 sd_type = 0;
  96.             }
  97.         }
  98.         //返回0x05是SD1.0
  99.         else if (r == 0x05)
  100.         {
  101.             //等待SD卡准备好,同上
  102.             i = 254;
  103.             do
  104.             {
  105.                 SDSendCmd(55, 0, 0);//这是发送CMD55,告诉SD卡下一条指令是ACMD
  106.                 r = SDSendCmd(41, 0, 0);//发送ACMD41,这里的参数是0,与2.0不同
  107.                 i–;
  108.             }while(r && i);//如果r的最低位为0说明SD卡准备好了
  109.             
  110.             if (i)
  111.             {
  112.                 //上电初始化完成
  113.                 sd_type = 1;
  114.             }
  115.             else
  116.             {
  117.                 //初始化超时,可能是MMC卡或SD卡无法使用
  118.                 sd_type = 0;
  119.             }
  120.             
  121.         }
  122.         //其他情况
  123.         else
  124.         {
  125.             //失败,不是SD卡或SD卡无法使用
  126.             sd_type = 0;
  127.         }
  128.     }
  129.     else
  130.     {
  131.         //SD卡初始化失败,或者是其他问题
  132.         sd_type = 0;
  133.     }
  134.     
  135.     //额外8个时钟,让SD卡完成其他操作
  136.     SPI_RW(0xFF);
  137.     //先取消片选,再更改通信速度
  138.     SPI_SS = 1;
  139.     //SD卡初始化完成后可以进行高速通信
  140.     InitSPI(0);
  141.     return sd_type;
  142. }
  143. //发送SD卡指令,这三个参数分别是指令编号,指令参数,crc
  144. unsigned char SDSendCmd(unsigned char CmdNum, unsigned long int dat, unsigned char crc)
  145. {
  146.     unsigned char i = 0, r = 0;
  147.     SPI_RW(0x40 | CmdNum);
  148.     SPI_RW(dat >> 24);
  149.     SPI_RW(dat >> 16);
  150.     SPI_RW(dat >> 8);
  151.     SPI_RW(dat);
  152.     SPI_RW(crc);
  153.     //一定次数之内如果没有得到SD卡的返回值,说明通信失败,这个次数可以进行适当更改
  154.     i = WAIT_COUNT;
  155.     do
  156.     {
  157.         r = SPI_RW(0xFF);
  158.     }while(i– && (r & 0x80));
  159.     //SPI模式下大多数指令的应答都是R1,R7的格式是一个R1+4字节的参数,只有CMD13的应答是R2,在这里不考虑应答R2的情况
  160.     return r;
  161. }
  162. //等待传输开始的标识0xFE
  163. unsigned char SDWaitStartToken()
  164. {
  165.     unsigned char i = 254, r;
  166.     do
  167.     {
  168.         r = SPI_RW(0xFF);
  169.         i–;
  170.     }while(i && (r != 0xFE));
  171.     if (i == 0)
  172.     {
  173.         //等待超时
  174.         return 1;
  175.     }
  176.     else
  177.     {
  178.         //得到响应
  179.         return 0;
  180.     }
  181. }
  182. unsigned char SDReadSingleBlock(unsigned char *buf, unsigned long int address)
  183. {
  184.     unsigned int i;
  185.     unsigned char r;
  186.     SPI_SS = 0;
  187.     //普通SD卡使用字节寻址,SDHC使用块寻址(就是以扇区为单位进行寻址)
  188.     if (SD_TYPE < 3)
  189.     {
  190.         address <<= 9;
  191.     }
  192.     r = SDSendCmd(17, address, 0);
  193.     SPI_SS = 0;
  194.     
  195.     if (r == 0)
  196.     {    
  197.         if (SDWaitStartToken())
  198.         {
  199.             //等待超时
  200.             return 1;
  201.         }
  202.         i = 512;
  203.         while (i)
  204.         {
  205.             *buf = SPI_RW(0xFF);//接收的数据保存到buf数组中
  206.             buf++;
  207.             i–;
  208.         }
  209.         //抛弃两个字节的CRC
  210.         SPI_RW(0xFF);
  211.         SPI_RW(0xFF);
  212.     }
  213.     SPI_SS = 1;
  214.     return r;
  215. }
  216. unsigned char SDWriteSingleBlock(unsigned char *buf, unsigned long int address)
  217. {
  218.     unsigned int i;
  219.     unsigned char r;
  220.     SPI_SS = 0;
  221.     //普通SD卡使用字节寻址,SDHC使用块寻址
  222.     if (SD_TYPE < 3)
  223.     {
  224.         address <<= 9;
  225.     }
  226.     
  227.     r = SDSendCmd(24, address, 0);
  228.     SPI_SS = 0;
  229.     
  230.     //r == 0说明SD卡准备好了,可以开始写入
  231.     if (r == 0)
  232.     {    
  233.         //得到r==0的响应后,无论发送什么SD卡都返回0xFF
  234.         //发送开始信号
  235.         SPI_RW(0xFE);
  236.         i = 512;
  237.         while (i)
  238.         {
  239.             SPI_RW(*buf);
  240.             buf++;
  241.             i–;
  242.         }
  243.         //发送两个字节的CRC
  244.         SPI_RW(0xFF);
  245.         SPI_RW(0xFF);
  246.         //接收返回值,检查写入是否成功
  247.         r = SPI_RW(0xFF);
  248.         if ((r & 0x1F) != 0x05)
  249.         {
  250.             return 2;//写入失败
  251.         }
  252.     }
  253.     else
  254.     {
  255.         return 1;//等待SD卡准备超时
  256.     }
  257.     SPI_SS = 1;
  258.     return 0;
  259. }





像代码中这样操作SD卡实质就是把SD卡当成了一个24XX或是25QXX的EEPROM使用。而我们知道,当你把SD卡插在电脑上时,你会看到SD卡内的各种文件和文件夹,这就是文件系统。文件系统的知识又能再写一篇文章了,这里就不详细介绍了,有兴趣的可以去看《数据重现——文件系统原理精解与数据恢复最佳实践》一书的“第三章FAT文件系统”,这本书怎么弄,大家都懂。看完这部分你会对FAT文件系统有一个很好的理解,如果你的能力比较强,完全可以自己写一个简单的FAT32驱动。能力有限的话可以直接移植Petit Fatfs,目前(2016年8月)最新版本是R0.03,官网是
http://elm-chan.org/fsw/ff/00index_p.html
,下载地址是
http://elm-chan.org/fsw/ff/pff3.zip今天的文章SD卡驱动分析分享到此就结束了,感谢您的阅读。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/4886.html

(0)
编程小号编程小号

相关推荐

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注