Skip to main content

EXTI外部中断

1. 中断系统

中断:在主程序运行过程中,出现了特定的中断触发条件(中断源),使得CPU暂停当前正在运行的程序,转而去处理中断程序,处理完成后又返回原来被暂停的位置继续运行。

中断优先级:当有多个中断源同时申请中断时,CPU会根据中断源的轻重缓急进行裁决,优先响应更加紧急的中断源。

中断嵌套:当一个中断程序正在运行时,又有新的更高优先级的中断源申请中断,CPU再次暂停当前中断程序,转而去处理新的中断程序,处理完成后依次进行返回。可以简单地理解为将中断程序作为另一个主程序又套了一个中断。

2. STM32中断

  • 68个可屏蔽中断通道,包含EXTI、TIM、ADC、USART、SPI、I2C、RTC等多个外设。

  • 使用NVIC统一管理中断,每个中断通道都拥有16个可编程的优先等级,可对优先级进行分组,进一步设置抢占优先级和响应优先级。

Image Image Image

3. NVIC 基本结构

NVIC,嵌套中断向量控制器。用于统一分配中断优先级和管理中断,是一个内核外设。

为了避免CPU出现过多线引,并且中断同时申请,产生很多拥堵,设计了一个叫号系统,这就是NVIC。

3.1 NVIC 优先级分组

为了处理不同的优先级,采用了分组的设计,NVIC的中断优先级由优先级寄存器的4位(0-15)决定,这4位可以进行切分,分为高nn位的抢占优先级

抢占优先级高的可以中断嵌套。响应优先级高的可以优先排队,抢占优先级和响应优先级均相同的按中断号排队。

3.2 EXTI

EXTI是(External interrupt/event controller)的缩写,外部中断/事件控制器的简称。管理控制器的 20个中断/事件线,每个中断/事件线都对应一个边沿检测器,可以实现输入信号的上升沿检测和下降沿的检测,可以实现对每个中断/事件线进行单独配置,可单独配置为中断或事件及其相对应的触发事件的属性。

EXTI由19个产生事件/中断要求的边沿检测器组成。每个输入线可以独立地配置输入类型(脉冲或挂起)和对应的触发事件(上升沿、下降沿、双边沿触发)。每个输入线都可以被独立的屏蔽。

STM32每个GPIO都可以触发一个外部中断,GPIO中断以组为单位,同组同一时间只能用一个。

比如PA0、PB0、PC0、PD0、PE0和PG0为一组,如果PA0作为外部中断源,那么同组的其它GPIO(PB0、PC0、PD0、PE0和PG0)就不能再,只能使用类似于PB1、PC2这种数字序号不同的外部中断源。

warning

但是这个分组是不可以改的哦!它在STM32里面是固定的。它是按引脚序号(数字)进行分组的。

4. NVIC 配置函数

4.1 NVIC初始化

首先我们来看下基本的几个结构体:

NVIC_Type: 中断控制器的寄存器结构体,包含了中断使能寄存器、中断优先级寄存器等。

typedef struct
{
// 中断使能寄存器组 (Interrupt Set-Enable Registers)
// 每个bit对应一个中断的使能状态,共8个32位寄存器(支持最多256个中断)
__IO uint32_t ISER[8];
uint32_t RESERVED0[24]; // 保留区域,用于对齐内存地址

// 中断清除使能寄存器组 (Interrupt Clear-Enable Registers)
// 用于禁用中断,与ISER对应
__IO uint32_t ICER[8];
uint32_t RSERVED1[24]; // 保留区域(注意这里有拼写错误:应为RESERVED1)

// 中断挂起设置寄存器组 (Interrupt Set-Pending Registers)
// 用于手动触发中断
__IO uint32_t ISPR[8];
uint32_t RESERVED2[24]; // 保留区域

// 中断挂起清除寄存器组 (Interrupt Clear-Pending Registers)
// 用于清除挂起状态
__IO uint32_t ICPR[8];
uint32_t RESERVED3[24]; // 保留区域

// 中断活跃状态寄存器组 (Interrupt Active Bit Registers)
// 只读,显示当前正在处理的中断
__IO uint32_t IABR[8];
uint32_t RESERVED4[56]; // 保留区域

// 中断优先级寄存器组 (Interrupt Priority Registers)
// 每个8位字段配置一个中断的优先级(实际使用的位数取决于芯片实现)
__IO uint8_t IP[240];
uint32_t RESERVED5[644]; // 保留区域

// 软件触发中断寄存器 (Software Trigger Interrupt Register)
// 通过写入中断号来软件触发中断
__O uint32_t STIR;
} NVIC_Type;

NVIC_InitTypeDef: 中断初始化结构体,包含了中断通道、中断优先级、中断使能等信息。

typedef struct
{
uint8_t NVIC_IRQChannel;
uint8_t NVIC_IRQChannelPreemptionPriority;
uint8_t NVIC_IRQChannelSubPriority;
FunctionalState NVIC_IRQChannelCmd;
} NVIC_InitTypeDef;

NVIC_PriorityGroupConfig: 中断优先级分组函数,用于设置中断优先级分组。

void NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup)
{
/* Check the parameters */
assert_param(IS_NVIC_PRIORITY_GROUP(NVIC_PriorityGroup));

/* Set the PRIGROUP[10:8] bits according to NVIC_PriorityGroup value */
SCB->AIRCR = AIRCR_VECTKEY_MASK | NVIC_PriorityGroup;
}

使能一个串口1的中断,同时设置抢占式优先级为1,响应式优先级为 2:

void NVIC_Example_Config(void){
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = EXTI1_IRQn; // 给个串口1的中断
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriorty = 1; // 抢占优先级1
NVIC_InitStructure.NVIC_IRQChannelSubPriorty = 2; // 响应优先级2
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}

4.2 EXTI配置函数

首先我们看下EXTI相关的结构体:

EXTI_TypeDef: EXTI外设寄存器结构体,包含了中断屏蔽寄存器、事件屏蔽寄存器、上升沿触发选择寄存器、下降沿触发选择寄存器、软件中断事件寄存器和挂起寄存器等。

typedef struct
{
__IO uint32_t IMR; // Interrupt Mask Register(中断屏蔽寄存器)
__IO uint32_t EMR; // Event Mask Register(事件屏蔽寄存器)
__IO uint32_t RTSR; // Rising Trigger Selection Register(上升沿触发选择寄存器)
__IO uint32_t FTSR; // Falling Trigger Selection Register(下降沿触发选择寄存器)
__IO uint32_t SWIER; // Software Interrupt Event Register(软件中断事件寄存器)
__IO uint32_t PR; // Pending Register(挂起寄存器)
} EXTI_TypeDef;
tip

“IMR管中断,EMR管事件;RTSR上升,FTSR下降;SWIER软件触发,PR记得清!”

EXTI_InitTypeDef: EXTI初始化结构体,包含了中断线、工作模式、触发方式和使能状态等信息。

typedef struct
{
uint32_t EXTI_Line; // 选择要配置的EXTI线(如EXTI_Line0~EXTI_Line15)
EXTIMode_TypeDef EXTI_Mode; // 工作模式:中断(Interrupt)或事件(Event)
EXTITrigger_TypeDef EXTI_Trigger; // 触发方式:上升沿、下降沿或双边沿
FunctionalState EXTI_LineCmd; // 使能(ENABLE)或禁用(DISABLE)该EXTI线
} EXTI_InitTypeDef;

电路有两个按键分别为KEY1、KEY2,两个LED分别为D1、D2。使用中断方式实现以下两个功能:

  1. KEY1按键按下,D1点亮;

  2. KEY2按键按下,D2点亮。

这里的中断初始化函数:

void NVIC_Example_Init(void){
EXTI_InitTypeDef EXTI_InitStructure;

KeyInit();

RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); // 使能GPIO的复用时钟功能,因为配置外部中断的寄存器(AFIO_EXTICRx)之前要先打开AFIO时钟。

GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource0);
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource1);// 将PB组对应的IO口映射到外部中断的EXTI0和EXTI1上。

EXTI_InitStructure.EXTI_Line = EXTI_Line_0 | EXTI_Line_1; // 选择EXTI0和EXTI1
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; // 选择中断模式
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling; // 选择下降沿触发
EXTI_InitStructure.EXTI_LineCmd = ENABLE; // 使能EXTI0和EXTI1
EXTI_Init(&EXTI_InitStructure); // 初始化EXTI0和EXTI1

// 别忘了!在NVIC大管家这里配置中断优先级
NVIC_EnableIRQ(EXTI0_IRQn);
NVIC_EnableIRQ(EXTI1_IRQn);
NVIC_SetPriority(EXTI0_IRQn, 5);
NVIC_SetPriority(EXTI1_IRQn, 6);
}