基于51单片机的电子日历+温度显示设计.docx
《基于51单片机的电子日历+温度显示设计.docx》由会员分享,可在线阅读,更多相关《基于51单片机的电子日历+温度显示设计.docx(36页珍藏版)》请在冰豆网上搜索。
基于51单片机的电子日历+温度显示设计
2电子日历设计与实现
2.1任务分析
一、功能
本设计要求具有显示生肖、年、月、日、星期、时、分、秒等功能;阳历与阴历能够自动关联;具有温度计功能;具备年、月、日、星期、时、分、秒校准功能;设计最终达到效果如图2-1。
2010年
28度
牛
05
月
05
农历已丑
四月十三
星期二
10:
27:
05
图2-1设计效果图
二、系统原理
按照系统设计功能的要求,系统由单片机、时钟模块DS1302、显示模块12864、键盘以及温度采集模块DS18B20共5部分电路组成,电路构成框图如图2-2所示。
图2-2系统设计原理框图
三、系统硬件要求
本设计电路采用AT89S51单片机为控制核心,AT89S51具有在线编程功能,低功耗,能在3V超低压工作。
时钟电路由DS1302提供,它是一种高性能、低功耗、带RAM的实时时钟电路,它可以对年、月、日、周、时、分、秒进行计时,具有闰年补偿功能,工作电压为2.5V~5.5V。
DS1302内部有一个用于临时性存放数据的31*8RAM寄存器。
器件在加电情况下,可自动生成年、月、日、周、时、分、秒时间数据,该器件具有使用寿命长,精度高和低功耗等特点,同时具有掉电自动保存功能。
温度的采集采用数字式温度传感器DS18B20,此类传感器为数字式传感器而且仅需要一条数据线进行数据传输,易于与单片机连接,可以去除A/D模块,降低硬件成本,简化系统电路。
另外,温度传感器DS18B20还具有测量精度高、测量范围广等优点。
显示部分用12864LED液晶显示屏,液晶显示屏的显示功能强大,可显示大量文字、图形,显示多样,清晰可见,能够达到较好的显示效果。
2.2电路设计
一、单片机主控制模块的设计
AT89S51单片机为40引脚双列直插芯片,有四个I/O口P0,P1,P2,P3,MCS-51单片机共有4个8位的I/O口(P0、P1、P2、P3),每一条I/O线都能独立地作输出或输入。
单片机的最小系统如图3-1所示,18引脚和19引脚接时钟电路,XTAL1接外部晶振和微调电容的一端,在片内它是振荡器倒相放大器的输入,XTAL2接外部晶振和微调电容的另一端,在片内它是振荡器倒相放大器的输出。
第9引脚为复位输入端,接上电容,电阻及开关后够上电复位电路,20引脚为接地端,40引脚为电源端,单片机电路如图2-3所示。
图2-3单片机电路
二、时钟电路模块的设计
DSl302数据操作原理:
DSl302在任何数据传送时必须先初始化,把RST脚置为高电平,然后把8位地址和命令字装入移位寄存器,数据在SCLK的上升沿被输入。
无论是读周期还是写周期,开始8位指定40个寄存器中哪个将被访问到。
在开始8个时钟周期,把命令字节装入移位寄存器之后,另外的时钟周期在读操作时输出数据,在写操作时写入数据。
DS1302的引脚连接如图2-4所示。
其中VCC1为后备电源,VCC2为主电源。
在主电源关闭的情况下,也能保持时钟的连续运行。
DS1302由VCC1或VCC2两者中的较大者供电。
当VCC2大于VCC1+0.2V时,VCC2给DS1302供电。
当VCC2小于VCC1时,DS1302由VCC1供电。
X1和X2是振荡源,外接32.768kHz晶振。
RST是复位/片选线,通过把RST输入驱动置高电平来启动所有的数据传送,与AT89C51的P3.0相连。
RST输入有两种功能:
首先,RST接通控制逻辑,允许地址/命令序列送入移位寄存器;其次,RST提供终止单字节或多字节数据的传送手段。
当RST为高电平时,所有的数据传送被初始化,允许对DS1302进行操作。
如果在传送过程中RST置为低电平,则会终止此次数据传送,I/O引脚变为高阻态。
上电运行时,在VCC≥2.5V之前,RST必须保持低电平。
只有在SCLK为低电平时,才能将RST置为高电平。
I/O为串行数据输入输出端(双向),与AT89C51的P3.1相连。
SCLK始终是输入端。
图2-4DS1302的引脚连接图
如果在传送过程中置RST脚为低电平,则会终止本次数据传送,并且I/O引脚变为高阻态。
上电运行时,在Vcc≥2.5V之前,RST脚必须保持低电平。
只有在SCLK为低电平时,才能将RCT置为高电平。
三、温度采集模块设计
如图2-5所示。
采用数字式温度传感器DS18B20,它是数字式温度传感器,具有测量精度高,电路连接简单特点,此类传感器仅需要一条数据线进行数据传输,使用P0.7与DS18B20的I/O口连接加一个上拉电阻,Vcc接电源,Vss接地。
图2-5DS18B20温度采集
四、显示模块的设计
液晶显示模块12864是一种图形点阵液晶显示器,它主要是由128×64个液晶显示点组成的一个128列×64行的阵列,可以显示8×4个(16×16点阵)汉字。
每个显示点对应一位二进制数,1表示亮,0表示灭。
存储这些点阵信息的RAM称为显示数据存储器。
要显示某个图形或汉字就是将相应的点阵信息写入到相应的存储单元中。
图形或汉字的点阵信息当然由自己设计,问题的关键就是显示点在液晶屏上的位置(行和列)与其在存储器中的地址之间的关系。
LCD12864的主要技术参数和性能见表2.1。
表2.112864主要技术参数和性能
参数
性能
电源
电源功耗电流
显示内容
接口总线
工作温度
储存温度
VDD:
+5V;模块内自带-10V负压,用于LCD的驱动电压
电源功耗电流(Idd)≤2.8mA
128(列)×64(行)点
与CPU接口采用8位数据总线并行输入输出和8条控制线;
-20℃~+60℃
-30℃~+70℃,当温度低于–15℃时,不能做动画显示。
液晶显示模块12864的DB0-DB7是用来传输数据的,与AT89S51的P0.0-P0.7相连。
片选线
和
与AT89C51的P1.1、P1.0相连。
GND是接地线,VCC与AT89C51的电源连接起来。
由于设计结果仿真实现,因此12864的对比度调节控制和背光照明电源等引脚为系统默认,在设计中可以不予处理。
12864的电路连接方式见图2-6所示。
图2-6LCD12864连接图
2.3系统的软件设计
一、软件设计平台与设计过程
目前单片机程序的设计大多采用Keil设计平台,图2-7是该软件的操作界面,本设计程序采用Keilc51v802版本,利用C语言编写,由于电子日历的数据处理和函数较多,因此程序模块设计。
在设计过程中,首先编写主程序以外的子程序模块,如12864显示器驱动、DS1302驱动等,然后编写主函数。
为了程序结构简单化,各个子程序都经过条件编译形成头文件被主程序包含。
被主程序包含的文件含有汉字与字符库、线条图形库等。
图2-7Keil软件的使用界面
二、程序流程框图
在编程部分,首先要初始化DS1302模块,即从DS1302中读出日期和时间,还要从温度传感器DS18B20中读取温度。
分离日期、时间、温度后送到显示模块。
最后由农历更新子程序来实现与阳历自动关联。
程序流程框图见图2-7。
图2-7主程序流程图
主程序包含主函数和12864.h、model.h、ds1302.h、one-wire.h、lunar.h、input_080627.h头文件,并且含有系统的硬件的预处理指令。
主函数的作用是获取时间数据、显示温度、显示时间信息、对时间调整函数调整等,主函数的部分程序如下:
#include
#include"12864.h"
#include"model.h"
#include"ds1302.h"
#include"one-wire.h"
#include"lunar.h"
#include"input_080627.h"
#defineucharunsignedchar
#defineuintunsignedint
#defineNoUpLine1
#defineUpLine0
#defineNoUnderLine1
#defineUnderLine0
#defineFALSE0
#defineTRUE1
voidmain(void)
{
SFR_Init();
CAL_Init();
GUI_Init();
TR0=1;
TR1=1;
while
(1)
{
GetTime(&sys);//获得时间
LCD_ShowTemp();//显示温度
LCD_ShowWNL();//显示万年历
Time_Set();//时间设置
}
}
三、阳历程序的设计
时钟芯片DSl302本身能够产生时间信息,阳历程序只需从DSl302各寄存器中读出年、月、日、星期、小时、分、秒等数据,然后再处理即可。
在首次对DSl302进行操作之前,必须对它进行初始化,
图2-8阳历程序流程图
然后从DSl302中读出数据,再经过处理后,送给显示缓冲单元。
图2-8所示为阳历程序流程图,程序如下:
#include
//#include"ds1302.h"
#defineucharunsignedchar
#defineuintunsignedint
#defineSECOND0x80//秒
#defineMINUTE0x82//分鍾
#defineHOUR0x84//小時
#defineDAY0x86//天
#defineMONTH0x88//月
#defineWEEK0x8a//星期
#defineYEAR0x8c//年
sbitDS1302_RST=P3^0;
sbitDS1302_SCLK=P3^1;
sbitDS1302_IO=P3^2;
typedefstructsystime
{
ucharcYear;
ucharcMon;
ucharcDay;
ucharcHour;
ucharcMin;
ucharcSec;
ucharcWeek;
}SYSTIME;
voidDS1302_Write(ucharD)
{
uchari;
for(i=0;i<8;i++)
{
DS1302_IO=D&0x01;
DS1302_SCLK=1;
DS1302_SCLK=0;
D=D>>1;
}
}
ucharDS1302_Read()
{
ucharTempDat=0,i;
for(i=0;i<8;i++)
{
TempDat>>=1;
if(DS1302_IO)TempDat=TempDat|0x80;
DS1302_SCLK=1;
DS1302_SCLK=0;
}
returnTempDat;
}
voidWDS1302(ucharucAddr,ucharucDat)
{
DS1302_RST=0;
DS1302_SCLK=0;
DS1302_RST=1;
DS1302_Write(ucAddr);//地址,命令
DS1302_Write(ucDat);//写1Byte数据
DS1302_SCLK=1;
DS1302_RST=0;
}
ucharRDS1302(ucharucAddr)
{
ucharucDat;
DS1302_RST=0;
DS1302_SCLK=0;
DS1302_RST=1;
DS1302_Write(ucAddr);//地址,命令
ucDat=DS1302_Read();
DS1302_SCLK=1;
DS1302_RST=0;
returnucDat;
}
voidSetTime(SYSTIMEsys)
{
WDS1302(YEAR,sys.cYear);
WDS1302(MONTH,sys.cMon&0x1f);
WDS1302(DAY,sys.cDay&0x3f);
WDS1302(HOUR,sys.cHour&0xbf);
WDS1302(MINUTE,sys.cMin&0x7f);
WDS1302(SECOND,sys.cSec&0x7f);
WDS1302(WEEK,sys.cWeek&0x07);
}
voidGetTime(SYSTIME*sys)
{
ucharuiTempDat;
uiTempDat=RDS1302(YEAR|0x01);
(*sys).cYear=(uiTempDat>>4)*10+(uiTempDat&0x0f);
//uiTempDat=RDS1302(0x88|0x01);
//(*sys).cMon=((uiTempDat&0x1f)>>4)*10+(uiTempDat&0x0f);
uiTempDat=RDS1302(0x88|0x01);
(*sys).cMon=((uiTempDat&0x1f)>>4)*10+(uiTempDat&0x0f);
uiTempDat=RDS1302(DAY|0x01);
(*sys).cDay=((uiTempDat&0x3f)>>4)*10+(uiTempDat&0x0f);
uiTempDat=RDS1302(HOUR|0x01);
(*sys).cHour=((uiTempDat&0x3f)>>4)*10+(uiTempDat&0x0f);
uiTempDat=RDS1302(MINUTE|0x01);
sys->cMin=((uiTempDat&0x7f)>>4)*10+(uiTempDat&0x0f);
uiTempDat=RDS1302(SECOND|0x01);
sys->cSec=((uiTempDat&0x7f)>>4)*10+(uiTempDat&0x0f);
uiTempDat=RDS1302(MONTH|0x01);
(*sys).cMon=uiTempDat&0x17;
uiTempDat=RDS1302(WEEK|0x01);
sys->cWeek=uiTempDat&0x07;
}
四、阴历程序设计
由于DS1302没有阳历计时功能,因此阴历的计时与实现是要通过阳历日期来推导。
根据阳历来推算阴历日期,推算方法是,根据阳历当前日期在一年中的天数来计算阴历日期。
阳历一个月不是30天就是3l天(2月除外,闰年2月为29天,平年2月为28天)。
阴历一年有12个月或13个月(含闰月),一个月为30天或29天。
如果把一个只有29天的月称为小月,用1为标志,把30天的月称为大月,用0为标志,那么12位二进制就能表示一年12个月的大小。
如果有闰月,则把闰月的月份作为一个字节的高4位,低4位表示闰月大小,大月低四位为0,小月为1。
这样一个字节就包含了所有闰月的信息。
阴历春节和阳历元旦相差的天数也用一个字节表示。
总共用1字节就可以存储一年中任何一天阳历和阴历的对应关系的有关数据。
按此方法,先要根据当前阳历的日期,算出阳历为该年中的第几天。
计算出当前阳历日期为该年中的第几天后,再减去阳历该年春节和元旦的日差,如果够减。
则相减的结果就是阴历在该年的总第几天了。
根据该数据就可以推算出具体的当前阴历日期;如果不够减,则表示当的阴历年为阳历年的前一年。
这种情况下,当前阴历日期会处于阴历11月或12月,此时春节和元旦的日差减去前面计算出的当前阳历日期在阳历年为第几天的数据。
其结果表示当的阴历日期离春节的天数。
计算出的阳历天数为该年的第几天,存放在寄存器R2和R3中。
计算出天数后,如果大于#FFH,则把#FFH存放在R2中,余值存放在R3中。
也就是说在用寄存器R2和R3表示的天数信息中。
R2充当主寄存器,数据先存满R2,再存R3。
整个转换程序中,这里面的数据不能被覆盖。
图2-9推算阴历日期的程序流程图
计算出阳历总天数后,就可以根据它来推算阴历日期。
推算方法是,先用总天数减去春节和元旦的日差,如果结果为1,则该天正好是春节(因为春节在元旦之后,在计算春节和几旦的日差0时,假设元旦为0天,春节为M天,则日差为M。
而前面计算的阳历总天数是该天在该年中的第几天,是以元旦为1而得到的,与计算春节和元旦日差的这种方法相比,其数值少了1。
所以要在原来本应该以0作为该天就是春节的依据的基础上加1,所以以1作为该天是春节的标志)。
如果结果小于1,则阴历应该是阳历的前一年;如果结果大于1,说明阳历和阴历为同一年。
图2-9为由总天数推算出阴历日期的程序流程图。
五、时间调整程序设计
调整时间用3个调整按钮,1个作为控制用,另外2个作为加和减用,分别定义为设置按钮、加按钮和减按钮。
在调整时间过程中,要调整的位与别的位应该有区别,所以增加了闪烁功能。
时间调整程序程序流程图如图2-10所示。
程序见附录。
图2-10时间调整程序程序流程图
2.4系统仿真
本设计仿真采用Proteus电路仿真平台,该软件由Labcenter公司开发,是目前世界上最先进、最完整的嵌入式系统设计与仿真平台,可以实现数字电路、模拟电路及微控制器系统与外设的混合电路系统的电路仿真、软件仿真、系统协同仿真和PCB设计等功能,是目前唯一能够对各种处理器进行实时仿真、调试与测试的EDA工具。
图2-11电路仿真图
Proteus7.4版本的电路仿真界面下搭建本设计电路,为了精简电路设计,电路器件之间的连接采用网路标号,电路见图2-11所示。
然后加载程序的HEX文件,然后点击仿真运行按钮进入电路仿真状态。
仿真效果见图2-12所示,系统通过在Proteus软件仿真,模拟出了实际电路的运行效果,验证了程序的正确性,达到了本设计的目的。
图2-12电路仿真效果
总结
本系统电路较为简单,但软件工作量较大。
在系统软件设计过程中,利用C语言编程,模块化程序设计,由于程序率为复杂,数据处理指令较多,逻辑性强,对与软件设计工作提出了较高要求。
参考文献
[1]刘勇.数字电路[M].电子工业出版社,2006.156-169.
[2]陈正振.电子电路设计与制作[J].广西交通职业技术学院信息工程系,2007.
[3]杨子文.单片机原理及应用[M].西安电子科技大学出版社,2006.
[4]张毅刚.MCS-51单片机原理及应用[M].北京:
教育出版社,2007.227-237.
[5]周润景,张丽娜.基于PROTEUS的电路及单片机系统设计与仿真[M].北京:
北京航空航天大学出版社,2006.96-125.
[6]杨将新,李华军,刘东骏.单片机程序设计及应用从基础到实践[M].电子工业出版社,2006.4(3):
93一95.
[7]林志琦,郎建军.基于PROTEUS的单片机可视化软硬件仿真[M].北京:
北京航空航天大学出版社,2006.9(5):
45-51.
[8]白驹珩,雷晓平.单片计算机及运用[M].电子科技大学出版社,2008.
[9]ATMEL.HowtoCalculateTheCapacitorofTheResetInputofaC51Microcontroller.2003(9).
[10]ATMEL.8-bitMicrocontrollerwith4KBytesIn-SystemProgrammableFlashAT89S51.2002(12).
附录
程序清单
#include
#include"12864.h"
#include"model.h"
#include"ds1302.h"
#include"one-wire.h"
#include"lunar.h"
#include"input_080627.h"
#defineucharunsignedchar
#defineuintunsignedint
#defineNoUpLine
#defineUpLine
#defineNoUnderLine1
#defineUnderLine
#defineFALSE
#defineTRUE
ucharbdatagTmp;
sbitt0=gTmp^0;
sbitt1=gTmp^1;
sbitt2=gTmp^2;
sbitt3=gTmp^3;
sbitt4=gTmp^4;
sbitt5=gTmp^5;
sbitt6=gTmp^6;
sbitt7=gTmp^7;
uchardispBuf[7];
ucharT0_Count=0,Tmp_Count=0;
bitT0_Flag,Tmp_Flag,Flash_Flag;
SYSTIMEsys;//系统日期
SPDATESpDat;//农历日期
//设置时间的标志
bitHour_Flag=TRUE,Min_Flag=TRUE,Sec_Flag=TRUE;
bitYear_Flag=TRUE,Mon_Flag=TRUE,Day_Flag=TRUE;
ucharState_Set=0;//设置状态(用来区分此时是在设置时,分,秒,日,月,年)
bitState_Flag=FALSE,Inc_Flag=FALSE,Dec_Flag=FALSE;//用来标志三个按键是否按下。
ucharcodeMon2[2][13]={0,31,28,31,30,31,30,31,31,30,31,30,31,
0,31,29,31,30,31,30,31,31,30,31,30,31};
/***********************************************************
cDat:
要显示的数
X:
行数0~7
Y:
列数0~127
show_flag:
是否反白显示,0反白,1不反白
upline:
上划线,0表示带上划线
underline:
下划线,0表示带下划线
返回:
无
************************************************************/
voidLCD_ShowTime(charcDat,ucharX,ucharY,bitshow_flag,bitup,bitunder)
{
uchars[2];
s[0]=cDat/10+'0';
s[1]=cDat%10+'0';
en_disp(X,Y,2,Asc,s,show_flag,up,under);
}
/***********************************************************
//函数名:
Show_YMD()
//入口参数:
void
//出口参数:
void
//功能:
显示公历与农历的年、月、日、星期
//说明:
普通模式每天刷新一次
//设置模式每次循环刷新一次