常见的按键判定程序,如正点原子按键例程,只能判定单击事件,对于双击、长按等的判定逻辑较复杂,且使用main函数循环扫描的方式,容易被阻塞,或按键扫描函数会阻塞其他程序的执行。使用定时器设计状态机可以规避这一问题。
本文在博主 老子姓李! 程序的基础上添加连按功能,整合双击功能并补充双击二次按下消抖,添加可调消抖时间,添加长按、连按、双击使能,扩展一个按键为多按键,修改在定时器运行状态机,在主函数判断,精简部分代码,并添加大量注释方便阅读。
参考博主文章【源码详解-按键状态机-简洁易懂】1.单个按键实现短按长按的功能(基于STM32)
功能介绍
本程序功能:
使用定时器状态机实现按键单击、双击、长按、连按功能。消抖时间可调,长按时间可调,双击判定时间可调,连按单击间隔可调,可选择使能长按、连按、双击功能,无延时不阻塞,稳定触发。移植只需修改读IO函数,结构体初始化和宏定义时间参数即可。
注:
- 在定时器状态机判定产生事件标志,在主函数处理并清除事件标志。
- 单击是最基本事件,除以下情况外,经过消抖后,在按键释放时触发单击事件。
- 使能长按后,若按键按下时间大于长按判定时间,则释放时触发长按事件,若不使能,释放时触发单击事件。
- 使能连按后,按住按键时持续触发连按事件,可自定义等效为单击事件。无论是否使能长按,按键长按不释放,先经过长按判定时间触发第一次连按事件,然后循环进行连按计时,每次计时结束后都会触发一次连按事件,直到按键释放,触发长按事件(使能长按),或单击事件(不使能长按)。
- 使能双击后,若两次单击行为之间,由释放到按下的时间小于双击判定时间,则第一次单击行为释放时不触发单击事件,第二次单击行为在释放时触发双击事件。一次单击行为在双击判定时间内无按键按下动作,之后才触发单击事件。无论是否使能长按,若上述第二次行为是长按,则第二次释放时不会触发双击事件,而是到达长按判定时间后先触发属于第一次的单击事件,然后在第二次释放按键时触发长按事件(使能长按),或单击事件(不使能长按)。
代码
头文件 my_key.h
#ifndef ___MY_KEY_H__
#define ___MY_KEY_H__
#include "main.h"
#define ARR_LEN(arr) ((sizeof(arr)) / (sizeof(arr[0]))) //数组大小宏函数
#define KEY_DEBOUNCE_TIME 10 //消抖时间
#define KEY_LONG_PRESS_TIME 500 //长按判定时间
#define KEY_QUICK_CLICK_TIME 100 //连按时间间隔
#define KEY_DOUBLE_CLICK_TIME 200 //双击判定时间
#define KEY_PRESSED_LEVEL 0 //按键被按下时的电平
//按键动作
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);
#endif
源文件 my_key.c
#include "my_key.h"
static uint8_t KEY_ReadPin(uint8_t key_label)
{
switch (key_label)
{
case 0:
return