不同的卡,使用的协议标准是不一样的。小容量的SD卡,用的是SD1.0的标准。而中容量的SD卡。用的是SD2.0的标准。至于大容量,没有研究,不清楚。
手里面有一个8G的SD卡,属于中容量。所以以下内容都是关于中容量SD卡,即使用标准SD2.0。
SD卡的传输模式有两种,一种是SDIO模式,一种是SPI模式。两种模式区别在于协议不一样而已。这里用的是SPI模式。
其实SD的驱动还是比较简单的。就是向SD卡发各种命令,然后接受命令判断。最后在发数据,读数据。
首先说下SD卡命令格式。
SD卡的指令由6字节(Byte)组成,如下:
Byte1:0 1 x x x x x x(命令号,由指令标志定义,如CMD39为100111即16进制0x27,那么完整的CMD39第一字节为01100111,即0x27+0x40)
Byte2-5:Command Arguments,命令参数,有些命令没有参数
Byte6:前7位为CRC(Cyclic Redundacy Check,循环冗余校验)校验位,最后一位为停止位0.
这里如果是用SPI模式的话,CRC效验是不重要的,即什么都可以(我设置为0xff)。除了CMD0.CMD0的CRC是一定要正确的。
SD卡的命令非常的多,在不同的时候就要发送不同的命令。命令是很多,但是有很多我们都用不上,经常用的有CMD0,CMD8,CMD55,ACMD41和读取和写入的命令
SD卡命令共分为12类,分别为class0到class11
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
命令的传输过程采用发送应答机制,过程如下:
我们就是通过响应来判断发送命令是否成功的,每个命令都有自己的响应。但是该响应一定不是0xff。所以我们只要判断响应不是0xff的话,就说明有响应了。
先来个命令的时序图。
这是复位的时序图。 前面的74个时钟这里不用关心。
对于发送命令。从时序图可以看出,首先拉低CS。然后通过SPI发送命令,即6字节数据。发送完成后,就一直接受响应。当响应为0x01(这里的响应,不同命令响应不一样)。就说明发送命令成功。然后在拉高CS。注意,拉高CS后,要额外多发送8个时钟,为了通信的可靠。也可以把这8个时钟放在最前面CS拉低之前也行。
弄清楚了时序图,那写代码就很简单了。
+ 查看代码
//向SD中发送一个命令
//输入参数1 为命令
//输入参数2 为命令的参数
//输入参数3 为命令的CRC
//返回值是命令的响应
uint8_t SD_write_command(uint8_t command, uint32_t arg, uint8_t crc)
{
uint8_t retry=0;
uint8_t response;
SPI_CS_HIGH(); //取消上次片选
spi_write_byte(0xff); //发送8个时钟脉冲
SPI_CS_LOW(); //使能片选
command = command | 0x40;
//发送命令
spi_write_byte(command);
spi_write_byte((uint8_t)((arg&0xff000000)>>24));
spi_write_byte((uint8_t)((arg&0x00ff0000)>>16));
spi_write_byte((uint8_t)((arg&0x0000ff00)>>8));
spi_write_byte((uint8_t)((arg&0x000000ff)));
spi_write_byte(crc);
//读取响应
do
{
response = spi_write_byte(0xff);
retry++;
if(retry == 200)
return RESPONSE_OVER_TIME;
}while( (response == 0xff) );
return response;
}
这代码一看就可以看懂了。这里要注意的是因为命令的第二位一定要为1 ,所以对于发送的命令需要和0x40或一下。保证第二位为1。
其次为了避免一直接受不到响应信号,就死在循环里面了。所以要设置一个变量来计数,当计数超过范围,就认为接受响应超时。就退出循环。把超时响应返回,这样调用的函数在进行处理。当接收响应不是0xff时,就说明接收响应成功,然后将该响应返回即可。
spi_write_byte(byte)这个函数就是通过SPI协议发送一个字节数据,同时返回接收到的一个字节数据。
这里采用的是IO模拟的方式。
+ 查看代码
uint8_t spi_write_byte(uint8_t byte)
{
uint8_t temp=0;
uint8_t i;
for(i=0x80; i!=0; i=i>>1)
{
temp <<= 1;
sd_spi_clk_low(); //拉低clk
//准备好发送的数据
if((byte & i) != 0 )
sd_spi_do_high();
else
sd_spi_do_low();
if(sd_reset_flag) //如果是复位,clk时钟不能超过400K
delay_us(10);
else
delay_us(1);
sd_spi_clk_high(); //拉高clk
__NOP();
__NOP();
__NOP();
if(sd_spi_di() == 1)
temp |= 0x01;
if(sd_reset_flag) //如果是复位,clk时钟不能超过400K
delay_us(10);
else
delay_us(1);
}
sd_spi_do_high();
return temp;
}
这里就注意一下,如果是在复位的时候,SPI的时钟不能太快,就需要延时久一点。如果不是在复位的时候,SPI的时钟就可以快一点,就不用延时那么久。
底层的函数就这么两个,剩下的就是利用这两个函数来完成这个SDHC卡的驱动程序。