1.2 EXIT 中断 - 实验篇

现在我们来看看在现实中我们如何具体的利用中断

我们的计划是这样的, 使用按钮来控制 LED 的亮还是灭 一开始默认是灯灭, 按钮被按下, 灯亮, 按钮再次被按下, 灯灭

这个与实验一相衔接, 你可以直接在实验一的工程上进行配置, 也可以再新开一个工程, 不过要注意将小灯等东西给配置好

接下来我在实验一的基础上开始继续讲解

Step 0 - 接线

  • 将按钮的左右两边分别连接在 PA1 和 GND 上

Step 1 - 配置中断引脚

Pasted image 20260622034547

  • 在芯片图中我们选择一个引脚, 这里以 PA1 为例, 将它配置为 GPIO_EXIT1

然后我们来到 GPIO 配置界面Pasted image 20260622040530 Pasted image 20260622041000

  • 来到 GPIO 配置界面, 选择我们的 PA1 口, 模式选择 External Interrupt Mode with Falling edge trigger detection, 同时将 GPIO 配置成上拉

这里解释一下 Falling edgeRising edge 都是什么意思

翻译成中文就是 上升沿 - Rising edge, 下降沿 - Falling edge

上升沿就是电平从低到高, 上升了, 就是上升沿 下降沿就是电平从高到低, 下降了, 就是下降沿 Pasted image 20260622041551 看起来很简单, 那和我们我们配置中断有什么关系呢?为什么选择下降沿?

选择下降沿是因为我们将它配置成了上拉模式Pasted image 20260622042856

如图所示, 现在将 PA1 配置成上拉模式, 那么 PA1 的左边是高电平

如果按钮被按下, 连接 GND 的导线被接通, PA1 上的电平从高电平变成低电平, 这个不就是下降沿的过程吗. 这个下降沿变化被 detection, 就认为按钮被按下, 然后触发中断

解释完了之后会到正题, 我们继续配置引脚 PA1

  • 设置名称 user lable 为 Button
  • 在NVIC中将 EXIT line 1 启用

Pasted image 20260622043711


Step 2 - 生成代码并编写中断函数

生成代码之后使用 Clion 打开, 记得新增一个 OpenOCD 项目

Pasted image 20260622044224

  • 打开这个项目, 我们需要更改的是 stm32f1xx_it.c 这个文件, 他用来专门写有关中断的函数(再此之前, 你需要把之前在实验一中 main 函数写的 LED 闪烁的代码清除)

打开 stm32f1xx_it.c 我们可以看到:Pasted image 20260622044838

有一段注释是:Cortex-M3 Processor Interruption and Exception Handlers 这就说明下面的所有东西都是有关中断处理函数的内容, ISR

我们看看下面的函数, 也确实都是一些 叫做 xxx_Handler, 这就是中断处理函数

而我们要用到的函数在这个文件的最下面Pasted image 20260622045845

这里又有一段注释块写着 STM32F1xx Peripheral Interrupt Handlers - STM32F1xx 外设中断处理函数

我们要用到的函数就是叫做 EXTI1_IRQHandler() 的函数, 就是 EXTI1 线上的 IRQ(中断信号)处理函数

在这个函数的内部, 只有一句 HAL_GPIO_EXTI_IRQHandler(Button_Pin);, 这个就是 CubeMX 自动帮我们生成的 HAL 库函数, 我们按住 command(ctrl) 同时鼠标点击这个函数

于是我们跳转到 stm32f1xx_hal_gpio.c这个文件, 这是在 Driver 层的 HAL 库的驱动文件 Pasted image 20260622050728 在我们要查看的 HAL_GPIO_EXTI_IRQHandler(uint16_t GPIO_Pin); 函数中他首先检查并清空了标志位, 这就是我在原理篇中提到的 HAL 库会自动帮你清理中断标志 flag

随后, 它调用了 HAL_GPIO_EXTI_Callback(GPIO_Pin); 这个函数, 我们继续点击它, 看看他是做什么的

他跳转到了下面这里Pasted image 20260622051339 我们注意到, 这个函数前面有一个 ___weak , 这是什么意思呢?

__weak弱符号(weak symbol)标记: 作用是在链接阶段告诉链接器:这是一个默认实现,如果别处有同名的强定义,就用那个、忽略我这个

为什么要这样, 不能直接重写吗? C 没有面向对象的重写机制,HAL 借助弱符号,巧妙地给你留了个覆盖默认行为的机制, 这就是C 语言里伪重写的常见手法

所以 HAL_GPIO_EXTI_Callback() 是一个用弱符号实现的回调函数, 要注意他不是虚函数

我这里说到了另外的一些个名词回调函数,弱函数, 虚函数, 他们经常被搞混, 所以你可以看[[番外篇 - 从 STM32 的 __weak说起弱符号、回调函数与虚函数|番外篇 - 从 STM32 的 __weak说起弱符号、回调函数与虚函数]]

慢慢研究吧, 总有一天你会悟的

  • 于是我们在stm32f1xx_it.c 最后写上HAL_GPIO_EXTI_Callback() 的强函数, 让他覆盖掉弱实现Pasted image 20260622062328
1
2
3
4
5
6
7
8
9
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)  
{
if (GPIO_Pin == Button_Pin)
{
// 简单的延时消抖并不适用在这里, 因为 HAL_Delay 因为SysTick 中断优先级不如 NVIC 会被抢占
// HAL_Delay(10);
HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);
}
}

Step 3 - 构建并烧录

其实很简单, 只要写这一段代码就行了, 但是要真正理解背后的原理并不简单

现在接好你的 daplink, 我们锤一锤代码, 然后烧录到开发板中

按下按钮, 你发现灯亮了, 再按, 灯亮

我们成功的实现了这个实验, 但是你真正理解了其中的原理, 你才算真正成功了