重点强调一遍,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卡的初始化、读写单个数据块(扇区)是如何操作的。
像代码中这样操作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卡的发展过程。
到目前为止(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卡的初始化、读写单个数据块(扇区)是如何操作的。
- #include "spi.h"//这是SPI的驱动
- #define WAIT_COUNT 10//等待SD卡进行某些操作的计数器,超过这个次数认为等待超时,高速单片机(如STM32)可以将这个数值适当扩大
- sbit SPI_SS = P1^6;//SPI从机选择口, 连接到其它MCU的SS口
- //SD卡读写扇区的缓存,小容量卡的扇区大小设置成512字节,大容量卡的扇区就是512字节不可更改
- unsigned char xdata SDBlockBuffer[512] = {0};
- //初始化完成后可获得SD卡的类型,方便其他程序使用
- unsigned char SD_TYPE = 0;
- //返回值
- //0,初始化失败,电压不正确,SD卡无法使用,或是MMC卡
- //1,初始化成功,SD1.0的卡
- //2,初始化成功,SD2.0的标准卡(2GB以内)
- //3,初始化成功,SD2.0的大容量卡(32GB以内)
- unsigned char SDInit()
- {
- unsigned char i = 0, r = 0;//i是计数器,r用于保存其他函数的返回值
- char sd_type;
- unsigned char buf[4] = {0};
- //用低速初始化SPI,初始化时SPI总线时钟频率不要超过400KHz
- InitSPI(3);
- //使能
- SPI_SS = 0;
- //至少74个时钟信号
- for (i=0; i<10; i++)
- {
- SPI_RW(0xFF);
- }
- //一定次数之内如果没有得到SD卡的返回值,说明通信失败,这个次数可以进行适当更改
- i = WAIT_COUNT;
- do
- {
- r = SDSendCmd(0, 0, 0x95);
- }while(i-- && (r & 0x80));
- //注意:在SPI模式下CRC是默认关闭的,但是要保证CMD0的CRC正确,之后的指令的CRC可以是任意值,CMD0的CRC是0x95,已经计算好了,直接用就行。
- //如有需要可以使用CMD59打开或关闭CRC,详情自行查看手册
- //r=0x01说明SD卡正常,可以继续进行下一步操作
- if(r == 0x01)
- {
- //CMD8是SD 2.0新添加的指令,如果响应说明是SD2.0或更高版本
- r = SDSendCmd(8, 0x1AA, 0x87);
- //返回0x01是SD2.0
- if (r == 0x01)
- {
- //对于SPI模式的R7,只有5个字节,首先返回的是R1,然后的4个字节,一般情况下是0x0000 01AA
- SPI_SS = 0;
- for (i=0; i<4; i++)
- {
- buf[i] = SPI_RW(0xFF);
- }
- SPI_SS = 1;
- if (buf[2] == 0x01 && buf[3] == 0xAA)
- {
- //支持电压范围2.7-3.6
- //备注[1]
- //等待SD卡准备好,这个阶段时间可能较长,需要多等一段时间,但不应该超过一秒,超过一秒应重新初始化
- i = 254;
- do
- {
- SDSendCmd(55, 0, 0);//这是发送CMD55
- r = SDSendCmd(41, 0x40000000, 0);//发送ACMD41
- i--;
- }while(r && i);//如果r的最低位为0说明SD卡准备好了
- if (i)
- {
- //使用CMD58检查SD卡版本和电压,这里只检查版本,不检查电压
- r = SDSendCmd(58, 0, 0);
- SPI_SS = 0;
- for (i=0; i<4; i++)
- {
- buf[i] = SPI_RW(0xFF);
- }
- SPI_SS = 1;
- if (buf[0] & 0x40)
- {
- //是2.0或更高版本的大容量卡,即SDHC
- sd_type = 3;
- }
- else
- {
- //是2.0或更高版本的标准卡
- sd_type = 2;
- }
- }
- else
- {
- //SD卡准备时间过长
- sd_type = 0;
- }
- }
- else
- {
- //失败,电压不正常或此卡无法使用
- sd_type = 0;
- }
- }
- //返回0x05是SD1.0
- else if (r == 0x05)
- {
- //等待SD卡准备好,同上
- i = 254;
- do
- {
- SDSendCmd(55, 0, 0);//这是发送CMD55,告诉SD卡下一条指令是ACMD
- r = SDSendCmd(41, 0, 0);//发送ACMD41,这里的参数是0,与2.0不同
- i--;
- }while(r && i);//如果r的最低位为0说明SD卡准备好了
- if (i)
- {
- //上电初始化完成
- sd_type = 1;
- }
- else
- {
- //初始化超时,可能是MMC卡或SD卡无法使用
- sd_type = 0;
- }
- }
- //其他情况
- else
- {
- //失败,不是SD卡或SD卡无法使用
- sd_type = 0;
- }
- }
- else
- {
- //SD卡初始化失败,或者是其他问题
- sd_type = 0;
- }
- //额外8个时钟,让SD卡完成其他操作
- SPI_RW(0xFF);
- //先取消片选,再更改通信速度
- SPI_SS = 1;
- //SD卡初始化完成后可以进行高速通信
- InitSPI(0);
- return sd_type;
- }
- //发送SD卡指令,这三个参数分别是指令编号,指令参数,crc
- unsigned char SDSendCmd(unsigned char CmdNum, unsigned long int dat, unsigned char crc)
- {
- unsigned char i = 0, r = 0;
- SPI_RW(0x40 | CmdNum);
- SPI_RW(dat >> 24);
- SPI_RW(dat >> 16);
- SPI_RW(dat >> 8);
- SPI_RW(dat);
- SPI_RW(crc);
- //一定次数之内如果没有得到SD卡的返回值,说明通信失败,这个次数可以进行适当更改
- i = WAIT_COUNT;
- do
- {
- r = SPI_RW(0xFF);
- }while(i-- && (r & 0x80));
- //SPI模式下大多数指令的应答都是R1,R7的格式是一个R1+4字节的参数,只有CMD13的应答是R2,在这里不考虑应答R2的情况
- return r;
- }
- //等待传输开始的标识0xFE
- unsigned char SDWaitStartToken()
- {
- unsigned char i = 254, r;
- do
- {
- r = SPI_RW(0xFF);
- i--;
- }while(i && (r != 0xFE));
- if (i == 0)
- {
- //等待超时
- return 1;
- }
- else
- {
- //得到响应
- return 0;
- }
- }
- unsigned char SDReadSingleBlock(unsigned char *buf, unsigned long int address)
- {
- unsigned int i;
- unsigned char r;
- SPI_SS = 0;
- //普通SD卡使用字节寻址,SDHC使用块寻址(就是以扇区为单位进行寻址)
- if (SD_TYPE < 3)
- {
- address <<= 9;
- }
- r = SDSendCmd(17, address, 0);
- SPI_SS = 0;
- if (r == 0)
- {
- if (SDWaitStartToken())
- {
- //等待超时
- return 1;
- }
- i = 512;
- while (i)
- {
- *buf = SPI_RW(0xFF);//接收的数据保存到buf数组中
- buf++;
- i--;
- }
- //抛弃两个字节的CRC
- SPI_RW(0xFF);
- SPI_RW(0xFF);
- }
- SPI_SS = 1;
- return r;
- }
- unsigned char SDWriteSingleBlock(unsigned char *buf, unsigned long int address)
- {
- unsigned int i;
- unsigned char r;
- SPI_SS = 0;
- //普通SD卡使用字节寻址,SDHC使用块寻址
- if (SD_TYPE < 3)
- {
- address <<= 9;
- }
- r = SDSendCmd(24, address, 0);
- SPI_SS = 0;
- //r == 0说明SD卡准备好了,可以开始写入
- if (r == 0)
- {
- //得到r==0的响应后,无论发送什么SD卡都返回0xFF
- //发送开始信号
- SPI_RW(0xFE);
- i = 512;
- while (i)
- {
- SPI_RW(*buf);
- buf++;
- i--;
- }
- //发送两个字节的CRC
- SPI_RW(0xFF);
- SPI_RW(0xFF);
- //接收返回值,检查写入是否成功
- r = SPI_RW(0xFF);
- if ((r & 0x1F) != 0x05)
- {
- return 2;//写入失败
- }
- }
- else
- {
- return 1;//等待SD卡准备超时
- }
- SPI_SS = 1;
- return 0;
- }
像代码中这样操作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