中断系统NVIC

LPC824的中断系统非常强大,要用好中断,就必须先了解LPC824的整个中断系统。下面来讨论一下NVIC中断系统。
在LPC8xx系列处理器中,有一个部分被称为“私有外设总线”(Private peripheral bus),它位于Memory map中地址为0xE0000000~0xE0100000的地方,包含有下表中的几个核心外设。
其中的NVIC(Nested Vectored Interrupt Contorller)就是中断系统,被称为“内嵌套向量中断控制器”。它与处理器内核紧密耦合,可实现低中断延迟及对新中断的有效处理。它具有以下特征:
拥有32路向量中断;每个中断的优先级均可编程设置为0~192(步长64),数值越小优先级越高,0级为最高优先级;支持电平和边沿触发中断;支持中断尾链;拥有一个外部不可屏蔽中断NMI。
NVIC所涉及到的寄存器如下表所示。
从表中可以看出,每个寄存器都是32位的结构,都具有可读可写的属性,复位值都为全0。其中ISER0寄存器是设置中断的使能,32位对应32路中断,值为1使能中断,值为0不使能中断。ICER0寄存器是设置中断的禁能,32位对应32路中断,值为1禁能中断,值为0不禁能。ISPR0寄存器是设置中断的挂起,32位对应32路中断,值为1挂起,值为0不挂起。ICPR0寄存器是清除中断的挂起,32位对应32路中断,值为1清除挂起,值为0不清除挂起。IPR0~7寄存器是设置中断优先级。
下面是NVIC寄存器组所对应的结构体形式(位于头文件core_cm0plus.h中)。
typedef struct
{
  __IOM uint32_t ISER[1U];     
        uint32_t RESERVED0[31U];
  __IOM uint32_t ICER[1U];     
        uint32_t RSERVED1[31U];
  __IOM uint32_t ISPR[1U];     
        uint32_t RESERVED2[31U];
  __IOM uint32_t ICPR[1U];     
        uint32_t RESERVED3[31U];
        uint32_t RESERVED4[64U];
  __IOM uint32_t IP[8U];       
}  NVIC_Type;
因NVIC寄存器组的基址为0xE000E100,所以要将基址指针强制转换为上述结构体,还必须要加上下面的定义。
#define SCS_BASE            (0xE000E000UL)
#define NVIC_BASE           (SCS_BASE + 0x0100UL)
#define NVIC                ((NVIC_Type *) NVIC_BASE )
接下来给出的是上面NVIC32位寄存器所对应的32路中断向量的中断源。
 
为了能描述上面的32路中断源,在C语言中运用了枚举类型,代码如下所示(位于头文件lpc82x.h中)。
typedef enum {
/*---Cortex-M0PLUS Processor Exceptions Numbers---*/
  Reset_IRQn                    = -15,
  NonMaskableInt_IRQn           = -14,
  HardFault_IRQn                = -13,
  SVCall_IRQn                   =  -5,
  DebugMonitor_IRQn             =  -4,
  PendSV_IRQn                   =  -2,
  SysTick_IRQn                  =  -1,
/*---LPC82x Specific Interrupt Numbers---*/
  SPI0_IRQn                     =   0,
  SPI1_IRQn                     =   1,
  UART0_IRQn                    =   3,
  UART1_IRQn                    =   4,
  UART2_IRQn                    =   5,
  I2C1_IRQn                     =   7,
  I2C0_IRQn                     =   8,
  SCT_IRQn                      =   9,
  MRT_IRQn                      =  10,
  CMP_IRQn                      =  11,
  WDT_IRQn                      =  12,
  BOD_IRQn                      =  13,
  FLASH_IRQn                    =  14,
  WKT_IRQn                      =  15,
  ADC_SEQA_IRQn                 =  16,
  ADC_SEQB_IRQn                 =  17,
  ADC_THCMP_IRQn                =  18,
  ADC_OVR_IRQn                  =  19,
  DMA_IRQn                      =  20,
  I2C2_IRQn                     =  21,
  I2C3_IRQn                     =  22,
  PIN_INT0_IRQn                 =  24,
  PIN_INT1_IRQn                 =  25,
  PIN_INT2_IRQn                 =  26,
  PIN_INT3_IRQn                 =  27,
  PIN_INT4_IRQn                 =  28,
  PIN_INT5_IRQn                 =  29,
  PIN_INT6_IRQn                 =  30,
  PIN_INT7_IRQn                 =  31
} IRQn_Type;
从上述代码中可以看出,除了32路中断源外,还加入了编号为负数的、优先级更高的7个中断源。这里先不进行说明,在后面用到时再来讨论。定义好上述代码后,就可以来写中断所需要的函数了。下面就是依据CMSIS规范所定义的13个中断操作函数(位于头文件core_cm0plus.h中)。 
1.允许某个中断
__STATIC_INLINE void __NVIC_EnableIRQ(IRQn_Type IRQn)
{
  if ((int32_t)(IRQn) >= 0)
  {
    NVIC->ISER[0U] = (uint32_t)(1UL << (((uint32_t)IRQn) & 0x1FUL));
  }
}
2.读取某个中断的使能状态
__STATIC_INLINE uint32_t __NVIC_GetEnableIRQ(IRQn_Type IRQn)
{
  if ((int32_t)(IRQn) >= 0)
  {
    return((uint32_t)(((NVIC->ISER[0U] & (1UL << (((uint32_t)IRQn) & 0x1FUL))) != 0UL) ? 1UL : 0UL));
  }
  else
  {
    return(0U);
  }
}
3.禁止某个中断
__STATIC_INLINE void __NVIC_DisableIRQ(IRQn_Type IRQn)
{
  if ((int32_t)(IRQn) >= 0)
  {
    NVIC->ICER[0U] = (uint32_t)(1UL << (((uint32_t)IRQn) & 0x1FUL));
    __DSB();
    __ISB();
  }
}
4.读取某个中断的挂起状态
__STATIC_INLINE uint32_t __NVIC_GetPendingIRQ(IRQn_Type IRQn)
{
  if ((int32_t)(IRQn) >= 0)
  {
    return((uint32_t)(((NVIC->ISPR[0U] & (1UL << (((uint32_t)IRQn) & 0x1FUL))) != 0UL) ? 1UL : 0UL));
  }
  else
  {
    return(0U);
  }
}
5.把某个中断的挂起状态设为1
__STATIC_INLINE void __NVIC_SetPendingIRQ(IRQn_Type IRQn)
{
  if ((int32_t)(IRQn) >= 0)
  {
    NVIC->ISPR[0U] = (uint32_t)(1UL << (((uint32_t)IRQn) & 0x1FUL));
  }
}
6.把某个中断的挂起状态清为0
__STATIC_INLINE void __NVIC_ClearPendingIRQ(IRQn_Type IRQn)
{
  if ((int32_t)(IRQn) >= 0)
  {
    NVIC->ICPR[0U] = (uint32_t)(1UL << (((uint32_t)IRQn) & 0x1FUL));
  }
}
7.把某个中断的可配置优先级设为1
__STATIC_INLINE void __NVIC_SetPriority(IRQn_Type IRQn, uint32_t priority)
{
  if ((int32_t)(IRQn) >= 0)
  {
    NVIC->IP[_IP_IDX(IRQn)]  = ((uint32_t)(NVIC->IP[_IP_IDX(IRQn)]  & ~(0xFFUL << _BIT_SHIFT(IRQn))) |
       (((priority << (8U - __NVIC_PRIO_BITS)) & (uint32_t)0xFFUL) << _BIT_SHIFT(IRQn)));
  }
  else
  {
    SCB->SHP[_SHP_IDX(IRQn)] = ((uint32_t)(SCB->SHP[_SHP_IDX(IRQn)] & ~(0xFFUL << _BIT_SHIFT(IRQn))) |
       (((priority << (8U - __NVIC_PRIO_BITS)) & (uint32_t)0xFFUL) << _BIT_SHIFT(IRQn)));
  }
}
8.读取某个中断的优先级
__STATIC_INLINE uint32_t __NVIC_GetPriority(IRQn_Type IRQn)
{
  if ((int32_t)(IRQn) >= 0)
  {
    return((uint32_t)(((NVIC->IP[ _IP_IDX(IRQn)] >> _BIT_SHIFT(IRQn) ) & (uint32_t)0xFFUL) >> (8U - __NVIC_PRIO_BITS)));
  }
  else
  {
    return((uint32_t)(((SCB->SHP[_SHP_IDX(IRQn)] >> _BIT_SHIFT(IRQn) ) & (uint32_t)0xFFUL) >> (8U - __NVIC_PRIO_BITS)));
  }
}
9.编码优先级
__STATIC_INLINE uint32_t NVIC_EncodePriority (uint32_t PriorityGroup, uint32_t PreemptPriority, uint32_t SubPriority)
{
  uint32_t PriorityGroupTmp = (PriorityGroup & (uint32_t)0x07UL);
  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)))
         );
}
10.解码优先级
__STATIC_INLINE void NVIC_DecodePriority (uint32_t Priority, uint32_t PriorityGroup, uint32_t* const pPreemptPriority, uint32_t* const pSubPriority)
{
  uint32_t PriorityGroupTmp = (PriorityGroup & (uint32_t)0x07UL);
  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));
  *pPreemptPriority = (Priority >> SubPriorityBits) & (uint32_t)((1UL << (PreemptPriorityBits)) - 1UL);
  *pSubPriority     = (Priority                   ) & (uint32_t)((1UL << (SubPriorityBits    )) - 1UL);
}
11.设置中断向量
__STATIC_INLINE void __NVIC_SetVector(IRQn_Type IRQn, uint32_t vector)
{
#if defined (__VTOR_PRESENT) && (__VTOR_PRESENT == 1U)
  uint32_t *vectors = (uint32_t *)SCB->VTOR;
#else
    uint32_t *vectors = (uint32_t *)0x0U;
#endif
  vectors[(int32_t)IRQn + NVIC_USER_IRQ_OFFSET] = vector;
}
12.读取中断向量
__STATIC_INLINE uint32_t __NVIC_GetVector(IRQn_Type IRQn)
{
#if defined (__VTOR_PRESENT) && (__VTOR_PRESENT == 1U)
  uint32_t *vectors = (uint32_t *)SCB->VTOR;
#else
  uint32_t *vectors = (uint32_t *)0x0U;
#endif
  return vectors[(int32_t)IRQn + NVIC_USER_IRQ_OFFSET];
}
13.复位NVIC
__NO_RETURN __STATIC_INLINE void __NVIC_SystemReset(void)
{
  __DSB();
  SCB->AIRCR  = ((0x5FAUL << SCB_AIRCR_VECTKEY_Pos) | SCB_AIRCR_SYSRESETREQ_Msk);
  __DSB();
  for(;;)
  {
    __NOP();
  }
}
在上述函数中有几点要说明一下,一是数组的引用其取值只能是0(即第一个元素),这是因为在结构体定义中只定义了一个数组元素,且由于需要利用数组的地址连续性来对映CPU物理地址,所以也不能将其定义为一个普通变量;二是关键字“__STATIC_INLINE”在头文件cmsis_armcc.h中已做了宏定义“#define __STATIC_INLINE static __inline”,__inline是通知编译器其后面的函数为内联形式;三是中断源IRQn要与0x1F“与”一下,是为了屏蔽高27位的值,因为中断源的最大值只到31,所以只用了32位中的低5位(31的二进制是11111,十六进制是0x1F);四是在函数的参数中,由于引入了枚举类型,所以可以在调用函数的时候,在参数部分可直接使用枚举中的名称,这样就可以省去记忆32个中断源在32位寄存器中的对应位置,便于书写和阅读。例如,要开启端口0的外部中断,执行程序“NVIC_EnableIRQ(PIN_INT0_IRQn)”即可。
上述就是LPC824中的整个中断系统,即“内嵌套向量中断控制器”。可以看出,它控制着整个处理器32路中断源的使能与挂起等13个动作,功能确实非常强大。
 
posted @ 2020-06-15 14:21  fxzq  阅读(1548)  评论(0编辑  收藏  举报