STM32 HAL库极简入门:中断系统与按键消抖

前言

在上一篇中,我们学会了用串口观察程序的运行状态。但你是否想过:如果程序一直卡在HAL_Delay()里等待,那它怎么同时去检测按键是否被按下?

答案就是中断(Interrupt)。中断是嵌入式开发的灵魂,它让单片机能够“一心多用”——平时做自己的事,一旦有紧急事件(比如按键按下、数据到达),立刻暂停当前工作去处理,处理完再回来继续。

今天,我们就来学习 STM32 最基础也最常用的外部中断(EXTI),并通过一个按键控制 LED的实例,彻底搞懂中断配置和按键消抖。


一、什么是外部中断 EXTI?

EXTI(External Interrupt/Event Controller)是 STM32 的外部中断/事件控制器。简单来说,它能让 GPIO 引脚上的电平变化(上升沿、下降沿或双边沿)触发一个中断信号,从而执行我们预设的回调函数。

常见应用场景:

  • 按键检测(按下瞬间触发中断)
  • 传感器信号触发(如人体红外感应)
  • 通信模块的状态通知(如 Wi-Fi 模块收到数据)

二、CubeMX 配置外部中断

假设我们要用PA0连接一个按键,按下时为低电平(下降沿触发),用PC13控制 LED。

1. 配置按键引脚为外部中断模式

  • 在 CubeMX 的芯片引脚图中,找到 PA0,左键点击选择 GPIO_EXTI0
  • 注意:不是 GPIO_Input,而是 GPIO_EXTI0,这表示将该引脚映射到外部中断线 0。

2. 配置 GPIO 参数

在左侧 System Core -> GPIO 中,选中 PA0:

  • GPIO mode: External Interrupt Mode with Falling edge trigger detection(下降沿触发)
  • GPIO Pull-up/Pull-down: Pull-up(上拉,按键未按下时保持高电平)

3. 开启 NVIC 中断

  • 进入 NVIC 选项卡,勾选 EXTI line0 interruptEnabled
  • 可以设置中断优先级(Preemption Priority),数字越小优先级越高。对于简单的按键,默认优先级即可。

4. 生成代码

点击 GENERATE CODE,CubeMX 会自动生成中断初始化代码,包括 NVIC 配置和 EXTI 线路使能。


三、编写中断回调函数

HAL 库为外部中断提供了一个标准的回调函数。当 PA0 检测到下降沿时,HAL 库会自动调用它:

/* USER CODE BEGIN 4 */
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
    if (GPIO_Pin == GPIO_PIN_0)
    {
        // 按键被按下了!
        HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13); // 翻转 LED
    }
}
/* USER CODE END 4 */

注意:这个函数需要写在 main.cUSER CODE BEGIN 4 区域里。

编译下载后,每按一次按键,LED 就会翻转一次。是不是很简单?

……但先别高兴太早。如果你实际测试,可能会发现:按一次按键,LED 却翻转了好几次!这就是机械按键的抖动问题。


四、按键消抖:理论与实践

什么是按键抖动?

机械按键在按下或松开的瞬间,内部的金属触点并不会立刻稳定接触/断开,而是会快速通断多次,产生一连串的电平跳变。对于单片机来说,这看起来就像是按键被按下了很多次。

消抖的本质:在检测到电平变化后,等待一段时间(通常是 10~20ms),再次确认电平状态,如果仍然保持变化后的状态,才认为按键确实被按下了。

消抖方案一:延时消抖(简单但不推荐)

在中断回调函数里直接用 HAL_Delay() 延时消抖:

void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
    if (GPIO_Pin == GPIO_PIN_0)
    {
        HAL_Delay(20); // 延时 20ms 消抖
        if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_RESET)
        {
            HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13);
        }
    }
}

为什么不推荐?

  • HAL_Delay() 依赖 SysTick 中断,而在中断服务函数里调用延时函数,可能会阻塞其他中断,甚至导致程序卡死。
  • 中断函数应该越快越好,不要做耗时操作。

消抖方案二:标志位 + 主循环检测(推荐)

更合理的做法是在中断里只设置一个标志位,具体的消抖和逻辑处理放在主循环里完成:

/* 定义全局变量 */
uint8_t key_flag = 0;        // 按键中断标志
uint32_t key_press_time = 0; // 按键触发时间戳

/* 中断回调函数 */
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
    if (GPIO_Pin == GPIO_PIN_0)
    {
        key_flag = 1;
        key_press_time = HAL_GetTick(); // 记录当前系统时间
    }
}

/* main 函数中的 while(1) 循环 */
while (1)
{
    if (key_flag == 1)
    {
        // 等待 20ms 后再次检测按键状态
        if (HAL_GetTick() - key_press_time >= 20)
        {
            if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_RESET)
            {
                HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13); // 确认按下,翻转 LED
            }
            key_flag = 0; // 清除标志位
        }
    }
    
    /* 这里可以继续执行其他任务 */
}

优点:

  • 中断函数极短,不阻塞系统
  • 消抖逻辑在主循环中完成,不影响其他中断
  • 程序结构清晰,易于扩展

消抖方案三:定时器消抖(工业级方案)

对于更复杂的项目,可以用定时器每隔 10ms 扫描一次按键状态。如果连续两次扫描都检测到按键按下,才认为是有效按键。这是最稳定、最常用的消抖方案,我们后续会专门出一篇定时器的文章来详细讲解。


五、中断使用中的常见坑

问题 原因 解决方案
按一次按键触发多次中断 按键抖动 加入消抖逻辑
中断里调用 HAL_Delay 卡死 SysTick 优先级低于当前中断 中断里不要调用延时函数
中断不触发 NVIC 未使能或引脚配置错误 检查 CubeMX 的 NVIC 和 GPIO 模式
LED 状态与预期相反 触发沿设置错误或上下拉配置不对 确认按键电路是高电平还是低电平触发

六、总结

通过今天的学习,你应该已经掌握了:

  • 如何在 CubeMX 中配置外部中断(EXTI)
  • 如何编写 HAL 库的中断回调函数
  • 机械按键抖动的原因及消抖方法
  • 中断编程中的注意事项和避坑指南

中断让单片机从“单线程”变成了“多任务”,是嵌入式开发中不可或缺的核心技能。掌握它,你就迈出了从“点灯小白”到“嵌入式工程师”的关键一步。


下一篇预告:STM32 HAL库极简入门:定时器与PWM输出。

文末附加内容
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇