目录
1. 什么是中断
1.1 中断概念
1.2 中断优先级
1.3 中断嵌套
2.STM32中断
2.1 NVIC中断优先级
3 外部中断
3.1 EXTI简介
3.2 EXTI中断/事件线
3.3 EXTI功能框图
3.4 中断和事件的区别?
3.5 什么时候用外部中断?
3.怎么使用STM32中断
3.1 STM32外部中断编程要点
3.2 GPIO&AFIO
3.3 EXTI初始化
3.4 配置NVIC
3.5 编写中断服务函数
3.6 清除中断标志位
1. 什么是中断
1.1 中断概念
中断是计算机的一种机制,描述了这样一种场景:CPU暂停当前正在处理的程序,转而去处理紧急的事情,这种场景就叫中断。
中断其实很好理解,因为这样的场景经常发生在我们日常生活中。比如小李正在写作业,但是电话铃声响了,他转而去接电话,接完电话回来后继续写作业。在这个情境中,小李就完成了一次中断,下图根据现实生活的中断场景形象的表示了CPU中断过程,便于理解。
1.2 中断优先级
中断优先级即当有多个中断源请求中断时,CPU会根据中断优先级来判断首先执行那个中断。
1.3 中断嵌套
这个和中断优先级一起适配。当CPU正在相应中断,执行中断服务程序,此时又来了一个比当前中断优先级还高的中断,则此时CPU会停下当前执行的中断服务程序,转而去响应中断优先级更高的中断,等执行高优先级的中断后,回来继续执行未执行完的中断服务程序。中断可以有多个嵌套,有点套娃的意思。
2.STM32中断
STM32共有6个内核中断,68个可屏蔽中断。可见下图。
STM32中断使用嵌套向量中断控制器NVIC管理包括内核异常等中断,NVIC和处理器核的接口紧密相连,可以实现低延迟和高效的中断处理。
使用NVIC,主要使其ISER(使能中断)、ICER(失能中断)、IP(中断优先级)三个寄存器。
2.1 NVIC中断优先级
NVIC有一个寄存器来进行优先级设置:中断优先级寄存器NVIC_IPRx。NVIC的中断优先级分为抢占优先级和响应优先级。抢占优先级即可以进行中断嵌套的一种优先级,CPU会优先处理抢占优先级高的中断。而响应优先级只在抢占优先级相同的时候才会触发,相同抢占优先级下,优先执行相应优先级的高的中断。如果抢占优先级和响应优先级都相同,则比较中断的硬件中断编号(图2所示优先级),编号越小,优先级越高。
如何配置优先级?
NVIC的中断优先级由优先级寄存器的4位(0~15)决定,这4位可以进行切分,分为高n位的抢占优先级和低4-n位的响应优先级(n的取值由用户在程序中进行选择)。抢占优先级高的可以中断嵌套(低优先级中断在占用CPU时,抢占优先级高的中断可以抢占低优先级中断的CPU,让低优先级中断暂停执行),响应优先级高的可以优先排队,抢占优先级和响应优先级均相同的按中断号排队
总结下NVIC、中断和CPU之间的关系
3 外部中断
3.1 EXTI简介
EXTI(External interrupt/event controller)外部中断/事件控制器由20个产生事件/中断请求的边沿检测器组成。每个输入线可以独立的配置、触发和屏蔽。
3.2 EXTI中断/事件线
EXTI支持20个中断/事件线,其中前16根为GPIO输入线,另外4根用于特定的外设事件,可参考下图。
由上图可以看到,EXIT支持所有的GPIO口产生中断,但是相同的Pin不能同时触发中断(比如PA0和PB0不能同时使用,但PA0和PB1可以同时使用)。因为EXTI0线同时只能选择其中1个进行中断输出,可以看下图,PA0、PB0、PC0、PD0、PE0、PF0、PG0都输入到同一个数据选择器,EXTI0线输出的信号通过AFIO_EXTICR1寄存器的EXTI0[3:0]位来配置。
外部中断配置寄存器共有4个,即AFIO_EXTICRx[x=1,2,3,4],其中每个AFIO_EXTICRx中管理4个EXTI中断,因此共有16个GPIO对应的EXTI中断/事件线。外部中断配置寄存器可以看下图:
图4中外部中断配置寄存器和图3中EXTI0—EXTI15是对应上的,AFIO中有16个数据选择器,以0号数据选择器为例,它负责选择PA0、PB0……PG0中的其中一个引脚,在写程序时如果想选择PB0作为中断引脚,就需要对AFIO的0号数据选择器进行配置,让它选择GPIOB的0号引脚。另外注意图3中最后一句话,通过AFIO_EXTICRx配置GPIO线上的外部中断/事件,必须先使能AFIO的时钟。
3.3 EXTI功能框图
这个框图可以从右往左看,分为两路,其中红色表示产生中断,绿色表示产生事件。1、2、3号电路是产生中断和产生事件共用的,到3号电路输出信号分开,一路走至NCIV产生中断信号,一路走至脉冲发生器产生事件。框图中“/20”表示线路有20个,这和3.2章节有20根中断/事件线,并且每个线路都是可以独立配置、屏蔽、触发是相一致的。
我们首先来看红色部分产生中断信号的流程。其中1号电路输入线即为3.2章节介绍的EXTI中断/事件线,一般是传入电平变化的信号。之后往左将该信号传递到2号边沿检测电路。该电路连接了两个寄存器,分别是上升沿触发选择寄存器和下降沿触发选择寄存器,这两个寄存器用来判断2号边沿检测电路检测哪些类型的电平信号,即上升沿触发或者下降沿触发,或者上升沿下降沿都触发。
之后继续往左,来到3号电路,是一个或门,或门的意思是有1即1。或门的两个输入端分别是来自2号电路边沿检测电路的信号和软件中断/事件寄存器。由此可见,产生中断信号可以有两个方式,其一即20跟输入线产生电平跳变信号,其二即在软件中断事件寄存器进行配置,也可达到产生中断/事件的效果。
3号电路输出的信号就表示是否有中断/事件信号,从图中可以看出该输出也兵分两步:其一往上输入到请求挂起寄存器,其二输入到4号与门电路中。我们先看上面部分。
当在外部中断线上发生了选择的边沿跳变,则请求挂起寄存器的相应中断线位置会被置1、之后继续往左,走到7号电路,该电路是一个与门,共有两个输入端,其一为请求挂起寄存器的输出信号,其二为中断屏蔽寄存器。中断屏蔽寄存器[19:0]位为1则表示可以该中断线可以产生中断,否则将屏蔽该中断线上的请求。
之后往左,可以看到与门输出为NVIC中断控制器,可以知道经过一系列判断最终输入线产生的中断被送往NVIC中断控制器。至此,外部中断信号产生过程已介绍完毕。
接着看下面部分,事件产生过程,3号电路输出信号输入到4号电路中,该电路与7号电路作用一致,也是一个与门,通过事件屏蔽寄存器来决定是否能产生事件,相应位置1表示可以产生事件,置0则表示屏蔽该线路中断。
4号电路信号输出到5号电路脉冲发生器中,4号电路是一个有效信号,则此时5号电路就会产生一个脉冲;否则不会产生脉冲。脉冲信号就是产生事件的意思,因为这个脉冲信号可以给其他外设发送信息,即可以通过该脉冲信号来触发TIM、ADC等外设。
这个框图比较复杂,我们抽取一些有用的信息来记忆:
1.要想产生某个线路的中断/事件,必须将该线路的中断/事件屏蔽寄存器响应位置位。
2.20个中断/事件线是独立的,有各自独立的边沿触发寄存器、屏蔽寄存器。
3.中断/事件产生可以通过20个输入线来产生,也可以通过软件来产生。通过软件来产生需要置位软件中断/事件寄存器相应位为1。
4.20个输入触发中断/事件的方式有:上升沿、下降沿、上升沿+下降沿。
3.4 中断和事件的区别?
产生事件的目的是传送一个脉冲信号给其他外设使用。
产生中断是把中断信号传递给NVIC,NVIC会通过优先级配置最终传递给CPU,让CPU去执行中断服务函数,实现中断功能。这就是产生事件和产生中断的区别。
3.5 什么时候用外部中断?
对于由外部触发的一些场景,例如遥控器,调整声音的按钮等,这些不是时时刻刻发生,而是由人为(外部信号)发出,并且发生后需要立即捕获其信号的场景,可以使用外部中断来进行捕获。
3.怎么使用STM32中断
3.1 STM32外部中断编程要点
1. 明确中断源(一般是GPIO),开启GPIO时钟,初始化要产生中断的GPIO。此步骤需注意,复用AFIO,需开启AFIO时钟,并且选择AFIO中断引脚【GPIO_EXTILineConfig()】。
2.初始化EXTI,主要是EXTI_InitTypeDef结构体。
3.经前面介绍,中断过后会到NVIC控制器中,所以需要配置NVIC相应寄存器【NVIC中断分组、NVIC配置】。
4.编写中断服务函数。
5.清除中断标志位
3.2 GPIO&AFIO
GPIO时钟和配置此处就省略了,重点关注开启IO复用时钟。查看stm32f10x_rcc.h,RCC_APB2PeriphResetCmd()函数用来开启APB2总线上的时钟。
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE); //开启IO口复用时钟
之后,需要设置IO口与中断线的映射关系。查看stm32f10x_gpio.h,GPIO_EXTILineConfig()函数用来选择在EXTI中断线的GPIO引脚。
//例如,将外部中断的0号线映射到GPIOB,即选择PB0作为外部中断引脚
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource0);
3.3 EXTI初始化
我们来看下EXTI初始化结构体,配置EXTI初始化结构体可以配置中断/事件线、产生中断还是产生事件、边沿触发方式、使能EXTI线。
//EXTI初始化结构体
//配置EXTI初始化结构体可以配置中断/事件线、产生中断还是产生事件、边沿触发方式、使能EXTI线
typedef struct
{
uint32_t EXTI_Line; //中断/事件线
EXTIMode_TypeDef EXTI_Mode; //中断/事件模式,可选为产生中断(EXTI_Mode_Interrupt)或 者产生事件(EXTI_Mode_Event)
EXTITrigger_TypeDef EXTI_Trigger; //EXTI边沿触发方式。上升沿触发(EXTI_Trigger_Rising)、下降沿触发(EXTI_Trigger_Falling)、上升沿下降沿都触发(EXTI_Trigger_Rising_Falling)
FunctionalState EXTI_LineCmd; //是否使能EXTI线
}EXTI_InitTypeDef;
示例代码如下:
/*EXTI初始化*/
EXTI_InitTypeDef EXTI_InitStructure; //定义结构体变量
EXTI_InitStructure.EXTI_Line = EXTI_Line0 | EXTI_Line1; //选择配置外部中断的0号线和1号线
EXTI_InitStructure.EXTI_LineCmd = ENABLE; //指定外部中断线使能
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; //指定外部中断线为中断模式
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling; //指定外部中断线为下降沿触发
EXTI_Init(&EXTI_InitStructure); //将结构体变量交给EXTI_Init,配置EXTI外设
3.4 配置NVIC
/*NVIC中断分组*/
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //配置NVIC为分组2
//即抢占优先级范围:0~3,响应优先级范围:0~3
//此分组配置在整个工程中仅需调用一次
//若有多个中断,可以把此代码放在main函数内,while循环之前
//若调用多次配置分组的代码,则后执行的配置会覆盖先执行的配置
/*NVIC配置*/
NVIC_InitTypeDef NVIC_InitStructure; //定义结构体变量
NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn; //选择配置NVIC的EXTI0线
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //指定NVIC线路使能
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; //指定NVIC线路的抢占优先级为1
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; //指定NVIC线路的响应优先级为1
NVIC_Init(&NVIC_InitStructure); //将结构体变量交给NVIC_Init,配置NVIC外设
3.5 编写中断服务函数
void EXTI0_IRQHandler(void){}
3.6 清除中断标志位
EXTI_ClearITPendingBit(EXTI_Line0); //清除外部中断0号线的中断标志位
//中断标志位必须清除
//否则中断将连续不断地触发,导致主程序卡死
至此,STM32外部中断部分介绍完毕。