常见的按键判定程序,如正点原子按键例程,只能判定单击事件,对于双击、长按等的判定逻辑较复杂,且使用main函数循环扫描的方式,容易被阻塞,或按键扫描函数会阻塞其他程序的执行。
使用定时器设计状态机可以规避这一问题。
功能介绍
本程序功能:
使用定时器状态机实现按键单击、双击、长按、连按功能。
消抖时间可调,长按时间可调,双击判定时间可调,连按单击间隔可调,可选择使能长按、连按、双击功能,无延时不阻塞,稳定触发。
移植只需修改读IO函数,结构体初始化和宏定义时间参数即可。
注:
在定时器状态机判定产生事件标志,在主函数处理并清除事件标志。
单击是最基本事件,除以下情况外,经过消抖后,在按键释放时触发单击事件。
使能长按后,若按键按下时间大于长按判定时间,则释放时触发长按事件,若不使能,释放时触发单击事件。
使能连按后,按住按键时持续触发连按事件,可自定义等效为单击事件。
无论是否使能长按,按键长按不释放,先经过长按判定时间触发第一次连按事件,然后循环进行连按计时,每次计时结束后都会触发一次连按事件,直到按键释放,触发长按事件(使能长按),或单击事件(不使能长按)。
使能双击后,若两次单击行为之间,由释放到按下的时间小于双击判定时间,则第一次单击行为释放时不触发单击事件,第二次单击行为在释放时触发双击事件。
一次单击行为在双击判定时间内无按键按下动作,之后才触发单击事件。
无论是否使能长按,若上述第二次行为是长按,则第二次释放时不会触发双击事件,而是到达长按判定时间后先触发属于第一次的单击事件,然后在第二次释放按键时触发长按事件(使能长按),或单击事件(不使能长按)。
代码
头文件 my_key.h
//按键动作
typedef enum
{
KEY_Action_Press, //按住
KEY_Action_Release, //松开
} KEY_Action_TypeDef;
//按键状态
typedef enum
{
KEY_Status_Idle, //空闲
KEY_Status_Debounce, //消抖
KEY_Status_ConfirmPress, //确认按下
KEY_Status_ConfirmPressLong, //确认长按
KEY_Status_WaitSecondPress, //等待再次按下
KEY_Status_SecondDebounce, //再次消抖
KEY_Status_SecondPress, //再次按下
} KEY_Status_TypeDef;
//按键事件
typedef enum
{
KEY_Event_Null, //空事件
KEY_Event_SingleClick, //单击
KEY_Event_LongPress, //长按
KEY_Event_QuickClick, //连击
KEY_Event_DoubleClick, //双击
} KEY_Event_TypeDef;
//按键模式使能选择
typedef enum
{
KEY_Mode_OnlySinge = 0x00, //只有单击
KEY_Mode_Long = 0x01, //单击长按
KEY_Mode_Quick = 0x02, //单击连按
KEY_Mode_Long_Quick = 0x03, //单击长按连按
KEY_Mode_Double = 0x04, //单击双击
KEY_Mode_Long_Double = 0x05, //单击长按双击
KEY_Mode_Quick_Double = 0x06, //单击连按双击
KEY_Mode_Long_Quick_Double = 0x07, //单击长按连按双击
} KEY_Mode_TypeDef;
//按键配置
typedef struct
{
uint8_t KEY_Label; //按键标号
KEY_Mode_TypeDef KEY_Mode; //按键模式
uint16_t KEY_Count; //按键按下计时
KEY_Action_TypeDef KEY_Action; //按键动作,按下或释放
KEY_Status_TypeDef KEY_Status; //按键状态
KEY_Event_TypeDef KEY_Event; //按键事件
} KEY_Configure_TypeDef;
extern KEY_Configure_TypeDef KeyConfig[];
extern KEY_Event_TypeDef key_event[];
void KEY_ReadStateMachine(KEY_Configure_TypeDef *KeyCfg);
源文件 my_key.c
#include "my_key.h"
static uint8_t KEY_ReadPin(uint8_t key_label)
{
switch (key_label)
{
case 0:
return (uint8_t)HAL_GPIO_ReadPin(K0_GPIO_Port, K0_Pin);
case 1:
return (uint8_t)HAL_GPIO_ReadPin(K1_GPIO_Port, K1_Pin);
case 2:
return (uint8_t)HAL_GPIO_ReadPin(K2_GPIO_Port, K2_Pin);
case 3:
return (uint8_t)HAL_GPIO_ReadPin(K3_GPIO_Port, K3_Pin);
case 4:
return (uint8_t)HAL_GPIO_ReadPin(K4_GPIO_Port, K4_Pin);
// case X:
// return (uint8_t)HAL_GPIO_ReadPin(KX_GPIO_Port, KX_Pin);
}
return 0;
}
KEY_Configure_TypeDef KeyConfig[] = {
{0, KEY_Mode_Long_Quick_Double, 0, KEY_Action_Release, KEY_Status_Idle, KEY_Event_Null},
{1, KEY_Mode_Long_Quick_Double, 0, KEY_Action_Release, KEY_Status_Idle, KEY_Event_Null},
{2, KEY_Mode_Long_Quick_Double, 0, KEY_Action_Release, KEY_Status_Idle, KEY_Event_Null},
{3, KEY_Mode_Long_Quick_Double, 0, KEY_Action_Release, KEY_Status_Idle, KEY_Event_Null},
{4, KEY_Mode_Long_Quick_Double, 0, KEY_Action_Release, KEY_Status_Idle, KEY_Event_Null},
// {X, KEY_Mode_Long_Quick_Double, 0, KEY_Action_Release, KEY_Status_Idle, KEY_Event_Null},
};
KEY_Event_TypeDef key_event[ARR_LEN(KeyConfig)] = {KEY_Event_Null}; //按键事件
//按键状态处理
void KEY_ReadStateMachine(KEY_Configure_TypeDef *KeyCfg)
{
static uint16_t tmpcnt[ARR_LEN(KeyConfig)] = {0};
//按键动作读取
if (KEY_ReadPin(KeyCfg->KEY_Label) == KEY_PRESSED_LEVEL)
KeyCfg->KEY_Action = KEY_Action_Press;
else
KeyCfg->KEY_Action = KEY_Action_Release;
//状态机
switch (KeyCfg->KEY_Status)
{
//状态:空闲
case KEY_Status_Idle:
if (KeyCfg->KEY_Action == KEY_Action_Press) //动作:按下
{
KeyCfg->KEY_Status = KEY_Status_Debounce; //状态->消抖
KeyCfg->KEY_Event = KEY_Event_Null; //事件->无
}
else //动作:默认动作,释放
{
KeyCfg->KEY_Status = KEY_Status_Idle; //状态->维持
KeyCfg->KEY_Event = KEY_Event_Null; //事件->无
}
break;
//状态:消抖
case KEY_Status_Debounce:
if ((KeyCfg->KEY_Action == KEY_Action_Press) && (KeyCfg->KEY_Count >= KEY_DEBOUNCE_TIME)) //动作:保持按下,消抖时间已到
{
KeyCfg->KEY_Count = 0; //计数清零
KeyCfg->KEY_Status = KEY_Status_ConfirmPress; //状态->确认按下
KeyCfg->KEY_Event = KEY_Event_Null; //事件->无
}
else if ((KeyCfg->KEY_Action == KEY_Action_Press) && (KeyCfg->KEY_Count < KEY_DEBOUNCE_TIME)) //动作:保持按下,消抖时间未到
{
KeyCfg->KEY_Count++; //消抖计数
KeyCfg->KEY_Status = KEY_Status_Debounce; //状态->维持
KeyCfg->KEY_Event = KEY_Event_Null; //事件->无
}
else //动作:释放,消抖时间未到,判定为抖动
{
KeyCfg->KEY_Count = 0; //计数清零
KeyCfg->KEY_Status = KEY_Status_Idle; //状态->空闲
KeyCfg->KEY_Event = KEY_Event_Null; //事件->无
}
break;
//状态:确认按下
case KEY_Status_ConfirmPress:
if ((KeyCfg->KEY_Action == KEY_Action_Press) && (KeyCfg->KEY_Count >= KEY_LONG_PRESS_TIME)) //动作:保持按下,长按时间已到
{
KeyCfg->KEY_Count = KEY_QUICK_CLICK_TIME; //计数置数,生成第一次连按事件
KeyCfg->KEY_Status = KEY_Status_ConfirmPressLong; //状态->确认长按
KeyCfg->KEY_Event = KEY_Event_Null; //事件->无
}
else if ((KeyCfg->KEY_Action == KEY_Action_Press) && (KeyCfg->KEY_Count < KEY_LONG_PRESS_TIME)) //动作:保持按下,长按时间未到
{
KeyCfg->KEY_Count++; //长按计数
KeyCfg->KEY_Status = KEY_Status_ConfirmPress; //状态->维持
KeyCfg->KEY_Event = KEY_Event_Null; //事件->无
}
else //动作:长按时间未到,释放
{
if ((uint8_t)(KeyCfg->KEY_Mode) & 0x04) //双击模式
{
KeyCfg->KEY_Count = 0; //计数清零
KeyCfg->KEY_Status = KEY_Status_WaitSecondPress; //状态->等待再按
KeyCfg->KEY_Event = KEY_Event_Null; //事件->无
}
else //非双击模式
{
KeyCfg->KEY_Count = 0; //计数清零
KeyCfg->KEY_Status = KEY_Status_Idle; //状态->空闲
KeyCfg->KEY_Event = KEY_Event_SingleClick; //事件->单击****
}
}
break;
//状态:确认长按
case KEY_Status_ConfirmPressLong:
if (KeyCfg->KEY_Action == KEY_Action_Press) //动作:保持按下
{
if ((uint8_t)KeyCfg->KEY_Mode & 0x02) //连按模式
{
if (KeyCfg->KEY_Count >= KEY_QUICK_CLICK_TIME) //连按间隔时间已到
{
KeyCfg->KEY_Count = 0; //计数清零
KeyCfg->KEY_Status = KEY_Status_ConfirmPressLong; //状态->维持
KeyCfg->KEY_Event = KEY_Event_QuickClick; //事件->连按****
}
else //连按间隔时间未到
{
KeyCfg->KEY_Count++; //连按计数
KeyCfg->KEY_Status = KEY_Status_ConfirmPressLong; //状态->维持
KeyCfg->KEY_Event = KEY_Event_Null; //事件->无
}
}
else //非连按模式
{
KeyCfg->KEY_Count = 0; //计数清零
KeyCfg->KEY_Status = KEY_Status_ConfirmPressLong; //状态->维持
KeyCfg->KEY_Event = KEY_Event_Null; //事件->无
}
}
else //动作:长按下后释放
{
if ((uint8_t)KeyCfg->KEY_Mode & 0x01) //长按模式
{
KeyCfg->KEY_Count = 0; //计数清零
KeyCfg->KEY_Status = KEY_Status_Idle; //状态->空闲
KeyCfg->KEY_Event = KEY_Event_LongPress; //事件->长按****
}
else //非长按模式
{
KeyCfg->KEY_Count = 0; //计数清零
KeyCfg->KEY_Status = KEY_Status_Idle; //状态->空闲
KeyCfg->KEY_Event = KEY_Event_SingleClick; //事件->单击****
}
}
break;
//状态:等待是否再次按下
case KEY_Status_WaitSecondPress:
if ((KeyCfg->KEY_Action != KEY_Action_Press) && (KeyCfg->KEY_Count >= KEY_DOUBLE_CLICK_TIME)) //动作:保持释放,双击等待时间已到
{
KeyCfg->KEY_Count = 0; //计数清零
KeyCfg->KEY_Status = KEY_Status_Idle; //状态->空闲
KeyCfg->KEY_Event = KEY_Event_SingleClick; //事件->单击****
}
else if ((KeyCfg->KEY_Action != KEY_Action_Press) && (KeyCfg->KEY_Count < KEY_DOUBLE_CLICK_TIME)) //动作:保持释放,双击等待时间未到
{
KeyCfg->KEY_Count++; //双击等待计数
KeyCfg->KEY_Status = KEY_Status_WaitSecondPress; //状态->维持
KeyCfg->KEY_Event = KEY_Event_Null; //事件->无
}
else //动作:双击等待时间内,再次按下
{
tmpcnt[KeyCfg->KEY_Label] = KeyCfg->KEY_Count; //计数保存
KeyCfg->KEY_Count = 0; //计数清零
KeyCfg->KEY_Status = KEY_Status_SecondDebounce; //状态->再次消抖
KeyCfg->KEY_Event = KEY_Event_Null; //事件->无
}
break;
//状态:再次消抖
case KEY_Status_SecondDebounce:
if ((KeyCfg->KEY_Action == KEY_Action_Press) && (KeyCfg->KEY_Count >= KEY_DEBOUNCE_TIME)) //动作:保持按下,消抖时间已到
{
KeyCfg->KEY_Count = 0; //计数清零
KeyCfg->KEY_Status = KEY_Status_SecondPress; //状态->确认再次按下
KeyCfg->KEY_Event = KEY_Event_Null; //事件->无
}
else if ((KeyCfg->KEY_Action == KEY_Action_Press) && (KeyCfg->KEY_Count < KEY_DEBOUNCE_TIME)) //动作:保持按下,消抖时间未到
{
KeyCfg->KEY_Count++; //消抖计数
KeyCfg->KEY_Status = KEY_Status_SecondDebounce; //状态->维持
KeyCfg->KEY_Event = KEY_Event_Null; //事件->无
}
else //动作:释放,消抖时间未到,判定为抖动
{
KeyCfg->KEY_Count = KeyCfg->KEY_Count + tmpcnt[KeyCfg->KEY_Label]; //计数置数
KeyCfg->KEY_Status = KEY_Status_WaitSecondPress; //状态->等待再按
KeyCfg->KEY_Event = KEY_Event_Null; //事件->无
}
break;
//状态:再次按下
case KEY_Status_SecondPress:
if ((KeyCfg->KEY_Action == KEY_Action_Press) && (KeyCfg->KEY_Count >= KEY_LONG_PRESS_TIME)) //动作:保持按下,长按时间已到
{
KeyCfg->KEY_Count = 0; //计数清零
KeyCfg->KEY_Status = KEY_Status_ConfirmPressLong; //状态->确认长按
KeyCfg->KEY_Event = KEY_Event_SingleClick; //事件->先响应单击
}
else if ((KeyCfg->KEY_Action == KEY_Action_Press) && (KeyCfg->KEY_Count < KEY_LONG_PRESS_TIME)) //动作:保持按下,长按时间未到
{
KeyCfg->KEY_Count++; //计数
KeyCfg->KEY_Status = KEY_Status_SecondPress; //状态->维持
KeyCfg->KEY_Event = KEY_Event_Null; //事件->无
}
else //动作:释放,长按时间未到
{
KeyCfg->KEY_Count = 0; //计数清零
KeyCfg->KEY_Status = KEY_Status_Idle; //状态->空闲
KeyCfg->KEY_Event = KEY_Event_DoubleClick; //事件->双击
}
break;
}
if (KeyCfg->KEY_Event != KEY_Event_Null) //事件记录
key_event[KeyCfg->KEY_Label] = KeyCfg->KEY_Event;
}
定时器中断调用和主函数使用
中断周期为1ms
//调用
uint32_t tim_cnt = 0;
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if (htim->Instance == htim1.Instance)
{
tim_cnt++;
if (tim_cnt % 1 == 0) // 1ms
{
KEY_ReadStateMachine(&KeyConfig[0]);
KEY_ReadStateMachine(&KeyConfig[1]);
KEY_ReadStateMachine(&KeyConfig[2]);
KEY_ReadStateMachine(&KeyConfig[3]);
KEY_ReadStateMachine(&KeyConfig[4]);
}
}
}
int main(void)
{
while (1)
{
if (key_event[1] == KEY_Event_SingleClick) //单击
{
something1();
}
if (key_event[2] == KEY_Event_LongPress) //长按
{
something2();
}
if ((key_event[3] == KEY_Event_QuickClick) || (key_event[3] == KEY_Event_SingleClick)) //连按
{
something3();
}
if (key_event[4] == KEY_Event_DoubleClick) //双击
{
something4();
}
memset(key_event, KEY_Event_Null, sizeof(key_event)); //清除事件
}
}
直接来源:钜锋智联
文章来源于网络,版权归原作者所有,如有侵权,请联系删除。
关注【一起学嵌入式】,回复“加群”进技术交流群。
觉得文章不错,点击“分享”、“赞”、“在看” 呗