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)函數用于設置優先級,該函數的參數說明如圖所示。

-
單片機
+關注
關注
6043文章
44621瀏覽量
638565 -
嵌入式
+關注
關注
5092文章
19177瀏覽量
307679 -
開發板
+關注
關注
25文章
5121瀏覽量
98195 -
GD32
+關注
關注
7文章
413瀏覽量
24470
發布評論請先 登錄
相關推薦
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 結構與使用注意事項

評論