学习过单片机技术的人都知道,单片机的按键输入一般可分为简单的独立式按键输入及行列式键盘输入两种。图1为简单的独立式键盘输入示意图,独立式键盘输入适合于按键输入不多的情况(<5个按键),具有占用口线较少、软件编写简单容易等特点。
图2为行列式键盘输入示意图,列线接P1.0~P1.3,行线接P1.4~P1.7。行列式键盘输入适合于按键输入多的情况,如有16个按键输入,用简单按键输入用要占用2个输入口(共16位),而使用行列式键盘输入只需占用一个输入口(8位)。但行列式键盘输入软件编写较复杂,对初学者而言有一定的难度。
以上略谈了一下按键输入的情况。在很多状态下,按键输入的值要同时要在LED数码管上显示出来。如一个按键设计为输入递增(加法)键,可以设计成每点按一下,数值递增加1,同时在LED数码管上显示出来;也可设计成持续按下时,数值以一定时间间隔(如0.3秒)累加。但是当欲输入值较大时(如三位LED数码管作输入显示时的输入值最大为999),则可能按下键的时间太长(最长达300秒),看来这种方式只适用于一位或至多两位数值(最大99)的输入。当然你也可多设几个键,每个键只负责一位数值的输入,但这样会占用较多的口线,浪费宝贵的硬件资源。
大家可能见到过,一些进口的温度控制器(如日本RKC INSTRUMENT INC. 生产的REX_C700温控器)的面板设计为:温度测量值用4位LED数码管显示,输入设定值显示也用4位LED数码管,输入按键只有4个,一个为“模式设定键”,一个为“左移键”,另两个为“加法键”、“减法键”。欲输入设定值(温控值)时,按一下“模式设定键”,程序进入设定状态,此时输入设定值显示的4位LED数码管中,个位显示最亮(稳定显示),而十、百、千位显示较暗(有闪烁感),说明可对个位进行输入。按下“加法键”或“减法键”,即可输入个位数的值;点按一下“左移键”,变为十位显示最亮,而个、百、千位显示较暗,说明可对十位进行输入。按下“加法键”或“减法键”,即可输入十位数的值;……这样可完成4位数的输入。完成输入后,再按一下“模式设定键”,程序即退出设定状态,进入工作运行。用这种输入方法,不仅输入4位数用4个键即可,再多位(5位至24位)的输入也用这4个键就够了。
大家了解了这种新颖的按键输入方式后,一定很感兴趣,也想掌握设计方法。为了便于大家理解,这里结合笔者设计的一款“节能时控器”,详细进行讲解。“节能时控器”用于定时控制大功率电器工作,因现采用分时计费方法,可起到节约开支的作用,对工业生产成效显著。
图3为“节能时控器”硬件构成原理图。“节能时控器”共有4个输入按键:set--模式设定键,left--左移键,up—加法键,on/off--定时1、2启动/关闭键。单片机IC1(AT89C2051)只有15条I/O线,由于受I/O线数量限制,因此P1口中的P1.0~P1.3既作为驱动4位LED数码管的数据输出一部分,同时也用作按键的输入。无疑,这种方式大大节约了硬件的I/O线,但也给编程者提出了更高的技术要求。限于篇幅,我们只对要详解的按键输入程序进行分析,其它部分只略作介绍。如读者需“节能时控器”详细的源程序,可以Email:xuyuandz@163.com与作者联系。
图4为主程序状态流程图。可见主程序只负责进行走时或调整时间的运算及显示,而判断按键输入则放在T1定时中断(10mS)服务子程序中。T0作为走时的基准被设置为100mS定时中断。这种设计的优点是大大简化了主程序设计,并且CPU会定时关心键盘,只要定时中断时间足够短(如为几十mS),就不会漏掉每一次的按键输入。
下面为判断按键输入的T1定时中断服务子程序,序号为解释方便而加。
/*10mS定时中断服务子函数*/
序号 1:void zd1(void) interrupt 3
2:{
3:uchar i,j;i=P1;j=P3;
4:TH1=-(5000/256);
5:TL1=-(5000%256);
6:if(m==1)n++;
7:if(n>=30){n=0;m=0;}
8:P3_7=0;
9:P1=0xff;
10:if(P1!=0xff)
11:{
12:if(n==0)m=1;
13:{if(n==1)
14:{
15:if(P1_0==0){set++;left=0;}
16:if(set>=4)set=0;
17:if(set==1)flag=0x55;
18:if(P1_1==0)left++;
19:if(left>=4)left=0;
20:if(P1_2==0){up++;
21:switch(left)
22:{
23:case 0:{if(up>=10)up=0;}break;
24:case 1:{if(up>=6)up=0;}break;
25:case 2:{if(up>=10)up=0;}break;
26:case 3:{if(up>=3)up=0;}break;
27:default:break;
28:}
29:}
30:if(P1_2==0){
31:switch(set)
32:{case 0:break;
33:case 1:x[left]=up;break;
34:case 2:{y[left]=up;if(P1_3==0)o_f1=!o_f1;}break;
35:case 3:{z[left]=up;if(P1_3==0)o_f2=!o_f2;}break;
36:default:break;
37:} }
38:else {
39:switch(set)
40:{case 0:break;
41:case 1:up=x[left];break;
42:case 2:{up=y[left];if(P1_3==0)o_f1=!o_f1;}break;
43:case 3:{up=z[left];if(P1_3==0)o_f2=!o_f2;}break;
44:default:break;}
45 :}}
46:}}
47:P1=i;P3=j;
48:}
序号1(程序解释,以下同):声明定时1中断函数。
序号2:定时1中断函数开始。
序号3:定义i、j为无符号字符型局部变量。将当时的P1口、P3口状态送i、j暂存。
序号4、5:定时器T1重新载入10mS初值。
序号6:如变量m等于1,则变量n递增。说明:m、n为整个程序开始时定义的无符号字符型全局变量。
序号7:如变量n大于等于30,则m、n清零。
序号8:P3.7置0,准备读取按键输入。
序号9:P1口置全1,准备读取按键输入。
序号10:如果P1口不等于全1,说明4个按键中有键按下。
序号11:进入if(P1!=0xff)语句范围。
序号12:如果n等于0,进入if(n==0)语句,m置1。
序号13:如果n等于1,进入if(n==1)语句,同时进行下面的具体判断按键语句。作用效果为:开始时m、n均赋0,一旦有键按下,第一次中断产生时m赋1;第二次中断产生时n递增。当n等于1时(第二次中断产生)进入下面的具体判断按键语句。若持续按下键,则第三次中断产生~第三十一次中断产生时,程序不进入具体的判断按键语句过程(因这时n不等于1)。由于中断每10mS产生一次,这样可实现每0.31秒(31x10=0.31秒)进行一次加法或移位的操作,与人眼的视觉特性相吻合。
序号14:进入具体判断按键语句范围。
序号15:如果P1.0等于0(即电路中的set键按下),变量set递增,变量left清0。说明:set、left是为了判断模式设定及左移而在整个程序开始时定义的无符号字符型全局变量。
序号16:如果set大于等于4,则set清0。说明:set值只能在0~3间变化,只有4种工作模式(走时及输出控制模式、走时调整模式、定时1调整模式、定时2调整模式)。
序号17:在set等于1时,向RAM区标志变量flag写入55H。说明:flag是在整个程序开始时定义的无符号字符型全局变量,用作判断RAM区是否受干扰的依据。
序号18:如果P1.1等于0(即电路中的left键按下),变量left递增。
序号19:如果left大于等于4,则left清0。说明:left值只能在0~3间变化,LED数码管只有4位显示。
序号20:如果P1.2等于0(即电路中的up键按下),进入if(P1_2==0)语句,变量up递增。说明:up是为了判断数值增量而在整个程序开始时定义的无符号字符型全局变量。
序号21:随即进入switch(left)开关语句。
序号22:switch(left)开关语句开始。
序号23:left值为0时,如果up大于等于10,则up清0。随即退出。说明:电子钟的个位可在0~9之间调整。
序号24:left值为1时,如果up大于等于6,则up清0。随即退出。说明:电子钟的十位可在0~5之间调整。
序号25:left值为2时,如果up大于等于10,则up清0。随即退出。说明:电子钟的百位可在0~9之间调整。
序号26:left值为3时,如果up大于等于3,则up清0。随即退出。说明:电子钟的千位可在0~2之间调整。
序号27:若left为其它值,也退出。
序号28:switch(left)开关语句结束。
序号29:if(P1_2==0)语句结束。
序号30:如果P1.2等于0(即电路中的up键按下时),进入if(P1_2==0)语句,同时进入switch(set)开关语句。
序号31:switch(set)开关语句开始。
序号32:set值为0时,退出。
序号33:set值为1时,将此时up值送入X数组的第left位。随即退出。说明:X数组是显示走时缓存区。
序号34:set值为2时,将此时up值送入Y数组的第left位。若此时P1.3等于0(即电路中的on/off键按下),则定时1启停标志位o_f1取反(启动/关闭)。随即退出。说明:Y数组是定时1记忆缓存区。o_f1是为了判断定时1启动/关闭而在整个程序开始时定义的位标志。
序号35:set值为3时,将此时up值送入Z数组的第left位。若此时P1.3等于0(即电路中的on/off键按下),则定时2启停标志位o_f2取反(启动/关闭)。随即退出。说明:Z数组是定时2记忆缓存区。o_f2是为了判断定时2启动/关闭而在整个程序开始时定义的位标志。
序号36:若set为其它值,也退出。
序号37:switch(set)开关语句结束。if(P1_2==0)语句结束。
序号38:else语句开始。
序号39:又进入switch(set)开关语句。说明:上一个switch(set)开关语句是将按键产生的up值送入X、Y、Z数组存放,现在这个switch(set)开关语句是调出X、Y、Z数组内容至变量up,以便在原来的基础上递增。例如:原来的X[0]值为5,则在调整时个位LED数码管显示就从5开始往上调,而不会产生从0或其它值开始上调的情况,适合人的一般直觉。
序号40:set值为0时,退出。
序号41:set值为1时,将此时X数组的第left位值送入变量up。随即退出。
序号42:set值为2时,将此时Y数组的第left位值送入变量up。若此时P1.3等于0(即电路中的on/off键按下),则定时1启停标志位o_f1取反(启动/关闭)。随即退出。
序号43:set值为3时,将此时Z数组的第left位值送入变量up。若此时P1.3等于0(即电路中的on/off键按下),则定时2启停标志位o_f2取反(启动/关闭)。随即退出。
序号44:若set为其它值,也退出。
序号45:switch(set)开关语句结束。else语句结束。
序号46:if(n==0)语句结束。if(P1!=0xff)语句结束。
序号47:本次定时中断快结束时,将暂存于i,j的当时P1口、P3口状态还原。
序号48:定时1中断函数结束。
上面为按键输入程序设计的详细解释,按键输入时需将当时状态实时显示出来,我们将显示走时、显示调整走时、显示调整定时1、显示调整定时2做成四个子程序,分别由set为0、1、2、3时散转后的“显示走时并判断定时1、2到否程序”、“显示调整走时程序”、“显示调整定时1程序”、“显示调整定时2程序”进行调用。为达到需输入的某位显示最亮(稳定显示),而其它三位显示较暗(有闪烁感)的视觉效显,让三位需显示较暗的数码管每位点亮3mS,而显示最亮的那位数码管点亮36mS即可。限于篇幅,具体程序就不进行详解了,读者朋友可自行编写。