一、前言

在 STM32 嵌入式开发中,按键是最基础的人机交互外设之一,而单纯的按键电平检测往往无法满足复杂的交互需求(如单击、双击、长按)。本文基于 STM32F407 芯片,结合 HAL 库实现了按键的外部中断驱动,不仅解决了按键消抖问题,还区分了单击、双击、长按三种事件,并联动 LED 和蜂鸣器完成功能演示,代码模块化程度高,可直接移植到同类项目中。

二、硬件设计

1. 硬件资源

  • 主控芯片:STM32F407ZGT6
  • 按键:KEY1(PA0)、KEY2(PC13),均采用外部中断触发方式
  • LED:RGB 三色灯(LED1-PF6、LED2-PF7、LED3-PF8),推挽输出
  • 蜂鸣器:PG7 引脚,推挽输出控制

2. 硬件原理

  • 按键采用 “高低电平触发”(上升沿 + 下降沿),通过外部中断实现按键状态的快速响应;
  • LED 和蜂鸣器均为 GPIO 推挽输出,高 / 低电平分别对应 “亮 / 灭”“响 / 停”(具体电平逻辑见代码宏定义);
  • 所有外设时钟均独立使能,保证资源按需分配。

三、软件设计

1. 工程结构

工程采用模块化设计,核心文件分工清晰:

plaintext

├── Inc/

│   ├── main.h          // 全局头文件

│   ├── bsp_led.h       // LED驱动头文件

│   ├── bsp_key.h       // 按键驱动头文件

│   └── bsp_buzzer.h    // 蜂鸣器驱动头文件

├── Src/

│   ├── main.c          // 主函数(逻辑控制)

│   ├── bsp_led.c       // LED初始化及操作

│   ├── bsp_key.c       // 按键中断及事件解析

│   └── bsp_buzzer.c    // 蜂鸣器初始化及操作

2. 核心模块实现

(1)LED 驱动(bsp_led.c/.h)

LED 驱动的核心是 GPIO 初始化,采用推挽输出、上拉模式,通过宏定义封装 LED 的亮 / 灭 / 翻转操作,简化代码调用:

// bsp_led.h 核心宏定义

#define ON  GPIO_PIN_RESET

#define OFF GPIO_PIN_SET

#define LED1(a) HAL_GPIO_WritePin(LED1_GPIO_PORT,LED1_PIN,a)

#define LED1_TOGGLE digitalToggle(LED1_GPIO_PORT,LED1_PIN

)#define LED_RGBOFF LED1_OFF;LED2_OFF;LED3_OFF

// bsp_led.c 初始化函数void LED_GPIO_Config(void){

    GPIO_InitTypeDef  GPIO_InitStruct;

    // 使能时钟

    LED1_GPIO_CLK_ENABLE();

    LED2_GPIO_CLK_ENABLE();

    LED3_GPIO_CLK_ENABLE();

    // 配置LED1引脚

    GPIO_InitStruct.Pin = LED1_PIN;

    GPIO_InitStruct.Mode  = GPIO_MODE_OUTPUT_PP;   // 推挽输出

    GPIO_InitStruct.Pull  = GPIO_PULLUP;           // 上拉

    GPIO_InitStruct.Speed = GPIO_SPEED_HIGH;       // 高速

    HAL_GPIO_Init(LED1_GPIO_PORT, &GPIO_InitStruct);

    // LED2、LED3同理...

    LED_RGBOFF; // 默认关闭所有LED}

(2)蜂鸣器驱动(bsp_buzzer.c/.h)

蜂鸣器驱动逻辑与 LED 一致,仅引脚不同(PG7),通过宏定义封装 “响 / 停 / 翻转” 操作:

// bsp_buzzer.h 核心宏定义#define BUZZER_ON       HAL_GPIO_WritePin(BUZZER_GPIO_PORT, BUZZER_GPIO_PIN, GPIO_PIN_SET)

#define BUZZER_OFF      HAL_GPIO_WritePin(BUZZER_GPIO_PORT, BUZZER_GPIO_PIN, GPIO_PIN_RESET)

// bsp_buzzer.c 初始化函数void Buzzer_GPIO_Config(void){

    GPIO_InitTypeDef GPIO_InitStruct;

    BUZZER_GPIO_CLK_ENABLE();  // 使能GPIOG时钟

    GPIO_InitStruct.Pin = BUZZER_GPIO_PIN;

    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;   // 推挽输出

    GPIO_InitStruct.Pull = GPIO_NOPULL;

    GPIO_InitStruct.Speed = GPIO_SPEED_HIGH;

    HAL_GPIO_Init(BUZZER_GPIO_PORT, &GPIO_InitStruct);

    BUZZER_OFF;  // 默认关闭蜂鸣器}

(3)按键中断驱动(bsp_key.c/.h)

这是本文核心模块,实现了消抖、中断触发、事件解析(单击 / 双击 / 长按) 三大功能:

① 宏定义与枚举(bsp_key.h):

// 按键引脚定义

#define KEY1_PIN                  GPIO_PIN_0                

 #define KEY1_GPIO_PORT            GPIOA                      

#define KEY2_PIN                  GPIO_PIN_13                 

#define KEY2_GPIO_PORT            GPIOC                      

// KEY2事件枚举typedef enum{

    KEY2_NONE = 0,

    KEY2_SINGLE,  // 单击

    KEY2_DOUBLE,  // 双击

    KEY2_LONG     // 长按}KEY2_EventDef;

② 中断初始化(bsp_key.c):配置 GPIO 为 “上升沿 + 下降沿” 中断触发,设置 NVIC 优先级并使能中断:

void Key_EXTI_Config(void){

    GPIO_InitTypeDef GPIO_InitStruct;

    KEY1_GPIO_CLK_ENABLE();

    KEY2_GPIO_CLK_ENABLE();

    // 配置为中断模式(上升沿+下降沿)

    GPIO_InitStruct.Mode = GPIO_MODE_IT_RISING_FALLING;

    GPIO_InitStruct.Pull = GPIO_NOPULL;

    GPIO_InitStruct.Speed = GPIO_SPEED_HIGH;

    // KEY1(PA0)初始化

    GPIO_InitStruct.Pin = KEY1_PIN;

    HAL_GPIO_Init(KEY1_GPIO_PORT, &GPIO_InitStruct);

    // KEY2(PC13)初始化

    GPIO_InitStruct.Pin = KEY2_PIN;

    HAL_GPIO_Init(KEY2_GPIO_PORT, &GPIO_InitStruct);

    // 配置NVIC

    HAL_NVIC_SetPriority(EXTI0_IRQn, 2, 0);    // KEY1对应EXTI0

    HAL_NVIC_EnableIRQ(EXTI0_IRQn);

    HAL_NVIC_SetPriority(EXTI15_10_IRQn, 2, 0); // KEY2对应EXTI15_10

    HAL_NVIC_EnableIRQ(EXTI15_10_IRQn);}

③ 中断回调与事件解析(bsp_key.c):

  • 消抖:通过HAL_GetTick()判断两次中断的时间差,过滤抖动(1ms);
  • KEY1:直接控制蜂鸣器(按下响、松开停);
  • KEY2:通过计时区分单击(400ms 内仅 1 次按下)、双击(400ms 内 2 次按下)、长按(按下超过 500ms):

void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin){

    uint32_t current_tick = HAL_GetTick();

    // KEY1消抖+蜂鸣器控制

    if(GPIO_Pin == KEY1_PIN)

    {

        if(current_tick - key1_tick > DEBOUNCE_MS)

        {

            key1_tick = current_tick;

            BUZZER_ON = (HAL_GPIO_ReadPin(KEY1_GPIO_PORT, KEY1_PIN) == GPIO_PIN_SET) ? BUZZER_ON : BUZZER_OFF;

        }

    }

    // KEY2事件解析

    if(GPIO_Pin == KEY2_PIN)

    {

        if(current_tick - key2_tick > DEBOUNCE_MS)

        {

            key2_tick = current_tick;

            if(HAL_GPIO_ReadPin(KEY2_GPIO_PORT, KEY2_PIN) == GPIO_PIN_SET)

            {

                key2_press_tick = current_tick; // 记录按下时间

            }

            else

            {

                // 长按判断(按下超过500ms)

                if(current_tick - key2_press_tick >= KEY2_LONG_MS)

                {

                    key2_event = KEY2_LONG;

                    key2_click_cnt = 0;

                }

                else

                {

                    key2_click_cnt++; // 单击/双击计数

                }

            }

        }

    }}

// 双击检测(主循环调用)void Key2_Click_Check(void){

    uint32_t current_tick = HAL_GetTick();

    if(key2_click_cnt == 1)

    {

        // 400ms内无第二次按下,判定为单击

        if(current_tick - key2_tick >= KEY2_DOUBLE_MS)

        {

            key2_event = KEY2_SINGLE;

            key2_click_cnt = 0;

        }

    }

    else if(key2_click_cnt == 2)

    {

        // 400ms内第二次按下,判定为双击

        key2_event = KEY2_DOUBLE;

        key2_click_cnt = 0;

    }}

(4)主函数逻辑(main.c)

初始化外设后,在主循环中检测 KEY2 事件,并联动 LED 翻转:

int main(void){

    KEY2_EventDef key2_event;

    HAL_Init();

    SystemClock_Config(); // 配置系统时钟为168MHz

    // 外设初始化

    LED_GPIO_Config();

    Buzzer_GPIO_Config();

    Key_EXTI_Config();

    while(1)

    {

        Key2_Click_Check(); // 检测双击事件

        key2_event = Key2_GetEvent(); // 获取KEY2事件

        switch(key2_event)

        {

            case KEY2_SINGLE:

                LED1_TOGGLE; // 单击翻转LED1(红)

                break;

            case KEY2_DOUBLE:

                LED2_TOGGLE; // 双击翻转LED2(绿)

                break;

            case KEY2_LONG:

                LED3_TOGGLE; // 长按翻转LED3(蓝)

                break;

            default:

                break;

        }

    }

}

四、功能测试

  1. KEY1 测试:按下 KEY1,蜂鸣器响起;松开 KEY1,蜂鸣器停止(无抖动);
  2. KEY2 测试
    • 单击 KEY2:红色 LED(LED1)翻转;
    • 双击 KEY2:绿色 LED(LED2)翻转;
    • 长按 KEY2(超过 500ms):蓝色 LED(LED3)翻转;
  3. 所有操作无延迟、无误触发,消抖和事件解析逻辑稳定。

五、总结与扩展

1. 核心亮点

  • 模块化设计:LED、按键、蜂鸣器驱动独立,便于维护和移植;
  • 中断驱动:相比轮询方式,中断减少 CPU 占用,响应更及时;
  • 事件解析:通过简单的计时和计数,实现了单击 / 双击 / 长按的区分,逻辑清晰易扩展;
  • HAL 库适配:基于 STM32 HAL 库开发,兼容 STM32F4 系列其他芯片。

2. 扩展方向

  • 增加更多按键事件(如三连击、长按释放);
  • 联动更多外设(如 LCD 屏显示按键状态、继电器控制);
  • 加入按键防抖的硬件滤波(如 RC 电路),进一步提升稳定性;
  • 封装为通用按键驱动库,适配不同引脚和事件需求。

3. 注意事项

  • HAL_GetTick()的精度为 1ms,若需更高精度,可改用定时器计时;
  • NVIC 优先级需合理配置,避免按键中断影响其他核心功能;
  • 不同硬件的按键电平逻辑可能不同,需根据实际电路调整宏定义(ON/OFF)。

本文完整代码可直接在 STM32F407 开发板上编译运行,也可适配 STM32F1/F7 等系列芯片(仅需修改引脚和时钟配置)。如果有问题,欢迎在评论区交流~

附:代码已在文中完整展示


创作不易,欢迎点赞 + 收藏 + 关注! 

Logo

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

更多推荐