之前一直在做SD卡,一开始是基于8366的,但后面为了给同学方便,直接做成XS的了。现在可以进行SD卡的读写,TXT的创建与BMP的创建。
下面是SD卡的驱动程序(基于XS128)。
#include "SD_Card.h"
#define SD_CS PTM_PTM3
//PLL初始化void PLL_Init(void) //PLLCLK=2*OSCCLK*(SYNR+1)/(REFDV+1){ //PLLCLK=2*16M*(2+1)/(0+1)= 96M SYNR = 0xC2; //总线频率为PLL频率的一半48M REFDV = 0xC0; ECLKCTL &= ~0x80; while(!(CRGFLG&0x08)); //自循环语句,等待时钟频率稳定后允许锁相环时钟作为系统时钟 CLKSEL=0x80; //选择锁相环频率为系统时钟频率}
//SPI初始化void SPI_Init(void) { MODRR = 0x00; MODRR_MODRR4=1; //使用PM 口 DDRM |= 0x38; //可知SCK0=1,MOSI=1,SS0=1 本步可以忽略,这样做复位可以抗干扰下,并且直接让 //片选无效 SPI0CR1 = 0x5e; //CPOL=1,时钟选择低有效,spsck空闲时为高电平 //CPHA=1 会在发送 8 位数据开始 sck就发生一次跳变 SPI0CR2 = 0x10; //modfen=1 .和上面 ssoe=1 确定 spi在 master 模式下 ss 位从机选择输出。并允许 modf标志设置 SPI0BR = 0x17; //波特率设置波特率= BR=busclk/((SPPR + 1)· 2^(SPR + 1))=48M / (1+1)*2^(7+1) = 93KHz }
//设置 spi 高速 void SPI_High(void) { SPI0BR = 0x01; //BR=busclk/((SPPR + 1)· 2^(SPR + 1))=48M / 4= 12m }
//用SPI写1bytevoid SPI_Write(unsigned char date) { while (!SPI0SR_SPTEF); //等待发送器空 (void)SPI0SR; SPI0DRL = date; while (!SPI0SR_SPIF); //等待发送完成 (void)SPI0DRL; }
//用SPI读1byteunsigned char SPI_Read(void) { while (!SPI0SR_SPTEF); //等待发送器空 (void)SPI0SR; SPI0DRL = 0xff; while (!SPI0SR_SPIF); //等待发送完成 (void)SPI0DRL; return SPI0DRL; }
//SPI写命令uchar SD_Send_Cmd(uchar cmd,ulong arg,uchar crc,uchar Res){ volatile unsigned char r1; unsigned char Retry = 200;
unsigned char addr_1; unsigned char addr_2; unsigned char addr_3; unsigned char addr_4; arg <<= 9; //arg = arg * 512; addr_1 = arg & 0xff; addr_2 = (arg >> 8 ) & 0xff; addr_3 = (arg >> 16) & 0xff; addr_4 = (arg >> 24) & 0xff; SD_CS=1; SPI_Write(0xff); //提高兼容性,如果没有这里,有些SD卡可能不支持 SD_CS=0; //SPI_Write(0xff);
//发送 SPI_Write(cmd | 0x40); //分别写入命令 SPI_Write(addr_4); SPI_Write(addr_3); SPI_Write(addr_2); SPI_Write(addr_1); SPI_Write(crc);
//等待响应,或超时退出 do { r1 = SPI_Read(); Retry--; if(r1 == Res) break; } while(Retry>0);
//在总线上额外增加个时钟,让SD卡完成剩下的工作 SPI_Write(0xFF);
//返回状态值 if(Retry>0) return 0; //成功返回0 else return 1; //失败返回1}
void SDCard_Init(void){ unsigned char Success_Flag = 0; unsigned int Count = 0; //最高要大于600,所以选择16位 unsigned char r1 = 0xFF;
SPI_Init(); SD_CS = 1; //CS抬高,补74以上时钟 for(Count=0;Count<10;Count++) SPI_Write(0xFF); SD_CS = 0; //CS拉低,补16以上时钟 for(Count=0;Count<4;Count++) SPI_Write(0xFF); //-----------------------复位SD卡,并选择为SPI模式----------------------------- do { SD_CS = 0; //SD卡片选拉低 ,使能 for(Count=0;Count<4;Count++) SPI_Write(0xFF); //补时钟(大于16个时钟) r1 = SD_Send_Cmd(SD_CMD0,0x00000000,0x95,SD_IDLE); Count++; } while((r1 != 0x00) && Count < 20); SD_CS = 1; //SD卡禁能 (void)SPI_Read(); // Dummy SPI cycle //相当于补8个时钟 //--------------------------激活SD卡------------------------------------------ //备注:此种激活使用APP类命令,首先发送CMD55然后发送ACMD41 if(Count < 20) { SD_CS = 0; //SD卡使能 do { r1 = SD_Send_Cmd(SD_CMD55,0x00000000,0x95,SD_IDLE); //写CMD55 r1 = SD_Send_Cmd(SD_CMD41,0x00000000,0x95,SD_OK ); //写ACMD41 Count++; } while((r1 != 0x00) && Count <300); SD_CS = 1; //SD卡禁能 (void)SPI_Read(); // Dummy SPI cycle } }
//-----------------------设置读块写块长度-----------------------------unsigned char Set_BL(void){ unsigned int Count = 0; //计数 unsigned int r1 = 0; //响应 SD_CS = 0; //SD卡使能 do { r1 = SD_Send_Cmd(SD_CMD16,0x00000200,0xFF,SD_OK); //写CMD16,设置块长度为512 Count ++; } while((r1 != 0x00) && Count <100); SD_CS = 1; //SD卡使能 (void)SPI_Read(); // Dummy SPI cycle if(Count < 100) return 0; //成功返回0 if(Count >= 100) return 1; //成功返回1}
//求2的N次方 unsigned long Match_2N(unsigned int number){ unsigned int count = 0; unsigned int answer = 1; for(count = 0;count < number;count++) { answer *= 2; } return answer;}
//----------------------读CSD寄存器-----------------------unsigned char SD_Read_CSD(void){ unsigned int count = 0; unsigned char r1 = 0; volatile unsigned int buffer[16] = {0}; volatile unsigned long C_SIZE = 0; //设备大小 volatile unsigned long MULT = 0; //乘数 volatile unsigned long BL_LEN = 0; //块长度 volatile float SD_SIZE= 0; //经过计算后的SD卡大小 SD_CS = 0; //SD卡使能 do { r1 = SD_Send_Cmd(SD_CMD9,0x00000000,0xFF,SD_OK); //发送"CMD9"指令读取寄存器CSD,不需要CRC验证(CRC位置为0xFF) count++; } while((r1 != 0x00) && count <200); if(count>=200) { SD_CS = 1; //SD卡禁能 (void)SPI_Read(); // Dummy SPI cycle return 1; //失败 } while((SPI_Read() != 0xFE)); //等待数据起始位 for(count = 0;count <16; count++) { buffer[count] = SPI_Read(); } SD_CS = 0; //SD卡禁能 (void)SPI_Read(); // Dummy SPI cycle //-------------------------计算尺寸,块数量-------------------// C_SIZE = (((buffer[6] & 0x03) << 10) + (buffer[7]<<2) + (buffer[8]>>6)); MULT = Match_2N(((buffer[9]&0x03)<<1) + (buffer[10]>>7)+2); BL_LEN = Match_2N((buffer[5] & 0x0f)); SD_SIZE = (float)(((C_SIZE+1)*(BL_LEN)*(MULT))/1048576);
return 0; }
//-------------------------读块函数----------------------------------unsigned char SD_Read_BL(unsigned long Addr,unsigned char *buffer) { unsigned int r1 = 0; unsigned int count = 0; SD_CS = 0; //SD卡使能 do { r1 = SD_Send_Cmd(SD_CMD17,Addr,0xFF,SD_OK); //发送"CMD17"指令按地址读块 count++; } while((r1 != 0x00) && count <100); if(count>=100) { SD_CS = 1; //SD卡禁能 (void)SPI_Read(); // Dummy SPI cycle return 1; //失败 } while((SPI_Read() != 0xFE)); //等待数据起始位 for(count = 0;count <512; count++) { *buffer++ = SPI_Read(); } (void)SPI_Read(); // 读取两个校验码,然后扔掉 (void)SPI_Read(); SD_CS = 1; //SD卡禁能 SPI_Write(0xFF); return 0;}
//-------------------------写块函数----------------------------------unsigned char SD_Write_BL(unsigned long Addr,unsigned char *buffer) { unsigned int r1 = 0; unsigned int count = 0; unsigned char temp; unsigned char i = 0; SD_CS = 0; //SD卡使能 do { r1 = SD_Send_Cmd(SD_CMD24,Addr,0xFF,SD_OK); //发送"CMD24"指令按地址写块 } while((r1 != 0x00) && count <100); if(count>=100) { SD_CS = 1; //SD卡禁能 //(void)SPI_Read(); // Dummy SPI cycle return 1; //失败 } for(i=0;i<100;i++) //这里要插入若干时钟信号 { SPI_Write(0xFF); }
SPI_Write(0xFE); //写入数据起始位 for(count = 0;count <512; count++) { SPI_Write(*buffer++); //写入数据起始位 } SPI_Write(0xFF); SPI_Write(0xFF); //两个字节的CRC校验码,不用关心 temp = SPI_Read(); //读返回值 if((temp&0x1F)!=0x05) { SD_CS = 1; //SD卡禁能 return 1; //失败 } while(SPI_Read()!=0xff);//等到SD卡不忙(数据被接受以后,SD卡要将这些数据写入到自身的FLASH中,需要一个时间) //忙时,读回来的值为0x00,不忙时,为0xff SD_CS = 1; //SD卡禁能 SPI_Write(0xFF); //按照时序,补8个时钟 return 0;}
有些地方有些改动。
改动1.写命令时候,地址自动乘了512(右移9位),为了方便读块与写块。读写某个块时候,只要输入块就可以,不用折算为地址。
改动2.写命令之前,将时钟拉高,补了8个时钟,再将其拉低,为了提高兼容性。(此处来源于振南老师的源代码,实际效果未知,我手上的卡基本都兼容。)
改动3.读块时候,把512字节数据读出后多读了两次,为了读出CRC校验码,之后补了8个时钟。
改动4.写块时候,写完512字节数据后,多写了两个0xFF作为校验码,之后增加了等待0x05响应(数据成功接受),之后拉高CS端口补了8个时钟。
按照这个,可以完成SD卡的读写。如果要加入读写TXT与BMP,就要加入FAT32文件层的相关东西了。TXT不需要文件头,只需要在FAT表中标明数据启始与结束。BMP文件则需要特定的文件头,那么通过单片机写入文件头与数据,PC端口才能打开。如果想通过嵌入式设备需要读出BMP文件的数据,则要去掉文件头。
其实8366用来驱动SD卡,程序都差不多,只是寄存器不同而已。之前8366的程序我已经贴出来了,但是以上要注意的地方再改动一下就可以了。完整的程序我还没有,以后做的话,在贴出来分享吧。