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);//写入abcdefg 6 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分别是指什么?
答:P10
TMOD用了控制和设定定时器的工作方式和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 mode 15 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;如果复位失败,则返回0
SdCommand(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 command 10 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 data 18 //start with DATA TOKEN = 0xFE 19 SdWrite(0xfe); 20 //now send data 21 for(count=0;count<len;count++) SdWrite(*Block++); 22 23 for(;count<512;count++) SdWrite(0); 24 //data block sent - now send checksum 25 SdWrite(0xff); //两字节CRC校验, 为0XFFFF 表示不考虑CRC 26 SdWrite(0xff); 27 //Now read in the DATA RESPONSE token 28 dataResp=SdRead(); 29 //Following the DATA RESPONSE token 30 //are a number of BUSY bytes 31 //a zero byte indicates the MMC is busy 32 33 while(SdRead()==0); 34 35 dataResp=dataResp&0x0f; //mask the high byte of the DATA RESPONSE token 36 SD_CS=1; 37 SdWrite(0xff); 38 if(dataResp==0x0b) 39 { 40 //printf("DATA WAS NOT ACCEPTED BY CARD -- CRC ERROR\n"); 41 return 0; 42 } 43 if(dataResp==0x05) 44 return 1; 45 46 //printf("Invalid data Response token.\n"); 47 return 0; 48 } 49 //printf("Command 0x18 (Write) was not received by the MMC.\n"); 50 return 0; 51 }
【问题】
振南电子中说写扇区用命令24,但为什么这里用命令18?
答:这里还是用命令24,命令24是0x58,第1个命令是0x40,0x58-0x40=0x18
【分析】
SdWriteBlock做了这些事情:
SdCommand(0x18,address,0xff);执行写命令0x58
if(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 command 12 SdCommand(0x11,address,0xff); 13 14 if(SdResponse()==00) 15 { 16 //command was a success - now send data 17 //start with DATA TOKEN = 0xFE 18 while(SdRead()!=0xfe); 19 20 for(count=0;count<len;count++) *Block++=SdRead(); 21 22 for(;count<512;count++) SdRead(); 23 24 //data block sent - now send checksum 25 SdRead(); 26 SdRead(); 27 //Now read in the DATA RESPONSE token 28 SD_CS=1; 29 SdRead(); 30 return 1; 31 } 32 //printf("Command 0x11 (Read) was not received by the MMC.\n"); 33 return 0; 34 }
【分析】
SdCommand(0x11,address,0xff);执行读命令0x51
if(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 else 13 { 14 SBUF=*string; 15 } 16 while(TI==0); 17 TI=0; 18 string++; 19 } 20 }
【问题】
SBUF、TI、CR是什么?
答:
SBUF是在数据收发过程中,串口收发数据暂存的地方 P376
TI是串口中断发送标志
为什么一定要把TI改为0?
答:表示不再中断
【分析】
CR为0x0D,也即回车
【完整代码】
1 //sd.h 2 #include <REGX51.H> 3 //定义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 exactly 116 //First Lower SS 117 118 SD_CS=0; 119 //Then send write command 120 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 data 128 //start with DATA TOKEN = 0xFE 129 SdWrite(0xfe); 130 //now send data 131 for(count=0;count<len;count++) SdWrite(*Block++); 132 133 for(;count<512;count++) SdWrite(0); 134 //data block sent - now send checksum 135 SdWrite(0xff); //两字节CRC校验, 为0XFFFF 表示不考虑CRC 136 SdWrite(0xff); 137 //Now read in the DATA RESPONSE token 138 dataResp=SdRead(); 139 //Following the DATA RESPONSE token 140 //are a number of BUSY bytes 141 //a zero byte indicates the MMC is busy 142 143 while(SdRead()==0); 144 dataResp=dataResp&0x0f; //mask the high byte of the DATA RESPONSE token 145 SD_CS=1; 146 SdWrite(0xff); 147 if(dataResp==0x0b) 148 { 149 //printf("DATA WAS NOT ACCEPTED BY CARD -- CRC ERROR\n"); 150 return 0; 151 } 152 if(dataResp==0x05) 153 return 1; 154 155 //printf("Invalid data Response token.\n"); 156 return 0; 157 } 158 //printf("Command 0x18 (Write) was not received by the MMC.\n"); 159 return 0; 160 } 161 162 //======================================================================= 163 //从SD卡指定地址读取数据,一次最多512字节 164 unsigned char SdReadBlock(unsigned char *Block, unsigned long address,int len) 165 { 166 unsigned int count; 167 //Block size is 512 bytes exactly 168 //First Lower SS 169 170 //printf("MMC_read_block\n"); 171 172 SD_CS=0; 173 //Then send write command 174 SdCommand(0x11,address,0xff); 175 176 if(SdResponse()==00) 177 { 178 //command was a success - now send data 179 //start with DATA TOKEN = 0xFE 180 while(SdRead()!=0xfe); 181 182 for(count=0;count<len;count++) *Block++=SdRead(); 183 184 for(;count<512;count++) SdRead(); 185 186 //data block sent - now send checksum 187 SdRead(); 188 SdRead(); 189 //Now read in the DATA RESPONSE token 190 SD_CS=1; 191 SdRead(); 192 return 1; 193 } 194 //printf("Command 0x11 (Read) was not received by the MMC.\n"); 195 return 0; 196 }
1 //main.c 2 #include "sd.H" 3 #include <REGX51.H> 4 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 else 33 { 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);//写入abcdefg 46 SdReadBlock(DATA,0x000000,7); 47 Sen_String(DATA); 48 while(1); 49 }