08. NVIC中断控制器
一、什么是中断
在主程序运行过程中,出现了特定事件,使得 CPU 暂停当前正在运行的程序,转而去处理这个事件,等这个事件处理完成之后,CPU 再回到刚才被打断的位置继续处理,则就是 中断。那个打断 CPU 执行的特定事件,我们一般称之为 中断源。被中断打断的位置我们称为 断点。处理特定实际事件的过程,我们称为执行 中断处理函数。
正在执行中断程序的时候,这个时候有可能被另外一个中断源给中断,CPU 转而执行另外一个中断源的中断处理函数,这叫 中断嵌套。中断 B 能够打断中断 A,要看它们的优先级,优先级高的中断可以打断优先级的中断。优先级低的中断无法打断优先级高的中断。
通过中断,我们可以实现实时控制,即在确定的时间内对相应的事件作出响应。通过中断,我们可以对检测出来的故障进行第一时间的处理。对于不知何时会来的数据,我们也可以借助中断来实现数据传输。
中断向量表 定义在启动文件中,它是一块固定的内存,以 4 字节对齐,存放每个中断服务函数程序的首地址。当中断发生的时候,CPU 会自动执行对象的中断服务函数。
中断可以高效处理紧急程序,不会一直占用 CPU 的资源;
二、NVIC简介
NVIC 即 嵌套向量中断控制器,全称 Nested vectored interrupt controller。它是内核的器件。M3/M4/M7 内核都是支持 256 个中断,其中包含了 16 个系统中断和 240 个外部中断,并且具有 256 级的可编程中断设置。然而芯片厂商一般不会把内核的这些资源全部用完,如 STM32F407 的系统中断有 10 个,外部中断有 82 个。
STM32F407 的系统中断有如下:
关于 82 个外部中断部分在《STM32F4xx 参考手册_V4(中文版).pdf》的 10.2 小节有详细的列表,这里就不列出来了。STM32F407 的中断向量表在 stm32f407xx.h 文件中被定义。
三、NVIC寄存器
NVIC 相关的寄存器定义了可以在 core_cm4.h 文件中找到。
typedef struct
{
__IOM uint32_t ISER[8U]; // 中断使能寄存器
uint32_t RESERVED0[24U];
__IOM uint32_t ICER[8U]; // 中断清除使能寄存器
uint32_t RSERVED1[24U];
__IOM uint32_t ISPR[8U]; // 中断使能挂起寄存器
uint32_t RESERVED2[24U];
__IOM uint32_t ICPR[8U]; // 中断解挂寄存器
uint32_t RESERVED3[24U];
__IOM uint32_t IABR[8U]; // 中断有效位寄存器
uint32_t RESERVED4[56U];
__IOM uint8_t IP[240U]; // 中断优先级寄存器(8Bit位宽)
uint32_t RESERVED5[644U];
__OM uint32_t STIR; // 中断触发中断寄存器
} NVIC_Type;
ISER[8]:ISER 全称是:Interrupt Set Enable Registers,这是一个 中断使能寄存器组。上面说了 CM4 内核支持 256 个中断,这里用 8 个 32 位寄存器来控制,每个位控制一个中断。但是 STM32F407 的可屏蔽中断最多只有 82 个,所以对我们来说,有用的就是两个(ISER[0 ~ 3]),总共可以表示 128 个中断。而 STM32F407 只用了其中的 82 个。ISER[0] 的 bit0 ~ 31 分别对应中断 0 ~ 31;ISER[1] 的 bit0 ~ 31 对应中断 32 ~ 63; ISER[2] 的 bit0 ~ 16 对应中断 64 ~ 81,这样总共 82 个中断就可以分别对应上了。你要使能某个中断,必须设置相应的 ISER 位为 1,使该中断被使能(这里仅仅是使能,还要配合中断分组、屏蔽、IO 口映射等设置才算是一个完整的中断设置)。
ICER[8]:全称是:Interrupt Clear Enable Registers,是一个 中断除能寄存器组。该寄存器组与 ISER 的作用恰好相反,是用来清除某个中断的使能的。其对应位的功能,也和 ISER 一样。这里要专门设置一个 ICER 来清除中断位,而不是向 ISER 写 0 来清除,是因为 NVIC 的这些寄存器都是写 1 有效的,写 0 是无效的。
ISPR[8]:全称是:Interrupt Set Pending Registers,是一个 中断使能挂起控制寄存器组。每个位对应的中断和 ISER 是一样的。通过置 1,可以将正在进行的中断挂起,而执行同级或更高级别的中断。写 0 是无效的。
ICPR[8]:全称是:Interrupt Clear Pending Registers,是一个 中断解挂控制寄存器组。其作用与 ISPR 相反,对应位也和 ISER 是一样的。通过设置 1,可以将挂起的中断解挂。写 0 无效。
IABR[8]:全称是:Interrupt Active Bit Registers,是一个 中断激活标志位寄存器组。对应位所代表的中断和 ISER 一样,如果为 1,则表示该位所对应的中断正在被执行。这是一个只读寄存器,通过它可以知道当前在执行的中断是哪一个。在中断执行完了由硬件自动清零。
IP[240]:全称是:Interrupt Priority Registers,是一个 中断优先级控制的寄存器组。IP 寄存器组由 240 个 8bit的寄存器组成,每个可屏蔽中断占用 8bit,这样总共可以表示 240 个可屏蔽中断。而 STM32F407 只用到了其中的 82 个。IP[81] ~ IP[0] 分别对应中断 81 ~ 0。而每个可屏蔽中断占用的 8bit 并没有全部使用,而是只用了高 4 位。这 4 位,又分为抢占优先级和子优先级。抢占优先级在前,子优先级在后。而这两个优先级各占几个位又要根据 SCB->AIRCR 中的中断分组设置来决定。
四、中断优先级
STM32 中的中断优先级可以分为:抢占式优先级 和 响应优先级,响应优先级也称子优先级,每个中断源都需要被指定这两种优先级。抢占式优先级和响应优先级的区别:
- 抢占优先级:抢占优先级高的中断可以打断正在执行的抢占优先级低的中断。
- 响应优先级:抢占优先级相同,响应优先级高的中断不能打断响应优先级低的中断。
当两个或者多个中断的抢占式优先级和响应优先级相同时,那么就遵循自然优先级,看中断向量表的中断排序,数值越小,优先级越高。
在 NVIC 中由寄存器 NVIC_IPR0 ~ NVIC_IPR59 共 60 个寄存器控制中断优先级,每个寄存器的每 8 位又分为一组,可以分 4 组,所以就有了 240 组宽度为 8bit 的中断优先级控制寄存器,原则上每个外部中断可配置的优先级为 0 ~ 255,数值越小,优先级越高。但是实际上 M3/M4/M7 芯片为了精简设计,只使用了高四位 [7:4],低四位取零,这样以至于最多只有 16 级中断嵌套,即 2^4=16。
对于 NVCI 的中断优先级分组:STM32F407 将中断分为 5 个组,组 0 ~ 4。该分组的设置是由 SCB->AIRCR 寄存器的 bit10 ~ 8 来定义的。具体的分配关系如下表所示:
例如优先级分组设置为 2,那么此时所有的 82 个中断,每个中断的中断优先寄存器的高四位中的高 2 位是抢占优先级,低 2 位是响应优先级。每个中断,你可以设置抢占优先级为 0 ~ 3,响应优先级为 0 ~ 3。抢占优先级的级别高于响应优先级。而数值越小所代表的优先级就越高。
然后设置中断 1 的抢占优先级为 2,响应优先级为 1。中断 2 的抢占优先级为 3,响应优先级为 0。中断 3 的抢占优先级为 2,响应优先级为 0。那么这 3 个中断的优先级顺序为:中断 1 > 中断 3 > 中断 2。中断 1 和中断 3 都可以打断中断 2 的中断。而中断 1 和中断 3 却不可以相互打断!
五、NVIC相关函数
ST 公司把 core_cm4.h 文件的 NVIC 相关函数封装到 stm32f4xx_hal_cortex.c 文件中。
5.1、设置中断优先级分组函数
HAL_NVIC_SetPriorityGrouping() 函数是设置中断优先级分组函数。其声明如下:
void HAL_NVIC_SetPriorityGrouping(uint32_t PriorityGroup);
其中,参数 PriorityGroup 是 中断优先级分组号,可以选择范围如下:
#define NVIC_PRIORITYGROUP_0 0x00000007U /*!< 0 bits for pre-emption priority
4 bits for subpriority */
#define NVIC_PRIORITYGROUP_1 0x00000006U /*!< 1 bits for pre-emption priority
3 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 priority
1 bits for subpriority */
#define NVIC_PRIORITYGROUP_4 0x00000003U /*!< 4 bits for pre-emption priority
0 bits for subpriority */
这个函数在一个工程里基本只调用一次,而且是在程序 HAL 库初始化函数里面已经被调用,后续就不会再调用了。因为当后续调用设置成不同的中断优先级分组时,有可能造成前面设置好的抢占优先级和响应优先级不匹配。如果调用了多次,则以最后一次为准。
5.2、设置中断优先级函数
HAL_NVIC_SetPriority() 函数是设置中断优先级函数。其声明如下:
void HAL_NVIC_SetPriority(IRQn_Type IRQn, uint32_t PreemptPriority, uint32_t SubPriority);
其中,参数 IRQn 是 中断号,可以选择范围:IRQn_Type 定义的枚举类型,定义在 stm32f407xx.h。
typedef enum
{
/****** Cortex-M4 Processor Exceptions Numbers ****************************************************************/
NonMaskableInt_IRQn = -14, /*!< 2 Non Maskable Interrupt */
MemoryManagement_IRQn = -12, /*!< 4 Cortex-M4 Memory Management Interrupt */
BusFault_IRQn = -11, /*!< 5 Cortex-M4 Bus Fault Interrupt */
UsageFault_IRQn = -10, /*!< 6 Cortex-M4 Usage Fault Interrupt */
SVCall_IRQn = -5, /*!< 11 Cortex-M4 SV Call Interrupt */
DebugMonitor_IRQn = -4, /*!< 12 Cortex-M4 Debug Monitor Interrupt */
PendSV_IRQn = -2, /*!< 14 Cortex-M4 Pend SV Interrupt */
SysTick_IRQn = -1, /*!< 15 Cortex-M4 System Tick Interrupt */
/****** STM32 specific Interrupt Numbers **********************************************************************/
WWDG_IRQn = 0, /*!< Window WatchDog Interrupt */
PVD_IRQn = 1, /*!< PVD through EXTI Line detection Interrupt */
TAMP_STAMP_IRQn = 2, /*!< Tamper and TimeStamp interrupts through the EXTI line */
RTC_WKUP_IRQn = 3, /*!< RTC Wakeup interrupt through the EXTI line */
FLASH_IRQn = 4, /*!< FLASH global Interrupt */
RCC_IRQn = 5, /*!< RCC global Interrupt */
EXTI0_IRQn = 6, /*!< EXTI Line0 Interrupt */
EXTI1_IRQn = 7, /*!< EXTI Line1 Interrupt */
EXTI2_IRQn = 8, /*!< EXTI Line2 Interrupt */
EXTI3_IRQn = 9, /*!< EXTI Line3 Interrupt */
EXTI4_IRQn = 10, /*!< EXTI Line4 Interrupt */
DMA1_Stream0_IRQn = 11, /*!< DMA1 Stream 0 global Interrupt */
DMA1_Stream1_IRQn = 12, /*!< DMA1 Stream 1 global Interrupt */
DMA1_Stream2_IRQn = 13, /*!< DMA1 Stream 2 global Interrupt */
DMA1_Stream3_IRQn = 14, /*!< DMA1 Stream 3 global Interrupt */
DMA1_Stream4_IRQn = 15, /*!< DMA1 Stream 4 global Interrupt */
DMA1_Stream5_IRQn = 16, /*!< DMA1 Stream 5 global Interrupt */
DMA1_Stream6_IRQn = 17, /*!< DMA1 Stream 6 global Interrupt */
ADC_IRQn = 18, /*!< ADC1, ADC2 and ADC3 global Interrupts */
CAN1_TX_IRQn = 19, /*!< CAN1 TX Interrupt */
CAN1_RX0_IRQn = 20, /*!< CAN1 RX0 Interrupt */
CAN1_RX1_IRQn = 21, /*!< CAN1 RX1 Interrupt */
CAN1_SCE_IRQn = 22, /*!< CAN1 SCE Interrupt */
EXTI9_5_IRQn = 23, /*!< External Line[9:5] Interrupts */
TIM1_BRK_TIM9_IRQn = 24, /*!< TIM1 Break interrupt and TIM9 global interrupt */
TIM1_UP_TIM10_IRQn = 25, /*!< TIM1 Update Interrupt and TIM10 global interrupt */
TIM1_TRG_COM_TIM11_IRQn = 26, /*!< TIM1 Trigger and Commutation Interrupt and TIM11 global interrupt */
TIM1_CC_IRQn = 27, /*!< TIM1 Capture Compare Interrupt */
TIM2_IRQn = 28, /*!< TIM2 global Interrupt */
TIM3_IRQn = 29, /*!< TIM3 global Interrupt */
TIM4_IRQn = 30, /*!< TIM4 global Interrupt */
I2C1_EV_IRQn = 31, /*!< I2C1 Event Interrupt */
I2C1_ER_IRQn = 32, /*!< I2C1 Error Interrupt */
I2C2_EV_IRQn = 33, /*!< I2C2 Event Interrupt */
I2C2_ER_IRQn = 34, /*!< I2C2 Error Interrupt */
SPI1_IRQn = 35, /*!< SPI1 global Interrupt */
SPI2_IRQn = 36, /*!< SPI2 global Interrupt */
USART1_IRQn = 37, /*!< USART1 global Interrupt */
USART2_IRQn = 38, /*!< USART2 global Interrupt */
USART3_IRQn = 39, /*!< USART3 global Interrupt */
EXTI15_10_IRQn = 40, /*!< External Line[15:10] Interrupts */
RTC_Alarm_IRQn = 41, /*!< RTC Alarm (A and B) through EXTI Line Interrupt */
OTG_FS_WKUP_IRQn = 42, /*!< USB OTG FS Wakeup through EXTI line interrupt */
TIM8_BRK_TIM12_IRQn = 43, /*!< TIM8 Break Interrupt and TIM12 global interrupt */
TIM8_UP_TIM13_IRQn = 44, /*!< TIM8 Update Interrupt and TIM13 global interrupt */
TIM8_TRG_COM_TIM14_IRQn = 45, /*!< TIM8 Trigger and Commutation Interrupt and TIM14 global interrupt */
TIM8_CC_IRQn = 46, /*!< TIM8 Capture Compare global interrupt */
DMA1_Stream7_IRQn = 47, /*!< DMA1 Stream7 Interrupt */
FSMC_IRQn = 48, /*!< FSMC global Interrupt */
SDIO_IRQn = 49, /*!< SDIO global Interrupt */
TIM5_IRQn = 50, /*!< TIM5 global Interrupt */
SPI3_IRQn = 51, /*!< SPI3 global Interrupt */
UART4_IRQn = 52, /*!< UART4 global Interrupt */
UART5_IRQn = 53, /*!< UART5 global Interrupt */
TIM6_DAC_IRQn = 54, /*!< TIM6 global and DAC1&2 underrun error interrupts */
TIM7_IRQn = 55, /*!< TIM7 global interrupt */
DMA2_Stream0_IRQn = 56, /*!< DMA2 Stream 0 global Interrupt */
DMA2_Stream1_IRQn = 57, /*!< DMA2 Stream 1 global Interrupt */
DMA2_Stream2_IRQn = 58, /*!< DMA2 Stream 2 global Interrupt */
DMA2_Stream3_IRQn = 59, /*!< DMA2 Stream 3 global Interrupt */
DMA2_Stream4_IRQn = 60, /*!< DMA2 Stream 4 global Interrupt */
ETH_IRQn = 61, /*!< Ethernet global Interrupt */
ETH_WKUP_IRQn = 62, /*!< Ethernet Wakeup through EXTI line Interrupt */
CAN2_TX_IRQn = 63, /*!< CAN2 TX Interrupt */
CAN2_RX0_IRQn = 64, /*!< CAN2 RX0 Interrupt */
CAN2_RX1_IRQn = 65, /*!< CAN2 RX1 Interrupt */
CAN2_SCE_IRQn = 66, /*!< CAN2 SCE Interrupt */
OTG_FS_IRQn = 67, /*!< USB OTG FS global Interrupt */
DMA2_Stream5_IRQn = 68, /*!< DMA2 Stream 5 global interrupt */
DMA2_Stream6_IRQn = 69, /*!< DMA2 Stream 6 global interrupt */
DMA2_Stream7_IRQn = 70, /*!< DMA2 Stream 7 global interrupt */
USART6_IRQn = 71, /*!< USART6 global interrupt */
I2C3_EV_IRQn = 72, /*!< I2C3 event interrupt */
I2C3_ER_IRQn = 73, /*!< I2C3 error interrupt */
OTG_HS_EP1_OUT_IRQn = 74, /*!< USB OTG HS End Point 1 Out global interrupt */
OTG_HS_EP1_IN_IRQn = 75, /*!< USB OTG HS End Point 1 In global interrupt */
OTG_HS_WKUP_IRQn = 76, /*!< USB OTG HS Wakeup through EXTI interrupt */
OTG_HS_IRQn = 77, /*!< USB OTG HS global interrupt */
DCMI_IRQn = 78, /*!< DCMI global interrupt */
RNG_IRQn = 80, /*!< RNG global Interrupt */
FPU_IRQn = 81 /*!< FPU global interrupt */
} IRQn_Type;
参数 PreemptPriority 是 抢占优先级,可以选择范围:0 到 15,具体根据中断优先级分组决定。
参数 SubPriority 是 响应优先级,可以选择范围:0 到 15,具体根据中断优先级分组决定。
5.3、中断使能函数
HAL_NVIC_EnableIRQ() 函数是中断使能函数。其声明如下:
void HAL_NVIC_EnableIRQ(IRQn_Type IRQn);
其中,参数 IRQn 是 中断号,可以选择范围:IRQn_Type 定义的枚举类型,定义在 stm32f407xx.h。
5.4、中断失能函数
HAL_NVIC_DisableIRQ() 函数是中断失能函数。其声明如下:
void HAL_NVIC_EnableIRQ(IRQn_Type IRQn);
其中,参数 IRQn 是 中断号,可以选择范围:IRQn_Type 定义的枚举类型,定义在 stm32f407xx.h。
5.5、系统复位函数
HAL_NVIC_SystemReset() 函数是系统复位函数。其声明如下:
void HAL_NVIC_SystemReset(void);