用数电真值表看嵌入式按键扫描

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/a1598025967/article/details/81904095

 在嵌入式系统中,一定要遵循能不用阻塞延时就不用阻塞延时的规则,通常在裸机系统中,很多人处理按键还是用非常老的延时消抖方法,这样的程序不仅可移植性非常差,而且程序效率也非常的低,在多任务系统中,按键状态机是用得比较多的方法,不过状态机的逻辑和程序复杂度都挺高的,今天就给大家介绍一种类似于数电里面学的真值表方法来处理按键,按键只有两种电平状态0/1(模拟的除外),这两种状态的组合逻辑就有四种,分别为00,01,10,11,如果我们约定未按下为0,按下为1,可以形成以下“真值表”;

未按下 按下 状态
0 0 释放
0 1 按下
1 0 弹起
1 1 任然按下

根据真值表我们可以在程序中把按键的这两个电平状态记为上一次状态和当前状态,那么代码就很简单了,这里以STM32为例,其它平台稍微实现下底层就行了。

#ifndef __KEY_H
#define __KEY_H

#include "stm32f1xx_hal.h"
#include "stdbool.h"

#define KEY1_GPIO_PORT    GPIOA
#define KEY1_GPIO_PIN     GPIO_PIN_0
#define KEY1_ON           GPIO_PIN_SET  /*按键按下的有效电平*/

#define KEY2_GPIO_PORT    GPIOC
#define KEY2_GPIO_PIN     GPIO_PIN_13
#define KEY2_ON           GPIO_PIN_RESET

/*按键编号枚举*/
typedef enum {
  KEY1,
  KEY2,
  KEY_NUM_CNT,/*按键数量统计*/
}keyNumType_e;

/*按键状态枚举值*/
typedef enum {
  KEY_PRESS,            /*按下*/
  KEY_PRESSING,        /*任然按下*/
  KEY_RELEASE,        /*弹起*/
  KEY_NONE,                /*未按下*/
}keyState_t;

/*按键参数相关结构体*/
typedef struct _keyPara_t{
  keyState_t keyState;
  bool keyLastStatus;
  bool keyThisStatus;
}keyPara_t;

/*按键 GPIO 初始化*/
void KEY_GPIO_InitCfg(void);
/*扫描按键(按一定周期调用)*/
void KeyScan(void);
/*获取按键状态*/
keyState_t GetKeyState(keyNumType_e keyNum);

#endif/*__KEY_H*/
#include "./KEY/key.h"

/*定义一个按键参数结构体数组*/
keyPara_t aKeyPara[KEY_NUM_CNT];

/*按键 GPIO 初始化*/
void KEY_GPIO_InitCfg(void)
{
  __HAL_RCC_GPIOA_CLK_ENABLE();
  __HAL_RCC_GPIOC_CLK_ENABLE();
  
  GPIO_InitTypeDef GPIO_InitStruct;
  
  GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
  
  GPIO_InitStruct.Pull = GPIO_PULLDOWN;
  GPIO_InitStruct.Pin = KEY1_GPIO_PIN;
  HAL_GPIO_Init(KEY1_GPIO_PORT,&GPIO_InitStruct);
  
  GPIO_InitStruct.Pull = GPIO_PULLUP;
  GPIO_InitStruct.Pin = KEY2_GPIO_PIN;
  HAL_GPIO_Init(KEY2_GPIO_PORT,&GPIO_InitStruct);
}

/*按键端口*/
GPIO_TypeDef *aKEY_Gpio_Port[]  = {KEY1_GPIO_PORT,
                                    KEY2_GPIO_PORT};
/*按键引脚*/
uint16_t aKEY_Gpio_Pin[]        = {KEY1_GPIO_PIN,
                                    KEY2_GPIO_PIN};
/*按键有效状态*/
GPIO_PinState aKEY_On_State[]   = {KEY1_ON,
                                    KEY2_ON};

/*获取按键 I/O 电平*/
bool CbGetKeyIoStatus(keyNumType_e keyNum)
{
  if(HAL_GPIO_ReadPin(aKEY_Gpio_Port[keyNum],aKEY_Gpio_Pin[keyNum]) == aKEY_On_State[keyNum])
  {
    return true;
  }
  return false;
}

/*检查按键是否有效*/
bool CheckKeyIfValid(bool (*pCbGetKeyIoStatus)(keyNumType_e ),keyNumType_e keyNum)
{
  return pCbGetKeyIoStatus(keyNum);
}

/*扫描按键(按一定周期调用)*/
void KeyScan(void)
{
  for(uint32_t i = 0;i<sizeof(aKeyPara) / sizeof(keyPara_t);i++)
  {
    aKeyPara[i].keyThisStatus = CheckKeyIfValid(CbGetKeyIoStatus,(keyNumType_e)i);
    
    if ( (aKeyPara[i].keyThisStatus) && (!(aKeyPara[i].keyLastStatus)) )//10
    {
      aKeyPara[i].keyState = KEY_PRESS;
    }
    else if ( (!(aKeyPara[i].keyThisStatus)) && aKeyPara[i].keyLastStatus )//01
    {
      aKeyPara[i].keyState = KEY_RELEASE;
    }
    else if( aKeyPara[i].keyThisStatus && aKeyPara[i].keyLastStatus )//11
    {
      aKeyPara[i].keyState = KEY_PRESSING;
    }
    else if( (!(aKeyPara[i].keyThisStatus)) && (!(aKeyPara[i].keyLastStatus)) )//00
    {
      aKeyPara[i].keyState = KEY_NONE;
    }

    aKeyPara[i].keyLastStatus = aKeyPara[i].keyThisStatus; 
  }
}

/*获取按键状态*/
keyState_t GetKeyState(keyNumType_e keyNum)
{
    return aKeyPara[keyNum].keyState;
}

/*用户函数*/
void UserFun(void)
{
    if(GetKeyState(KEY1) == KEY_PRESS)
    {
        /*do something*/
    }
    if(GetKeyState(KEY1) == KEY_RELEASE)
    {
        /*do something*/
    }
    if(GetKeyState(KEY1) == KEY_PRESSING)
    {
        /*do something*/
    }

    if(GetKeyState(KEY2) == KEY_PRESS)
    {
        /*do something*/
    }
    if(GetKeyState(KEY2) == KEY_RELEASE)
    {
        /*do something*/
    }
    if(GetKeyState(KEY2) == KEY_PRESSING)
    {
        /*do something*/
    }
}

核心代码就在keyScan();函数中,简单几句就可以实现,主要是原理懂了就行,代码仅自己用,并没有做过多的封装和降低耦合度,第一次写文章,文笔限制,如果读者有不懂的地方还请留言,欢迎吐槽。

猜你喜欢

转载自blog.csdn.net/a1598025967/article/details/81904095