江协stm32学习:5-1~5-2 EXTI外部中断
一、EXTI(Extern Interrupt)简介
1、中断系统
-
中断:在主程序运行过程中,出现了特定的中断触发条件(中断源),使得CPU暂停当前正在运行的程序,转而去处理中断程序,处理完成后又返回原来被暂停的位置继续运行
-
中断优先级:当有多个中断源同时申请中断时,CPU会根据中断源的轻重缓急进行裁决,优先响应更加紧急的中断源
-
中断嵌套:当一个中断程序正在运行时,又有新的更高优先级的中断源申请中断,CPU再次暂停当前中断程序,转而去处理新的中断程序,处理完成后依次进行返回
2、stm32中断
-
68个可屏蔽中断通道,包含EXTI、TIM、ADC、USART、SPI、I2C、RTC等多个外设(几乎所有外设都可以申请中断)
-
中断号
中断类别 | 中断号范围(示例) | 描述 |
---|---|---|
内核中断 | 0-15 | Cortex-M内核产生的中断 |
外部中断(EXTI) | 16-31 | 外部中断,通常与GPIO引脚相关联 |
GPIO中断 | 32-47 | GPIO端口中断 |
USART中断 | 48-53 | USART1-3中断 |
SPI中断 | 54-59 | SPI1-3中断 |
I2C中断 | 60-63 | I2C1-3中断 |
ADC中断 | 64-67 | ADC1-3中断 |
定时器中断 | 68-83 | TIM1-8中断 |
RTC中断 | 84-85 | 实时时钟中断 |
USB中断 | 86-89 | USB中断 |
DMA中断 | 90-95 | DMA1-2中断 |
其他外设中断 | 96-111 | 其他外设中断,如SDIO、FSMC等 |
-
使用NVIC统一管理中断,每个中断通道都拥有16个可编程的优先等级,可对优先级进行分组,进一步设置抢占优先级和响应优先级。
-
中断向量列表:我们程序中的中断程序,它的地址是由编译器来分配的,是不固定的,但是中断跳转由于硬件的限制只能跳转到固定的地址执行程序,所以这里就需要在内存中定义一个地址的列表,这个列表使固定的,中断发生后就跳到这个固定位置,由编译器再加上一条跳转到中断函数的代码,进而跳转到任意位置,这个中断地址的列表,就叫中断向量列表。(由编译器进行,无需我们操作)
3、EXIT介绍
-
EXTI可以监测指定GPIO口的电平信号,当其产生电平变化时,EXTI将立即向NVIC发出中断申请,经过NVIC裁决后即可中断CPU主程序,使CPU执行EXTI对应的中断程序,而当中断程序处理完成后还可以返回主程序中断的位置继续执行主程序。(这个返回后能够继续进行主程序是因为之前保护了现场以及回来后可以成功还原现场,这个工作C语言帮我们做了)
-
支持的触发方式:上升沿(从低电平变到高电平的瞬间触发中断)/下降沿(从高电平变到低电平的瞬间触发中断)/双边沿(电平变化)/软件触发(程序里执行一句代码触发)
-
支持的GPIO口:所有GPIO口,但相同的Pin不能同时触发中断(GPIOA1和GPIOB1也不可以一起)(原因见EXTI结构)
-
通道数:16个GPIO_Pin,外加PVD输出、RTC闹钟、USB唤醒、以太网唤醒(这4个是来蹭网的,因为外部中断可以在低功耗的模式下唤醒stm32)
-
触发响应方式:中断响应/事件响应
-
中断的流程
AFIO是一个数据选择器,可以在前面3个GPIO外设的16个引脚里选择其中一个连接到后面EXTI的通道里。
※补充:AFIO复用IO口
主要功能:
-
引脚复用配置:允许用户选择特定的GPIO引脚来复用为外设功能,例如将GPIO引脚配置为USART的TX(发送)或RX(接收)引脚。
-
重映射:STM32提供了引脚重映射功能,允许用户改变某些外设的默认引脚分配。例如,可以将USART1的TX和RX引脚从默认的PA9和PA10重映射到PB6和PB7。
-
中断和事件管理:AFIO还涉及到外部中断(EXTI)的配置,允许用户选择哪个GPIO引脚可以触发外部中断。
-
调试接口重映射:在某些STM32系列中,AFIO还允许重映射调试接口(如SWD和JTAG)的引脚,以避免与应用中的其他功能冲突。
配置步骤:
-
使能AFIO时钟:在使用AFIO之前,需要使能其时钟。
-
配置GPIO引脚:将GPIO引脚配置为复用功能模式,并选择相应的复用功能。
-
重映射配置(如果需要):如果需要改变外设的默认引脚分配,需要在AFIO中进行相应的重映射配置。
-
配置外部中断(如果需要):如果需要使用GPIO引脚作为外部中断输入,需要在AFIO中进行配置。
- EXTI的结构
外部中断/事件控制器——EXIT功能框图解析 讲的很详细的一篇文,不过要关注博主才可以看
二、初始化配置EXTI
1、配置步骤
根据上文的EXTI中断流程,我们配置就需要配置从GPIO到NVIC的路径上的每个外设模块(RCC、GPIO、AFIO、EXTI、NVIC),打通这个信号电路
Step 1 配置RCC,打开时钟(EXTI和NVIC不需要)
Step 2 配置GPIO,选择端口为输入模式
Step 3 配置AFIO,选择我们用的这路的GPIO,连接到后面的EXTI
Step 4 配置EXTI,选择触发中断条件:边沿触发方式,上/下/双边沿;选择触发相应方式(如何处理中断):中断响应或者事件响应
Step 5 配置NVIC,给中断选择一个合适的优先级
2、配置AFIO
库函数和GPIO在一个文件里
void GPIO_AFIODeInit(void); //复位,AFIO外设的配置会全部清除
void GPIO_PinLockConfig(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin); //调用这个函数,参数指定某个引脚,他的配置就会锁定,防止更改(GPIO也可以使用)
void GPIO_EventOutputConfig(uint8_t GPIO_PortSource, uint8_t GPIO_PinSource);
void GPIO_EventOutputCmd(FunctionalState NewState); //这两个函数用来配置AFIO的事件输出
void GPIO_PinRemapConfig(uint32_t GPIO_Remap, FunctionalState NewState); //用来进行引脚重映射 //第一个参数选择重映射的方式,第二个参数是新的状态
void GPIO_EXTILineConfig(uint8_t GPIO_PortSource, uint8_t GPIO_PinSource); //调用这个函数就可以配置AFIO的数据选择器,来选择我们想要的中断引脚 //第一个参数是选择某个GPIO外设作为外部中断源,第二个参数是指定要配置的外部中断线
void GPIO_ETH_MediaInterfaceConfig(uint32_t GPIO_ETH_MediaInterface); //与以太网有关
3、配置EXTI
void EXTI_DeInit(void); //清楚配置,恢复成上电的默认状态
void EXTI_Init(EXTI_InitTypeDef* EXTI_InitStruct); //根据结构体里面的参数配置EXTI外设,初始化主要使用这个,用法和GPIO_Init一样
void EXTI_StructInit(EXTI_InitTypeDef* EXTI_InitStruct); //同上,但是是默认值
void EXTI_GenerateSWInterrupt(uint32_t EXTI_Line); //软件触发外部中断,参数是一个指定的中断线,调用后,可以软件触发一次外部中断
//外设运行中会产生一些标志位,当想要查看时可以使用这四个函数,分别是在主程序中获取指定标志位是否被置1、清除置1的标志位,以及在中断函数里的查看和清除
FlagStatus EXTI_GetFlagStatus(uint32_t EXTI_Line);
void EXTI_ClearFlag(uint32_t EXTI_Line);
ITStatus EXTI_GetITStatus(uint32_t EXTI_Line);
void EXTI_ClearITPendingBit(uint32_t EXTI_Line);
4、配置NVIC
//配置基本只使用前两个函数
void NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup); //用于中断分组的,参数是中断分组的方式。每个程序只分组一次,相当于必须使用同一种分组
NVIC_PriorityGroup
:这是一个枚举值,用于指定优先级分组的数量。STM32的不同系列可能支持不同数量的优先级分组。常见的分组数量有:
NVIC_PriorityGroup_0
:没有分组,所有中断具有相同的优先级。NVIC_PriorityGroup_1
:2个分组,抢占优先级和响应优先级各4个级NVIC_PriorityGroup_2
:3个分组,抢占优先级3个级别,响应优先级8个级别NVIC_PriorityGroup_3
:4个分组,抢占优先级2个级别,响应优先级16个级别NVIC_PriorityGroup_4
:不在所有STM32系列中支持,具体请参考对应的参考手册。
抢占优先级高于响应优先级:抢占优先级总是高于响应优先级。即,即使一个中断的响应优先级很高,如果它的抢占优先级较低,它也可能被抢占优先级更高的中断打断。
相同抢占优先级:当两个中断具有相同的抢占优先级时,它们的处理顺序由响应优先级决定。
void NVIC_Init(NVIC_InitTypeDef* NVIC_InitStruct); //根据结构体里面的参数配置NVIC
#include "stm32f10x.h"
void USART1_Config(void) {
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
// 使能GPIOA和USART1时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_USART1, ENABLE);
// 配置USART1 TX (PA.9) 和 RX (PA.10) 引脚
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9 | GPIO_Pin_10;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
// 配置USART1
USART_InitStructure.USART_BaudRate = 9600;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
USART_InitStructure.USART_StopBits = USART_StopBits_1;
USART_InitStructure.USART_Parity = USART_Parity_No;
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
USART_Init(USART1, &USART_InitStructure);
// 使能USART1接收中断
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
// 使能USART1
USART_Cmd(USART1, ENABLE);
// 配置NVIC
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
void NVIC_SetVectorTable(uint32_t NVIC_VectTab, uint32_t Offset); //设置中断向量表
void NVIC_SystemLPConfig(uint8_t LowPowerMode, FunctionalState NewState);//低功耗设置
三、中断函数
1、中断函数名称
在stm32中中断函数的名字都是固定的,每个中断通道都对应一个中断函数,中断函数名称可以在启动文件中找
每个中断服务函数通常遵循以下命名约定:void [外设名]_IRQHandler(void)
外设/功能 | 中断服务函数(ISR)名称 |
---|---|
GPIO(通用输入输出) | GPIOx_IRQHandler (x为端口号,如A、B、C等) |
USART(串行通信) | USARTx_IRQHandler (x为端口号,如1、2、3等) |
SPI(串行外设接口) | SPIx_IRQHandler (x为端口号,如1、2、3等) |
I2C(集成电路总线) | I2Cx_IRQHandler (x为端口号,如1、2、3等) |
定时器(Timer) | TIMx_IRQHandler (x为定时器号,如1、2、3等) |
ADC(模数转换器) | ADCx_IRQHandler (x为ADC号,如1、2、3等) |
DMA(直接内存访问) | DMAx_Streamy_IRQHandler (x为DMA号,y为流号) |
RTC(实时时钟) | RTC_IRQHandler 或 RTCAlarm_IRQHandler |
EXTI(外部中断) | EXTIx_IRQHandler (x为中断线号,如0、1、2等) |
SysTick(系统滴答定时器) | SysTick_Handler |
2、中断函数内容
1)先进行一个中断标志位的判断,确保是我们想要的中断源触发的这个函数
2)写中断函数要干嘛
3)最后中断程序结束后进行一个清除中断标志位的函数,避免中断程序被一直调用
※无需声明函数,因为中断函数不需要调用,是自动执行的
void USART1_IRQHandler(void) {
if (USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) {
//中断程序想干嘛
// 清除中断标志位
USART_ClearITPendingBit(USART1, USART_IT_RXNE);
}
}
3、编写建议
-
中断函数应该尽可能简短,以避免阻塞其他中断。
-
中断函数应该尽量避免在中断函数和主函数中调用相同的函数或者操作同一个硬件,因为现场保护只保证了CPU程序能正常返回,但是没有保护外部电路。在实现功能时,可以在中断里操作变量或者标志位,当中断返回时再利用变量进行其他操作。仅使用变量、标志位和函数作为接口,可以让程序更加清晰。
-
在中断函数中,避免使用浮点运算和复杂的数学运算,因为可能会影响中断的实时性。
-
在中断函数中,避免调用可能产生中断的函数,因为这可能导致嵌套中断和优先级反转问题。