按键状态机(实现单击,长按,双击)的模块分享
目录
一、相关说明
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.要区分状态和动作,按下既可以是状态也可以是动作,空闲是一种状态,弹起是一种动作。
在理解代码的时候可以把单击,双击,长按的路线走几遍,有助于理解。
四、作者的话
我们团队在学习状态机很痛苦,因为比较抽象难以理解,所以我们将学习经验分享,希望能对刚学习这方面内容的人有所帮助。
本人自学小白,如果有纰漏和错误,希望大佬们多多指教,欢迎大家一起交流。