STM32F103五分钟入门系列NVIC中断优先级管理

Posted 自信且爱笑‘

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了STM32F103五分钟入门系列NVIC中断优先级管理相关的知识,希望对你有一定的参考价值。

学习板:STM32F103ZET6

强推系列:
STM32F103五分钟入门系列(一)跑马灯(库函数+寄存器)+加编程模板+GPIO总结

STM32F103五分钟入门系列(二)GPIO的七大寄存器+GPIOx_LCKR作用和配置

STM32F103五分钟入门系列(三)GPIO的常用库函数使用方法总结+一个网络上的误区

参考:

51单片机(四)定时器中断(+数码管—24小时制钟表)

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]优先级
01110位抢占优先级,4位响应优先级
11101位抢占优先级,3位响应优先级
21012位抢占优先级,2位响应优先级
31003位抢占优先级,1位响应优先级
40114位抢占优先级,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五分钟入门系列外部中断大汇总

STM32F103五分钟入门系列SysTick滴答定时器+SysTick中断实现跑马灯

STM32F103五分钟入门系列(十五)输入捕获(精雕细琢-.-)

STM32F103五分钟入门系列(十六)输入捕获(精雕细琢-.-)

STM32F103五分钟入门系列(十五)输出比较(PWM输出)+各类测试

STM32F103五分钟入门系列SystemInit()函数SetSysClock()函数