前段时间在51上模拟SPI实现了对SD卡的读取,效果还算不错,最近将其移植到STM32上,不过使用硬件SPI和使用软件SPI还是有差别的。
代码如下:
void User_SPIInit(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
SPI_InitTypeDef SPI_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_SPI1,ENABLE); //使能时钟
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_5|GPIO_Pin_6|GPIO_Pin_7;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_3;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
SPI_InitStructure.SPI_Direction=SPI_Direction_2Lines_FullDuplex; //双线全双工
SPI_InitStructure.SPI_Mode=SPI_Mode_Master; //主模式
SPI_InitStructure.SPI_DataSize=SPI_DataSize_8b; //8位数据
SPI_InitStructure.SPI_CPOL=SPI_CPOL_High; //这里要注意,一定要配置为上升沿数据有效,因为SD卡为上升沿数据有效
SPI_InitStructure.SPI_CPHA=SPI_CPHA_2Edge;
SPI_InitStructure.SPI_NSS=SPI_NSS_Soft;
SPI_InitStructure.SPI_BaudRatePrescaler=SPI_BaudRatePrescaler_256;
SPI_InitStructure.SPI_FirstBit=SPI_FirstBit_MSB;
SPI_InitStructure.SPI_CRCPolynomial=7;
SPI_Init(SPI1,&SPI_InitStructure);
SPI_Cmd(SPI1,ENABLE);
}
SPI初始化以后就可以写SPI读写函数了,以下两个函数参照了网上的资料,出处找不到了,但是这两个函数帮了我大忙,再次感谢提供资料的无名者
void SD_WriteByte(unsigned char data)
{
uint16_t temp;
temp=data;
while(SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_TXE )==RESET);
SPI_I2S_SendData(SPI1,temp);
while(SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_RXNE )==RESET);
SPI_I2S_ReceiveData(SPI1);
}
unsigned char SD_ReadByte(void)
{
unsigned char temp;
while(SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_TXE )==RESET);
SPI_I2S_SendData(SPI1,0xFF);
while(SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_RXNE )==RESET);
temp=SPI_I2S_ReceiveData(SPI1);
return temp;
}
有了上面两个函数,问题就好解决了,下面实现发送SD命令函数
unsigned char SD_SendCmd(unsigned char *Cmd) //Cmd为unsigned char Cmd[6]数组,存放SD固定6字节命令
{
unsigned char i,temp;
temp=0xFF; //赋予一个初值
for(i=0;i<6;i++)
SD_WriteByte(CMD[i]); //发送6字节命令
do
{
temp=SD_ReadByte(); //一直读SD的应答字节,其实应答字节数量不定,这里简化只收取第一个应答字节,赋temp为0xFF主要因为发现所有应答字节序列的第一个字节不
//会是0xFF
}while(temp==0xFF);
return temp;
}
发送命令函数完成后下面就该是SD_Init()函数了
unsigned char SD_Init(void)
{
unsigned char i,temp;
for(i=0;i<200;i++) //SD卡要求复位前至少发送74个clock,这里我发了很多,足够多
SD_WriteByte(0xFF);
temp=0x00;
CMD[0]=0x40; //发送复位命令CMD0,CMD[1]-CMD[4]初始化为0,这里不用再写了,因为CMD0不需要参数
CMD[5]=0x95;
do
{
temp=SD_SendCmd(CMD);
}while(temp!=0x01); //不断发送CMD0,直到返回0x01,即SD卡的Idle状态(我设置的无论何时SD卡CSS始终为低电平)
temp=0x03; //发送指令CMD55和指令ACMD41
CMD[5]=0xFF;
do
{
CMD[0]=0x77; //CMD55
temp=SD_SendCmd(CMD);
CMD[0]=0x69; //ACMD41
temp=SD_SendCmd(CMD);
}while(temp!=0x00); //循环发送CMD55和ACMD41,直到SD卡返回0x00,即初始化完成且进入到SPI模式,注意在整个所有的过程中,SD卡的CSS时钟为低电平
return temp; //当然,返回0x00则SD卡初始化成功
}
既然SD卡初始化成功,下面就好说了,下面实现读取一个512字节的块和写入512字节的块
void SD_Read_SigleBlock(unsigned long addr,unsigned char *ptr) //addr为4字节地址,这里必须为512的整数倍,ptr为大于512字节的接受缓冲区指针,必须为byte
{
unsigned char temp;
unsigned int i=0;
temp=0xFF;
CMD[0]=0x51;
CMD[4]=addr;
addr=addr>>8;
CMD[3]=addr;
addr=addr>>8;
CMD[2]=addr;
addr=addr>>8;
CMD[1]=addr;
CMD[5]=0xFF;
do
{
temp=SD_SendCmd(CMD);
}while(temp!=0x00); //直到返回读取单块命令的正确应答字节,即返回0x00,说明命令发送成功,发送成功后就要读取SD发送的数据了
do
{
temp=SD_ReadByte();
}while(temp!=0xFE&&temp!=0xFC); //读取SD卡发送的数据,不断的读取,直到读到SD发送的数据开始信号,即0xFE或0xFC,再往下就是512字节的正式数据
for(i=0;i<512;i++)
ptr[i]=SD_ReadByte(); //读取512字节的正式数据
temp=SD_ReadByte(); //下面还要读取两个字节的CRC校验数据,SD的SPI模式下除了CMD0的CRC有效外,其他CRC校验都无效
temp=SD_ReadByte();
}
void SD_Write_SigleBlock(unsigned long addr,unsigned char *ptr) //参数addr为写入数据的地址,必须为512整数倍;ptr为512字节的发送缓冲区指针,必须为byte
{
unsigned char temp;
unsigned int i=0;
temp=0xFF;
CMD[0]=0x58;
CMD[4]=addr;
addr=addr>>8;
CMD[3]=addr;
addr=addr>>8;
CMD[2]=addr;
addr=addr>>8;
CMD[1]=addr;
CMD[5]=0xFF;
do
{
temp=SD_SendCmd(CMD);
}while(temp!=0x00); //循环发送写单块命令,直到返回正确应答信号0x00
SD_WriteByte(0xFE); //给SD卡发送正式数据的开始字节信号0xFE或0xFC,这里我选取0xFE
for(i=0;i<512;i++)
SD_WriteByte(ptr[i]); //给SD卡发送要写的512字节的正式数据
SD_WriteByte(0xFF); //发送两字节的CRC校验数据,虽说没有用,但形式上还是要发送的
SD_WriteByte(0xFF);
}
以上都完成后,我们就可以操作SD卡了
unsigned char data[512];
unsigned int i=0;
void mian()
{
for(i=0;i<512;i++)
data[i]=0xFF;
SD_Write_SigleBlock(0x00000000,data);
for(i=0;i<512;i++)
data[i]=0;
SD_Read_SigleBlock(0x00000000,data);
//在这里检查data里的内容是否都为0xFF即可,如果为0xFF,说明一切成功,否则,要检查了
while(1);
}