07. 按键输入

一、按键简介

  常态下,独立按键是断开的,按下的时候才闭合。每个独立按键会单独占用一个 IO 口,通过 IO 口的高低电平判断按键的状态。但是按键在闭合和断开的时候,都存在抖动现象,即按键在闭合时不会马上就稳定的连接,断开时也不会马上断开。这是机械触点,无法避免。

  按键抖动波形图如下:

按键抖动波形图

  按下抖动和释放抖动的时间一般为 5~10ms,如果在抖动阶段采样,其不稳定状态可能出现一次按键动作被认为是多次按下的情况。为了避免抖动可能带来的误操作,我们要做的措施就是给按键消抖(即采样稳定闭合阶段)。消抖方法分为硬件消抖和软件消抖,我们常用软件的方法消抖。

  软件消抖,最简单的延时消抖。检测到按键按下后,一般进行10ms 延时,用于跳过抖动的时间段,如果消抖效果不好可以调整这个 10ms 延时,因为不同类型的按键抖动时间可能有偏差。待延时过后再检测按键状态,如果没有按下,那我们就判断这是抖动或者干扰造成的;如果还是按下,那么我们就认为这是按键真的按下了。对按键释放的判断同理。

  硬件消抖,利用 RC 电路的电容充放电特性来对抖动产生的电压毛刺进行平滑出来,从而实现消抖,但是成本会更高一点。

二、原理图

K_UP按键

KEY_UP按键引脚接线图

按键模块

按键模块引脚接线图

  K1、K2 和 K3 设计为采样到按键另一端的低电平为有效电平,而 KEY_UP 则需要采样到高电平才为按键有效,并且按键外部没有上下拉电阻,所以需要在 STM32F407 内部设置上下拉。因此,K1、K2 和 K3 配置为 上拉输入,KEY_UP 配置为 下拉输入

三、程序源码

#define WKUP_GPIO_PORT          GPIOA
#define WKUP_GPIO_PIN           GPIO_PIN_0
#define WKUP_GPIO_CLK_ENABLE()  do{ \
                                    __HAL_RCC_GPIOA_CLK_ENABLE(); \
                                } while(0);

#define KEY1_GPIO_PORT          GPIOE
#define KEY1_GPIO_PIN           GPIO_PIN_4
#define KEY1_GPIO_CLK_ENABLE()  do{ \
                                    __HAL_RCC_GPIOE_CLK_ENABLE(); \
                                } while(0);

#define KEY2_GPIO_PORT          GPIOE
#define KEY2_GPIO_PIN           GPIO_PIN_3
#define KEY2_GPIO_CLK_ENABLE()  do{ \
                                    __HAL_RCC_GPIOE_CLK_ENABLE(); \
                                } while(0);

#define KEY3_GPIO_PORT          GPIOE
#define KEY3_GPIO_PIN           GPIO_PIN_2
#define KEY3_GPIO_CLK_ENABLE()  do{ \
                                    __HAL_RCC_GPIOE_CLK_ENABLE(); \
                                } while(0);

/* 读取按键对应的GPIO引脚的电平状态 */
#define WK_UP                   HAL_GPIO_ReadPin(WKUP_GPIO_PORT, WKUP_GPIO_PIN)
#define KEY1                    HAL_GPIO_ReadPin(KEY1_GPIO_PORT, KEY1_GPIO_PIN)
#define KEY2                    HAL_GPIO_ReadPin(KEY2_GPIO_PORT, KEY2_GPIO_PIN)
#define KEY3                    HAL_GPIO_ReadPin(KEY3_GPIO_PORT, KEY3_GPIO_PIN)


/* 对应按键按下时代表的数值 */
#define WKUP_PRESS              1
#define KEY1_PRESS              2
#define KEY2_PRESS              3
#define KEY3_PRESS              4

  按键初始化函数内容如下:

/**
 * @brief 按键初始化函数
 * 
 */
void Key_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStruct = {0};

    WKUP_GPIO_CLK_ENABLE();
    KEY1_GPIO_CLK_ENABLE();
    KEY2_GPIO_CLK_ENABLE();
    KEY3_GPIO_CLK_ENABLE();

    GPIO_InitStruct.Pin = WKUP_GPIO_PIN;                                        // GPIO引脚
    GPIO_InitStruct.Mode = GPIO_MODE_INPUT;                                     // 输入模式
    GPIO_InitStruct.Pull = GPIO_PULLDOWN;                                       // 使用下拉
    HAL_GPIO_Init(WKUP_GPIO_PORT, &GPIO_InitStruct);                            // GPIO初始化

    GPIO_InitStruct.Pin = KEY1_GPIO_PIN;                                        // GPIO引脚
    GPIO_InitStruct.Pull = GPIO_PULLUP;                                         // 使用上拉
    HAL_GPIO_Init(KEY1_GPIO_PORT, &GPIO_InitStruct);                            // GPIO初始化

    GPIO_InitStruct.Pin = KEY2_GPIO_PIN;                                        // GPIO引脚
    HAL_GPIO_Init(KEY2_GPIO_PORT, &GPIO_InitStruct);                            // GPIO初始化

    GPIO_InitStruct.Pin = KEY3_GPIO_PIN;                                        // GPIO引脚
    HAL_GPIO_Init(KEY3_GPIO_PORT, &GPIO_InitStruct);                            // GPIO初始化
}

  按键扫描函数内容如下:

/**
 * @brief 按键扫描函数
 * 
 * @param mode 0:不支持连续按;1:支持连续按
 * @return uint8_t WKUP_PRES,1,WKUP按下
 *                 KEY1_PRES,2,KEY0按下
 *                 KEY2_PRES,3,KEY1按下
 *                 KEY3_PRES,4,KEY2按下
 * 
 * @note 该函数有响应优先级,同时按下多个按键:WK_UP > KEY3 > KEY2 > KEY1
 */
uint8_t Key_Scan(uint8_t mode)
{
    static uint8_t flag = 1;                                                    // 按键按松开标志
    uint8_t keyValue = 0;

    flag = (mode ? 1 : flag);                                                   // 支持连按

    if (flag && ( WK_UP == 1 || KEY1 == 0 || KEY2 == 0 || KEY3 == 0))           // 按键松开标志为1, 且有任意一个按键按下了
    {
        HAL_Delay(10);                                                          // 按键消抖
        flag = 0;               
        // 再次读取GPIO引脚的电平
        keyValue = ((KEY1 == 0) ? KEY1_PRESS : keyValue);
        keyValue = ((KEY2 == 0) ? KEY2_PRESS : keyValue);
        keyValue = ((KEY3 == 0) ? KEY3_PRESS : keyValue);
        keyValue = ((WK_UP == 1) ? WKUP_PRESS : keyValue);              
    }
    else if (WK_UP == 0 && KEY1 == 1 && KEY2 == 1 && KEY3 == 1)                 // 没有任何按键按下, 标记按键松开
    {
        flag = 1;
    }

    return keyValue;                                                            // 按键没有按下返回0 
}

  main() 函数内容如下:

int main(void)
{
    HAL_Init();
    System_Clock_Init(8, 336, 2, 7);
    Delay_Init(168);

    LED_Init();
    Key_Init();

    while (1)
    {
        switch (Key_Scan(0))
        {
        case WKUP_PRESS:
            HAL_GPIO_WritePin(GPIOF, GPIO_PIN_9, GPIO_PIN_RESET);               // 点亮LED1
            break;
  
        case KEY1_PRESS:
            HAL_GPIO_WritePin(GPIOF, GPIO_PIN_10, GPIO_PIN_RESET);              // 点亮LED2
            break;

        case KEY2_PRESS:
            HAL_GPIO_WritePin(GPIOF, GPIO_PIN_9, GPIO_PIN_SET);                 // 熄灭LED1
            break;

        case KEY3_PRESS:
            HAL_GPIO_WritePin(GPIOF, GPIO_PIN_10, GPIO_PIN_SET);                // 熄灭LED2
            break;
        }
    }
  
    return 0;
}
posted @ 2023-11-02 18:08  星光樱梦  阅读(52)  评论(0编辑  收藏  举报