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)函數用于設置優先級,該函數的參數說明如圖所示。
-
單片機
+關注
關注
6037文章
44558瀏覽量
635353 -
嵌入式
+關注
關注
5082文章
19126瀏覽量
305242 -
開發板
+關注
關注
25文章
5050瀏覽量
97483 -
GD32
+關注
關注
7文章
403瀏覽量
24353
發布評論請先 登錄
相關推薦
評論