目录

​1、概述​

​2、国内外研究现状​

​3、硬件设计方案​

​ 4、系统软件设计​

​(1)主程序​

​ (2)子程序​

​(3)代码​

​5、心得​


1、概述

       单片机自20世纪70年代问世以来,以其极高的性能价格比,受到人们的重视和关注,应用很广、发展很快。单片机体积小、重量轻、抗干扰能力强、环境要求不高、价格低廉、可靠性高、灵活性好、开发较为容易。而AT89S51系列单片机是各单片机中最为典型和最有代表性的一种。通过对它的学习、应用,这次设计是基于AT89C51单片机的具有准点报时、调时功能的简单电子时钟系统的设计。以AT89C51为核心控制器,系统分为时钟模块、显示模块、按键模块。系统以单片机内部定时器作为时钟模块的主要控制模块,通过频率计数实现计时功能,采用了液晶屏来显示时间,采用独立按键作为调整按钮,采用蜂鸣器来构成整点报时功能。通过Keil软件C语言程序的编写、编译、调试以及硬件单片机的连接,实现了时间显示(24小时制)、时间调试,并运行了该电路的程序,得出了符合实验设计要求的结果。

2、国内外研究现状

       从单片机电子时钟近年的发展趋势来看,正朝着多层次用户、多品种、多规格、高精度、小体积、低能耗等方面发展。在这种趋势下,时钟的数字化,智能化已经成为现代时钟生产研究的主导设计方向。带有时钟功能的电子产品和电子设备进年来广泛地出现在国内外市场中,例如奥运会倒计时显示屏、铁路安全显示屏、生产线看板、体育比赛记时屏、大型室外高亮度时钟等,这类产品覆盖银行、医院、地铁车站、体育运动、电视台、监控系统、高大建筑物等行业。作为一种人机接口方式,语音比显示屏,鼠标键盘等设备更易于使用。而在设计里加上语音提示、音乐或者其他语音功能,还使得设计显得既人性化又有趣,不但能提高开发者的兴趣和积极性,同时也能让设计作品与众不同。

3、硬件设计方案

       该设计是利用AT89S51单片机内部的定时/计数器、中断系统、以及开关和LCD1602液晶显示器等部件,设计的一个单片机电子时钟。设计的电子时钟通过LCD 1602液晶显示,并能通过开关实现设置时间和星期控制,用定时/计数器T0,工作于定时,采用方式1,对12MHZ的系统时钟进行定时计数,初值设为50000。形成定时时间为50ms。计时周期为24小时,显示满刻度为23时59分55秒。因此,一个基本的数字钟电路主要由显示器“时”,“分”,“秒”、“星期”和单片机组成。LCD 1602液晶显示的引脚选接到单片机的P0口,位选接到单片机的P3口。LCD1602 液晶显示按程序设计显示星期和时间,对基准时间计数形成秒,秒计60次形成分,分计60次形成小时,小时计24次则计满一天,并改变日期,并通过LCD把它们的内容在相应位置显示出来即可。显示电路将“星期”“时”、“分”、“秒”通过LCD1602液晶显示出来。

系统整体硬件电路:

单片机电子钟_算法

 4、系统软件设计

(1)主程序

       数字时钟设计原理主要利用AT89C51单片机,由单片机的P0口控制数码管的位显示,P2口控制数码管的段显示,P3口与按键相接用于时间的校正以及闹钟的设定。设计的主要方面有计时原理与整点报时、调时方式、按键的消抖以及延时函数。主程序框图如下:

单片机电子钟_算法_02

 (2)子程序

        延时函数:延时的作用就是让数据正确处理。​​CPU​​程序按指定时间间隔取值,因此有必要采用延时函数。通过for循环定义函数,在主程序中对延时函数进行调用达到延时目的。

       按键的消抖:通常的按键所用开关为机械弹性开关,断开、闭合时由于机械触点的弹性作用,不会马上稳定地接通或者断开。因而在闭合及断开的瞬间均伴随有一连串的抖动,为了判断按键是否误操作,因此我们要进行按键消抖。我们在编译过程中调用延时函数,若在延时结束后开关状态仍然保持不变,则说明开关状态改变非误操作产生。

       计时原理:首先设定单片机内部的一个定时器\计数器工作于定时方式,对机器周期计数形成基准时间,然后用另一个定时器\计数器或软件计数的方法通过循环叠加对基准时间计数形成秒,秒计60次形成分,分计60次形成小时,小时计24次则计满一天,并改变日期,并通过LCD把它们的内容在相应位置显示出来即可。其中,时间量的循环叠加是在T0溢出后进入的中断函数中通过条件语句判断实现实时变化的功能。选择方式1,重复定时,定时时间设为50ms,定时时间到则中断,在中断服务程序中用一个计数器对50ms计数,计20次则对秒单元加1,秒单元加到60则对分单元加1,同时秒单元清0;分单元加到60则对时单元加1,同时分单元清0;时单元加到24则对时单元清0,标志一天时间计满。

(3)代码

#include <reg51.h>    //包含特殊功能寄存器的地址声明
#include <intrins.h> //包含_nop_()空函数指令的头文件
#include <LCD1602.h> //包含液晶显示屏1602的头文件
#define uchar unsigned char
#define uint unsigned int
uchar key=0;
uchar timer; //定义中断次数计数变量
uint second; //秒计数变量
uint minute; //分钟计数变量
uint hour; //小时计数变量
uchar G=0; //G是模式,更新时间2种模式加上正常模式 S_flag闪烁标志
uchar time[]="TIME 23:59:55"; //LCD显示器第二行显示内容
uchar k=1;
uchar s[2],f[2],m[2];
sbit K1=P3^0; //设置键
sbit K2=P3^1; //增加键
sbit K3=P3^2; //减少键
sbit sound1=P1^2;
sbit sound2=P1^3;
sbit sound3=P1^4;
sbit sound4=P1^5;
sbit sound5=P1^6;
sbit sound6=P1^7;

void week(uchar u);
void key_prc();
void delay1(unsigned n);

void main()
{
LCD_init(); //LCD初始化
init(); //时钟初始化
TMOD=0x01; //设置定时器T0为方式1定时
EA=1; //总中断开
ET0=1; //允许T0中断
TH0=0x3c; //置T0高8位初值,50ms的实现
TL0=0xb0; //置T0低8位初值
TR0=1; //启动计数
timer=0; //中断次数、秒、分、时单元清0
second=55;
minute=59;
hour=23;
while(1)
{
key_prc();
s[0] = toString(hour)[0]; //将小时的十位uint类型转换为uchar类型
s[1] = toString(hour)[1]; //将小时的个位数据类型转换
f[0] = toString(minute)[0]; //将分钟的十位数据类型转换
f[1] = toString(minute)[1]; //将分钟的个位数据类型转换
m[0] = toString(second)[0]; //将秒的十位数据类型转换
m[1] = toString(second)[1]; //将秒的个位数据类型转换
week(k); //调用周几显示函数,显示星期
write_string(0xc7,":");
write_string(0xca,":");
clock_write(m,f,s); //调用此函数,对分秒时不断计数
}
}

void TO_interserve(void) interrupt 1 using 1 //定时器T0中断函数
{
timer ++;
if(timer==20)
{
timer=0; //中断次数清零
second++; //秒计数加1
}
if(second==60) //若计满60秒
{
second=0; //秒计数又从开始
minute++; //分钟计数变量加1
}
if(minute==60) //若计满60分钟
{
minute=0; //分钟计数又从零开始
hour++; //小时计数加1
}
if(hour==24) //若小时计数计满24小时
{
k++;
hour=0; //将小时计数清零,又从零开始
if(k==8) k=0;
}
TH0=0x3c; //定时器重新赋值
TL0=0xb0;
if(minute==0&&second==0) sound1=0;
if(minute==0&&second==1) {sound1=1;sound2=0;}
if(minute==0&&second==2) {sound2=1;sound3=0;}
if(minute==0&&second==3) {sound3=1;sound1=0;}
if(minute==0&&second==4) {sound1=0;sound3=1;}
if(minute==0&&second==4&&timer==10) {sound1=1;}
if(minute==0&&second==5) {sound1=1;sound2=0;}
if(minute==0&&second==6) {sound2=1;sound3=0;}
if(minute==0&&second==7) {sound3=1;sound1=0;}
if(minute==0&&second==8) {sound1=1;sound3=0;}
if(minute==0&&second==9) {sound3=1;sound4=0;}
if(minute==0&&second==10){sound4=1;sound5=0;}
if(minute==0&&second==11)sound5=1;

}
void week(uchar u) //周几显示函数
{
switch(u)
{
case 1:write_string(0x80,"Monday "); break;
case 2:write_string(0x80,"Tuesday "); break; //从第一行显示星期数
case 3:write_string(0x80,"Wednesday"); break;
case 4:write_string(0x80,"Thursday "); break;
case 5:write_string(0x80,"Friday "); break;
case 6:write_string(0x80,"Saturday "); break;
case 7:write_string(0x80,"Sunday "); break;
}
}

void key_prc() //按键
{

if(K1==0) //如果K1被按下
{
delay1(10); //延时去抖
if(K1==0) //按K1进行模式切换
{ G++;
if(G==4) G=0;
}
while(!K1);//等待按键释放
}

if(G!=0)
{
switch(G)
{
case 1: //模式1——调时
{
if(K2==0)
{
delay1(10); //延时去抖
if(K2==0) //加键按下
{
if(hour<23) hour++;
else hour=0;
}
while(!K2); //等待按键释放
}

if(K3==0)
{
delay1(10);
if(K3==0)
{
if(hour> 0) hour--;
else hour=23;
}
while(!K3);
}
} break;

case 2: //模式2——调分
{
if(K2==0)
{
delay1(10);
if(K2==0)
{
if(minute<59) minute++;
else minute=0;
}
while(!K2);
}

if(K3==0)
{
delay1(10);
if(K3==0)
{
if(minute>0) minute--;
else minute=59;
}
while(!K3);
}
} break;

case 3: //模式3——调星期
{
if(K2==0)
{
delay1(10);
if(K2==0)
{
if(k<7) k++;
else k=1;
}
while(!K2);
}

if(K3==0)
{
delay1(10);
if(K3==0)
{
if(k>1) k--;
else k=7;
}
while(!K3);
}
} break;
}
}
}
void delay1(unsigned int n) //0.2毫秒 延时函数需要计算,采用的是网上提供的函数
{
int x,y;
for(x=0;x<n;x++)
for(y=0;y<24;y++);
}

5、心得

       此次单片机课程设计虽然最后的运行结果看上去很简单,就是一个能调时间和星期以及整点报时而已,但是它的程序并不是很简单,需要运用到单片机的四个模块,所以要对这四个模块有一定的了解。哪怕是简单的硬件连接也并不是一帆风顺,一开始打算选择晶体管显示器来作为我们的显示模块,但是后来经过小组讨论以后,觉得使用外接的液晶显示器效果更好,而液晶显示器的连接使用和选择也花了不少心思,而且手头的单片机经常出现各种小问题需要排查和调试。