[STM32 HAL源码解析] 为什么中断里要判断挂起寄存器?为什么非要用回调函数?

[STM32 HAL库函数底层思考]为什么 EXTI的外部GPIO 中断需要判断挂起寄存器和使用回调函数,为何不像标准库一样直接在中断写业务逻辑?

思考起因

在阅读 STM32 HAL 库源码时 (stm32f1xx_hal_gpio.c),发现通用的EXTI公用中断处理函数 HAL_GPIO_EXTI_IRQHandler 内部有这样的代码:

c 复制代码
void HAL_GPIO_EXTI_IRQHandler(uint16_t GPIO_Pin)
{
  /* EXTI line interrupt detected */
  if (__HAL_GPIO_EXTI_GET_IT(GPIO_Pin) != 0x00u)
  {
    __HAL_GPIO_EXTI_CLEAR_IT(GPIO_Pin);
    HAL_GPIO_EXTI_Callback(GPIO_Pin);
  }
}

其中这两行代码让我思考了一下

c 复制代码
if (__HAL_GPIO_EXTI_GET_IT(GPIO_Pin) != 0x00u)//判断中断挂起寄存器是否置1
    
HAL_GPIO_EXTI_Callback(GPIO_Pin);//调用回调函数

这让我产生了两个疑问:

  1. 既然进入了中断,肯定是因为对应 GPIO 触发了中断,为什么还要多此一举传入对应GPIO_Pin这个参数去检查挂起寄存器?
  2. 为什么不直接在中断里写业务就像标准库那样,非要绕一圈用回调函数?

经过深入分析与思考,我总结了以下两点原因。

一、 为什么要检查挂起寄存器?(解决共用中断线问题)

很多人认为,外部中断 GPIO 经过 AFIO 选择后连接到 EXTI,触发条件满足不就直接进中断了吗?

对于 EXTI0 ~ EXTI4 来说,确实如此。

  • EXTI0_IRQHandler 只负责 Line 0。
  • EXTI4_IRQHandler 只负责 Line 4。
    它们是"独享"中断向量的,进了这个门,一定是这个人来了,也就是说我甚至不用去判断对应的中断挂起寄存器的位是否标志位置1

但是!对于 EXTI9_5 和 EXTI15_10 就不一样了。

STM32 为了节省资源,设计了共用中断向量

  • EXTI9_5_IRQHandler:同时管理 Line 5, 6, 7, 8, 9 (5条线共用一个入口)。
  • EXTI15_10_IRQHandler:同时管理 Line 10 ~ 15 (6条线共用一个入口)。

如果发生下面这个场景,不判断中断挂起寄存器是否标志位置1就会出问题

假设我同时开启了 PA5 (连接 EXTI5) 和 PB6 (连接 EXTI6) 的外部中断。

因为它们属于同一个组,无论谁触发,CPU 都会跳转到同一个函数 EXTI9_5_IRQHandler

如果不判断挂起寄存器(标志位)和传入对应GPIO_Pin:

  1. 假设只有 PA5 触发了中断。
  2. 进入 EXTI9_5 中断服务函数。
  3. 如果不检查对应GPIO_Pin标志位,程序可能会顺次执行 PA5 和 PB6 的处理逻辑。
  4. 结果: 明明只有 PA5 动了,代码却把 PB6 的业务逻辑也跑了一遍。这是严重的逻辑错误。

所以,if (__HAL_GPIO_EXTI_GET_IT(GPIO_Pin) != 0x00u) 这句代码就是为了验明正身,确认当前检查的这个 Pin 真的被触发了,才执行后续操作,换句话来说我认为它就是专门为了EXTI9_5_IRQHandler和EXTI15_10_IRQHandler这两个共用中断向量专门做的

二、 为什么要用回调函数?(解耦与去冗余)

如果我们抛弃 HAL 库的设计,直接在中断服务函数里写业务逻辑,代码会变得非常冗杂。

不使用回调的痛点:

EXTI9_5_IRQHandler 里,我们需要:

  1. 手动写代码判断是 Line 5 还是 Line 6...
  2. 如果是 Line 5 -> 手动清除标志位 -> 写业务代码。
  3. 如果是 Line 6 -> 手动清除标志位 -> 写业务代码。
  4. ...

这里面,"判断标志位"和"清除标志位"是大量重复的机械劳动,而且很容易忘记清除标志位导致程序卡死。

于是HAL库干脆把这些机械的东西搞成一个函数:

HAL 库把这些脏活累活封装在了一个通用函数 HAL_GPIO_EXTI_IRQHandler 里。

  • 它帮我们自动判断标志位。
  • 它帮我们自动清除标志位。
  • 最后,它调用一个统一的接口:HAL_GPIO_EXTI_Callback

这样实现了完美的分层解耦

  • 驱动层:负责处理硬件细节(寄存器操作自动判断标志位和自动清除标志位)。
  • 业务层 :我们在 Callback 里只管写业务逻辑(比如翻转 LED),完全不需要关心底层是哪个寄存器在工作。

只需要再次加个判断if(GPIO_Pin==GPIO_Pinx)这样的逻辑去统一管理所有EXTI外部GPIO中断触发后需要完成事情。

相关推荐
liwulin05062 小时前
【ESP32-S3】WINDOWS+VMware+ROS2+YDLIDA X2导航初步调试
windows·stm32·单片机
dozenyaoyida4 小时前
RS预览失败问题分析和解决
网络·经验分享·嵌入式硬件·tcp·wifi6兼容性·视频预览卡顿
forAllforMe5 小时前
STM32 驱动CAN接口的拉线位移传感器
stm32·单片机·嵌入式硬件
Struggle to dream6 小时前
STM32---关于DMA的入门详解
stm32·单片机·嵌入式硬件
BackCatK Chen6 小时前
第16篇:TMC2240多轴联动软件设计|2轴_3轴同步控制框架(保姆级)
嵌入式硬件·自动化·tmc2240·多轴联动·同步控制·2轴联动·3轴联动
Hello_Embed6 小时前
STM32F030CCT6 开发环境搭建
笔记·stm32·单片机·嵌入式·freertos
国科安芯7 小时前
航空级电机控制系统的抗辐照MCU功能安全设计与电磁兼容验证方法
单片机·嵌入式硬件·安全·性能优化·架构·安全性测试
myron66887 小时前
基于STM32LXXX的模数转换芯片ADC(ADS1220IPWR)驱动C程序设计
c语言·stm32·嵌入式硬件
隔壁大炮7 小时前
I2C基本电路结构
单片机·嵌入式硬件·铁头山羊