矩阵按键数字电子钟DOC.docx
《矩阵按键数字电子钟DOC.docx》由会员分享,可在线阅读,更多相关《矩阵按键数字电子钟DOC.docx(33页珍藏版)》请在冰豆网上搜索。
矩阵按键数字电子钟DOC
方案二
矩
阵
键
盘
数
字
电
子
钟
电科0902班
组长:
祁俐俐
组员:
吉才韩江蔡杨
苏欣李程付磊
2012年7月12日
矩阵键盘数字电子钟
一、电子钟设计目的
1、学习数字电子钟的原理和实现方法。
2、掌握键盘的控制原理和编程方法。
3、掌握51单片机定时器与中断的使用。
4、掌握LED数码管显示的原理及编程方法。
二电子钟设计要求
设计一个数字电子钟,要求可进行时、分、秒显示,最大显示时间为23:
59:
59,,并且具有时间调整、闹铃、启动、暂停和清零(复位)等功能。
可以完成以下几点功能:
1)六个LED上实现正常的时间显示,24小时制
2)实现时间的正确调节
3)闹钟的定时及到时间之后的音乐响铃
4)矩阵键盘,0到9键,光标的左移右移键,闹钟调节键,时间调节键,走时键,复位键,停止键等,其中涉及到某些键的复用
在设计过程中还用到8255来扩展并行
三设计方案规划与选定
根据要求采用AT89C51单片机进行设计,AT89C51单片机是一款低功耗,高性能CMOS8位单片机,片内含4KB在线可编程(ISP)的可反复擦写1000次的Flash只读程序存储器,器件采用高密度、非易失性存储技术制造,兼容标准MCS-51指令系统及80C51引脚结构。
这样,既能做到经济合理又能实现预期的功能。
在程序方面,采用分块设计的方法,这样既减小了编程难度、使程序易于理解,又能便于添加各项功能。
程序可分为闹钟的音乐程序、时间显示程序、闹钟显示程序、调时显示、定时程序,走时程序,复位的模块化的程序。
硬件接线图如图所示:
对于程序的设计,遵循分块的原则,不同的子程序实现不同的功能,通过函数的调用实现相应的功能。
原理分析如下:
主程序:
执行主程序,按照得到的的10到15键值转到相应的子程序去执行相应的功能。
模块一:
时间显示模块:
用51单片机的6个LED七段数码管,依次分别显示时,分,秒,中间用小数点分开。
正常走时时秒数满60进位,分钟加1,分钟满60小时加1,小时满24清0
模块二:
显示时间调整模块:
当按下键值为10的开关后,进入时间调整程序,对六个数码管相应位的的控制按照我们生活中的正常逻辑进行控制。
进行调整时间
模块三:
闹钟音乐模块:
当按下键值为11的开关后,进入闹钟时间设置状态,设定相应的时间,当到了设定的时间之后,进入产生中断,进入闹钟音乐程序
模块四:
键盘扫描得到按键值函数:
4*4矩阵键盘,用8255的PC口得到行值,p1口得到列值,最后得到按下开关的键值,根据键值转到相应的模块执行相应的功能。
各个模块程序设计好之后,要进行最后的整合,函数的调用参数设置要正确,使程序能够正常的运行,在keil上调试通过之后,检查proteus中硬件连接有没有错误,确定无误后,在proteus中进行模拟实验,最后可以到实验室进行真实元器件的连接。
四硬件设计
1.AT89C51:
该单片机功能强大,不仅能满足设计的需要,也可以在设计要求的基础上进行一些扩展。
单片机的结构如下:
主要应用AT89C51单片机的p1.0到p1.3得到键盘的列值,p1.7通过放大器进行功率放大控制喇叭的响音乐。
同时对单片机的并行接口扩展了一个8255,用p0.0和p0.1作为其地址线,同时用到了89c51的两个十六位定时计数器T0,T1,进行显示时间和闹钟响音乐的中断控制。
引脚功能如下:
VCC:
供电电压。
GND:
接地。
P0口:
P0口为一个8位漏极开路双向I/O口,每脚可吸收8TTL门电流。
当P1口的管脚第一次写1时,被定义为高阻输入。
P0能够用于外部程序数据存储器,它可以被定义为数据/地址的低八位。
在FIASH编程时,P0口作为原码输入口,当FIASH进行校验时,P0输出原码,此时P0外部必须被拉高。
P1口:
P1口是一个内部提供上拉电阻的8位双向I/O口,P1口缓冲器能接收输出4TTL门电流。
P1口管脚写入1后,被内部上拉为高,可用作输入,P1口被外部下拉为低电平时,将输出电流,这是由于内部上拉的缘故。
在FLASH编程和校验时,P1口作为第八位地址接收。
P2口:
P2口为一个内部上拉电阻的8位双向I/O口,P2口缓冲器可接收,输出4个TTL门电流,当P2口被写“1”时,其管脚被内部上拉电阻拉高,且作为输入。
并因此作为输入时,P2口的管脚被外部拉低,将输出电流。
这是由于内部上拉的缘故。
P2口当用于外部程序存储器或16位地址外部数据存储器进行存取时,P2口输出地址的高八位。
在给出地址“1”时,它利用内部上拉优势,当对外部八位地址数据存储器进行读写时,P2口输出其特殊功能寄存器的内容。
P2口在FLASH编程和校验时接收高八位地址信号和控制信号。
P3口:
P3口管脚是8个带内部上拉电阻的双向I/O口,可接收输出4个TTL门电流。
当P3口写入“1”后,它们被内部上拉为高电平,并用作输入。
作为输入,由于外部下拉为低电平,P3口将输出电流(ILL)这是由于上拉的缘故。
表3-1AT89C51的特殊功能口
P3口也可作为AT89C51的一些特殊功能口,如表3-1所示:
口管脚
备选功能
P3.0
RXD(串行输入口)
P3.1
TXD(串行输出口)
P3.2
/INT0(外部中断0)
P3.3
/INT1(外部中断1)
P3.4
T0(记时器0外部输入)
P3.5
T1(记时器1外部输入)
P3.6
/WR(外部数据存储器写选通)
P3.7
/RD(外部数据存储器读选通)
P3口同时为闪烁编程和编程校验接收一些控制信号。
RST:
复位输入。
当振荡器复位器件时,要保持RST脚两个机器周期的高电平时间。
ALE/PROG:
当访问外部存储器时,地址锁存允许的输出电平用于锁存地址的地位字节。
在FLASH编程期间,此引脚用于输入编程脉冲。
在平时,ALE端以不变的频率周期输出正脉冲信号,此频率为振荡器频率的1/6。
因此它可用作对外部输出的脉冲或用于定时目的。
然而要注意的是:
每当用作外部数据存储器时,将跳过一个ALE脉冲。
如想禁止ALE的输出可在SFR8EH地址上置0。
此时,ALE只有在执行MOVX,MOVC指令是ALE才起作用。
另外,该引脚被略微拉高。
如果微处理器在外部执行状态ALE禁止,置位无效。
/PSEN:
外部程序存储器的选通信号。
在由外部程序存储器取值期间,每个机器周期两次/PSEN有效,但在访问外部数据存储器时两次/PSEN信号不出现。
/EA:
位低电平时,为外部存储器,/EA将内部锁定为RESET;高电平时,此间内部程序存储器。
XTAL1:
反向振荡放大器的输入以及内部时钟工作电路的输入。
XTAL2:
来自反向振荡器的输出。
2.8255
8255是一个可编程并行接口芯片,有一个控制口和三个8位数据口,外设通过数据口与单片机进行数据通信,各数据口的工作方式和数据传送方向是通过用户对控制口写控制字控制的。
我们用到了PA,PB口进行对数码显示管的片选和段选,PC口得到按键的行值。
3.数码管:
LED数码管实际上是由七个发光管组成8字形构成的,加上小数点就是8个。
如图3-10。
这些段分别由字母a,b,c,d,e,f,g,dp来表示。
当数码管特定的段加上电压后,这些特定的段就会发亮,以形成我们眼睛看到的字样了。
如:
显示一个“2”字,那么应当是a亮b亮g亮e亮d亮f不亮c不亮dp不亮。
LED数码管有一般亮和超亮等不同之分,也有0.5寸、1寸等不同的尺寸。
小尺寸数码管的显示笔画常用一个发光二极管组成,而大尺寸的数码管由二个或多个发光二极管组成,一般情况下,单个发光二极管的管压降为1.8V左右,电流不超过30mA。
发光二极管的阳极连接到一起连接到电源正极的称为共阳数码管,发光二极管的阴极连接到一起连接到电源负极的称为共阴数码管。
常用LED数码管显示的数字和字符是0、1、2、3、4、5、6、7、8、9。
4.矩阵键盘
在矩阵式键盘中,每条水平线和垂直线在交叉处不直接连通,而是通过一个按键加以连接。
这样,两个端口就可以构成4×4=16个按键,比之直接将端口线用于键盘多出了一倍,而且线数越多,区别越明显,比如再多加一条线就可以构成20键的键盘,而直接用端口线则只能多出一键(9键)。
由此可见,在需要的键数比较多时,采用矩阵法来做键盘是合理的。
矩阵式结构的键盘显然比直接法要复杂一些,识别也要复杂一些,如图所示,列线通过电阻接正电源,并将行线所接的单片机的I/O口作为输出端,而列线所接的I/O口则作为输入。
这样,当按键没有按下时,所有的输入端都是高电平,代表无键按下。
行线输出是低电平,一旦有键按下,则输入线就会被拉低,这样,通过读入输入线的状态就可得知是否有键按下了。
5.74LS373
1D~8D为8个输入端1Q~8Q为8个输出端
LE是数据锁存控制端;当LE=1时,锁存器输出端同输入端;当LE由“1”变为“0”时,数据输入锁存器中。
OE为输出允许端;当OE=“0”时,三态门打开;当OE=“1”时,三态门关闭,输出呈高阻状态。
在MCS-51单片机系统中,常采用74LS373作为地址锁存器使用,其连接方法如电路硬件图所示。
其中输入端1D~8D接至单片机的P0口,输出端提供的是低8位地址,LE端接至单片机的地址锁存允许信号ALE。
输出允许端OE接地,表示输出三态门一直打开。
五软件设计
关于电子钟的功能,程序的主要功能是准确的实现走时功能,我们用定时器来准确的设定时间(定时器T0准确设定中断时间为20ms,终端中统计50次中断即为一秒)。
而次要功能,则是修改时间,设定闹钟等。
如LED显示时,光标的显示与移动可以方便的修改设定时间及闹钟。
流程图如下:
软件设计思路如下:
1.主程序设计:
主程序中完成对8255芯片和定时器设置的初始化,然后进入无限循环的查询模块,动态扫描LED显示模块,使显示走时正常。
同时在循环中完成对矩阵键盘的状态(是否有键按下,如有是哪一个)进行监控,如果有键按下根据其键值跳转到相应的子程序中进行执行,完成相应的功能后会自动跳转回来。
这样整个程序就实现了连续有效的运行。
2.按键扫描子程序:
首先根据端口状态判断是否有键按下,如果没有就跳过读键值这个阶段。
如果有键按下就通过行列扫描判断出所按下键的位置,并相应的形成键值,保存在一个全局变量中等待被查询。
然后跳出子程序。
3.显示子程序:
该子程序在LED扫描中被调用。
首先根据参数判别是显示时间还是显示闹钟,然后针对六个位形成相应的段码值。
通过参数值在相应位输出显示。
4.时间及闹钟设定子程序:
本程序完成时间的修改及闹钟的设置,对时间和闹钟的设置是通过修改时间值的全局变量或闹钟值的全局变量来完成的。
在修改过程中正在修改位用光标来显示,按下数字后光标自动转移到下一位,可以通过左右移动光标来实现正在修改位的调整。
当六位全部修改完毕,或者按下确定/退出键后自动跳出子程序。
5.响铃子程序:
当设定的闹钟时间到时,转入本程序执行。
本程序通过读取频率表来设定T1定时中断的设定,以在响铃端口输出频率一定的脉冲波。
同时通过读取时间表控制每一频率所响的时间,这样就可以在扬声器输出音乐了。
同时在程序中添加了键盘扫描环节,一旦按下退出键,就关闭T1定时器,跳出程序,响铃就可以终止。
六调试
由于我们采用了“模块化”的编程思路,程序的调试变得相对简单,分工也就比较明确。
首先我们编制出主程序,使走时功能正常完成,显示现在的时间,过程中出现的主要问题是动态扫面的相关参数设置不合理,显示出现不正常的现象;然后我们开始编写相关子程序,由于几个主要的子程序之间没有参数上的直接联系,所以我们分别编写各个子程序,调试功能无误后添加到主干程序中来。
由于程序编写分工完成,这个阶段碰到最大的问题之一就是程序中对变量的复杂定义极大的浪费了资源,甚至有些重名的非法定义。
然后我们通过设置几个统一的全局变量,在各个子程序中分别对全局变量做出不同的修改已完成不同的功能。
最后这个问题得到了比较好的解决。
程序调试中还出现了种种命令的错误使用,尤其是时间调整函数模块,对编程的不熟悉,导致时间调整程序模块浪费了很多时间,里面涉及到大量的逻辑编程,通过不断地查资料以及请教别人,最后克服了困难,以及不注意所造成的错误,而更重要的是逻辑错误。
通过一步步的调整,在错误中启发自己,最终使调试成功。
七心得体会:
通过这次实训使我们更深刻地感受到课程设计的综合性之强大,完成对数字电子钟的设计与制作调试,使我们对单片机应用系统的设计过程进行了掌握。
当我们选择一个课程设计的时候,不是马上就动手做,而是先进行可行性论证。
首先提出几套方案,然后对各个方案进行对比,由易到难,先做出一个简单的仿真,然后根据要求一步步修改,直至达到最终要求。
对于芯片的使用,我们应该在了解它的各项功能的前提条件下,灵活巧妙地运用。
具体的芯片资料和图片我们通过查阅相关的书籍,在网上能够很方便的查找。
这次设计仿真我们用到了仿真软件Proteus和编译软件keil,从软件的安装到使用,从网上查阅资料学到了很多课堂之外的专业知识。
这次的设计最主要是单片机的应用,从控制到接口。
技术是一个多学科的综合,要做到灵活应用需要自我学习各种辅助技术的应用。
这次的课程设计,我作为我们小组的组长,我主要是负责画仿真图,调试,以及其中一部分程序的编写,最终还要把几部分程序都结合在一起,任务艰巨,但是也具有挑战性,让我学会了很多东西,最终我们团结一致,成功调制除了电子钟。
调试成功的那一刻,大家都很兴奋,几天的辛苦终于得到了很大的收获。
:
源程序代码
#include"Absacc.h"
#include"reg51.h"
//8255端口地址定义
#defineC8255_AXBYTE[0x7F00]
#defineC8255_BXBYTE[0x7F01]
#defineC8255_CXBYTE[0x7F02]
#defineC8255_CONXBYTE[0x7F03]
//键盘及数码管显示变量组
unsignedchardatatimer=0,second=0,minute=0,hour=0;
unsignedchardatasecond_bell=59,minute_bell=59,hour_bell=23;
unsignedcode
tab_high[10]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f};
tab_low[10]={0xbf,0x86,0xdb,0xcf,0xe6,0xed,0xfd,0x87,0xff,0xef};
unsignedcode
tab_position[6]={0x3e,0x3d,0x3b,0x37,0x2f,0x1f};
unsignedchartab2[6]={0x00,0x00,0x00,0x00,0x00,0x00};
unsignedcharkey_down;
unsignedcharkey_value;
unsignedcharflag,tab,pp=1;
unsignedchardataposition=0;
unsignedintval;
unsignedchari;
unsignedcharnumber=100;
//响铃函数变量组
#defineClk0x070000
sbitP17=P1^7;//扬声器控制引脚
unsignedchardataval_H;//计数器高字节
unsignedchardataval_L;//计数器低字节
//响铃音乐频率表
unsignedintcodefreq_list[]={371,495,495,495,624,556,495,556,624,
495,495,624,742,833,833,833,742,624,
624,495,556,495,556,624,495,416,416,371,495,833,742,624,624,495,556,495556,833,742,624,624,742,833,990,742624,624,495,556,495,556,624,495,416416,371,495,0};
//响铃音乐频率对应时间表
unsignedcharcodetime_list[]=
{4,6,2,4,4,6,2,4,4,6,
2,4,4,12,1,3,6,2,4,4,
6,2,4,4,6,2,4,4,12,4,
6,2,4,4,6,2,4,4,6,2,
4,4,12,4,6,2,4,4,6,2,
4,4,6,2,4,4,12};
//延时函数
voiddelay_short(void)
{
inti=0;
for(i=0;i<=255;i++){}
}
//清除函数
voidclear()
{
timer=0;
second=0;
minute=0;
hour=0;
pp=0;
}
//***********************************//
//键盘函数组(包括按键扫描函数、得到按键值函数和键盘情况函数)
//按键扫描函数
voidkeyscan()
{
unsignedcharcc;
P1=0x00;
cc=C8255_C;
key_down=(~cc)&0x0f;
}
//得到按键值函数
voidgetkey()
{
unsignedcharvalue;
unsignedchari,j=0x0e;
for(i=0;i<4;i++)
{
P1=j;
value=C8255_C&0x0f;
if(!
(value&0x01))
{
key_value=i+0;
return;
}
if(!
(value&0x02))
{
key_value=i+4;
return;
}
if(!
(value&0x04))
{
key_value=i+8;
return;
}
if(!
(value&0x08))
{
key_value=i+12;
return;
}
j<<=1;
}
}
//键盘情况函数
voidkeycondition()
{
keyscan();
if(key_down)
{
delay_short();
keyscan();
if(key_down)
{
getkey();
}
}
}
//***********************************//
//显示函数组(包括数码管显示函数和显示光标函数)
//数码管显示函数
voiddisplay(unsignedcharV,unsignedcharM)
{
if(M==0)
{
tab2[5]=hour/10;
tab2[4]=hour%10;
tab2[3]=minute/10;
tab2[2]=minute%10;
tab2[1]=second/10;
tab2[0]=second%10;
}
if(M==1)
{
tab2[5]=hour_bell/10;
tab2[4]=hour_bell%10;
tab2[3]=minute_bell/10;
tab2[2]=minute_bell%10;
tab2[1]=second_bell/10;
tab2[0]=second_bell%10;
}
if(V==0)
{
C8255_A=tab_position[0];
C8255_B=tab_high[tab2[5]];
delay_short();
}
if(V==1)
{
C8255_A=tab_position[1];
C8255_B=tab_low[tab2[4]];
delay_short();
}
if(V==2)
{
C8255_A=tab_position[2];
C8255_B=tab_high[tab2[3]];
delay_short();
}
if(V==3)
{
C8255_A=tab_position[3];
C8255_B=tab_low[tab2[2]];
delay_short();
}
if(V==4)
{
C8255_A=tab_position[4];
C8255_B=tab_high[tab2[1]];
delay_short();
}
if(V==5)
{
C8255_A=tab_position[5];
C8255_B=tab_high[tab2[0]];
delay_short();
}
}
//显示光标函数
voiddisplay_cursor(unsignedcharV)
{
if((V%2)==0)
tab=0x08;
if((V%2)==1)
tab=0x88;
C8255_A=tab_position[V];
C8255_B=tab;
delay_short();
}//**********************************//
//响铃函数
voidbell_sound(void)
{
unsignedintval;
unsignedchari;
unsignedcharm,k,mark=1;
TR1=1;
while(mark)
{
i=0;
while((freq_list[i]!
=0)&&(mark==1)){
keycondition();
if(key_value==15)
{
TR1=0;
mark=0;
}
val=Clk/(freq_list[i]);
val=0xFFFF-val;
val_H=(val>>8)&0xff;
val_L=val&0xff;
TH1=val_H;
TL1=val_L;
for(m=0;m{
for(k=0;k<0x26;k++)
{
i