【GD32 MCU入门教程】四、GD32 MCU 常见外设介绍(3)NVIC 介绍

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)函数用于设置优先级,该函数的参数说明如图所示。

更多GD32 MCU相关咨询:https://www.gd32bbs.com/

相关推荐
eybk2 小时前
Pytorch+Mumu模拟器+萤石摄像头实现对小孩学习的监控
学习
6.942 小时前
Scala学习记录 递归调用 练习
开发语言·学习·scala
qq_459730032 小时前
4-3 MCU中ARM存储器的作用
arm开发·单片机·嵌入式硬件
守护者1703 小时前
JAVA学习-练习试用Java实现“使用Arrays.toString方法将数组转换为字符串并打印出来”
java·学习
学会沉淀。3 小时前
Docker学习
java·开发语言·学习
Rinai_R3 小时前
计算机组成原理的学习笔记(7)-- 存储器·其二 容量扩展/多模块存储系统/外存/Cache/虚拟存储器
笔记·物联网·学习
吃着火锅x唱着歌3 小时前
PHP7内核剖析 学习笔记 第四章 内存管理(1)
android·笔记·学习
ragnwang3 小时前
C++ Eigen常见的高级用法 [学习笔记]
c++·笔记·学习
Web阿成5 小时前
3.学习webpack配置 尝试打包ts文件
前端·学习·webpack·typescript