SD卡的驱动有两种模式,SD模式与SPI模式,用单片机驱动时常使用SPI模式,一方面容易实现,另一方面操作数据量并不是很大,速度要求不高。
SD卡工作电压时3.3V,在SPI模式时只需要4根信号线,即CS片选、DIN数据输入、CLK时钟、DOUT数据输出。
问题:
代码运行时出现:main.c(1): warning C318: can't open file 'REGX51.H'分析:
在sd.h里边定义了管脚//定义SD卡需要的4根信号线sbit SD_CLK = P1^0;sbit SD_DI = P1^2;sbit SD_DO = P1^1;sbit SD_CS = P1^3;//main.c
#include <REGX51.H>#include "sd.H"#define F_OSC 11059200//晶振频率Hz#define F_BAUD 9600#define RELOAD 256-F_OSC/12/32/F_BAUD#define CR 0x0D //回车unsigned char xdata DATA[512];main代码如下:
1 void main()2 {3 UART();4 while(!SdInit());5 SdWriteBlock("ABCDEFG",0x000000,7);//写入abcdefg6 SdReadBlock(DATA,0x000000,7);7 Sen_String(DATA);8 while(1);9 }
【分析】main做了这些事情:串口初始化SD卡初始化写入字符串abcdefg读取字符串向串口发送字符串死循环
其中UART函数如下:
1 /******************************************* 2 串口初始化 3 *******************************************/ 4 void UART() 5 { 6 SCON=0x40;//工作方式1,不允许接受串口数据 7 TMOD=0x20;//定时器1工作于方式2自动重装模式 8 TH1=RELOAD; 9 TR1=1;10 TI=0; 11 }
【问题与分析】TMOD TH1分别是指什么?答:P10TMOD用了控制和设定定时器的工作方式和4种工作模式。低四位用于T0,高四位用于T1。TH1用于保存定时器T1的初值,TL1用于计数,TL1溢出时,若在模式2则会自动重装TH1中的初值。这里TMOD为0x20,也即0010 0000,每个定时器各四位,分别指GATE C/~T M1 M0.对定时器T1,GATE=0,定时器启动与中断无关;C/~T为0,工作在定时方式,以单片机机器周期为计数脉冲;M1=1,M0=0,工作在模式2,常数自动装入.对定时器T2,GATE=0,定时器启动与中断无关;C/~T为0,工作在定时方式,以单片机机器周期为计数脉冲;M1=0,M0=0,工作在模式0,13位定时/计数器.
TI是指什么?
答:TI是串口中断发送标志 (P377上方)SCON是指什么?P376
答:SCON是串口控制寄存器。 SCON的八位分别是SM0 SM1 SM2 REN TB8 RB8 TI RI.这里SCON八位被设为0x40,也即0100 0000.SM0 SM1为01,表示采用模式1,也即10位异步收发模式,数据传输率由定时器控制;模式SM2为多机通信控制位,在模式0 1下不应使用,应置为0;REN是允许接收位,为0禁止串口接收;TB8 RB8分别代表数据发送 接收第九位,主要用于模式2、3。在模式1中,若SM2为0,则RB8用于存放接收到的停止位。TI、RI分别是发送、接收中断标志位,用于指示一帧数据是否发送、接收完毕,都由软件复位、硬件置位。TR1是指什么?
答:P11 T1定时器的运行控制位,为1时开始计时TH1怎么计算的(跟P379公式,数据传输率=2^SMOD*f_osc/32/12/(2^k-初值)有关吗)?
SdInit函数如下:1 //sd.h 2 //初始化SD卡 3 unsigned char SdInit(void) 4 { 5 int delay=0, trials=0; 6 unsigned char i; 7 unsigned char response=0x01; 8 9 SD_CS=1;10 for(i=0;i<=9;i++)11 SdWrite(0xff);12 SD_CS=0;13 14 //Send Command 0 to put MMC in SPI mode15 SdCommand(0x00,0,0x95);16 17 18 response=SdResponse();19 20 if(response!=0x01)21 {22 return 0;23 } 24 25 while(response==0x01)26 {27 SD_CS=1;28 SdWrite(0xff);29 SD_CS=0;30 SdCommand(0x01,0x00ffc000,0xff);31 response=SdResponse();32 } 33 34 SD_CS=1;35 SdWrite(0xff);36 return 1; 37 }
【问题】1、振南电子说初始化响应信号时0x00,但是这里边的响应信号却写0x01,为什么?答:这里的响应信号是指复位的响应,复位响应信号就是0x01。如果复位失败,那初始化必然失败。
2、最后命令执行完毕后,为什么还要再写入一次校验码?而且为什么不检查初始化响应信号0x00?
再写入一次校验码是为了稳定起见,再输入8个时钟信号,这个信号内容没有什么特殊含义,只是为了输入8个时钟附带的。【分析】
SdInit做了这些事情:SdWrite(0xff);先写入命令,0xff是表示不使用校验码SdCommand(0x00,0,0x95);执行0x95复位命令response=SdResponse(); if(response!=0x01) return 0;如果复位失败,则返回0SdCommand(0x01,0x00ffc000,0xff);接下来执行初始化命令SdWrite(0xff);写入表示不使用的校验码其中,SdWrite函数如下:
1 //写一字节到SD卡,模拟SPI总线方式 2 void SdWrite(unsigned char n) 3 { 4 5 unsigned char i; 6 7 for(i=8;i;i--) 8 { 9 SD_CLK=0;10 SD_DI=(n&0x80);11 n<<=1;12 SD_CLK=1;13 }14 SD_DI=1; 15 } 16 }
【问题】为什么SD_DI要和0x80做&操作,来标记后面七位呢?又为什么要左移一位? SdCommand函数如下:
1 void SdCommand(unsigned char command, unsigned long argument, unsigned char CRC) 2 { 3 4 SdWrite(command|0x40); 5 SdWrite(((unsigned char *)&argument)[0]); 6 SdWrite(((unsigned char *)&argument)[1]); 7 SdWrite(((unsigned char *)&argument)[2]); 8 SdWrite(((unsigned char *)&argument)[3]); 9 SdWrite(CRC);10 }
【分析】
命令格式:命令|参数|CRC校验码所有命令是从0x40开始的,使用或操作的话,只要输入命令0、1即可,不用记忆那些个奇怪的0x40、0x41...中间四个字节是参数,不使用参数的时候也可记0.最后是CRC校验码。SdResponse函数如下:
1 //检测SD卡的响应 2 unsigned char SdResponse() 3 { 4 unsigned char i=0,response; 5 6 while(i<=8) 7 { 8 response = SdRead(); 9 if(response==0x00)10 break;11 if(response==0x01)12 break;13 i++;14 }15 return response;16 }
【分析】这里只return两种,一是0x00,二是0x01,除了这些都是不正常的返回信号。
SdRead函数如下:
1 //从SD卡读一字节,模拟SPI总线方式 2 unsigned char SdRead() 3 { 4 unsigned char n,i; 5 for(i=8;i;i--) 6 { 7 SD_CLK=0; 8 SD_CLK=1; 9 n<<=1;10 if(SD_DO) n|=1;11 12 }13 return n;14 }
【问题】为什么要左移?
【分析】
读取的时候,如果SD_DO没有被拉低,那么n就是全1 SdWriteBlock函数代码如下:1 unsigned char SdWriteBlock(unsigned char *Block, unsigned long address,int len) 2 { 3 unsigned int count; 4 unsigned char dataResp; 5 //Block size is 512 bytes exactly 6 //First Lower SS 7 8 SD_CS=0; 9 //Then send write command10 SdCommand(0x18,address,0xff);11 12 if(SdResponse()==00)13 {14 SdWrite(0xff);15 SdWrite(0xff);16 SdWrite(0xff);17 //command was a success - now send data18 //start with DATA TOKEN = 0xFE19 SdWrite(0xfe);20 //now send data21 for(count=0;count
【问题】振南电子中说写扇区用命令24,但为什么这里用命令18?答:这里还是用命令24,命令24是0x58,第1个命令是0x40,0x58-0x40=0x18
【分析】
SdWriteBlock做了这些事情:SdCommand(0x18,address,0xff);执行写命令0x58if(SdResponse()==00) 命令被写入成功SdWrite(0xff); 给入若干时钟周期(100个可以了)SdWrite(0xfe); 写入开始字节SdWrite(*Block++); 写入字符SdWrite(0xff); SdWrite(0xff);两字节CRC校验dataResp=SdRead();while(SdRead()==0); 读字节,如果字节为0,则是忙状态。 dataResp=dataResp&0x0f;标记高四位SdWrite(0xff);再给一个周期if(dataResp==0x0b) return 0;读得CRC为0x0b则发生错误if(dataResp==0x05) return 1;正确 SdReadBlock函数如下:1 //从SD卡指定地址读取数据,一次最多512字节 2 unsigned char SdReadBlock(unsigned char *Block, unsigned long address,int len) 3 { 4 unsigned int count; 5 //Block size is 512 bytes exactly 6 //First Lower SS 7 8 //printf("MMC_read_block\n"); 9 10 SD_CS=0;11 //Then send write command12 SdCommand(0x11,address,0xff);13 14 if(SdResponse()==00)15 {16 //command was a success - now send data17 //start with DATA TOKEN = 0xFE18 while(SdRead()!=0xfe);19 20 for(count=0;count
【分析】
SdCommand(0x11,address,0xff);执行读命令0x51if(SdResponse()==00) 命令被成功写入while(SdRead()!=0xfe);不停读取,直到开始字节*Block++=SdRead();读取数据SdRead();SdRead();两个CRC校验码的读取,不用作处理SdRead();补充8个时钟周期 Sen_String函数如下:1 /******************************************* 2 发送字符串 3 *******************************************/ 4 void Sen_String(unsigned char *string) 5 { 6 while(*string!='\0') 7 { 8 if(*string=='\n') 9 {10 SBUF=CR;11 }12 else13 {14 SBUF=*string;15 }16 while(TI==0);17 TI=0;18 string++;19 }20 }
【问题】SBUF、TI、CR是什么?答:SBUF是在数据收发过程中,串口收发数据暂存的地方 P376TI是串口中断发送标志
为什么一定要把TI改为0?
答:表示不再中断【分析】
CR为0x0D,也即回车 【完整代码】1 //sd.h 2 #include3 //定义SD卡需要的4根信号线 4 sbit SD_CLK = P1^0; 5 sbit SD_DI = P1^2; 6 sbit SD_DO = P1^1; 7 sbit SD_CS = P1^3; 8 //定义512字节缓冲区,注意需要使用 xdata关键字 9 10 11 //=========================================================== 12 //写一字节到SD卡,模拟SPI总线方式 13 void SdWrite(unsigned char n) 14 { 15 16 unsigned char i; 17 18 for(i=8;i;i--) 19 { 20 SD_CLK=0; 21 SD_DI=(n&0x80); 22 n<<=1; 23 SD_CLK=1; 24 } 25 SD_DI=1; 26 } 27 28 //=========================================================== 29 //从SD卡读一字节,模拟SPI总线方式 30 unsigned char SdRead() 31 { 32 unsigned char n,i; 33 for(i=8;i;i--) 34 { 35 SD_CLK=0; 36 SD_CLK=1; 37 n<<=1; 38 if(SD_DO) n|=1; 39 40 } 41 return n; 42 } 43 //============================================================ 44 //检测SD卡的响应 45 unsigned char SdResponse() 46 { 47 unsigned char i=0,response; 48 49 while(i<=8) 50 { 51 response = SdRead(); 52 if(response==0x00) 53 break; 54 if(response==0x01) 55 break; 56 i++; 57 } 58 return response; 59 } 60 //================================================================ 61 //发命令到SD卡 62 void SdCommand(unsigned char command, unsigned long argument, unsigned char CRC) 63 { 64 65 SdWrite(command|0x40); 66 SdWrite(((unsigned char *)&argument)[0]); 67 SdWrite(((unsigned char *)&argument)[1]); 68 SdWrite(((unsigned char *)&argument)[2]); 69 SdWrite(((unsigned char *)&argument)[3]); 70 SdWrite(CRC); 71 } 72 //================================================================ 73 //初始化SD卡 74 unsigned char SdInit(void) 75 { 76 int delay=0, trials=0; 77 unsigned char i; 78 unsigned char response=0x01; 79 80 SD_CS=1; 81 for(i=0;i<=9;i++) 82 SdWrite(0xff); 83 SD_CS=0; 84 85 //Send Command 0 to put MMC in SPI mode 86 SdCommand(0x00,0,0x95); 87 88 89 response=SdResponse(); 90 91 if(response!=0x01) 92 { 93 return 0; 94 } 95 96 while(response==0x01) 97 { 98 SD_CS=1; 99 SdWrite(0xff);100 SD_CS=0;101 SdCommand(0x01,0x00ffc000,0xff);102 response=SdResponse();103 } 104 105 SD_CS=1;106 SdWrite(0xff);107 return 1; 108 }109 //================================================================110 //往SD卡指定地址写数据,一次最多512字节111 unsigned char SdWriteBlock(unsigned char *Block, unsigned long address,int len)112 {113 unsigned int count;114 unsigned char dataResp;115 //Block size is 512 bytes exactly116 //First Lower SS117 118 SD_CS=0;119 //Then send write command120 SdCommand(0x18,address,0xff);121 122 if(SdResponse()==00)123 {124 SdWrite(0xff);125 SdWrite(0xff);126 SdWrite(0xff);127 //command was a success - now send data128 //start with DATA TOKEN = 0xFE129 SdWrite(0xfe);130 //now send data131 for(count=0;count
1 //main.c 2 #include "sd.H" 3 #include4 5 #define F_OSC 11059200//晶振平率Hz 6 #define F_BAUD 9600 7 #define RELOAD 256-F_OSC/12/32/F_BAUD 8 #define CR 0x0D //回车 9 unsigned char xdata DATA[512];10 /*******************************************11 串口初始化12 *******************************************/13 void UART()14 {15 SCON=0x40;//工作与方式1不允许接受16 TMOD=0x20;//定时器1工作与方式2自动重装模式17 TH1=RELOAD;18 TR1=1;19 TI=0; 20 }21 /*******************************************22 发送字符串23 *******************************************/24 void Sen_String(unsigned char *string)25 {26 while(*string!='\0')27 {28 if(*string=='\n')29 {30 SBUF=CR;31 }32 else33 {34 SBUF=*string;35 }36 while(TI==0);37 TI=0;38 string++;39 }40 }41 void main()42 {43 UART();44 while(!SdInit());45 SdWriteBlock("ABCDEFG",0x000000,7);//写入abcdefg46 SdReadBlock(DATA,0x000000,7);47 Sen_String(DATA);48 while(1);49 }