NVIC(Nested vectored interrupt controller,嵌套向量中断控制器)是Cortex-M处理器的一部分,它是可编程的,且寄存器位于存储器映射的系统控制空间(SCS)。NVIC与内核相辅相成,共同完成对中断的响应。本章将介绍中断的优先级设置、如何定义中断函数名称、中断向量如何偏移。有关NVIC的更多知识,请见《ARM Coretex-M3权威指南》。
3.1.优先级的设置
在Cortex-M中,优先级对于异常来说很关键的,它会影响一个异常是否能被响应,以及何时可以响应。优先级的数值越小,则优先级越高。Cortex-M支持中断嵌套,使得高优先级异常会抢占低优先级异常。有3个系统异常:复位,NMI以及硬fault,它们有固定的优先级,并且它们的优先级号是负数,从而高于所有其它异常。所有其它异常的优先级则都是可编程的,但不能编程为负数。
原则上,Cortex-M支持3 个固定的高优先级和多达256 级的可编程优先级,并且支持128级抢占。但是,绝大多数CM3芯片都会精简设计,以致实际上支持的优先级数会更少,如8级,16级,32级 等。它们在设计时会裁掉表达优先级的几个低端有效位,以达到减少优先级数的目的。
举例来说,如果只使用了4位来表达优先级,则优先级配置寄存器的结构如图所示。
使用 4bit 表达优先级

GD32Fxxx系列、GD32E50x系列、GD32H7xx系 列、GD32A5x系列、GD32W51x系列和GD32VF103、GD32E10X系列使用了4位来表达优先级。GD32E23x和GD32L23x使用的是M23内核,只使用了2位表达优先级。
说明:GD32Fxxx系列是指:GD32F10x、GD32F1x0、GD32F20x、GD32F30x、GD32F3x0、GD32F40x、GD32F4xx。
用于表达优先级的这4bit,又被分组成抢占优先级和子优先级。如果有多个中断同时响应,抢占优先级高的就会抢占抢占优先级低的优先得到执行。如果抢占优先级相同,就比较子优先级。如果抢占优先级和子优先级都相同的话,就比较他们的硬件中断编号,编号越小,优先级越高。
GD32Fxxx系列、GD32E10x系列、GD32E50x系列、GD32H7xx系列、GD32A5x系列、GD32W51X系列和GD32VF103和GD32E10x系列可以设置抢占优先级和子优先级的等级,GD32E23x和GD32L23x系列没有抢占优先级和子优先级的说法,只可以设置优先级。
下面以GD32F10x举例说明如何设置优先级位数以及抢占优先级和子优先级的等级。在GD32f10x_misc.c文件中,nvic_priority_group_set函数用于设置多少位用于抢占优先级,多少位用于子优先级;nvic_irq_enable函数用于设置相应中断的抢占优先级和子优先级的等级。比如现在要设置SPI0的中断,其抢占优先级和子优先级的位数均为2,抢占优先级的等级为0,子优先级 的等级为1,那么代码如代码清单SPI0中断优先级设置所示。
nvic_priority_group_set(NVIC_PRIGROUP_PRE2_SUB2); nvic_irq_enable(SPI0_IRQn,0,1);
有关这两个函数的原型以及函数参数的说明,请见代码清单nvic_priority_group_set函数原型、参数nvic_prigroup说明表、代码清单nvic_irq_enable函数原型、nvic_irq_enable()函数的参数说明表。
代码清单nvic_priority_group_set 函数原型
void nvic_priority_group_set(uint32_t nvic_prigroup) { /* set the priority group value */ SCB->AIRCR = NVIC_AIRCR_VECTKEY_MASK | nvic_prigroup; }
参数 nvic_prigroup 说明表

代码清单nvic_irq_enable 函数原型
void nvic_irq_enable(uint8_t nvic_irq, uint8_t nvic_irq_pre_priority, uint8_t nvic_irq_sub_priority) { uint32_t temp_priority = 0x00U, temp_pre = 0x00U, temp_sub = 0x00U; /* use the priority group value to get the temp_pre and the temp_sub */ switch ((SCB->AIRCR) & (uint32_t)0x700U) { case NVIC_PRIGROUP_PRE0_SUB4: temp_pre = 0U; temp_sub = 0x4U; break; case NVIC_PRIGROUP_PRE1_SUB3: temp_pre = 1U; temp_sub = 0x3U; break; case NVIC_PRIGROUP_PRE2_SUB2: temp_pre = 2U; temp_sub = 0x2U; break; case NVIC_PRIGROUP_PRE3_SUB1: temp_pre = 3U; temp_sub = 0x1U; break; case NVIC_PRIGROUP_PRE4_SUB0: temp_pre = 4U; temp_sub = 0x0U; break; default: nvic_priority_group_set(NVIC_PRIGROUP_PRE2_SUB2); temp_pre = 2U; temp_sub = 0x2U; break; } /* get the temp_priority to fill the NVIC->IP register */ temp_priority = (uint32_t)nvic_irq_pre_priority << (0x4U - temp_pre); temp_priority |= nvic_irq_sub_priority &(0x0FU >> (0x4U - temp_sub)); temp_priority = temp_priority << 0x04U; NVIC->IP[nvic_irq] = (uint8_t)temp_priority; /* enable the selected IRQ */ NVIC->ISER[nvic_irq >> 0x05U] = (uint32_t)0x01U << (nvic_irq & (uint8_t)0x1FU); }
nvic_irq_enable()函数的参数说明表

参数nvic_irq是一个枚举变量,它定义了每一个中断的编号,具体定义在gd32f10x.h文件中,如代码清单中断号定义所示。
typedef enum IRQn { /* Cortex-M3 processor exceptions numbers */ NonMaskableInt_IRQn = -14, /*!< 2 non maskable interrupt */ MemoryManagement_IRQn = -12, /*!< 4 Cortex-M3 memory management interrupt */ BusFault_IRQn = -11, /*!< 5 Cortex-M3 bus fault interrupt */ UsageFault_IRQn = -10, /*!< 6 Cortex-M3 usage fault interrupt */ SVCall_IRQn = -5, /*!< 11 Cortex-M3 SV call interrupt */ DebugMonitor_IRQn = -4, /*!< 12 Cortex-M3 debug monitor interrupt */ PendSV_IRQn = -2, /*!< 14 Cortex-M3 pend SV interrupt */ SysTick_IRQn = -1, /*!< 15 Cortex-M3 system tick interrupt */ /* interruput numbers */ WWDGT_IRQn = 0, /*!< window watchDog timer interrupt */ LVD_IRQn = 1, /*!< LVD through EXTI line detect interrupt */ TAMPER_IRQn = 2, /*!< tamper through EXTI line detect */ RTC_IRQn = 3, /*!< RTC through EXTI line interrupt */ FMC_IRQn = 4, /*!< FMC interrupt */ RCU_CTC_IRQn = 5, /*!< RCU and CTC interrupt */ EXTI0_IRQn = 6, /*!< EXTI line 0 interrupts */ EXTI1_IRQn = 7, /*!< EXTI line 1 interrupts */ EXTI2_IRQn = 8, /*!< EXTI line 2 interrupts */ EXTI3_IRQn = 9, /*!< EXTI line 3 interrupts */ EXTI4_IRQn = 10, /*!< EXTI line 4 interrupts */ DMA0_Channel0_IRQn = 11, /*!< DMA0 channel0 interrupt */ DMA0_Channel1_IRQn = 12, /*!< DMA0 channel1 interrupt */ DMA0_Channel2_IRQn = 13, /*!< DMA0 channel2 interrupt */ DMA0_Channel3_IRQn = 14, /*!< DMA0 channel3 interrupt */ DMA0_Channel4_IRQn = 15, /*!< DMA0 channel4 interrupt */ DMA0_Channel5_IRQn = 16, /*!< DMA0 channel5 interrupt */ DMA0_Channel6_IRQn = 17, /*!< DMA0 channel6 interrupt */ ADC0_1_IRQn = 18, /*!< ADC0 and ADC1 interrupt */ #ifdef GD32F10X_MD USBD_HP_CAN0_TX_IRQn = 19, /*!< CAN0 TX interrupts */ USBD_LP_CAN0_RX0_IRQn = 20, /*!< CAN0 RX0 interrupts */ CAN0_RX1_IRQn = 21, /*!< CAN0 RX1 interrupts */ CAN0_EWMC_IRQn = 22, /*!< CAN0 EWMC interrupts */ EXTI5_9_IRQn = 23, /*!< EXTI[9:5] interrupts */ TIMER0_BRK_IRQn = 24, /*!< TIMER0 break interrupts */ TIMER0_UP_IRQn = 25, /*!< TIMER0 update interrupts */ TIMER0_TRG_CMT_IRQn = 26, /*!< TIMER0 trigger and commutation interrupts */ TIMER0_Channel_IRQn = 27, /*!< TIMER0 channel capture compare interrupts */ TIMER1_IRQn = 28, /*!< TIMER1 interrupt */ TIMER2_IRQn = 29, /*!< TIMER2 interrupt */ TIMER3_IRQn = 30, /*!< TIMER3 interrupts */ I2C0_EV_IRQn = 31, /*!< I2C0 event interrupt */ I2C0_ER_IRQn = 32, /*!< I2C0 error interrupt */ I2C1_EV_IRQn = 33, /*!< I2C1 event interrupt */ I2C1_ER_IRQn = 34, /*!< I2C1 error interrupt */ SPI0_IRQn = 35, /*!< SPI0 interrupt */ SPI1_IRQn = 36, /*!< SPI1 interrupt */ USART0_IRQn = 37, /*!< USART0 interrupt */ USART1_IRQn = 38, /*!< USART1 interrupt */ USART2_IRQn = 39, /*!< USART2 interrupt */ EXTI10_15_IRQn = 40, /*!< EXTI[15:10] interrupts */ RTC_Alarm_IRQn = 41, /*!< RTC alarm interrupt */ USBD_WKUP_IRQn = 42, /*!< USBD Wakeup interrupt */ EXMC_IRQn = 48, /*!< EXMC global interrupt */ #endif /* GD32F10X_MD */ } IRQn_Type;
3.2.中断服务函数的命名
上一小节介绍了如何设置中断的优先级,那么中断服务函数如何命名和使用呢? 本小结将介绍这方面的内容。
下面以GD32F103C8T6产品为例,介绍如何命名中断服务函数名。GD32F103C8T6的flash容量为64KB,属于中密度产品,其对应的启动文件为startup_gd32f10x_md.s。在该启动文件中我们预先为每个中断都命名了一个中断服务函数,为的是初始化中断向量表。实际的中断服务函数里面的内容需要我们重新编写,中断服务函数我们统一写在gd32f10x_it.c文件里。
需要注意的是,中断服务函数的函数名必须和启动文件里面的一样,如果写错了,系统在中断向量表中就会找不到中断服务函数的入口,从而导致进不了中断。为了避免该错误,简单的处理方法是:打开startup_gd32f10x_md.s,找到需要的中断服务函数名,复制该函数名到gd32f10x_it.c文 件 中 即 可 。 以 SPI0 中 断 为 例 , 打 开 startup_gd32f10x_md.s , 找 到 SPI0_IRQHandler (SPI0_IRQHandler就是SPI0中断服务函数的名称),复制SPI0_IRQHandler到gd32f10x_it.c,修改其如代码清单SPI0中断服务函数所示即可。在该函数中就可以添加用户所需的中断服务 代码了。
void SPI0_IRQHandler(void) { }
3.3.中断向量偏移
当发生了异常并且要响应它时,Cortex-M 需要定位其处理例程的入口地址。这些入口地址存储在所谓的“异常向量表”中。默认情况下,Cortex-M认为该表位于零地址处,且各向量占用4 节,因此每个表项占用4 字节,如上电后的向量表所示。

因为地址0处应该存储引导代码,所以它通常是Flash或者是ROM器件,并且它们的值不得在运行时改变。然而,为了动态重分发中断,Cortex-M允许向量表重定位,从其它地址处开始定位各异常向量。这些地址对应的区域可以是代码区,但更多在RAM区。在RAM区就可以修改向量的入口地址了。为了实现这个功能,NVIC中有一个寄存器,称为“向量表偏移量寄存器”(在地址0xE000_ED08处),通过修改它的值就能定位向量表。但必须注意的是:向量表的起始地址是有要求的:必须先求出系统中共有多少个向量,再把这个数字向上增大到是2 的整次幂,而起始地址必须对齐到后者的边界上。例如,如果一共有32个中断,则共有32+16(系统异常)=48个向量,向上增大到2 的整次幂后值为64,因此地址地址必须能被64*4=256 整除,从而合法的起始地址可以是:0x0, 0x100, 0x200等。向量表偏移量寄存器的定义如向量表偏移寄存器(VTOR)表所示。

在gd32f10x_misc.c文件中,nvic_vector_table_set函数就是用来定义中断向量偏移的,该函数的原型如代码清单 0-16 nvic_vector_table_set函数原型所示,函数参数说明如参数说明表所示。
代码清单nvic_vector_table_set 函数原型
void nvic_vector_table_set(uint32_t nvic_vict_tab, uint32_t offset) { SCB->VTOR = nvic_vict_tab | (offset & NVIC_VECTTAB_OFFSET_MASK); }
参数说明表

下面举例说明如何使用该函数。
在实际使用中,用户会把FALSH分成BOOT区和APP区。BOOT区只用于代码升级,实际应用的程序在APP区里运行。假设客户把FLASH的第0页(大小为1KB)作为BOOT区,该页的地址范围为0x08000000~0x080003FF,第2页、第3页作为APP区,地址范围为0x08000800~0x08000FFF。执行完BOOT区的代码后,程序会跳转到0x08000800的地址开始执行APP程序。0x08000800相对于基地址0x08000000的偏移地址为0x800,此时调用nvic_vector_table_set函数的格式如代码清单调用nvic_vector_table_set函数所示。
nvic_vector_table_set(NVIC_VECTTAB_FLASH, 0x800);
3.4.NVIC 使用注意事项
E23x 系列使用的是 M23 内核,该内核的 NVIC 使用 2bit 定义优先级,并且不分抢占优先级和子优先级。在 gd32e23x_misc.c 文件中,nvic_irq_enable(uint8_t nvic_irq, uint8_t nvic_irq_priority)函数用于设置优先级,该函数的参数说明如图所示。

-
单片机
+关注
关注
6051文章
44726浏览量
641942 -
嵌入式
+关注
关注
5109文章
19314浏览量
310736 -
开发板
+关注
关注
25文章
5243浏览量
99483 -
GD32
+关注
关注
7文章
416浏览量
24721
发布评论请先 登录
相关推荐
GD32 MCU 入门教程】GD32 MCU 常见外设介绍(12)FMC 模块介绍

GD32 MCU移植
兆易创新GD32 MCU选型手册,适用于GD32全系列MCU
【GD32 MCU 入门教程】一、GD32 MCU 开发环境搭建(1)使用Keil开发GD32

【GD32 MCU 入门教程】一、GD32 MCU 开发环境搭建(2)使用 IAR 开发 GD32

【GD32 MCU 入门教程】一、GD32 MCU 开发环境搭建(3)使用 Embedded Builder 开发 GD32

【GD32 MCU 入门教程】二、GD32 MCU 烧录说明(1)ISP 烧录

【GD32 MCU 入门教程】GD32 MCU 常见外设介绍(14)RTC 模块介绍

【GD32 MCU入门教程】GD32 MCU GPIO 结构与使用注意事项

评论