說到中斷, 先介紹兩組暫存器 ,
- NVIC_ENn -- Enable Interrupt
- NVIC_PRIn -- 設置中斷的 Priority
先看 NVIC_ENn , TM4C123GH6PM 有5個 NVIC_ENn 暫存器 , n 代表 0 ~ 4. 分別如下 :
NVIC_EN0_R : 控制 interrupt 0 ~ 31
NVIC_EN1_R : 控制 interrupt 32 ~ 63
NVIC_EN2_R : 控制 interrupt 64 ~ 95
NVIC_EN3_R : 控制 interrupt 96 ~ 127
NVIC_EN4_R : 控制 interrupt 128 ~ 138
如何知道我使用的中斷是 Interrupt xxx ? 可以查 TM4C123GH6PM Data Sheet, page 104, Table 2-9 Interrupts. 截取部分如下圖 :
找到 Interrupt Number(Bit in interrupt Registers) 欄位 , 裡面的編號就是 IRQ number, 相對應到 ENn .
例如我們使用 GPIO Port D 的 Interrupt , IRQ number 是 3, 是屬於 EN0, 要 Enable 它, 只要寫入 :
NVIC_EN0_R = 0x00000008; //bit3
又如果使用 UART0 的 Interrupt , IRQ number 是 5, 還是屬於 EN0, 我們可以寫入 :
NVIC_EN0_R = 0x00000020; //bit5
接著我們看 NVIC_PRIn 暫存器 , 它是四個IRQ為一組, 例如 IRQ number 0 ~ 3 是屬於 NVIC_PRI0_R , 可以參考上圖左邊紅色的部分 . 詳細的暫存器內容請看下圖 :
其中 INTA 就是對應到 IRQ0, INTB 對應到 IRQ1, INTC 對應到 IRQ2, INTD 對應到 IRQ3, 我想你也發現只有高三位有效 , 所以從 000 ~ 111 共有8個 Priority . 數字越小, 優先權越高. 000為最高, 111為最低, 我們沿用上面的例子 , 使用 GPIO Port C 的 Interrupt , 假設我們設它的 Priority 為 2 :
NVIC_PRI0_R = 0x40000000;
GPIO Port D 的 Interrupt 是 IRQ3, 對應到的是 INTD 為 bit 31~29, Priority 2 為 0x10, 置入 bit 31~29, 為 b0100.0000.0000.0000.0000.0000.0000.0000 = 0x4000.0000 .
那如果是 UART0 的 Interrupt 呢? 假設我們設它的 Priority 為 1 :
NVIC_PRI1_R = 0x00002000;
UART0 的 Interrupt , IRQ number 是 5 , 對應到的是 PRI1 暫存器的 INTB 為 bit 15~13, Priority 1 為 0x01, 置入 bit 15~13, 為 b0000.0000.0000.0000.0010.0000.0000.0000 = 0x0000.2000 .
我想這樣對這兩組暫存器的設置應該有了初步的了解了 , 接著要說明的是 GPIO 使用於中斷的暫存器 .
- GPIO_PORTx_IS_R
- GPIO_PORTx_IBE_R
- GPIO_PORTx_IEV_R
- GPIO_PORTx_IM_R
- GPIO_PORTx_RIS_R
- GPIO_PORTx_MIS_R
- GPIO_PORTx_ICR_R
總共有7個, 先看 GPIO_PORTx_IS_R : 這個暫存器是用來決定檢測 Level 或是 Edge 的. 要檢測 Level 則在該相對應腳位的 bit 寫入 1 , 若是 Edge 則寫 0 . 例如我們要使用 PF4 , Level detect, 則寫 : GPIO_PORTF_IS_R |= 0x10 ; 若是 Edge 則為 : GPIO_PORTF_IS_R &= ~ 0x10 ; 詳細的暫存器內容請看下圖 :
GPIO_PORTx_IBE_R 暫存器是用來決定是否檢測 Both Edges 的, 也就是 Rising 和 Falling 都動作 ,如果是的話 , 則在該相對應腳位的 bit 寫入 1 , 詳細的暫存器內容請看下圖 :
GPIO_PORTx_IEV_R 暫存器是用來決定檢測 Rising Edge / High Level 或是 Falling Edge / Low Level 的 , 在該相對應腳位的 bit 寫入 1 為檢測 Rising Edge / High Level , 寫入 0 為檢測 Falling Edge / Low Level . 詳細的暫存器內容請看下圖 :
GPIO_PORTx_IM_R 暫存器是 Interrupt Mask 用來決定是否將 Pin 腳的中斷信號傳送給中斷控制器 . 在該相對應腳位的 bit 寫入 1 則傳送 . 詳細的暫存器內容請看下圖 :
GPIO_PORTx_RIS_R 暫存器是一個中斷狀態暫存器 , 中斷發生時會在相對應腳位的 bit 顯示 1 , 透過讀取暫存器的值可以知道是哪個腳位發生中斷, 此暫存器只可讀取 , 不可寫入 . 若中斷發生後 , 要把該相對應腳位的 bit 清為 0 , 否則下一個中斷將不會出現. 要把該 bit 清為 0 , 可以透過對 GPIO_PORTx_ICR_R 暫存器相對應的 bit 寫入 1 . 詳細的暫存器內容請看下圖 :
GPIO_PORTx_MIS_R 暫存器是和 GPIO_PORTx_RIS_R 類似 . 中斷發生時會在相對應腳位的 bit 顯示 1 , 也是只可讀取 , 不可寫入 , 也是透過對 GPIO_PORTx_ICR_R 暫存器相對應的 bit 寫入 1 , 把該 bit 清為 0 . 詳細的暫存器內容請看下圖 :
GPIO_PORTx_ICR_R 暫存器是一個清除暫存器 , 在 bit 寫入 1 則在 GPIO_PORTx_RIS_R 和 GPIO_PORTx_MIS_R 暫存器相對應的 bit 清為 0 .
接下來我們可以開始進行程式了 , 這裡的設置是用 PF4 (SW1) interrupt, 並 Enable PF1/PF2/PF3. 當按下 PF4 (SW1) , 產生中斷 , 在中斷副程式裡 toggle PF3 (Green LED).
#include <stdint.h>
#include "inc/tm4c123gh6pm.h"
#include "PLL_80MHZ.h"
void PortF_Init(void){
volatile unsigned long delay;
SYSCTL_RCGC2_R |= 0x00000020;
delay = SYSCTL_RCGC2_R;
GPIO_PORTF_DIR_R |= 0x0E;
GPIO_PORTF_DIR_R &= ~0x10; // Set PF4 input
GPIO_PORTF_AFSEL_R &= ~0x1E;
GPIO_PORTF_DEN_R |= 0x1E;
GPIO_PORTF_PCTL_R &= ~0x000FFFF0;
GPIO_PORTF_AMSEL_R &= ~0x1E;
GPIO_PORTF_PUR_R |= 0x10; // pull-up on PF4
GPIO_PORTF_IS_R &= ~0x10; // PF4 is edge-sensitive
GPIO_PORTF_IBE_R &= ~0x10; // PF4 is not both edges
GPIO_PORTF_IEV_R &= ~0x10; // PF4 falling edge event
GPIO_PORTF_ICR_R = 0x10; // Clear GPIORIS and GPIOMIS registers
GPIO_PORTF_IM_R |= 0x10; // arm interrupt on PF4
NVIC_PRI7_R = (NVIC_PRI7_R&0xFF0FFFFF)|0x00A00000; // priority 5
NVIC_EN0_R = 0x40000000; // enable interrupt 30 in NVIC
}
void GPIOF_Handler(void){
GPIO_PORTF_ICR_R = 0x10; // Clear GPIORIS and GPIOMIS registers
GPIO_PORTF_DATA_R ^= 0x08;
}
int main(void){
PLL_Init80MHZ();
PortF_Init(); // initialize GPIO Port F interrupt
while(1){
}
}
這裡有個地方要說明一下 , 就是 :
void GPIOF_Handler(void){
GPIO_PORTF_ICR_R = 0x10;
GPIO_PORTF_DATA_R ^= 0x08;
}
這就是 ISR (Interrupt Service Routine) 中斷服務副程式 , 也就是當中斷發生時執行的任務內容 , 這裡的任務就是 Toggle PF3 . 然而這個 ISR 的名稱不是任意取的, (例如此處的 GPIOF_Handler) , 我們打開 startup_TM4C123.s 檔案 . (如下圖)
往下拉 , 可以找到這裡定義了 ISR 的名稱 , GPIO Port F 是 GPIOF_Handler , 而我們寫的 ISR 的名稱要和這裏的一樣 , 當然你也可以修改 startup_TM4C123.s 裡的名稱來符合你的 ISR 名稱 , 不過不建議這麼做 .
關於 GPIO Interrupt 的部分 , 到此告一段落 .
Embedded Systems: Introduction to ARM® Cortex™-M Microcontrollers - Jonathan Valvano
Getting Started with the Tiva™ TM4C123G LaunchPad Workshop
TM4C123GH6PM DATA SHEET
Embedded Systems Shape The World









