STM32按键扫描

外部中断

此方法需注意Timer base(也就是HAL_Delay)的优先级,要低于外部中断的优先级,要么将HAL_Delay删除,否则会卡死。推荐使用状态机实现的方法。

  1. 上拉输入:
if (HAL_GPIO_ReadPin(KEY_GPIO_Port, KEY_Pin) == GPIO_PIN_SET) {  // 按下
            HAL_Delay(20); // 去抖
            if (HAL_GPIO_ReadPin(KEY_GPIO_Port, KEY_Pin) == GPIO_PIN_SET) {
                /* 用户代码 */
            }
        }
  1. 长按短按:
if (HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_12) == GPIO_PIN_RESET) {  // 按下
        HAL_Delay(20);  // 去抖
        if (HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_12) == GPIO_PIN_RESET) {  
            HAL_Delay(500);  // 判断长按
            if (HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_12) == GPIO_PIN_RESET) {  
                /* 长按 */ 
            } 
            else {
                /* 短按 */
            }
        }
    }

状态机实现多按键短按长按

功能

可以多按键扫描,检测所按下按键id和触发事件event

id为0、1、2、3、4的按键,检测按键短按事件为KEY_EVENT_SHORT,长按事件为KEY_EVENT_LONG

使用

  1. 修改 key.h中定义按键数量、按键长按时间、按键消抖时间
#define KEY_NUM         3     // 按键数量
#define LONG_PRESS_TIME 1000  // 长按时间
#define DEBOUNCE_TIME   20    // 消抖时间
  1. 添加按键接口参数
    首先,修改key.c中初始化按键代码
变量 含义
keys[x].port GPIO端口
keys[x].pin GPIO引脚
keys[x].active_level 按下时的有效电平(true: 高电平, false: 低电平)
keys[x].last_level 取反`active_level

key.c

#include "key.h"

#define KEY_NUM 3
#define LONG_PRESS_TIME 1000
#define DEBOUNCE_TIME 20

Key_t keys[KEY_NUM];

/**
 * @brief 初始化按键接口参数
 * @param active_level 有效电平
 */
void Key_Init(void)
{
    keys[0].port = KEY_L_GPIO_Port;
    keys[0].pin = KEY_L_Pin;
    keys[0].active_level = 1;
    keys[0].state = KEY_IDLE;
    keys[0].last_level = 0;

    keys[1].port = KEY_M_GPIO_Port;
    keys[1].pin = KEY_M_Pin;
    keys[1].active_level = 0;
    keys[1].state = KEY_IDLE;
    keys[1].last_level = 1;

    keys[2].port = KEY_R_GPIO_Port;
    keys[2].pin = KEY_R_Pin;
    keys[2].active_level = 0;
    keys[2].state = KEY_IDLE;
    keys[2].last_level = 1;
}

/**
 * @brief 按键扫描函数
 * @retval result.key_id    按键id
 * @retval result.event     事件类型
 */
KeyEvent_t Key_Scan(void)
{
    KeyEvent_t result = {0, KEY_EVENT_NONE};

    for(uint8_t i = 0; i < KEY_NUM; i++)
    {
        bool current_level;
        if (HAL_GPIO_ReadPin(keys[i].port, keys[i].pin) == keys[i].active_level)
        {
            current_level = 1;
        }
        else current_level = 0;

        switch (keys[i].state)
        {
        case KEY_IDLE:
            if (current_level && !keys[i].last_level)
            {
                keys[i].state = KEY_PRESSED;
                keys[i].timer = HAL_GetTick();
            }
            break;
           
        case KEY_PRESSED:
            if (current_level)
            {
                if (HAL_GetTick() - keys[i].timer >= DEBOUNCE_TIME)
                {
                    keys[i].state = KEY_HOLD;
                } 
            }
            else keys[i].state = KEY_IDLE;
            break;

        case KEY_HOLD:
            if (current_level)
            {
                if (HAL_GetTick() - keys[i].timer >= LONG_PRESS_TIME)
                {
                    result.key_id = i;
                    result.event = KEY_EVENT_LONG;
                    keys[i].state = KEY_RELEASED;
                    keys[i].last_level = current_level;
                    return result;  // 长按事件
                }
            }
            else
            {
                if (HAL_GetTick() - keys[i].timer >= DEBOUNCE_TIME)
                {
                    result.key_id = i;
                    result.event = KEY_EVENT_SHORT;
                    keys[i].state = KEY_IDLE;
                    keys[i].last_level = current_level;
                    return result;  // 短按事件
                }
                else keys[i].state = KEY_RELEASED;
            }
            break;
        
        case KEY_RELEASED:
            if (!current_level && (HAL_GetTick() - keys[i].timer >= DEBOUNCE_TIME))
            {
                keys[i].state = KEY_IDLE;
            }
            break;
        
        
        default:
            break;
        }
        keys[i].last_level = current_level;
    }
    return result;
}

key.h

#ifndef __KEY_H
#define __KEY_H

#include "main.h"

#include <stdbool.h>

// 按键状态枚举
typedef enum {
    KEY_IDLE,      // 空闲
    KEY_PRESSED,   // 按下(去抖中)
    KEY_HOLD,      // 持续按下
    KEY_RELEASED   // 释放(去抖中)
} KeyState;

// 按键事件枚举
typedef enum {
    KEY_EVENT_NONE,   // 无事件
    KEY_EVENT_SHORT,  // 短按
    KEY_EVENT_LONG    // 长按
} KeyEvent;

// 按键事件返回结构体
typedef struct {
    uint8_t key_id;   // 按键ID
    KeyEvent event;   // 事件类型
} KeyEvent_t;

// 按键结构体
typedef struct {
    GPIO_TypeDef* port;      // GPIO端口
    uint16_t pin;            // GPIO引脚
    bool active_level;       // 按下时的有效电平(true: 高电平, false: 低电平)
    KeyState state;          // 当前状态
    uint32_t timer;          // 计时器
    bool last_level;         // 上次电平
} Key_t;

void Key_Init(void);
KeyEvent_t Key_Scan(void);



#endif

实现方法

main.c文件中:

#include "key.h"

KeyEvent_t key;

void main(void)
{
  Key_Init();
  while (1)
  {
    key = Key_Scan();
    if (key.event != KEY_EVENT_NONE)
    {
      switch (key.event)
      {
      case KEY_EVENT_SHORT:
        printf("key %d short press\n", key.key_id);
        break;

      case KEY_EVENT_LONG:
        printf("key %d long press\n", key.key_id);
        break;

      default:
        break;
      }
    }
  }
}
posted @ 2025-11-23 12:20  流水灯明  阅读(15)  评论(0)    收藏  举报