weiqi7777

stm32驱动SDHC卡 (SPI方式) 一

0
阅读(10680)

SD卡是比较常见的存储设备了。SD卡也有分类,按照容量有三类。

1、 SD卡,小容量的卡。容量小于2G。现在用得比较少了。

2、 SDHC卡。中容量的卡,容量大于2G,小于32G。目前用得普遍。

3、 SDXC卡。大容量的卡,容量大于32G。

不同的卡,使用的协议标准是不一样的。小容量的SD卡,用的是SD1.0的标准。而中容量的SD卡。用的是SD2.0的标准。至于大容量,没有研究,不清楚。

手里面有一个8G的SD卡,属于中容量。所以以下内容都是关于中容量SD卡,即使用标准SD2.0。

SD卡的传输模式有两种,一种是SDIO模式,一种是SPI模式。两种模式区别在于协议不一样而已。这里用的是SPI模式。

其实SD的驱动还是比较简单的。就是向SD卡发各种命令,然后接受命令判断。最后在发数据,读数据。

首先说下SD卡命令格式。

clip_image002

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

命令的传输过程采用发送应答机制,过程如下:

clip_image004

我们就是通过响应来判断发送命令是否成功的,每个命令都有自己的响应。但是该响应一定不是0xff。所以我们只要判断响应不是0xff的话,就说明有响应了。

先来个命令的时序图。

clip_image006

这是复位的时序图。 前面的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卡的驱动程序。