综合实验报告
实验题目: 万年历
学生班级: 学生姓名: 学生学号: 指导教师: 实验时间:
摘要
本设计是电子万年历。具备三个功能:能显示:年、月、日、时、分、秒及星期信息,并具有可调整日期和时间功能。
我选用的是单片机STC89C52来实现电子万年历的功能。该电子万年历能够成功实现时钟运行,调整,显示年月日时分秒等信息。
该电子万年历使用12MHZ晶振与单片机STC89C52相连接,通过软件编程的方法实现了以24小时为一个周期,同时显示小时、分钟和秒的要求。利用单片机定时器及计数器产生定时效果通过编程形成数字钟效果,再利用数码管动态扫描显示单片机内部处理的数据。同时通过端口读入当前外部控制状态来改变程序的不同状态,实现不同功能。
电子万年历设计与制作可采用数字电路实现,也可以采用单片机来完成。若用数字电路完成,所设计的电路相当复杂,大概需要十几片数字集成块,其功能也主要依赖于数字电路的各功能模块的组合来实现,焊接的过程比较复杂,成本也非常高。若用单片机来设计制作完成,由于其功能的实现主要通过软件编程来完成,这样一来就降低了硬件电路的复杂性,从而使得其成本降低,更适合我们大学生自主研发。所以在该设计与制作中我选用了单片机STC89C52,它是低功耗、高性能的CMOS型8位单片机。片内带有4KB的Flash存储器,且允许在系统内改写或用编程器编程。另外, 单片机STC89C52的指令系统和引脚与8051完全兼容,片内有128B 的RAM、32条I/O口线、2个16位定时计数器、5个中断源、一个全双工串行口等。
因此,采用单片机STC89C52原理制作的电子万年历,不仅仅在原理上能够成功实现计时等功能,也更经济,更适用,更符合我们实际生活的需要,对我们大学生来说也更加有用。
关键词:STC89S52 ,DS1302,LCD,PROTEUS
1 目录
一、实验要求: ...............................................................................................................................3
1.1基本要求: .........................................................................................................................3 1.2拓展要求: .........................................................................................................................3
二、方案论证: ...............................................................................................................................3
2.1.显示部分: ...........................................................................................................................3 2.2.数字时钟: .........................................................................................................................3
三、总体方案: ...............................................................................................................................4
四、系统硬件设计: .......................................................................................................................4
4.1.时间设置模块: ...............................................................................................................4 4.2.时钟模块: ...........................................................................................................................4 4.3.整点报时电路 .....................................................................................................................5 4.4. LCD液晶显示模块: .......................................................................................................5
五、整体电路: ...............................................................................................................................6
5.1.电路 .....................................................................................................................................6
六、数字时钟使用说明: ...............................................................................................................7
七、心得体会: ...............................................................................................................................7
八、程序代码: ...............................................................................................................................7
一、实验要求:
1.1基本要求:
1、能动态显示年、月、日、星期、小时、分钟、秒。
2、可用键盘进行日期时间的修改。 1.2拓展要求:
3、具有闹钟功能:可设定闹钟时间,在时间到达后,发出报警时间,持续5秒。
二、方案论证:
2.1.显示部分: 显示部分是本次设计的重要部分,一般有以下两种方案: 方案一:
采用LED显示,分静态显示和动态显示。对于静态显示方式,所需的译码驱动装置很多,引线多而复杂,且可靠性也较低。而对于动态显示方式,虽可以避免静态显示的问题,但设计上如果处理不当,易造成亮度低,有闪烁等问题。 方案二:
采用LCD显示。LCD液晶显示具有丰富多样性、灵活性、电路简单、易于控制而且功耗小等优点,对于信息量多的系统,是比较适合的。
鉴于上述原因,我们采用方案二。 2.2.数字时钟:
数字时钟是本设计的核心的部分。根据需要可采用以下两种方案实现: 方案一:
方案完全用软件实现数字时钟。原理为:在单片机内部存储器设三个字节分别存放时钟的时、分、秒信息。利用定时器与软件结合实现1秒定时中断,每产生一次中断,存储器内相应的秒值加1;若秒值达到60,则将其清零,并将相应的分字节值加1;若分值达到60,则清零分字节,并将时字节值加1;若时值达到24,则将时字节清零。该方案具有硬件电路简单的特点,但当单片机不上电,程序将不执行。而且由于每次执行程序时,定时器都要重新赋初值,所以该时钟精度不高。
方案二:
方案采用Dallas公司的专用时钟芯片DS1302。该芯片内部采用石英晶体振荡器,其芯片精度不大于10ms/年,且具有完备的时钟闹钟功能,因此,可直接对其以用于显示或设置,使得软件编程相对简单。为保证时钟在电网电压不足或突然掉电等突发情况下仍能正常工作,芯片内部包含锂电池。当电网电压不足或突然掉电时,可使系统自动转换到内部锂电池供电系统。而且即使系统不上电,程序不执行时,锂电池也能保证芯片的正常运行,以备随
3 时提供正确的时间。
基于时钟芯片的上述优点,本设计采用方案二完成数字时钟的功能。
三、总体方案:
本设计采用STC89C52RC单片机作为本系统的控制模块。单片机可把由DS130
2、LCD液晶显示模块中的数据利用软件来进行处理,从而把数据传输到显示模块,实现日历和修改的显示。以LCD液晶显示器为显示模块,把单片机传来的数据显示出来,并且显示多样化。在显示电路中,主要靠按键来实现日期的修改和选择。
四、系统硬件设计:
4.1.时间设置模块:
时间设置模块通过四个按键实现万年历的时间调整设置。KEY1键进入时间设置模式,KEY2键实现设置内容选择,KEY3键实现设置内容加1,KEY4键实现设置内容减1。
图1.时间设置模块
4.2.时钟模块: 时钟模块采用DS1302芯片,DS1302是DALLAS公司推出的涓流充电时钟芯片内含有一个实时时钟/日历和31字节静态RAM通过简单的串行接口与单片机进行通信实时时钟/日历电路提供秒分时日日期月年的信息每月的天数和闰年的天数可自动调整时钟操作可通过 4 AM/PM指示决定采用24或12小时格式DS1302与单片机之间能简单地采用同步串行的方式进行通信仅需用到三个口线:
RST复位、I/O数据线、SCLK串行时钟。时钟/RAM的读/写数据以一个字节或多达31个字节的字符组方式通信。DS1302工作时功耗很低,保持数据和时钟信息时功率小于1mW,其接线电路如图2所示:
图2.时钟模块
4.3.整点报时电路
整点报时电路由一个NPN三极管和蜂鸣器组成,当时间走到整点时分,会每间隔一秒发出一次报时,连响五次。
图3.报时电路
4.4.LCD液晶显示模块: LCD液晶显示模块采用LCD1602型号,具有很低的功耗,正常工作时电流仅2.0mA/5.0V。通过编程实现自动关闭屏幕能够更有效的降低功耗。LCD1602分两行显示,每行可显示多达16个字符。LCD1602液晶模块内部的字符发生存储器(CGROM)已经存储了160个不同的点阵字符图形,通过内部指令可实现对其显示多样的控制,并且还能利用空余的空间自定义字符。其接线如图4所示:
图4.LCD液晶显示
五、整体电路:
5.1.电路
图5.整体电路
六、数字时钟使用说明:
调整时间设定:在时间显示界面中按KEY1键后,进入时间调整,通过KEY2选择需要修改的或设定的时间。按KEY2键对应内容向右移动,KEY3键加1,KEY4键减1,设置完毕,自动退出设置功能。
七、心得体会:
通过这一周的课程设计,加深了我对于单片机和数字电路的认识,通过查阅大量的资料,我获得了以前在课堂上学不到的东西,我想这对于以后的毕业设计,或者工作也好,都是很有帮助的。在设计电路中,往往是先仿真后连接实物图,但有时候仿真和电路连接并不是完全一致的,例如仿真的连接示意图中,往往没有接高电平的16脚或14脚以及接低电平的7脚或8脚,因此在实际的电路连接中往往容易遗漏。在设计的过程中遇到了很多问题,本来自己的单片机基础就比较弱,又是第一次做这样的设计,难免会遇到过各种各样的问题。同时在设计的过程中发现了自己的不足之处,比如对以前所学过的知识理解得不够深刻,掌握得不够牢固,对单片机汇编语言掌握得不好。此次的电子万年历设计重在于仿真和接线,虽然能把电路图接出来,并能正常显示,但对于电路本身的原理并不是十分熟悉.总的来说,通过这次的设计实验更进一步地增强了实验的动手能力。
八、程序代码:
#include \"reg51.h\" #include \"intrins.h\" typedef unsigned char uint8; typedef unsigned int uint16;
7
#define LED_Y P2 #define LCD_DATA 1 #define LCD_COMMAND 0 #define WORDNUU 11
sbit ALARM=P2^0; sbit LCD_RS=P1^5; sbit LCD_RW=P1^6; sbit LCD_EN=P1^7; sbit RTC_SDA=P3^6; sbit RTC_SCL=P3^5; sbit RTC_RST=P3^4; sbit KEY1=P1^0; sbit KEY2=P1^1; sbit KEY3=P1^2; sbit KEY4=P1^3;
#define LCD_PORT P0 uint8 LCD_NO; uint16 i1,i2; uint8 bdata AA; sbit AA7 = AA^7; #define READ_SECOND #define WRITE_SECOND #define READ_MINE
#define WRITE_MINE
#define READ_HOUR
#define WRITE_HOUR #define READ_DAY #define WRITE_DAY #define READ_MONTH #define WRITE_MONTH #define READ_WEEK #define WRITE_WEEK #define READ_YEAR
#define WRITE_YEAR
uint8 SET; uint8 PRE_HOUR; uint8 DATE[16]; uint8 TIME[16];
0x81 0x80 0x83 0x82 0x85 0x84 0x87 0x86 0x89 0x88 0x8b 0x8a 0x8d 0x8c 8
void delaynus(uint8 n) {
uint8 i;
for(i=0;i
}
void RTC_Write(uint8 dat) { uint8 i;
RTC_SCL=0;
//拉低SCLK,为脉冲上升沿写入数据做好准备
delaynus(2);
//稍微等待,使硬件做好准备
for(i=0;i
//连续写8个二进制位数据
{
RTC_SDA=dat&0x01;
//取出dat的第0位数据写入1302
delaynus(2);
//稍微等待,使硬件做好准备
RTC_SCL=1;
//上升沿写入数据
delaynus(2);
//稍微等待,使硬件做好准备
RTC_SCL=0;
//重新拉低SCLK,形成脉冲
dat>>=1;
//将dat的各数据位右移1位,准备写入下一个数据位
}
}
uint8 RTC_Read() { uint8 i,dat;
delaynus(2);
//稍微等待,使硬件做好准备
for(i=0;i
//连续读8个二进制位数据
{
dat>>=1;
//将dat的各数据位右移1位,因为先读出的是字节的最低位
if(RTC_SDA==1)
//如果读出的数据是1
dat|=0x80;
//将1取出,写在dat的最高位
RTC_SCL=1;
//将SCLK置于高电平,为下降沿读出
delaynus(2); //稍微等待
RTC_SCL=0;
//拉低SCLK,形成脉冲下降沿
delaynus(2); //稍微等待
}
return dat;
//将读出的数据返回 }
void RTC_Write_Byte(uint8 addr,uint8 dat) {
RTC_RST=0;
//禁止数据传递
RTC_SCL=0;
//确保写数居前SCLK被拉低
RTC_RST=1;
//启动数据传输
delaynus(2);
//稍微等待,使硬件做好准备
RTC_Write(addr);
//写入命令字
RTC_Write(dat);
//写数据
RTC_SCL=1;
//将时钟电平置于已知状态
RTC_RST=0;
//禁止数据传递
}
uint8 RTC_Read_Byte(uint8 addr) {
uint8 dat;
RTC_RST=0;
//拉低RST
RTC_SCL=0;
//确保写数居前SCLK被拉低
RTC_RST=1;
//启动数据传输
RTC_Write(addr);
//写入命令字
dat=RTC_Read();
//读出数据
RTC_SCL=1;
//将时钟电平置于已知状态
RTC_RST=0;
//禁止数据传递
return dat;
//将读出的数据返回 }
typedef struct __SYSTEMTIME__ { uint8 Second; uint8 Mine; uint8 Hour; uint8 Day; uint8 Month; uint8 Week; uint8 Year; }SYSTEMTIME;
static SYSTEMTIME TIMETYPE;
void CurrentTime(SYSTEMTIME *dat) { uint8 TimeValue;
TimeValue=RTC_Read_Byte(READ_SECOND); dat->Second=(((TimeValue&0x70)>>4)*10)+(TimeValue&0x0f);
TimeValue=RTC_Read_Byte(READ_MINE);
10 dat->Mine= (((TimeValue&0x70)>>4)*10)+(TimeValue&0x0f);
TimeValue=RTC_Read_Byte(READ_HOUR); dat->Hour= (((TimeValue&0x70)>>4)*10)+(TimeValue&0x0f);
TimeValue=RTC_Read_Byte(READ_DAY); dat->Day= (((TimeValue&0x70)>>4)*10)+(TimeValue&0x0f);
TimeValue=RTC_Read_Byte(READ_MONTH); dat->Month= (((TimeValue&0x70)>>4)*10)+(TimeValue&0x0f);
TimeValue=RTC_Read_Byte(READ_WEEK); dat->Week= (TimeValue&0x0f);
TimeValue=RTC_Read_Byte(READ_YEAR); dat->Year= (((TimeValue&0x70)>>4)*10)+(TimeValue&0x0f); }
bit LCD_BUSY() { bit result; LCD_RS=0; LCD_RW=1; LCD_EN=1; _nop_(); _nop_(); result=(bit)(LCD_PORT&0x80); LCD_EN=0; return result; }
void LCD_Write(uint8 dat,uint8 type)
//LCD写字符 { uint8 i; while((LCD_BUSY()&&i99)LCD_NO=1; LCD_RS=type; LCD_RW=0; LCD_EN=0; _nop_(); _nop_(); _nop_(); _nop_(); LCD_PORT=dat; _nop_(); _nop_(); _nop_();
11 _nop_(); LCD_EN=1; _nop_(); _nop_(); _nop_(); _nop_(); LCD_EN=0; }
void LCD_INITIALIZE()
//LCD初始化 { LCD_Write(0x06,LCD_COMMAND); LCD_Write(0x38,LCD_COMMAND); LCD_Write(0x0c,LCD_COMMAND); LCD_Write(0x01,LCD_COMMAND); }
void LCD_Prints(uint8 *dat,uint8 add)
//LCD打印函数 { LCD_Write(add|0x80,LCD_COMMAND); while(*dat!=\'\\0\')LCD_Write((*(dat++)),LCD_DATA); }
void KEY_Delay() { uint8 i,j; for(i=0;i
void SET_YEAR() { uint8 YEAR[3]; LCD_Prints(\" YEAR SETTING \",0); LCD_Prints(\" YEAR:20
\",0x40); if((!KEY3)&&(TIMETYPE.Year79)TIMETYPE.Year=0; if((!KEY4)&&(TIMETYPE.Year>0)){KEY_Delay();i1=i2=0;TIMETYPE.Year--;} YEAR[0]=(TIMETYPE.Year/10)+\'0\'; YEAR[1]=(TIMETYPE.Year%10)+\'0\';
12 YEAR[2]=\'\\0\'; LCD_Prints(YEAR,0x49); }
void SET_MONTH() { uint8 MONTH[3]; LCD_Prints(\" MONTH SETTING \",0); LCD_Prints(\" MONTH:
\",0x40); if((!KEY3)&&(TIMETYPE.Month12)TIMETYPE.Month=1; if((!KEY4)&&(TIMETYPE.Month>1)){KEY_Delay();TIMETYPE.Month--;i1=i2=0;} MONTH[0]=(TIMETYPE.Month/10)+\'0\'; MONTH[1]=(TIMETYPE.Month%10)+\'0\'; MONTH[2]=\'\\0\'; LCD_Prints(MONTH,0x48); }
void SET_DAY() { uint8 DAY[3]; LCD_Prints(\" DAY SETTING
\",0); LCD_Prints(\" DAY:
\",0x40); if((!KEY3)&&(TIMETYPE.Day31)TIMETYPE.Day=1; if((!KEY4)&&(TIMETYPE.Day>1)){KEY_Delay();TIMETYPE.Day--;i1=i2=0;} DAY[0]=(TIMETYPE.Day/10)+\'0\'; DAY[1]=(TIMETYPE.Day%10)+\'0\'; DAY[2]=\'\\0\'; LCD_Prints(DAY,0x46); }
void SET_WEEK() { uint8 WEEK[2]; LCD_Prints(\" WEEK SETTING \",0); LCD_Prints(\" WEEK:
\",0x40); if((!KEY3)&&(TIMETYPE.Week7)TIMETYPE.Week=1; if((!KEY4)&&(TIMETYPE.Week>1)){KEY_Delay();TIMETYPE.Week--;i1=i2=0;} WEEK[0]=TIMETYPE.Week+\'0\';
13 WEEK[1]=\'\\0\'; LCD_Prints(WEEK,0x47); }
void SET_HOUR() { uint8 HOUR[3]; LCD_Prints(\" HOUR SETTING \",0); LCD_Prints(\" HOUR:
\",0x40); if((!KEY3)&&(TIMETYPE.Hour23)TIMETYPE.Hour=0; if((!KEY4)&&(TIMETYPE.Hour>0)){KEY_Delay();TIMETYPE.Hour--;i1=i2=0;} HOUR[0]=(TIMETYPE.Hour/10)+\'0\'; HOUR[1]=(TIMETYPE.Hour%10)+\'0\'; HOUR[2]=\'\\0\'; LCD_Prints(HOUR,0x47); }
void SET_MINE() { uint8 MINE[3]; LCD_Prints(\" MINE SETTING \",0); LCD_Prints(\" MINE:
\",0x40); if((!KEY3)&&(TIMETYPE.Mine59)TIMETYPE.Mine=0; if((!KEY4)&&(TIMETYPE.Mine>0)){KEY_Delay();TIMETYPE.Mine--;i1=i2=0;} MINE[0]=(TIMETYPE.Mine/10)+\'0\'; MINE[1]=(TIMETYPE.Mine%10)+\'0\'; MINE[2]=\'\\0\'; LCD_Prints(MINE,0x47); }
void SET_SECOND() { uint8 SECOND[3]; LCD_Prints(\" SECOND SETTING \",0); LCD_Prints(\" SECOND:
\",0x40); if((!KEY3)&&(TIMETYPE.Second59)TIMETYPE.Second=0; if((!KEY4)&&(TIMETYPE.Second>0)){KEY_Delay();TIMETYPE.Second--;i1=i2=0;} SECOND[0]=(TIMETYPE.Second/10)+\'0\';
14 SECOND[1]=(TIMETYPE.Second%10)+\'0\'; SECOND[2]=\'\\0\'; LCD_Prints(SECOND,0x48); }
void Write_Time() { uint8 year,month,day,hour,mine,second,week; year=((TIMETYPE.Year/10)
RTC_Write_Byte(0x80,WRITE_SECOND|0x80); RTC_Write_Byte(0x8e,0x80); //禁止写入
RTC_Write_Byte(0x8e,0x00); //写入允许
RTC_Write_Byte(WRITE_YEAR,year); //写入新的秒数
RTC_Write_Byte(WRITE_MONTH,month); //写入新的秒数
RTC_Write_Byte(WRITE_DAY,day); //写入新的秒数
RTC_Write_Byte(WRITE_HOUR,hour); //写入新的秒数
RTC_Write_Byte(WRITE_MINE,mine&0x7f); //写入新的秒数
RTC_Write_Byte(WRITE_WEEK,week); //写入新的秒数
RTC_Write_Byte(WRITE_SECOND,second); RTC_Write_Byte(0x8e,0x00); //写入允许
RTC_Write_Byte(0x8e,0x80); }
void SET_TIME() { uint8 i; CurrentTime(&TIMETYPE); while(1) {
switch(i)
{
case 0:SET_YEAR();break;
case 1:SET_MONTH();break;
case 2:SET_DAY(); break;
case 3:SET_WEEK();break;
case 4:SET_HOUR();break;
case 5:SET_MINE();break;
case 6:SET_SECOND();break;
}
if(!KEY2){i++;KEY_Delay();i1=i2=0;}
while(!KEY2);
if(i==7){i=0;Write_Time();break;}
i1++;
if(i1==10){i2++;i1=0;}
if(i2==100){i=0;Write_Time();break;} } SET = 1; }
void TIMESETTING() {
if(!KEY1){SET_TIME();} }
void LCD_DISP() { DATE[0]=\'2\'; DATE[1]=\'0\'; DATE[4]=\'/\'; DATE[7]=\'/\'; DATE[10]=\'/\'; DATE[11]=\' \'; DATE[14]=\' \'; DATE[16]=\'\\0\'; TIME[2]=\':\'; TIME[5]=\':\'; }
void LCD_Disp_Time()
//读显时间 { DATE[2]=TIMETYPE.Year/10+\'0\'; DATE[3]=TIMETYPE.Year%10+\'0\'; DATE[5]=TIMETYPE.Month/10+\'0\'; DATE[6]=TIMETYPE.Month%10+\'0\'; DATE[8]=TIMETYPE.Day/10+\'0\'; DATE[9]=TIMETYPE.Day%10+\'0\';
16 DATE[11]=TIMETYPE.Week+\'0\';
TIME[0]=TIMETYPE.Hour/10+\'0\'; TIME[1]=TIMETYPE.Hour%10+\'0\'; TIME[3]=TIMETYPE.Mine/10+\'0\'; TIME[4]=TIMETYPE.Mine%10+\'0\'; TIME[6]=TIMETYPE.Second/10+\'0\'; TIME[7]=TIMETYPE.Second%10+\'0\';
LCD_Prints(DATE,0x02); LCD_Prints(TIME,0x44); }
void main() { uint8 INIT = 1;
TMOD=0x01; TH0=(65536-45872)/256; TL0=(65536-45872)%256;
EA = 1; ET0 = 1; TR0 = 1;
ALARM = 0; LCD_INITIALIZE();
//LCD初始化
LCD_DISP();
//液晶显示初始值
PRE_HOUR = TIME[1];
while(1) {
if((!KEY1)&&(!LCD_NO))TIMESETTING();
if(SET == 1)
{
SET = 0;
LCD_INITIALIZE(); //LCD初始化
}
CurrentTime(&TIMETYPE);
LCD_Disp_Time();
if(INIT == 1)
{
INIT = 0;
PRE_HOUR = TIME[1];
} }
}
void timer0() interrupt 1 { uint16 i,TIM;
TH0=(65536-45872)/256; TL0=(65536-45872)%256; TIM++;
if(TIM == 10) {
TIM = 0;
if(PRE_HOUR != TIME[1])
{
i++;
ALARM =~ ALARM;
if(i == 10)
{
i = 0;
PRE_HOUR = TIME[1];
}
} }
} 18