跳转至

嵌套向量中断控制器NVIC

{NVIC}的全称是{Nested vectored interrupt controller},即嵌套向量中断控制器。

基础知识

对于Arm Star内核的MCU,每个中断的优先级都是用{NVIC->IPR}寄存器中的8位来设置的。8位的话就可以设置256级中断,实际中用不了这么多,而SWM341的{__NVIC_PRIO_BIT}设置为3,只使用了8位中的高三位[7:5],低五位取零,这样只能表示8级中断嵌套。

对于NVIC,有个重要的知识点就是优先级分组:组优先级和子优先级。

组优先级和子优先级共同使用{NVIC->IPR}的8bit,如何分配由{SCB->AIRCR.PRIGROUP}进行控制,可以通过函数{NVIC_SetPriorityGrouping}进行设置。

中断的总优先级通过函数{NVIC_EncodePriority}计算得出,通过函数{NVIC_SetPriority}进行设置。

SWM341只使用了{NVIC->IPR}寄存器的高三位[7:5]。故实际的组优先级和子优先级如下表所示。

AIRCR.PRIGROUP值 组优先级 子优先级 实际组优先级 实际子优先级
0 [7:1] [0] [7:5] -
1 [7:2] [1:0] [7:5] -
2 [7:3] [2:0] [7:5] -
3 [7:4] [3:0] [7:5] -
4 [7:5] [4:0] [7:5] -
5 [7:6] [5:0] [7:6] [5]
6 [7] [6:0] [7] [6:5]
7 - [7:0] - [7:5]

从上面的表格可以看出,SWM341支持4种优先级分组。系统上电复位后,默认使用的是优先级分组0,也就是只有组优先级,没有子优先级。关于这个组优先级和子优先级有几点一定要说清楚。

  • 具有高组优先级的中断可以在具有低组优先级的中断服务程序执行过程中被响应,即中断嵌套,或者说高组优先级的中断可以抢占低组优先级的中断的执行。

  • 在组优先级相同的情况下,有几个子优先级不同的中断同时到来,那么高子优先级的中断优先被响应。

  • 在组优先级相同的情况下,如果有低子优先级中断正在执行,高子优先级的中断要等待已被响应的低子优先级中断执行结束后才能得到响应,即子优先级不支持中断嵌套。

  • Reset、NMI、Hard Fault 优先级为负数,高于普通中断优先级,且优先级不可配置。

  • 还有一个比较纠结的问题,就是系统中断(比如:PendSV,SVC,SysTick)是不是一定比外部中断(比如 SPI,USART)要高?答案:不是的,它们是在同一个 NVIC 下面设置的。

掌握了这些基础知识基本就够用了。另外特别注意一点,{配置组优先级和子优先级,它们合并成的3bit数字的数值越小,优先级越高},这一点千万不要搞错了。

常用函数

SWM341.c
1
#define __NVIC_PRIO_BITS          3U        /* Number of Bits used for Priority Levels */
core_star.h
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
__STATIC_INLINE void __NVIC_SetPriorityGrouping(uint32_t PriorityGroup)
{
  uint32_t reg_value;
  uint32_t PriorityGroupTmp = (PriorityGroup & (uint32_t)0x07UL);             /* only values 0..7 are used          */

  reg_value  =  SCB->AIRCR;                                                   /* read old register configuration    */
  reg_value &= ~((uint32_t)(SCB_AIRCR_VECTKEY_Msk | SCB_AIRCR_PRIGROUP_Msk)); /* clear bits to change               */
  reg_value  =  (reg_value                                   |
                ((uint32_t)0x5FAUL << SCB_AIRCR_VECTKEY_Pos) |
                (PriorityGroupTmp << SCB_AIRCR_PRIGROUP_Pos)  );              /* Insert write key and priority group */
  SCB->AIRCR =  reg_value;
}

__STATIC_INLINE uint32_t NVIC_EncodePriority (uint32_t PriorityGroup, uint32_t PreemptPriority, uint32_t SubPriority)
{
  uint32_t PriorityGroupTmp = (PriorityGroup & (uint32_t)0x07UL);   /* only values 0..7 are used          */
  uint32_t PreemptPriorityBits;
  uint32_t SubPriorityBits;

  PreemptPriorityBits = ((7UL - PriorityGroupTmp) > (uint32_t)(__NVIC_PRIO_BITS)) ? (uint32_t)(__NVIC_PRIO_BITS) : (uint32_t)(7UL - PriorityGroupTmp);
  SubPriorityBits     = ((PriorityGroupTmp + (uint32_t)(__NVIC_PRIO_BITS)) < (uint32_t)7UL) ? (uint32_t)0UL : (uint32_t)((PriorityGroupTmp - 7UL) + (uint32_t)(__NVIC_PRIO_BITS));

  return (
           ((PreemptPriority & (uint32_t)((1UL << (PreemptPriorityBits)) - 1UL)) << SubPriorityBits) |
           ((SubPriority     & (uint32_t)((1UL << (SubPriorityBits    )) - 1UL)))
         );
}

__STATIC_INLINE void __NVIC_SetPriority(IRQn_Type IRQn, uint32_t priority)
{
  if ((int32_t)(IRQn) >= 0)
  {
    NVIC->IPR[((uint32_t)IRQn)]               = (uint8_t)((priority << (8U - __NVIC_PRIO_BITS)) & (uint32_t)0xFFUL);
  }
  else
  {
    SCB->SHPR[(((uint32_t)IRQn) & 0xFUL)-4UL] = (uint8_t)((priority << (8U - __NVIC_PRIO_BITS)) & (uint32_t)0xFFUL);
  }
}

用法示例

如果需要修改优先级分组,请使用{NVIC_SetPriorityGrouping},这个函数在系统中{只需要被调用一次,一旦分组确定就最好不要更改,否则容易造成程序分组混乱}。

设置中断优先级可以如下操作。

test.c
1
NVIC_SetPriority(IRQn, NVIC_EncodePriority (NVIC_GetPriorityGrouping(), PreemptPriority, SubPriority));