单片机晶振proteus代码(基于proteus的51单片机开发实例32-DS1302时钟)(1)

1. 基于proteus的51单片机开发实例32-使用DS1302实现数字电子钟1.1. 实验目的

我们在学习51单片机定时器的相关内容时,了解到,在对时间要求不是很准确的情况下,可以使用51单片机的定时器中断实现电子钟。如果要求很精准的时钟,那就需要使用专门的时钟芯片。DS1302就是一款应用非常广泛、时间非常准确的时钟芯片。本实例的目的就是通过设计一幅基于实时时钟芯片DS1302的应用电路,通过51单片机控制DS1302的读写来获取时钟信息,并通过LCD1602液晶显示出年、月、日、时、分、秒。

单片机晶振proteus代码(基于proteus的51单片机开发实例32-DS1302时钟)(2)

图1 DS1302数字电子钟

1.2. 设计思路

本实例通过设计实时时钟芯片DS1302的读写控制电路,实现51单片机对DS1302的时间设置、时钟读取功能,并在电路中加入LCD1602液晶显示电路,51单片机通过读取DS1302的时钟数据,并把这些数据送到LCD1602液晶显示。

1.3. 基础知识

关于LCD1602的工作原理、电路实现、程序实现,我们在之前的例子中已经作过介绍,这里不再赘述。

本例主要讲解DS1302的原理、电路及编程控制。

DS1302能够计算2100年之前的年、月、日、时、分、秒、星期等信息。并且DS1302有闰年的调整功能。

1.3.1. DS1302引脚功能

DS1302与单片机的接口很简单:只需三根I/O口线即可。下图是DS1302的引脚图。

单片机晶振proteus代码(基于proteus的51单片机开发实例32-DS1302时钟)(3)

VCC1:主电源

VCC2:备用电源输入,当VCC2>(VCC1 0.2V)时,由VCC2供电,当VCC2<VCC1时,由VCC1供电。

SCLK:串行时钟

RST:复位引脚,高有效。

I/O:数据输入/输出

X1,X2:外部晶振引脚。

1.3.2. DS1302的操作方法

DS1302的操作有两种:从DS1302读取数据;向DS1302写入数据。执行这两种操作时,都需要通过特定的寄存器进行。下图所示是DS1302的寄存器。

例如:想要设置秒的初始值,需要先写入命令字H80,然后才能向秒寄存器写入初始值;再例如要读出当前时间的分钟数值,则需要先写入命令字83H,然后才能从分钟寄存器中读取数据。

1、将命令字写入DS1302

单片机与DS1302的通信时,必须是单片机首先发起。无论是对DS1302进行读数据或者写数据的操作,单片机都必须首先向DS1302发送一个命令帧,其格式如下。

单片机晶振proteus代码(基于proteus的51单片机开发实例32-DS1302时钟)(4)

Bit7为1时,允许写入,为0时,禁止写入;

Bit6为1时,对RAM操作,为0时,对时钟寄存器操作;

Bit5~Bit1是RAM或时钟寄存器的内部地址;

Bit0位1时,读操作,为0时,写操作。

DS1302还有两个状态寄存器命令字“0x8e”--允许将数据写入DS1302;“0x8f”--禁止将数据写入DS1302。

如果向状态寄存器写入数据“0x00”,表示不需要对DS1302写保护;

如果向状态寄存器写入数据“0x80”,表示对DS1302写保护,此时不能将数据写入其它时钟寄存器和数据寄存器;

1.3.3. DS1302数据读/写的方法

DS1302的通信协议规定,在没有数据传递时,SCLK应保持低电平。如果RST由低电平变为高电平,则表示开始进行数据操作。RST为低电平时,禁止进行数据操作。

在SCLK的上升沿,数据被写入DS1302,在时钟脉冲的下降沿,从DS1302读出数据。

数据发送时,低位数据在前,高位数据在后。

单片机晶振proteus代码(基于proteus的51单片机开发实例32-DS1302时钟)(5)

1.4. 电路设计

本实例电路如图1所示。使用单片机的P3.5,P3.6,P3.7分别连接DS1302的RST,SCLK,I/O。注意这三个端口要连接上拉电阻。单片机的P0口连接LCD1602液晶的8根数据线,单片机P2.1,P2.2,P2.3分别连接LCD1602液晶的RS,RW,E。

1.5. 程序设计

本程序采用模块化设计方法。包括4个模块程序:主程序模块,时钟芯片DS1302程序模块,延时程序模块,LCD1602液晶程序模块。

各模块程序代码如下:

主程序模块

#include<reg51.h> //包含单片机寄存器的头文件 #include<intrins.h> //包含_nop_()函数定义的头文件 #include"LCD1602.h" //包含1602液晶显示函数的头文件 #include"DS1302.h" //包含1302时钟读写的头文件 #include"Delay.h" //包含延时函数的头文件 unsigned char code DispDigit[10]={"0123456789"}; //定义字符数组显示数字 /***************************************************** 函数功能:主函数 ***************************************************/ void main(void) { unsigned char Second,Minute,Hour,Day,Month,Year; //分别储存苗、分、小时,日,月,年 unsigned char ReadValue; //储存从1302读取的数据 LcdInitiate(); //液晶初始化 WriteAddress(0x00); //在第1行第1列开始显示 WriteData('D'); //显示:Date: WriteData('a'); WriteData('t'); WriteData('e'); WriteData(':'); WriteAddress(0x08); //在第1行第9列显示年月之间的分隔符- WriteData('-'); WriteAddress(0x0b); //在第1行第12列显示月日之间的分隔符- WriteData('-'); //将字符常量写入LCD WriteAddress(0x40); //在第2行第1列开始显示:Time: WriteData('T'); WriteData('i'); WriteData('m'); WriteData('e'); WriteData(':'); WriteAddress(0x48); //显示小时与分钟之间的分隔符 WriteData(':'); WriteAddress(0x4b); //显示分钟与秒之间的分隔符 WriteData(':'); Init_DS1302(); //将DS1302初始化 while(1) { ReadValue = ReadSetDS1302(0x81); //从秒寄存器读数据 Second=((ReadValue&0x70)>>4)*10 (ReadValue&0x0F);//将读出数据转化 DisplaySecond(Second); //显示秒 ReadValue = ReadSetDS1302(0x83); //从分寄存器读 Minute=((ReadValue&0x70)>>4)*10 (ReadValue&0x0F); //将读出数据转化 DisplayMinute(Minute); //显示分 ReadValue = ReadSetDS1302(0x85); //从分寄存器读 Hour=((ReadValue&0x70)>>4)*10 (ReadValue&0x0F); //将读出数据转化 DisplayHour(Hour); //显示小时 ReadValue = ReadSetDS1302(0x87); //从分寄存器读 Day=((ReadValue&0x70)>>4)*10 (ReadValue&0x0F); //将读出数据转化 DisplayDay(Day); //显示日 ReadValue = ReadSetDS1302(0x89); //从分寄存器读 Month=((ReadValue&0x70)>>4)*10 (ReadValue&0x0F); //将读出数据转化 DisplayMonth(Month); //显示月 ReadValue = ReadSetDS1302(0x8d); //从分寄存器读 Year=((ReadValue&0x70)>>4)*10 (ReadValue&0x0F); //将读出数据转化 DisplayYear(Year); //显示年 } }

时钟芯片DS1302程序模块。

DS1302.h代码

sbit DATA=P3^7; //位定义1302芯片的接口,数据输出端定义在P1.1引脚 sbit RST=P3^5; //位定义1302芯片的接口,复位端口定义在P1.1引脚 sbit SCLK=P3^6; //位定义1302芯片的接口,时钟输出端口定义在P1.1引脚 //向DS1302写一个字节数据 void WriteDS1302(unsigned char DS1302data); //根据命令字,向DS1302写一个字节数据 void WriteSetDS1302(unsigned char DS1302Cmd,unsigned char DS1302data); //从1302读一个字节数据 unsigned char ReadDS1302(void); //根据命令字,从DS1302读取一个字节数据 unsigned char ReadSetDS1302(unsigned char DS1302Cmd); // DS1302初始化设置 void Init_DS1302(void);

DS1302.c代码

#include<reg51.h> //包含单片机寄存器的头文件 #include<intrins.h> //包含_nop_()函数定义的头文件 #include"LCD1602.h" //包含1602液晶显示函数的头文件 #include"DS1302.h" //包含1302时钟读写的头文件 #include"Delay.h" //包含延时函数的头文件 extern unsigned char code DispDigit[10]; //定义字符数组显示数字 /***************************************************** 函数功能:向1302写一个字节数据 入口参数:DS1302data ***************************************************/ void WriteDS1302(unsigned char DS1302data) { unsigned char i; SCLK=0; //拉低SCLK,为脉冲上升沿写入数据做好准备 DelayXus(2); //稍微等待,使硬件做好准备 for(i=0;i<8;i ) //连续写8个二进制位数据 { DATA=DS1302data&0x01; //取出待写数据的第0位数据写入1302 DelayXus(2); //稍微等待,使硬件做好准备 SCLK=1; //上升沿写入数据 DelayXus(2); //稍微等待,使硬件做好准备 SCLK=0; //重新拉低SCLK,形成脉冲 DS1302data>>=1; //将数据位右移1位,准备写入下一个数据位 } } /***************************************************** 函数功能:根据命令字,向1302写一个字节数据 入口参数:DS1302Cmd,储存命令字;DS1302data,储存待写的数据 ***************************************************/ void WriteSetDS1302(unsigned char DS1302Cmd,unsigned char DS1302data) { RST=0; //禁止数据传递 SCLK=0; //确保写数居前SCLK被拉低 RST=1; //启动数据传输 DelayXus(2); //稍微等待,使硬件做好准备 WriteDS1302(DS1302Cmd); //写入命令字 WriteDS1302(DS1302data); //写数据 SCLK=1; //将时钟电平置于已知状态 RST=0; //禁止数据传递 } /***************************************************** 函数功能:从1302读一个字节数据 入口参数:x ***************************************************/ unsigned char ReadDS1302(void) { unsigned char i,ucdata; DelayXus(2); //稍微等待,使硬件做好准备 for(i=0;i<8;i ) //连续读8个二进制位数据 { ucdata>>=1; //将数据位右移1位,因为先读出的是字节的最低位 if(DATA==1) //如果读出的数据是1 ucdata|=0x80; //将1取出,写在dat的最高位 SCLK=1; //将SCLK置于高电平,为下降沿读出 DelayXus(2); //稍微等待 SCLK=0; //拉低SCLK,形成脉冲下降沿 DelayXus(2); //稍微等待 } return ucdata; //将读出的数据返回 } /***************************************************** 函数功能:根据命令字,从1302读取一个字节数据 入口参数:DS1302Cmd ***************************************************/ unsigned char ReadSetDS1302(unsigned char DS1302Cmd) { unsigned char ucdata; RST=0; //拉低RST SCLK=0; //确保写数居前SCLK被拉低 RST=1; //启动数据传输 WriteDS1302(DS1302Cmd); //写入命令字 ucdata=ReadDS1302(); //读出数据 SCLK=1; //将时钟电平置于已知状态 RST=0; //禁止数据传递 return ucdata; //将读出的数据返回 } /***************************************************** 函数功能: 1302进行初始化设置 ***************************************************/ void Init_DS1302(void) { WriteSetDS1302(0x8E,0x00); //根据写状态寄存器命令字,写入不保护指令 WriteSetDS1302(0x80,((30/10)<<4|(30))); //根据写秒寄存器命令字,写入秒的初始值 WriteSetDS1302(0x82,((11/10)<<4|(11))); //根据写分寄存器命令字,写入分的初始值 WriteSetDS1302(0x84,((8/10)<<4|(8))); //根据写小时寄存器命令字,写入小时的初始值 WriteSetDS1302(0x86,((18/10)<<4|(18))); //根据写日寄存器命令字,写入日的初始值 WriteSetDS1302(0x88,((8/10)<<4|(8))); //根据写月寄存器命令字,写入月的初始值 WriteSetDS1302(0x8c,((20/10)<<4|(20))); //根据写年寄存器命令字,写入小时的初始值 }

LCD1602液晶模块

LCD1602.h代码

sbit RS=P2^1; //寄存器选择位,将RS位定义为P2.0引脚 sbit RW=P2^2; //读写选择位,将RW位定义为P2.1引脚 sbit E=P2^3; //使能信号位,将E位定义为P2.2引脚 sbit BF=P0^7; //忙碌标志位,,将BF位定义为P0.7引脚 //判断液晶模块的忙碌状态 bit BusyTest(void); //将模式设置指令或显示地址写入液晶模块 void WriteInstruction (unsigned char dictate); //指定字符显示的实际地址 void WriteAddress(unsigned char x); //将数据(字符的标准ASCII码)写入液晶模块 void WriteData(unsigned char y); //对LCD的显示模式进行初始化设置 void LcdInitiate(void); /************************************************************** 以下是1302数据的显示程序 **************************************************************/ //显示秒 void DisplaySecond(unsigned char x); //显示分钟 void DisplayMinute(unsigned char x); //显示小时 void DisplayHour(unsigned char x); //显示日 void DisplayDay(unsigned char x); //显示月 void DisplayMonth(unsigned char x); //显示年 void DisplayYear(unsigned char x);

LCD1602.c

#include<reg51.h> //包含单片机寄存器的头文件 #include<intrins.h> //包含_nop_()函数定义的头文件 #include"LCD1602.h" //包含1602液晶显示函数的头文件 #include"DS1302.h" //包含1302时钟读写的头文件 #include"Delay.h" //包含延时函数的头文件 extern unsigned char code DispDigit[10]; //定义字符数组显示数字 /******************************************************************************* 以下是对液晶模块的操作程序 *******************************************************************************/ /***************************************************** 函数功能:判断液晶模块的忙碌状态 返回值:result。result=1,忙碌;result=0,不忙 ***************************************************/ bit BusyTest(void) { bit result; RS=0; //根据规定,RS为低电平,RW为高电平时,可以读状态 RW=1; E=1; //E=1,才允许读写 _nop_(); //空操作 _nop_(); _nop_(); _nop_(); //空操作四个机器周期,给硬件反应时间 result=BF; //将忙碌标志电平赋给result E=0; //将E恢复低电平 return result; } /***************************************************** 函数功能:将模式设置指令或显示地址写入液晶模块 入口参数:dictate ***************************************************/ void WriteInstruction (unsigned char dictate) { while(BusyTest()==1); //如果忙就等待 RS=0; //根据规定,RS和R/W同时为低电平时,可以写入指令 RW=0; E=0; //E置低电平(根据表8-6,写指令时,E为高脉冲, // 就是让E从0到1发生正跳变,所以应先置"0" _nop_(); _nop_(); //空操作两个机器周期,给硬件反应时间 P0=dictate; //将数据送入P0口,即写入指令或地址 _nop_(); _nop_(); _nop_(); _nop_(); //空操作四个机器周期,给硬件反应时间 E=1; //E置高电平 _nop_(); _nop_(); _nop_(); _nop_(); //空操作四个机器周期,给硬件反应时间 E=0; //当E由高电平跳变成低电平时,液晶模块开始执行命令 } /***************************************************** 函数功能:指定字符显示的实际地址 入口参数:x ***************************************************/ void WriteAddress(unsigned char x) { WriteInstruction(x|0x80); //显示位置的确定方法规定为"80H 地址码x" } /***************************************************** 函数功能:将数据(字符的标准ASCII码)写入液晶模块 入口参数:y(为字符常量) ***************************************************/ void WriteData(unsigned char y) { while(BusyTest()==1); RS=1; //RS为高电平,RW为低电平时,可以写入数据 RW=0; E=0; //E置低电平(根据表8-6,写指令时,E为高脉冲, // 就是让E从0到1发生正跳变,所以应先置"0" P0=y; //将数据送入P0口,即将数据写入液晶模块 _nop_(); _nop_(); _nop_(); _nop_(); //空操作四个机器周期,给硬件反应时间 E=1; //E置高电平 _nop_(); _nop_(); _nop_(); _nop_(); //空操作四个机器周期,给硬件反应时间 E=0; //当E由高电平跳变成低电平时,液晶模块开始执行命令 } /***************************************************** 函数功能:对LCD的显示模式进行初始化设置 ***************************************************/ void LcdInitiate(void) { DelayXms(15); //延时15ms,首次写指令时应给LCD一段较长的反应时间 WriteInstruction(0x38); //显示模式设置:16×2显示,5×7点阵,8位数据接口 DelayXms(5); //延时5ms ,给硬件一点反应时间 WriteInstruction(0x38); DelayXms(5); //延时5ms ,给硬件一点反应时间 WriteInstruction(0x38); //连续三次,确保初始化成功 DelayXms(5); //延时5ms ,给硬件一点反应时间 WriteInstruction(0x0c); //显示模式设置:显示开,无光标,光标不闪烁 DelayXms(5); //延时5ms ,给硬件一点反应时间 WriteInstruction(0x06); //显示模式设置:光标右移,字符不移 DelayXms(5); //延时5ms ,给硬件一点反应时间 WriteInstruction(0x01); //清屏幕指令,将以前的显示内容清除 DelayXms(5); //延时5ms ,给硬件一点反应时间 } /************************************************************** 以下是1302数据的显示程序 **************************************************************/ /***************************************************** 函数功能:显示秒 入口参数:x ***************************************************/ void DisplaySecond(unsigned char x) { unsigned char i,j; //i,j分别表示秒的十位、个位 i=x/10;//取十位 j=x;//取个位 WriteAddress(0x4c); //写显示地址,将在第2行第7列开始显示 WriteData(DispDigit[i]); //将百位数字的字符常量写入LCD WriteData(DispDigit[j]); //将十位数字的字符常量写入LCD DelayXms(50); //延时1ms给硬件一点反应时间 } /***************************************************** 函数功能:显示分钟 入口参数:x ***************************************************/ void DisplayMinute(unsigned char x) { unsigned char i,j; //i,j分别表示分钟的十位、个位 i=x/10;//取十位 j=x;//取个位 WriteAddress(0x49); //写显示地址,将在第2行第7列开始显示 WriteData(DispDigit[i]); //将百位数字的字符常量写入LCD WriteData(DispDigit[j]); //将十位数字的字符常量写入LCD DelayXms(50); //延时1ms给硬件一点反应时间 } /***************************************************** 函数功能:显示小时 入口参数:x ***************************************************/ void DisplayHour(unsigned char x) { unsigned char i,j; //i,j分别表示小时的十位、个位 i=x/10;//取十位 j=x;//取个位 WriteAddress(0x46); //写显示地址,将在第2行第7列开始显示 WriteData(DispDigit[i]); //将百位数字的字符常量写入LCD WriteData(DispDigit[j]); //将十位数字的字符常量写入LCD DelayXms(50); //延时1ms给硬件一点反应时间 } /***************************************************** 函数功能:显示日 入口参数:x ***************************************************/ void DisplayDay(unsigned char x) { unsigned char i,j; //i,j分别表示日的十位、个位 i=x/10;//取十位 j=x;//取个位 WriteAddress(0x0c); //写显示地址,将在第2行第7列开始显示 WriteData(DispDigit[i]); //将百位数字的字符常量写入LCD WriteData(DispDigit[j]); //将十位数字的字符常量写入LCD DelayXms(50); //延时1ms给硬件一点反应时间 } /***************************************************** 函数功能:显示月 入口参数:x ***************************************************/ void DisplayMonth(unsigned char x) { unsigned char i,j; //i,j分别表示月的十位、个位 i=x/10;//取十位 j=x;//取个位 WriteAddress(0x09); //写显示地址,将在第2行第7列开始显示 WriteData(DispDigit[i]); //将百位数字的字符常量写入LCD WriteData(DispDigit[j]); //将十位数字的字符常量写入LCD DelayXms(50); //延时1ms给硬件一点反应时间 } /***************************************************** 函数功能:显示年 入口参数:x ***************************************************/ void DisplayYear(unsigned char x) { unsigned char i,j; //i,j分别表示年的十位、个位 i=x/10;//取十位 j=x;//取个位 WriteAddress(0x06); //写显示地址,将在第2行第7列开始显示 WriteData(DispDigit[i]); //将百位数字的字符常量写入LCD WriteData(DispDigit[j]); //将十位数字的字符常量写入LCD DelayXms(50); //延时1ms给硬件一点反应时间 }

延时程序模块。

Delay.h代码

//延时1ms void Delay1ms(); //延时若干毫秒 void DelayXms(unsigned int DelayCounter); //延时若干微秒 void DelayXus(unsigned char n);

Delay.c代码

#include<reg51.h> //包含单片机寄存器的头文件 #include<intrins.h> //包含_nop_()函数定义的头文件 #include"Delay.h" //包含延时函数的头文件 /***************************************************** 函数功能:延时1ms (3j 2)*i=(3×33 2)×10=1010(微秒),可以认为是1毫秒 ***************************************************/ void Delay1ms() { unsigned char i,j; for(i=0;i<10;i ) for(j=0;j<33;j ) ; } /***************************************************** 函数功能:延时若干毫秒 入口参数:n ***************************************************/ void DelayXms(unsigned int DelayCounter) { unsigned int i; for(i=0;i<DelayCounter;i ) Delay1ms(); } /***************************************************** 函数功能:延时若干微秒 入口参数:n ***************************************************/ void DelayXus(unsigned char n) { unsigned char i; for(i=0;i<n;i ) ; }

1.6. 实例仿真

在Proteus环境下建立图1所示的电路,将编译完成的hex文件装载到单片机中,开始仿真,观察LCD1602液晶的显示变化情况。液晶第一行显示日期:年、月、日。第二行显示时间:时、分、秒

1.7. 总结

本实例我们学习了时钟芯片DS1302的工作原理,电路设计、编程实现。

单片机晶振proteus代码(基于proteus的51单片机开发实例32-DS1302时钟)(6)

,