题目要求: 数字电子日历/时钟设计
设计一个基于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。
进行代码展示:
javascript
/*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; //开放外部中断0
IT1=1; //设置下降沿触发方式
EX1=1; //开放外部中断0
EA=1;
}
/*外部中断0中断函数*/
voidInt0_int(void) interrupt 0
{
alarm=!alarm; //闹钟的开关切换
}
/*中断号1是定时器T0中断*/
voidTimer0_int(void) interrupt 1
{
EA=0; //关中断
TL0=0xb7; //重装定时初值,在主频12MHZ下,定时50ms
TH0=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;
else
leap=0;
return leap;
}
/*星期的自动运算和处理*/
/*基姆拉尔森计算公式*/
unsignedchar week_proc()
{ unsigned char num_leap;
unsigned char c;
unsigned char week;
//from http://blog.csdn.net/wylloong/article/details/35818451
num_leap=year/4-year/100+year/400;//自00年起到year所经历的闰年数
if( leap_year()&& month<=2 ) //既是闰年且是1月和2月
c=5;
else
c=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; //ds1820data
void 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); //精确延时 大于 480us
DQ = 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或0
dat|=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/LSB
t= 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 int
sbit 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