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


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

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

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

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

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

         1.有按下的动作。
         2.有一定的保持时间。
         3.有弹起的动作。
    
    • 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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

      这时在主程序中只需要查询按键有效标志为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;
    	}
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

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

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

  • 相关阅读:
    ES6 Promise的使用详解
    Redis-双写一致性
    双十一什么数码好物值得买?双十一最值得买的数码好物分享
    【力扣-每日一题】LCP 06. 拿硬币
    一招解决所有依赖冲突
    WEB端显示三维地形模型
    麒麟v10操作系统 安装docker
    [附源码]计算机毕业设计基于SpringBoot的党务管理系统
    华为配置蓝牙终端定位实验
    KeyDB源码解析二——线程安全
  • 原文地址:https://blog.csdn.net/qq_20222919/article/details/127591233