晨辉教你轻松学51按键篇Word下载.docx
《晨辉教你轻松学51按键篇Word下载.docx》由会员分享,可在线阅读,更多相关《晨辉教你轻松学51按键篇Word下载.docx(13页珍藏版)》请在冰点文库上搜索。
看出什么区别来了没。
呵呵,只要你不是傻子我相信都能看出其中的区别。
由于按键的机械特性。
当按键闭合时,并不能马上保存良好的接触,而是来回弹跳。
这个时间很短,我们的手根本感觉不出来。
但是对于一秒钟执行百万条指令的单片机而言,这个时间是相当的长了。
那么在这段抖动的时间内,单片机可能读到多次高低电平的变化。
如果不加任何处理的话,就会认为已经按下,或者松开很多次了。
而事实上,我们的手一直按在按键上,并没有重复按动很多次。
要想能够正确的判断按键是否按下就要避开这段抖动的时间。
根据一般按键的机械特点,以及按键的新旧程度等而言,这段抖动的时间一般在5MS~20MS之间。
看到这里你明白了该如何做了吧。
看看下面的这个流程图,你应该不陌生吧。
这个流程是好多教科书上的做法。
可惜,误导了好多人。
为什么呢。
因为它根本就没有考虑实际情况。
我们根据这幅流程图来写它的代码看看。
unsignedcharv_ReadKey_f(void)
{
unsignedcharKeyPress;
if(P17==0)
Delay(20);
//延时20MS
If(P17==0)
{
KeyPress=1;
While(!
P17);
//等待释放
}
else
KeyPress=0;
这样一个程序,相信对很多初学者而言都不陌生。
因为好多书上基本都是这样的一个流程和写法。
可是当有一天,我们想做一个数码管加按键调整的时钟,发现当我们按键按下去的时候,数码管就不亮了。
原因就在这个键盘扫描函数。
平常没有按键按下还好。
一旦有键按下,它先是浪费了CPU的大部分时间(就是那个什么事情都没做的延时20MS函数)然后,又霸占CPU(就是哪个死死等在那里的while(P17);
语句)直到按键释放。
对于这种情况我们是忍无可忍的,那么就让我们彻底的抛弃它吧。
那么到底按键扫描函数改如何写呢……..所谓众里寻她千XX,蓦然回首,那人却在灯火阑珊处。
如果我们把CPU延时的那20MS拿出来去做其它事情,那么不就充分利用CPU的时间了吗。
而一般情况下我们只要前沿去抖动就可以了。
也就是说了,我们只需在按键按下后去抖就可以了,对于按键的释放抖动可以不必要过于关注。
当然这主要和应用的场合有关。
一个能有效识别按键按下并支持连发功能的按键已经能够应用到大多数的场合了。
下面以四个独立按键的处理程序为例来讲解(支持单击和连发)
#include"
regx52.h"
sbitKeyOne=P1^0;
sbitKeyTwo=P1^1;
sbitKeyThree=P1^2;
sbitKeyFour=P1^3;
#defineuint16unsignedint
#defineuint8unsignedchar
#defineNOKEY
0xff
#defineKEY_WOBBLE_TIME500
//去抖动时间(待定)
#defineKEY_OVER_TIME15000
//等待进入连击时间(待定),该常数要比正常//按键时间要长,防止非目的性进入连击模式
#defineKEY_QUICK_TIME1000
//等待按键抬起的连击时间(待定)
voidv_KeyInit_f(void)
KeyOne=1;
//按键初始化(相应端口写1)
KeyTwo=1;
KeyThree=1;
KeyFour=1;
uint8u8_ReadKey_f(void)
staticuint8LastKey=NOKEY;
//保存上一次的键值
staticuint16KeyCount=0;
//按键延时计数器
staticuint16KeyOverTime=KEY_OVER_TIME;
//按键抬起时间
uint8KeyTemp=NOKEY;
//临时保存读到的键值
KeyTemp=P1&
0x0f;
//读键值
if(KeyTemp==0x0f)
KeyCount=0;
KeyOverTime=KEY_OVER_TIME;
returnNOKEY;
//无键按下返回NOKEY
else
if(KeyTemp==LastKey)
//是否第一次按下
if(++KeyCount==KEY_WOBBLE_TIME)
//不是第一次按下,则判断//抖动是否结束
returnKeyTemp;
//去抖动结束,返回键值
if(KeyCount>
KeyOverTime)
KeyOverTime=KEY_QUICK_TIME;
}
else
//是第一次按下则保存键值,以便下次执行此函数时与读到的键值作比较
LastKey=KeyTemp;
//保存第一次读到的键值
//延时计数器清零
下面是我测试用的主程序(相关头文件未列出,仅仅作测试演示用)
voidmain(void)
uint8KeyValue;
int16Count;
v_LcdInit_f();
v_KeyInit_f();
CLS
LOCATE(3,1)
PRINT("
KeyTest"
)
LOCATE(6,2)
SHOW_ICON
while
(1)
{
KeyValue=u8_ReadKey_f();
if(KeyValue!
=NOKEY)
LOCATE(1,2)
if(KeyValue==0x0e)Count++;
if(KeyValue==0x0d)Count--;
if(KeyValue==0x0b)Count=0;
if(KeyValue==0x07)Count=0;
HIDE_ICON
PRINTD(Count,5)
//SHOW_ICON
}
}
每次执行读键盘函数时,只是对一些标志进行判断,然后退出。
因此能够充分的利用CPU的资源。
同时可以处理连发按键。
此按键扫描按键函数可以直接放在主函数中。
如果感觉按键太过灵敏或者迟钝则改一下相关消抖动的宏定义即可。
此函数也可以通过中断标志位进行定时的扫描。
此时,需要添加一个定时标志位,并将相关消抖动的和连击时间的宏定义改小即可。
然后在主程序类似下面这样写即可
if(KeyTime)
//定时扫描时间到
具体的工作就交给您去完成啦。
看看效果:
按键单击
连发时候的截图
至此,关于单个按键的学习就告一段落了,您是否已经明白了。
如果您还不明白,那么把这个程序好好的看看,并画下流程图,分析分析。
估计您就会恍然大悟。
关键是思路要转换过来。
下面我们来看看多个按键的情况吧
一般情况下,如果多个按键每个都直接接在单片机的I/O上的话会占用很多的I/O资源。
比较合理的一种做法是,按照行列接成矩阵的形式。
按键接在每一个的行列的相交处。
这样对于m行n列的矩阵,可以接的按键总数是m*n。
这里我们以常见的4*4矩阵键盘来讲解矩阵键盘的编程。
上图就是矩阵键盘的一般接法。
这里我们要介绍一种快速的键盘扫描法:
线反转法(或者称为行列翻转法)。
具体流程如下。
首先,让单片机的行全部输出0,列全部输出1,读取列的值(假设行接P3口的高四位,列接低四位)。
即P3=0x0f;
此时读列的值,如果有键按下,则相应的列读回来的值应该为低。
譬如此时读回来的值为0x0e;
即按键列的位置已经确定。
这时反过来,把行作为输入,列作为输出,即P0=0xf0;
此时再读行的值,如果按键仍然被按下,则相应的行的值应该为低,如果此时读回来的值为0xe0,则确定了行的位置。
说到这里,您应该笑了,知道了一个按键被按下的行和列的位置,那么就可以肯定确定它的位置了。
我们把读回来的行值和列值进行或运算。
即0xe0|0x0e即0xee。
那么0xee就是我们按下的按键的键值了。
怎么样。
只需几步就可以判断所有的键值,简单吧。
下面再结合一个例子具体看看。
/******************************************
*此模块所需相关支持库
*
******************************************/
/****************************************
*与硬件连接相关的定义及宏定义和操作宏
*****************************************/
#defineKEYBOARD
P3
//键盘连接到单片机上的端口位置
#defineREAD_ROW_ENLABLE
KEYBOARD=0x0f;
//读端口之前先把相应口置位(由基本51单片机特性决定的)
#defineREAD_COL_ENLABLE
KEYBOARD=0xf0;
//根据实际硬件连接情况修改
/*****************************************
*模块内相关的宏定义及常数宏
*
0xff
//定义无键按下时的返回值
#defineDELAY_COUNT
2
//消抖时间常数
*此模块所需的全局或者外部变量
bitbdataStartScan=0;
//此变量需放在定时中断中置位
*按键扫描函数,按下去后经去抖,确定按下*
*则返回键值0~15;
无键按下则返回0xff;
*此函数需要定时器的支持(去抖....)
uint8u8_KeyBoardScan_f()
staticuint8DelayCount=0;
uint8KeyValueRow=0;
uint8KeyValueCol=0;
uint8KeyValue=0;
if(StartScan)
//开始扫描,StartScan在定时中断中置位
StartScan=0;
//清除开始扫描标志位,避免多次重复执行扫描程序
//读入按键状态前先向相应端口写1(由基本51单片机硬件结构决定)
READ_ROW_ENLABLE
if((KEYBOARD&
0x0f)!
=0x0f)//判断是否有键按下
DelayCount++;
if(DelayCount<
=DELAY_COUNT)
//有键按下则判断延时去抖的时间是否达到
//消除了抖动
=0x0f)
//再次判断是否按键真的按下
DelayCount=0;
//确定按下后,延时去抖计时器清0
KeyValueRow=KEYBOARD&
//取得行码
//准备读列,先向相应端口写1(由基本51单片机硬件结构决定)
READ_COL_ENLABLE
if((KEYBOARD&
0xf0)!
=0xf0)//反转,读列码
KeyValueCol=KEYBOARD&
0xf0;
//取得列码
//合并取得的行码和列码,即是相应按键的键值
switch(KeyValueCol|KeyValueRow)
case0x77:
KeyValue=0;
break;
case0xb7:
KeyValue=1;
case0xd7:
KeyValue=2;
case0xe7:
KeyValue=3;
case0x7b:
KeyValue=4;
case0xbb:
KeyValue=5;
case0xdb:
KeyValue=6;
case0xeb:
KeyValue=7;
case0x7d:
KeyValue=8;
case0xbd:
KeyValue=9;
case0xdd:
KeyValue=10;
break;
case0xed:
KeyValue=11;
case0x7e:
KeyValue=12;
case0xbe:
KeyValue=13;
case0xde:
KeyValue=14;
case0xee:
KeyValue=15;
default:
returnNOKEY;
returnKeyValue;
DelayCount=0;
voidv_T0_I