STM32单片机学习(18) —— 外部中断
文章目录
EXTI外部中断
在前面我们已经讲过了STM32中断系统的系统框图,如下所示:

像 USART、这类片内外设,本身就是单片机内部集成的功能模块。
它们在检测到特定事件发生(比如接收完成)时,可以直接向NVIC发送中断请求。
再由NVIC统一进行中断调度执行。
那么问题来了:
那些连接在 GPIO 引脚上的“片外外设”,如果它们也想发送中断请求,怎么办?
比如:
- 某个接在IO引脚上的按键,希望能够在按键按下时,触发一个中断请求,怎么实现呢?
- 某个可以输出高低电平状态的传感器,希望在输出高电平时,触发一个中断请求,怎么实现呢?
中断是单片机内部系统的功能,所以这些外部设备,需要借助一个单片机外设来向NVIC发送中断请求。
这个外设就是:EXTI外设。
注意:
- 只要输入型的设备,才具有发送中断请求的需求,比如按键、传感器等。
- 输出驱动型设备,像LED、蜂鸣器等显然没有这种需求。
EXTI外设简述
EXTI(External interrupt / event controller,外部中断 / 事件控制器),一般直接简称为外部中断。
需要特别注意的是:
**EXTI 外设不是 “EXIT / exit”,并不是“退出”的英文单词,而是 “External Interrupt” 的缩写,请不要混淆。
EXTI 外设,是单片机内部的一个片内外设,用于统一处理来自 GPIO 引脚所连接的外部设备 的中断请求。
说得更具体一点:
EXTI 外设的作用,是将 GPIO 引脚上的外部电平变化(例如由低电平变为高电平),转换为单片机内部可以识别的中断请求。
说得更通俗一点:
GPIO引脚上接入的外部设备,在引脚输入电平变化时,可以借助EXTI外设,由EXTI外设向NVIC发送中断请求。
再举一个实际的例子:
- PA0 引脚上接入了一个传感器,该传感器可以输出高电平或低电平。
- 在正常情况下,传感器输出低电平,PA0 引脚输入低电平。
- 当外部环境发生变化时,传感器转而输出高电平,此时 PA0 引脚的输入电平从低电平切换为高电平。
- EXTI 外设捕捉到 PA0 引脚输入电平的这一变化,并向 NVIC 发送中断请求。
- 后续的处理过程与我们前面学习的中断流程一致,即通过配置 NVIC 使能此外部中断,并由 NVIC 统一完成中断调度与执行。
如果能够理解以上内容,就可以发现:
EXTI 只是帮 GPIO 引脚上的外部设备“代发”了一次中断请求。
一旦中断请求进入 NVIC,后面走的流程,就和 USART外设中断没有任何区别了。
EXTI外部中断执行流程
GPIO引脚只有在设置为输入模式,连接一个输入向设备时,才有借助EXTI外设发起中断请求的需求。
为了让EXTI外设,能够及时检测IO引脚的输入电平变化,我们需要将IO引脚的电平信号映射到EXTI外设。
这里需要借助一个新的外设——AFIO,来完成这个信号映射。
EXTI外部中断,整个执行流程如下图所示:

在这个系统中:
- GPIO引脚,负责接收外部信号,必须配置为输入工作模式。
- AFIO外设,负责将GPIO引脚接收到的外部信号,映射传递给EXTI模块。
- EXTI是信号的处理者,负责检测GPIO引脚的电平变化并提交中断请求给NVIC。
- NVIC作为中断的中心处理管理者,负责管理中断优先级以及中断响应等。
- 最后,由NVIC打断主程序/其它中断执行,执行当前EXTI的中断处理程序。
整个外部中断流程,从外部信号输入开始,依次经过 GPIO、AFIO、EXTI、NVIC 四个模块,最终由 CPU 执行中断处理程序。
因此,后续在实现外部中断功能时,需要严格按照这一流程,对各个模块进行正确配置。
其中的第一步,即设置IO引脚为输入模式,这个我们已经非常熟悉了,这里不再赘述。
下面我们来介绍一下AFIO模块。
AFIO外设模块
AFIO(Alternate Function I/O,复用 I/O 接口)是 STM32 系列单片机中一个功能相对较少的外设模块,主要起到辅助和配置的作用。
在SPL库中,它也没有专门的函数模块,其相关函数都合并在GPIO模块。
AFIO最主要的用途就只有两个:
- 实现引脚的重定义功能,参考表格引脚定义表。
- 当硬件设计中默认复用功能引脚被占用时,可以通过 AFIO 重映射切换到其他引脚。
- 在SPL库中,实际只需要调用一个
GPIO_PinRemapConfig函数就可以实现引脚功能重定义。
- **将某个IO引脚的输入信号,映射到EXTI外设上。**AFIO 可以将特定的 GPIO 引脚映射到 EXTI模块,用于处理外部信号输入并触发中断请求。
下面我们就来看一下,AFIO外设模块的这两个功能。
需要注意的是:
- 作为一个独立的外设模块,在使用 AFIO 之前,必须先开启其外设时钟。
- AFIO 外设挂载在 APB2 外设总线 上,相关时钟使能方式与其他 APB2 外设一致,这里不再赘述。
AFIO重定义引脚功能
在之前所有的串口通信中,我们都直接使用了PA9和PA10的引脚复用功能,直接把它们当成USART1_TX和USART1_RX引脚使用。
现在假如PA9和PA10引脚不可用了,于是我们只能考虑重定义引脚功能。比如参考下图:

我们把串口通信的接线图稍作修改:
- 将USB-TTL设备的RXD针脚接入单片机的PB6(USART1_Tx)引脚。
- 将USB-TTL设备的TXD针脚接入单片机的PB7(USART1_Rx)引脚。
如下图所示:

现在我们使用AFIO模块,来将PB6和PB7重定义为串口通信的引脚。
此时我们需要使用的函数就是GPIO_PinRemapConfig函数。
GPIO_PinRemapConfig 是 STM32 标准库中的一个函数,主要用于重定义GPIO引脚的功能。
其函数声明如下:
void GPIO_PinRemapConfig(uint32_t GPIO_Remap, FunctionalState NewState);
第一个参数GPIO_Remap是此函数的核心:
用于指定将要重定义的引脚类型。
它使用一系列的宏定义作为传参,比如我们需要的:GPIO_Remap_USART1
通过查表我们知道能够作为USART1类型的重定义功能引脚,只有PB6和PB7。
也就是说调用此函数后,只需要将这两个引脚按照需求正常初始化,即可实现它们的重定义USART1功能。
第二个参数NewState则没什么可说的,直接传参ENABLE表示开启重定义功能,DISABLE表示禁用重定义功能。
除此之外,此函数还有以下注意事项:
- 引脚重定义功能的实现需要AFIO外设的参与,所以调用此函数之前不要忘记开启AFIO外设时钟。
- 此函数一次调用只能重定义一种功能的引脚,不能同时重定义多种类型的引脚。
最终,我们实现代码如下:
#include "stm32f10x.h"
#include "../tools/Delay.h"
#include "../tools/USART.h"
#include "../tools/OLED.h"
// 初始化引脚以及AFIO重定义引脚
void init_USART1_Rx_TX(){
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB | RCC_APB2Periph_AFIO, ENABLE);
// 重定义USART1引脚
GPIO_PinRemapConfig(GPIO_Remap_USART1, ENABLE);
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_6;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_2MHz;
GPIO_Init(GPIOB, &GPIO_InitStruct);
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_7;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_2MHz;
GPIO_Init(GPIOB, &GPIO_InitStruct);
}
//初始化USART1
void init_USART(){
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
USART_InitTypeDef USART_InitStruct;
USART_InitStruct.USART_BaudRate = 115200;
USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None ;
USART_InitStruct.USART_Mode = USART_Mode_Rx | USART_Mode_Tx ;
USART_InitStruct.USART_Parity = USART_Parity_No;
USART_InitStruct.USART_StopBits = USART_StopBits_1;
USART_InitStruct.USART_WordLength = USART_WordLength_8b;
USART_Init(USART1, &USART_InitStruct);
USART_Cmd(USART1, ENABLE);
}
// 初始化中断
void init_NVIC() {
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);
NVIC_InitTypeDef NVIC_InitStruct;
NVIC_InitStruct.NVIC_IRQChannel = USART1_IRQn;
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = NVIC_PriorityGroup_4;
NVIC_InitStruct.NVIC_IRQChannelSubPriority = NVIC_PriorityGroup_4;
NVIC_Init(&NVIC_InitStruct);
}
// 设置中断源
void USART_RXNEConfig() {
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
}
// 设置ISR
void USART1_IRQHandler() {
if (USART_GetITStatus(USART1, USART_IT_RXNE) == SET) {
char rch = USART_ReceiveData(USART1);
OLED_ShowChar(2,2,rch);
}
}
int main(void) {
init_USART();
init_USART1_Rx_TX();
OLED_Init();
init_NVIC();
USART_RXNEConfig();
while(1){
...
...
}
}
AFIO映射IO引脚到EXTI线(重点)
AFIO外设的第二个功能是,映射IO引脚电平信号到EXTI外设。
AFIO映射IO引脚到EXTI,再由EXTI产生中断请求,其过程如下图所示:

下面详细来解释一下这张图。
EXTI线
AFIO外设会将GPIO引脚的电平信号,映射到EXTI中断线上。
EXTI外设内部一共提供了16条外部中断线(后续直接简称为**“EXTI线”**),分别是:
EXTI_Line0 ~ EXTI_Line15
每一条线都对应一个固定的IO引脚编号,而不是对应某个具体的IO引脚。
比如:
- EXTI_Line0对应所有编号为0的引脚,比如PA0、PB0、PD0
- …
- EXTI_Line4对应所有编号为4的引脚,比如PA4、PB4
- …
假如PA0引脚连接的设备需要发中断请求,那么就需要让AFIO外设,将PA0引脚的电平信号映射到EXTI线0上。
当然,这里就自然而然地联想到一个问题:
某条EXTI线,同一时刻可以映射多个IO引脚吗?
比如EXTI线0,可不可以同时映射PA0和PB0引脚呢?
当然不可以。
(重点)某条EXTI线,在同一时刻,只能映射相同编号的一个IO引脚!!
比如:
- 将PA1引脚映射到EXTI线1上,就不能再映射PB1引脚。
- 如果再次将PB1引脚映射到EXTI线1,那么PB1引脚的映射就会覆盖PA1引脚!EXTI线1此时的电平信号就是PB1引脚的。
(了解)这是因为引脚到EXTI线的映射配置,是AFIO外设的外部中断配置寄存器组决定的,同一个编号的引脚不能同时映射到同一条线。
寄存器的定义内容, 可以参考下面的图片(从参考手册中获取):

可以看到AFIO外部中断配置寄存器中,某一条线的映射引脚是固定4位确定的,如果重复映射,就会产生覆盖行为!
这一个细节大家可以留意一下。
EXTI外部中断请求
EXTI检测IO引脚的电平变化,最终的目的是要向NVIC发送中断请求。
那么EXTI外部中断外设,总共可以发送多少个中断请求呢?(类比整个USART1外设,只能发送1个全局中断)

很明显,通过阅读这张图:EXTI外设一共可以产生7个中断请求。
具体来说是:
- GPIO所有编号为04的IO引脚,它们都被AFIO映射到04的EXTI线上,并最终各自单独发送一个EXTIx_IRQn的中断请求。
- 这5条线,都可以发送单独的中断请求:
- EXTI_Line0 → EXTI0_IRQn
- EXTI_Line1 → EXTI1_IRQn
- EXTI_Line2 → EXTI2_IRQn
- EXTI_Line3 → EXTI3_IRQn
- EXTI_Line4 → EXTI4_IRQn
- GPIO所有编号为59的IO引脚,它们分别被AFIO映射到59的EXTI线上,但它们共同触发一个中断请求EXTI9_5_IRQn
- 注意这5条线,虽然它们的映射线也5有条,但它们只能发出同一个中断请求。
- EXTI线号和引脚编号独立对应,但中断请求并不和线号独立对应!
- GPIO所有编号为1015的IO引脚,它们分别被AFIO映射到1015的EXTI线上,但它们共同触发一个中断请求EXTI15_10_IRQn
- 注意这6条线,虽然它们的映射线也有6条,但它们只能发出同一个中断请求。
- EXTI线号和引脚编号独立对应,但中断请求并不和线号独立对应!
综合上面讲解的EXTI线,以及外部中断请求的知识点,我们举一些例子:
- PB14引脚上连接的设备,有发送外部中断请求的需求。
- 借助AFIO外设,将PB14引脚的输入信号映射到EXTI线14。
- EXTI外设,此时产生的中断请求是:EXTI15_10_IRQn。
- PA3引脚上连接的设备,有发送外部中断请求的需求。
- 借助AFIO外设,将PA3引脚的输入信号映射到EXTI线3。
- EXTI外设,此时产生的中断请求是:EXTI3_IRQn。
最后,我们再思考几个问题:
问题1:与此同时,PB3引脚上的设备也想发中断请求,能够实现吗?
答:当然无法实现。
同一条线在同一时刻,只能映射唯一的一个IO引脚。
比如EXTI线3映射了PA3引脚,就不可能再同时映射PB3引脚了。
问题2:既然EXT线3不能同时映射两个引脚,那么能不能让PB3引脚映射到EXTI线4呢?
答:当然也不可以。EXTI线号和引脚编号是严格一一对应的,这是硬件设计问题。
如果出现这种情况,最好的办法就是,考虑换一个引脚编号,不再都使用"Pin_3"。
问题3:与此同时,PB11引脚上连接的设备,也有发送外部中断请求的需求。这个能实现吗?
答:这是允许的。PB11引脚会映射到EXTI线11,并没有冲突。
但是EXTI线11和线14,都发送同一个外部中断请求EXTI15_10_IRQn,在编写中断处理函数时需要对触发线做判断!
到目前为止,关于AFIO映射EXTI线的相关问题,我们都理解了。
EXTI外设获取了IO引脚的电平变化,那么它会在什么情况下产生中断请求呢?
换句话说,EXTI外设产生中断请求的条件是什么呢?
下面我们就来了解一下EXTI外设触发中断的条件与机制。
EXTI触发中断的条件与机制(重点)
通过 AFIO 外设的映射,EXTI 外设已经能够获取 GPIO 引脚上的外部输入电平变化。
接下来,EXTI 外设就需要根据 引脚电平变化的方式,来决定 是否产生中断请求。
EXTI外设提供了三种不同的中断触发方式:
- 上升沿触发:当 GPIO 引脚从 低电平(0)变化为 高电平(1)时,EXTI 产生中断请求。
- 下降沿触发:当 GPIO 引脚从 高电平(1)变化为 低电平(0)时,EXTI 产生中断请求。
- 双边沿触发:只要 GPIO 引脚的输入电平发生了变化(低到高或者高到低),EXTI 都会产生中断请求。
如下图所示:

对于EXTI外设而言,它的中断机制可以用下图来描述:

也就是说,要让 某一条 EXTI 线产生中断请求,需要进行以下配置:
- 配置开启中断产生的EXTI线。
- 配置EXTI线的模式为中断模式,而不是事件模式。
- 配置EXTI中断的触发条件:上升沿,下降沿或者双边沿。
除此之外,使用外部中断有一个特别需求注意的事情:
在外部中断的中断处理函数中,一定要手动清除(清零)中断标志位,否则中断会一直触发!程序直接卡死。
如此,在EXTI外设中配置产生中断请求就全部弄明白了。
剩下的步骤是什么呢?剩下的步骤就是配置NVIC,然后中断触发执行处理逻辑。
如此,EXTI外部中断触发的完整流程如下:
- 配置 GPIO 引脚为输入模式。
- AFIO映射IO引脚到EXTI线。
- 配置EXTI外设,设置中断触发条件,设置模式为发送中断请求,设置某条EXTI线工作等。
- 配置NVIC,设置EXTI外部中断的中断号、优先级并开启此中断。
- 编写对应EXTI外部中断号的中断处理函数。
- NVIC 管理中断请求,并根据优先级决定是否响应。
- 中断响应执行对应中断号的中断处理函数,并且在中断处理函数中清除(清零)中断标志位!
现在,我们万事俱备,几乎所有的原理都搞明白了,只差学习对应的函数。
之后我们会用具体的实验案例,来加深对原理的理解,并且实现外部中断。
扩展了解:EXTI的事件模式
EXTI 外设最主要的作用,是产生外部中断请求。
但在少数应用场景下,EXTI 外设还可以用来产生事件信号,这就是 EXTI 外设的事件模式。
在事件模式下,当某一条 EXTI 线满足设定的触发条件(例如上升沿或下降沿)时:
- EXTI 外设会在内部产生一个硬件事件信号
- 这个硬件事件信号,不会发中断请求,不会触发 NVIC
- 自然也不会进入中断处理函数
也就是说:
EXTI外设的事件模式,只产生事件,不发中断请求。
那EXTI产生的事件,有什么用呢?
这些事件信号可以被单片机内部的某些片上外设接收,从而触发特定的硬件行为。
EXTI外设的事件模式比较少用到,下面举一个比较简单的例子,大家了解一下这个事件模式的作用就可以了:
STM32 的 PWR 电源控制器模块,可以让单片机进入一种超低功耗的 STOP 模式。在该模式下单片机的时钟会几乎完全停止,CPU不再执行,只使
用少部分电力维持SRAM和寄存器的数据。
此时,如果希望单片机从 STOP 模式中退出,重新回到正常运行状态,可以通过:
- 中断唤醒
- 事件唤醒
在支持事件唤醒的情况下,可以使用 EXTI 外设的事件模式:
- 将某个 GPIO 引脚连接到按键
- 配置对应 EXTI 线为事件模式
- 设置触发条件为上升沿或下降沿
- 当按键被按下时,产生事件信号
- 事件信号触发单片机从 STOP 模式中唤醒
这种方式进行唤醒,不会打断主程序的执行流程,不会进入中断服务程序,非常适合用于节能应用。[TOC]
EXTI外部中断
在前面我们已经讲过了STM32中断系统的系统框图,如下所示:

像 USART、这类片内外设,本身就是单片机内部集成的功能模块。
它们在检测到特定事件发生(比如接收完成)时,可以直接向NVIC发送中断请求。
再由NVIC统一进行中断调度执行。
那么问题来了:
那些连接在 GPIO 引脚上的“片外外设”,如果它们也想发送中断请求,怎么办?
比如:
- 某个接在IO引脚上的按键,希望能够在按键按下时,触发一个中断请求,怎么实现呢?
- 某个可以输出高低电平状态的传感器,希望在输出高电平时,触发一个中断请求,怎么实现呢?
中断是单片机内部系统的功能,所以这些外部设备,需要借助一个单片机外设来向NVIC发送中断请求。
这个外设就是:EXTI外设。
注意:
- 只要输入型的设备,才具有发送中断请求的需求,比如按键、传感器等。
- 输出驱动型设备,像LED、蜂鸣器等显然没有这种需求。
EXTI外设简述
EXTI(External interrupt / event controller,外部中断 / 事件控制器),一般直接简称为外部中断。
需要特别注意的是:
**EXTI 外设不是 “EXIT / exit”,并不是“退出”的英文单词,而是 “External Interrupt” 的缩写,请不要混淆。
EXTI 外设,是单片机内部的一个片内外设,用于统一处理来自 GPIO 引脚所连接的外部设备 的中断请求。
说得更具体一点:
EXTI 外设的作用,是将 GPIO 引脚上的外部电平变化(例如由低电平变为高电平),转换为单片机内部可以识别的中断请求。
说得更通俗一点:
GPIO引脚上接入的外部设备,在引脚输入电平变化时,可以借助EXTI外设,由EXTI外设向NVIC发送中断请求。
再举一个实际的例子:
- PA0 引脚上接入了一个传感器,该传感器可以输出高电平或低电平。
- 在正常情况下,传感器输出低电平,PA0 引脚输入低电平。
- 当外部环境发生变化时,传感器转而输出高电平,此时 PA0 引脚的输入电平从低电平切换为高电平。
- EXTI 外设捕捉到 PA0 引脚输入电平的这一变化,并向 NVIC 发送中断请求。
- 后续的处理过程与我们前面学习的中断流程一致,即通过配置 NVIC 使能此外部中断,并由 NVIC 统一完成中断调度与执行。
如果能够理解以上内容,就可以发现:
EXTI 只是帮 GPIO 引脚上的外部设备“代发”了一次中断请求。
一旦中断请求进入 NVIC,后面走的流程,就和 USART外设中断没有任何区别了。
EXTI外部中断执行流程
GPIO引脚只有在设置为输入模式,连接一个输入向设备时,才有借助EXTI外设发起中断请求的需求。
为了让EXTI外设,能够及时检测IO引脚的输入电平变化,我们需要将IO引脚的电平信号映射到EXTI外设。
这里需要借助一个新的外设——AFIO,来完成这个信号映射。
EXTI外部中断,整个执行流程如下图所示:

在这个系统中:
- GPIO引脚,负责接收外部信号,必须配置为输入工作模式。
- AFIO外设,负责将GPIO引脚接收到的外部信号,映射传递给EXTI模块。
- EXTI是信号的处理者,负责检测GPIO引脚的电平变化并提交中断请求给NVIC。
- NVIC作为中断的中心处理管理者,负责管理中断优先级以及中断响应等。
- 最后,由NVIC打断主程序/其它中断执行,执行当前EXTI的中断处理程序。
整个外部中断流程,从外部信号输入开始,依次经过 GPIO、AFIO、EXTI、NVIC 四个模块,最终由 CPU 执行中断处理程序。
因此,后续在实现外部中断功能时,需要严格按照这一流程,对各个模块进行正确配置。
其中的第一步,即设置IO引脚为输入模式,这个我们已经非常熟悉了,这里不再赘述。
下面我们来介绍一下AFIO模块。
AFIO外设模块
AFIO(Alternate Function I/O,复用 I/O 接口)是 STM32 系列单片机中一个功能相对较少的外设模块,主要起到辅助和配置的作用。
在SPL库中,它也没有专门的函数模块,其相关函数都合并在GPIO模块。
AFIO最主要的用途就只有两个:
- 实现引脚的重定义功能,参考表格引脚定义表。
- 当硬件设计中默认复用功能引脚被占用时,可以通过 AFIO 重映射切换到其他引脚。
- 在SPL库中,实际只需要调用一个
GPIO_PinRemapConfig函数就可以实现引脚功能重定义。
- **将某个IO引脚的输入信号,映射到EXTI外设上。**AFIO 可以将特定的 GPIO 引脚映射到 EXTI模块,用于处理外部信号输入并触发中断请求。
下面我们就来看一下,AFIO外设模块的这两个功能。
需要注意的是:
- 作为一个独立的外设模块,在使用 AFIO 之前,必须先开启其外设时钟。
- AFIO 外设挂载在 APB2 外设总线 上,相关时钟使能方式与其他 APB2 外设一致,这里不再赘述。
AFIO重定义引脚功能
在之前所有的串口通信中,我们都直接使用了PA9和PA10的引脚复用功能,直接把它们当成USART1_TX和USART1_RX引脚使用。
现在假如PA9和PA10引脚不可用了,于是我们只能考虑重定义引脚功能。比如参考下图:

我们把串口通信的接线图稍作修改:
- 将USB-TTL设备的RXD针脚接入单片机的PB6(USART1_Tx)引脚。
- 将USB-TTL设备的TXD针脚接入单片机的PB7(USART1_Rx)引脚。
如下图所示:

现在我们使用AFIO模块,来将PB6和PB7重定义为串口通信的引脚。
此时我们需要使用的函数就是GPIO_PinRemapConfig函数。
GPIO_PinRemapConfig 是 STM32 标准库中的一个函数,主要用于重定义GPIO引脚的功能。
其函数声明如下:
void GPIO_PinRemapConfig(uint32_t GPIO_Remap, FunctionalState NewState);
第一个参数GPIO_Remap是此函数的核心:
用于指定将要重定义的引脚类型。
它使用一系列的宏定义作为传参,比如我们需要的:GPIO_Remap_USART1
通过查表我们知道能够作为USART1类型的重定义功能引脚,只有PB6和PB7。
也就是说调用此函数后,只需要将这两个引脚按照需求正常初始化,即可实现它们的重定义USART1功能。
第二个参数NewState则没什么可说的,直接传参ENABLE表示开启重定义功能,DISABLE表示禁用重定义功能。
除此之外,此函数还有以下注意事项:
- 引脚重定义功能的实现需要AFIO外设的参与,所以调用此函数之前不要忘记开启AFIO外设时钟。
- 此函数一次调用只能重定义一种功能的引脚,不能同时重定义多种类型的引脚。
最终,我们实现代码如下:
#include "stm32f10x.h"
#include "../tools/Delay.h"
#include "../tools/USART.h"
#include "../tools/OLED.h"
// 初始化引脚以及AFIO重定义引脚
void init_USART1_Rx_TX(){
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB | RCC_APB2Periph_AFIO, ENABLE);
// 重定义USART1引脚
GPIO_PinRemapConfig(GPIO_Remap_USART1, ENABLE);
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_6;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_2MHz;
GPIO_Init(GPIOB, &GPIO_InitStruct);
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_7;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_2MHz;
GPIO_Init(GPIOB, &GPIO_InitStruct);
}
//初始化USART1
void init_USART(){
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
USART_InitTypeDef USART_InitStruct;
USART_InitStruct.USART_BaudRate = 115200;
USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None ;
USART_InitStruct.USART_Mode = USART_Mode_Rx | USART_Mode_Tx ;
USART_InitStruct.USART_Parity = USART_Parity_No;
USART_InitStruct.USART_StopBits = USART_StopBits_1;
USART_InitStruct.USART_WordLength = USART_WordLength_8b;
USART_Init(USART1, &USART_InitStruct);
USART_Cmd(USART1, ENABLE);
}
// 初始化中断
void init_NVIC() {
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);
NVIC_InitTypeDef NVIC_InitStruct;
NVIC_InitStruct.NVIC_IRQChannel = USART1_IRQn;
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = NVIC_PriorityGroup_4;
NVIC_InitStruct.NVIC_IRQChannelSubPriority = NVIC_PriorityGroup_4;
NVIC_Init(&NVIC_InitStruct);
}
// 设置中断源
void USART_RXNEConfig() {
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
}
// 设置ISR
void USART1_IRQHandler() {
if (USART_GetITStatus(USART1, USART_IT_RXNE) == SET) {
char rch = USART_ReceiveData(USART1);
OLED_ShowChar(2,2,rch);
}
}
int main(void) {
init_USART();
init_USART1_Rx_TX();
OLED_Init();
init_NVIC();
USART_RXNEConfig();
while(1){
...
...
}
}
AFIO映射IO引脚到EXTI线(重点)
AFIO外设的第二个功能是,映射IO引脚电平信号到EXTI外设。
AFIO映射IO引脚到EXTI,再由EXTI产生中断请求,其过程如下图所示:

下面详细来解释一下这张图。
EXTI线
AFIO外设会将GPIO引脚的电平信号,映射到EXTI中断线上。
EXTI外设内部一共提供了16条外部中断线(后续直接简称为**“EXTI线”**),分别是:
EXTI_Line0 ~ EXTI_Line15
每一条线都对应一个固定的IO引脚编号,而不是对应某个具体的IO引脚。
比如:
- EXTI_Line0对应所有编号为0的引脚,比如PA0、PB0、PD0
- …
- EXTI_Line4对应所有编号为4的引脚,比如PA4、PB4
- …
假如PA0引脚连接的设备需要发中断请求,那么就需要让AFIO外设,将PA0引脚的电平信号映射到EXTI线0上。
当然,这里就自然而然地联想到一个问题:
某条EXTI线,同一时刻可以映射多个IO引脚吗?
比如EXTI线0,可不可以同时映射PA0和PB0引脚呢?
当然不可以。
(重点)某条EXTI线,在同一时刻,只能映射相同编号的一个IO引脚!!
比如:
- 将PA1引脚映射到EXTI线1上,就不能再映射PB1引脚。
- 如果再次将PB1引脚映射到EXTI线1,那么PB1引脚的映射就会覆盖PA1引脚!EXTI线1此时的电平信号就是PB1引脚的。
(了解)这是因为引脚到EXTI线的映射配置,是AFIO外设的外部中断配置寄存器组决定的,同一个编号的引脚不能同时映射到同一条线。
寄存器的定义内容, 可以参考下面的图片(从参考手册中获取):

可以看到AFIO外部中断配置寄存器中,某一条线的映射引脚是固定4位确定的,如果重复映射,就会产生覆盖行为!
这一个细节大家可以留意一下。
EXTI外部中断请求
EXTI检测IO引脚的电平变化,最终的目的是要向NVIC发送中断请求。
那么EXTI外部中断外设,总共可以发送多少个中断请求呢?(类比整个USART1外设,只能发送1个全局中断)

很明显,通过阅读这张图:EXTI外设一共可以产生7个中断请求。
具体来说是:
- GPIO所有编号为04的IO引脚,它们都被AFIO映射到04的EXTI线上,并最终各自单独发送一个EXTIx_IRQn的中断请求。
- 这5条线,都可以发送单独的中断请求:
- EXTI_Line0 → EXTI0_IRQn
- EXTI_Line1 → EXTI1_IRQn
- EXTI_Line2 → EXTI2_IRQn
- EXTI_Line3 → EXTI3_IRQn
- EXTI_Line4 → EXTI4_IRQn
- GPIO所有编号为59的IO引脚,它们分别被AFIO映射到59的EXTI线上,但它们共同触发一个中断请求EXTI9_5_IRQn
- 注意这5条线,虽然它们的映射线也5有条,但它们只能发出同一个中断请求。
- EXTI线号和引脚编号独立对应,但中断请求并不和线号独立对应!
- GPIO所有编号为1015的IO引脚,它们分别被AFIO映射到1015的EXTI线上,但它们共同触发一个中断请求EXTI15_10_IRQn
- 注意这6条线,虽然它们的映射线也有6条,但它们只能发出同一个中断请求。
- EXTI线号和引脚编号独立对应,但中断请求并不和线号独立对应!
综合上面讲解的EXTI线,以及外部中断请求的知识点,我们举一些例子:
- PB14引脚上连接的设备,有发送外部中断请求的需求。
- 借助AFIO外设,将PB14引脚的输入信号映射到EXTI线14。
- EXTI外设,此时产生的中断请求是:EXTI15_10_IRQn。
- PA3引脚上连接的设备,有发送外部中断请求的需求。
- 借助AFIO外设,将PA3引脚的输入信号映射到EXTI线3。
- EXTI外设,此时产生的中断请求是:EXTI3_IRQn。
最后,我们再思考几个问题:
问题1:与此同时,PB3引脚上的设备也想发中断请求,能够实现吗?
答:当然无法实现。
同一条线在同一时刻,只能映射唯一的一个IO引脚。
比如EXTI线3映射了PA3引脚,就不可能再同时映射PB3引脚了。
问题2:既然EXT线3不能同时映射两个引脚,那么能不能让PB3引脚映射到EXTI线4呢?
答:当然也不可以。EXTI线号和引脚编号是严格一一对应的,这是硬件设计问题。
如果出现这种情况,最好的办法就是,考虑换一个引脚编号,不再都使用"Pin_3"。
问题3:与此同时,PB11引脚上连接的设备,也有发送外部中断请求的需求。这个能实现吗?
答:这是允许的。PB11引脚会映射到EXTI线11,并没有冲突。
但是EXTI线11和线14,都发送同一个外部中断请求EXTI15_10_IRQn,在编写中断处理函数时需要对触发线做判断!
到目前为止,关于AFIO映射EXTI线的相关问题,我们都理解了。
EXTI外设获取了IO引脚的电平变化,那么它会在什么情况下产生中断请求呢?
换句话说,EXTI外设产生中断请求的条件是什么呢?
下面我们就来了解一下EXTI外设触发中断的条件与机制。
EXTI触发中断的条件与机制(重点)
通过 AFIO 外设的映射,EXTI 外设已经能够获取 GPIO 引脚上的外部输入电平变化。
接下来,EXTI 外设就需要根据 引脚电平变化的方式,来决定 是否产生中断请求。
EXTI外设提供了三种不同的中断触发方式:
- 上升沿触发:当 GPIO 引脚从 低电平(0)变化为 高电平(1)时,EXTI 产生中断请求。
- 下降沿触发:当 GPIO 引脚从 高电平(1)变化为 低电平(0)时,EXTI 产生中断请求。
- 双边沿触发:只要 GPIO 引脚的输入电平发生了变化(低到高或者高到低),EXTI 都会产生中断请求。
如下图所示:

对于EXTI外设而言,它的中断机制可以用下图来描述:

也就是说,要让 某一条 EXTI 线产生中断请求,需要进行以下配置:
- 配置开启中断产生的EXTI线。
- 配置EXTI线的模式为中断模式,而不是事件模式。
- 配置EXTI中断的触发条件:上升沿,下降沿或者双边沿。
除此之外,使用外部中断有一个特别需求注意的事情:
在外部中断的中断处理函数中,一定要手动清除(清零)中断标志位,否则中断会一直触发!程序直接卡死。
如此,在EXTI外设中配置产生中断请求就全部弄明白了。
剩下的步骤是什么呢?剩下的步骤就是配置NVIC,然后中断触发执行处理逻辑。
如此,EXTI外部中断触发的完整流程如下:
- 配置 GPIO 引脚为输入模式。
- AFIO映射IO引脚到EXTI线。
- 配置EXTI外设,设置中断触发条件,设置模式为发送中断请求,设置某条EXTI线工作等。
- 配置NVIC,设置EXTI外部中断的中断号、优先级并开启此中断。
- 编写对应EXTI外部中断号的中断处理函数。
- NVIC 管理中断请求,并根据优先级决定是否响应。
- 中断响应执行对应中断号的中断处理函数,并且在中断处理函数中清除(清零)中断标志位!
现在,我们万事俱备,几乎所有的原理都搞明白了,只差学习对应的函数。
之后我们会用具体的实验案例,来加深对原理的理解,并且实现外部中断。
扩展了解:EXTI的事件模式
EXTI 外设最主要的作用,是产生外部中断请求。
但在少数应用场景下,EXTI 外设还可以用来产生事件信号,这就是 EXTI 外设的事件模式。
在事件模式下,当某一条 EXTI 线满足设定的触发条件(例如上升沿或下降沿)时:
- EXTI 外设会在内部产生一个硬件事件信号
- 这个硬件事件信号,不会发中断请求,不会触发 NVIC
- 自然也不会进入中断处理函数
也就是说:
EXTI外设的事件模式,只产生事件,不发中断请求。
那EXTI产生的事件,有什么用呢?
这些事件信号可以被单片机内部的某些片上外设接收,从而触发特定的硬件行为。
EXTI外设的事件模式比较少用到,下面举一个比较简单的例子,大家了解一下这个事件模式的作用就可以了:
STM32 的 PWR 电源控制器模块,可以让单片机进入一种超低功耗的 STOP 模式。在该模式下单片机的时钟会几乎完全停止,CPU不再执行,只使
用少部分电力维持SRAM和寄存器的数据。
此时,如果希望单片机从 STOP 模式中退出,重新回到正常运行状态,可以通过:
- 中断唤醒
- 事件唤醒
在支持事件唤醒的情况下,可以使用 EXTI 外设的事件模式:
- 将某个 GPIO 引脚连接到按键
- 配置对应 EXTI 线为事件模式
- 设置触发条件为上升沿或下降沿
- 当按键被按下时,产生事件信号
- 事件信号触发单片机从 STOP 模式中唤醒
这种方式进行唤醒,不会打断主程序的执行流程,不会进入中断服务程序,非常适合用于节能应用。
更多推荐


所有评论(0)