寄存器操作 STM32 临时屏蔽 GPIO EXTI 外部按键中断

在编写单片机程序时,常常会有这样的需求:

graph LR
   A[监听按键事件中断] -- 按键按下 ---> B[中断回调函数] --> C[执行用户代码]

但是,由于按键抖动等原因,按键中断可能会被意外触发,导致按钮事件重复执行,这不是我们希望看到的。

解决此问题的一个方法,就是“在中断回调函数头部屏蔽按键中断事件,在尾部重新开启中断事件”,以达到防止按钮误触的目的。

本文以 STM32F103C8T6 为例,介绍通过寄存器屏蔽特定线路的外部中断的方法。

EXTI Line

要想操作外部中断,首先要了解 “EXTI 线”。这里放一张手册上的图。

EXTI与GPIO映射图

本次我们把目光聚集于 EXTI1 ~ EXTI15 部分。

我们可以发现,每个 EXTIx 都对应了 GPIOx

而且,此处的 GPIOx 对所有 GPIO Group 都有效。

也就是说,如果我们想要屏蔽 PB12 上的外部中断,那么我们就需要屏蔽 EXTI12 ,由此带来的副作用就是 PA12、PC12、PD12... 上的外部中断都会被屏蔽(呃,如果有更好的解决办法欢迎留言补充)。

Register

寄存器的主角是 EXTI_IMR 。查阅手册可知,其名称为“外部中断屏蔽寄存器”。

EXTI_IMR 寄存器

寄存器的 20~31 为保留位,剩下的每一位都对应一条 EXTI Line

使用方式也很简单,如果想要屏蔽第 xEXTI Line,就把 MRx 置为1即可。

代码操作

了解寄存器结构后,代码操作就很简单了:

1
2
EXTI->IMR &= ~(GPIO_PIN_12);   // 临时关闭
EXTI->IMR |= GPIO_PIN_12; // 重新启用

将上下两条语句分别插入在中断回调函数的头和尾部分即可。亲测能有效屏蔽中断,防止误触。

当然,按照位运算的规律,如果要同时操纵两条 EXTI LINE ,可以这么写:

1
2
EXTI->IMR &= ~(GPIO_PIN_12 | GPIO_PIN_8);
EXTI->IMR |= (GPIO_PIN_12 | GPIO_PIN_8);

注意事项

我在开发时使用了 STM32CubeMX ,并给管脚自定义了名称 KEY_1_Pin

于是在生成的 main.h 中有了如下宏定义:

1
2
3
#define KEY_1_Pin GPIO_PIN_12
#define KEY_1_GPIO_Port GPIOB
#define KEY_1_EXTI_IRQn EXTI15_10_IRQn

需要注意,不要被宏名称误导,不能写成:

1
2
EXTI->IMR &= ~(EXTI15_10_IRQn);
EXTI->IMR |= EXTI15_10_IRQn;

我们预期的右值是 0x1000 ,但是 KEY_1_EXTI_IRQn 的值为 40。

或者也不能写成:

1
2
EXTI->IMR &= ~(EXTI_LINE_12);
EXTI->IMR |= EXTI_LINE_12;

因为此处 EXTI_LINE_12 的值为 12。