引言
在嵌入式系统中,按键消抖是一个常见问题,延时消抖方法是教科书上常提到的一种按键消抖方法。延时消抖方法的原理是通过调用一个延时程序跳过抖动态,从而达到消抖的目的。这种方法有两个致命的缺点:一是延时期间CPU一直被延时程序占用,长达几十ms,这对于实时性要求很高的嵌入式系统来说无异于死机;二是延时程序通过指令周期的累加来实现延时,而不同系统的指令周期是不同的,所以该方法不具备可移植性。
为了克服延时消抖的缺点,有人提出了定时器中断消抖方法,该方法的原理和延时消抖其实是一样的,也是通过延时跳过抖动态,区别在于使用定时器取代延时程序。虽然通过定时器延时解决了实时性的问题,但是定时器的配置和使用在不同系统中是不同的,所以依然没有解决可移植性的问题。
本文提出了一种新的按键消抖方法,与上述两种方法不同,该方法不是机械地跳过抖动态,而是设法识别按键的抖动态和稳定态,从而避开抖动影响。因为该方法的核心是通过一个累乘算法来识别抖动态和稳定态,所以将该方法命名为累乘消抖方法。
1按键信号分析
按键是嵌入式系统中常用的输入单元,常见的电路形式如图1所示。
按下按键SW,按键值(即keyin端电平)由高变低;松开按键, keyin端电平由低变高,即在keyin端出现一个负脉冲,如图2所示。
图1 按键电路单元
图2 理想的按键信号
这样,系统通过读取keyin端电平来识别按键状态,一个负脉冲代表一次按键。然而,实际中,在按下按键和松开按键的瞬间,由于触点的弹性作用而引发电信号不稳定,导致在这两个瞬间都会有非常短暂(10 ms左右,取决于按键的机电特性)的抖动毛刺存在,如图3所示(为便于讨论,将按键的状态分为前沿抖动态、稳定按下态、后沿抖动态和稳定松开态)。
图3 实际的按键信号
尽管抖动时间非常短暂,但是对于指令周期ns级的微控制器来说还是漫长的,以至于在微控制器看来就不是一个负脉冲,而是很多个负脉冲,从而导致一次按键被误认为多次按键。按键消抖就是为了消除抖动影响,对于一次按键,只让系统看作一次。延时消抖的原理是通过延时程序跳过抖动态,本文介绍的累乘消抖方法的原理是有效地识别当前按键所处状态,然后根据不同的按键状态做相应的处理。
2累乘消抖方法
2.1抖动态和稳定态的区别
累乘消抖方法的关键是识别抖动态和稳定态的区别。由图3可知,抖动态是频繁的1、0跳变;稳定态或者一直为0(稳定按下态),或者一直为1(稳定松开态)。抖动态的特征是变化,稳定态的特征是不变,基于这点,只要设计出一个能够区别变化量和不变量的算法,就可以识别出抖动态和稳定态。
如图3所示,在一次按键过程中,按键依次经历了前沿抖动态、稳定按下态、后沿抖动态和稳定松开态。其中,前沿抖动态是变化的,期间按键值keyin不会一直为0,会有若干次跳变回1;稳定按下态是不变的,期间按键值一直为0;后沿抖动态是变化的,期间按键值不会一直为1,会有若干次跳变为0;稳定松开态是不变的,期间按键值一直为1。
两种“变化”都表现为“不一直”,两种“不变”都表现为“一直”。按键状态分析见表1。
设计一种算法,如果能够区别“不一直”和“一直”,那么问题便迎刃而解。借助逻辑乘运算的“全真才真,有假则假”的特点可以轻松解决这个问题。
2.2累乘算法
在等待按键按下时,如果检测到有低电平,则紧接着连续读取按键信号N次。如果处于刚按下时的抖动态,这N次读取可能发生在毛刺的底部(低电平),也可能发生在毛刺的顶部(高电平),当N不是太小时,能够保证其中至少会有一次发生在毛刺的顶部。如果对这N次按键值是否为0进行累计逻辑乘操作,那么结果为假。如果是处于稳定的按下态,这N次读取都为低电平,则对这N次按键值是否为0进行累计逻辑乘操作,结果为真。
在等待按键松开时,如果检测到有高电平,紧接着连续读取按键信号N次。如果处于刚松开时的抖动态,这N次读取可能发生在毛刺的顶部(高电平),也可能发生在毛刺的底部(低电平),当N不是太小时,能够保证其中至少会有一次发生在毛刺的底部。如果对这N次按键值是否为1进行累计逻辑乘操作,则结果为假。如果是处于稳定松开态,这N次读取都为高电平,对这N次按键值是否为1进行累计逻辑乘操作,结果为真。
至此可以给出一个算法,在N次循环中,将按键值与期望值进行比较,将它们是否与期望值相等的逻辑结果用逻辑乘运算作累计,根据累计值来判别按键状态,鉴于累计、逻辑乘这两个关键要素,将此算法命名为累乘算法,描述如下:
bool Andup=true;int n=0;
Andup=Andup&&(keyin==期望值);//等待按键按下时,期望值为0;等待按键松开时,期望值为1
while(Andup&&n++<COUNT){
//如果出现期望值,进入循环累乘体,判断是抖动态还是稳定态
Andup=Andup&&(keyin==期望值);
}
if (Andup){
为稳定态,做相应处理;
}
算法分析:从循环条件可以看出,对于抖动态,是不会将循环执行到底的,在第一次没有读到期望值时,就否定了循环条件。对于稳定态,一直读到的都是期望值,退出循环的原因是计数次数已到。退出循环后的累计值Andup若为假,即为抖动态,若为真,即为稳定态。
2.3累乘消抖方法
基于累乘算法识别按键状态,建立的按键消抖程序如下:
bool KeyScan(){
static bool Pressed=false;
bool Fallingede=false;
bool Andup=true
int n=0;
if (Pressed==false){//查询是否按下
Andup=Andup&&(keyin==0);
while(Andup&&n++<COUNT){
Andup=Andup&&(keyin==0);
}
if (Andup){
Pressed=true;
Fallingedge=true;
}
}
else{//查询是否松开
Andup=Andup&&(keyin==1);
while(Andup&& n++<COUNT){
Andup=Andup&&(keyin==1);
}
if (Andup){
Pressed=false;
}
}
return Fallingedge;
}
程序分析:静态布尔变量Pressed记录按键的状态,假表示松开,真表示按下。当按键处于稳定按下态时,返回真,其他状态返回假。通过一个不超过COUNT次的循环检测来判别抖动态和稳定态,尚未达到COUNT次但因为Andup为假而退出循环的是抖动态;达到了COUNT次而退出循环的是稳定态,此时Andup为真。
循环次数COUNT的设置很重要,COUNT值太小,可能导致判断不准确;取值太大,则会浪费。浪费只会发生在稳定态,在抖动态是不存在浪费的,因为一旦没有读到期望值就导致Andup值为假,从而否定了循环条件。只有在稳定态,直到COUNT次才退出循环,总共循环COUNT次。COUNT次循环相对于延时消抖里的几十ms来说,可以说是完全释放了CPU,大大提高了系统的实时性。另外,该方法不涉及指令周期和定时器等底层硬件,所以完全具备可移植性。
3实验与分析
本文以Tiva C LaunchPad为实验平台验证累乘消抖方法的可行性和实时性。在将算法实例化之前,首先要考虑的就是COUNT的取值问题,因为COUNT的取值决定了算法的可行性和实时性。
3.1COUNT取值方法
COUNT取值太小的话,不能保证在次数很少的循环累乘中一定能读到抖动态的某次逆跳变,所谓逆跳变指前沿抖动态的跳回1,以及后沿抖动态的跳回0。可能出现这样的情况,前沿抖动时恰巧每次读取都发生在0处,后沿抖动时恰巧每次读取都发生在1处。这样就导致把抖动态误认为稳定态,从而使算法失去了可行性。COUNT取值太大的话,又会在稳定态的循环累乘中浪费大量CPU时间,从而使算法损失了实时性。
实验中,依次取COUNT值为20、10、2。同时,为了记录每次循环累乘的执行情况,在循环累乘的一开始添加一条语句,将循环计数器n通过串行口输出到上位机的串口终端程序,这样便可以清楚地看到每次循环累乘都执行了几次。不到COUNT次的是抖动态,到了COUNT次的是稳定态。特别要注意每次抖动态的循环次数,目的是了解大概几次就可以读到逆跳变。这为尽量缩小COUNT提供了依据,缩小COUNT就等于提高实时性。
当COUNT=20时,按键灵敏,证明可行,其中随机一次按键,串口记录的执行情况见表2。
第1次循环累乘,循环体执行了2次,在第2次时读到了逆跳变,说明是抖动。第2次循环累乘,循环体执行了3次,在第3次时读到了逆跳变,说明是抖动。第3次循环累乘,循环体执行了20次,一直没有读到逆跳变,说明是稳定态。第4次循环累乘,循环体执行了2次,在第2次时读到了逆跳变,说明是抖动。第5次循环累乘,循环体执行了3次,在第3次时读到了逆跳变,说明是抖动。第6次循环累乘,循环体执行了2次,在第2次时读到了逆跳变,说明是抖动。第7次循环累乘,循环体执行了20次,一直没有读到逆跳变,说明是稳定态。对于本次按键,抖动态的循环累乘次数为2~3次。
当COUNT=10时,按键灵敏,证明可行。其中随机一次按键,串口记录情况见表3。
对于本次按键,抖动态的循环累乘次数也为2~3次。
当COUNT=2时,按键不灵,说明不可行。因为两次循环不足以保证在抖动态期间一定有一次能读到逆跳变,以至于将抖动态误认为稳定态,从而导致一次按键被误认为多次。
多做几次实验之后发现,每次抖动态的循环累乘最多需要三次就可以读到逆跳变,所以COUNT值要大于3,为了留有一定冗余,取COUNT值为10即可。限于篇幅不能把每次实验结果都罗列出来。
3.2评估
累乘消抖程序总是在执行几次循环后就交出执行权,最多的循环次数也就是COUNT次,相比较延时消抖中的百万次循环(将ns级的指令周期累加到ms级,需要执行百万次循环)来说大大提高了实时性。另外,累乘消抖程序并不涉及指令周期和定时器等底层硬件具有可移植性。
结语
使用累乘消抖方法建立的消抖程序在识别出按键的状态后立刻返回,交出CPU使用权。区别是不同的返回代码反映了不同的按键状态,如果是抖动态则不予处理,如果是稳定态则给予相应处理。如果把按键消抖比作家长吩咐正在看电视的小孩看着烧水的水壶,抖动态好比水正在烧,稳定态好比水已烧开。那么,延时消抖就好比一个笨小孩,离开了电视机,一直盯着水壶,直到水开。
累乘消抖则好比一个聪明的小孩,没有停止看电视,只是定期地看一眼水壶的状态,如果水没开,继续看电视,如果水开了就告知家长。通过这个比喻可以形象地看到累乘消抖具有很强的实时性。另外,累乘消抖不涉及指令周期和定时器等底层硬件,所以又具备可移植性。虽然为了达到最理想的实时性,要通过实验确定当前系统最适合的COUNT值,但如果要求不是过于严苛,直接选用COUNT为100即可。