3. 外部中断:触发方式、配置流程与按键实战

外部中断,说白了就是让单片机对外部信号的变化“敏感”起来。你想想看,如果CPU一直在轮询按键,那得多浪费算力?外部中断就是解决这个问题的——信号一变,CPU立刻停下手里的事去处理。

我个人习惯把外部中断比作“门铃”。你正在屋里看书,有人按门铃(触发信号),你才起身去开门(执行中断服务函数)。没人按的时候,你继续看书,互不干扰。

3.1 三种触发方式:上升沿、下降沿、电平触发

外部中断的触发方式,决定了单片机在什么条件下响应。我刚开始学的时候也搞混过,后来在项目里踩了坑才真正理解。

触发方式 信号变化 典型应用 注意事项
上升沿触发 低电平→高电平的瞬间 脉冲计数、编码器信号 抗干扰能力较强
下降沿触发 高电平→低电平的瞬间 按键按下、边沿检测 按键抖动可能误触发
电平触发 持续保持低电平或高电平 紧急停止、报警信号 必须手动清除触发条件

关键区别:边沿触发只响应信号变化的“瞬间”,电平触发则响应信号的“持续状态”。电平触发有个坑——如果中断服务函数执行完,电平还没恢复,会再次触发,形成死循环。

我记得有一次做工业控制项目,用下降沿触发检测急停按钮。结果现场电磁干扰严重,信号线上毛刺不断,单片机频繁误进中断。后来换成电平触发,配合硬件滤波,问题才解决。嗯,这里要注意:边沿触发对噪声敏感,电平触发对信号持续时间敏感,选哪个要看现场环境。

3.2 外部中断的配置流程

配置外部中断,其实就五步。我习惯按这个顺序来,不容易漏。

  1. 初始化GPIO——把引脚设为输入模式,带上拉或下拉电阻
  2. 配置中断触发方式——选择上升沿、下降沿还是电平触发
  3. 使能中断——打开对应中断通道
  4. 编写中断服务函数——处理具体业务逻辑
  5. 清除中断标志——退出前必须清标志,否则会反复进中断

以STM32为例,配置一个下降沿触发的外部中断,代码长这样:

// 1. 初始化GPIO
GPIO_InitTypeDef GPIO_InitStruct = {0};
__HAL_RCC_GPIOA_CLK_ENABLE();
GPIO_InitStruct.Pin = GPIO_PIN_0;
GPIO_InitStruct.Mode = GPIO_MODE_IT_FALLING;  // 下降沿触发
GPIO_InitStruct.Pull = GPIO_PULLUP;           // 内部上拉
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

// 2. 配置中断优先级
HAL_NVIC_SetPriority(EXTI0_IRQn, 2, 0);
HAL_NVIC_EnableIRQ(EXTI0_IRQn);

// 3. 中断服务函数
void EXTI0_IRQHandler(void)
{
    if(__HAL_GPIO_EXTI_GET_IT(GPIO_PIN_0) != RESET)
    {
        // 处理按键逻辑
        // ...
        
        // 4. 清除中断标志(必须!)
        __HAL_GPIO_EXTI_CLEAR_IT(GPIO_PIN_0);
    }
}

我的习惯:中断服务函数里尽量少做事,只设标志位。具体处理放到主循环里做。中断里做太多事,会影响系统实时性,甚至导致中断嵌套崩溃。

3.3 按键中断实战——避坑指南

按键中断看起来简单,但实际做起来坑不少。我曾经在一个消费电子项目里,因为按键中断没处理好,导致产品在用户松手时误触发两次。排查了一整天,最后发现是抖动惹的祸。

机械按键按下和松开的瞬间,会产生10~20ms的抖动。如果用下降沿触发,一次按下可能产生多次中断。解决方案有两个:

  • 硬件消抖:RC低通滤波 + 施密特触发器,成本高但效果好
  • 软件消抖:中断触发后延时10~20ms再读一次电平,确认状态

我个人推荐软件消抖,成本低、灵活。下面是我常用的按键中断处理模板:

volatile uint8_t key_press_flag = 0;

void EXTI0_IRQHandler(void)
{
    if(__HAL_GPIO_EXTI_GET_IT(GPIO_PIN_0) != RESET)
    {
        // 延时消抖(用定时器或简单循环)
        delay_ms(15);
        
        // 再次确认电平
        if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_RESET)
        {
            key_press_flag = 1;  // 设置标志,主循环处理
        }
        
        __HAL_GPIO_EXTI_CLEAR_IT(GPIO_PIN_0);
    }
}

注意:中断里用delay_ms()要小心。如果系统用了RTOS,delay_ms可能导致任务调度异常。更好的做法是用定时器做延时,或者用状态机处理。

还有一个容易被忽略的问题——中断优先级。如果多个中断共用优先级,或者优先级设置不当,可能导致中断丢失或响应延迟。我一般把按键中断设成中等优先级,把定时器、通信这类实时性要求高的设成高优先级。

3.4 知识体系总览

下面这张图,把外部中断的核心知识点串起来了。我画图的时候习惯把“配置流程”放在中间,因为它是实操的骨架。

外部中断知识体系 触发方式 上升沿 下降沿 电平触发 配置流程 GPIO初始化 触发方式配置 使能中断 编写ISR 清除标志 按键实战 硬件消抖 软件消抖 优先级设置 核心原则:中断服务函数要短、要快、要清标志

这张图把触发方式、配置流程、按键实战三个模块串在一起。你从“触发方式”开始理解原理,然后按“配置流程”写代码,最后用“按键实战”验证效果。每一步都有坑,但踩过了就记住了。

嗯,外部中断的内容就这些。记住一句话:中断是手段,不是目的。别为了用中断而用中断,该轮询的时候轮询,该中断的时候中断,这才是成熟工程师的判断。