STM32按键扫描
外部中断
此方法需注意Timer base(也就是HAL_Delay)的优先级,要低于外部中断的优先级,要么将HAL_Delay删除,否则会卡死。推荐使用状态机实现的方法。
- 上拉输入:
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) {
/* 用户代码 */
}
}
- 长按短按:
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
使用
- 修改
key.h中定义按键数量、按键长按时间、按键消抖时间
#define KEY_NUM 3 // 按键数量
#define LONG_PRESS_TIME 1000 // 长按时间
#define DEBOUNCE_TIME 20 // 消抖时间
- 添加按键接口参数
首先,修改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;
}
}
}
}

浙公网安备 33010602011771号