按键硬件连接

按键通过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

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐