什么是中断?
在STM32微控制器中,基于其ARM单核架构,程序的执行遵循单线程模式。为了提高处理效率,微控制器可以利用其丰富的外设资源来优化CPU的资源分配,从而增强系统的并发处理能力。
中断机制是首个我们学习的能够通过其特性来优化CPU资源分配,进而提升系统并发处理能力的外设。熟练掌握中断的使用对于今后编写高效且逻辑清晰的程序至关重要
下图是它在整个程序执行过程中的逻辑图:
在系统执行程序的时候,由于在STM32的单核ARM架构影响下,整个进行过程是单线程的。由于中断的加入,在单线程的执行过程中,程序可以通过是否触了进入中断的条件,如果成功触发条件就进入中断里面的执行中断事件,相反。(它会打断原进程)
这里就可以得出它的普遍意义和作用
意义:
1.实时控制:在确定的时间内对相应事件做出相应;例如:温度控制;
2.故障处理:检测到故障,需要第一时间进行处理;
3.数据传输:不确定数据何时会来,利用中断进行控制;
作用:
中断的作用:高效处理紧急程序,并且不会占用CPU资源。
举一个简单的例子:
小明在家写作业,听到了自己的手机响了就去接电话了,接完后继续写作业。
在原话中有三个关键词:写作业、手机响了、接电话。它们分别对应了:原程序、发生中断条件、进入中断发生的事件。
那么如果是电话和门铃同时响,小明他应该先干嘛嘞?下面就要引入“中断管家”的概念
一、中断的管家——NVIC中断优先级分组
1、NVIC的概念
NVIC(Nested vectored interrupt controller),嵌入向量中断控制器,它是ARM Cortex-M系列微控制器的中的一个关键组件,负责管理和控制中断。
对于它的降级需要引入几个概念:中断管理、向量表、优先级、中断屏蔽、活跃状态、异常处理。
在这之前要先澄清CPU与中断服务函数与main(程序执行的主要内容的地方)。
如下图所示:
在执行主程序main前,以STM32F103c8t6为例:在执行前,会将部分中断服务函数使能
这里的中断服务函数分为:
内核中断服务函数:这部分的中断是微控制器运行的基础,确保系统的正常运行。
外设中断服务函数:在对这部分的中断使能前是为了保证“应用程序需要与外部设备通信或者需要定时器进行精确的时序控制”
在keil5中中断服务函数应该怎么去找嘞,不管是在标准库还是HAL库,都在这个启动文件里面找到:
下图就是部分中断服务函数入口地址(中断向量表):
对于中断向量表的描述:用来查询中断服务函数地址,也是32默认的中断服务函数的执行顺序。所以对于中断向量表能提供2 个信息,一个是中断服务函数的地址,另一个就是中断服务函数默认的执行的顺序。
执行这些中断服务函数的目的就是去抢夺ARM Cortex-M资源,也就是它的运算能力,抢夺过来来处理中断内的事件,当这些做完后STM32才会去执行mian内的程序。
在这个抢夺就出现了顺序的问题:应该让谁来做第一个,谁来做最后一个嘞?
这里就引回了上面在将中断概念提到的“小明”事件了,下面我将细细讲来。
2、NVIC寄存器、工作原理
NVIC寄存器:
(以下资料来自于:参考手册 STM32F10xxx Cortex-M3 编程手册.pdf)
中断使能寄存器(ISER)
位置:4.3.2—NVIC_ISERx
中断除能寄存器(ICER)
位置:4.3.3—NVIC_ICERx
应用程序中断及复位控制寄存器
位置: 4.4.5—SCB_AIRCR
中断优先级寄存器
位置: 4.3.7—NVIC_IPRx
NVIC工作原理
(1)这里的AIRCR寄存器是用来规定中断优先级(抢占优先级与响应优先级(又称子优先级))的个数的的总个数的;
(2)寄存器ISER、ICER,分别用1bit控制中断的使能、失能(外设中断),使能后才能对该中断的中断优先级设置;
(3)IPR寄存器在AIRCR寄存器分配后进行中断优先级的数量的设置,8bit控制一个中断优先级;
(4)SHPR寄存器是只对内核中断的中断优先级(抢占优先级)进行数量的控制,所以在内核中断与外设中断执行顺序的比较中只会比较抢占优先级,而且内核中断是一直使能,它不需要经过ISER、ICER的寄存器的控制。
下图是各个STM32型号里的内核中断、外部中断、中断有优先级的总数量。
在这里的分组(抢占优先级:响应优先级)有这几种情况:
[4:0]:16个抢占优先级,0个响应优先级
[3:1]:12个抢占优先级,4个响应优先级
[2:2]:8个抢占优先级,8个响应优先级
[1:3]:4个抢占优先级,12个响应优先级
[0:4]:0个抢占优先级,16个响应优先级
NVIC中断优先级分组规则:
(1).抢占优先级:高抢占优先级可以打断正在执行的低抢占优先级中断
(2).响应优先级:当抢占优先级相同时,响应优先级高的先执行,但不能互相打断
(3).抢占和响应都相同的情况下,自然优先级高的,先执行
(4).自然优先级:中断向量表的优先级
补充:
(1).上述的高是指的是顺序高,不是数值高,也就是说数值越小,表示优先级越高
(2).先抢占再响应!
例如:
这里的抢占优先级和响应优先级的位数是通过AIRCR寄存器控制分组、IPR寄存器设置前4位数来实现的。
额。。。如果这里你还没理解的话,下面我叫一个通俗易懂的:
现在我有两个外部中断 EXTIO和EXTI1:
我的NVIC分组为[0:4]——代表现在的外部中断只能设置响应优先级,也就是16给优先级
现在只能设置EXTI0 、 EXTI1的响应优先级
设置:EXTIO 12 个响应优先级
EXTI1 10 个响应优先级
比较下:两个外部中断的执行顺序为EXTI1 在 EXTI0 之前
3、在HAL中对于在CUBE MX里的配置和在kile5 中 NVIC相关的函数
在CUBE MX 中的配置方法:
先使能两个EXTI0和EXTI1两个外部中断
设置NVIC:
先说主要的
Priority Group中断优先级分组:这里可以选择抢占优先级与响应优先级分组方式,这里对应着我上面说的NVIC分组来的
Enabled,勾选这个就能直接使能对应的中断,也可以用来检查是否将对应的中断段使能了。
Preemption Priority,设置抢占优先级;Sub Priority,设置响应优先级
补充:具体的数量是根据优先级分组来定的,特别说明,它这里的个数是从0开始的也就是说0作为第一个,0-15代表16个中断优先级,但如果是在[4:0]或者[0:4]的情况下,这时候0就对应了没有抢占优先级、没有响应优先级
其他选项的说明:
Search,这里可以直接搜寻,对应的中断名
Sort by Premption Priority and Sub Priorrity,勾选这个进行,抢占与响应优先级的排序
Sort by interrupts names,勾选这个进行按中断名称排序
Force DMA channels Interrupts,指定是否强制使能DMA通道的中断。(这个我会在下面将中断的在HAL库中的机制时具体讲解)
HAL库,在kile5中相关函数
(注意:在CUBE MX配置后这些会自动生成,如果你翔去手动修改直接按照下面函数的作用传入参数就行):
使能函数,传入参数为对应的需要使能的中断
例如:这样,使能EXTI0外设中断
失能也一样的用法。
规定NVIC中断分组情况,传入参数为抢占与响应优先级的分组情况
例如:这样分组抢占优先级:响应优先级[0:4]
设置优先级数量,对抢占优先级与响应优先级数量的设置。
例如:设置外部中断EXTI0的抢占优先级为8、响应优先级为8。
再次提醒以上的对NVIC的配置均可以在CUBE MX里面直接配置,上面讲解是为了帮助大家理解NVIC的配置的由来,所以不是很建议,在已经配置完后再去手动配置一遍NVIC!!!!
二、在HAL库中的“中断服务函数”——中断回调机制
1、概念
在HAL库里面用来对于进入中断后执行内部的事件的这个过程不叫做“中断服务函数”,这个概念是来之标准库的。
从逻辑上面来讲:
标准库的处理逻辑,进入中断里开始执行程序,执行完后,先清除标志位然后在退出中断;而在HAL库里面处理的逻辑就截然不同,在HAL库里面想要执行中断服务函数,它不像标准库直接编写中断服务函数就行。在HAL库里面,你需要通过HAL库中断处理公用函数、HAL库数据处理回调函数来实现具体流程为可以表为:
实现一个中断内的时间 =中断服务例程——>HAL库中断处理公用函数——>HAL库数据处理回调函数——>HAL库中断处理公用函数——>中断服务例程
特别注意:
(1)中断例程:是指中断向量表中地址映射的函数,这个函数如果在没有使能前,是为被定义的,它只是在提前为地址做了个标识,这个需要自己使能后,才能对这个函数进行操作,当然这个函数会在,你CUBE MX生成工程过程中直接生成这个函数。
(2)HAL库中断处理公用函数:
中断向量表中,可以看出,去使用那个中断,就需要先去定义那个中断例程
在自动生成的中断例程中,它会生成指向中断处理公用的函数
在中断处理公用函数里,执行着异常处理、清中断标志等处理
最后就是需要设计进入中断后执行的事件,需要你在回调函数中编程,注意在每个外设只能定义一个回调函数,但像EXTI这种外部中断有多个的时候,但只有一个回调函数的情况,可以在回调函数里面,可以通过条件编程来实现,应用多个EXTI外部中断,如下图:
下图是它一个工作的逻辑图,便于大家理解:
2、代码实例(这一部分博主会在下一张用一个简单和一个进阶的代码实例,来细细讲解)
(博主用的是正点原子的mini板)。
代码逻辑:
按下Key_0,再按下按键Key_1,观察是那个灯亮,按照EXTI0先执行EXTI1后执行的逻辑,
正常结果:会出现LED_0发生闪烁而不是LED_发生闪烁,
错误结果:如果是EXTI1先执行的话当LED_1闪烁完后再次重复按键操作会出现LED_0长亮
先选择芯片型号,STM32F103RCT6
使能LED0与LED1引脚PA8与PD2:初始化为推完输出与高电平,电平触发方式为电平下降沿触发。
使能EXTI0与EXTI1外部中断引脚PA0与PA1:初始化为下降沿电平触发EXTI0与EXTI1外部中断。
设置NVIC中断优先级分组:
(1)分组为[4:0],16位抢占优先级,0位响应优先级
(2)分配优先级数量:EXTI0抢占优先级数量为0,EXTI1抢占优先级的数量为1,既如果同时触发外部中断EXTI0与EXTI1的条件时先执行外部中断EXTI0里的事件再执行后者。
设置按键Key_0与Key_1的引脚为PA15与PC5:初始化为上拉输出,电平下降沿触发
创建工程,打开Keli5进行编程:
宏定义两个按键对应的引脚,创建第二个中断触发的条件变量
#define Key_0 HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_15) #define Key_1 HAL_GPIO_ReadPin(GPIOC,GPIO_PIN_5) int num=0;
编写EXTI中断回调函数
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) { switch (GPIO_Pin) { case GPIO_PIN_0: HAL_GPIO_WritePin(GPIOA,GPIO_PIN_8,GPIO_PIN_RESET); HAL_Delay(100); HAL_GPIO_WritePin(GPIOA,GPIO_PIN_8,GPIO_PIN_SET); break; case GPIO_PIN_1: HAL_GPIO_WritePin(GPIOD,GPIO_PIN_2,GPIO_PIN_RESET); HAL_Delay(100); HAL_GPIO_WritePin(GPIOA,GPIO_PIN_8,GPIO_PIN_SET); num = 1; break; } }
编写自建逻辑
/*这里的自检逻辑 按下Key_0,再按下按键Key_1,观察是那个灯亮,按照EXTI0先执行EXTI1后执行的逻辑, 正常结果:会出现LED_0发生闪烁而不是LED_发生闪烁, 错误结果:如果是EXTI1先执行的话当LED_1闪烁完后再次重复按键操作会出现LED_0长亮 */ if(Key_0 == RESET) { if(num ==1&&Key_1 == 0) { while(1) { HAL_GPIO_WritePin(GPIOA,GPIO_PIN_0,GPIO_PIN_RESET); } } else HAL_GPIO_WritePin(GPIOA,GPIO_PIN_0,GPIO_PIN_RESET); HAL_Delay(10); } if(Key_0 == RESET) { if(Key_1 == 0) HAL_GPIO_WritePin(GPIOA,GPIO_PIN_1,GPIO_PIN_RESET); }