按键状态机实现(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;
            
        }

    }
    
}

按键处理函数根据需要可以轻易实现

 

posted @ 2023-04-06 12:36  哈啰世界  阅读(845)  评论(0编辑  收藏  举报