高可移植性状态机(实现单击,双击,长按)

该文介绍了一个STM32的按键处理程序,包括无消抖和有消抖两个版本,用于检测按键的单击、双击和长按操作。程序通过状态机实现,使用了定时器进行10ms周期的检测,并通过3个LED灯进行反馈。在无消抖版本中,直接处理按键状态,而在有消抖版本中,增加了20ms的消抖时间来提高稳定性。最后,代码会在特定的定时器中断里调用按键扫描函数,并根据返回值控制LED灯的状态。
摘要由CSDN通过智能技术生成

一、所需资源

        1.定时器:定时,每10ms进入一次按键检测函数

        2.一个按键:实现单击,双击,长按这三种不同的按键方式

        3.3个LED灯:用于体现单击,双击,长按是否出现(验证程序的合理性)

二、代码模块

1.key.h部分的相关宏

#define KEYON  HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_13)==GPIO_PIN_SET
#define KEYOFF HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_13)==GPIO_PIN_RESET
//无消抖版
#define KEY_Up  1         //按键弹起
#define KEY_Down  3       //按键按下
#define KEY_wait  4       //等待状态
//有消抖版
#define KEY_Up  1         //按键弹起
#define KEY_DownShake  2  //按下抖动
#define KEY_Down  3       //按键按下
#define KEY_UpShake  4    //按下抖动
#define KEY_LongUpShake  5    //按下抖动
#define KEY_wait  6       //等待状态

#define SHORT_KEY 1       //结果为短按
#define LONG_KEY 2        //结果为长按
#define DOUBLE_CLICK 3    //结果为双击

#define FALSE 0
#define TRUE 1

2.key.c中的函数主体

(1)不加消抖的版本

uint8_t 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;//双击判断标志位
	static uint8_t  key_state = 0;//假设一开始为按下
	uint8_t key_return=0;
	//计时系统
	//定时器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(KEYON)
		{
			key_state = KEY_Down;//切换到状态2
			Long_Cnt = 100;//长按计时开始
		}
	  else
	  {
	    //判断是否为按键弹起状态
	    if(Click_Buf == TRUE)
	    {
			//弹起时间超过200ms,双击判定时间失效,且一定不为长按,直接判断为单击
			if(Twice_Cnt<=0)
			{
				KEY_flag = TRUE;
				Click = TRUE;
			}
	    }
	  }
	  break;
	}
    
    /*状态3:按键按下到标志触发状态*/
    case KEY_Down:
    {
      if(KEYOFF)
      {
        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(KEYOFF)
        key_state = KEY_Up;//完成一次按键动作,切换到状态1
      break;
	}
    default:
      key_state = KEY_Up;//默认情况都切换到状态1
      break;
  }
	//标志触发,反馈结果
	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;
		//按键状态位清零,为下一次按下准备
		Click_Buf = FALSE;
		KEY_flag= FALSE;
		Click = FALSE;
		Long_Press = FALSE;
		Double_Click  = FALSE;
    }
	return key_return;
} 

(2)加上消抖的版本

uint8_t 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;//双击判断标志位
	static uint8_t  key_state = 0;//假设一开始为按下
	uint8_t key_return=0;
	//计时系统
	//定时器10ms进入一次函数
	static uint8_t  Long_Cnt = 100;//长按计时时长1s
	static uint8_t  Twice_Cnt = 20;//双击计时时长200ms
	static uint8_t  Shake_Cnt = 2;//消抖计时时长20ms
	Long_Cnt--;
	Twice_Cnt--;
	Shake_Cnt--;
	//状态系统
	switch(key_state)
	{
	/*状态1:空闲状态(单击)和按键弹起后(双击)*/
	case KEY_Up:
	{
	  if(KEYON)
	  {
		  Shake_Cnt = 2;//消抖计时开始
		  key_state = KEY_DownShake;//切换到状态2
	  }
	  else
	  {
	    //判断是否为按键弹起状态
	    if(Click_Buf == TRUE)
	    {
			//弹起时间超过200ms,双击判定时间失效,且一定不为长按,直接判断为单击
			if(Twice_Cnt<=0)
			{
				KEY_flag = TRUE;
				Click = TRUE;
			}
	    }
	  }
	  break;
	}

    /*状态2:按下抖动(过渡状态)*/
    case KEY_DownShake:
    {
    	if(Shake_Cnt<=0)
    	{
    		if(KEYON)
    		{
    			Long_Cnt = 100;//长按计时开始
    			key_state = KEY_Down;//确认按下,切换到状态3
    		}
    		else key_state = KEY_Up;//没有按下,切换到状态1
    	}
      break;
    }

    /*状态3:按键按下到标志触发状态*/
    case KEY_Down:
    {
      if(KEYOFF)
      {
    	Shake_Cnt = 2;//消抖计时开始
        key_state = KEY_UpShake;//切换到状态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_UpShake:
    {
    	if(Shake_Cnt<=0)
    	{
		  if(KEYOFF) key_state = KEY_Up;//完成一次按键动作,切换到状态1
		  else key_state = KEY_Down;//没有弹起,返回按下状态
    	}
      break;
    }

    /*状态5:长按弹起抖动(过渡状态)*/
    case KEY_LongUpShake:
    {
    	if(Shake_Cnt<=0)
    	{
		  if(KEYOFF) key_state = KEY_Up;//完成一次按键动作,切换到状态1
		  else key_state = KEY_wait;//没有弹起,返回长按状态
    	}
      break;
    }
    /*状态6:标志触发到等待按键弹起状态*/
    case KEY_wait:
    {
    	if(KEYOFF)
    	{
    		Shake_Cnt = 2;//消抖计时开始
    		key_state = KEY_LongUpShake;//进入长按弹起消抖
    	}
      break;
	}
    default:
      key_state = KEY_Up;//默认情况都切换到状态1
      break;
  }
	//标志触发,反馈结果
	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;
		//按键状态位清零,为下一次按下准备
		Click_Buf = FALSE;
		KEY_flag= FALSE;
		Click = FALSE;
		Long_Press = FALSE;
		Double_Click  = FALSE;
  }
	return key_return;
}

3.定时器中断里的具体使用

static int count=0;
uint8_t key_return;
count++;
if(count>=10)
{
	count=0;
	key_return=KEY_Scan();
	switch(key_return)
	{
	case 1:{HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_14);key_return = 0;break;}//RED
	case 2:{HAL_GPIO_TogglePin(GPIOE,GPIO_PIN_1);key_return = 0;break;}//YELLOW
	case 3:{HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_0);key_return = 0;break;}//GREEN
	default: break;
	}
}

三、使用说明

有没有加消抖差别不大。推荐选择无消抖版

使用时只需要把三段代码放到相应位置,初始化按键和GPIO,打开定时器中断即可。

可以不使用静态变量的变量我都没有使用。

如果想更深层次学习这里面的逻辑,可以看我的上一篇文章。

按键状态机(实现单击,长按,双击)的模块分享_常工第一深情呀的博客-CSDN博客

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

废话文学创始人

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值