||
按键总结篇
作者:HouZuping
在所有智能产品中,按键是最为常用的,所以按键程序的好坏很重要。以前我们在学校里学的按键检测方法都是不适用,很浪费时间,减少了CPU的效率。在大家的不断努力下,基本上编程都是用状态转移思想。用状态机思想编的按键检测程序也很多,像一些高手就写过很好的按键检测程序,下面我总结了用一个定时器的独立按键检测,能检测单击,连击,长击等。这个按键检测程序中包含了状态转移思想,时间片段思想,对象编程。
关于按键流程图我就不说了,这个大家都清楚。首先对按键进行一个数据结构定义。
struct KeyData
{
unsigned char KeyValue; //按键返回值
unsigned char LongClickFlag; //长击标志
unsigned char RunClickFlag; //连击标志
unsigned char JudgeRunFlag; //软定时器的连击标志
unsigned int RunCount; //连击间隔时间计数
};
//按键常量的定义,根据具体的时钟来跟改
#define KeyLongTime_2s 100 // 1/Fcy*timerValue*1000
#define KeyRunClick_500Ms 40 //连击间隔时间大约是500ms左右
这个数据结构是把按键当成一个对象,这样每次按键按下后都会有一个返回值,其他标志都是按键需要其他功能功能。不管有几个独立按键,每个按键返回值都不同。需要几个按键时,就定义几个按键关键字。
//------------接口区
#define KeyOne P1_0
#define KeyTwo P1_1
//----------初始状态
#define IdleState 1
这里我只用了两个按键,其实本按键最为不好的地方就是按键检测和读取按键的值,每次移植时都需要修改,这里还需要修改,在读取按键值时,我可以采用关键字固定,是来确定按键返回值固定这种方式,但是我觉得没有必要,移植时就改吧,有的单片机直接支持位操作,有的不支持。
//****************************************************************
//键盘扫描接口部分,移植时需要修改
//****************************************************************
unsigned char KeySwap()
{
if((KeyOne!=IdleState)||(KeyTwo!=IdleState))//按键数目判断状态,这里是位变量
return 1;
else
return 0;
}
//***************************************************************
//读取接口值函数,移植时需要修改
//********************************************************************
unsigned char RdKeyValue()
{
if(KeyOne!=IdleState)
return 0x02;
if(KeyTwo!=IdleState)
return 0x03;
else
return 0;
}
下面来看看我主要的按键检测部分,也是最重要的部分,我在定时器内定义每隔12ms就去扫描一次按键,这样每个状态间隔的时间就是12ms,我就是利用这个时间来去抖动,我也是利用这个时间来进行连击和长击的判断。不说了,看程序吧
/********************************************************************
基于软定时器的通用模块的建立之一:独立按键通用模块
创建人 :侯祖平
创建日期:2010.7.01
博客网址:
欢迎大家到我的博客交流..........
*********************************************************************
#include "EvenKey.h"
#include "KeyBasic.h"
//********************************************************************
//状态变量
//********************************************************************
enum
{
KeyIdle = 0,
KeyDownDelay, //判断按键按下
KeyWait, //按下等待每隔一个软定时器,变量就加一
Key, //长击和短击判断
KeyLongClick, //若按下时间超出,则为长按
KeyShortClick, //
KeyRunClick, //连击,可以用一个变量来记录连击的次数
KeyUp, //按键抬起
KeyUpDelay, //长击按下延时
KeyOver
};
//********************************************************************
//键值扫描循环
//********************************************************************
void KeyLoop(struct KeyData *KeyNumber)
{
static unsigned char KeyCurValue = 0 , KeyLastValue = 0;
static unsigned char KeyState = 0;
static unsigned int LongCount = 0;
switch(KeyState)
{
case KeyIdle :
if(KeySwap())
{
KeyState = KeyDownDelay;
}
else
{
KeyState = KeyIdle;
}
break;
case KeyDownDelay :
if(KeySwap())
{
KeyState = KeyWait;
}
else
{
KeyState = KeyIdle;
}
break;
case KeyWait : //有键按下,看是否是连击
KeyCurValue = RdKeyValue();
if((KeyCurValue==KeyLastValue)&&(KeyNumber->JudgeRunFlag))
{ //
if(KeyNumber->RunCount < KeyRunClick_500Ms)
{ //则为连击
KeyNumber->RunCount = 0;
KeyNumber->JudgeRunFlag = 0;
KeyState = KeyRunClick; //跳转到连击状态
}
else
{
KeyNumber->RunClickFlag = 0;
KeyNumber->RunCount = 0;
KeyNumber->JudgeRunFlag = 0;
KeyState = Key; //若不是继续判断
}
}
else
{
KeyState = Key;
}
break;
case Key :
if(KeySwap())
{
KeyCurValue = RdKeyValue();
LongCount ++;
if(LongCount >= KeyLongTime_2s)
{
LongCount = 0;
KeyState = KeyLongClick;
}
}
else
{
if(LongCount < KeyLongTime_2s)
{
LongCount = 0;
KeyState = KeyShortClick;
}
}
break;
case KeyLongClick : //长击
#ifdef HaveLongKey
KeyNumber->LongClickFlag = 1;
KeyNumber->KeyValue = KeyCurValue|0x80;
#endif
if(!KeySwap()) //若按键抬起,则状态跳转
KeyState = KeyUpDelay;
break;
case KeyRunClick : //连击
#ifdef HaveRunKey
KeyNumber->RunClickFlag = 1;
KeyNumber->KeyValue = KeyCurValue|0x40;
#endif
if(!KeySwap()) //若按键抬起,则状态跳转
KeyState = KeyUpDelay;
break;
case KeyShortClick : //短击和连击的判断
KeyNumber->JudgeRunFlag = 1; //连击判断启动标志位
KeyNumber->KeyValue = KeyCurValue;
KeyState = KeyUp;
break;
case KeyUp :
KeyNumber->RunClickFlag = 0;
KeyNumber->LongClickFlag = 0;
KeyState = KeyOver;
break;
case KeyUpDelay:
if(!KeySwap()) //若按键抬起,则状态跳转
KeyState = KeyOver;
break;
case KeyOver : //结束
KeyLastValue = KeyCurValue;
KeyState = KeyIdle;
break;
}
}
//********************************************************************
//end of file
//********************************************************************
其中连击的判断也是我写这个按键检测的真正目的,以前我都是用两个定时器来判断连击的,我在以前的程序添加了一个程序。
//****************************************************************
//参数传递函数,用于传递连击的时间间隔
//****************************************************************
void WrRunTime(struct KeyData *KeyNumber)
{
if(KeyNumber->JudgeRunFlag)
{
KeyNumber->RunCount ++; //间隔计数启动
if(KeyNumber->RunCount >= KeyRunClick_500Ms)
KeyNumber->JudgeRunFlag = 0; //若大于等待间隔,自动关闭标志
}
else
{
KeyNumber->RunCount = 0;
}
}
void WrRunTime(struct KeyData *KeyNumber)这个函数放在定时器中断函数中,在一次短击过后,程序会启动连击检测标志KeyNumber->JudgeRunFlag = 1; //连击判断启动标志位
在中断函数中,若标志KeyNumber->JudgeRunFlag = 1则启动计时。
//********************************************************************
//中断函数非常重要,一切以中断函数为参考
//********************************************************************
void Timer0Interrupt(void) interrupt 1
{
TH0 = (65536-22000)/256; //12MS
TL0 = (65536-22000)%256;
Run = 1; //时间片段标志位
WrRunTime(&KeyNumber); //连击时间判断
}
一旦检测到if(KeyNumber->RunCount >= KeyRunClick_500Ms)
则连击检测自动关闭。
我的长击和连击判断后并没有在返回值中加什么东西,只有返回了一个标志位。
所以在测试程序中,你需要写看标志是否至1。
你的测试程序中需要写一下函数:
struct KeyData KeyNumber;
//时间片段标志位
unsigned char Run = 0;
void TimerInit()
{
TMOD = 0x01;
TH0 = (65536-22000)/256; //12MS
TL0 = (65536-22000)%256;
ET0 = 1;
EA = 1;
TR0 = 1;
}
//测试
void Loop_f(unsigned char KeyValue)
{
switch(KeyValue)
{
}
}
//test
//test
void main()
{
TimerInit();
while(1)
{
if(Run)
{
Run = 0;
KeyLoop(&KeyNumber);
Loop_f(KeyNumber.KeyValue);
}
}
}