题目要求: 数字电子日历/时钟设计
设计一个基于MCS51的电子日历和时钟。
- 基本要求
(1) 可通过按键在日历和时间之间切换显示;
(2) 可由按键调整日期和时间
(3) 可整点报时(“嘟、嘟”声)
(4) 可设定时,定时时间到发出“嘟、嘟”声
(5) 具有秒表功能
课程设计电路图:
该课程设计采用ATMEL公司的FLASH 型经典芯片——AT89C51系列单片机作为时钟的控制核心,用于自动显示当前时间、日期、星期、温度。利用单片机定时器T0的中断程序设计出一秒钟的精确定时,通过单一按钮实现了当前时间、日期、秒表、设定闹钟、温度测量等功能的切换,通过矩阵键盘编程实现设置年、月、日、时、分、秒的功能,也可以设定闹钟时间。闹钟可以自定义开关,并具有贪睡功能,闹钟到达预设的时间时,利用扬声器发出“嘟、嘟”声音。系统具有实现整点报时功能。最后在设计中时附加了测温功能,利用DS18B20实时测试环境的温度。 该设计实现功能:
(1) 可通过按键在日历和时间之间切换显示;
(2) 可由按键调整日期和时间;
(3) 可整点报时(“嘟、嘟”声);
(4) 可设定时,定时时间到发出“嘟、嘟”声,并具有贪睡功能和开关功能;
(5) 具有秒表功能;
(6) 具有实时温度测量功能;
(7) 具有星期查询功能;
由于C语言程序设计较汇编可读性强,可移植性,且可以大大降低编程的难度和缩短开发周期,本系统程序采用C语言设计。
- 软件设计思想:
(1)单片机控制模块:单片机控制模块在系统中处于核心地位,其工作包括读取并处理键盘输入、显示模块控制、显示模块切换、闹钟控制等任务。
(2)按键输入模块:此模块完成对各种功能的控制,功能的切换在硬件上通过此部分来操作完成。
(3)温度传感器模块:此模块完成测温功能,通过温度传度器对外部温度的读取,并将信号输入单片机,单片机将此信号进行处理,并在LCD1602上显示。
(4)闹钟模块:此模块实现时钟的整点报时,定时响铃。系统正常工作后,每到整点或闹钟设定时间时,扬声器会发出“嘟、嘟”声。
(1) 可通过按键在日历和时间之间切换显示;
(2) 可由按键调整日期和时间;
(3) 可整点报时(“嘟、嘟”声);
(4) 可设定时,定时时间到发出“嘟、嘟”声,并具有贪睡功能和开关功能;
(5) 具有秒表功能;
(6) 具有实时温度测量功能;
(7) 具有星期查询功能;
由于C语言程序设计较汇编可读性强,可移植性,且可以大大降低编程的难度和缩短开发周期,本系统程序采用C语言设计。
- 软件设计思想:
(1)单片机控制模块:单片机控制模块在系统中处于核心地位,其工作包括读取并处理键盘输入、显示模块控制、显示模块切换、闹钟控制等任务。
(2)按键输入模块:此模块完成对各种功能的控制,功能的切换在硬件上通过此部分来操作完成。
(3)温度传感器模块:此模块完成测温功能,通过温度传度器对外部温度的读取,并将信号输入单片机,单片机将此信号进行处理,并在LCD1602上显示。
(4)闹钟模块:此模块实现时钟的整点报时,定时响铃。系统正常工作后,每到整点或闹钟设定时间时,扬声器会发出“嘟、嘟”声。
使用定时器T0定时50ms用于时钟计时,定时器T1定时10ms用于秒表计时,外部中断0用于设定定时开关,外部中断1用于设定秒表的计时和清零。
初值计算:单片机主频12MHz,机器周期为1微妙,定时器T0使用方式1定时50ms,则计数初值为216-(50ms/1us)=65536-50000=0x3cb0;定时器T1使用方式1定时10ms,则计数初值为216-(10ms/1us)=65536-10000=0xd8f0。
进行代码展示:
/*Main.c*/#include<reg51.h>#include"LCD1602.h"#include"KeyScan.h"#include"SetValue.h"#include"Sounder.h"#include"Ds18b20.h"intIntCount=0,StopCount=0,StopMin=0;ucharSec=0,Min=0,Hour=0,Date=1,month=1,SetMin=1,SetHour=0,NUM1,CountWeek;intyear=2014,Qiehuan;char KEY;unsignedchar code dis_week[]={"SUN,MON,TUE,WED,THU,FRI,SAT"};unsignedchar code para_month[13]={0,0,3,3,6,1,4,6,2,5,0,3,5}; //星期月参变数ucharcode Timetable1[]=" CurrentTime ";ucharcode Datetable1[]=" CurrentDate ";ucharcode Settable1[]=" Set RingTime ";ucharcode CurrentTime[]=" 00:00:00 ";ucharcode SetTime[]=" 00:00 ";ucharcode CurrentDate[]=" 2014-01-01 ";ucharcode CurrentTemp[]=" Temperature ";ucharcode Temptable[]=" 00.0'C ";ucharcode Stopwatch[]=" StopWatch ";ucharcode Stoptable[]=" 000.00s ";bitalarm;/*定时器0、1初始化*/voidTimer_Init(void){ TMOD=0x11; /*定时器T0、T1初始化为方式1*/TL0=0xb0; /*装入定时初值,在主频12MHZ下,定时50ms*/TH0=0x3c;TL1=0xf0; /*装入定时初值,在主频12MHZ下,定时10ms*/TH1=0xd8;ET0=1; /*开启定时器T0中断*/ET1=1; /*开启定时器T1中断*/TR0=1; /*启动定时器T0定时*/TR1=0;}/*外部中断0初始化*/voidInt_Init(void){ IT0=1; //设置下降沿触发方式EX0=1; //开放外部中断0IT1=1; //设置下降沿触发方式EX1=1; //开放外部中断0EA=1;}/*外部中断0中断函数*/voidInt0_int(void) interrupt 0{alarm=!alarm; //闹钟的开关切换}/*中断号1是定时器T0中断*/voidTimer0_int(void) interrupt 1{EA=0; //关中断TL0=0xb7; //重装定时初值,在主频12MHZ下,定时50msTH0=0x3c; //修正量为7个机器周期IntCount++;if(IntCount==20){Sec++; //秒位加一IntCount=0;}EA=1; //开中断}/*外部中断1的中断函数,设定秒表的起始*/voidInt1_int(void) interrupt 2{TR1=!TR1;if(TR1)StopMin=0;}/*中断号3是定时器T1中断*/void Timer1_int(void) interrupt 3{EA=0; /*关中断*/TL1=0xf0; /*装入定时初值,在主频12MHZ下,定时10ms*/TH1=0xd8;StopMin++;EA=1;}/*按键处理程序*/voidKey_Process(uchar Qkeynum){if(Qkeynum=='1') //按键一用于显示切换{Qiehuan++;if(Qiehuan==1) //显示当前时间{uchar num;write_com(0x01); //显示清屏write_com(0x80);for(num=0;num<15;num++){write_data(Timetable1[num]);delay(5);}write_com(0x80+0x40);for(num=0;num<16;num++){write_data(CurrentTime[num]);delay(5);} }else if(Qiehuan==2) //显示当前日期{uchar num;write_com(0x01);//显示清屏write_com(0x80);for(num=0;num<15;num++){write_data(Datetable1[num]);delay(5);}write_com(0x80+0x40);for(num=0;num<16;num++){write_data(CurrentDate[num]);delay(5);} }else if(Qiehuan==3) //显示当前定时{uchar num;write_com(0x01);//显示清屏write_com(0x80);for(num=0;num<15;num++){write_data(Settable1[num]);delay(5);}write_com(0x80+0x40);for(num=0;num<16;num++){write_data(SetTime[num]);delay(5);} }else if(Qiehuan==4) //显示当前温度{uchar num;write_com(0x01);//显示清屏write_com(0x80);for(num=0;num<15;num++){write_data(CurrentTemp[num]);delay(5);}write_com(0x80+0x40);for(num=0;num<16;num++){write_data(Temptable[num]);delay(5);} }else if(Qiehuan==5) //显示秒表{uchar num;write_com(0x01);//显示清屏write_com(0x80);for(num=0;num<15;num++){write_data(Stopwatch[num]);delay(5);}write_com(0x80+0x40);for(num=0;num<16;num++){write_data(Stoptable[num]);delay(5);} Qiehuan=0;}}elseif(Qkeynum=='2'){Sec++;if(Sec>59)Sec=0;}if(Qkeynum=='3'){Min++;if(Min>59)Min=0;}if(Qkeynum=='4'){Hour++;{if(Hour>23)Hour=0;}}if(Qkeynum=='5'){Date++;if(month==1||month==3||month==5||month==7||month==8||month==10||month==12)if (Date>31){Date=1;} //大月31天else if(month==4||month==6||month==9||month==11) if (Date>30) {Date=1;} //小月30天}if(Qkeynum=='6'){month++;if (month>12)month=1;}if(Qkeynum=='7'){year++;if(year>2099)year=2000;}if(Qkeynum=='8'){SetMin++;if(SetMin>59)SetMin=0;}if(Qkeynum=='9'){SetHour++;if(SetHour>23)SetHour=0;}if(Qkeynum=='A') //设置贪睡功能{SetMin=SetMin+2;if(SetMin>60){SetMin=SetMin-60;SetHour++;if(SetHour>23)SetHour=0;}}}/*闰年的计算*/bitleap_year(){bit leap;if((year%4==0&&year%100!=0)||year%400==0)//闰年的条件leap=1;elseleap=0;return leap;}/*星期的自动运算和处理*//*基姆拉尔森计算公式*/unsignedchar week_proc(){ unsigned char num_leap; unsigned char c;unsigned char week;//from http://blog.csdn.net/wylloong/article/details/35818451num_leap=year/4-year/100+year/400;//自00年起到year所经历的闰年数if( leap_year()&& month<=2 ) //既是闰年且是1月和2月 c=5;elsec=6;week=(year+para_month[month]+Date+num_leap+c+4)%7;//计算对应的星期return week;}/*显示当前时间*/voidShowTime(void) {write_com(0x80+0x40);write_Dec(4,Hour);write_Dec(7,Min);write_Dec(10,Sec);}/*显示闹钟设定时间*/voidShowSetTime(void){write_com(0x80+0x40);write_Dec(5,SetHour);write_Dec(8,SetMin);}/*显示日期*/voidShowDate(void){ write_com(0x80+0x40);write_Dec(2,20);write_Dec(4,(year%100));write_Dec(7,month);write_Dec(10,Date);}/*显示秒表时间*/voidShowStopWatch(){write_com(0x80+0x40+4);write_data(StopMin/10000+0x30);write_data((StopMin%10000)/1000+0x30);write_data((StopMin%1000)/100+0x30);write_com(0x80+0x40+8);write_data((StopMin%100)/10+0x30);write_data((StopMin%10)+0x30);}voidCalcu_time(void){if(Sec>59){Sec=0;Min++; if(Min>59){Min=0;Hour++;if(Hour>23) //*24H从零点开始{Date++;Hour=0;if(month==1||month==3||month==5||month==7||month==8||month==10||month==12)if (Date>31){Date=1;month++;} //大月31天else if(month==4||month==6||month==9||month==11) if (Date>30) {Date=1;month++;} //小月30天else if (month==2) {if(leap_year()) //闰年的条件{if (Date>29){Date=1;month++;}} //闰年2月为29天else{if (Date>28) {Date=1;month++;} //平年2月为28天} } //if (month==2) else if (month>12){month=1;year++;if(year>2099)year=2000;} //if (month>12)} //if(Hour==24)} //if(Min==60)} //if(Sec==60)if(Qiehuan==2) //日期和星期同时显示{CountWeek=week_proc();write_com(0x80+0x40+13);write_data(dis_week[4*CountWeek]);write_data(dis_week[4*CountWeek+1]);write_data(dis_week[4*CountWeek+2]);}if((Min==SetMin)&&(Hour==SetHour)) //闹钟定时到达后响铃 {if (alarm)BellRing();else Sounder=0;}else Sounder=0; //扬声器关闭if (alarm&&(Qiehuan==1)) //闹钟开关标志和时间同时显示{write_com(0x80+0x40);write_data('O');write_data('N');}else if((!alarm)&&(Qiehuan==1)){write_com(0x80+0x40);write_data('O');write_data('F');}}void main(){LCD_init(); //LCD1602初始化Timer_Init(); //定时器初始化Int_Init(); //外部中断初始化Qiehuan=1;while(1){Calcu_time(); //时间累计及显示if(Qiehuan==1){ShowTime(); //在LCD1602上显示时间}else if(Qiehuan==2){ShowDate();} else if(Qiehuan==3){ShowSetTime();}elseif(Qiehuan==4){ds18b20Process(); //显示温度}else if (Qiehuan==0){ ShowStopWatch(); //显示秒表}WholePoint(); //整点报时函数 KEY=keynum(); //按键检测及扫描if(KEY!=0){ NUM1=coding(KEY);Key_Process(NUM1);} }}/*LCD1602.c*/#include"LCD1602.h"#include"SetValue.h"ucharcode Timetable[]=" CurrentTime ";ucharcode Datetable[]=" CurrentDate ";ucharcode Currenttable[]=" 00:00:00 ";voidLCD_init(void){ uchar num;rw=0;write_com(0x38);//显示模式设置write_com(0x0c); //开显示,不显示光标write_com(0x06); //地址指针加一,光标加一write_com(0x01);//显示清屏write_com(0x80);for(num=0;num<15;num++){write_data(Timetable[num]);delay(5);}write_com(0x80+0x40);for(num=0;num<16;num++){write_data(Currenttable[num]);delay(5);}}voiddelay(uint z){uint x,y;for(x=z;x>0;x--)for(y=110;y>0;y--);}/*写指令:RS=L,RW=L,D0~D7=指令码,E=高脉冲*/voidwrite_com(unsigned char com){rs=0;lcden=0;P0=com;delay(5);lcden=1;delay(5);lcden=0; }/*写数据:RS=H,RW=L,D0~D7=指令码,E=高脉冲*/voidwrite_data(unsigned char Data){rs=1;lcden=0;P0=Data;delay(5);lcden=1;delay(5);lcden=0; }/*显示两位数,add为第几列,date为数据*/voidwrite_Dec(uchar add,uchar date){uchar decade,unit;decade=date/10;unit=date%10;write_com(0x80+0x40+add);write_data(0x30+decade);write_data(0x30+unit);}/*KeyScan.c*/#include<KeyScan.h> #include<reg51.h>#include"SetValue.h"#include"LCD1602.h"unsigned char code a[]={0xFE,0xFD,0xFB,0xF7};/*获得按键对应的键值*/unsigned char coding(unsigned char m) {unsigned char k;switch(m){case (0x18):k='1';break;case (0x28):k='2';break;case (0x48):k='3';break;case (0x88):k='4';break;case (0x14):k='5';break;case (0x24):k='6';break;case (0x44):k='7';break;case (0x84):k='8';break;case (0x12):k='9';break;case (0x22):k='A';break;case (0x42):k='9';break;case (0x82):k='C';break;case (0x11):k='*';break;case (0x21):k='0';break;case (0x41):k='#';break;case (0x81):k='D';break;}return(k);}//=====================按键检测并返回按键值==========================unsigned char keynum(void){unsigned char row,col,i;P1=0xf0; //低四位输出低电平if((P1&0xf0)!=0xf0) //P1口输入值是否为0xf0,不是则有键按下{delay(10); //延时按键消抖if((P1&0xf0)!=0xf0){row=P1^0xf0; //异或确定哪列有键按下i=0;P1=a[i]; //精确定位某列的按键while(i<4){if((P1&0xf0)!=0xf0){col=~(P1&0xff); //确定行线break; //已定位后提前退出 }else{i++;P1=a[i];}}}else{return 0;}while((P1&0xf0)!=0xf0); //等待按键释放return (row|col); //行线与列线组合后返回}else return 0; //无键按下时返回0}/*Sounder.c*/#include"SetValue.h"void Soundelay(uint z){uint x,y;for(x=z;x>0;x--)for(y=110;y>0;y--);}/*引脚取反产生高低电平使扬声器发声*/void BellRing(void){Sounder=!Sounder;Soundelay(50);}/*整点报时函数*/void WholePoint(void){if((Min==0)&&(Sec<3))BellRing();else Sounder=0;}/*ds18b20.c*/#include<reg51.h>#include"LCD1602.h"sbit DQ = P2 ^4; //ds1820datavoid DSDelay(int num)//延时函数{while(num--) ;}/*初始化ds18b20,参见datasheet初始化时序图*/void Init_DS18B20(void)//{unsigned char x=0;DQ = 1; //DQ复位DSDelay(8); //稍做延时DQ = 0; //单片机将DQ拉低DSDelay(80); //精确延时 大于 480usDQ = 1; //拉高总线DSDelay(14);x=DQ; //稍做延时后ds18b20发出存在脉冲,若x=0则初始化成功 x=1则初始化失败DSDelay(20);}/**********************************************************************/unsigned char ReadOneChar(void)//读一个字节{unsigned char i=0;unsigned char dat = 0;for (i=8;i>0;i--){/*当总线控制器把数据线从高电平拉到低电平,读时序开始*/DQ = 0; // 给脉冲信号dat>>=1;DQ = 1; // 给脉冲信号if(DQ)//ds18b20通过拉高或拉低总线来传递1或0dat|=0x80;DSDelay(4);}return(dat);}/***********************************************************/void WriteOneChar(unsigned char dat)//写一个字节{unsigned char i=0;for (i=8; i>0; i--){DQ = 0; //数据线拉低再释放,写时序开始DQ = dat&0x01; //写0时序或写1时序DSDelay(5);DQ = 1;dat>>=1;}}/*读取温度*/unsigned int ReadTemperature(void){unsigned char a=0;unsigned char b=0;unsigned int t=0;float tt=0;Init_DS18B20();WriteOneChar(0xCC); //跳过读序号列号的操作WriteOneChar(0x44); //启动温度转换Init_DS18B20();WriteOneChar(0xCC); //跳过读序号列号的操作WriteOneChar(0xBE); //读取温度寄存器a=ReadOneChar(); //读低8位b=ReadOneChar(); //读高8位t=b;t<<=8;t=t|a;tt=t*0.0625;//0.0625/LSBt= tt*10+0.5; //放大10倍输出并四舍五入return(t);}void ds18b20Process(void){unsigned int i=0;unsigned chara=0,b=0,c=0,f=0;i=ReadTemperature();//读温度并送显a=i/100;b=i/10-a*10;c=i-a*100-b*10;write_com(0x80+0x40+5);write_data(0x30+a);write_data(0x30+b);write_com(0x80+0x40+8);write_data(0x30+c);}/*SetValue.h*/#ifndef _SetValue_H_#define _SetValue_H_#include<reg51.h>#define uchar unsigned char#define uint unsigned intsbit rs=P2^0;sbit rw=P2^1;sbit lcden=P2^2;sbit Sounder=P2^5;extern uchar SetHour;extern uchar SetMin;extern int IntCount;extern uchar Sec;extern uchar Min;extern uchar Hour;extern uchar Date;extern uchar month;extern int year;#endif/*1602.h*/#ifndef _LCD1602_H_#define _LCD1602_H_void LCD_init(void);void delay(unsigned int z);void write_com(unsigned char com);void write_data(unsigned char Data);void write_Dec(unsigned char add,unsigned char date);#endif/*Sounder.h*/void BellRing(void);void WholePoint(void);/*KeyScan.h*/#ifndef _KeyScan_H_#define _KeyScan_H_unsigned char coding(unsigned char m);unsigned char keynum(void);#endif