嵌入式成长轨迹36 【Zigbee项目】【单片机基础】【单片机SD卡】

时间:2022-11-14 18:58:49

 

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 }