由一个按键程序引发的思考(中)

55 阅读7分钟

“ 本文正在参加「金石计划 . 瓜分6万现金大奖」 ”

  书接上回,上回书 由一个按键程序引发的思考(上) 中写道,按键检测既不能在按下的时候判断,又不能在弹起的时候判断。那要如何快速的检测到按键,又不会误判呢?

  下面从按键的波形开始分析。 在这里插入图片描述

  按键的波形无非就是高、低、高三种状态。要判断按键从根本上来说就是对这三种状态的检测和分析。由于按下未按下时和弹起时都是高电平,那么直接通过电平判断不能有效区分这两个高电平的状态,就不能直接同电平来判断。观察按键波形可以发现,按键按下的一瞬间电平出现了下降沿,按键弹起的一瞬间出现了上升沿。这样通过上升沿和下降沿就能区分出按键按下或者弹起。

  下面要解决的问题就是,如何有效的去界定按键按下了,然后去执行相应的动作,同时只能执行一次。不能重复执行。 那就得把按键的动作分解开来看。按键的动作其实可以分为三部分: 按下、保持、弹起。 在这里插入图片描述   当按键按下后保持一定的时间之后,就可认为按键被有效的按下了,那么此时就可以去执行按键的动作了。当按键动作执行完成之后,发现按键没有弹起,那么此时就不进行第二次按键检测。这样不就可以有效的识别到按键了,同时根据有效时间的长短来滤除那些按键时间很短的情况。

在这里插入图片描述   一个有效的按键必须满足三个条件:

     1.有按下的动作。
     2.有一定的保持时间。
     3.有弹起的动作。

  只有同时满足这三个条件,才认为是一次有效的按键。在上图中可以看到通过有效时间的控制,可以很快的检测到按键,同时也能将第二种低电平持续很短时间的按键排除掉,当按键按下很长时间时,不需要等到按键弹起,也能及时响应按键动作。

  检测按键的流程就可以修改为: 在这里插入图片描述

  当检测到按键有下降沿同时持续一定时间后就认为按键有效,立即去执行按键动作,同时继续监测按键是否弹起,当出现上升沿之后,就结束本次按键检测,执行下一次按键检测。这样如果要实现按键按下一次,LED灯翻转一次。这种效果时,按键和LED灯的波形如下。

在这里插入图片描述   第一次为正常按键,LED状态翻转。第二次按键时间太短,LED状态不变。第三次为长按键,LED状态再次翻转。通过这样的方法,按键动作可以及时响应,同时又不会误判。这种按键效果不管用户如何按键,那么产品的实现效果都是一样的。

  下面就要面临一个新的问题,这种按键状态要如何去检测呢?既要实时检测按键,同时还不能影响主程序其他代码的运行。这不得像操作系统一样,并行的去执行吗?按键检测和主程序户可以同时取运行?难道实现一个简单的按键还要给单片机上个操作系统?这岂不是大材小用了?

  肯定不能搞个系统去检测按键,但是可以借鉴操作系统的思路。操作系统的并行处理任务,本质上还是串行执行的,只不过它是每个任务执行一定时间之后,立即去执行下一个任务,然后一定时间之后又去执行下一个任务。一个任务是分好多时间段去执行的。当系统的速度非常快,每个任务的执行时间段非常短的时候,看起来就行并行执行一样。就像LED灯的亮灭速度非常快的时候,就感觉灯是一直亮着的。单片机中也有定时器功能,虽然它的速度不是很快,但是检测一个按键的状态是足够了。我们使用定时器的定时中断功能,每隔一段时间去检测一下按键的电平状态,同时将各种状态和电平持续的时间记录下来。最后根据存储的按键状态去和时间去判断按键是否按下或者弹起。

  那么需要记录的数据就有上升沿、下降沿、低电平持续时间。在单片机中设置一个定时器10ms中断一次去读取按键的状态。为什么是10ms呢?因为通常按键的滤波时间都为10ms,这样每10ms去检测一次的话,刚好就相当于实现了软件滤波。每次读取到的按键状态都认为是有效的。如果上一次检测到的电平状态是高,本次检测到的电平状态是低,那么就说明出现了下降沿,同时开始给低电平计时,当低电平时间超过设置的按键有效时间之后,标记按键成功按下标志。当下一次检测到高电平的时候,说明出现了上升沿,本次按键检测结束,置按键结束标志位。主程序检测到按键结束标志之后,才开始进行下一次按键检测。

  检测流程如下:

在这里插入图片描述   这段按键状态检测的函数在定时器中断种调用,每10ms被调用一次。代码实现如下:

char last_state = 1,now_state = 1;		//上次电平状态和本次电平状态,默认为高电平。
char falling_flag = 0,rising_flag = 0;  //下降沿 和上升沿 标记
int low_time_cnt = 0;					//低电平计时
char key_ok  = 0, key_end = 0;			//按键按下有效标志,按键结束标志
void key_check(void)
{
	now_state =  key;					//存储按键当前电平值
	if(now_state  != last_state)		//电平出现了变化
	{
		last_state = now_state;			//本次状态赋给上次状态
		if(now_state  == 0)				//下降沿
		 {
		 	falling_flag = 1;
		}
		else							//上升沿			
		{
			rising_flag = 1;
			key_end  = 1;				//标记按键结束
		}
	}
	if((now_state  == 0) &&(falling_flag == 1))			//出现过下降沿且为低电平 低电平计时
	{
		low_time_cnt++; 
		if(low_time_cnt > 10)		//每10ms被调用一次,计数10次,说明低电平持续时间超过了100ms,此时标记按键有效,可以执行按键动作。
		{
			key_ok  = 1;
		}
	}
}

  这时在主程序中只需要查询按键有效标志为1时,就可以执行按键动作了。当按键结束后,清除所有标志位,开始下一次按键检测。主函数代码如下:

__interrupt void Timer4_Handle( void )         //10ms 定时中断
{
    TIM4_SR = ( ~0x01 );                        //清除更新中断标志
	key_check();								//检测按键状态
}

void main(void)
{
  while(1)
  {
	if( key_ok == 1 )		//	如果按键有效
	{
	    key_ok  = 0;		//清除标志位
		LED = !LED;			//翻转LED 
	}
   if(key_end == 1)			//按键结束后复位所有标志位
   {
		last_state = 1;
		now_state = 1;
		falling_flag = 0;
		rising_flag = 0;
		low_time_cnt = 0;
		key_end = 0;
	}
  }
}

  在定时器中10ms去检测一次按键的状态,主程序不需要知道按键当前处于哪种状态,只要按键有效标志位为1时,就说明按键被按下了100ms,此时直接翻转LED灯,并将标志位清0.由于按键没有弹起,所有不可能再出现下降沿,这个标志位就会一直为0,直到下次按键再次按下为止。所以按键不管是短按一下,还是长按,只要有下降沿,同时低电平持续时间大于100ms,按键动作就会立马得到响应。不会误动作,也不会反应很迟钝。当按键弹起之后,将所有标志位清除,等待下一次按键。

  那这种方法就是完美的吗?就能适应各种按键场合吗?这不一定,程序永远都不会有完美的,永远有它的局限性,只可能尽量去适应更多场合,提高它的鲁棒性。假如现在有这种按键需求,一个按键需要支持短按、双击、长按三种状态。那么上面的情况还适用吗? 在这里插入图片描述   通过按键波形可以看出,上面的那种按键方法根本不能有效区分这三种按键方式。那么对于这种一个按键有多种模式的又要去怎么处理呢?要知后事如何,且听下回分解。 由一个按键程序引发的思考(下)