• 基于有限状态机与STM32实现按键扫描


    有限状态机其实是一种概念性机器,表示有限个状态以及在这些状态之间的转移和动作等行为的框图(在程序上)。

    以我的程序绘成的图为例:

    以高电平作为标志,在S1时检测输入电平是否为高,是高电平则运行至S2,否则保持在S1;

    在S2时再次判断判断输入电平是否为高,若为低则说明刚刚的改变是干扰,回到S1,若为高电平则说明按键被按下,运行至S3;

    在S3可判断这次对按键的操作是长按还是短按,具体方法是判断电平是否还是为高,若为高则令状态机保持在S3,同时定义一个变量(建议用指针,具体见代码),使其一直自加。若为低则运行至S4;

    在S4时判断那个变量是否大于某个数值,大于则为长按,小于则为短按,并执行相关的操作,执行完之后令状态机回到S1

    这种写法是可以利用时间片的形式,每隔10ms运行一次状态机,即可实现消抖的目的,还可以对长按的时间精确控制。

    运用状态机+时间片,大幅提升CPU的资源的利用率,而且效率高了不少。

    时间片的话举个例子,比如我一个周期30ms,10ms需要执行一次按键扫描,20ms执行一次ADC,那么利用时间片的话程序就是:
    注:Time是在定时器中断里每隔1ms自加一次。

    if( Time %10 == 0 ){
          KeyScan();
    }
     
    if( Time %20 == 0 ){
          ADC();
    }
     
    if( Time > 30 ){
        Time = 1;
    }

    贴上我写的完整代码:

    Key.h:

    #ifndef _BSP_KEY_H
    #define _BSP_KEY_H
     
    #include "stm32f10x.h"
     
    #define    KEY0_GPIO_PORT        GPIOE
    #define KEY0_GPIO_PIN        GPIO_Pin_0
    #define KEY0_GPIO_CLOCK        RCC_APB2Periph_GPIOE
     
    #define    KEY1_GPIO_PORT        GPIOE
    #define KEY1_GPIO_PIN        GPIO_Pin_1
    #define KEY1_GPIO_CLOCK        RCC_APB2Periph_GPIOE
     
    #define    KEY2_GPIO_PORT        GPIOE
    #define KEY2_GPIO_PIN        GPIO_Pin_2
    #define KEY2_GPIO_CLOCK        RCC_APB2Periph_GPIOE
     
    #define ON                1
    #define OFF                0       
    #define LONGTIME            8000
     
    //---------定义状态Sx的枚举类型
    typedef enum {
        FsmState_1 = 1,
        FsmState_2 = 2,
        FsmState_3 = 3,
        FsmState_4 = 4,
        FsmState_5 = 5,
    }FsmState_x;
     
    //---------定义按键的结构体 
    typedef struct FsmTable_s{
        uint8_t event;                      /* 触发事件 */
        uint8_t CurState;                /* 当前状态 */
        void (*EventFunction)(void);        /* 动作函数 */
        uint16_t Time;                      /* 时间计数 */
        uint16_t GpioPin;          /* 按键引脚 */
        GPIO_TypeDef * GpioPort;      /* 引脚GPIO */ 
    }Key;
     
    void key_GPIO_Init(void);
    void Key_1_EventFunction(void);
    void Key_1_LongEventFunction(void);
    void Key_2_EventFunction(void);
    void Key_2_LongEventFunction(void);
    void Key_Fsm(Key *key);
     
    #endif

    Key.c:

    /**********************************************
     *                                            
     * 状态机相关                                  
     *                                            
     **********************************************/
     
    /**********************************************
     *
     * 按键1短按时的事件函数
     *
     *********************************************/
    void Key_1_EventFunction()
    {
        
    }
     
    /**********************************************
     *
     * 按键1长按时的事件函数
     *
     *********************************************/
    void Key_1_LongEventFunction()
    {
        
    }
     
    /**********************************************
     *
     * 按键2短按时的事件函数
     *
     *********************************************/
    void Key_2_EventFunction()
    {
        
    }
     
    /**********************************************
     *
     * 按键2长按时的事件函数
     *
     *********************************************/
    void Key_2_LongEventFunction()
    {
        
    }
     
    /**********************************************
     *
     * 基于状态机的按键扫描
     * 输入:Key类型的指针变量
     * 输出:无
     *
     *********************************************/
    void Key_Fsm(Key *key)
    {
        void (*EventFunction)(void);
        
        switch( key->CurState )
        {
            case FsmState_1:{//--------状态1
                if( GPIO_ReadInputDataBit(key->GpioPort,key->GpioPin) == ON ){
                    key->CurState = FsmState_2;
                }
                
            }break;
            
            case FsmState_2:{//--------状态2
                if( GPIO_ReadInputDataBit(key->GpioPort,key->GpioPin) == ON ){
                    key->CurState = FsmState_3;
                }
                else{//--------干扰返回状态1
                    key->CurState = FsmState_1;
                }
            }break;
            
            case FsmState_3:{
                if( GPIO_ReadInputDataBit(key->GpioPort,key->GpioPin) == ON ){
                    key->Time++;//--------变量自加
                    key->CurState = FsmState_3;//--------保持在状态3
                }
                else{
                    key->CurState = FsmState_4;
                }
            }break;
            
            case FsmState_4:{
                EventFunction = key->EventFunction;//--------短按函数
                if( (key->Time > LONGTIME) && (key->GpioPin == KEY0_GPIO_PIN) ){
                    key->Time = 0;
                    EventFunction = Key_1_LongEventFunction;//--------Key1的长按函数
                }
     
                if( (key->Time > LONGTIME) && (key->GpioPin == KEY1_GPIO_PIN) ){
                    key->Time = 0;
                    EventFunction = Key_2_LongEventFunction;//--------Key2的长按函数
                }
                key->CurState = FsmState_1;//--------返回状态1
                EventFunction();//--------执行对应的函数
            }break;
        }
    }

    main.c:

    #include "bsp_key.h"
     
    extern uint16_t TimeSlice;
     
    int main()
    {
        Key *key_1,*key_2;
        Key key1,key2;
        
        key_1 = &key1;
        key_2 = &key2;
        
        TIM_TimeBase_Init();
        key_GPIO_Init();
        
        key_1->GpioPin  = KEY0_GPIO_PIN;
        key_1->GpioPort = KEY0_GPIO_PORT;
        key_1->CurState = FsmState_2;
        key_1->EventFunction = Key_1_EventFunction;
        
        key_2->GpioPin  = KEY1_GPIO_PIN;
        key_2->GpioPort = KEY1_GPIO_PORT;
        key_2->CurState = FsmState_1;
        key_2->EventFunction = Key_2_EventFunction;
        
        while(1)
        {
        //--------------按键扫描,10ms执行一次--------------//
            if( TimeSlice %10==0 ){
                Key_Fsm(key_1);
                Key_Fsm(key_2);
            }
        }
    }

     原文地址: https://blog.csdn.net/Unlimited_Bit/article/details/95811859

  • 相关阅读:
    Python中使用MongoEngine
    Python中MongoDB使用
    JAVA 日期相关API (JDK 8 新增)
    JAVA 日期相关API(JDK 8 之前)
    StringBuffer 和StringBuilder
    String 类型转换
    String类常用方法
    JAVA String类
    关于线程锁的释放和保留
    java线程同步--使用线程池
  • 原文地址:https://www.cnblogs.com/since1996/p/13558065.html
Copyright © 2020-2023  润新知