STM32 中断非常强大,每个外设都可以产生中断,中断也是STM32非常重要的一个内容。
NVIC:嵌套向量中断控制器,属于内核外设,管理着包括内核和片上所有外设的中断相关的功能。
ARM cortex_m3 内核支持 256 个中断(16 个内核+240 外部)和可编程 256 级中断优先级的设置,与其相关的中断控制和中断优先级控制寄存器(NVIC、SYSTICK 等)也都属于cortex_m3 内核的部分。STM32 采用了 cortex_m3 内核,所以这部分仍旧保留使用,但 STM32并没有使用 cortex_m3 内核全部的东西(如内存保护单元 MPU 等),因此它的 NVIC 是cortex_m3 内核的 NVIC 的子集。
这篇文章主要记录我学习中断时关于NVIC的两个问题。
问题1:NVIC_Type和NVIC_InitTypeDef结构体的关系?
NVIC寄存器的定义是在core_cm3.h中:
typedef struct
{
__IO uint32_t ISER[8]; /*!< Offset: 0x000 Interrupt Set Enable Register 中断使能寄存器 */
uint32_t RESERVED0[24];
__IO uint32_t ICER[8]; /*!< Offset: 0x080 Interrupt Clear Enable Register 中断清除寄存器 */
uint32_t RSERVED1[24];
__IO uint32_t ISPR[8]; /*!< Offset: 0x100 Interrupt Set Pending Register 中断使能悬起寄存器 */
uint32_t RESERVED2[24];
__IO uint32_t ICPR[8]; /*!< Offset: 0x180 Interrupt Clear Pending Register 中断清除悬起寄存器 */
uint32_t RESERVED3[24];
__IO uint32_t IABR[8]; /*!< Offset: 0x200 Interrupt Active bit Register 中断有效位寄存器 */
uint32_t RESERVED4[56];
__IO uint8_t IP[240]; /*!< Offset: 0x300 Interrupt Priority Register (8Bit wide)中断有效位寄存器 */
uint32_t RESERVED5[644];
__O uint32_t STIR; /*!< Offset: 0xE00 Software Trigger Interrupt Register 软件触发中断寄存器 */
} NVIC_Type;
在配置中断的时候我们一般只用 ISER、ICER 和 IP 这三个寄存器,ISER 用来使能中断,ICER用来失能中断,IP 用来设置中断优先级。而初始化这个结构体的函数就是void NVIC_Init(NVIC_InitTypeDef* NVIC_InitStruct),在misc.c中定义,比较复杂,有兴趣可以看看:
/**
* @brief Initializes the NVIC peripheral according to the specified
* parameters in the NVIC_InitStruct.
* @param NVIC_InitStruct: pointer to a NVIC_InitTypeDef structure that contains
* the configuration information for the specified NVIC peripheral.
* @retval None
*/
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);
}
}
NVIC初始化函数是通过传递的参数NVIC_InitTypeDef* NVIC_InitStruct来初始化NVIC_Type结构体的,而对NVIC_InitTypeDef结构体的初始化就要看具体情况,比如说普通的按键外部中断等等。
NVIC_InitTypeDef结构体结构体定义在misc.h中:
typedef struct
{
uint8_t NVIC_IRQChannel; /*!< Specifies the IRQ channel to be enabled or disabled.
This parameter can be a value of @ref IRQn_Type
(For the complete STM32 Devices IRQ Channels list, please
refer to stm32f10x.h file) */
uint8_t NVIC_IRQChannelPreemptionPriority; /*!< Specifies the pre-emption priority for the IRQ channel
specified in NVIC_IRQChannel. This parameter can be a value
between 0 and 15 as described in the table @ref NVIC_Priority_Table */
uint8_t NVIC_IRQChannelSubPriority; /*!< Specifies the subpriority level for the IRQ channel specified
in NVIC_IRQChannel. This parameter can be a value
between 0 and 15 as described in the table @ref NVIC_Priority_Table */
FunctionalState NVIC_IRQChannelCmd; /*!< Specifies whether the IRQ channel defined in NVIC_IRQChannel
will be enabled or disabled.
This parameter can be set either to ENABLE or DISABLE */
} NVIC_InitTypeDef;
NVIC_Type和NVIC_InitTypeDef这两个结构体就是通过NVIC_Init函数来联系起来的。即NVIC_Type和NVIC_InitTypeDef的关系就是:先在具体的外部中断里将NVIC_InitTypeDef结构体初始化完成,然后通过NVIC_Init函数,把NVIC_InitTypeDef作为参数,将NVIC_Type结构体初始化。
问题2:关于中断优先级和中断优先级分组的问题,这两个有什么区别?
中断优先级分组的设定是在NVIC_Type和NVIC_InitTypeDef结构体初始化的前面。
中断优先级的设定是NVIC->IPRx寄存器(共8bit,只使用高4bit):
相关函数:NVIC_SetPriority(IRQn_Type IRQn, uint32_t priority) // core_cm3.h 1586行
/**
* @brief Set the priority for an interrupt
*
* @param IRQn The number of the interrupt for set priority
* @param priority The priority to set
*
* Set the priority for the specified interrupt. The interrupt
* number can be positive to specify an external (device specific)
* interrupt, or negative to specify an internal (core) interrupt.
*
* Note: The priority cannot be set for every core interrupt.
*/
static __INLINE void NVIC_SetPriority(IRQn_Type IRQn, uint32_t priority)
{
if(IRQn < 0) {
SCB->SHP[((uint32_t)(IRQn) & 0xF)-4] = ((priority << (8 - __NVIC_PRIO_BITS)) & 0xff); } /* set Priority for Cortex-M3 System Interrupts */
else {
NVIC->IP[(uint32_t)(IRQn)] = ((priority << (8 - __NVIC_PRIO_BITS)) & 0xff); } /* set Priority for device specific Interrupts */
}
即就是上面函数中的NVIC->IP。
中断优先级分组的设定是SCB->AIRCR:PRIGROUP[10:8]
优先级分组是由3个bit来控制,所以可以有2 ^ 3 = 8 个分组,但是STM32只用了五组(即0~4),如下图:
相关函数:void NVIC_PriorityGroupConfig(uint32_t NVIC_PriorityGroup)
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;
}
所以中断优先级和中断优先级分组有什么区别或关系呢?SCB->AIRCR:PRIGROUP[10:8]设置的是中断优先级分组,也就是它的值是NVIC_PriorityGroup_0、NVIC_PriorityGroup_1、NVIC_PriorityGroup_2、NVIC_PriorityGroup_3、NVIC_PriorityGroup_4这五个值,而NVIC->IP设置的是分组中主优先级和子优先级的值,STM32使用高四位,根据不同的分组,主优先级和子优先级占用这四位中不同的位数。
要注意的就是:中断优先级的设置和中断优先级分组的设置是在不同寄存器中设置的,前者是NVIC->IPRx,后者是SCB->AIRCR。
以上就是我遇到的两个问题及其答案,如有错误,欢迎指正。
中断编程的顺序有四个步骤:
①使能中断请求;
②配置中断优先级分组;
③配置NVIC寄存器,初始化NVIC_InitTypeDef;
④编写中断服务函数。
第一个步骤,使能中断请求是配置外设相应的寄存器,配置好外设的中断使能寄存器后,外设就可以触发中断,即可以向CPU发送中断请求,但是要让CPU接受中断请求,还需要配置NVIC(嵌套向量中断控制器)的中断使能位,这个使能位是NVIC结构体的一个成员,即上面结构体中的__IO uint32_t ISER[8]。可以说,外设的中断请求使能是小门,而NVIC(嵌套向量中断控制器)的中断使能是大门,一个大门对应多个小门。
第二个步骤,配置中断优先级分组,为什么要配置中断优先级分组呢?因为STM32有多个外设,所以就有可能产生多个中断请求,这时候就需要解决先处理哪个中断请求这个问题,所以就要配置中断优先级分组,优先级高的先处理。
第三个步骤,当中断优先级分组配置完了,就要配置具体的外部中断了,除了要初始化NVIC_InitTypeDef结构体,还要配置EXTI_InitTypeDef结构体。
第四个步骤,中断服务函数也是在具体的外部中断中配置的,比如EXTI0_IRQHandler函数,具体问题具体分析,这里不再多说。