前言
本次我们学习一下STM32的中断控制器—— NVIC,控制着整个STM32芯片中断相关的功能,它跟Cortex-M3 内核紧密联系,是内核里面的一个外设。
本篇博客大部分是自己收集和整理,借鉴了很多大佬的图片和知识点整理,如有侵权请联系我删除。
本博客内容原创,创作不易,转载请注明
一. NVIC(嵌套向量中断控制器)
NVIC 是
Cortex-M
处理器不可或缺的部分,它为处理器提供了卓越的中断处理能力。
Cortex-M
处理器使用一个矢量表,其中包含要为特定中断处理程序执行的函数的地址。
接受中断时,处理器会从该矢量表中提取地址。
NVIC 是嵌套向量中断控制器,控制着整个芯片中断相关的功能,它跟内核紧密耦合,是内核里面的一个外设。但是各个芯片厂商在设计芯片的时候会对 Cortex-M3 内核里面的 NVIC 进行裁剪,把不需要的部分去掉,所以STM32 的 NVIC 是 Cortex-M3 的 NVIC 的一个子集。
片上外设与内核外设他们都在芯片里面,但内核外设是在内核CPU里面,片上外设就是内核之外。
几个关于内核外设重要的库文件:
Cortex-M3 内核的外设也比较多,但STM32并没有用到这么多内核外设对其进行了裁剪,STM32重要的内核外设用到的库函数放在了misc.c文件之中所以core_cm3.c文件用的较少。
core_cm3.c:内核外设的驱动固件库
core_cm3.h:实现了内核(CPU)里面的外设的寄存器映射,还有很多关于内核外设的库函数。
misc.h:NVIC_InitTypeDef结构体,以及库函数的参数和声明
misc.c:NVIC(嵌套向量中断控制器)、SysTick(系统滴答定时器)相关函数
STM32 目前支持的中断共为
84
个(
16
个内核
+68
个外部),和
16
级可编程中断优先级
的设置(仅使用中断优先级设置
8bit
中的高
4
位,见后面解释)。
二 . NVIC 中断概念
1.现实NVIC中断的演示
用文字进行描述就是:
CPU 在正常运行程序时,由于内部或外部事件引起暂时中止现行程序,转去执行请求
CPU
为其服务的那个外设或事件的
服务程序
,等待该服务程序执行完成后又返回到被中止
的地方程序,这样一个过程。
2.NVIC中断流程
3.NVIC中断在程序中的意义
中断,程序设计中占着非常重要的地位。如果没有中断,则 CPU 的工作效率会大折扣。
例如:当我们在使用
UART
模块中,接收电脑发送来的数据,使用了
while(
查询标志
){}
查询状态
的方式,如果电脑没有发送数据,则程序会一直阻塞,使
CPU
做不了其他事情。
如果有一 种机制,不用 CPU 循环查询是否有数据到来,而是硬件自动接收数据,当收到数据时候的 自动通知 CPU
,这时候
CPU
再去把数据读取出来。这样,在没有数据接收到前,
CPU
可以去做其他事情,工作效率自然就提高了。
在 CPU 硬件中,通过中断这种机制来实现这个功能,每个片上外设硬件都提供一个中断信号,当模块处理特定工作状态时,会发出中断信号通信 CPU
。
4.中断嵌套
现在的 CPU
都集成了许多片上外设模块,而
每个模块都有自己的中断信号,CPU 设计
者给这些中断源分配好一个固定的地址
,当
CPU
收到某一中断信号时,中断源就会强制中
止当前的程序,跳转到和中断信号源对应的中断入口地址,而这个地址上,通常在编程时候
是放置一个函数地址,
这样,CPU
实际就是去执行这个函数的代码,这个函数称作中断服 务函数。中断源之间有优先级之分,高优先级可以中断低优先级程序。
比如,先是发生发一 个优先级比较低的中断,CPU 转到其中断服务函数去执行
,
在执行过程中,发生更高优先级 的中断。
那么,CPU 同样中止当前的代码,转到高优先级的中断源对应的中断入口去执行中断服务函数,当高优先级中断服务函数执行完成后再返回原来被中断的低优先级的中断服务函数断点处继续运行,运行完成后,返回到主程序的断点片继续运行。
三. NVIC 中断优先级
NVIC 控制器的优先级使用
8
个二进制位表示,分成两部分表示,分别是
抢占优先级
和
响应优先级
。
抢占优先级:
含义是不同等级间的中断可以嵌套,高优先级可以中断低优先级,数字小的优先级高。
响应优先级:
含义是不同响应优先级的中断不能嵌套,但是当抢占优先级相同,响应优先级不同,多个中断源同时发生中断时候,响应优先级高的中断事件会优先响应,数字小的优先级高。
自然优先级:
就是
NVIC
控制器的中断源编号,数字小的,优先级高。作用:当抢占优先级和响应优先级都相同的中断源同时发生中断时候,自动优先级高的优先响应。不存在嵌套关系。
优先级等级小结:
抢占优先级 >响应优先级 >自然优先级 .
抢占优先级决定是否可以嵌套,响应优先级和自然优先级决定同时发生时,先响应谁
(如果抢占相同,才依据响应,如果抢占和响应都相同,自然优先级才会起作用)。
如图所示,每当抢占优先级和响应优先级一样的时候,最终都是会回到芯片设置的自然优先级顺序来判断优先层数,从上到下,越高优先级越高
1.中断优先组
NVIC分组设置 Void NVIC_SetPriorityGrouping(uint32_tPriorityGroup)
PriorityGroup:分组方式对应的值
在 Cortex-M4
中每个中断源使用
8
个比特位设置中断源的优先级,这
8
个比特位可以
有
8
种分配方式,如下:
第
0
组
:所有
8
位用于指定响应优先级。 抢占级取值范围:
0
,响应级取值范围
0~255
第
1
组
:最高
1
位用于指定抢占式优先级,
0~1
,最低
7
位用于指定响应优先级
0~127
第
2
组
:最高
2
位用于指定抢占式优先级,
0~3
,最低
6
位用于指定响应优先级
0~63
第
3
组
:最高
3
位用于指定抢占式优先级,
0~7
,最低
5
位用于指定响应优先级
0~31
第
4
组
:最高
4
位用于指定抢占式优先级,
0~15
,最低
4
位用于指定响应优先级
0~15
第
5
组
:最高
5
位用于指定抢占式优先级,
0~31
,最低
3
位用于指定响应优先级
0~7
第
6
组
:最高
6
位用于指定抢占式优先级,
0~63
,最低
2
位用于指定响应优先级
0~3
第
7
组
:最高
7
位用于指定抢占式优先级,
0~127
,最低
1
位用于指定响应优先级
0~1
是否存在第
8
种分组?不存在
8
个位都用来表示抢占优先级的情况。
2、NVIC优先级编码
uint32_t NVIC_EncodePriority(uint32_tPriorityGroup,uint32_t PreemptPriority,uint32_tSubPriority); 这个函数负责把分组方式,抢占优先级,子优先级数值编码成一个32位的数字,做为返回值返回。
3、NVIC优先级设置
Void NVIC_SetPriority(IRQn_TypeIRQn,uint32_tpriority) 该函数用于设置指定中断源的中断优先级。
IRQn_Type是在stm32F10x.h文件中有定义,一个枚举类型,定义了STM32F10x芯片对应所有的中断源编号。
IRQn:中断源编号;
priority:中断优先级,就是使用NVIC_EncodePriority()函数编码的返回值。
4、NVIC中断使能
Void NVIC_EnableIRQ(IRQn_TypeIRQn) 该函数用于使能指定中断源。
四 .初始化NVIC控制器一般步骤:
1.设置分组方式
2.确定中断源的抢占优先级和响应优先级,使用NVIC_EncodePriority函数进行编码
3.把上下编码得到优先级值写入优先级配置寄存器,使用NVIC_SetPriority函数。
4.使能对应中断源,使用NVIC_EnableIRQ函数。
5.具体的模块中断:还要去配置外设中断相关的寄存器(下小节会以外部中断以例子讲解)
6.编写中断服务函数:
去启动代码中找到对应的中断入口,以确定中断函数名字 在C文件中编写一个无返回值,无类型的函数。函数名就是上一步得到的名字,如串口1 :
则以
C
程序中编写一个中断服务函数:
void USART1_IRQHandler(void)
{
发生串口
1
中断时候你要做什么
,
写在这里
}
中断服务函数
中断服务函数编写内容上和普通代码没有什么区别,用户根据需要编写功能代码,只是
里面
一定要写有清除中断标志的代码
。中断服务函数名要和启动代码对应中断源的中断入口
标志相同。且函数原型是
void fun(void)
。
NVIC 函数库函数配置
1.NVIC 分组设置
void NVIC_SetPriorityGrouping(uint32_t PriorityGroup)
PriorityGroup
: 分组方式对应的值,
0~7
前面表格中说过。 比如说选择第
2
组, 则
是
2
位抢占优级, 那对应的值是
7-2 = 5
。
设置分组
2
方式,
NVIC_SetPriorityGrouping(7-2);
2. NVIC 优先级编码
uint32_t NVIC_EncodePriority (uint32_t PriorityGroup, uint32_t PreemptPriority, uint32_t
SubPriority);
这个函数负责把分组方式,抢占优先级,子优先级数值编码成一个
32
位的数字,做为返回值返回。
PriorityGroup
: 分 组 方 式 对 应 的 值
;
也 就 是
AIRCR[10:8]
中 的 值 。 或 者 说 是
NVIC_SetPriorityGrouping
函数的参数。
PreemptPriority
: 正确的抢占优先级值
,
比如分组
2
, 范围是
0~3
取值。
SubPriority
: 正确的子优先级值
,
比如分组
2
, 范围是
0~3
取值。
说明: 这个函数并没有把优先级设置到寄存器中, 只是简单的合成一个表示优先级数字。 后面再使用其他函数设置这个值到寄存器中。可以使用移位方式配置优先级。 但是配置上比较麻烦。
Pri = NVIC_EncodePriority (7-2, 1, 3);
分组
2
, 抢占优先级是
1
, 响应优先级是
3
。
返回编码后的值 ,这个值就是写入对应中断源中断寄存器中的值。 这个函数并没有写入寄
存器中。
3.NVIC 优先级设置
void NVIC_SetPriority(IRQn_Type IRQn, uint32_t priority)
该函数用于设置指定中断源的中断优先级。
IRQn_Type
是在
stm32F10x.h
文件中有定义,一个枚举类型,定义了
STM32F10x
芯
片对应所有的中断源编号。
IRQn
: 中断源编号。
priority
: 中断优先级, 就是使用
NVIC_EncodePriority()
函数编码的返回值。
如:配置串口
1
的优先级。
Pri = NVIC_EncodePriority (7-2, 1, 3);
NVIC_SetPriority(USART1_IRQn , Pri )
;
//
设置串口
1
的中断优先级,
USART1_IRQn =
37
。
4.NVIC 中断使能
void NVIC_EnableIRQ(IRQn_Type IRQn)
IRQn_Type
是在
stm32F10x.h
文件中有定义,一个枚举类型,定义了
STM32F10x
芯
片对应所有的中断源编号。
IRQn
: 中断源编号。
如
:
使能串口
1
的
NVIC
。
NVIC_EnableIRQ(USART1_IRQn)
;
//
使能串口
1
的
NVIC
5.NVIC 中断除能
void NVIC_DisableIRQIRQn_Type IRQn)
参数跟中断使能的参数是一样的。
NVIC_DisableIRQ
如
:
除能串口
1
的
NVIC
。
NVIC_DisableIRQ(USART1_IRQn)
;
//
除能串口
1
的
NVIC
五 .中断控制
1.中断概念
1)对于 STM32 讲,外部中断通道位置 28(35 号优先级)是给外部设备 TIME2 的,但 TIME2
本身能够引起中断的中断源或事件有好多个,比如更新事件(上溢/下溢)、输入捕获、输出
匹配、DMA 申请等。所有 TIME2 的中断事件都是通过一个 TIME2 的中断通道向 STM32 内核提
出中断申请,那么 STM32 中如何处理和控制 TIME2 和它众多的、不同的、中断申请呢?
2.寄存器说明
cortex_m3 内核对于每一个外部中断通道都有相应的控制字和控制位,用于单独的和总
的控制该中断通道。它们包括有:
z
中断优先级控制字:PRI_n(上面提到的)
z
中断允许设置位:在 ISER 寄存器中
z
中断允许清除位:在 ICER 寄存器中
z
中断悬挂 Pending(排队等待)位置位:在 ISPR 寄存器中(类似于置中断通道标志位)
z
中断悬挂 Pending(排队等待)位清除:在 ICPR 寄存器中(用于清除中断通道标志位)
z
正在被服务(活动)的中断(Active)标志位:在 IABR 寄存器中,(只读,可以知道当
前内核正在处理哪个中断通道)
因此,与 TIME2 中断通道相关的,在 NVIC 中有 13 个 bits,它们是
PRI_28(
IP[28]
),
的 8 个 bits(只用高 4 位);加上中断通道允许,中断通道清除(相当禁止中断),中断通道
Pending 置位(我的理解是中断请求发生了,但当前有其它中断服务在执行,你的中断级别
又不能打断别人,所以 Pending 等待,这个应该由硬件自动置位的),中断 Pending 位清除
(可以通过软件将本次中断请求、且尚处在 Pending 状态,取消掉),正在被服务的中断
(Active)标志位,各 1 个 bit。
作为外围设备 TIME2 本身也包括更具体的,管理自己不同中断的中断控制器(位),它们
主要是自身各个不同类型中断的允许控制位,和各自相应的中断标志位(这个在 STM32 的手
册中有详细的说明了)。
3.中断过程和控制过程
a/ 初始化过程
首先要设置寄存器
AIRC 中 PRIGROUP 的值,规定系统中的抢先优先级和子优先级的个数
(在 4 个 bits 中占用的位数);
设置 TIME2 本身的寄存器,允许相应的中断,如允许 UIE(TIME2_DIER 的第[0]位)
设置 TIME2 中断通道的抢先优先级和子优先级(
IP[28]
,在 NVIC 寄存器组中)
设置允许 TIME2 中断通道。
在 NVIC 寄存器组的 ISER 寄存器中的一位。
b/ 中断响应过程
当 TIME2 的 UIE 条件成立(更新,上溢或下溢),硬件将 TIME2 本身寄存器中 UIE 中断
标志置位,然后通过 TIME2 中断通道向内核申请中断服务。
此时内核硬件将 TIME2 中断通道的 Pending 标志置位(相当与中断通道标志置位),表
示 TIME2 有中断申请。
如果当前有中断在处理,TIME2 的中断级别不够高,那么就保持 Pending 标志,当然用
户可以在软件中通过写 ICPR 寄存器中相应的位把本次中断清除掉。
当内核有空,开始响应 TIME2 的中断,进入 TIME2 的中断服务。此时硬件将 IABR 寄存
器中相应的标志位置位,表示 TIME2 中断正在被处理。同时硬件清除 TIME2 的 Pending 标志
位。
c/ 执行 TIME2 的中断服务程序
所有 TIME2 的中断事件,都是在一个 TIME2 中断服务程序中完成的,所以进入中断程序
后,中断程序需要首先判断是哪个 TIME2 的具体事件的中断,然后转移到相应的服务代码段
去。
注意不要忘了把该具体中断事件的中断标志位清除掉,硬件是不会自动清除 TIME2 寄存
器中具体的中断标志位的。
如果 TIME2 本身的中断事件多于 2 个,那么它们服务的先后次序就由用户编写的中断服
务决定了。换句话说,对于 TIME2 本身的多个中断的优先级,系统是不能设置的。所以用户
在编写服务程序时,应该根据实际的情况和要求,通过软件的方式,将重要的中断优先处理
掉。
当然你也可以每次中断服务只处理其中的一个,然后再次进入中断,处理下一个。
d/ 中断返回
内核执行完中断服务后,便进入中断返回过程,在这个过程中需要:
硬件将 IABR 寄存器中相应的标志位清另,表示该中断处理完成
如果 TIME2 本身还有中断标志位置位,表示 TIME2 还有中断在申请,则重新将 TIME2
的 Pending 标志置为 1,等待再次进入 TIME2 的中断服务。
以上中断过程在《ARM Cortex-M3 权威指南》中有详细描述,并配合时序图说明,可以
参考。
上述两点弄清楚后,就可以在 ST 提供的函数库的帮助下,正确的设置和使用 STM32 的
中断系统了。
总结:
NVIC中断的执行流程并不复杂,其实就是在正常程序过程中执行其他的操作。大家如果对我的博客有疑问或者错误,可以@我修改,大家相互交流。
点赞收藏关注博主,不定期分享单片机知识,互相学习交流。
————————————————