原理图:
SPI简介:
SPI,是英语Serial Peripheral interface的缩写,顾名思义就是串行外围设备接口。是Motorola首先在其MC68HCXX系列处理器上定义的。SPI接口主要应用在 EEPROM,FLASH,实时时钟,AD转换器等芯片,还有数字信号处理器和数字信号解码器之间。
SPI,是一种高速的,全双工,同步的通信总线,并且在芯片的管脚上只占用四根线,节约了芯片的管脚,同时为PCB的布局上节省空间,提供方便,正是出于这种简单易用的特性,现在越来越多的芯片集成了这种通信协议,比如AT91RM9200。
SPI连接图
#SPI总线四种工作方式
SPI 模块为了和外设进行数据交换,根据外设工作要求,其输出串行同步时钟极性和相位可以进行配置,时钟极性(CPOL)对传输协议没有重大的影响。
CPOL
时钟极性选择,为0时SPI总线空闲为低电平,为1时SPI总线空闲为高电平
CPHA 时钟相位选择,为0时在SCK第一个跳变沿采样,为1时在SCK第二个跳变沿采样
Mode 0 SCLK 空闲是为低电平,数据在上升沿有效
Mode 1 SCLK 空闲是为低电平,数据在下降沿有效
Mode 2 SCLK 空闲是为高电平,数据在下降沿有效
Mode 3 SCLK 空闲是为高电平,数据在上升沿有效
//定义SD卡需要的4根信号线
sbit SD_CLK = P1^4;
sbit SD_DI = P1^6;
sbit SD_DO = P1^5;
sbit SD_CS = P1^7;
//写一字节到SD卡,模拟SPI总线方式
void SdWrite(unsigned char n)
{
unsigned char i;
for(i=8;i;i--){
SD_CLK=0;
SD_DI=(n&0x80);//上升沿写入数据
n<<=1;
SD_CLK=1;
}
SD_DI=1;
}
//从SD卡读一字节,模拟SPI总线方式
unsigned char SdRead()
{
unsigned char n,i;
for(i=8;i;i--)
{
SD_CLK=0;
SD_CLK=1; //上升沿读取数据
n<<=1;
if(SD_DO) n|=1;
}
return n;
}
原文链接:https://blog.csdn.net/ming1006/article/details/7281597
SPI模式的SD卡
SD卡的命令格式如下,6字节共48位,传输时最高位(MSB)先传输:
SD卡的command(命令)占6 bit,一般叫CMDx或ACMDx,比如CMD1就是1,CMD13就是13,ACMD41就是41,依此类推。Command Argument(命令参数)占4 byte,并不是所有命令都有参数,没有参数的话该位一般就用置0。最后一个字节由7 bit CRC校验位和1 bit停止位组成。在SPI模式下,CRC是被忽略的,可以都置1或置0.但是发送CMD0时要记得加上CRC,即最后1字节为0x95(因为发送CMD0时还未进入SPI模式,PS:CMD8也要,但一般大家都把发送CMD8省略了)。
每次发送完一次命令后,SD卡都会有回应。SD卡的回应有多种格式,1字节的R1,2字节的R2等,不过一般在SPI模式中我们只用到R1,下面介绍R1的格式:
关于SD卡SPI和command的发送要注意以下几点:
1.SD卡的SPI总线,在读入数据时SD卡的SPI是CLK的上升沿输入锁存,输出数据也是在上升沿。
2.向SD卡写入一个CMD或者ACMD指令的过程是这样的: 首先使CS为低电平,SD卡使能;其次在SD卡的Din写入指令;写入指令后还要附加8个填充时钟,是SD卡完成内部操作;之后在SD卡的Dout上接受回应;回应接受完毕使CS为低电平,再附加8个填充时钟。
3.在SD卡的Din没有数据写入时,应使Din保持高电平。
SD卡初始化复位方法:
1.拉高CS,发送至少74个clk周期来使SD卡达到正常工作电压和进行同步
2.选低CS,发送CMD0,需要收到回应0x01表示成功进入idle状态, 复位成功后,SD卡就进入了SPI模式,接着应该进行初始化。
3、发送CM1进行初始化
unsigned char SdInit(void)
{
int delay=0, trials=0;
unsigned char i;
unsigned char response=0x01;
SD_CS=1;
for(i=0;i<=9;i++)
SdWrite(0xff);
SD_CS=0;
//Send Command 0 to put MMC in SPI mode
SdCommand(0x00,0,0x95);
response=SdResponse();
if(response!=0x01)
{
return 0;
}
while(response==0x01)
{
SD_CS=1;
SdWrite(0xff);
SD_CS=0;
SdCommand(0x01,0x00ffc000,0xff);
response=SdResponse();
}
SD_CS=1;
SdWrite(0xff);
return 1;
}
读单块和多块:
SD卡读单块和多块的命令分别为CMD17和CMD18,他们的参数即要读的区域的开始地址。因为考虑到一般SD卡的读写要求地址对齐,所以一般我们都将地址转为块,并以扇区(块)(512Byte)为单位进行读写,比如读扇区0参数就为0,读扇区1参数就为1<<9(即地址512),读扇区2参数就为2<<9(即地址1024),依此类推。
读单块方法:
1.发送CMD17,收到0x00表示成功
2.连续读直到读到开始字节0xFE
3.读512个字节
4.读两个CRC字节
读多块方法:
1.发送CMD18读,收到0x00表示成功
2.连续读直到读到开始字节0xFE
3.读512字节
4.读两个CRC字节
5.如果还想读下一扇区,重复2-4
6.发送CMD12来停止读多块操作
//从SD卡指定地址读取数据,一次最多512字节,这个是读单块
unsigned char SdReadBlock(unsigned char *Block, unsigned long address,int len)
{
unsigned int count=0;
SD_CS=0;
SdCommand(0x11,address,0xff);
if(SdResponse()==00){
while(SdRead()!=0xfe);
for(count=0;count<len&&count<512;count++) *Block++=SdRead();
for(;count<512;count++) SdRead();
SdRead();
SdRead();
SD_CS=1;
SdRead();
return 1;
}
return 0;
}
写单块方法:
1.发送CMD24,收到0x00表示成功
2.发送若干时钟
3.发送写单块开始字节0xFE
4.发送512个字节数据
5.发送2字节CRC(可以均为0xff)
6.连续读直到读到XXX00101表示数据写入成功
7.继续读进行忙检测(读到0x00表示SD卡正忙),当读到0xff表示写操作完成
写多块方法:
1.发送CMD25,收到0x00表示成功
2.发送若干时钟
3.发送写多块开始字节0xFC
4.发送512字节数据
5.发送两个CRC(可以均为0xff)
6.连续读直到读到XXX00101表示数据写入成功
7.继续读进行忙检测,直到读到0xFF表示写操作完成
8.如果想读下一扇区重复2-7步骤
9.发送写多块停止字节0xFD来停止写操作
10.进行忙检测直到读到0xFF
//往SD卡指定地址写数据,一次最多512字节
unsigned char SdWriteBlock(unsigned char *Block, unsigned long address,int len)
{
unsigned int count;
unsigned char dataResp;
SD_CS=0;
SdCommand(0x18,address,0xff);
if(SdResponse()==00)
{
SdWrite(0xff);
SdWrite(0xff);
SdWrite(0xff);
SdWrite(0xfe);
for(count=0;count<len&&count<512;count++) SdWrite(*Block++);
for(;count<512;count++) SdWrite(0);
SdWrite(0xff); //两字节CRC校验, 为0XFFFF 表示不考虑CRC
SdWrite(0xff);
dataResp=SdRead();
while(SdRead()==0);
dataResp=dataResp&0x0f; //mask the high byte of the DATA RESPONSE token
SD_CS=1;
SdWrite(0xff);
if(dataResp==0x0b){
return 0;
}
if(dataResp==0x05)
return 1;
return 0;
}
return 0;
}