硬件平台:stm8单片机
软件平台:IAR
参考文档:SD卡协议(物理层)(附件中的PDF)
作者:小强
PDF的查看链接: http://wenku.baidu.com/view/b08b868e84868762caaed5fc.html
一、概述
SD卡的通信模式有两种:SDIO和SPI模式,其中SPI模式比较简单,而SDIO模式速度快。
暂时只讲述SPI模式。本文不讲述各种错误的检查,只讲述有用的 初始化和读写操作。
SPI工作模式为:MSB在前,数据在 CLK 上升沿采集
每发送一个命令,SD将会返回8位或16位的 Response
读写必须以一个扇区为单位(Sector) 512字节或 它的倍数
二、命令结构(PDF中P101)
u8 Write_Command(u8 cmd, u32 arg, u8 crc)
{
u8 r1,retry;
CLR_SD_CS();//Select SD Card
SPI_SendByte( cmd | 0x40);//bit47 = 0, bit46 = 1
SPI_SendByte( arg >> 24 );
SPI_SendByte( arg >> 16 );
SPI_SendByte( arg >> 8 );
SPI_SendByte( arg );
SPI_SendByte( crc | 0x01);//bit0 = 1
retry = 0;
do{
r1 = SPI_SendByte(0xff);
if( retry ++ > 200 ) break;
}while(r1 == 0xff);//Get Response
SET_SD_CS();//Release SD Card
return r1;
}
三、初始化过程:
①CMD0进入挂起状态
②CMD1选择SD卡
③CMD59关闭CRC校检
④CMD16设定Block Length
参考文档P109 的R1 结构:
CMD0发送后,SD卡处于 Idle状态,因此Response为0x01
而其他命令发送后,R1应该为0x00
由于 大容量SD的读写Block大小固定为512bytes
u8 SD_IDLE(void)
{
u8 r1, retry = 0;
do{
r1 = Write_Command(CMD0, 0, 0x95);//发送就绪命令
if(retry ++ > 200) return 1;
}while(r1 != 0x01 );//等待返回为0x01
retry = 0;
do{
r1 = Write_Command(CMD1, 0, 0x95);
if(retry ++ > 200) return 1;
}while(r1 != 0x00 ); //等待返回为 0x00
r1 = Write_Command(CMD59, 0, 0x95);//关闭CRC
r1 = Write_Command(CMD16, 512, 0xff);//设定block块大小为512
return 0;
}
四、Read_Single_Block
时序图 详细:参考资料P96
要点:CRC是16bit,即2字节
Start Block 为 0xFE
参考资料P112
要点: 2~513个字节
u8 GetResponse(u8 Res)
{
u8 retry = 0,r1;
do{
r1 = SPI_SendByte(0xFF);
if( retry++ > 250) return 1;
}while(r1 != Res);
return 0;
}
u8 ReceiveData(u8 *buffer, u16 len)
{
u16 i;
u8 r1;
CLR_SD_CS();
r1 = GetResponse(0xFE);//等待起始标志 0xFE
if( r1 ){
SET_SD_CS();//返回非0 发生错误
return r1;
}
for(i=0; i
*buffer++ = SPI_SendByte(0xFF); //接收数据
}
SPI_SendByte(0xFF);//CRC 8+8位 = 16位CRC
SPI_SendByte(0xFF);//CRC
SET_SD_CS();
return 0;
}
u8 ReadSingleBlock(u8 *buffer, u32 sector)
{
u8 r1;
if(sector<1) return 1;// 保护0扇区
r1 = Write_Command(CMD17, sector<<9, 0xFF);//由于512字节对齐,所以sector<<9
if(r1) return r1;//发生错误
r1 = ReceiveData(buffer, 512);//接收512个字节数据
if(r1) return r1;//发生错误
return 0;
}
五、Read_Multiple_Block
与读取单个块,不同点:结束时,需要发送CMD12
u8 ReadMultipleBlock(u8 *buffer, u32 sector, u8 count)
{
u8 r1;
u16 i;
if(sector<1) return 1;// 保护0扇区
r1 = Write_Command(CMD18, sector<<9, 0xFF);//由于512字节对齐,所以 sector<<9
if(r1) return r1;
CLR_SD_CS();
while( SPI_SendByte(0xFF) != 0xFE );//Wait for Start Block Taken
do{
for( i=0; i <512; i++)
*buffer++ = SPI_SendByte(0xFF);
SPI_SendByte(0xFF);//CRC
SPI_SendByte(0xFF);//CRC
}while(--count);
SET_SD_CS();//Release
r1 = Write_Command(CMD12, 0, 0); //Stop Tran
if(r1) return r1;
return count;
}
六、Write_Single_Block
详细查看参考资料P97页
要点:每个块都以 Start Block Token (0xFE) 开始 与读取数据一样
CRC与Read_Single_Block一样,即16位
Busy时,SD卡会把总线拉低
Data_Response
资料P111
即后6位数据为0x05为数据正常
u8 WriteSingleBlock(u8 *buffer, u32 sector)
{
u8 r1;
u16 i;
if(sector<1) return 1;
r1 = Write_Command(CMD24, sector<<9, 0xFF);
if(r1) return r1;
CLR_SD_CS();
SPI_SendByte(0xFE);//Start Block Taken
for( i=0; i <512; i++)
SPI_SendByte( *buffer++ );//Write Data
SPI_SendByte(0xFF);//CRC 16位
SPI_SendByte(0xFF);//CRC
r1 = SPI_SendByte(0xFF) & 0x1F;//data_Response
if( r1 != 0x05) return r1;//Data accepted 0x05
while( !SPI_SendByte(0xFF) );//busy
SET_SD_CS();//Release
return 0;
}
七、Write_Multiple_Block
资料P98
资料P112
与 写入单个块不同的是:
Start Block Token 是0XFC
停止传送数据 要发送Stop Tran Token 0xFD
以下是实践证明的,资料中没有
而且:发送Stop Tran Token后,
还要发送 2个字节或以上,参考 读取数据中的 Start Block Token 的说明
之后还要检查 第二次 Busy (在写多个块的时序图说明)
u8 WriteMultipleBlock(u8 *buffer, u32 sector, u8 count)
{
u8 r1;
u16 i;
if(sector<1) return 1;
r1 = Write_Command(CMD25, sector<<9, 0xFF);
if(r1) return r1;
CLR_SD_CS();
SPI_SendByte(0xFF);
SPI_SendByte(0xFF);
do{
SPI_SendByte(0xFC);//Start Block Taken
for( i=0; i <512; i++)
SPI_SendByte( *buffer++ );
SPI_SendByte(0xFF);//CRC
SPI_SendByte(0xFF);//CRC
r1 = SPI_SendByte(0xFF) & 0x1F;//data_Response
if( r1 != 0x05) return r1;//Data accepted
while( !SPI_SendByte(0xFF) );//busy
}while(--count);
SPI_SendByte(0xFD);//Stop Tran Taken
//以下三步是重点
//写入数据 最少为 2个字节
SPI_SendByte(0xFF);//CRC 16位
SPI_SendByte(0xFF);//CRC
while( !SPI_SendByte(0xFF) );// 第二个 busy
SET_SD_CS();//Release
return count;
}