一、什么是外部中断?
外部中断是指由微处理器或微控制器外部引脚(通常是GPIO引脚)上的外部事件触发的中断。这些外部事件可以是来自外部设备、传感器或其他外部信号源的触发。
二、外部中断EXTI详细内容
由AFIO时钟管理的寄存器有AFIO_EVCR(时间控制寄存器)、AFIO_MAPR(备用功能重映射和调试IO配置寄存器)、AFIO_EXTICRX(外部中断配置寄存器)。我们需要通过AFIO_EXTICRx配置GPIO线上的外部中断/事件,必须先使能AFIO时钟。所以在配置EXTI时需要先开启AFIO时钟。
1. 总线分布
每个不同的GPIO序号对应着不同的EXTI线,在配置时这也是至关重要的一步。
● EXTI线16连接到PVD输出。
● EXTI线17连接到RTC闹钟事件。
● EXTI线18连接到USB唤醒事件。
● EXTI线19连接到以太网唤醒事件(只适用于互联型产品) 。
2. 相关寄存器
(1)中断屏蔽寄存器(EXTI_IMR): 屏蔽(置0)或开放(置1)来自线x的中断请求。使能/失能中断。
(2) 挂起寄存器(EXTI_PR) : 没有发生(置0) /发生(置1)触发请求。
当在外部中断线上发生了选择的边沿事件,该位被置’1’。在该位中写入’1’可以清除它,也可以通过改变边沿检测的极性清除。
(3) 事件屏蔽寄存器(EXTI_EMR) : 屏蔽(置0)或开放(置1)来自线x的事件请求。使能/失能事件。
(4)上升沿触发选择寄存器(EXTI_RTSR) : 禁止(置0)或允许(置1)输入线x上的上升沿触发(中断和事件) 。
(5)下降沿触发选择寄存器(EXTI_FTSR) : 禁止(置0)或允许(置1)输入线x上的下降沿触发(中断和事件) 。
(6)软件中断事件寄存器(EXTI_SWIER) : 当该位为’0’时,写’1’将设置EXTI_PR中相应的挂起位。如果在EXTI_IMR和EXTI_EMR中允许产生该中断,则此时将产生一个中断。
注:通过清除EXTI_PR的对应位(写入’1’),可以清除该位为’0’。
3. 框架图分析
(1) 如果采用外设(输入线)来产生中断,可以选择上升/下降/双边沿触发中断。此时软件中断事件寄存器必须写0。因为软件中断事件寄存器和边沿检测电路通过"或门"连接。如果软件中断寄存器置1,那么会直接产生中断信号,那么外设产生或不产生中断都没有作用。 通过在软件中对软件中断/事件寄存器写 1 ,也可以产生中断/事件请求。
(2) EXTI 可分为两大部分功能,一个是产生中断,另一个是产生事件,这两个功能从硬件上就有所不同。
●产生中断线路目的: 是把输入信号输入到 NVIC,进一步会运行中断服务函数,实现功能,这样是软件级的。
●产生事件线路目的: 就是传输一个脉冲信号给其他外设使用,并且是电路级别的信号传输,属于硬件级的。
●产生中断步骤: 根据输入线外设电路需要的边沿检测设置触发寄存器,并将 “软件中断事件寄存器” 置0(原因如上述),同时在 “中断屏蔽寄存器” 的相应位写 1 使能中断请求。当外部中断线上出现选定信号沿时,便会产生中断请求,“请求挂起寄存器” 的对应的挂起位会置1。在 “请求挂起寄存器” 的对应位写 1 ,将清除该中断请求。
●产生事件的步骤: 根据输入线外设电路需要的边沿检测设置触发寄存器,同时在 “事件屏蔽寄存器” 的相应位写 1 允许事件请求。当事件线上出现选定信号沿时,便会产生事件脉冲。
(3)“脉冲发生器” 去触发事件响应,它主要连到其它外设,中断信号来了就激活脉冲发生器,给它连接的其它外设发射一次脉冲信号。
(4) 两个“屏蔽寄存器”。它们都和我们的信号连到了一个 “与门” 上,这就意味着如果把这个屏蔽寄存器的某些位设为 0,来的中断信号就永远过不去这个与门,达到了一个“屏蔽”的效果。例如不希望某个中断通道起作用,那就把中断屏蔽寄存器的那一位设为 0,这里来的中断信号就永远到不了 NVIC;再例如不需要事件响应的功能,那么就把事件屏蔽器全设为 0,事件响应就无效了。
4. 中断
GPIO的端口号对应着总线,例如PA1对应总线1,PC3对应总线3。而总线EXTI0~4分别对应着自己的中断,即总线EXTI0使用EXTI0中断,总线EXTI1使用EXTI1中断…
总线EXTI5~9选择中断EXTI9_5
总线EXTI10~15选择中断EXTI15_10
5. 相关配置
(1)EXTI模式:
typedef enum
{
EXTI_Mode_Interrupt = 0x00, //产生中断
EXTI_Mode_Event = 0x04 //产生事件
}EXTIMode_TypeDef;
(2)EXTI触发类型:
typedef enum
{
EXTI_Trigger_Rising = 0x08, //上升沿
EXTI_Trigger_Falling = 0x0C, //下降沿
EXTI_Trigger_Rising_Falling = 0x10 //上升沿和下降沿都触发
}EXTITrigger_TypeDef;
6. 配置示例(以 PA10 为例)
功能:开启外部中断,当PA10引脚为高电平时(上升沿)触发外部中断。
(1)使能时钟(因为GPIO映射外部中断,所以需要开启AFIO时钟)
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); // GPIOA 时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); // AFIO 时钟
(2)配置GPIO引脚
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; // 以上拉输入模式为例
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
(3)配置GPIO口与外部中断线的映射关系。(PA10挂在EXTI10总线上)
GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource10);//外部中断线10与PA10相映射。
(4)配置EXTI
EXTI_InitTypeDef EXTI_InitStructure;
EXTI_InitStructure.EXTI_Line = EXTI_Line10; // 选择总线,因为是PA10,所以选择10号线。端口为几就选择几号线。
EXTI_InitStructure.EXTI_LineCmd = ENABLE; // 使能总线
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; // 中断模式
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising; // 上升沿触发,当PA10口为高电平时触发中断。
EXTI_Init(&EXTI_InitStructure);
(5)配置NVIC
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn; // PA10 选择EXTI15_10
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; // 使能中断通道
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; // 抢占优先级,数值范围要注意符合前面分组的规范
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; // 响应优先级,数值范围要注意符合前面分组的规范
NVIC_Init(&NVIC_InitStructure);
//(5)在主函数配置中断优先组
//NVIC_PriorityGroupConfig(NVIC_Priority_Group_2);
(6)编写中断服务函数
void EXTI15_10_IRQHandler(void)
{
if (EXTI_GetITStatus(EXTI_Line10) == SET) { // 判断 PX10 对应的挂起寄存器是否置位,如果置位说明产生了中断
EXTI_ClearITPendingBit(EXTI_Line10); // 清除中断标志位
// 处理你的中断 ...
}
}
为什么要在中断服务函数中清除中断标志位?
答:STM32在产生中断信号后,中断标志位会被硬件置1,所以在处理外部中断信号时,中断服务程序应该在最后的清除操作中将中断标志位重新设置为“0”,以保证系统正常运行。如果中断标志位没有被清除,那么即使该中断信号已经被处理完毕,处理器仍然会认为该中断信号还没有被处理,从而一直处于中断状态,无法处理下一次中断。
7. 附录
(1)软件触发中断的库函数.
void EXTI_GenerateSWInterrupt(uint32_t EXTI_Line);
三、完整代码
功能:这段代码实现了 STM32 外部中断(EXTI)配置,特别是针对 GPIOC 的第 9 引脚(通常用于按钮输入)。该配置会在按钮按下时(触发下降沿)产生外部中断。
exti.c
#include "exti.h"
void EXTI_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
EXTI_InitTypeDef EXTI_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
// 1. 使能 AFIO 时钟(用于配置外部中断线)
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); // 外部中断需要使能 AFIO 时钟
// 2. 使能 GPIOC 时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE); // 使能 GPIOC 端口时钟
// 3. 配置 GPIOC 的第 9 引脚为上拉输入
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; // 选择端口为 GPIOC 第 9 引脚
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; // 配置为上拉输入模式
GPIO_Init(GPIOC, &GPIO_InitStructure); // 初始化 GPIOC 第 9 引脚
// 4. 配置外部中断线连接到 GPIOC 第 9 引脚
GPIO_EXTILineConfig(GPIO_PortSourceGPIOC, GPIO_PinSource9);
// 5. 配置外部中断(EXTI)
EXTI_InitStructure.EXTI_Line = EXTI_Line9; // 选择外部中断线9(对应 GPIOC 第 9 引脚)
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; // 设置为中断模式
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling; // 设置为下降沿触发
EXTI_InitStructure.EXTI_LineCmd = ENABLE; // 使能外部中断线
EXTI_Init(&EXTI_InitStructure); // 初始化 EXTI 寄存器
// 6. 配置中断优先级并使能外部中断通道
NVIC_InitStructure.NVIC_IRQChannel = EXTI9_5_IRQn; // 配置中断通道(EXTI9_5_IRQn)
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x02; // 设置抢占优先级为 2
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x01; // 设置子优先级为 1
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; // 使能外部中断通道
NVIC_Init(&NVIC_InitStructure); // 初始化 NVIC 配置
}
void EXTI9_5_IRQHandler(void)
{
// 判断 EXTI_Line9 是否触发了中断
if (EXTI_GetITStatus(EXTI_Line9) != RESET)
{
// 处理 Line9 的中断(例如,控制 LED)
led=!led; // 切换 LED 状态
// 清除 EXTI_Line9 中断标志
EXTI_ClearITPendingBit(EXTI_Line9);
}
}