STM32 HAL库中NVIC初始化函数解析与梳理

使用的是stm32f407vg,代码来源stm32cubemx。

已经配置了中断的是GPIOD_pin6和TIM2。

 这个是STM32CubeMX里面NVIC的控制面板;其中最上面priority grope是优先级组,我设置的是4位抢占优先级,0位响应优先级,就和以前刚学中断时中断嵌套那样。

下面是有哪些中断源,勾上之后,在最下面选择抢占优先级是几。

 接上一节,GPIO初始化函数中还有最后两句关于中断的配置。

/* EXTI interrupt init*/
  HAL_NVIC_SetPriority(EXTI9_5_IRQn, 2, 0);
  HAL_NVIC_EnableIRQ(EXTI9_5_IRQn);

同样,在TIM2初始化中也有这样的初始化:

    /* TIM2 interrupt Init */
    HAL_NVIC_SetPriority(TIM2_IRQn, 1, 0);
    HAL_NVIC_EnableIRQ(TIM2_IRQn);

一、 函数的分析

1.1 中断优先级组设置

首先是主函数会调用 HAL_Init();

HAL_StatusTypeDef HAL_Init(void)
{
  /* Configure Flash prefetch, Instruction cache, Data cache */ 
#if (INSTRUCTION_CACHE_ENABLE != 0U)
  __HAL_FLASH_INSTRUCTION_CACHE_ENABLE();
#endif /* INSTRUCTION_CACHE_ENABLE */

#if (DATA_CACHE_ENABLE != 0U)
  __HAL_FLASH_DATA_CACHE_ENABLE();
#endif /* DATA_CACHE_ENABLE */

#if (PREFETCH_ENABLE != 0U)
  __HAL_FLASH_PREFETCH_BUFFER_ENABLE();
#endif /* PREFETCH_ENABLE */

  /* Set Interrupt Group Priority */
  HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4);

  /* Use systick as time base source and configure 1ms tick (default clock after Reset is HSI) */
  HAL_InitTick(TICK_INT_PRIORITY);

  /* Init the low level hardware */
  HAL_MspInit();

  /* Return function status */
  return HAL_OK;
}

这两个函数一个是初始化中断优先级组的配置,一个是设置了优先级组。

 void HAL_NVIC_SetPriorityGrouping(uint32_t PriorityGroup)
{
  /* Check the parameters */
  assert_param(IS_NVIC_PRIORITY_GROUP(PriorityGroup));
  
  /* Set the PRIGROUP[10:8] bits according to the PriorityGroup parameter value */
  NVIC_SetPriorityGrouping(PriorityGroup);
}

  #define NVIC_SetPriorityGrouping    __NVIC_SetPriorityGrouping

__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;
}


#define SCB_AIRCR_VECTKEY_Pos              16U   /*!< SCB AIRCR: VECTKEY Position */
#define SCB_AIRCR_VECTKEY_Msk       (0xFFFFUL << SCB_AIRCR_VECTKEY_Pos)                                                                            /*!< SCB AIRCR: VECTKEY Mask */

#define SCB_AIRCR_PRIGROUP_Pos             8U /*!< SCB AIRCR: PRIGROUP Position */
#define SCB_AIRCR_PRIGROUP_Msk             (7UL << SCB_AIRCR_PRIGROUP_Pos)                /*!< SCB AIRCR: PRIGROUP Mask */

这个函数是对SCB_AIRCR寄存器进行操作;

先是把SCB_AIRCR里面的数据读出来,再把对应位全置0,再和想要的参数相或就写好了。

 描述里写道,要给bit[31:16]位写入0x5fa,否则写入将会被忽略;

给bit[10:8]位写入就是优先级组的配置值;bit2是系统复位的,没有用到。

 #define NVIC_PRIORITYGROUP_0         0x00000007U

                                                /*!< 0 bits for pre-emption priority4 bits for subpriority */
#define NVIC_PRIORITYGROUP_1         0x00000006U

                                                /*!< 1 bits for pre-emption priority3 bits for subpriority */
#define NVIC_PRIORITYGROUP_2         0x00000005U

                                                /*!< 2 bits for pre-emption priority 2 bits for subpriority */
#define NVIC_PRIORITYGROUP_3         0x00000004U

                                                /*!< 3 bits for pre-emption priority1 bits for subpriority */
#define NVIC_PRIORITYGROUP_4         0x00000003U /*!< 4 bits for pre-emption priority
以上是可以写入的实参。可以看出HAL_Init()里面第一个调用此函数是把GROUP_4写入,算是一次初始化。

 void HAL_MspInit(void)
{
  /* USER CODE BEGIN MspInit 0 */

  /* USER CODE END MspInit 0 */

  __HAL_RCC_SYSCFG_CLK_ENABLE();
  __HAL_RCC_PWR_CLK_ENABLE();

  HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_3);

  /* System interrupt init*/

  /* USER CODE BEGIN MspInit 1 */

  /* USER CODE END MspInit 1 */
}


#define __HAL_RCC_PWR_CLK_ENABLE()     do { \
                                    __IO uint32_t tmpreg = 0x00U; \
                                    SET_BIT(RCC->APB1ENR, RCC_APB1ENR_PWREN);\
                                     /* Delay after an RCC peripheral clock enabling */ \
                                    tmpreg = READ_BIT(RCC->APB1ENR, RCC_APB1ENR_PWREN);\
                                    UNUSED(tmpreg); \
                                          } while(0U)

这里我把配置改了一下,生成了配置中断优先级组的函数,当默认时这儿没有这个函数,

原理和上面一样。

这儿也使能了系统配置控制器时钟,从休眠模式下使能这个时钟,前面也见到了,在上一节2.2.2外部中断的配置小节里面解释了一下,感觉和f4芯片的休眠模式有关。

此处还使能了RCC_APB1ENR中PWREN位,使能电源接口时钟。

1.2  HAL_NVIC_SetPriority();

 void HAL_NVIC_SetPriority(IRQn_Type IRQn, uint32_t PreemptPriority, uint32_t SubPriority)
{ 
  uint32_t prioritygroup = 0x00U;
  
  /* Check the parameters */
  assert_param(IS_NVIC_SUB_PRIORITY(SubPriority));
  assert_param(IS_NVIC_PREEMPTION_PRIORITY(PreemptPriority));
  
  prioritygroup = NVIC_GetPriorityGrouping();
  
  NVIC_SetPriority(IRQn, NVIC_EncodePriority(prioritygroup, PreemptPriority, SubPriority));
}

1.2.1 NVIC_GetPriorityGrouping();

#define NVIC_GetPriorityGrouping    __NVIC_GetPriorityGrouping

__STATIC_INLINE uint32_t __NVIC_GetPriorityGrouping(void)
{
  return ((uint32_t)((SCB->AIRCR & SCB_AIRCR_PRIGROUP_Msk) >>                                                                                 SCB_AIRCR_PRIGROUP_Pos));
}


#define SCB_AIRCR_PRIGROUP_Pos              8U                                          

                                                                         /*!< SCB AIRCR: PRIGROUP Position */
#define SCB_AIRCR_PRIGROUP_Msk             (7UL << SCB_AIRCR_PRIGROUP_Pos)                                                                              /*!< SCB AIRCR: PRIGROUP Mask */

函数作用是得到\读取优先级组,前面可知,优先级组写入到SCB_AIRCR寄存器bin[10:8]所以把7左移8位再和原寄存器值相与,就只剩下这三位,其他清零,再右移8位得到三位数据。

 1.2.2 NVIC_EncodePriority(prioritygroup, PreemptPriority, SubPriority);

__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)))
         );
}


#define __NVIC_PRIO_BITS       4U /*!< STM32F4XX uses 4 Bits for the Priority Levels */
首先第一行是把优先级组的数[011 ~ 111]转换成3~7;

后面得到PreemptPriorityBits 和 SubPriorityBits 以GROUP_4为例

7 – 3 !> 4 则 PreemptPriorityBits = 7 – 3 = 4;PreemptPriorityBits = 4

4 + 4 !< 7 则  SubPriorityBits = (3 – 7) + 4 = 0 

这两个数就是写入到nvic寄存器里面,控制中断优先级为第几级。总共4位,GROUP_4把4位都用来做抢断优先级,那么共有16个优先级可以写入。如果是GROUP_3,则前三位写抢断优先级、后一位写响应优先级,有8个抢断优先级、2个响应优先级可以写。

最后一步操作就是把预设的抢断优先级和响应优先级的实参写到对应的位中,例如写入抢占优先级为2.

就是1 左移4位 得10000 ,减一得 ffff ,即15 和2相与得2,再左移0位。1左移0位再减一等于0 和响应优先级相与等于0最后写入的数据就是2.

1.2.3  NVIC_SetPriority(IRQn, NVIC_EncodePriority());

   #define NVIC_SetPriority            __NVIC_SetPriority

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


EXTI9_5_IRQn -> IRQn

EXTI9_5_IRQn = 23,  EXTI9_5_IRQn是一个枚举量。

就是给NVIC_IP[23]赋值,等于 2左移(8 – 4)位再和0xff相与。

也就是给IP[23]里面写入20000;

 IP[x]是用来控制中断优先级的,一共有240个中断源,即IP[0~239],每个元素有8位,那么一个寄存器有4个IP数组的元素,共用了60个寄存器,NVIC_IPR0~59。按照分配来说每个IP有8位控制,应该有2^8个,即256个优先级,但是设置了每个IP只有4位有效,所以只有16个优先级。

 每个优先级段只写入一个优先级数值,大小是0到255,数值越小,对应的中断优先级越高。处理器只实现bit[7:4],bit[3:0]读为0,并忽略写。

所以前面的操作要左移4位。

1.3 HAL_NVIC_EnableIRQ(EXTI9_5_IRQn); 

 void HAL_NVIC_EnableIRQ(IRQn_Type IRQn)
{
  /* Check the parameters */
  assert_param(IS_NVIC_DEVICE_IRQ(IRQn));
  
  /* Enable interrupt */
  NVIC_EnableIRQ(IRQn);
}

  #define NVIC_EnableIRQ              __NVIC_EnableIRQ

__STATIC_INLINE void __NVIC_EnableIRQ(IRQn_Type IRQn)
{
  if ((int32_t)(IRQn) >= 0)
  {
    NVIC->ISER[(((uint32_t)IRQn) >> 5UL)] = (uint32_t)(1UL << (((uint32_t)IRQn) & 0x1FUL));
  }
}

右移5位就是除以32,23 / 32 商0 ,即给NVIC_ISER[0]的对应位写入值。

 如果一个挂起的中断被启用,NVIC会根据它的优先级激活这个中断。如果中断未启用,断言其中断信号将中断状态更改为挂起,但NVIC不会激活中断,无论其优先级如何。

写操作时,0,无动作;1,使能中断。读操作时,0,中断未使能;1,中断使能。

 二、剩余函数和寄存器分析

 2.1 hal_cortex.c下和nvic相关的函数

void HAL_NVIC_SetPriorityGrouping(uint32_t PriorityGroup);  设置优先级组,形参是优先级组名。

void HAL_NVIC_SetPriority(IRQn_Type IRQn, uint32_t PreemptPriority, uint32_t SubPriority);                 设置优先级,形参是中断序号(中断源),抢断优先级,响应优先级;

void HAL_NVIC_EnableIRQ(IRQn_Type IRQn);         使能中断,形参是中断源;

void HAL_NVIC_DisableIRQ(IRQn_Type IRQn);         关闭中断,形参是中断源;

void HAL_NVIC_SystemReset(void);        nvic系统复位,实现方法是把默认值写入到SCB_AIRCR寄存器,前面提到bit[2]作用是复位。

2.2 core_cm4.h下和nvic相关的函数

 __STATIC_INLINE void __NVIC_SetPriorityGrouping(uint32_t PriorityGroup);

__STATIC_INLINE uint32_t __NVIC_GetPriorityGrouping(void);读优先级组,读AIRCR;

__STATIC_INLINE void __NVIC_EnableIRQ(IRQn_Type IRQn);

__STATIC_INLINE uint32_t __NVIC_GetEnableIRQ(IRQn_Type IRQn);

__STATIC_INLINE void __NVIC_DisableIRQ(IRQn_Type IRQn);

__STATIC_INLINE uint32_t __NVIC_GetPendingIRQ(IRQn_Type IRQn);读中断挂起信息。读取NVIC_ISPR寄存器对应位得到挂起信息。

__STATIC_INLINE void __NVIC_SetPendingIRQ(IRQn_Type IRQn);设置中断挂起,写NVIC_ISPR寄存器。

__STATIC_INLINE void __NVIC_ClearPendingIRQ(IRQn_Type IRQn);清除中断挂起,操作NVIC_ICPR寄存器。

__STATIC_INLINE uint32_t __NVIC_GetActive(IRQn_Type IRQn);获得中断活动信息,操作NVIC_IABR寄存器。

__STATIC_INLINE void __NVIC_SetPriority(IRQn_Type IRQn, uint32_t priority); 写入中断优先级,形参是中断源和中断优先级。

__STATIC_INLINE uint32_t __NVIC_GetPriority(IRQn_Type IRQn);

读取中断优先级,形参是中断源。

__STATIC_INLINE uint32_t NVIC_EncodePriority (uint32_t PriorityGroup, uint32_t PreemptPriority, uint32_t SubPriority);编码中断优先级,按分组把抢占优先级和响应优先级放到对应的位上。形参是中断优先级分组、抢占优先级、响应优先级数值。

__STATIC_INLINE void NVIC_DecodePriority (uint32_t Priority, uint32_t PriorityGroup, uint32_t* const pPreemptPriority, uint32_t* const pSubPriority);把编码好的优先级数值反编码。形参是中断优先级、中断优先级分组、抢占优先级的指针、响应优先级的指针。
__STATIC_INLINE void __NVIC_SetVector(IRQn_Type IRQn, uint32_t vector);设置中断向量表,读取SCB_VTOR,向量表基偏移字段。设置中断向量在基于中断向量表的SRAM中设置一个中断向量。中断号可以是正数来指定设备特定的中断,也可以是负数来指定处理器异常。VTOR必须重新定位到SRAM之前。按照函数内容来看,是给startup汇编函数中断向量表写入中断处理函数,正常使用不到。

__STATIC_INLINE uint32_t __NVIC_GetVector(IRQn_Type IRQn);读取中断向量表,得到中断处理函数的地址。

2.3 剩余相关寄存器

 NVIC_ICER和ISER写操作作用相反,写一时关闭中断。

 NVIC_ISPR控制中断是否挂起,写1时挂起。读寄存器时,0表示没有挂起;1表示挂起。

NVIC_ICPR寄存器写操作作用和ISPR相反,写1时取消中断挂起。读操作一样。

NVIC_IAPR寄存器数据是和活动性相关这一位读为1表示这个中断是活动的或者挂起中。

三、总结 

中断初始化这一部分内容不算多,但还是有点抽象。我知道有抢占优先级和响应优先级,通过一点点地分析这个函数,我知道大概控制哪些寄存器可以配置这些优先级。但是梳理完了以后还是有一些疑惑,比如nvic是怎么区分抢占优先级和响应优先级的?我的猜测是抢占优先级发射中断时,优先级高的直接打断别的中断,而响应优先级有个挂起的操作,等待中断结束以后再进行中断。这个表面的行为还是很好理解的,通过分组的那个寄存器配合,感觉nvic还是很厉害的,把中断管理得有条不紊。

在进行梳理的时候,最开始最困扰我的是,在nvic的初始化里面,没有对中断优先级级组的初始化。后来找了很久才发现在主函数里面调用了hal库的初始化,在hal库里面初始化了。还是对hal库不太熟悉。

物联沃分享整理
物联沃-IOTWORD物联网 » STM32 HAL库中NVIC初始化函数解析与梳理

发表评论