STM32中断
- STM32中断包含EXTI外部中断,TIM定时中断,ADC数模中断,USART串口中断,SPI通讯中断,IIC通讯中断,RTC实时时钟等多个外设中断。
- 引发中断的事件称为中断源。有些中断还能够被其它高优先级的中断所中断,那么这种情况又叫做中断的嵌套。
中断处理
中断函数名字在启动文件 startup_stm32f40_41xxx.s 中可以找到。
NVIC
NVIC的全称是Nested vectored interrupt controller,即嵌套向量中断控制器。
高抢占式优先级的中断可以在具有低抢占式优先级的中断服务程序执行过程中被响应,即中断嵌套。
使用NVIC1统一管理中断。
407的NVIC
CM4 内核支持 256 个中断,其中包含了 16 个内核中断和 240 个外部中断,并且具有256 级的可编程中断设置。但 STM32F4 并没有使用 CM4 内核的全部东西,而是只用了它的一部分。
- STM32F40xx/STM32F41xx 总共有 92 个中断
- 包括 10 个内核中断和 82 个可屏蔽中断,我们常用的就是这 82 个可屏蔽中断
- 16 个可编程优先级(使用了 4 位中断优先级)
STM32F407 的中断优先级分组
STM32 将中断分为 5 个组,组 0~4。该分组的设置是由内核外设 SCB 的应用程序中断及复位控制寄存器 AIRCR 的 PRIGROUP[10:8]bit10~8 位决定
例如组设置为 3,那么此时所有中断,每个中断的中断优先寄存器的高四位中的最高 3 位是抢占优先级,低 1 位是响应优先级。
- 每个中断,你可以设置抢占优先级为 0~7,响应优先级为 1 或 0。
- 抢占优先级的级别高于响应优先级。
- 数值越小所代表的优先级就越高。
优先级原则
- 第一,如果两个中断的抢占优先级和响应优先级都是一样的话,则看哪个中断先发生就先执行;
- 第二,高优先级的抢占优先级是可以打断正在进行的低抢占优先级中断的。而抢占优先级相同的中断,高优先级的响应优先级不可以打断低响应优先级的中断。
NVIC_PriorityGroupConfig
中断优先级分组函数 ,在misc.c中可以查看,其函数申明如下:
void NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup);
这个函数的作用是对中断的优先级进行分组,这个函数在系统中只能被调用一次,一旦分组确定就最好不要更改。
NVIC_PriorityGroup
#define NVIC_PriorityGroup_0 ((uint32_t)0x700) /*!< 0 bits for pre-emption priority
4 bits for subpriority */
#define NVIC_PriorityGroup_1 ((uint32_t)0x600) /*!< 1 bits for pre-emption priority
3 bits for subpriority */
#define NVIC_PriorityGroup_2 ((uint32_t)0x500) /*!< 2 bits for pre-emption priority
2 bits for subpriority */
#define NVIC_PriorityGroup_3 ((uint32_t)0x400) /*!< 3 bits for pre-emption priority
1 bits for subpriority */
#define NVIC_PriorityGroup_4 ((uint32_t)0x300) /*!< 4 bits for pre-emption priority
0 bits for subpriority */
#define IS_NVIC_PRIORITY_GROUP(GROUP) (((GROUP) == NVIC_PriorityGroup_0) || \
((GROUP) == NVIC_PriorityGroup_1) || \
((GROUP) == NVIC_PriorityGroup_2) || \
((GROUP) == NVIC_PriorityGroup_3) || \
((GROUP) == NVIC_PriorityGroup_4))
比如我们设置整个系统的中断优先级分组值为 2,那么方法是:
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
这样就确定了一共为“2 位抢占优先级,2 位响应优先级”。
NVIC_Init
NVIC为内核外设,程序中不需要开启专门的NVIC时钟。
下面我们讲解一个重要的函数为中断初始化函数 NVIC_Init,其函数申明为:
void NVIC_Init(NVIC_InitTypeDef* NVIC_InitStruct)
其中 NVIC_InitTypeDef 是一个结构体,我们可以看看结构体的成员变量:
typedef struct
{
uint8_t NVIC_IRQChannel;
uint8_t NVIC_IRQChannelPreemptionPriority;
uint8_t NVIC_IRQChannelSubPriority;
FunctionalState NVIC_IRQChannelCmd;
} NVIC_InitTypeDef;
NVIC_InitTypeDef 结构体中间有三个成员变量,这三个成员变量的作用是:
NVIC_IRQChannel
定义初始化的是哪个中断,这个我们可以在 stm32f10x.h 中找到每个中断对应的名字。例如 USART1_IRQn。
NVIC_IRQChannelPreemptionPriority
定义这个中断的抢占优先级别。
NVIC_IRQChannelSubPriority
定义这个中断的子优先级别。
NVIC_IRQChannelCmd
该中断是否使能。
我们要使能串口 1 的中断,同时设置抢占优先级为 1,子优先级位 2,初始化的方法是:
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;//串口 1 中断
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=1 ;// 抢占优先级为 1
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;// 子优先级位 2
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ 通道使能
NVIC_Init(&NVIC_InitStructure); //根据上面指定的参数初始化 NVIC 寄存器
书接上文,配置完中断优先级之后,接着就是编写中断服务函数。
NVIC_SetPriority
单独设置优先级。
__STATIC_INLINE void NVIC_SetPriority(IRQn_Type IRQn, uint32_t priority)
{
if(IRQn < 0) {
SCB->SHP[((uint32_t)(IRQn) & 0xF)-4] = ((priority << (8 - __NVIC_PRIO_BITS)) & 0xff); } /* set Priority for Cortex-M System Interrupts */
else {
NVIC->IP[(uint32_t)(IRQn)] = ((priority << (8 - __NVIC_PRIO_BITS)) & 0xff); } /* set Priority for device specific Interrupts */
}
中断处理函数
407的可以在F4中文参考手册指南10.2的234页浏览。
NVIC相关寄存器
NVIC中断寄存器总览
STM32 的中断在这些寄存器的控制下有序的执行的。只有了解这些中断寄存器,才能方便的使用 STM32 的中断。
typedef struct
{
__IO uint32_t ISER[8]; /*!< Interrupt Set Enable Register */
uint32_t RESERVED0[24];使能
__IO uint32_t ICER[8]; /*!< Interrupt Clear Enable Register */
uint32_t RSERVED1[24];除能
__IO uint32_t ISPR[8]; /*!< Interrupt Set Pending Register */
uint32_t RESERVED2[24];悬起
__IO uint32_t ICPR[8]; /*!< Interrupt Clear Pending Register */
uint32_t RESERVED3[24];清悬
__IO uint32_t IABR[8]; /*!< Interrupt Active bit Register */
uint32_t RESERVED4[56];有效位寄存器
__IO uint8_t IP[240]; /*!< Interrupt Priority Register, 8Bit wide */
uint32_t RESERVED5[644];优先级寄存器
__O uint32_t STIR; /*!< Software Trigger Interrupt Register */
} NVIC_Type;软件触发中断寄存器
中断优先级寄存器组IP
NVIC 有一个专门的寄存器,中断优先级寄存器 NVIC_IPRx,全称是:Interrupt Priority Registers,用来配置外部中断的优先级,IPR 宽度为 8bit,原则上每个外部中断可配置的优先级为 0~255,数值越小,优先级越高。
这个寄存器组相当重要!
STM32 的中断分组与这个寄存器组密切相关。
IP 寄存器组由 240 个 8bit 的寄存器组成,每个可屏蔽中断占用8bit,这样总共可以表示 240 个可屏蔽中断。
而 STM32F4只用到了其中的 82 个。IP[81]~IP[0]分别对应中断 81~0。
每个可屏蔽中断占用的 8bit 并没有全部使用,而是 只用了高 4 位。这 4 位,又分为抢占优先级和子优先级。
抢占优先级在前,子优先级在后。而这两个优先级各占几个位又要根据 SCB->AIRCR 中的中断分组设置来决定。
中断使能寄存器组ISER
ISER[8] 全称是:Interrupt Set-Enable Registers
CM3 内核支持 256 个中断,这里用 8 个 32 位寄存器来控制,每个位控制一个中断。
CM3 中可以有 240 对使能位/除能位,每个中断拥有一对。
STM32F103 的可屏蔽中断只有 60 个,所以对我们来说,有用的只有两个(ISER[0]和 ISER[1])
STM32F103 只用了其中的前 60 位。ISER[0]的 bit0~bit31 分别对应中断 0~31。ISER[1]的 bit0~27 对应中断 32~59;这样总共 60 个中断就分别对应上了。
欲使能一个中断,你需要写 1 到对应 SETENA 的位中;欲除能一个中断,你需要写 1 到对应的 CLRENA 位中;
这里仅仅是使能,还要配合中断分组、屏蔽、IO 口映射等设置才算是一个完整的中断设置
中断除能寄存器组ICER
ICER[8]:全称是:Interrupt Clear-Enable Registers。该寄存器组与 ISER 的作用恰好相反,是用来清除某个中断的使能的。其对应位的功能,也和 ICER 一样。
这里要专门设置一个 ICER 来清除中断位,而不是向 ISER 写 0 来清除,是因为 NVIC 的这些寄存器都是写 1 有效的,写 0 是无效的。
中断挂起控制寄存器组
ISPR[8]:全称是:Interrupt Set-Pending Registers,
每个位对应的中断和 ISER 是一样的。通过置 1,可以将正在进行的中断挂起,而执行同级或更高级别的中断。写 0 是无效的。
中断解挂控制寄存器组
ICPR[8]:全称是:Interrupt Clear-Pending Registers
其作用与 ISPR 相反,对应位也和 ISER 是一样的。通过设置 1,可以将挂起的中断接挂。写 0 无效。
中断激活标志位寄存器组
IABR[8]:全称是:Interrupt Active Bit Registers。对应位所代表的中断和 ISER 一样,如果为 1,则表示该位所对应的中断正在被执行。这是一个只读寄存器,通过它可以知道当前在执行的中断是哪一个。在中断执行完了由硬件自动清零。
EXTI控制器
(External interrupt/event controller)—外部中断/事件控制器
- 每个中断/事件线都对应有一个边沿检测器,可以实现输入信号的上升沿检测和下降沿的检测。
- STM32 的每个 IO 都可以作为外部中断的中断输入口。
- EXTI的每根输入线都可单独进行配置,以选择类型(中断或事件)和相应的触发事件(上升沿触发、下降沿触发或边沿触发),还可独立地被屏蔽。
F103外部中断
STM32F103 的中断控制器支持 19 个外部中断/事件请求。每个中断设有状态位,每个中断/事件都有独立的触发和屏蔽设置。
19 个外部中断为:
线 0~15:对应外部 IO 口的输入中断。
线 16:连接到 PVD 输出。
线 17:连接到 RTC 闹钟事件。
线 18:连接到 USB 唤醒事件。
软件中断事件寄存器是小开关
中断屏蔽寄存器是一个中开关
后面NVIC还有一个大开关
事件:ADC采集,定时器计时等
- 1-2-3-4-5,用来产生中断;另一条是:1-2-3-6-7-8,用来产生事件
F407外部中断
STM32F407 的中断控制器支持 22个外部中断/事件请求。每个中断设有状态位,每个中断/事件都有独立的触发和屏蔽设置。
STM32F407 的 22 个外部中断为:
EXTI 线 0~15:对应外部 IO 口的输入中断。
EXTI 线 16:连接到 PVD 输出。
EXTI 线 17:连接到 RTC 闹钟事件。
EXTI 线 18:连接到 USB OTG FS 唤醒事件。
EXTI 线 19:连接到以太网唤醒事件。
EXTI 线 20:连接到 USB OTG HS(在 FS 中配置)唤醒事件。
EXTI 线 21:连接到 RTC 入侵和时间戳事件。
EXTI 线 22:连接到 RTC 唤醒事件。
中断与事件的关系
中断响应:申请中断,让CPU执行中断函数
事件响应:外部中断的信号不通向CPU,转而通向其他外设,触发其他外设的操作,如:ADC转换,DMA等。
STM32的IO中断与IO口的对应
- F103与407的对应关系是一致的。
- STM32 供 IO 口使用的中断线只有 16 个,但是 STM32 的 IO 口却远远不止 16 个(112)。
- GPIO 的管脚 GPIOx.0~GPIOx.15(x=A,B,C,D,E,F,G)分别对应中断线 0~15。这样每个中断线对应了最多 7 个 IO 口
- 以线 0 为例:它对应了 GPIOA.0、GPIOB.0、GPIOC.0、GPIOD.0、GPIOE.0、GPIOF.0、GPIOG.0。
- **中断线每次只能连接到 1 个 IO 口上,**这样就需要通过配置来决定对应的中断线配置到哪个 GPIO 上了。
STM32的IO中断处理函数(综合)
- F103与F407的外部IO中断与对应的中断处理函数是一样的。STM32 的 IO 口外部中断函数只有 7 个,分别为:
EXPORT EXTI0_IRQHandler
EXPORT EXTI1_IRQHandler
EXPORT EXTI2_IRQHandler
EXPORT EXTI3_IRQHandler
EXPORT EXTI4_IRQHandler
EXPORT EXTI9_5_IRQHandler
EXPORT EXTI15_10_IRQHandler
中断线 0-4 每个中断线对应一个中断函数
中断线 5-9 共用中断函数 EXTI9_5_IRQHandler
中断线 10-15 共用中断函数 EXTI15_10_IRQHandler
EXTI_GetITStatus中断状态
第一个函数是判断某个中断线上的中断是否发生(标志位是否置位):
ITStatus EXTI_GetITStatus(uint32_t EXTI_Line);
这个函数一般使用在中断服务函数的开头判断中断是否发生。
EXTI_ClearITPendingBit清除中断标志
另一个函数是清除某个中断线上的中断标志位:
void EXTI_ClearITPendingBit(uint32_t EXTI_Line);
这个函数一般应用在中断服务函数结束之前,清除中断标志位。
STM32F103的AFIO寄存器
所谓AFIO,即alternate function IO 复用IO端口
对寄存器 AFIO_EVCR , AFIO_MAPR 和 AFIO_EXTICRX 进行读写操作前,应当首先打开 AFIO的时钟。
使用场景
- 当使用外部中断时,需要打开AFIO时钟。
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
然后使用GPIO_EXTILineConfig函数进行挂载。例如:
GPIO_EXTILineConfig(GPIO_PortSourceGPIOC,GPIO_PinSource4);
- 当使用重映射功能时也需要打开AFIO时钟。
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
然后进行重映射
GPIO_PinRemapConfig(GPIO_Remap_USART1,ENABLE);
事件控制寄存器(AFIO_EVCR)
复用重映射和调试I/O 配置寄存器(AFIO_MAPR)
外部中断配置寄存器 1(AFIO_EXTICR1)
该寄存器共有四个,分别对应广大GPIO引脚中断配置。
EXIT寄存器
事件屏蔽寄存器(EXTI_EMR)
中断屏蔽寄存器(EXTI_IMR)
上升沿触发选择寄存器(EXTI_RTSR)
软件中断事件寄存器EXTI_SWIER
SWIER :19位有效,配置线x上的软件中断。配置1的时候将EXTI_PR中的相应中断挂起。
挂起寄存器EXTI_PR
挂起代表中断来了,捕捉到了。
F103中断的配置和触发
GPIO_EXTILineConfig
在库函数中,配置 GPIO 与中断线的映射关系的函数 GPIO_EXTILineConfig()来实现的:
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);//先打开时钟
void GPIO_EXTILineConfig(uint8_t GPIO_PortSource, uint8_t GPIO_PinSource)
该函数将 GPIO 端口与中断线映射起来,使用范例是:
GPIO_EXTILineConfig(GPIO_PortSourceGPIOE,GPIO_PinSource2);
将中断线 2 与 GPIOE 映射起来,很显然是 GPIOE.2 与 EXTI2 中断线连接了。
EXTI_Init
初始化线上中断,设置触发条件。中断线上中断的初始化是通过函数 EXTI_Init()实现的。EXTI_Init()函数的定义是:
void EXTI_Init(EXTI_InitTypeDef* EXTI_InitStruct);
使用方法
EXTI_InitTypeDef EXTI_InitStructure;
EXTI_InitStructure.EXTI_Line=EXTI_Line4;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStructure); //根据 EXTI_InitStruct 中指定的
//参数初始化外设 EXTI 寄存器
EXTI_Line
中断线的标号
F103取值范围为EXTI_Line0~EXTI_Line15。
F407取值范围为EXTI_Line0~EXTI_Line23。
EXTI_Mode
中断模式,可选值为中断 EXTI_Mode_Interrupt 和事件 EXTI_Mode_Event。
EXTI_Trigger
触发方式,可以是下降沿触发 EXTI_Trigger_Falling,上升沿触发 EXTI_Trigger_Rising,或者任意电平(上升沿和下降沿)触发EXTI_Trigger_Rising_Falling,
NVIC_Init,并使能中断
设置好中断线和 GPIO 映射关系,然后又设置好了中断的触发模式等初始化参数。既然是外部中断,涉及到中断我们当然还要设置 NVIC 中断优先级。
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = EXTI2_IRQn; //使能按键外部中断通道
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x02; //抢占优先级 2,
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x02; //子优先级 2
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能外部中断通道
NVIC_Init(&NVIC_InitStructure); //中断优先级分组初始化
这里先打断一下思路,可以看一下后文NVIC的介绍。
编写中断服务函数
常用的中断服务函数格式为:
void EXTI3_IRQHandler(void)
{
if(EXTI_GetITStatus(EXTI_Line3)!=RESET)//判断某个线上的中断是否发生
{
中断逻辑…
EXTI_ClearITPendingBit(EXTI_Line3); //清除 LINE 上的中断标志位
}
}
EXTI_GetITStatus
第一个函数是判断某个中断线上的中断是否发生(标志位是否置位):
ITStatus EXTI_GetITStatus(uint32_t EXTI_Line);
这个函数一般使用在中断服务函数的开头判断中断是否发生。
另一个函数是清除某个中断线上的中断标志位:
EXTI_ClearITPendingBit
void EXTI_ClearITPendingBit(uint32_t EXTI_Line);
这个函数一般应用在中断服务函数结束之前,清除中断标志位。
固件库还提供了两个函数用来判断外部中断状态以及清除外部状态
标志位的函数 EXTI_GetFlagStatus 和 EXTI_ClearFlag,他们的作用和前面两个函数的作用类似。
STM32F4 所有中断服务函数的名字,都已经在startup_stm32f40_41xx.s 里面定义好了,如果有不知道的,去这个文件里面找就可以了
F407外部中断配置
需要注意的一点是IO开启中断之后,不影响正常使用,也可读取状态。
开启 SYSCFG 时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG, ENABLE);//使能 SYSCFG 时钟
SYSCFG_EXTILineConfig
设置 IO 口与中断线的映射关系
SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOA, EXTI_PinSource0);
GPIOA.0 与 EXTI1 中断线连接了。
EXTI_Init
设置中断结构体,初始化线上中断
EXTI_InitTypeDef EXTI_InitStructure;
EXTI_InitStructure.EXTI_Line=EXTI_Line4;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStructure); //初始化外设 EXTI 寄存器
与F103的中断的结构体一致
NVIC_Init,并使能中断
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = EXTI2_IRQn; //使能按键外部中断通道
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x02; //抢占优先级 2,
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x02; //响应优先级 2
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能外部中断通道
NVIC_Init(&NVIC_InitStructure); //中断优先级分组初始化
103的参数凑合看一下,
优先级
编写中断服务函数
参考前文的 : STM32的IO中断处理函数
编写中断服务函数中的常用函数与103是一样的。
EXTI_GetITStatus
第一个函数是判断某个中断线上的中断是否发生(标志位是否置位):
ITStatus EXTI_GetITStatus(uint32_t EXTI_Line);
这个函数一般使用在中断服务函数的开头判断中断是否发生。
EXTI_ClearITPendingBit
另一个函数是清除某个中断线上的中断标志位:
void EXTI_ClearITPendingBit(uint32_t EXTI_Line);
这个函数一般应用在中断服务函数结束之前,清除中断标志位。
代码
按键硬件连接图
KEY0->PE4 上拉输入
KEY1->PE3 上拉输入
KEY2->PE2 上拉输入
WK_UP->PA0 下拉输入
中断处理函数
//外部中断4服务程序
void EXTI4_IRQHandler(void)
{
delay_ms(10); //消抖
if(KEY0==0)
{
LED0=!LED0;
LED1=!LED1;
}
EXTI_ClearITPendingBit(EXTI_Line4);//清除LINE4上的中断标志位
}
主函数代码
int main(void)
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置系统中断优先级分组2
delay_init(168); //初始化延时函数
uart_init(115200); //串口初始化
LED_Init(); //初始化LED端口
BEEP_Init(); //初始化蜂鸣器端口
EXTIX_Init(); //初始化外部中断输入
LED0=0; //先点亮红灯
while(1)
{
printf("OK\r\n"); //打印OK提示程序运行
delay_ms(1000); //每隔1s打印一次
}
}
void EXTIX_Init(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
EXTI_InitTypeDef EXTI_InitStructure;
//KEY_Init(); //按键对应的IO口初始化
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG, ENABLE);//使能SYSCFG时钟
SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOE, EXTI_PinSource4);//PE4 连接到中断线4
/* 配置EXTI_Line2,3,4 */
EXTI_InitStructure.EXTI_Line = EXTI_Line2 | EXTI_Line3 | EXTI_Line4;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;//中断事件
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling; //下降沿触发
EXTI_InitStructure.EXTI_LineCmd = ENABLE;//中断线使能
EXTI_Init(&EXTI_InitStructure);//配置
NVIC_InitStructure.NVIC_IRQChannel = EXTI4_IRQn;//外部中断4
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x01;//抢占优先级1
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x02;//子优先级2
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;//使能外部中断通道
NVIC_Init(&NVIC_InitStructure);//配置
}