引言
在单片机控制系统中,对按键操作进行去抖动处理,是整个系统设计中必须考虑的一个重要问题。目前常用的去键抖动设计方法与对应的硬件环境结合较为紧密,考虑到单片机硬件种类繁多,此实现方法可移植性差。文章通过使用定时器中断,在软件程序中实现了对按键抖动的处理,程序代码可以在不同的单片机硬件环境中快速移植,缩短了项目开发的周期,增加项目的灵活性。
1按键抖动的原理描述

图1 按键电压抖动波形
单片机应用系统中的按键通常为机械弹性开关,由于机械触点的弹性作用,其在实际的闭合及断开瞬间,常常会产生如图1所示的一连串电压的抖动[1]。对按键处理的重要环节就是去抖动,即去除键按下和抬起瞬间的电压抖动问题。如果对抖动不做任何处理,则会引起程序的误操作,对整个系统产生较大的负面影响。
对于常用的8051单片机,如果使用的是下降沿触发中断的工作方式,则在电压低于0.7 V时会产生一次触发。在图1中,如果A点的电压高于0.7 V,则在键按下的过程中会触发单片机产生两次中断。在键释放的阶段,如果B点的电压高于0.7 V,而C点电压低于0.7 V,同样会触发单片机产生两次中断。按键抖动的时间长短与按键的机械特性、操作人员的按键动作和习惯有关,一般而言,认为抖动的时间大概为5~10 ms。按键抖动会导致一次按键被误判为多次,为了确保对按键动作的正确处理,必须去除按键抖动。
2常用的去键抖动硬件和软件消除方法
按键抖动的硬件消除方法有很多,如RS去抖、积分型去抖、翻转式去抖等[2],其设计原理在大量论文及书籍中均有介绍,这里不再赘述。硬件实现的方法只适合按键数目比较少、对项目要求比较高的情况。在实际的项目中,考虑到电路的复杂程度以及成本、体积等问题,常用的方法是软件去抖动[3],其思路是:在按键处理程序中首先执行一个延时函数产生5~10 ms的延时,在前沿抖动消失后检测按键的状态,如果保持闭合状态电平,则认为真正有按键被按下。当检测到按键释放后同样进行延时处理,让后沿抖动消失后才转入该键的处理代码。以外部中断的按键为例,代码的一种C语言实现如下[45]:
void ex0()interrupt 0{ //外部中断按键处理程序
DelayTime();//程序处理
……
}
voidDelayTime(){ //延时子函数
int i;
for( i=0; i<10000; i++);//空操作,在12 MHz的晶振频率下,耗时大约10 ms
}
采用以上软件去抖动的方法存在以下两个问题:
① 在代码实现中,延时子函数空循环,消耗了单片机CPU的时间,造成资源的浪费,程序的效率不高。
② 代码的可移植性不高。按键抖动的时间取决于按键的机械特性,按键开关的抖动波形、抖动次数、抖动时间都是随机的,不同的按键抖动时间不同;不同的操作者按键动作习惯也会导致抖动的时间不同。综合考虑以上因素,如果单片机系统的硬件有变化,或者代码有移植的需求,则在新的硬件环境中必须对延时子函数进行调试,找出适合该硬件系统的延时时间,而这将增加程序调试的时间成本,不利于在不同硬件系统中进行快速移植。
3去键抖动程序的可移植性设计

图2 程序流程图
结合系统设计时对正常按键频率的考虑,比如最快0.5 s按键一次,可以在程序中使用定时器来控制按键抖动。具体的思路是:在软件中定义一个全局变量,设定其初始值为0。当按键中断处理程序被调用时,首先读取该全局变量的值。如果变量值为0,即此时是第一次检测按键抖动,将该变量置为1,同时启动定时器并进行按键事件的处理;如果由于按键抖动导致按键中断处理程序被重复调用时,在该全局变量的值为1的情况下就跳过按键事件处理代码。另一方面,在定时器的中断处理程序中,经过设定的时间后将该全局变量重置为0,保证程序对下一次按键的正确处理。程序的流程图如图2所示。
采取以上处理方法,虽然按键抖动会导致中断处理程序被反复调用,但在定时器设定的时间内,中断程序实质上只处理第一次按键事件,避免了对按键事件的重复处理以及使用延时子函数造成的空循环,提高了CPU的执行效率。当工程的硬件环境有变动时,根据项目对按键使用的要求,开发人员只需根据具体情况更改定时器的预设值即可,程序的其他部分保持不变,这样就提高了程序的可移植性。程序框架的一种C语言实现如下:
main(){//主函数
……
TH0=0xE0;//设置定时器的初始值
TL0=0xB1;
int IsSet=0; //设置全局变量的初始值
……
}
void ex0()interrupt 0{ //外部中断按键处理程序
if( 0==IsSet){//等于0则处理,否则跳过处理代码
IsSet=1;
TR1=0; //启动定时器程序处理
……
}
}
void Timer0() interrupt 1{ //定时器中断处理程序
if(count < 8){//在12 MHz的晶振频率下,大约定时0.5 s
count++;
}
else{//定时器超时,重置变量并关闭定时器
IsSet=0;
TR0=0; //关闭定时器
}
}
结语
笔者在工程项目中验证了该设计方法实际可行,具有较好的灵活性和可移植性,对于类似的单片机去抖程序的开发具有一定的借鉴意义。