3. 外部中断EXTI:结构框图、触发方式与软硬件中断

外部中断,也就是EXTI,是嵌入式开发里最常用的外设之一。说白了,它就是芯片的「门铃」——外部来了一个信号,芯片就知道该停下手里的事去处理了。

我个人习惯把EXTI比作一个「带过滤器的门卫」。它不光要通知你有人来了,还得告诉你:是前门还是后门?是按门铃还是踹门?是长按还是短按?这些细节,全在EXTI的结构里。

3.1 EXTI结构框图

先看整体结构。EXTI的核心其实不复杂,我画了一张图帮你理清脉络:

输入阶段 边沿检测 中断/事件输出 GPIO引脚 GPIOx_PIN_y 软件中断 SWIER 寄存器 RTC闹钟等 其他外设事件 上升沿触发选择 RTSR 寄存器 下降沿触发选择 FTSR 寄存器 ≥1 中断屏蔽寄存器 IMR 事件屏蔽寄存器 EMR → NVIC 中断控制器 或 → 事件脉冲输出

这张图从左往右看。最左边是输入源,可以是GPIO引脚、软件中断寄存器(SWIER),或者其他外设(比如RTC闹钟)。中间是边沿检测,决定你是上升沿触发还是下降沿触发。最右边是输出,经过屏蔽寄存器后,送到NVIC或者直接输出事件脉冲。

核心要点:EXTI的输入源有3种,但同一时间一个EXTI线只能选一个。GPIO和软件中断是互斥的,别想着同时用。

3.2 触发方式选择

触发方式,说白了就是「什么样的信号变化才算数」。EXTI支持三种模式:

触发方式 配置寄存器 典型应用场景
上升沿触发 RTSR 按键按下(从低到高)
下降沿触发 FTSR 按键松开(从高到低)
双边沿触发 RTSR + FTSR 编码器信号、脉冲计数

我在项目中遇到过一个问题:用下降沿检测按键,结果按键抖动导致一次按下触发了七八次中断。后来我改成「下降沿+硬件消抖+软件延时过滤」,才算稳定下来。你想想看,如果没做消抖,一个按键能让你系统卡死——中断太频繁了。

我的习惯:对于机械按键,我一般用下降沿触发,然后在中断服务函数里加一个10-20ms的延时去抖。虽然不优雅,但可靠。如果你用RTOS,可以用定时器消抖,更专业。

3.3 软件中断与硬件中断

这里有个容易混淆的点:EXTI的中断源,既可以是硬件引脚,也可以是软件写寄存器。我刚开始学的时候也懵过——软件中断?那不就是自己给自己发中断吗?

嗯,没错,就是自己给自己发。但这不是闲得慌,而是有实际用途的。

硬件中断

硬件中断就是外部引脚电平变化触发的。比如你接了一个按键,按下时引脚从高变低,下降沿触发中断。这是最常用的方式。

// 配置PA0为下降沿触发中断
void EXTI0_Init(void) {
    // 1. 使能GPIOA时钟
    RCC->AHB1ENR |= (1 << 0);
    
    // 2. 配置PA0为输入模式
    GPIOA->MODER &= ~(0x3 << 0);
    
    // 3. 连接EXTI0到PA0
    SYSCFG->EXTICR[0] &= ~(0xF << 0);
    SYSCFG->EXTICR[0] |= (0x0 << 0);  // PA0
    
    // 4. 配置下降沿触发
    EXTI->FTSR |= (1 << 0);
    
    // 5. 取消中断屏蔽
    EXTI->IMR |= (1 << 0);
    
    // 6. 配置NVIC
    NVIC_EnableIRQ(EXTI0_IRQn);
    NVIC_SetPriority(EXTI0_IRQn, 1);
}

软件中断

软件中断是通过写SWIER寄存器来模拟一个中断。说白了,就是你在代码里「假装」外部来了一个信号。有什么用呢?

  • 调试阶段:你可以手动触发中断,测试中断服务函数是否正常,不用真的去碰硬件。
  • 任务同步:在某些设计里,软件中断可以用来唤醒一个等待中断的任务。
  • 模拟外部事件:比如你要测试一个超时逻辑,但外部信号很难精确控制,软件触发就方便多了。
// 软件触发EXTI0中断
void EXTI0_SoftwareTrigger(void) {
    // 写1到SWIER0,立即触发中断
    EXTI->SWIER |= (1 << 0);
    
    // 注意:硬件会自动清除这个位,不需要手动清
    // 但中断标志位PR还是要清的
}

我曾经踩过的坑:软件中断触发后,中断标志位(PR寄存器)不会自动清除!你必须像处理硬件中断一样,在ISR里手动写1清标志。否则,中断会一直触发,系统直接卡死。别问我怎么知道的...那次调试花了我整整一个下午。

3.4 中断优先级与嵌套

EXTI的中断优先级由NVIC管理。这里有个原则:高优先级中断可以打断低优先级中断。但EXTI本身有多个线(EXTI0~EXTI15),每个线可以独立配置优先级。

我个人习惯把实时性要求高的中断(比如编码器计数)设高优先级,把按键这种不那么紧急的设低优先级。你想想看,如果按键中断把电机控制中断给抢了,电机可能就抖一下——这在工业现场是要出事故的。

EXTI线 典型用途 建议优先级
EXTI0 按键、唤醒 低(3~5)
EXTI1 编码器Z信号 高(0~1)
EXTI2~EXTI15 传感器、通信 按需分配

一个小技巧:如果你有多个EXTI线共用一个中断服务函数(比如EXTI9_5_IRQHandler),在ISR里一定要先判断是哪个线触发的。读EXTI->PR寄存器,然后分别处理。别偷懒,否则你会收到「莫名其妙的中断」bug反馈。

3.5 实际项目中的配置流程

好了,理论说完了,咱们捋一下实际配置的步骤。我一般按这个顺序来:

  1. 确定引脚和EXTI线——比如PA0对应EXTI0,PB1对应EXTI1,一一对应,别搞混。
  2. 配置GPIO为输入模式——上拉还是下拉?看你的外部电路。按键一般配上拉。
  3. 连接GPIO到EXTI——通过SYSCFG_EXTICR寄存器选择。
  4. 选择触发方式——RTSR或FTSR,或者两个都选。
  5. 配置中断屏蔽——IMR寄存器,别忘了。
  6. 配置NVIC优先级——根据实时性要求来。
  7. 编写ISR——记得清PR标志位!

嗯,基本上就这些。EXTI本身不复杂,但细节多。尤其是那个PR标志位,我见过太多新手在这上面翻车了。记住:进ISR第一件事,读PR,清PR,再干活。


可可精品资料库