STM32F103五分钟入门系列NVIC中断优先级管理
Posted 自信且爱笑‘
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了STM32F103五分钟入门系列NVIC中断优先级管理相关的知识,希望对你有一定的参考价值。
学习板:STM32F103ZET6
强推系列:
STM32F103五分钟入门系列(一)跑马灯(库函数+寄存器)+加编程模板+GPIO总结
STM32F103五分钟入门系列(二)GPIO的七大寄存器+GPIOx_LCKR作用和配置
STM32F103五分钟入门系列(三)GPIO的常用库函数使用方法总结+一个网络上的误区
参考:
STM32F103五分钟入门系列(八)SysTick滴答定时器+SysTick中断实现跑马灯
前言
之前总结SysTick定时器时用SysTick中断实现了跑马灯实验,之前的中断只是稍微提了一下。从本博开始,在接下来几篇博客将总结一下中断这一块的东西。
本博先总结一下NVIC中断优先级管理,从底层中深入了解NVIC的配置,旨在为后面的中断总结提供基础。中断这块一直是所有单片机使用的重点和难点,所以这块内容博主会总结的比较详细,也比较啰嗦。
一、32的中断
(一)中断的数量
中断的概念这些就不在赘述了,之前51单片机中断中有了非常详细的总结,且前文附了链接。相比于32,51的中断就是个弟弟。首先我们了解一下32的中断。
我们知道STM32的内核是CM3,该内核有256个中断,其中有16个内部中断和240个外部中断。
STM32只用到84个中断,即16个内核中断和68个可屏蔽中断,我们用到的就是这68个中断。
而STM32F1中用到的可屏蔽中断又少于68个中断,比如我现在用的板子只有60个。
(二)中断数量的查找
具体板子中有多少个可屏蔽中断,可以去对应板子的中文参考手册中查找,不过参考手册的内容都可以在底层中得到体现,所以我们可以直接在底层中去找。
随便打开一个工程,找到stm32f10x.h
在这个头文件中定义了各类中断,可以发现,前18个中断所有STM单片机的都是一样的,这里并没有区分板子类型。
上图所示,之后的中断就开始区分板子类型了。比如STM32F10X_LD板子,在STM32的68个可屏蔽中断中,只用了42个。
找到本博板子类型:
共有60个可屏蔽中断,即0~59。这个数字并不是随便定义的,而是根据CM3内核中,可用中断的顺序来定义的。
如使能中断的寄存器是ISER[8],这是一个寄存器组。即ISER[0]表示一个32位寄存器…ISER[7]表示一个32位寄存器组。该寄存器可使能8×32=256个中断,所以这里定义中断的名称和数字的时候,都是按照这个寄存器使能的顺序来的。如使能DMA2,这里定义DMA2_Channel4_5_IRQn = 59
,使能该中断时,需要将ISER[1]的寄存器的位27置1。即排序是ISER[0]的0位、1位…31位再到ISER[1]的0位、1位…31位,如此排序下来,按照顺序定义的各类中断的数字。
二、NVIC中断优先级管理
(一)NVIC相关寄存器
NVIC相关寄存器定义在core.cm3文件中:
翻译一下注释:
typedef struct
{
__IO uint32_t ISER[8]; //个中断使能寄存器组
uint32_t RESERVED0[24];
__IO uint32_t ICER[8]; // 中断除能寄存器组
uint32_t RSERVED1[24];
__IO uint32_t ISPR[8]; //中断挂起控制寄存器组
uint32_t RESERVED2[24];
__IO uint32_t ICPR[8]; //中断解挂控制寄存器组
uint32_t RESERVED3[24];
__IO uint32_t IABR[8]; //中断激活标志位寄存器组
uint32_t RESERVED4[56];
__IO uint8_t IP[240]; //中断优先级控制的寄存器组
uint32_t RESERVED5[644];
__O uint32_t STIR; //软件触发中断寄存器
} NVIC_Type;
这些寄存器都是32位寄存器,不过目前只需知道ISER[8]中断使能寄存器和IP[240]中断优先级控制寄存器即可。
前文中用过ISER[8]举过例子。IP[240]寄存器可用控制240个可屏蔽寄存器,并且顺序与之前底层中定义的寄存器顺序一致,所以对于F1的板子,只会用到IP[]数组的前几位(本博板子IP[0]~IP[59])。
前4个寄存器都是1有效,0无效的,使能和失能、挂起和解挂、两个寄存器来控制。
举例说明寄存器用法:
使能和使能EXTI0_IRQn (EXTI0_IRQn = 6,
)
ISER[0]=(1<<6); //使能EXTI0_IRQn
ICER[0]=(1<<6); //失能EXTI0_IRQn
使能和使能USART1_IRQn (USART1_IRQn = 37
)
ISER[1]=(1<<5); //USART1_IRQn
ICER[1]=(1<<5); //USART1_IRQn
以上这些只了解就好,以后配置中断用寄存器配置太麻烦了,还是直接用库函数方便。
(二)优先级分组(重点)
1、寄存器
控制中断优先级分组的寄存器在SCB中定义:(core_cm3.h)
中断优先级分为5组,由AIRCR寄存器的位10:8来控制。
组 | AIRCR[10:8] | 优先级 |
---|---|---|
0 | 111 | 0位抢占优先级,4位响应优先级 |
1 | 110 | 1位抢占优先级,3位响应优先级 |
2 | 101 | 2位抢占优先级,2位响应优先级 |
3 | 100 | 3位抢占优先级,1位响应优先级 |
4 | 011 | 4位抢占优先级,0位响应优先级 |
2、抢占优先级和响应优先级
(1)首先要明白,是0位、1位、2位…是位,比如:
3位抢占优先级,1位响应优先级
3位就是000~111,即0 ~7,即抢占优先级可以是0 ~7的数字;1位就是0 ~1,即响应优先级只能是0和1。
再如:
4位抢占优先级,0位响应优先级
则抢占优先级可以是0000~1111,即0 ~15。
所以抢占优先级和响应优先级都是0~15的数,但是一定要注意它们所占位数和为4,数值不能同时大于3(都占两位时都为3)。
在底层中也有体现:
可以看到定义的这些优先级<0x10,即最大为15,即所占位数为4位时:1111B=15
(2)高抢占优先级可以打断低抢占优先级。
比如:
选用第三组优先级,即3位抢占优先级,1位响应优先级。则抢占优先级等级:0~7;响应优先级等级为0 ~1。
此时0等级的抢占优先级可以打断1等级的抢占优先级;1等级的抢占优先级可以打断7等级的抢占优先级。
(3)抢占优先级相同的中断,响应优先级高的不能打断响应优先级低的中断。抢占优先级相同的中断,当两个中断同时发生的情况下,哪个响应优先级高,哪个先执行
刚刚的例子:
当两个中断的抢占优先级都为6时,如果中断A响应优先级为0,中断B响应优先级为1,则中断A不能打断中断B。但是如果中断A和中断B事件同时发生,则响应优先级高的中断A先执行,中断A中断返回后再执行中断B。
当然两个抢占优先级不同的中断同时发生,肯定是抢占优先级高的先执行。
(4)如果两个中断的抢占优先级和响应优先级都相同,则谁先发生中断,谁就先执行
抢占优先级相同的话,就不存在抢占问题;响应优先级相同的话,就不存在同时发生时的顺序问题,当同时发生时只能随机执行。(不过近期做F4的项目的时候发现当两个优先级都一样的中断,同时发生中断,会只执行前面配置的那个中断)
三、面向程序
(一)中断优先级分组函数NVIC_PriorityGroupConfig()
该函数定义在misc.h头文件中。
打开函数体:
void NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup)
{
/* Check the parameters */
assert_param(IS_NVIC_PRIORITY_GROUP(NVIC_PriorityGroup));
/* Set the PRIGROUP[10:8] bits according to NVIC_PriorityGroup value */
SCB->AIRCR = AIRCR_VECTKEY_MASK | NVIC_PriorityGroup;
}
传递过来的参数就是组别:0~4
根据函数体,就是对SCB->AIRCR寄存器的设置
比如#define NVIC_PriorityGroup_0 ((uint32_t)0x700)
组0定义为0x700,即011100000000,经过:SCB->AIRCR = AIRCR_VECTKEY_MASK | NVIC_PriorityGroup;
后,将SCB->AIRCR 寄存器的8:10位都置1,跟前面那个表是一致的。
这个各组的定义数字可记可不记,不过可以通过以下方法记忆:
组0——>~000=111——>0x7
组1——>~001=110——>0x6
组2——>~010=101——>0x5
组3——>~011=100——>0x4
组4——>~100=011——>0x3
这个函数在程序中一般写在主函数里面各类初始化函数之前。
如设置中断分组为2,2位抢占优先级,2位响应优先级:
#include "stm32f10x.h"
#include "delay.h"
#include "led.h"
int main(void)
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
LED_Init();
delay_Init();
...
...
...
while(1)
{
}
}
(二)中断优先级初始化函数NVIC_Init()
该函数也是定义在misc.h头文件中
直接附底层代码:
void NVIC_Init(NVIC_InitTypeDef* NVIC_InitStruct)
{
uint32_t tmppriority = 0x00, tmppre = 0x00, tmpsub = 0x0F;
/* Check the parameters */
assert_param(IS_FUNCTIONAL_STATE(NVIC_InitStruct->NVIC_IRQChannelCmd));
assert_param(IS_NVIC_PREEMPTION_PRIORITY(NVIC_InitStruct->NVIC_IRQChannelPreemptionPriority));
assert_param(IS_NVIC_SUB_PRIORITY(NVIC_InitStruct->NVIC_IRQChannelSubPriority));
if (NVIC_InitStruct->NVIC_IRQChannelCmd != DISABLE)
{
/* Compute the Corresponding IRQ Priority --------------------------------*/
tmppriority = (0x700 - ((SCB->AIRCR) & (uint32_t)0x700))>> 0x08;
tmppre = (0x4 - tmppriority);
tmpsub = tmpsub >> tmppriority;
tmppriority = (uint32_t)NVIC_InitStruct->NVIC_IRQChannelPreemptionPriority << tmppre;
tmppriority |= NVIC_InitStruct->NVIC_IRQChannelSubPriority & tmpsub;
tmppriority = tmppriority << 0x04;
NVIC->IP[NVIC_InitStruct->NVIC_IRQChannel] = tmppriority;
/* Enable the Selected IRQ Channels --------------------------------------*/
NVIC->ISER[NVIC_InitStruct->NVIC_IRQChannel >> 0x05] =
(uint32_t)0x01 << (NVIC_InitStruct->NVIC_IRQChannel & (uint8_t)0x1F);
}
else
{
/* Disable the Selected IRQ Channels -------------------------------------*/
NVIC->ICER[NVIC_InitStruct->NVIC_IRQChannel >> 0x05] =
(uint32_t)0x01 << (NVIC_InitStruct->NVIC_IRQChannel & (uint8_t)0x1F);
}
}
有一个if…else语句,else语句为disable时的情况,即对对应中断(函数传递过来的第一个参数)失能。if中是enable的情况。
首先看一下传递过来的参数有哪些:
发现有四个参数
第一个参数:表示是哪个中断,本博第一部分《中断数量的查找》这块已经说明了有哪些中断,这里参数的名称都是从那里来的。
第二个参数:设置抢占优先级
第三个参数:设置响应优先级。上一个函数已经分好组了,上一函数设置的抢占优先级和响应优先级的位数,这里是设置对应的等级,如上一个函数设置2位抢占优先级,2位响应优先级,则这里的第2、第3参数都填0~3中数字。
第四个参数:使能或失能ENABLE、DISABLE
之前总结GPIO的时候,总结过这类型函数怎么设置:
例如:
设置串口1中断,优先组为2,即2位抢占优先级,2位响应优先级,抢占优先级设为1,响应优先级设为2,则有以下设置:
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_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->ISER[NVIC_InitStruct->NVIC_IRQChannel >> 0x05] =
(uint32_t)0x01 << (NVIC_InitStruct->NVIC_IRQChannel & (uint8_t)0x1F);
代码:NVIC->ISER[NVIC_InitStruct->NVIC_IRQChannel >> 0x05]
的意思是对ISER[8]这个寄存器组操作。为什么要右移5位呢?
在ISER[8]寄存器中中断编号与寄存器映射关系:
①ISER[0]——>编号0~31 =0x000000 ~0x011111
如果中断编号在0~31,右移5位后:NVIC_InitStruct->NVIC_IRQChannel >> 0x05
=0,即编号在0~31的中断在寄存器ISER[0]中。
②ISER[1]——>编号32~63 =0x100000 ~0x111111
如果中断编号在32~63 ,右移5位后:NVIC_InitStruct->NVIC_IRQChannel >> 0x05
=1,即编号在32~63 的中断在寄存器ISER[1]中。
后面的:NVIC_InitStruct->NVIC_IRQChannel & (uint8_t)0x1F
就是看该中断在ISER[0]、ISER[1]数组的第几位。相当于NVIC_InitStruct->NVIC_IRQChannel%32。
如NVIC_InitStruct->NVIC_IRQChannel=59时(本博板子最后一个中断)
59=0x111011
NVIC_InitStruct->NVIC_IRQChannel & (uint8_t)0x1F=0x111011 & 0x1F=0x11011=27
执行该函数后 将ICER[1]寄存器的为27置1,使能该中断。
以上是关于STM32F103五分钟入门系列NVIC中断优先级管理的主要内容,如果未能解决你的问题,请参考以下文章
STM32F103五分钟入门系列SysTick滴答定时器+SysTick中断实现跑马灯
STM32F103五分钟入门系列(十五)输入捕获(精雕细琢-.-)
STM32F103五分钟入门系列(十六)输入捕获(精雕细琢-.-)