当前位置: 首页 > news >正文

STM32实现按键单击、双击、长按、连按功能,使用状态机,无延时,不阻塞

常见的按键判定程序,如正点原子按键例程,只能判定单击事件,对于双击、长按等的判定逻辑较复杂,且使用main函数循环扫描的方式,容易被阻塞,或按键扫描函数会阻塞其他程序的执行。使用定时器设计状态机可以规避这一问题。

本文在博主 老子姓李! 程序的基础上添加连按功能,整合双击功能并补充双击二次按下消抖,添加可调消抖时间,扩展为多按键,修改在定时器运行状态机,在主函数判断,并尽可能多的添加注释方便阅读。。
参考博主文章【源码详解-按键状态机-简洁易懂】1.单个按键实现短按长按的功能(基于STM32)

功能介绍

本程序功能:
使用定时器状态机实现按键单击、双击、长按、连按功能。消抖时间可调,长按时间可调,双击判定时间可调,连按单击间隔可调。无延时不阻塞,稳定触发。移植只需修改读IO函数,结构体初始化和宏定义时间参数即可。

注:

  1. 在定时器状态机判定产生事件标志,在主函数处理并清除事件标志。
  2. 单击、长按、双击事件为抬起时触发。
  3. 使能双击会稍微滞后单击事件的判定,可以选择是否使能双击判定。
  4. 可选择是否使能长按判定。
  5. 连按功能是指长按一定时间不松后,间隔较短时间多次触发快速单击事件,松开后结束。“快速单击”为单独事件,不与单击、长按事件冲突。

代码

头文件 my_key.h

#ifndef ___MY_KEY_H__
#define ___MY_KEY_H__#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 //按键被按下时的电平
#define TOTAL_KEYS 5        //物理按键数量//按键事件
typedef enum
{KEY_Event_Null,        //空事件KEY_Event_SingleClick, //单击KEY_Event_LongPress,   //长按KEY_Event_QuickClick,  //连击KEY_Event_DoubleClick, //双击
} KEY_EventList_TypeDef;//按键动作
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_StatusList_TypeDef;// 按键引脚的电平
typedef enum
{KKEY_PinLevel_Low,KEY_PinLevel_High,KEY_PinLevel_Unknow,
} KEY_PinLevel_TypeDef;//按键配置
typedef struct
{uint8_t KEY_Label;                 //按键标号uint16_t KEY_Count;                //按键按下计时KEY_Action_TypeDef KEY_Action;     //按键动作,按下或释放KEY_StatusList_TypeDef KEY_Status; //按键状态KEY_EventList_TypeDef KEY_Event;   //按键事件
} KEY_Configure_TypeDef;extern KEY_Configure_TypeDef KeyConfig[TOTAL_KEYS];
extern KEY_EventList_TypeDef key_event[TOTAL_KEYS];
extern uint8_t double_click_mode;
extern uint8_t long_press_mode;void KEY_ReadStateMachine(KEY_Configure_TypeDef *KeyCfg);#endif

源文件 my_key.c

#include "my_key.h"//单独封装函数方便移植
static KEY_PinLevel_TypeDef KEY_ReadPin(uint8_t key_label)
{switch (key_label){case 1:return (KEY_PinLevel_TypeDef)HAL_GPIO_ReadPin(K1_GPIO_Port, K1_Pin);case 2:return (KEY_PinLevel_TypeDef)HAL_GPIO_ReadPin(K2_GPIO_Port, K2_Pin);case 3:return (KEY_PinLevel_TypeDef)HAL_GPIO_ReadPin(K3_GPIO_Port, K3_Pin);case 4:return (KEY_PinLevel_TypeDef)HAL_GPIO_ReadPin(K4_GPIO_Port, K4_Pin);case 5:return (KEY_PinLevel_TypeDef)HAL_GPIO_ReadPin(K5_GPIO_Port, K5_Pin);// case X://   return (KEY_PinLevel_TypeDef)HAL_GPIO_ReadPin(KX_GPIO_Port, KX_Pin);}return KEY_PinLevel_Unknow;
}KEY_Configure_TypeDef KeyConfig[TOTAL_KEYS] = {{1, 0, KEY_Action_Release, KEY_Status_Idle, KEY_Event_Null},{2, 0, KEY_Action_Release, KEY_Status_Idle, KEY_Event_Null},{3, 0, KEY_Action_Release, KEY_Status_Idle, KEY_Event_Null},{4, 0, KEY_Action_Release, KEY_Status_Idle, KEY_Event_Null},{5, 0, KEY_Action_Release, KEY_Status_Idle, KEY_Event_Null},// {X, 0, KEY_Action_Release, KEY_Status_Idle, KEY_Event_Null},
};KEY_EventList_TypeDef key_event[TOTAL_KEYS] = {KEY_Event_Null};
uint8_t double_click_mode = 0; //双击模式
uint8_t long_press_mode = 1;   //长按模式
//按键状态处理
void KEY_ReadStateMachine(KEY_Configure_TypeDef *KeyCfg)
{//按键动作读取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 (double_click_mode) //双击模式{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) && (KeyCfg->KEY_Count >= KEY_QUICK_CLICK_TIME)) //动作:连按时间已到,保持按下{KeyCfg->KEY_Count = 0;                            //计数清零KeyCfg->KEY_Status = KEY_Status_ConfirmPressLong; //状态->维持KeyCfg->KEY_Event = KEY_Event_QuickClick;         //事件->连按****}else if ((KeyCfg->KEY_Action == KEY_Action_Press) && (KeyCfg->KEY_Count < KEY_QUICK_CLICK_TIME)) //动作:时间未到,保持按下{KeyCfg->KEY_Count++;                              //连按计数KeyCfg->KEY_Status = KEY_Status_ConfirmPressLong; //状态->维持KeyCfg->KEY_Event = KEY_Event_Null;               //事件->无}else //动作:长按下后释放{if (long_press_mode) //长按模式{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 //动作:双击等待时间内,再次按下{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 = 0;                //计数清零KeyCfg->KEY_Status = KEY_Status_Idle; //状态->空闲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)) //动作:长按时间已到,保持按下{if (long_press_mode) //长按模式{KeyCfg->KEY_Count = 0;                            //计数清零KeyCfg->KEY_Status = KEY_Status_ConfirmPressLong; //状态->确认长按KeyCfg->KEY_Event = KEY_Event_SingleClick;        //事件->先响应单击}else //非长按模式{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_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[KEY_1]);KEY_ReadStateMachine(&KeyConfig[KEY_2]);KEY_ReadStateMachine(&KeyConfig[KEY_3]);KEY_ReadStateMachine(&KeyConfig[KEY_4]);KEY_ReadStateMachine(&KeyConfig[KEY_5]);}}
}//调用
int main(void)
{long_press_mode = 1;	 //使能长按模式double_click_mode = 1; //使能双击模式while (1){if (key_event[KEY_1] == KEY_Event_SingleClick) //单击{something1();}if (key_event[KEY_2] == KEY_Event_LongPress) //长按{something2();}if ((key_event[KEY_3] == KEY_Event_QuickClick) || (key_event[KEY_3] == KEY_Event_SingleClick)) //连按{something3();}if (key_event[KEY_4] == KEY_Event_DoubleClick) //双击{something4();}memset(key_event, KEY_Event_Null, sizeof(key_event)); //清除事件}
}
http://www.lryc.cn/news/389907.html

相关文章:

  • C#之Delta并联机械手的视觉同步分拣
  • 01:Linux的基本命令
  • GNSS 载波、测距码和导航电文的关系简介
  • deepE 定位系统卡顿问题实战(一) ----------- 锁造成的阻塞问题
  • YOLOv5改进 | 主干网络 | ODConv + ConvNeXt 增强目标特征提取能力
  • TIA博途WinCC通过VB脚本从 Excel中读取数据的具体方法介绍
  • 第5篇 区块链的技术架构:节点、网络和数据结构
  • vue长列表,虚拟滚动
  • 【实战场景】记一次UAT jvm故障排查经历
  • 线性代数--行列式1
  • tensorflow神经网络
  • Python基础001
  • 【udp报文】udp报文未自动分片,报文过长被拦截问题定位
  • 某网页gpt的JS逆向
  • 【python脚本】批量检测sql延时注入
  • 在C++中如何理解const关键字的不同用法(如const变量、const成员函数、const对象等)
  • JavaSEJava8 时间日期API + 使用心得
  • 【亲测解决】Python时间问题
  • Linux屏幕驱动开发调试笔记
  • Nginx Http缓存的必要性!启发式缓存有什么弊端?
  • 【RT摩拳擦掌】RT云端测试之百度天工物接入构建(设备型)
  • Mysql和ES使用汇总
  • Android中使用performClick触发点击事件
  • 重生之我要学后端01--后端语言选择和对应框架选择
  • C语言 | Leetcode C语言题解之第206题反转链表
  • Flink Window DEMO 学习
  • library source does not match the bytecode for class SpringApplication
  • Linux基础指令介绍与详解——原理学习
  • 【代码随想录算法训练Day52】LeetCode 647. 回文子串、LeetCode 516.最长回文子串
  • VUE项目安全漏洞扫描和修复