NVIC(Nested vectored interrupt controller,嵌套向量中斷控制器)是Cortex-M處理器的一部分,它是可編程的,且寄存器位于存儲器映射的系統(tǒng)控制空間(SCS)。NVIC與內(nèi)核相輔相成,共同完成對中斷的響應。本章將介紹中斷的優(yōu)先級設置、如何定義中斷函數(shù)名稱、中斷向量如何偏移。有關NVIC的更多知識,請見《ARM Coretex-M3權威指南》。
3.1.優(yōu)先級的設置
在Cortex-M中,優(yōu)先級對于異常來說很關鍵的,它會影響一個異常是否能被響應,以及何時可以響應。優(yōu)先級的數(shù)值越小,則優(yōu)先級越高。Cortex-M支持中斷嵌套,使得高優(yōu)先級異常會搶占低優(yōu)先級異常。有3個系統(tǒng)異常:復位,NMI以及硬fault,它們有固定的優(yōu)先級,并且它們的優(yōu)先級號是負數(shù),從而高于所有其它異常。所有其它異常的優(yōu)先級則都是可編程的,但不能編程為負數(shù)。
原則上,Cortex-M支持3 個固定的高優(yōu)先級和多達256 級的可編程優(yōu)先級,并且支持128級搶占。但是,絕大多數(shù)CM3芯片都會精簡設計,以致實際上支持的優(yōu)先級數(shù)會更少,如8級,16級,32級 等。它們在設計時會裁掉表達優(yōu)先級的幾個低端有效位,以達到減少優(yōu)先級數(shù)的目的。
舉例來說,如果只使用了4位來表達優(yōu)先級,則優(yōu)先級配置寄存器的結構如圖所示。
使用 4bit 表達優(yōu)先級

GD32Fxxx系列、GD32E50x系列、GD32H7xx系 列、GD32A5x系列、GD32W51x系列和GD32VF103、GD32E10X系列使用了4位來表達優(yōu)先級。GD32E23x和GD32L23x使用的是M23內(nèi)核,只使用了2位表達優(yōu)先級。
說明:GD32Fxxx系列是指:GD32F10x、GD32F1x0、GD32F20x、GD32F30x、GD32F3x0、GD32F40x、GD32F4xx。
用于表達優(yōu)先級的這4bit,又被分組成搶占優(yōu)先級和子優(yōu)先級。如果有多個中斷同時響應,搶占優(yōu)先級高的就會搶占搶占優(yōu)先級低的優(yōu)先得到執(zhí)行。如果搶占優(yōu)先級相同,就比較子優(yōu)先級。如果搶占優(yōu)先級和子優(yōu)先級都相同的話,就比較他們的硬件中斷編號,編號越小,優(yōu)先級越高。
GD32Fxxx系列、GD32E10x系列、GD32E50x系列、GD32H7xx系列、GD32A5x系列、GD32W51X系列和GD32VF103和GD32E10x系列可以設置搶占優(yōu)先級和子優(yōu)先級的等級,GD32E23x和GD32L23x系列沒有搶占優(yōu)先級和子優(yōu)先級的說法,只可以設置優(yōu)先級。
下面以GD32F10x舉例說明如何設置優(yōu)先級位數(shù)以及搶占優(yōu)先級和子優(yōu)先級的等級。在GD32f10x_misc.c文件中,nvic_priority_group_set函數(shù)用于設置多少位用于搶占優(yōu)先級,多少位用于子優(yōu)先級;nvic_irq_enable函數(shù)用于設置相應中斷的搶占優(yōu)先級和子優(yōu)先級的等級。比如現(xiàn)在要設置SPI0的中斷,其搶占優(yōu)先級和子優(yōu)先級的位數(shù)均為2,搶占優(yōu)先級的等級為0,子優(yōu)先級 的等級為1,那么代碼如代碼清單SPI0中斷優(yōu)先級設置所示。
nvic_priority_group_set(NVIC_PRIGROUP_PRE2_SUB2); nvic_irq_enable(SPI0_IRQn,0,1);
有關這兩個函數(shù)的原型以及函數(shù)參數(shù)的說明,請見代碼清單nvic_priority_group_set函數(shù)原型、參數(shù)nvic_prigroup說明表、代碼清單nvic_irq_enable函數(shù)原型、nvic_irq_enable()函數(shù)的參數(shù)說明表。
代碼清單nvic_priority_group_set 函數(shù)原型
void nvic_priority_group_set(uint32_t nvic_prigroup) { /* set the priority group value */ SCB->AIRCR = NVIC_AIRCR_VECTKEY_MASK | nvic_prigroup; }
參數(shù) nvic_prigroup 說明表

代碼清單nvic_irq_enable 函數(shù)原型
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()函數(shù)的參數(shù)說明表

參數(shù)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.中斷服務函數(shù)的命名
上一小節(jié)介紹了如何設置中斷的優(yōu)先級,那么中斷服務函數(shù)如何命名和使用呢? 本小結將介紹這方面的內(nèi)容。
下面以GD32F103C8T6產(chǎn)品為例,介紹如何命名中斷服務函數(shù)名。GD32F103C8T6的flash容量為64KB,屬于中密度產(chǎn)品,其對應的啟動文件為startup_gd32f10x_md.s。在該啟動文件中我們預先為每個中斷都命名了一個中斷服務函數(shù),為的是初始化中斷向量表。實際的中斷服務函數(shù)里面的內(nèi)容需要我們重新編寫,中斷服務函數(shù)我們統(tǒng)一寫在gd32f10x_it.c文件里。
需要注意的是,中斷服務函數(shù)的函數(shù)名必須和啟動文件里面的一樣,如果寫錯了,系統(tǒng)在中斷向量表中就會找不到中斷服務函數(shù)的入口,從而導致進不了中斷。為了避免該錯誤,簡單的處理方法是:打開startup_gd32f10x_md.s,找到需要的中斷服務函數(shù)名,復制該函數(shù)名到gd32f10x_it.c文 件 中 即 可 。 以 SPI0 中 斷 為 例 , 打 開 startup_gd32f10x_md.s , 找 到 SPI0_IRQHandler (SPI0_IRQHandler就是SPI0中斷服務函數(shù)的名稱),復制SPI0_IRQHandler到gd32f10x_it.c,修改其如代碼清單SPI0中斷服務函數(shù)所示即可。在該函數(shù)中就可以添加用戶所需的中斷服務 代碼了。
void SPI0_IRQHandler(void) { }
3.3.中斷向量偏移
當發(fā)生了異常并且要響應它時,Cortex-M 需要定位其處理例程的入口地址。這些入口地址存儲在所謂的“異常向量表”中。默認情況下,Cortex-M認為該表位于零地址處,且各向量占用4 節(jié),因此每個表項占用4 字節(jié),如上電后的向量表所示。

因為地址0處應該存儲引導代碼,所以它通常是Flash或者是ROM器件,并且它們的值不得在運行時改變。然而,為了動態(tài)重分發(fā)中斷,Cortex-M允許向量表重定位,從其它地址處開始定位各異常向量。這些地址對應的區(qū)域可以是代碼區(qū),但更多在RAM區(qū)。在RAM區(qū)就可以修改向量的入口地址了。為了實現(xiàn)這個功能,NVIC中有一個寄存器,稱為“向量表偏移量寄存器”(在地址0xE000_ED08處),通過修改它的值就能定位向量表。但必須注意的是:向量表的起始地址是有要求的:必須先求出系統(tǒng)中共有多少個向量,再把這個數(shù)字向上增大到是2 的整次冪,而起始地址必須對齊到后者的邊界上。例如,如果一共有32個中斷,則共有32+16(系統(tǒng)異常)=48個向量,向上增大到2 的整次冪后值為64,因此地址地址必須能被64*4=256 整除,從而合法的起始地址可以是:0x0, 0x100, 0x200等。向量表偏移量寄存器的定義如向量表偏移寄存器(VTOR)表所示。

在gd32f10x_misc.c文件中,nvic_vector_table_set函數(shù)就是用來定義中斷向量偏移的,該函數(shù)的原型如代碼清單 0-16 nvic_vector_table_set函數(shù)原型所示,函數(shù)參數(shù)說明如參數(shù)說明表所示。
代碼清單nvic_vector_table_set 函數(shù)原型
void nvic_vector_table_set(uint32_t nvic_vict_tab, uint32_t offset) { SCB->VTOR = nvic_vict_tab | (offset & NVIC_VECTTAB_OFFSET_MASK); }
參數(shù)說明表

下面舉例說明如何使用該函數(shù)。
在實際使用中,用戶會把FALSH分成BOOT區(qū)和APP區(qū)。BOOT區(qū)只用于代碼升級,實際應用的程序在APP區(qū)里運行。假設客戶把FLASH的第0頁(大小為1KB)作為BOOT區(qū),該頁的地址范圍為0x08000000~0x080003FF,第2頁、第3頁作為APP區(qū),地址范圍為0x08000800~0x08000FFF。執(zhí)行完BOOT區(qū)的代碼后,程序會跳轉(zhuǎn)到0x08000800的地址開始執(zhí)行APP程序。0x08000800相對于基地址0x08000000的偏移地址為0x800,此時調(diào)用nvic_vector_table_set函數(shù)的格式如代碼清單調(diào)用nvic_vector_table_set函數(shù)所示。
nvic_vector_table_set(NVIC_VECTTAB_FLASH, 0x800);
3.4.NVIC 使用注意事項
E23x 系列使用的是 M23 內(nèi)核,該內(nèi)核的 NVIC 使用 2bit 定義優(yōu)先級,并且不分搶占優(yōu)先級和子優(yōu)先級。在 gd32e23x_misc.c 文件中,nvic_irq_enable(uint8_t nvic_irq, uint8_t nvic_irq_priority)函數(shù)用于設置優(yōu)先級,該函數(shù)的參數(shù)說明如圖所示。

本教程由GD32 MCU方案商聚沃科技原創(chuàng)發(fā)布,了解更多GD32 MCU教程,關注聚沃科技官網(wǎng)
-
單片機
+關注
關注
6064文章
44932瀏覽量
647503 -
嵌入式
+關注
關注
5142文章
19561瀏覽量
315378 -
開發(fā)板
+關注
關注
25文章
5563瀏覽量
102681 -
GD32
+關注
關注
7文章
419瀏覽量
25208
發(fā)布評論請先 登錄
GD32 MCU 入門教程】GD32 MCU 常見外設介紹(12)FMC 模塊介紹

GD32 MCU移植
GD32的MCU介紹
《GD32 MCU原理及固件庫開發(fā)指南》+讀后感
兆易創(chuàng)新GD32 MCU選型手冊,適用于GD32全系列MCU
不同型號的GD32 MCU如何區(qū)分?

【GD32 MCU 入門教程】一、GD32 MCU 開發(fā)環(huán)境搭建(1)使用Keil開發(fā)GD32

【GD32 MCU 入門教程】一、GD32 MCU 開發(fā)環(huán)境搭建(2)使用 IAR 開發(fā) GD32

【GD32 MCU 入門教程】一、GD32 MCU 開發(fā)環(huán)境搭建(3)使用 Embedded Builder 開發(fā) GD32

【GD32 MCU 入門教程】二、GD32 MCU 燒錄說明(1)ISP 燒錄

【GD32 MCU 入門教程】GD32 MCU 常見外設介紹(14)RTC 模塊介紹

【GD32 MCU入門教程】GD32 MCU GPIO 結構與使用注意事項

評論