按键状态机实现(stm32平台,易移植)
为什么要选择状态机?
简单理解为:将一个事件划分为有限个状态,满足相应的条件,在有限个状态之间跳转;可以使用状态图来描述事件处理过程,这种方式使得程序逻辑思路更加清晰严谨。
以按键为例,按键检测的过程可以分为三个状态:按键检测状态、按键确认状态、按键释放状态;而在这三个状态之间跳转的条件为当前状态下按键的值。
在单片机中实现状态机最常用的语句便是switch case语句。(【stm32单片机基础】按键状态机实现长按和短按_按键状态机程序_Net_Walke的博客-CSDN博客)
通过状态机的思维逻辑,可以使程序逻辑更清晰,尤其是在按键这类存在多个状态转换的事件上
通过分析按键存在的三个状态,以及状态间转换的条件,可以轻松实现短按和长按,并且可以轻易的扩展功能和按键数量
对于单个按键,应该有三个状态:按键等待按下状态(s0),按键确认按下状态(s1),按键释放状态(s2)
三个状态转换的条件是按键电平状态。
头文件
#ifndef _KEY_H #define _KEY_H #include "main.h" #include "usart.h" #define KEYNUM 4 //按键数量 #define LONGPRESS 100 //长按时间计数,定时器为10ms //按键类 enum{ KEY0 = 0, KEY1, KEY2, KEY3 }; //按键动作类 enum{ KEY_WAITPRESS = 0, KEY_PRESS , KEY_RELEASE }; //按键状态类 enum{ KEY_NULL = 0, KEY_SHORT , KEY_LONG }; //按键电平状态 typedef struct { /* data */ uint8_t keyGpioStatus; uint8_t keyGpioLastStatus; }keyValueList; //按键消息类 typedef struct { /* data */ uint8_t id; uint8_t actionFlag; uint8_t keyNowState; uint16_t countPressTime; }keyMessageList; extern uint8_t count[KEYNUM]; uint8_t KEY_GetGPIOLevel(uint8_t ch); void KEY_Init(void); void KEY_Scan(void); void KEY_Process (void); #endif
源文件
/** * 多个按键检测短按和长按事件 * 短按:时间 10ms < T < 1 s, 长按:时间 T >1 s * 功能:使用状态机方式,扫描单个按键;扫描周期为10ms,10ms刚好跳过抖动; * 状态机使用switch case语句实现状态之间的跳转 * :长按键事件提前执行,短按键事件释放后才执行 */ void KEY_Scan() { for(int i = 0; i< KEYNUM; i++) { switch(keyMessage[i].actionFlag) { //按键等待按下 case KEY_WAITPRESS: keyValue[i].keyGpioStatus = KEY_GetGPIOLevel(i); //按键扫描,获取当前按键电平 if(keyValue[i].keyGpioStatus != keyValue[i].keyGpioLastStatus) //与上一次电平状态比较,判断按下还是释放,不相同说明按键被按下 { keyValue[i].keyGpioLastStatus = keyValue[i].keyGpioStatus; //更新上一次的状态 if(keyValue[i].keyGpioStatus == 0) //低电平表示按下 { keyMessage[i].id = i; //初始化各按键ID keyMessage[i].actionFlag = KEY_PRESS; //当前按键动作标志为->按下 keyMessage[i].countPressTime = 0; //长按计数初始化为0 } } break; //按键已经按下 /*长按键事件提前执行,短按键事件释放后才执行,通过if-else判断按键电平状态实现 按键电平状态不变(按住按键一直不释放),就等待按键状态变为长按。只有在按键电平状态改变(释放后),按键状态会变为短按*/ case KEY_PRESS: keyValue[i].keyGpioStatus = KEY_GetGPIOLevel(i); //按键扫描,获取当前按键电平 if(keyValue[i].keyGpioStatus == keyValue[i].keyGpioLastStatus) //按键电平状态没有变化,代表按键仍被按住 { keyMessage[i].countPressTime++; //开始计数 if(keyMessage[i].countPressTime >= LONGPRESS) //满1s,相应标志位置1 { keyMessage[i].keyNowState = KEY_LONG; //长按标志 keyMessage[i].countPressTime = 0; //清0 keyMessage[i].actionFlag = KEY_RELEASE; //按键动作为释放 } } else { keyValue[i].keyGpioLastStatus = keyValue[i].keyGpioStatus; //进入else说明按键电平状态改变,更新上一次的状态 if(keyMessage[i].countPressTime > 0) //长按计数大于0说明已经按下过 { keyMessage[i].keyNowState = KEY_SHORT; //短按标志 keyMessage[i].actionFlag = KEY_RELEASE; //按键动作为释放 } else { keyMessage[i].actionFlag = KEY_WAITPRESS; //返回上一个状态 } } break; case KEY_RELEASE: keyMessage[i].actionFlag = KEY_WAITPRESS; break; default: break; } } }
按键处理函数根据需要可以轻易实现