一、NVIC(Nested Vectored Interrupt Controller, 嵌套向量中断控制器)
当MCU片上外设向NVIC控制器发出中断请求信号后,NVIC根据此中断的优先级来决定是立刻响应还是暂时挂起,如果是立刻响应,则NVIC根据请求信号对应的中断编号,从中断向量表中找到此中断请求对应的中断服务函数(中断向量),并让CPU立刻去运行这个中断服务函数。
PS:可以理解为,当中断X发生且NVIC决定响应此中断时,NVIC让CPU立刻产生硬调用:当CPU正在运行函数A时,NVIC让CPU立刻去运行中断X的服务函数,此时相当于函数A调用了中断X的服务函数,注意调用时函数A的上下文由NVIC硬件负责压栈保存)。
二、关键概念
1、嵌套
① 每个中断都有可编程的的优先级:根据全局的优先级分组(即PRIGROUP的值),每个中断的8bit的优先级(STM32只用了高4bit)被拆分成抢占优先级、响应优先级。抢占优先级高的中断触发时,可随时打断抢占优先级低的、正在被CPU执行的中断;在多个中断同时触发或挂起时,响应优先级高的那个中断优先被CPU执行;若多个中断同时触发或挂起,且它们的抢占优先级和响应优先级都相同,则中断编号最小的那个中断优先被CPU执行
PS: CortexM3中,中断的中断优先级的数值越小,其逻辑优先级越高。复位中断的逻辑优先级最高,值为 -3。
② 每个中断都有自己的挂起标志:若中断0、中断1同时触发,此时CPU响应了中断0,则未能被响应的中断1的挂起标志将置位;后面当中断1被CPU响应时,硬件自动将中断1的挂起标志清0。
PS:软件写中断_X的挂起标志,也可手动触发此中断,RTOS正是通过这种方法手动触发PendSV中断并在PendSV中断函数中执行线程切换动作。
2、向量
每个中断都有自己的向量(中断服务函数指针,长度为4Byte):当NVIC确定要响应中断X时,NVIC先根据中断向量表地址(VTOR寄存器的值,此寄存器值可通过软件修改)找到中断向量表,然后根据中断X的中断编号,从中断向量表中找到与之对应的向量(中断服务函数指针),最后让CPU去运行其中断服务函数。
PS: STM32F0系列(CortexM0)的MCU,中断向量表的地址固定是0,此时可以通过将中断向量表拷贝到RAM起始地址,然后再将RAM起始地址映射为0地址,达到中断向量表重定位的目的。
3、可屏蔽
① 240个外中断(IRQ #0(编号16) ~ IRQ #239(编号255))全都有中断使能、禁能控制位(注意:对于编号1~编号15的系统异常,NVIC固定为永久使能)。
② 有三个全局的中断屏蔽寄存器(只针对抢占优先级屏蔽):
三、程序开发流程(以UART1为例)
1、复位后,软件首先重定位中断向量表:
SCB->VTOR = VECT_TAB_BASE_ADDRESS | VECT_TAB_OFFSET;
2、软件配置全局的NVIC中断优先级分组:
HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4);
3、在UART驱动程序中:
① 编写UART1的中断服务函数 USART1_IRQHandler(),并添加到中断向量表中。
② 在NVIC寄存器中, 设置UART1的中断优先级,并使能UART1中断:
HAL_NVIC_SetPriority(UART1_IRQn, UART1_PRE_PRIORITY, UART1_SUB_PRIORITY);
HAL_NVIC_EnableIRQ(UART1_IRQn);
③ 在UART1中,使能UART1的RXNE(接收不为空中断),TC(发送完成中断):
__HAL_UART_ENABLE_IT(UART1, UART_IT_RXNE);
__HAL_UART_ENABLE_IT(UART1, UART_IT_TC);
参考资料
[1] Joseph Yiu(著),宋岩(译). Cortex-M3 权威指南.
[2] ARMv7-M_Architecture_Reference_Manual.
[3] CortexM3_Technical_Reference_Manual.