基于AT89C51单片机的定时闹钟设计.docx
《基于AT89C51单片机的定时闹钟设计.docx》由会员分享,可在线阅读,更多相关《基于AT89C51单片机的定时闹钟设计.docx(43页珍藏版)》请在冰点文库上搜索。
![基于AT89C51单片机的定时闹钟设计.docx](https://file1.bingdoc.com/fileroot1/2023-5/4/e3a577f6-c325-407f-ad66-f1d0882c48df/e3a577f6-c325-407f-ad66-f1d0882c48df1.gif)
基于AT89C51单片机的定时闹钟设计
学号:
班级:
姓名:
基于89C51单片机的电子闹钟设计
一、设计要求
(1)设计并实现一个具有计时功能的电子时钟系统,电子表的时间精确到秒,并可以显示年、月、日、时、分、秒。
(2)利用液晶显示器显示定时器的日期和时间。
(3)使用键盘进行设置时间和设置闹钟。
(4)定时时间到通过蜂鸣器报警和发光二极管闪烁通知,并持续60s。
二、硬件设计
(1)系统设计框图
(2)选择硬件设备
单片机:
选择AT89C51;
液晶显示器:
具有16字符显示功能的1601
键盘:
选择4行*4列的矩阵键盘
LED:
选择红色的发光二极管
E2PROM:
X2545
图2时钟信号发生单元
基本元件:
蜂鸣器,电容,晶体振荡器,电阻,开关
电源:
使用+5v直流稳压源
基本模块的构成
①时钟信号发生单元如右图2
利用晶振和电容以及单片机内部
电路,构成晶体并联振荡器,产
生12MHz的时钟频率
②复位电路如右图3
利用一个简单的电容和按键实现
图3复位单元电路
实现对系统的复位功能
由此基本模块可以实现最小的单片机系统
(3)电子时钟硬件原理图
图4硬件电路原理电路
(4)主要器件的原理
①液晶显示原理
液晶显示器种类繁多,按输出样式分为,图案式,数码式,点阵式。
本设计方案利用的是点阵式液晶显示器,而液晶驱动方式又和数码管驱动截然不同,虽然比数码管需要更小的工作电压,但是其结构所需要的扫描方式较数码管来说,是比较复杂的,而且输入输出数据速度慢,市场上是常用点阵式液晶驱动器的,常用的有1601、1602……,“16”代表显示字符共有几列,“01”、“02”代表输出字符共有几行。
下面是驱动1601的驱动方法。
驱动1601的一个很重要的方面就是液晶显示器的初始化,主要是利用控制、数据复用总线来输入指令,进行初始化。
基本操作
读状态:
输入:
RS=L,RW=H,E=H输出:
D0~D7=状态字
写指令:
输入:
RS=L,RW=L,D0~D7=指令码,E=高脉冲输出:
无
读数据:
输入:
RS=H,RW=H,E=H输出:
D0~D7=数据
写数据:
输入:
RS=H,RW=L,D0~D7=数据,E=高脉冲输出:
无
状态字说明
Sta7
(读写操作能)
D7
Sta6
D6
Sta5
D5
Sta4
D4
Sta3
D3
Sta2
D2
Sta1
D0
Sta0
D0
1:
禁止
0:
允许
当前数据指针的数值
利用Sta7可以用来检测当前驱动器是否处于忙状态,这样的话可以避免在忙状态的情况下,进行写数据会产生漏写的错误;或者可以利用延时,具体时间可以参照datasheet,一般都为几毫秒左右。
初始化设置
初始化时首先要进行写命令操作,然后按照datasheet的说明进行写指令
显示模式设置
指令
指令使能
指令码
描述
RS
RW
DB7
DB6
DB5
DB4
DB3
DB2
DB1
DB0
显示模式
0
0
0
0
1
1
1
0
0
0
5*7点阵显示,8位数据接口
1)常用显示指令
指令
指令使能
指令码
描述
RS
RW
DB7
DB6
DB5
DB4
DB3
DB2
DB1
DB0
清屏
0
0
0
0
0
0
0
0
0
1
数据指针清零
所有显示清零
回车
0
0
0
0
0
0
0
0
1
0
数据指针清零
显示控制
0
0
0
0
0
0
1
D
C
B
D=1:
开;D=0:
关
C=1:
光标显示
C=1:
光标不显示
B=1:
光标闪烁
B=0:
光标不闪烁
2)屏幕移动指令
指令
指令使能
指令码
描述
RS
RW
DB7
DB6
DB5
DB4
DB3
DB2
DB1
DB0
入口
方式
设置
0
0
0
0
0
0
0
1
I/D
S
I/D=1:
cincrease;
I/D=0:
cdecrease;
S=1:
整屏移动;
S=0:
指针
或
显示移动
0
0
0
0
0
1
S/C
R/L
*
*
S/C=0,R/L=0:
指针左移显示不移
S/C=0,R/L=1:
指针右移显示不移
S/C=1,R/L=0:
显示和指针左移
S/C=1,R/L=1:
显示和指针右移
3)设置内部RAM地址
指令
指令使能
指令码
描述
RS
RW
DB7
DB6
DB5
DB4
DB3
DB2
DB1
DB0
设置CG
ADDR
0
0
0
1
A
A
A
A
A
A
对字符的点阵
进行编程
设置DD
ADDR
0
0
1
A
A
A
A
A
A
A
设置显示数据
存储器地址
②键盘扫描原理
键盘分为编码键盘,和非编码键盘。
编码键盘是靠硬件电路对每个键位进行编码,当有键按下时,输出固定的数码,并用来判断键位。
常见的编码键盘如PC键盘;非编码键盘是指,键盘不是靠固定的编码来实现对键位的识别的,而是靠一定的算法来对键位进行扫描,矩阵键盘就是常见的一种非编码键盘。
编码键盘一般需要较多的硬件电路,所以成本较非编码键盘较高,故一般的单片机系统都采用非编码键盘,目的是充分利用单片机的丰富的软件资源,弥补硬件的不足。
机械按键在按动的过程中,往往会产程抖动,这又是对一个控制系统的致命伤,因此必须采取措施消除抖动。
目前,消除抖动的方法主要有两种:
利用硬件RS锁存器进行消除抖动,将复杂跳变的信号变成稳定的信号;利用软件算法实现对抖动的排除,灵活性大,较常用,一般的,抖动的持续时间为数毫秒,所以为了简单起见,本设计方案利用延时来消除抖动。
对于键盘的扫描常用的有逐行扫描法和线反转法。
逐行或列扫描法的思路是,利用列线,做输入,行线做输出。
首先使四根列线的某一根置零,当在此列的某一行有键按下时,该行线即为低电平,其余均为高电平,这样这个键就被编码;然后再使另一列线置零,检测下一列是否有键按下;不断这样循环,就可以对整个键盘进行逐列扫描了。
线翻转法的思路是,把列线当做线,所以,对其中某条线置低电平,其余为高电平,然后去读行线状态,那么线的状态和行的状态相连就是当前按下的键的键值,例如,线的状态为1011,读得行的状态为1101,则当前按键的键值为10111101。
综观这两种方法,逐行扫描法,需要多次循环才能对整个键盘进行全扫描,速度慢,而且还有可能导致有些键检测不出,而线翻转法只需两句程序就可以扫描到键值,因此本设计采用先翻转法对键盘进行扫描。
键名
7
8
9
编码值
0x0EE
0x0DE
0x0BE
0x07E
索引值
7
8
9
15
键名
4
5
6
编码值
0x0ED
0x0DD
0x0BD
0x07D
索引值
4
5
6
14
键名
1
2
3
编码值
0xEb
0xDB
0xBB
0x07B
索引值
1
2
3
13
键名
0
编码值
0x0E7
0xD7
0x0B7
0x077
索引值
11
0
12
10
③E2PROM原理
三、软件仿真设计
(1)不同软件模块间的同步调度的总体设计
本系统的软件部分的功能模主要有初始化模块、定时器模块、液晶显示模块、键盘扫描模块、键盘识别模块、模式转换模块、常用调用子程序模块。
为了充分利用单片机的资源,对不同模块之间需要合理调度,对于只有一个任务的系统来说,实现该系统的程序往往是顺序执行的程序,各种模块之间也只是调用与被调用的关系,对于更复杂的系统,如菜单系统,也只不过是一个分支结构,菜单反复在死循环里检查输入状态,一旦选中某个选项,马上跳入分支当中顺序执行,执行完毕后立即返回到菜单检测模块。
对于这样的系统,往往不用考虑模块之间的并行关系,只需考虑顺序执行即可。
但是对于本次实现的系统中定时器模块属于中断模块,独立于任何模块;液晶显示模块,键盘扫描模块,键盘识别模块,模式转换模块均为同步的模块(从宏观上讲,各个模块是同步执行的,从微观上讲,是各个模块之间通过一定的标志变量顺序执行的),而初始化模块,子程序调用模块为调用与被调用的模块,无需考虑它与其他模块之间的同步关系。
现在介绍本次设计的各相互同步模块之间的调度关系。
定时器模块为本设计最为独立模块主要又定时器0和定时器1构成:
定时器0工作在方式1,为50ms的定时器,中断服务程序中软计数为键盘扫描模块、计时器运算模块的执行标志变量置位。
键盘扫描的执行可以是随机顺序执行或者时间片轮转方式执行,但是通过分析可以明确,随机顺序方法容易出现扫描失误。
所以采用中断方式的时间片轮转法调度键盘扫描模块,实践证明30ms的周期扫描键盘调度较为合适,但是考虑到计时器的计数触发基本单元是依靠定时器0得到1s的脉冲,而定时器0为50ms的定时间隔,所以利用定时器0产生的每50ms的中断信号,进行为键盘扫描模块的执行标志变量置位。
键盘扫描执行完毕后,进行键位判断,对于模式键的识别时,要调用模式变换模块。
软件框图如下:
(2)主要的功能模块设计说明
Ⅰ.应用的主要C语言关键字
A.位域的使用
设计中会用到很多标志变量,状态控制变量,如果单单利用CHAR型变量,会有0~255的个状态,会浪费很多存储器容量;所以这里使用位域,可以对一字节的某一位或者几位来操作,可以节省很多空间,利用一字节就可以设计出很多标志变量;然而对于状态变量,对状态变量会进行加减操作,往往还要判断是否溢出,而使用几位的位域变量来说,他的状态只在那几位所允许的状态,如3位,状态为0~7。
位域的声明方法:
Struct位域结构名
{
类型说明符位域名:
位域长度;
……;
……;
}
B.将数组声明成code类型的数据
(Const)数据类型名code变量名[]={…,…,…,…,…,…}
这样做可以节省较少的RAM空间,利用较大的ROM空间。
Ⅱ.初始化模块
A.定时器模块初始化:
定时器0和定时器1的初始化:
定时器0工作在方式1,初值为#3CB0,定时时间为50ms;定时器1工作方式1,初值为#FB1E,定时时间为1.25ms,产生400hz的脉冲。
B.液晶显示器驱动模块
显示模式初始化,数据总线初始化,光标显示初始化,当输入数据时,光标和显示右移。
C.标志变量初始化,系统变量初始化,状态变量初始化。
Ⅲ.定时器模块
键盘执行标志变量置位,计时器执行计数标志变量置位,闹钟检测模块执行标志变量置位
Ⅳ.液晶显示模块
反复对LcdStr字符数组进行扫描显示
Ⅴ.键盘扫描模块
利用线反转法进行扫描,调用keyscan()子程序。
Ⅵ.计时器模块
主要包括闹表检测,闹表输出系统,冒号闪烁,闰年检测和月份检测,时间运算及时间显示。
另外还有加减校时系统,当本系统工作模式0时,可进行加减校时,并可以进行转换,进而加减校分。
Ⅶ.键位识别模块
当检测到有键按下时,读取返回键的编码,利用Switch语句进行分支判断,利用各个键的索引进行判断,分别调用不同的模块。
Ⅷ.模式变换模块
模式共有四个模式分别为模式0、模式1、模式2、模式3;
模式0:
计时器模块,显示万年历功能,精确到秒,可以显示年月日,并可以进行加减校时,并可以像真正的电子时钟一样,“:
”一闪一闪的显示,以体现秒的计时。
模式1:
闹表预置数定义,可以逐个置数,若置数值为非法值则显示错误信息,按确定键返回,继续输入当前的闹表预置数;在预置数时,可以按“+/-”来设置当前设置的闹钟索引。
模式2:
可以直接定义当前的时间,只能定义时分秒,同样具有非法值检测,可以重新输入,输入正确后可以继续设置新值,或者转换到其他模式。
模式3:
显示已设置闹表的时刻值,同样利用“+/-”可以进行显示当前闹表时刻值的索引,进行加减变化
对于这些模块中的可以输入的模块,可以利用“=”进行将已输入的值清空。
常用的调用子程序模块
常用的子程序有:
延时子程序,键盘扫描子程序,清屏子程序,字符串复制子程序,显示子程序。
(3)程序流程图
四、系统总体功能实现
具体功能:
在开机时刻,欢迎界面滚动显示“welcome”,按“on/c”进入正常模式。
数字输入功能:
在模式1,模式2可以进行手动输入时刻值。
模式切换模式:
按“除”号进行切换模式加减校时、校分的功能,并可以正常溢出返回初值。
利用“*”进行时分切换校准。
在闹表设置和显示的模式中,可以利用“+”,“-”进行选择设置闹表预置数的索引。
键名
7
8
9
功能
数字7
数字8
数字9
切换工作
模式
键名
4
5
6
功能
数字7
数字5
数字6
确定/分时切换
键名
1
2
3
功能
数字1
数字2
数字3
时刻减一/闹表
索引减一
键名
0
功能
启动电子时钟
数字0
模式1/2清屏
时刻加一/闹表
索引加一
键盘各键位的功能
五、程序清单
/*电子时钟设计实验(fosc=6MHZ)*/
#include"reg51.h"
//externchar*strcpy(char*s1,char*s2);
sbitP3_0=P3^0;
sbitRS=P3^3;/*寄存器选择信号*/
sbitRW=P3^4;/*读/写控制信号*/
sbitE=P3^5;/*使能信号*/
sbitCON=P3^6;
sbitCLK=P3^7;
sbitAcc_b=ACC^7;/*定义累加器的第七位*/
sbitP0_0=P0^0;
sbitsign=PSW^5;
unsignedcharkey_value;
structfg
{
unsignedtim:
1;//秒增一允许标志位
unsigneddip:
1;//欢迎界面标志位
unsignedkey:
1;//有键按下标志位
unsignedkey1:
1;//键盘扫描调度时钟标志位
unsignedmode:
2;//状态模式控制
unsignederror:
1;//写数据超出范围错误标志位
unsignedsmart:
1;//闹表信号翻转标志位
}flag;
structffg
{
unsignedf1:
1;//
unsignedclk:
1;//
unsigneddm:
1;//校时较分标志位
unsignedclock:
3;//设置闹表时间索引
unsignedfclk:
1;//
unsignedf:
1;//
}ffg,ffg1;
structyear
{
unsignedren:
1;
unsignedeven:
1;
unsigneder:
1;
unsignedodd:
1;
unsignedfff:
4;
}y;
unsignedchartime,n,nn,sec,min,hour,day,month,cmin[8]={0},chour[8]={0};
unsignedintyear;
constcharcodedday[]={32,31,30,29};
constcharcodeerror[]="pleaseinputagain";
constcharcodeclock[]="CLOCK:
[]";
constcharcodetime1[]="settime:
";
constcharcodecurclk[]="CURCLOCK:
[]";
unsignedintcodetab[]={0xD7,0xEb,0xDB,0xBB,0x0ED,0x0DD,0x0bd,0x0EE,
0x0DE,0x0BE,0x077,0x0E7,0x0B7,0x07B,0x07D,0x07E};/*键码表*/
unsignedcharcodelt[]={0x7f,0x0bf,0x0df,0x0ef};/*行扫描码*/
unsignedcharcodedigit[]={0xc0,0xf9,0xa4,0xb0};
voiddelay(unsignedcharn)
{
unsignedchari,j;
while(n--)
{
for(i=0;i<9;i++)
for(j=0;j<11;j++)
;
}
}
voidstrcpy(char*p1,char*p2)
{
while(*p2!
='\0')
{
*(p1++)=*p2;
p2++;
}
*p1='\0';
}
voidPrint(void)/*写指令子程序*/
{
unsignedcharj;
RW=0;/*RW=0,写指令*/
E=1;
E=0;
for(j=0;j++<100;);/*延时*/
return;
}
voidclear(char*p)
{
char*pstr;
pstr=p;
while(*pstr!
='\0')
{
*(pstr++)='';
}
pstr=p;
}
void_clo_ck()interrupt3
{
//ffg.clk=~ffg.clk;
CLK=~CLK;
TH1=0xFB;
TL1=0x1E;
return;
}
voidtimeint()interrupt1using3/*定时器0中断程序,使用第3组寄存器*/
{
time--;/*循环次数减1*/
n--;
flag.key1=1;
if(time==0)/*若循环值为0,则对P1.0取反*/
{//cibuweifuzhugongnengbushibixude
time=20;/*恢复循环初值*/
flag.tim=1;
}
if(n==0)
{
n=10;
flag.smart=1;
sign=~sign;
}
TH0=0x3c;/*恢复计数初值*/
TL0=0x0b0;
return;/*中断返回*/
}
charscankey(void)
{
unsignedinti,a,b,y;
P2=0x0f;/*行线输出低电平,并判断是否有键按下*/
i=0;/*行计数器清零*/
b=lt[i];
for(i;i<4;i++)/*取行扫描码*/
{b=lt[i];
P2=b;/*送行扫描码到P2口*/
a=P2;/*读入列值*/
a=a&0x0f;/*保留低4位*/
if(a!
=0x0f)
break;
}
if(i<=3)
{/*循环到该列有键按下为止*/
b=b&0x0f0;/*取行扫描码的高4位*/
b=b|a;/*合并成为按键的扫描码*/
i=0;/*计数器清零*/
for(;b!
=tab[i];i++);/*在键值表中查找相应的键值*/
for(y=0;y<2000;y++);/*延时,去抖动*/
key_value=i;
for(P2=0x0f;P2!
=0x0f;);/*判断按键是否结束*/
for(y=0;y<2000;y++);/*延时*/
flag.key=1;
}
return(key_value);
}
display(unsignedchartemp)
{
unsignedk;
for(k=0;k++<100;);
RS=1;/*送出一个字母*/
RW=0;
E=1;
P1=temp;
for(k=0;k++<100;);/*延时*/
E=0;
}
lcd_init(void)
{
unsignedchari,j;
RS=0;/*向LCD写入3条30H?
使之复位*/
RW=0;
P1=0x30;
for(i=3;i>0;i--)
{E=1;
E=0;
for(j=0;j++<100;);/*延时*/
}
P1=0x38;/*设置8位数据总线方式*/
print();
P1=1;/*清屏指令01H*/
Print();/*调向LCD写指令子程序*/
P1=6;/*设置输入方式:
AC加1计数,光标右移1个字符*/
Print();/*设置显示方式:
开显示,光标显示;闪烁*/
P1=0x0c;
Print();
E=1;
E=0;
//j=0;/*显示计数器*/
RS=0;
RW=0;
P1=1;/*设置8位数据总线方式*/
print();
E=1;
E=0;
}
voidinit(void)
{
P0=0xc0;
n=10;
nn=12;
flag.tim=0;
ffg.f1=1;
ffg.clock=0;
}
time_init(void)
{
TMOD=0x11;/*设置计数器0工作方式1*/
TH0=0x3c;/*送计数器初值*/
TL0=0x0b0;
TH1=0xFB;
TL1=0x1E;
ET1=1;
TR1=1;
EA=1;/*开中断*/
ET0=1;
TR0=1;
time=20;/*设置循环次数初值*/
//TR0=1;/*开始计时*/
}
main()/*主程序*/
{
unsignedchari,j,k,m,p=0,vp;
charLcdStr[16]={"welcome"};/*定义数组并初始化*/
char*pstr=LcdStr;
m=10;
lcd_init();
init();
time_init();
j=0;/*显示计数器*/
CON=0;
for(i=0;i<16;i++)/*显示字母*/
{
//busy();/*判忙*/
RS=1;/*送出一个字母*/
RW=0;
E=1;
P1=LcdStr[i];
for(k=0;k++<100;);/*延时*/
E=0;
j++;/*显示计数器加1*/
//if(j==8)/*不到显示位置9跳转*/
//{
//for(k=