按键状态机(实现单击,长按,双击)的模块分享

目录

一、相关说明

二、分析

三、模块代码

三、代码讲解

四、作者的话


一、相关说明

        1.需要的资源:一个定时器,一个按键。

        2.相关设置:利用定时器计时中断,10ms进行一次按键扫描。

        3.使用说明:定时器中断的优先级要设置高一点,相关的宏定义可以自行定义。

        4.实现功能:区分单个按键的单击,双击,长按。

        5.规定:双击:2次按下的间隔不超过200ms属于双击。

                      单击:第一次按下持续时间小于1s属于单击。

                      长按:第一次按下持续时间不小于1s属于长按。

        (时间长短可自己调整)

        6.目标:帮助理解按键的状态机。

二、分析

        1.时间线分析和状态概览

        2.状态分析和编程思路

         说明:一般按键有两种状态,按下和弹起,这里将按键按下的状态拆分为两种状态,以长按1s的标志触发为断点,拆分为按键按下到标志触发状态和标志触发到按键弹起的状态。而对于单击和双击可直接理解为按下状态,只是形式上作了拆分。而且按下抖动的状态实际编程中没有编写,有兴趣的小伙伴可以尝试添加。

那为什么要拆分按键按下的状态?
定时器会10ms进行实时检测。
长按标志到时会实时清零,如果不拆分状态只能等待按键弹起进入1状态,但此时长按标志已被清零,系统错乱,误判为单击。
要想长按标志不被清零,就不能在按下状态动态清零,所以只能设计为松手反馈。
而拆分状态后可将1s前要做的事和1s后要做的事拆分开,1s到时即可实时反馈。

三、模块代码

1.相关宏

    #define KEY GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_5)
   
    #define KEY_Up  1         //按键弹起
    #define KEY_DownShake  2  //按下抖动
    #define KEY_Down  3       //按键按下
    #define KEY_wait  4       //等待状态
     
    #define SHORT_KEY 1       //短按反馈
    #define LONG_KEY 2        //长按反馈
    #define DOUBLE_CLICK 3    //双击反馈
    
    #define FALSE 0
    #define TRUE 1  

2.函数主体

/**
  * @brief  KEY_Scan();// 按键检测
  * @note   按键检测,返回单击,双击,长按
  * @param  无
  * @retval 无
  * @author 常州工学院电子协会22级团体
	PS:key_return属于外部全局变量,用于接受反馈信息
  */	
void KEY_Scan(void)
{
	//反馈系统
	static uint8_t  Click_Buf = FALSE;  //第一次弹起标志,用与区分双击的第一次按下和第二次按下
	static uint8_t  KEY_flag= FALSE;//标志触发判断标志位,用于在反馈结束后统一清零
  	static uint8_t  Click   = FALSE;//单击判断标志位
	static uint8_t  Long_Press = FALSE; //长按判断标志位
  	static uint8_t  Double_Click  = FALSE;//双击判断标志位
	
	//计时系统
	//定时器10ms进入一次函数
    static uint8_t  Long_Cnt = 100;//长按计时时长1s
    static uint8_t  Twice_Cnt = 20;//双击间隔计时时长200ms
	Long_Cnt--;
    Twice_Cnt--;
	
	//状态系统
    switch(key_state)
    {
        /*状态1:空闲状态(单击)和按键弹起后(双击)*/
        case KEY_Up:
        {
    	  if(KEY == 0)
		  {
			key_state = KEY_Down;//切换到状态3
			Long_Cnt = 100;//长按计时开始
		  }
          else
          {
            //判断是否为按键弹起状态
            if(Click_Buf == TRUE)
            {
			  //弹起时间超过200ms,双击判定时间失效,且一定不为长按,直接判断为单击
              if(Twice_Cnt<=0)
              {
				KEY_flag = TRUE;
                Click = TRUE;
              }
            }
          }
        break;
    }
    
//    /*状态2:按下抖动(过渡状态)*/
//    case KEY_DownShake:
//    {
//      if(KEY == 0)
//        key_state = KEY_Down;//确认按下,切换到状态3
//      break;
//    } 
    
    /*状态3:按键按下到长按标志触发状态*/
    case KEY_Down:
    {
      if(KEY == 1)
      {
        key_state = KEY_Up;//切换到状态4
        //不是长按操作,则判断是不是双击操作
        if(Long_Press == FALSE)
        {
          //双击检测
          //前面已经单击一次,这次就判断为双击操作
          if(Click_Buf == TRUE)
          {
			KEY_flag = TRUE;
		    Double_Click  = TRUE;
          }
          else
          {
            //这是单击或双击的第一次点击,标志位置1
            Click_Buf = TRUE;
            //双击计时器开始计时
			Twice_Cnt = 20;
          } 
        }
      }
      else
      {
        //长按检测(一直在按下,第一次弹起不会触发)
        if(Long_Press == FALSE&&Click_Buf == FALSE)
        {
		  //1s时间到就判断为长按
          if(Long_Cnt<=0)
          {
			key_state = KEY_wait;//切换到状态4
			KEY_flag = TRUE;
            Long_Press = TRUE;
          }
        }
      }  
      break;  
    }
    
    /*状态4:标志触发到等待按键弹起状态*/
    case KEY_wait:
    {
      if(KEY == 1)
        key_state = KEY_Up;//完成一次按键动作,切换到状态1
      break;
    }
		
    default:
      key_state = KEY_Up;//默认情况都切换到状态1
      break;
  }
    //标志触发,反馈结果
    //PS:key_return属于外部全局变量,用于接受反馈信息
    if(KEY_flag == TRUE)
	{
		//单击动作
		if(Click == TRUE)
		    key_return = SHORT_KEY;
		//长按动作
		else if(Long_Press == TRUE)
			key_return = LONG_KEY;
		//双击动作
		else if(Double_Click == TRUE)
			key_return = DOUBLE_CLICK;
        //按键状态位清零,为下一次按下准备
        KEY_flag= FALSE;
        Click_Buf = FALSE;
        Click = FALSE;
        Long_Press = FALSE;
        Double_Click  = FALSE;
    }
}

三、代码讲解

为了使代码更加清晰,可读性更强,我将按键状态机的代码做了分区,分成了三个系统,分别是反馈系统,计时系统,状态系统。

理解要点:梳理好状态之间的切换关系,特别关注按键按下和弹起在状态机中是怎么检测的。

难点:1.Click_Buf的实际意义不是按键按下的标志,是按键弹起的标志。

           2.要区分状态和动作,按下既可以是状态也可以是动作,空闲是一种状态,弹起是一种动作。

在理解代码的时候可以把单击,双击,长按的路线走几遍,有助于理解。

四、作者的话

我们团队在学习状态机很痛苦,因为比较抽象难以理解,所以我们将学习经验分享,希望能对刚学习这方面内容的人有所帮助。

本人自学小白,如果有纰漏和错误,希望大佬们多多指教,欢迎大家一起交流。