僚娥 发表于 2025-11-23 12:45:00

STM32按键扫描

外部中断


[*]此方法需注意Timer base(也就是HAL_Delay)的优先级,要低于外部中断的优先级,否则会卡死,推荐下面状态机实现的方法。

[*]上拉输入:
if (HAL_GPIO_ReadPin(KEY_GPIO_Port, KEY_Pin) == GPIO_PIN_SET) {// 按下
            OSTimeDly(20,OS_OPT_TIME_DLY,&err); // 去抖
            if (HAL_GPIO_ReadPin(KEY_GPIO_Port, KEY_Pin) == GPIO_PIN_SET) {
                /* 用户代码 */
            }
      }
[*]长按短按:
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 {
                /* 短按 */
            }
      }
    }状态机实现多按键短按长按

key.c
#include "key.h"

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

Key_t keys;

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

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

    keys.port = KEY_R_GPIO_Port;
    keys.pin = KEY_R_Pin;
    keys.active_level = 0;
    keys.state = KEY_IDLE;
    keys.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.port, keys.pin) == keys.active_level)
      {
            current_level = 1;
      }
      else current_level = 0;

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

      case KEY_HOLD:
            if (current_level)
            {
                if (HAL_GetTick() - keys.timer >= LONG_PRESS_TIME)
                {
                  result.key_id = i;
                  result.event = KEY_EVENT_LONG;
                  keys.state = KEY_RELEASED;
                  keys.last_level = current_level;
                  return result;// 长按事件
                }
            }
            else
            {
                if (HAL_GetTick() - keys.timer >= DEBOUNCE_TIME)
                {
                  result.key_id = i;
                  result.event = KEY_EVENT_SHORT;
                  keys.state = KEY_IDLE;
                  keys.last_level = current_level;
                  return result;// 短按事件
                }
                else keys.state = KEY_RELEASED;
            }
            break;
      
      case KEY_RELEASED:
            if (!current_level && (HAL_GetTick() - keys.timer >= DEBOUNCE_TIME))
            {
                keys.state = KEY_IDLE;
            }
            break;
      
      
      default:
            break;
      }
      keys.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;
      }
    }
}
}
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

莘度 发表于 2025-11-30 01:07:20

懂技术并乐意极积无私分享的人越来越少。珍惜

崔瑜然 发表于 7 天前

感谢分享,下载保存了,貌似很强大

咫噎 发表于 3 天前

谢谢分享,试用一下

呵桢 发表于 前天 14:31

谢谢分享,辛苦了
页: [1]
查看完整版本: STM32按键扫描