STM32 按键(单击长按)外部中断+定时器处理方式
·
按键硬件连接
按键通过GPIO口与STM32连接。引脚在CUBEMX中配置为外部中断+上拉,采用上升沿/下降沿双重触发。使用TIM7定时器开启中断进行长按检测,按键按下时定时器启动开始计时,按键释放时根据定时器记录的时间来判断单击还是长按。
按键事件处理流程
- 按键检测:通过外部中断检测按键状态变化
- 状态更新:更新按键的stable_state和press_ticks
- 事件判断:根据按下时间判断短按或长按事件
- 业务处理:调用相应的回调函数执行具体业务逻辑
一句话概括就是,外部中断下降沿触发,引起中断开始定时器计时,松手上升沿再进入中断,根据定时器的计时来判断长按还是单击,整套流程最大的特点就是几乎不占用CPU。

高级定时器timer1, timer8以及通用定时器timer9, timer10, timer11的时钟来源是APB2总线
通用定时器timer2~timer5,通用定时器timer12~timer14以及基本定时器timer6,timer7的时钟来源是APB1总线(APB1 84MHZ;APB2 168MHZ)
当前使用TIM7 配置为10ms一次中断


按键状态管理
每个按键对应一个KeyObj_t结构体,记录当前状态、按下时间、事件标志等。按键总数由KEY_COUNT宏定义(值为4),支持多键并发管理并采用状态机模式,通过stable_state记录按键的稳定状态。使用press_ticks计数器实现长按检测和消抖处理;通过event_sent标志防止按键事件重复触发。同时在key.h文件支持通过宏定义来修改长按判定时间。
结构体部分
typedef struct {
GPIO_TypeDef *port;
uint16_t pin;
uint8_t stable_state; // 1=释放(高),0=按下(低)
uint16_t press_ticks; // 按下持续时间计数(以 KEY_TICK_MS 单位)
uint8_t event_sent; // 事件是否已发送(防止重复触发)
} KeyObj_t;
static KeyObj_t keys[KEY_COUNT] = {
{GPIOA, GPIO_PIN_15, 1, 0, 0}, // B1
{GPIOA, GPIO_PIN_12, 1, 0, 0}, // B2
{GPIOA, GPIO_PIN_11, 1, 0, 0}, // B3
{GPIOC, GPIO_PIN_10, 1, 0, 0} // B4
};
初始化函数(记得在main.c加上)
// 初始化按键模块并启动定时器中断(若需要可由外部启动)
void key_init(void)
{
for (int i = 0; i < KEY_COUNT; i++) {
GPIO_PinState level = HAL_GPIO_ReadPin(keys[i].port, keys[i].pin);
keys[i].stable_state = (level == GPIO_PIN_RESET) ? 0 : 1;
keys[i].press_ticks = 0;
keys[i].event_sent = 0;
}
// 启动 TIM7 中断(确保 CubeMX 已配置并生成 htim7)
HAL_TIM_Base_Start_IT(&htim7);
}
中断部分
// EXTI 回调:在 HAL_GPIO_EXTI_Callback 中调用此函数进行转发
void key_exti_callback(uint16_t GPIO_Pin)
{
for (int i = 0; i < KEY_COUNT; i++) {
if (keys[i].pin == GPIO_Pin) {
// 读取当前GPIO状态
GPIO_PinState level = HAL_GPIO_ReadPin(keys[i].port, keys[i].pin);
uint8_t cur = (level == GPIO_PIN_RESET) ? 0 : 1;
// 状态发生变化时处理
if (cur != keys[i].stable_state) {
if (cur == 0) {
// 按键按下(下降沿):开始计时
keys[i].stable_state = 0;
keys[i].press_ticks = 0;
keys[i].event_sent = 0;
} else {
// 按键释放(上升沿):根据按下时间判断是短按还是长按
if (!keys[i].event_sent) {
if (keys[i].press_ticks * KEY_TICK_MS >= KEY_LONGPRESS_MS) {
// 长按事件(200ms以上)
key_long_press_callback((KeyID_t)i);
} else {
// 短按事件(200ms以下)
key_short_press_callback((KeyID_t)i);
}
keys[i].event_sent = 1; // 防止重复触发
}
keys[i].stable_state = 1;
keys[i].press_ticks = 0; // 清零计时器,准备下次按键
}
}
break;
}
}
}
// 定时器周期处理(在 TIM 中断上下文执行),建议周期为 KEY_TICK_MS
void key_timer_tick(void)
{
for (int i = 0; i < KEY_COUNT; i++) {
KeyObj_t *k = &keys[i];
// 只负责计时,不生成事件
if (k->stable_state == 0) { // 按键按下状态
k->press_ticks++;
}
}
}
// 将该函数放入 HAL_TIM_PeriodElapsedCallback 中或替换工程中的该回调以转发到 key_timer_tick
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if (htim == &htim7) {
key_timer_tick();
}
}
处理函数
static void key_short_press_callback(KeyID_t id)
{
switch (id) {
case KEY_B1: // 按键1
//这部分是按键1单击逻辑
break;
case KEY_B2: // 按键2
//这部分是按键2单击逻辑
break;
}
}
static void key_long_press_callback(KeyID_t id)
{
switch (id) {
case KEY_B1: // 按键1长按
//这部分是按键1长按逻辑
break;
case KEY_B2: // 按键2长按
//这部分是按键2长按逻辑
break;
}
}
KEY.H部分
#ifndef _KEY_H_
#define _KEY_H_
#include "stm32f4xx.h"
// 按键映射(硬件引脚)
typedef enum {
KEY_B1 = 0, // PA15
KEY_B2, // PA12
KEY_B3, // PA11
KEY_B4, // PC10
KEY_COUNT
} KeyID_t;
// 定时与判定参数(可按需修改)
#ifndef KEY_TICK_MS
#define KEY_TICK_MS 10U // 定时器周期 (ms)改动记得也在TIM那里修改一下
#endif
#ifndef KEY_LONGPRESS_MS
#define KEY_LONGPRESS_MS 200U // 长按判定 (ms)
#endif
// 对外接口
void key_init(void); // 初始化(启动定时器中断由用户配置)
void key_exti_callback(uint16_t GPIO_Pin); // EXTI 中断转发到此函数
void key_timer_tick(void); // 定时器周期处理(由 HAL_TIM_PeriodElapsedCallback 调用)
#endif
更多推荐
所有评论(0)