1、独立按键的一般性功能要求
对于独立按键我们一般要求功能 比较简单,能识别按键按下,不考虑识别多个按键同时按下,按下按键一次执行一次操作。不需要识别长按。
2、需要处理的内容
独立按键一般都连接一个上拉电阻,当按键没有按下的时候 GPIO 读到的是高电平 当有按键按下的时候 读到的是低电平。那么我们都知道 机械按键有一个抖动问题,在这里需要 5ms 左右的时间来消抖。然后等待按键松开 ,按键松开后执行相应的操作。
3、一般的识别程序
在 51或者AVR 等8 位机时代,我常常采用一种按键检测方法,一般步骤如下:
A:检测按键是否按下------是否有低电平在 GPIO上出现 如果没有 return 0;如果有进入 B;
B:软件延时(for循环)5ms左右 进入C;
C:检测GPIO 是否有低电平----是否有按键按下 如果没有 ---- 是抖动 return0 ;如果有低电平,则有按键被按下 进入 D;
D:检测是哪一个按键被按下,保存对应的编码到变量 key,然后进入E;
E:等待按键松开 (while---GPIO 是高电平),如果没有这一步 将造成 一次按下 检测到很多次的情况(这是我们不希望看到的);
F:如果E松开 则 return key ; 返回检测到按键值 ;
这就是一个完整的按键检测的过程,包含消抖和等待松开;
4、ARM时代遇到的尴尬
如3所说的检测方法固然简单,然而这里面有几个东西需要注意 ;第一 5ms 的延时 将极大的浪费 ARM的时间资源。 第二 while 等待松手 这将导致很多问题 如浪费 ARM时间资源,给其他处理带来问题等。虽然在8bit MCU时代,5ms 和while 貌似没关系,但是在 100MHz 主频 32bit 的ARM时代,这样的资源浪费是巨大的,将限制ARM的处理能力,拖慢整个系统。
5、适合于ARM使用的检测方法的提出
我们看下面的图(虽然很丑哈,凑合着看):
一个标准的按键按下 ,就像 蓝色的线条一样,按下开始 和结束都充满着抖动。那么 我们能否想一个方法检测红色线条呢;
很显然我们只需要 首先检测到 红色的高电平(红色虚线前面)然后 在虚线处检测到低电平,然后 我们延时 T (5ms),这样我们在检测 GPIO,是不是就相当于检测的是红色的线。
我们将按键 检测变成一个 有延时 消抖功能的 边沿检测,这样既保证消抖 又保证 按下一次只动作一次 无需 while等待。这样讲极大的释放我们的ARM资源。
我们这里虽然还行需要 5ms 延时 ,但是 我们的延时采用 系统节拍定时器 来做,而且整个检测算法采用状态机的方式,这样我们的检测过程就变成如下所示了:
A:检测GPIO是否是高电平,如果是保存状态和数据,检测函数退出;
B:下一个循环在检测GPIO是否是高电平 如果是 同 A ,如果不是 说明检测到了 红色的虚线,转移状态并保存数据;启用 延时;函数推出;
C:延时是否到达 如果没有 函数推出;
D:延时函数到达,检测GPIO是否是低电平,如果不是 那么是错误触发,转移状态到初始状态,如果是低电平 说明有按键按下,处理数据准备输出 return key ,将上上一个状态数据转移;函数退出;
E:接续检测,如果在底部横线间检测到 2个低电平,那么由于没有初始状态的 高电平,将不会输出,因此也不会重复输出造成输出多次的现象。
整个检测过程,没有一个需要函数内部 while 和for 循环的,这将极大的提高ARM的运行效率。
附录:示例代码: //GPIO3-11 to 14 是4个按键,SysMs是系统节拍定时器定时变量 单位是 1ms;SYSMSMAX宏 系统节拍定时器 的最大数值。 /**************************************************************** *名称:uint32_t ReadKey(void) *参数:NULL *功能:返回按键 key1--0x01 key2--0x02 key3--0x04 key4--0x08 *****************************************************************/ uint32_t ReadKey(void) { static uint32_t LastLastKey=0x0000000f; static uint32_t LastKey=0x0000000f; static uint32_t CountMs=SYSMSMAX; static uint32_t StartSign=0; uint32_t Key=0,KeyOut=0; Key=((LPC_GPIO_PORT->PIN[3])>>11)&0x0000000f; if(0==StartSign) { if((Key!=0x0000000f)&&(0x0000000f==LastLastKey)) //第一次检测到有低电平 保存并延时 { LastKey=Key; CountMs=SysMs; StartSign=1; } else { LastLastKey=Key; } } if(1==StartSign) //判断延时是否到时间 { if(SysMs>CountMs) { if((SysMs-CountMs)>5) //判断延时 5ms 是否到达 { Key=((LPC_GPIO_PORT->PIN[3])>>11)&0x0000000f; //读本次数据 KeyOut=LastLastKey&(~Key)&(~LastKey)&0x0000000f; //两次都是低电平 则有输出高电平 LastLastKey=0; StartSign=0; //翻转状态标志位 } } else { if((SYSMSMAX+SysMs-CountMs)>5) //计时正好走到 翻越的地方 的处理 { Key=((LPC_GPIO_PORT->PIN[3])>>11)&0x0000000f; KeyOut=LastLastKey&(~Key)&(~LastKey)&0x0000000f; LastLastKey=0; StartSign=0; } } } return KeyOut; }