基于51单片机的电子时钟设计
摘要
本电子时钟以STC89C52单片机作为主控芯片,采用DS12C887时钟芯片,使用1602液晶作为显示输出。该时钟走时精确,具有闹钟设置,以及可同时显示时间、日期等多种功能。本文将详细介绍该电子时钟涉及到的一些基本原理,从硬件和软件两方面进行分析。
【关键词】
STC89C52单片机
DS12C887时钟芯片
1602液晶
蜂鸣器
目
录
一、绪论…………………………………………………………4
1.1 电子时钟功能…………………………………………
4 1.
2设计方案………………………………………………4
二、硬件设计……………………………………………………4
2.
151
2.2 单片机部分设计………………………………4
USB供电电路设计………………………………5
2.3 串行通信电路设计………………………………6
2.
4DS12C887时钟芯片电路的设计…………………6
2.
51602LCD液晶屏显示电路设计………………7
2.6
蜂鸣器电路设计………………………………8
2.7
按键调整电路设计…………………………8
三、软件设计…………………………………………9
3.1 系统程序流程图设计…………………………9
3.
2程序设计……………………………………11
四、心得体会………………………………………………22 参考文献……………………………………………………23
一、绪
论
1.1电子时钟功能
(1)在1602液晶上显示年、月、日、星期、时、分、秒,并且按秒实时更新显示。 (2)具有闹铃设定即到时报警功能,报警响起时按任意键可取消报警。
(3)能够使用实验板上的按键随时调节各个参数,四个有效键分别为功能选择键、数值增大键、数值减小键和闹钟查看键。
(4)每次有键按下时,蜂鸣器都以短“滴”声报警。
(5)利用DS12C887自身掉电可继续走时的特性,该时钟可实现断电时间不停、再次上电时时间仍准确显示在液晶上的功能。
1.2设计方案
DS12C887时钟芯片+1602LCD液晶屏
DS12C887时钟芯片功能丰富、价格适中,能够自动产生世纪、年、月、日、时、分、秒等时间信息,其内部含有世纪寄存器,从而利用硬件电路解决“千年”问题。DS12C887中自带锂电池,外部掉电时,其内部时间信息还能保持10年之久。1602LCD液晶屏可以输出2行,每行显示16个字符。1602LCD液晶屏显示清晰且不会闪烁,由于液晶屏是数字式的,因此和单片机系统的接口简单,操作方便。
以STC89C52为主控芯片,DS12C887为时钟芯片,1602LCD液晶屏作为显示器。程序控制DS12C887时钟芯片实现小时、分、秒和年、月、日的计时,并在1602LCD液晶屏上显示出来。当时间走到程序所设定的时间时,蜂鸣器响起,起到闹钟功能。
二、硬
件
设
计
2.1 51单片机部分设计
单片机部分如图2—1所示:
以STC89C52单片机为核心,选用12MHZ的晶振,由于晶振的频率越高,单片机的运行速度就越快,考虑到单片机的运行速度快会导致对存储器的要求就会变高,因此12MHZ为最佳选择。外接电容的值虽然没有严格的要求,但是外接电容的大小会影响振荡器的频率高低、振荡器的稳定性和起振的快速性 ,因此选用30pF的电容作为起振电容。复位电路为按键高电平复位,当按键按下,RES端为高电平,当高电平持续4us的时间单片机即复位。
2.2 USB供电电路设计
USB供电电路如下图2—2所示:
该电子时钟采用USB端口的方式为单片机供电,LPOW1为电源显示灯,当按键S5按下,电源显示灯LPOW1亮,表示给单片机供+5V电。
2.3 串行通信电路设计
串行通信电路如下图2—3所示:
图中通过MAX232进行RS—232电平与单片机TTL电平之间的转换,从而为单片机和上位机之间通信提供通道。通信电路的目的就是让通信双方的电平匹配,单片机用的是TTL电平,上位机的串口用的是RS—232电平。TTL电平逻辑1的电压范围是+3.3V到+5V,逻辑0的电压范围是0到+3.3V;RS—232电平的逻辑1的电压范围是—15V到—5V,逻辑0的电压范围是+5V到+15V。MAX232可以把输入的+5V电源电压变换成为RS—232输出电平所需的+10V电压。所以采用此芯片接口的串行通信系统只需单一的+5V电源就可以了。对于没有+12V电源的场合,其适应性更强,
2.4 DS12C887时钟芯片电路的设计
时钟芯片电路如下图2—4所示:
DS12C887时钟芯片共需要13条信号线,分别是并行数据地址复用线AD0~AD7,CS,AS,R/W,DS和IRQ。
MOT—总线操作时序选择端。它有两种工作模式,当MOT接
VCC时,选用Motorola模式;当MOT接GND或悬空时,选用Intel模式。 NC—空引脚。
AD0~AD7—复用地址数据总线。在总线周期的前半部分,出现在AD0~AD7上的是地址信息,可用以选通DS12C887内的RAM,总线周期的后半部分出现在AD0~AD7上的是数据信息。 GND,VCC—系统电源接入端。当
VCC输入为+5V时,用户可以访问DS12C887内RAM
的输入小于+4.25V时,禁止用户对内部RAM中的数据,并可对其进行读/写操作;当
VCC进行读/写操作,此时用户不能正确芯片内的时间信息;当
VCC的输入小于+3V时,DS12C887会自动的将电源切换到内部自带的锂电池上,以保证内部的电路能正常工作。
CS—芯片片选端。
AS—地址选通输入端。在进行读/写操作时,AS的上升沿将AD0~AD7上出现的地址信息锁存到DS12C887上,而下一个下降沿清除AD0~AD7上的地址信息,不论CS是否有效,DS12C887都将执行该操作。
R/W—读/写输入端。该引接脚有两种工作模式,当MOT接
VCC时,R/W工作在Motorola模式。此时该引脚的作用是区分读操作还是写操作,R/W高电平时为读操作,R/W为低电平时为写操作;当MOT接GND时,该引脚工作在Intel模式,此时该引脚为写允许输入,此信号的上升沿锁存数据。
DS—数据选择或读输入脚。该引脚有两种工作模式,当MOT接
VCC时,选用Motorola模式,此时,每个总线周期后一部分的DS为高电平,称为数据选通。在读操作中,DS的上升沿使DS12C887将内部数据送往总线AD0~AD7上,以供外部读取。在写操作中,DS的下降沿将使总线AD0~AD7上的数据锁存在DS12C887中。当MOT接GND时,选用Intel模式,此时该引脚是读允许输入引脚。
RESET—芯片复位引脚。
IRQ—中断请求输出。用作处理器的中断申请输入。只要引起中断的状态位置位,并且相应中断使能位也置位,IRQ将一直保持低电平,处理器程序通常读取C存储器来清除IRQ引脚输出,RESET引脚也会清除未处理的中断。没有中断发生时,IRQ为高阻状态,可将多个中断器件接到一条IRQ总线上,只要它们均为漏极开路输出即可。IRQ引脚为漏极开路输出,需要使用一个外接上拉电阻与SQW—方波输出引脚。当供电电压
VCC相连。
VCC大于4.25V时,SQW引脚可输出方波。
2.5 1602LCD液晶屏显示电路设计
1602LCD液晶屏显示电路如下图2—5所示:
1602液晶为5V电压驱动,带背光,可显示2行,每行16个字符,不能显示汉字,内置含128个字符的ASCII字符集字库,只有并行接口,无串行接口。 接口说明如下: (1)液晶1,2端为电源;15,16为背光电源;为防止直接加5V电压烧坏背光灯,在15脚串接一个1K电阻用于限流。
(2)液晶3端为液晶对比度调节端,通过一个10K电位器接地来调节液晶显示对比度。首次使用时,在液晶上电状态下,调节至液晶上面一行显示出黑色小格为止。
(3)液晶4端为向液晶控制器写数据/写命令选择端,接单片机的P3.5口。
(4)液晶5端为读/写选择端,因为我们不从液晶读取任何数据,只向其写入命令和显示数据,因此此端始终选择为写状态,即低电平接地。
(5)液晶6端为使能信号,是操作时必须的信号,接单片机的P3.4口。
2.6 蜂鸣器电路设计
蜂鸣器电路如下图2—6所示:
蜂鸣器电路接在单片机的P2.3引脚上,当该引脚一个低电平,三极管导通,蜂鸣器发出声音作为闹铃。
2.7 按键调整电路设计
按键调整电路如下图2—7所示:
四个独立键盘均采用查询方式,将按键的一端接地,另一端各接一根输入线直接与STC89C52的I/O口相连。当按键闭合时,相当于该I/O口通过按键与地相连,变成低电平,单片机通过检测I/O口的电平状态,即可识别出按下的键。通过四个键实现参数的调节,S1为功能选择键,S2为数值增大键,S3为数值减小键,S4为闹钟查看键。
三、软
件
设
计
3.1 系统程序流程图设计
流程图1:实验主程序流程图
流程图2:定时中断程序流程图
流程图3:调时功能流程图
3.2 程序设计
#include #include void delay(uint z)
//延时函数 {
uint x,y;
for(x=z;x>0;x--)
for(y=110;y>0;y--); } void di( )
//蜂鸣器报警声音 {
beep=0;
delay(100);
beep=1; } void
write_com(uchar com)
//写液晶命令函数
{
rs=0;
lcden=0;
P0=com;
delay(3);
lcden=1;
delay(3);
lcden=0; } void write_data(uchar
data)
//写液晶数据函数 {
rs=1;
lcden=0;
P0=data;
delay(3);
lcden=1;
delay(3);
lcden=0; } void
init( )
//初始化函数 {
uchar
num;
EA=1;
//打开总中断
EX1=1;
//开外部中断1
IT1=1;
//设置负跳变沿触发中断
flag1=0;
//变量初始化
t0_num=0;
s1num=0;
week=1;
dula=0;
//关闭数码管显示
wela=0;
lcden=0;
rd=0; /*以下几行在首次设置DS12C887时使用,以后不必再写入
write_ds(0x0A,0x20);
//打开振荡器
write_ds(0x0B,0x26);
//设置24小时模式,数据二进制格式,开启闹铃中断
set_time( );
//设置上电默认时间
---------------*/
write_com(0x38);
//1602液晶初始化
write_com(0x0c);
write_com(0x06);
write_com(0x01);
write_com(0x80);
for(num=0;num
//写入液晶固定部分显示
{
write_data(table[num]);
delay(1); }
write_com(0x80+0x40);
for(num=0;num
{
write_data(table1[num]);
delay(1);
} } void write_sfm(uchar add,char data)
{ //1602液晶刷新时分秒函数,4为时,7为分,10为秒
char
shi,ge;
shi=data/10;
ge=data%10;
write_com(0x80+0x40+add);
write_data(0x30+shi);
write_data(0x30+ge); } void
write_nyr(uchar add,char
data)
{
//1602液晶刷新年月日函数,3为年,6为月,9为日
char
shi,ge;
shi=data/10;
ge=data%10;
write_com(0x80+add);
write_data(0x30+shi);
write_data(0x30+ge); void
write_week(char we)
//写液晶星期显示函数 {
write_com(0x80+12);
switch(we)
{
case 1:
write_data(\'M\');delay(5);
write_data(\'O\');delay(5);
write_data(\'N\');
break;
case 2:
write_data(\'T\');delay(5);
write_data(\'U\');delay(5);
write_data(\'E\');
break;
case 3:
write_data(\'W\');delay(5);
write_data(\'E\');delay(5);
write_data(\'D\');
break;
case 4:
write_data(\'T\');delay(5);
write_data(\'H\');delay(5);
write_data(\'U\');
break;
case 5:
write_data(\'F\');delay(5);
write_data(\'R\');delay(5);
write_data(\'I\');
break;
case 6:
write_data(\'S\');delay(5);
write_data(\'A\');delay(5);
write_data(\'T\');
break;
case 7:
write_data(\'S\');delay(5);
write_data(\'U\');delay(5);
write_data(\'N\');
break;
} } void keyscan( ) { if(flag_ri==1) {
//这里用来取消闹钟报警,按任意键取消报警
if((s1==0)||(s2==0)||(s3==0)||(s4==0))
{
delay(5);
if((s1==0)||(s2==0)||(s3==0)||(s4==0))
{
while(!(s1&&s2&&s3&s&s4));di( );
flag_ri=0;
//清除报警标志
}
}
}
if(s1==0)
//检测s1
{
delay(5);
if(s1==0)
{
s1num++;
//记录按下次数
if(flag1==1)
if(s1num==4)
s1num=1;
flag=1;
while(!s1);di( );
switch(s1num)
{
//光标闪烁点定位
case 1: write_com(0x80+0x40+10);
write_com(0x0f);
break;
case 2: write_com(0x80+0x40+7);
break;
case 3: write_com(0x80+0x40+4);
break;
case 4: write_com(0x80+12);
break;
case 5: write_com(0x80+9);
break;
case 6: write_com(0x80+6);
break;
case 7: write_com(0x80+3);
break;
case 8: s1num=0;
write_com(0x0c);
flag=0;
write_ds(0,miao);
write_de(2,fen);
write_ds(4,shi);
write_ds(6,week);
write_ds(7,day);
write_ds(8,month);
write_ds(9,year);
break;
}
}
}
if(s1num!=0)
//只有当s1按下后,才检测s2和s3
{
if(s2==0)
{
delay(1);
if(s2==0)
while(!s2);di( );
switch(s1num)
{
//根据功能键次数调节相应数值
case 1: miao++;
if(miao==60)
miao=0;
write_sfm(10,miao);
write_com(0x80+0x40+10);
break;
case 2: fen++;
if(fen==60)
fen=0;
write_sfm(7,fen);
write_com(0x80+0x40+7);
break;
case 3: shi++;
case 4:
case 5:
case 6:
case 7:
}
}
}
if(s3==0)
{
delay(1);
if(shi==24)
shi=0;
write_sfm(4,shi);
write_com(0x80+0x40+4);
break; week++;
if(week==8)
week=1;
write_week(week);
write_com(0x80+12);
break; day++;
if(day==32)
day=1;
write_nyr(9,day);
write_com(0x80+9);
break; month++;
if(month==13)
month=1;
write_nyr(6,month);
write_com(0x80+6);
break; year++;
if(year==100)
year=0;
write_nyr(3,year);
write_com(0x80+3);
break;
if(s3==0)
{
while(!s3);di( );
switch(s1num)
{
//根据功能键次数调节相应数值
case 1: miao--;
if(miao==-1)
miao=59;
write_sfm(10,miao);
write_com(0x80+0x40+10);
break;
case 2: fen--;
if(fen==-1)
fen=59;
write_sfm(7,fen);
write_com(0x80+0x40+7);
break;
case 3: shi--;
if(shi==-1)
shi=23;
write_sfm(4,shi);
write_com(0x80+0x40+4);
break;
case 4: week--;
if(week==0)
week=7;
write_week(week);
write_com(0x80+12);
break;
case 5: day--;
if(day==0)
day=31;
write_nyr(9,day);
write_com(0x80+9);
break;
case 6: month--;
if(month==0)
month=12;
write_nyr(6,month);
write_com(0x80+6);
break;
case 7: year--;
if(year==-1)
year=99;
write_nyr(3,year);
write_com(0x80+3);
break;
}
}
}
}
if(s4==0)
//检测s4
{
delay(5);
if(s4==0)
{
flag1=~flag1;
while(!s4);di( );
if(flag1==0)
{
//退出闹钟设置时保存数值
flag=0;
write_com(0x80+0x40);
write_data(\' \');
write_data(\' \');
write_com(0x0c);
write_ds(1,miao);
write_ds(3,fen);
write_ds(5,shi);
}
else
{
//进入闹钟设置
read_alarm( );
//读取原始数据
miao=amiao;
//重新赋值用以按键调节
fen=afen;
shi=ashi;
write_com(0x80+0x40);
write_data(\'R\');
//显示标志
write_data(\'i\');
write_com(0x80+0x40+3);
write_sfm(4,ashi);
//送液晶显示闹钟时间
write_sfm(7,afen);
write_sfm(10,amiao);
}
}
} } void write_ds(uchar add,uchar
data) {
//写12C887函数
dscs=0;
dsas=1;
dsds=1;
dsrw=1;
P0=add;
//先写地址
dsas=0;
dsrw=0;
P0=data;
//再写数据
dsrw=1;
dsas=1;
dscs=1; } uchar
read_ds(uchar add) {
//读12C887函数
uchar
ds_data;
dsas=1;
dsds=1;
dsrw=1;
dscs=0;
P0=add;
//先写地址
dsas=0;
dsds=0;
P0=0xff ;
ds_data=P0;
//再读数据
dsds=1;
dsas=1;
dscs=1;
return ds_data; } /*---首次操作12C887时给予寄存器初始化----- void set_time( ) {
//首次上电初始化时间函数
write_ds(0,0);
write_ds(1,0);
write_ds(2,0);
write_ds(3,0);
write_ds(4,0);
write_ds(5,0);
write_ds(6,0);
write_ds(7,0);
write_ds(8,0);
write_ds(9,0); } ----------------------*/
void
read-alarm( ) {
//读取12C887闹钟值
amiao=read_ds(1);
afen=read_ds(3);
ashi=read_ds(5); } void main( )
//主函数 {
init( );
//调用初始化函数
while(1)
{
keyscan( );
//按键扫描
if(flag_ri==1)
//当闹钟中断时进入这里
{
di( );
delay(100);
di( );
delay(500);
}
if(flag==0&&flag1==0)
//正常工作时进入这里
{
keyscan( );
//按键扫描
year=read_ds(9);
//读取12C887数据
month=read_ds(8);
day=read_ds(7);
week=read_ds(6);
shi=read_ds(4);
fen=read_ds(2);
miao=read_ds(0);
write_sfm(10,miao);
//送液晶显示
write_sfm(7,fen);
write_sfm(4,shi);
write_week(week);
write_nyr(3,year);
write_nyr(6,month);
write_nyr(9,day);
}
} } void
exter( ) interrupt 2
//外部中断1服务程序 {
uchar
c;
//进入中断表示闹钟时间到
flag_ri=1;
//设置标志位,用于大程序中报警提示
c=read_ds(0x0c);
//读取12C887的C寄存器表示响应了中断 }
_______________________________________________________________________________ 以下为define.h源代码:
_______________________________________________________________________________ #define uchar unsigned char #define uint unsigned int sbit dula=P2^6; sbit wela=P2^7; sbit rs=P3^5; sbit lcden=P3^4; sbit s1=P3^0;
//功能键 sbit s2=P3^1;
//增大键 sbit s3=P3^2;
//减小键 sbit s4=P3^6;
//闹钟查看键 sbit rd=P3^7; sbit beep=P2^3;
//蜂鸣器 sbit dscs=P1^4; sbit dsas=P1^5; sbit dsrw=P1^6; sbit dsds=P1^7; sbit dsirq=P3^3; bit flag1,flag_ri;
//定义两个位变量
uchar count,s1num,flag,t0_num;
//其他变量定义 char miao,shi,fen,year,month,day,week,amiao,afen,ashi; uchar code
table[]=\"201-
\";
//液晶固定显示内容 uchar code
table1[]=\"
:
:
\";
void write_ds(uchar,uchar);
//函数申明 void set_alarm(uchar,uchar,uchar); void read_alarm( ); uchar read_ds(uchar); void set_time( );
四、心
得
体
会
在本次电子时钟设计中对单片机的内部结构有了一定的了解,熟悉了各个引脚的功能,同时熟知了1602LCD液晶、DS12C887时钟芯片的使用,以及各种电路的功能。
通过此次课程设计,无论是从软件方面还是硬件方面,都进一步学习和巩固了程序的总体设计和单片机的应用。在软件方面,进一步熟悉了各条指令的功能及用法,定时、中断的用法,更深一步学习了用C语言编写实现电子时钟的功能。在硬件方面,进一步熟悉并使用了keil软件,在keil中编程,调试,运行;对电路的一些基本结构和设计有了更深一步的了解。在整个设计过程中,虽然出现了很多问题,有时确实叫人很心烦,但在发现问题后努力去解决,并获得成功,这时会感到无比的快乐和具有成就感。只有自己动手去做,去应用,才能将学到的知识变成自己的。
程序不要光看不写,一定要自己写一次。最开始的时候,可能自己啥都不懂,这时可以抄人家的程序过来。但在抄的时候一定要看看每一句是干什么用的,来达到什么目的,运行后有什么后果,看明白了之后,就要自己写一次。此时会发现,原来看明白别人的程序很容易,但到自己写的时候却一句也写不出来,这就是差距。
单片机提高重在实践,想要学好单片机,软件编程必不可少。但是熟悉硬件对于学好单片机也是非常重要的。如何学习好硬件,动手实践是必不可少的。我们可以通过自己动手做一个自己的电子制作,通过完成它,以提高对一些芯片的了解和熟练运用。这样我们就可以多了解一些芯片的结构。
我学习的目标是希望在若干年以后能够独立设计一个复杂的系统,包括硬件电路和软件部分。总之,通过这次电子技术设计我学到了许多,似乎离自己的目标又近了一步。
参
考
文
献
[1]郭天祥 . 51单片机C语言教程 .电子工业出版社 . 2009 [2]谭浩强 . C程序设计 . 清华大学出版社 .
1991 [3]孙育才 王荣兴
孙华芳 . ATMEL新型AT89S52系列单片机及其应用 . 清华大学出版社 .
2006 [4]谢维成 .
单片机原理及应用与51程序设计 .
清华大学出版社 .
2006
[5]鲍宏亚 . MCS—51系列单片机应用系统设计及实用技术 .
中国宇航出版社 .
2005 [6]赵文博
刘文涛 .
单片机语言C51程序设计 . 人民邮电出版社 .
2006 [7]求是科技 .
8051系列单片机C程序设计 .
人民邮电出版社 .
2006