EXTI
一、各类概念简述
- 中断:在主程序运行过程中,出现了特定的中断触发条件(中断源),使得CPU暂停当前正在运行的程序,转而去处理中断程序,处理完成后又返回原来被暂停的位置继续运行。
- 中断优先级:当有多个中断源同时申请中断时,CPU会根据中断源的轻重缓急进行裁决,优先响应更加紧急的中断源。
- 抢占优先级:抢占优先级高的中断允许中断嵌套(即可以打断当前正在运行的中断)。
- 响应优先级:响应优先级高的优先排队,但不允许中断嵌套(即在当前正在运行的中断运行结束后优先执行)。
- 抢占优先级和响应优先级均相同的中断,按中断号排队,没有先来后到的说法。
- 中断嵌套:当一个中断程序正在运行时,又有新的更高优先级的中断源申请中断,CPU再次暂停当前中断程序,转而去处理新的中断程序,处理完成后依次进行返回(即把中断程序再次中断)。
- 中断系统:管理和执行中断的逻辑结构。
- STM32的中断:
- 最多有68个可屏蔽中断通道,包含EXTI、TIM、ADC、USART、SPI、I2C、RTC等多个外设。其中,EXTI(Extern Interrupt)外部中断,是STM32中能触发中断的众多外设之一。
- 使用NVIC统一管理中断,每个中断通道都拥有16个可编程的优先等级,可对优先级进行分组,可以设置为抢占优先级或响应优先级。
二、NVIC
- 嵌套中断向量控制器,用来统一分配中断优先级和管理中断。
- NVIC的中断优先级由优先级寄存器的4位(0~15)决定,这4位可以进行切分,分为高n位的抢占优先级和低4-n位的响应优先级,有以下五种切分方式:
三、EXTI 原理
3.1 EXTI简述
-
EXTI(Extern Interrupt)外部中断。
-
EXTI的中断源是GPIO口的电平变化。
-
中断发起流程:EXTI监测指定GPIO口的电平信号 → \rightarrow →指定的GPIO口产生电平变化 → \rightarrow →EXTI向NVIC发出中断申请 → \rightarrow →NVIC经过裁决 → \rightarrow →中断CPU主程序,使CPU执行EXTI对应的中断程序。
-
支持的触发方式:上升沿/下降沿/双边沿/软件触发(即没有电平变化,用程序代码触发)。
-
触发响应方式:中断响应/事件响应
- 中断响应:申请中断,使CPU停止主程序,执行相应中断程序。
- 事件响应:EXTI的额外功能,中断源触发时,申请事件,触发其他外设进行操作,而不会通向CPU执行中断。
-
通道数:共20个中断通道,分别是:16个GPIO_Pin,外加PVD输出、RTC闹钟、USB唤醒、以太网唤醒(因为EXTI能从低功耗模式的停止模式下唤醒STM32)。
-
支持的GPIO口:所有GPIO口,但相同的Pin不能同时触发中断,因为EXTI的16个GPIO_PIN通道分别对应了16个编号的PIN脚。
eg:GPIOA_PIN0不能和GPIOB_PIN0同时触发中断,但可以和GPIOA_PIN1同时触发中断。
3.2 EXTI基本结构
- 因为EXTI只有16个GPIO_PIN的通道,所以需要先经过AFIO进行中断引脚选择。
- AFIO就是16个7进1出的数据选择器,对PA0、PB0~PG0经过数据选择,取一个连到EXTI0,1-15同理。
- 注意:EXTI9-5和15-10被分配到了同一个通道中,这意味着他们触发的是同一个中断函数,需要在该函数中用代码区分各个中断。
- 图右下角即为EXTI的事件响应,它们作用于其他外设,而不经过NVIC和CPU。
3.3 EXTI内部框图
四、配置EXTI(库函数)
4.1 初始化EXTI
'1. 配置RCC,使能需要用到的外设时钟'
// 注意:开启外设时钟的函数是与外设所在的总线对应的,APB1/APB2/AHB
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); // 使能GPIOA口的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); // 使能AFIO的时钟
//EXTI比较特殊,不需要开启时钟;而NVIC作为内核的外设,也不需要开启时钟(RCC只管内核外的外设)
'2. 配置GPIO'
// 见GPIO章,需设置为输入模式
'3. 配置AFIO'
GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource14); // 选择GPIOA_Pin14作为中断引脚
'4. 配置EXTI'
EXTI_InitTypeDef EXTI_InitStructure; // 结构体初始化,然后填入结构体
// 填入结构体
EXTI_InitStructure.EXTI_Line = EXTI_Line14; // 选择需要配置的中断线
EXTI_InitStructure.EXTI_LineCmd = ENABLE; // 选择中断线使能/失能
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; // 选择外部中断线的模式(中断模式/事件模式)
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling; // 选择触发信号的有效边沿(上升沿触发/下降沿触发/双边触发)
EXTI_Init(&EXTI_InitStructure); // 初始化EXTI
'5. 配置NVIC'
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); // 配置中断优先级的分组方式(整个工程只能用一种分组方式,即整个工程只需要配置一次分组方式)
NVIC_InitTypeDef NVIC_InitStructure; // 结构体初始化,然后填入结构体
// 填入结构体
NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn; // 选择中断通道
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; // 选择中断通道使能/失能
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; // 指定所选通道的抢占优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; // 指定所选通道的响应优先级
NVIC_Init(&NVIC_InitStructure); // 初始化NVIC
4.2 编写中断函数
在启动文件中找到中断向量表,其中以IRQHandler结尾的字符串就是中断函数的名字
void EXTI15_10_IRQHandler(void)
{
if (EXTI_GetITStatus(EXTI_Line14) == SET): // 判断指定通道的中断标志位是否为1
{
xxx功能块
EXTI_ClearITPendingBit(EXTI_Line14); // 清除中断标志位
}
}
4.3 AFIO常用库函数
void GPIO_AFIODeInit(void);
// AFIO复位函数
void GPIO_PinRemapConfig(uint32_t GPIO_Remap, FunctionalState NewState);
// 设置引脚重映射;参数一:重映射方式;参数二:新的状态;
void GPIO_EXTILineConfig(uint8_t GPIO_PortSource, uint8_t GPIO_PinSource);
// 配置AFIO的数据选择器,选择中断引脚;参数一:GPIO口;参数二:PIN脚数字;
4.4 EXTI常用库函数
void EXTI_DeInit(void);
// EXTI复位函数
void EXTI_Init(EXTI_InitTypeDef* EXTI_InitStruct);
// EXTI初始化函数
void EXTI_StructInit(EXTI_InitTypeDef* EXTI_InitStruct);
// 初始化EXTI结构体,用默认值填充每个EXTI_InitStruct成员
void EXTI_GenerateSWInterrupt(uint_32_t EXTI_Line);
// 用于软件触发外部中断
FlagStatus EXTI_GetFlagStatus(uint32_t EXTI_Line);
// 获取EXTI标志位(和下面的清除函数用于在主函数中操作标志位)
void EXTI_ClearFlag(uint32_t EXTI_Line);
// 清除EXTI标志位
ITStatus EXTI_GetITStatus(uint32_t EXTI_Line);
// 获取中断标志位(和下面的清除函数用于在中断函数中操作标志位)
void EXTI_ClearITPendingBit(uint32_t EXTI_Line);
// 清除中断标志位
4.5 NVIC常用库函数
void NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup);
// 选择中断分组
void NVIC_Init(NVIC_InitTypeDef* NVIC_InitStruct);
// 初始化NVIC
void NVIC_SetVectorTable(uint32_t NVIC_VectTab, uint32_t Offset);
// 设置中断向量表
void NVIC_SystemLPConfig(uint8_t LowPowerMode, FunctionalState NewState);
// 系统低功耗配置
void SysTick_CLKSourceConfig(uint32_t SysTick_CLKSource);
五、补充
- 中断程序所在的子函数不需要人为调用,中断条件触发时由硬件自动调用。
- 中断向量表:表中记录了STM32的所有中断(内核中断、外设中断)以及各个中断对应的地址。
- 中断地址的作用是:每当中断条件触发,硬件都会自动跳转到固定地址,但由于编写的中断函数的地址不固定,因此需要在固定地址处再做一个跳转(编译器自动执行,不需要管)。
Reference
STM32入门教程-2023版(江科大)