2014年7月18日 星期五

TM4C123GXL Launchpad 與 GPIO Interrupt

之前的文章裡有做過使用按鍵來控制 LED 燈, 不過是採用 Polling (輪詢) 的方式, 這次我們改用 Interrupt (中斷) 來處理.

說到中斷, 先介紹兩組暫存器 ,

  • 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