以古代官职角度分析stm32-NVIC中断优先级管理

讲在前面的话:

博主在学习正点原子这一章节的时候很是懵,感觉摸不到门路,于是在网上找了一些相关的视频讲解,将他们的内容整合了一下,并尽量用易懂的话讲解。(本文基于stm32f103ZET6)

一、预备知识 

  • CM3 内核支持256个中断,其中包含了 16 个内核中断和 240 个外部中断,并且具有 256 级的可编程中断设置。
  •  STM32 并没有使用 CM3 内核的全部东西,而是只用了它的一部分。
  • STM32 有84个中断,包括 16 个内核中断和 68 个可屏蔽中断,具有 16 级可编程的中断优先级。
  • 我们常用这68 个可屏蔽中断,在 STM32F103 系列上面,又只有 60 个(在 107 系列才有 68 个)。
  • 所以在此我们就只针对 STM32F103 系列这 60 个可屏蔽中断进行介绍。

     学过51单片机的我们都知道他只有定时器中断、外部中断、串口中断,一般只有五个基本中断源。

    而stm32里面有60个中断,这么多的中断我们如何管理呢?

    所以这里就需要NVIC中断优先级分组。

     二、NVIC中断优先级分组

    NVIC(Nested vectoredinterrupt controller):嵌套向量中断控制器。

    那它是如何管理中断的呢?

    首先你想想看,你手底下有60号人,如果是你你打算怎么管理?

    那当然是分而治之啊!拉拢一帮人给予高地位(官职),让他们帮你遏制另一些人。

    换到stm32里面分配地位的就是IP[240]。

    IP[240](Interrupt Priority Registers)是一个中断优先级控制的寄存器组。这个寄存器组相当重要!毕竟是分配地位的,STM32 的中断分组就与这个寄存器组有关。

    IP 寄存器组由 240 个 8bit 的寄存器组成,每个可屏蔽中断占用 8bit,这样总共可以表示 240 个可屏蔽中断,而本文 STM32 只用到 了其中的前 60 个。

    IP[59]~IP[0]分别对应中断 59~0。而每个可屏蔽中断占用的 8bit 并没有全部使用,而是只用了高 4 位,这高4位是给予中断地位的关键。

    好,现在这 4 位开始设置官职,它把中断分为不同品级(抢占优先级),同一品级又划分了不同品阶(子优先级)。看官职先看你的品级(抢占优先级在前),品级一样看你的品阶(子优先级在后)。

    好了,现在官职倒是定好了,那么如何分配呢?

    前面说了高四位是给予官职的关键,所以这两个优先级各占几个位又要根据 SCB->AIRCR 中的中断分组设置来决定。

    STM32 将中断分为 5 个组,组 0~4。该分组的设置是由SCB->AIRCR 寄存器的 bit10~8 来定义的。至于这个寄存器到底如何操作,目前我们先不用关心。

    这个响应优先级就是子优先级。

    抢占优先级和子优先级有什么区别呢? 

    前面我们提到过,我们把抢占优先级称为官职的品级,响应优先级称为品阶。

    官职品级和品阶的概念不用我多说了吧。

    抢占优先级和响应优先级有0~4, 在官职里面对于抢占优先级,我们称它有零品~四品,在官职里面数字越小地位越高,所以有一品大员的称呼。当然实际上是没有零品的,这是我造的,这里0最小,所以它地位最高,零品大员!

    对于响应优先级,我们称它有零品阶~四品阶,也是数字越小地位越高。

    高优先级的抢占优先级可以打断正在进行的低抢占优先级中断;

    (因为我品级高,地位高,可以打断你,先做我的事)

    抢占优先级相同的中断,高响应优先级不可以打断低响应优先级的中断;

    (咱俩品级一样,虽然你的品阶比我高,但是咱俩的级别一样,你好歹不能直接打断我做事,这不是不给我面子吗?我做完后你才能做事) 

    抢占优先级相同的中断,当两个中断同时发生的情况下,哪个响应优先级高,哪个先执行;

     (咱们品级一样,级别相同,现在咱俩同时要做一件事,你的品阶高,地位也就比我高了一点,不是我怂,只是客气还是你先做吧,你做完我再做)

     如果两个中断的抢占优先级和响应优先级都是一样的话,则看哪个中断先发生就先执行。

     (品级一样,品阶一样,你先接手,那你先做事)

     好,现在分析图中的意思。组 0~4意思就是这高四位中有几位可以设置抢占优先级。比如:

    组0:这四位全部只能设置响应优先级;

    组1:这四位的高一位可以设置抢占优先级,剩下三位可以设置响应优先级。

    组2:这四位高两位可以设置抢占优先级,剩下两位可以设置响应优先级。

    组4:这四位全部用来设置抢占优先级。

    看个例子:

    假定设置中断优先级组为2(高两位设置抢占优先级,后两位设置响应优先级),然后设置

    中断3(RTC中断)的抢占优先级为2(高两位设置为1 0),响应优先级为1(低两位设置为0 1);

    中断6(外部中断0)的抢占优先级为3(高两位设置为1 1),响应优先级为0(低两位设置为0 0);

    中断7(外部中断1)的抢占优先级为2(高两位设置为1 0),响应优先级为0(低两位设置为0  0)。

    结合前面所学
    ,这3个中断的优先级顺序为:中断7>中断3>中断6。

    请注意: 

    一般情况下,系统代码执行过程中,只设置一次中断优先级分组,比如分组2,设置好分组之后一般不会再改变分组。随意改变分组会导致中断管理混乱,程序出现意想不到的执行结果。 

    三、相关配置函数

    3.1 中断优先级分组函数

     中断优先级分组函数:

    void NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup);
    

    看其命名NVIC Priority(优先级) Group(组) Config(配置)

    转入他的释义看:

    void NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup)
    {
      assert_param(IS_NVIC_PRIORITY_GROUP(NVIC_PriorityGroup));
      SCB->AIRCR = AIRCR_VECTKEY_MASK | NVIC_PriorityGroup;
    }
    

    可以看到有配置SCB->AIRCR 寄存器的语句。

    这个中断优先级分组函数括号里面可以填上自己想要的中断分组,比如组2

    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
    

    分组设置好之后如何设置单个中断的抢占优先级和响应优先级呢?

    这其中涉及很多寄存器,先一起看看吧。

    3.2相关寄存器

    中断设置相关寄存器:

    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;  

    __IO uint8_t  IP[240]; //中断优先级控制的寄存器组

    __IO uint32_t ISER[8]; //中断使能寄存器组

    __IO uint32_t ICER[8]; //中断失能寄存器组

    __IO uint32_t ISPR[8]; //中断挂起寄存器组

    __IO uint32_t ICPR[8]; //中断解挂寄存器组

    __IO uint32_t IABR[8]; //中断激活标志位寄存器组

    先眼熟一下这些寄存器,然后我们再来说怎么设置。

    其实在目录二中已经提到过,就是IP[240] (Interrupt Priority Registers)

    中断优先级控制的寄存器组:IP[240]

     他有240个8位寄存器,每个中断使用一个寄存器来确定优先级。STM32F10x系列一共60个可屏蔽中断,所以只使用IP[59]~IP[0]。IP[59]~IP[0]分别对应中断 59~0,而每个可屏蔽中断占用的 8bit 并没有全部使用,而是只用了高 4 位。

    忘记的小伙伴可以往前翻一下。

    在库函数中设置抢占和响应优先级是用这个函数:

    void NVIC_Init(NVIC_InitTypeDef* NVIC_InitStruct);
    

    这个函数有一个指针类型成员变量,现在去看看NVIC_InitTypeDef*的释义:

    typedef struct
    {
      uint8_t NVIC_IRQChannel;                    
                                                      
      uint8_t NVIC_IRQChannelPreemptionPriority;  
    
      uint8_t NVIC_IRQChannelSubPriority;         
    
      FunctionalState NVIC_IRQChannelCmd;        
    } NVIC_InitTypeDef;

    这里面有4个成员变量:

    NVIC_IRQChannel:定义初始化的是哪个中断,这个我们可以在 stm32f10x.h 中找到 每个中断对应的名字。例如 USART1_IRQn。

    NVIC_IRQChannelPreemptionPriority:定义这个中断的抢占优先级别。

    NVIC_IRQChannelSubPriority:定义这个中断的响应优先级别。

    NVIC_IRQChannelCmd:该中断是否使能。

    比如我们要使能串口 1 的中断,同时设置抢占优先级为 1,子优先级位 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[8]

    ISER[8]: 中断使能寄存器组

    它是32位寄存器,每个位控制一个中断的使能。STM32F10x只有60个可屏蔽中断,所以只使用了

    其中的ISER[0]和ISER[1]。ISER[0]的bit0~bit31分别对应中断0~31,ISER[1]的bit0~27对应中断32~59。

    他的作用是使能中断,哎,这个作用是不是在哪里听过?

    再看看他用的函数也是:

    void NVIC_Init(NVIC_InitTypeDef* NVIC_InitStruct);

    所以它对应的是指针类型成员变量的第四个成员变量使能:

    FunctionalState NVIC_IRQChannelCmd

     所以配置这个成员变量就相当于配置这个寄存器

    对应的还有中断失能寄存器组。

    中断失能寄存器组:ICER[8]

    ICER[8]:中断失能寄存器组

    他的作用是用来失能中断。

    他也是32位寄存器,每个位控制一个中断的失能。STM32F10x只有60个可屏蔽中断,所以只使用

    了其中的ICER[0]和ICER[1]。ICER[0]的bit0~bit31分别对应中断0~31。ICER[1]的bit0~27对应中断

    32~59,配置方法跟ISER一样。也使用函数:

    void NVIC_Init(NVIC_InitTypeDef* NVIC_InitStruct);
    

    配置第四个成员变量失能,就是配置这个寄存器。 

    下面几个寄存器用的少,混个眼熟先:

    中断挂起控制寄存器组:ISPR[8]

    作用:用来挂起中断 

     中断解挂控制寄存器组:ICPR[8]

    作用:用来解挂中断 

    中断激活标志位寄存器组:IABR [8]

    作用:只读,通过它可以知道当前在执行的中断是哪一个,如果对应位为1,说明该中断正在执行。 

    3.3总结步骤

    先设置中断优先级分组,比如设置为组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 寄存器

    最后如果需要挂起/解挂,查看中断当前激活状态,分别调用相关函数即可。

    这个我们先不做探究。

    待补充,待更新…

    物联沃分享整理
    物联沃-IOTWORD物联网 » 以古代官职角度分析stm32-NVIC中断优先级管理

    发表评论