• STM32框架之按键扫描新思路


    我们学习了定时器实现毫秒级/秒级任务框架,这期我们基于任务框架学习按键扫描新思路。

    引入

    在按键扫描的过程中,最重要的一步就是按键消抖,解决的方法最简单粗暴的就是先扫描一次按键状态,判断按键按下后,延时,再次判断按键状态。就像这样:

    if(1-HAL_GPIO_ReadPin(KEY0_GPIO_Port,KEY0_Pin))				//判断按键0是否按下
        {
            HAL_Delay(20);											//延时消抖			
            if(1-HAL_GPIO_ReadPin(KEY0_GPIO_Port,KEY0_Pin))			 //再次判断按键0是否按下	
            {
                while(1-HAL_GPIO_ReadPin(KEY0_GPIO_Port,KEY0_Pin))	//等待按键0松开
                {
                    
                }
                Key_Num = 1;		//赋值按键值
            }
        }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    这是最笨的方法,当然还可以

    多次调用按键扫描函数,当按键按下的时候开始计数/计时。当数字/时间到达一定值时,判断按键按下,当有一次扫描到按键为按下时,计数/计时清零,代码如下:

    int Key_Scan(void)
    {
        int Key_Num = 0;        //定义按键值
        int temp = 0;           //定义临时变量
        static int Key0_Count=0,Key1_Count=0,Key2_Count=0,WKUP_Count=0 ;    //定义不同按键的按键值
        if(1-HAL_GPIO_ReadPin(KEY0_GPIO_Port,KEY0_Pin)) temp = 1;           //当某一个按键按下是,临时变量值为对应的按键值加一
        if(1-HAL_GPIO_ReadPin(KEY1_GPIO_Port,KEY1_Pin)) temp = 2;           //加一是因为temp为0时无法进入switch中,所以案件之都加一
        if(1-HAL_GPIO_ReadPin(KEY2_GPIO_Port,KEY2_Pin)) temp = 3;           //
        if(HAL_GPIO_ReadPin(WKUP_GPIO_Port,WKUP_Pin))   temp = 4;
        switch(temp)
        {
            case 1: Key0_Count++; Key1_Count=0;Key2_Count=0;WKUP_Count=0 ;  //通过temp变量的值,将对应按键计数值加一
                break;                                                      //同时将其他按键计数值清零,
            case 2: Key1_Count++; Key0_Count=0;Key2_Count=0;WKUP_Count=0 ;  
                break;
            case 3: Key2_Count++; Key0_Count=0;Key1_Count=0;WKUP_Count=0 ;
                break;
            case 4: WKUP_Count++; Key0_Count=0;Key1_Count=0;Key2_Count=0 ;
                break;
            default: Key0_Count=0; Key1_Count=0;Key2_Count=0;WKUP_Count=0 ;
                break;
        }
        if(Key0_Count >= 500)           //当temp到达一定值时,则可判断按键按下
        {
            Key_Num = 1;                //按键按下,将所有的按键计数值清零
            Key0_Count=0; Key1_Count=0;Key2_Count=0;WKUP_Count=0 ;
        }
        if(Key1_Count >= 500)
        {
            Key_Num = 2;
            Key0_Count=0; Key1_Count=0;Key2_Count=0;WKUP_Count=0 ;
        }
        if(Key2_Count >= 500)
        {
            Key_Num = 3;
            Key0_Count=0; Key1_Count=0;Key2_Count=0;WKUP_Count=0 ;
        }
        if(WKUP_Count >= 500)
        {
            Key_Num = 4;
            Key0_Count=0; Key1_Count=0;Key2_Count=0;WKUP_Count=0 ;
        }
            return Key_Num;	//返回按键值,当没有按键按下,或者存在抖动时,返回值为0
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44

    这种方法相对于第一种,逻辑上更严谨,但是不好控制计数值。并且多次进入函数,占用大量CPU资源,所以我们引入第三种方法,规定时间间隔,进行扫描,通过数组判断结果。代码如下:

    代码展示

    • main.c
    /* USER CODE BEGIN PFP */
    void Proc2msTask(void)        //2ms任务
    {
        static uint8_t i=0;
        if(Get_2ms_Flag() == 1)     //获取2ms标志位
        {
            Clear_2ms_Flag();      //清除2ms标志位
            
            //2mstask code
            i++;
            if(i==4)                //1s扫描一处按键状态
            {
                i=0;
                Key_One_Scan(Key_Name_Key0,Key0_Up_Task,Key0_Down_Task);         //扫描Key0状态
                Key_One_Scan(Key_Name_Key1,Key1_Up_Task,Key1_Down_Task);         //扫描Key1状态
                Key_One_Scan(Key_Name_Key2,Key2_Up_Task,Key2_Down_Task);         //扫描Key2状态
                Key_One_Scan(Key_Name_WKUP,WWKUP_Up_Task,WKUP_Down_Task);        //扫描WKUP状态
            }
            
        }
    }
    
    void Proc1sTask(void)       //1s任务
    {
      if(Get_1s_Flag() == 1)     //获取1s标志位
      {
        Clear_1s_Flag();         //清除1s标志位
          
          //1stask code
    //      HAL_GPIO_TogglePin(LED0_GPIO_Port,LED0_Pin);
      }      
    }
    /* USER CODE END PFP */
    
    /* Private user code ---------------------------------------------------------*/
    /* USER CODE BEGIN 0 */
    
    /* USER CODE END 0 */
    
    /**
     * @brief  The application entry point.
     * @retval int
      */
    int main(void)
    {
      /* USER CODE BEGIN 1 */
    
      /* USER CODE END 1 */
    
      /* MCU Configuration--------------------------------------------------------*/
    
      /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
      HAL_Init();
    
      /* USER CODE BEGIN Init */
    
      /* USER CODE END Init */
    
      /* Configure the system clock */
      SystemClock_Config();
    
      /* USER CODE BEGIN SysInit */
    
      /* USER CODE END SysInit */
    
      /* Initialize all configured peripherals */
      MX_GPIO_Init();
      MX_TIM10_Init();
      /* USER CODE BEGIN 2 */
    HAL_TIM_Base_Start_IT(&htim10);
      /* USER CODE END 2 */
    
      /* Infinite loop */
      /* USER CODE BEGIN WHILE */
      while (1)
      {
          Proc2msTask();       //调用2ms任务
          Proc1sTask();        //调用1s任务
        /* USER CODE END WHILE */
    
        /* USER CODE BEGIN 3 */
      }
      /* USER CODE END 3 */
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • Key.c
    /* USER CODE BEGIN 2 */
    void Key0_Down_Task(void)
    {
       HAL_GPIO_WritePin(LED0_GPIO_Port,LED0_Pin,GPIO_PIN_RESET); //按下亮灯 
    }
    void Key0_Up_Task(void)
    {
       HAL_GPIO_WritePin(LED0_GPIO_Port,LED0_Pin,GPIO_PIN_SET);  //松开关闭 
    }
    void Key1_Down_Task(void)
    {
       HAL_GPIO_WritePin(LED1_GPIO_Port,LED1_Pin,GPIO_PIN_RESET); //按下亮灯  
    }
    void Key1_Up_Task(void)
    {
       HAL_GPIO_WritePin(LED1_GPIO_Port,LED1_Pin,GPIO_PIN_SET);  //松开关闭  
    }
    void Key2_Down_Task(void)
    {
        HAL_GPIO_WritePin(LED2_GPIO_Port,LED2_Pin,GPIO_PIN_RESET); //按下亮灯 
    }
    void Key2_Up_Task(void)
    {
        HAL_GPIO_WritePin(LED2_GPIO_Port,LED2_Pin,GPIO_PIN_SET);  //松开关闭 
    }
    void WKUP_Down_Task(void)
    {
       HAL_GPIO_WritePin(LED3_GPIO_Port,LED3_Pin,GPIO_PIN_RESET); //按下亮灯  
    }
    void WWKUP_Up_Task(void)
    {
       HAL_GPIO_WritePin(LED3_GPIO_Port,LED3_Pin,GPIO_PIN_SET);  //松开关闭  
    }
    
    void Key_One_Scan(uint8_t KeyName ,void(*OnKeyOneUp)(void), void(*OnKeyOneDown)(void))
    {
       static uint8_t Key_Val[Key_Name_Max];    //按键值的存放位置
       static uint8_t Key_Flag[Key_Name_Max];   //KEY0~2为0时表示按下,为1表示松开,WKUP反之
        
       Key_Val[KeyName] = Key_Val[KeyName] <<1;  //每次扫描完,将上一次扫描的结果左移保存
       
        switch(KeyName)
        {
            case Key_Name_Key0:  Key_Val[KeyName] = Key_Val[KeyName] | (HAL_GPIO_ReadPin(KEY0_GPIO_Port, KEY0_Pin));    //读取Key0按键值
                break;
            case Key_Name_Key1:  Key_Val[KeyName] = Key_Val[KeyName] | (HAL_GPIO_ReadPin(KEY1_GPIO_Port, KEY1_Pin));   //读取Key1按键值
                break;
            case Key_Name_Key2:  Key_Val[KeyName] = Key_Val[KeyName] | (HAL_GPIO_ReadPin(KEY2_GPIO_Port, KEY2_Pin));   //读取Key2按键值
                break;
            case Key_Name_WKUP:  Key_Val[KeyName] = Key_Val[KeyName] | (HAL_GPIO_ReadPin(WKUP_GPIO_Port, WKUP_Pin));   //读取WKUP按键值
                break; 
            default:
                break;
        }
        if(KeyName == Key_Name_WKUP)     //WKUP的电路图与其他按键不同,所以需要特殊处理
        {
            //WKUP特殊情况
            //当按键标志为1(松开)是,判断是否按下,WKUP按下时为0xff
            if(Key_Val[KeyName] == 0xff && Key_Flag[KeyName] == 1)
            {
                (*OnKeyOneDown)();
               Key_Flag[KeyName] = 0;
            }
            //当按键标志位为0(按下),判断按键是否松开,WKUP松开时为0x00
            if(Key_Val[KeyName] == 0x00 && Key_Flag[KeyName] == 0)
            {
                (*OnKeyOneUp)();
               Key_Flag[KeyName] = 1;
            } 
        }
        else                               //Key0~2按键逻辑判断
        {
            //Key0~2常规判断
              //当按键标志为1(松开)是,判断是否按下
            if(Key_Val[KeyName] == 0x00 && Key_Flag[KeyName] == 1)
            {
                (*OnKeyOneDown)();
               Key_Flag[KeyName] = 0;
            }
            //当按键标志位为0(按下),判断按键是否松开
            if(Key_Val[KeyName] == 0xff && Key_Flag[KeyName] == 0)
            {
                (*OnKeyOneUp)();
               Key_Flag[KeyName] = 1;
            }  
        }
         
       
    }
    /* USER CODE END 2 */
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • Key.h
    /* Includes ------------------------------------------------------------------*/
    #include "main.h"
    
    /* USER CODE BEGIN Includes */
    
    /* USER CODE END Includes */
    
    /* USER CODE BEGIN Private defines */
    typedef enum
     {
         Key_Name_Key0 = 0,
         Key_Name_Key1,
         Key_Name_Key2,
         Key_Name_WKUP,
         Key_Name_Max
     }EnumKeyOneName;
     
    /* USER CODE END Private defines */
    
    void MX_GPIO_Init(void);
    
    /* USER CODE BEGIN Prototypes */
    void Key0_Down_Task(void);
        
    void Key0_Up_Task(void);
        
    void Key1_Down_Task(void);
    
    void Key1_Up_Task(void);
    
    void Key2_Down_Task(void);
    
    void Key2_Up_Task(void);
    
    void WKUP_Down_Task(void);
    
    void WWKUP_Up_Task(void);
    
    
    void Key_One_Scan(uint8_t KeyName ,void(*OnKeyOneUp)(void), void(*OnKeyOneDown)(void)); 
    /* USER CODE END Prototypes */
    
    #ifdef __cplusplus
    }
    #endif
    #endif /*__ GPIO_H__ */
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46

    思路分析

    按键按下的过程中,难免会有抖动,但是抖动的时间通常在10~20ms之间,而按键按下的过程往往会持续100ms以上,所以我们可以每10ms扫描一次按键状态,如果连续八次都是按下,则认为按键已经按下,想要的执行认为即可。反之,如果八次都是松开,则认为按键松开了,也可以执行按键松开的相关任务。

    具体细节,大家看代码吧,这个只是大体思路,代码还有很多精彩的地方。

  • 相关阅读:
    关于Maven,你真的了解它吗?
    8、统一处理异常(控制器通知@ControllerAdvice全局配置类、@ExceptionHandler统一处理异常)
    入坑机器学习:四,单变量线性回归
    存储技术知识分享
    websocket详解
    OSPFv2 和 OSPFv3 的区别?
    MySqL表的约束
    麒麟v10安装mysql(ARM架构)
    nginx根据不同的客户端设备进行转发请求——筑梦之路
    【LLM多模态】Qwen-VL模型结构和训练流程
  • 原文地址:https://blog.csdn.net/weixin_67907028/article/details/134469752