||
根据郭天翔学习版改写的,还好用吧,为了显示按键,加了数码显示。
请大侠们拍砖!!
#include <reg52.h>
#include <intrins.h>
#define uchar unsigned char
#define uint unsigned int
sbit dula=P2^6;
sbit wela=P2^7;
uchar code table[]={0xc0,0xf9,0xa4,0xb0,//数码显示依次为:0、1、2、3
0x99,0x92,0x82,0xf8, // 4、5、6、7
0x80,0x90,0x88,0x83, // 8、9、a、b
0xc6,0xa1,0x86,0x8e,0xff // c、d、e、f、全暗
};
uchar num,temp;
void delay(uint z) //延时子程序
{
uint x,y;
for(x=z;x>0;x--)
for(y=110;y>0;y--);
}
uchar keyscan() //键盘扫描程序,返回uchar型参数
{
uchar i;
for(i=0;i<4;i++)
{
P3=_crol_(0xfe,i);
temp=P3;
temp=temp&0xf0;
while(temp!=0xf0)
{
delay(5); //延时消抖
temp=P3;
temp=temp&0xf0;
while(temp!=0xf0)
{
temp=P3;
switch(temp)
{
case 0xee:num=1;
break;
case 0xde:num=2;
break;
case 0xbe:num=3;
break;
case 0x7e:num=4;
break;
case 0xed:num=5;
break;
case 0xdd:num=6;
break;
case 0xbd:num=7;
break;
case 0x7d:num=8;
break;
case 0xeb:num=9;
break;
case 0xdb:num=10;
break;
case 0xbb:num=11;
break;
case 0x7b:num=12;
break;
case 0xe7:num=13;
break;
case 0xd7:num=14;
break;
case 0xb7:num=15;
break;
case 0x77:num=16;
break;
}
while(temp!=0xf0) //等待按键释放
{
temp=P3;
temp=temp&0xf0;
}
}
}
}
return num;
}
void display(uchar aa)
{
dula=1;
P0=table[aa-1];
dula=0;
}
main()
{
num=17;
dula=1;
P0=0;
dula=0;
wela=1;
P0=0x20;
wela=0;
while(1)
{
display(keyscan());
}
}
这个流程是好多教科书上的做法。可惜,误导了好多人。为什么呢。因为它根本就没有考虑实际情况。
unsigned char v_ReadKey_f( void )
{
unsigned char KeyPress ;
if( P17 == 0)
{
Delay(20) ;
//延时20MS
If( P17 == 0)
{
KeyPress = 1 ;
While( !P17) ;
//等待释放
}
else
KeyPress = 0 ;
}
}
这样一个程序,相信对很多初学者而言都不陌生。因为好多书上基本都是这样的一个流程和写法。当有一天,我们想做一个数码管加按键调整的时钟,发现当我们按键按下去的时候,数码管就不亮了。为什么呢。原因就在这个键盘扫描函数。平常没有按键按下还好。一旦有键按下,它先是浪费了CPU的大部分时间(就是那个什么事情都没做的延时20MS函数)然后,又霸占CPU( 就是哪个死死等在那里的while(P17);语句)直到按键释放。对于这种情况我们是忍无可忍的,那么就让我们彻底的抛弃它吧。那么到底按键扫描函数改如何写呢……..所谓众里寻她千百度,蓦然回首,那人却在灯火阑珊处。如果我们把CPU延时的那20MS拿出来去做其它事情,那么不就充分利用CPU的时间了吗。而一般情况下我们只要前沿去抖动就可以了。也就是说了,我们只需在按键按下后去抖就可以了,对于按键的释放抖动可以不必要过于关注。当然这主要和应用的场合有关。一个能有效识别按键按下并支持连发功能的按键已经能够应用到大多数的场合了。
下面以四个独立按键的处理程序为例来讲解(支持单击和连发)
#include"regx52.h"
sbit KeyOne = P1^0 ;
sbit KeyTwo = P1^1 ;
sbit KeyThree = P1^2 ;
sbit KeyFour = P1^3 ;
#define uint16 unsigned int
#define uint8 unsigned char
#define NOKEY 0xff
#define KEY_WOBBLE_TIME 500 //去抖动时间(待定)
#define KEY_OVER_TIME 15000 //等待进入连击时间(待定),该常数要比正常 //按键时间要长,防止非目的性进入连击模式
#define KEY_QUICK_TIME 1000 //等待按键抬起的连击时间(待定)
void v_KeyInit_f( void )
{
KeyOne = 1 ; //按键初始化(相应端口写1)
KeyTwo = 1 ;
KeyThree = 1 ;
KeyFour = 1 ;
}
uint8 u8_ReadKey_f(void)
{
static uint8 LastKey = NOKEY ; //保存上一次的键值
static uint16 KeyCount = 0 ; //按键延时计数器
static uint16 KeyOverTime = KEY_OVER_TIME ; //按键抬起时间
uint8 KeyTemp = NOKEY ; //临时保存读到的键值
KeyTemp = P1 & 0x0f ; //读键值
if( KeyTemp == 0x0f )
{
KeyCount = 0 ;
KeyOverTime = KEY_OVER_TIME ;
return NOKEY ; //无键按下返回NOKEY
}
else
{
if( KeyTemp == LastKey ) //是否第一次按下
{
if( ++KeyCount == KEY_WOBBLE_TIME ) //不是第一次按下,则判断//抖动是否结束
{
return KeyTemp ; //去抖动结束,返回键值
}
else
{
if( KeyCount > KeyOverTime )
{
KeyCount = 0 ;
KeyOverTime = KEY_QUICK_TIME ;
}
return NOKEY ;
}
}
else //是第一次按下则保存键值,以便下次执行此函数时与读到的键值作比较
{
LastKey = KeyTemp ; //保存第一次读到的键值
KeyCount = 0 ; //延时计数器清零
KeyOverTime = KEY_OVER_TIME ;
return NOKEY ;
}
}
}
下面是我测试用的主程序(相关头文件未列出,仅仅作测试演示用)
void main(void)
{
uint8 KeyValue ;
int16 Count ;
v_LcdInit_f() ;
v_KeyInit_f() ;
CLS
LOCATE(3, 1)
PRINT("Key Test")
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)
LOCATE(6, 2)
}
else
{
//SHOW_ICON
}
}
}
每次执行读键盘函数时,只是对一些标志进行判断,然后退出。因此能够充分的利用CPU的资源。同时可以处理连发按键。此按键扫描按键函数可以直接放在主函数中。如果感觉按键太过灵敏或者迟钝则改一下相关消抖动的宏定义即可。此函数也可以通过中断标志位进行定时的扫描。此时,需要添加一个定时标志位,并将相关消抖动的和连击时间的宏定义改小即可。然后在主程序类似下面这样写即可
if( KeyTime ) //定时扫描时间到
{
KeyValue = u8_ReadKey_f() ;
}
具体的工作就交给您去完成啦。