一、所需资源
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,打开定时器中断即可。
可以不使用静态变量的变量我都没有使用。
如果想更深层次学习这里面的逻辑,可以看我的上一篇文章。