基于C51的电子钟设计.docx
《基于C51的电子钟设计.docx》由会员分享,可在线阅读,更多相关《基于C51的电子钟设计.docx(24页珍藏版)》请在冰点文库上搜索。
基于C51的电子钟设计
基于AT89C51单片机的电子钟设计
1系统电路设计
1.1系统总体设计思路
此设计原理框图如下所示,电路包括四个局部:
单片机,键盘,锁存及显示电路,掉电保护电路。
图1.1单片机实现数码管显示电子钟总框图
对于各局部:
(1)单片机发送的信号经过锁存电路最终在数码管上显示出来。
(2)单片机通过输出各种电脉冲信号来驱动控制各局部正常工作。
(3)掉电保护电路保证系统掉电时时钟不会停顿。
(4)为使时钟走时与标准时间一致,校时电路是必不可少的,键盘用来校正数码管上显示的时间。
1.2工作原理
设计的电路主要由四大模块构成:
掉电保护电路,单片机控制电路,显示电路以及校正电路。
本设计采用C语言程序设计,使单片机控制数码管显示年、月、日、时、分、秒,当秒计数计满60时就向分进位,分计数器计满60后向时计数器进位,小时计数器按“23翻0〞规律计数。
时、分、秒的计数结果经过数据处理可直接送显示器显示。
当计时发生误差的时候可以用校时电路进展校正。
时计数器计满24小时后自动向日计数器进一,日计数器需判断平年、闰年和大月、小月,当日计数器计满时,向月计数器进位,月计数器计满12月向年计数器进位。
设计采用的是年、月、日、时、分、秒显示,单片机对数据进展处理同时在数码管上显示。
2单元电路设计
2.1单片机电路设计
本设计采用AT89C52单片机进展设计,它是一种低功耗,高性能的CMOS8位微处理器,内部有8K字节的程序存储器和256字节的数据存储单元,32个I/O端口,3个16位定时/计数器,8个中断源。
时钟电路是单片机系统的心脏,它控制着单片机的工作节奏。
本设计采用内部时钟方式,12MHz的石英晶体振荡器。
电路图如下
图2.1时钟电路
复位电路由单片机引脚RST接入,只要RST端保持10ms以上的高电平,就能使单片机有效地复位,本设计采用上电复位和手动复位两种方式。
电路图如下
图2.2复位电路
2.2掉电保护电路设计
本设计采用如下掉电保护电路,当电源供电正常时,一方面给单片机供电,另一方面给电池充电,当电源断电时,电池放电,继续给单片机供电,保证其正常工作。
在电源掉电时,为了不使低电平影响到单片机的VCC端,在电源与单片机的VCC端加一二极管。
图2.3掉电保护电路
2.3独立键盘设计
此键盘是为设置时间而设计的人机交互装置。
虽然矩阵键盘操作方便,但其硬件电路和软件都较复杂,考虑各方面因素,最终决定采用独立键盘,这样的话只需三个按键即可。
电路如下列图所示
图2.4键盘电路
其中,按下set键进入时间设置方式,此时默认设置秒,按add键进展加1设置,按minus键进展减1设置,再次按下set键进入分设置,以此类推,直至年设置完成后再次按下set键,进入正常模式。
2.4显示电路设计
由于要显示年、月、日、时、分、秒,因此需14个8段数码管进展显示,为了节省单片机的I/O端口,本设计采用动态扫描的方式进展显示。
电路图如下
图2.5显示电路
其中,单片机的P0口与三个锁存器并行连接,P2.0,P2.1,P2.2分别作为它们的片选信号,锁存器U1的输出连接7段显示的8个引脚,锁存器U2,U3的输出分别作为14个7段显示的位选信号。
3软件设计
本设计的软件局部采用C语言编写,并将其模块化,在主程序中进展调用。
在主程序中首先要完成初始化工作,然后进入循环阶段,它包括对年月日时分秒的动态扫描子函数以及时钟设置子函数的循环调用,虽然主程序不断调用设置子函数,但只有当按下set键时才真正进入设置函数的内部,由于设置函数并不影响显示函数,因此设置时间日期的同时,可以在数码管上实时观测。
这样一来,主程序实际上主要完成数码管的动态扫描过程,与此同时,内部定时器在不断地工作着,每到1s就进入中断子程序,由CPU。
首先对电子钟的主程序进展分析,然后对各个子函数进展说明,主程序的流程图如下
图3.1主程序流程图
以下对各个模块的子函数进展说明。
3.1初始化子函数
此函数中要对各个变量和定时器进展初始化,具体流程图如下
图3.2初始化子函数流程图
其中,定时器为1s计时,1s过后引起中断,CPU进展相应的处理,本设计采用定时器0,且将其设置为方式1,由于晶振为12MHz,因此设置初值为50000,当其有20次中断时,那么1s时间到。
3.2显示子程序
本函数包括6个小局部,分别为年月日时分秒显示,在此以秒显示为例进展介绍。
由于本设计采取动态扫描的方式,因此年月日时分都采用类似的方法显示,且循环调用显示子函数以实现动态扫描。
3.3设置时间日期子函数
本函数的流程图如下
其中,flag为标志变量,当set键按下时,flag自加1,CPU会根据它的数值去判断设置哪一个变量,当flag的值到达7时,将其清0。
4优缺点分析
本设计的硬件电路简单可行,只需3个锁存器和14个7段显示数码管即可显示年月日时分秒,采用3个独立按键即可实现时间日期的修改;软件局部采用C语言编程,应用模块化编程思想,使得程序主次清楚,构造清晰,并且具有平年、闰年、大月、小月的判断,采用动态扫描显示节省了单片机的I/O端口。
由于时间的限制,本设计还有许多地方有待于完善,例如参加蜂鸣器及相应的软件即可实现闹钟功能,参加相应的算法即可实现星期的显示,参加温度传感器及相应的算法处理即可实现温度的实时显示等等。
5学习心得
本人是单片机的初学者,赵教师理论联系实际的讲课方式很吸引我,每个例子都是以一个实际的工程为背景的,这使得我对单片机的学习不再那么枯燥无味,相反地,我对单片机产生了浓厚的兴趣。
赵教师不仅具有独到而生动的授课方式,而且还给我们传授很多思想,这些思想不仅有助于我们做学问,而且会受益终生。
通过这次课程学习,我掌握了单片机的根本原理、功能及其应用,能够熟练应用汇编和C语言进展编程。
此次电子钟的设计让我稳固了模拟电路、数字电路及单片机的理论知识,也让我体会到设计一个系统的大致流程,感触很深。
学无止境,我会在今后的日子里不断学习,提高自己。
附录1:
硬件完整电路
附录2:
源程序
#include
#defineucharunsignedchar
#defineuintunsignedint
inti,sec,min,h,date,month,year,flag;
sbitdula=P2^0;
sbitwela1=P2^1;
sbitwela2=P2^2;
sbitkey_ch=P3^5;
sbitkey_add=P3^6;
sbitkey_minus=P3^7;
ucharcodetable[]={
0x3f,0x06,0x5b,0x4f,
0x66,0x6d,0x7d,0x07,
0x7f,0x6f};//数码管显示的数字对照表
voidinit();
voiddelay(uint);
voidsecond_display();
voidminute_display();
voidhour_display();
voiddate_display();
voidmonth_display();
voidyear_display();
voidcontrol();
voidtime();
voidmain()
{
init();
while
(1)
{
second_display();
minute_display();
hour_display();
date_display();
month_display();
year_display();
control();
}
}
voidinit()//初始化子函数
{
i=0;
sec=0;
min=0;
h=0;
date=1;
month=1;
year=1;
flag=0;
wela1=0;
wela2=0;
EA=1;
ET0=1;
TMOD=0x01;
TH0=(65536-50000)/256;
TL0=(65536-50000)%256;
TR0=1;
}
voiddelay(uintz)//延时子函数
{
uintx,y;
for(x=z;x>0;x--)
for(y=110;y>0;y--);
}
voidsecond_display()//秒显示
{
intsec_shi,sec_ge;
sec_shi=sec/10;//取十位
sec_ge=sec%10;//取个位
dula=1;//段选
P0=table[sec_ge];
dula=0;
wela2=1;//位选
P0=0xdf;//给锁存器U2送11011111,选中第14个数码管
wela2=0;
delay
(1);
dula=1;//段选
P0=table[sec_shi];
dula=0;
wela2=1;//位选
P0=0xef;//给锁存器U2送11101111,选中第13个数码管
wela2=0;
delay
(1);
}
voidminute_display()//分显示
{
intmin_shi,min_ge;
min_shi=min/10;
min_ge=min%10;
dula=1;//段选
P0=table[min_ge];
dula=0;
wela2=1;//位选
P0=0xf7;//给锁存器U2送11110111,选中第12个数码管
wela2=0;
delay
(1);
dula=1;//段选
P0=table[min_shi];
dula=0;
wela2=1;//位选
P0=0xfb;//给锁存器U2送11111011,选中第11个数码管
wela2=0;
delay
(1);
}
voidhour_display()//时显示
{
inth_shi,h_ge;
h_shi=h/10;
h_ge=h%10;
dula=1;//段选
P0=table[h_ge];
dula=0;
wela2=1;//位选
P0=0xfd;//给锁存器U2送11111101,选中第10个数码管
wela2=0;
delay
(1);
dula=1;//段选
P0=table[h_shi];
dula=0;
wela2=1;//位选
P0=0xfe;//给锁存器U2送11111110,选中第9个数码管
wela2=0;
delay
(1);
}
voiddate_display()//日显示
{
intdate_ge,date_shi;
date_shi=date/10;
date_ge=date%10;
dula=1;//段选
P0=table[date_ge];
dula=0;
wela1=1;//位选
P0=0xef;//给锁存器U1送01111111,选中第8个数码管
wela1=0;
delay
(1);
dula=1;//段选
P0=table[date_shi];
dula=0;
wela1=1;//位选
P0=0xbf;//给锁存器U1送10111111,选中第7个数码管
wela1=0;
delay
(1);
}
voidmonth_display()//月显示
{
intmonth_ge,month_shi;
month_shi=month/10;
month_ge=month%10;
dula=1;//段选
P0=table[month_ge];
dula=0;
wela1=1;//位选
P0=0xdf;//给锁存器U1送11011111,选中第6个数码管
wela1=0;
delay
(1);
dula=1;//段选
P0=table[month_shi];
dula=0;
wela1=1;//位选
P0=0xef;//给锁存器U1送11101111,选中第5个数码管
wela1=0;
delay
(1);
}
voidyear_display()//年显示
{
intyear_qian,year_bai,year_shi,year_ge;
year_qian=year/1000;
year_bai=year%1000/100;
year_shi=year%1000%100/10;
year_ge=year%10;
dula=1;//段选
P0=table[year_ge];
dula=0;
wela1=1;//位选
P0=0xf7;//给锁存器U1送11110111,选中第4个数码管
wela1=0;
delay
(1);
dula=1;//段选
P0=table[year_shi];
dula=0;
wela1=1;//位选
P0=0xfe;//给锁存器U1送11111011,选中第3个数码管
wela1=0;
delay
(1);
dula=1;//段选
P0=table[year_bai];
dula=0;
wela1=1;//位选
P0=0xfd;//给锁存器U1送11111101,选中第2个数码管
wela1=0;
delay
(1);
dula=1;//段选
P0=table[year_shi];
dula=0;
wela1=1;//位选
P0=0xfe;//给锁存器U1送11111110,选中第1个数码管
wela1=0;
delay
(1);
}
voidcontrol()//时间日期调整
{
if(!
key_ch)//set键按下
{
delay(5);//去抖
if(!
key_ch)
{
flag++;
if(flag==7)
flag=0;
}
}
while(!
key_ch);//松手检测
if(flag==1&&key_add==0)//设置秒加1
{
while(!
key_add);//松手检测
sec++;
if(sec==60)
sec=0;
}
if(flag==1&&key_minus==0)//设置秒减1
{
while(!
key_minus);//松手检测
sec--;
if(sec==-1)
sec=59;
}
if(flag==2&&key_add==0)//设置分加1
{
while(!
key_add);//松手检测
min++;
if(min==60)
min=0;
}
if(flag==2&&key_minus==0)//设置分减1
{
while(!
key_minus);//松手检测
min--;
if(min==-1)
min=59;
}
if(flag==3&&key_add==0)//设置时加1
{
while(!
key_add);//松手检测
h++;
if(h==24)
h=0;
}
if(flag==3&&key_minus==0)//设置时减1
{
while(!
key_minus);//松手检测
h--;
if(h==-1)
h=23;
}
if(flag==4&&key_add==0)//设置日加1
{
while(!
key_add);//松手检测
date++;
if((date==29))//平年2月判断
if((year%4!
=0)&&(month==2))
date=1;
if((date==30))//闰年2月判断
if((year%4==0)&&(month==2))
date=1;
if((date==31))//小月判断
if((month==4)||(month==6)||(month==9)||(month==11))
date=1;
if((date==32))//大月判断if((month==1)||(month==3)||(month==5)||(month==7)||(month==8)||(month==10)||(month==12))
date=1;
}
if(flag==5&&key_add==0)//设置月加1
{
while(!
key_add);//松手检测
month++;
if(month==13)
month=1;
}
if(flag==6&&key_add==0)//设置年加1
{
while(!
key_add);//松手检测
year++;
if(year==99)
year=0;
}
if(flag==6&&key_minus==0)//设置年减1
{
while(!
key_minus);//松手检测
year--;
if(year==0)
year=99;
}
}
voidT0_rpt()interrupt1//定时器0中断子函数
{
TH0=(65536-50000)/256;//重新装入初始值
TL0=(65536-50000)%256;
i++;
time();
}
voidtime()
{
if(i==20)
{
i=0;
sec++;
if(sec==60)
{
sec=0;
min++;
if(min==60)
{
min=0;
h++;
if(h==24)
{
h=0;
min=0;
sec=0;
date++;
if((date==29))//平年2月判断
if((year%4!
=0)&&(month==2))
{
date=1;
month++;
if(month==13)
{
month=1;
year++;
}
}
if((date==30))//闰年2月判断
if((year%4==0)&&(month==2))
{
date=1;
month++;
if(month==13)
{
month=1;
year++;
}
}
if((date==31))//小月判断
if((month==4)||(month==6)||(month==9)||(month==11))
{
date=1;
month++;
if(month==13)
{
month=1;
year++;
}
}
if((date==32))//大月判断
if((month==1)||(month==3)||(month==5)||(month==7)||(month==8)||(month==10)||(month==12))
{
date=1;
month++;
if(month==13)
{
month=1;
year++;
}
}
}
}
}
}
}