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

2014年5月18日 星期日

TM4C123GXL Launchpad 與 LCD1602

LCD 1602 是容易取得而且價格便宜的 文字型 LCD 模組 , 在電子材料行或是拍賣網站都有 . 控制晶片大部分是日立生產的 HD44780  .  這一次就用它來顯示計數器 . 因為 LCD 1602 module 有 5 V 和 3.3 V 兩種 . 測試了一下這兩個都可以 . 只不過在供給 LCD 1602 module 電源時, 如果 LCD 1602 module 是 5 V 的話 , 要接 Launchpad 上的 VBUS ,  那如果是 3.3 V 的 module , 當然接 Launchpad 上的 3.3 V .

底下是接線圖 : (3.3V LCD 1602 module)

LCD1602 module 共有 16 Pins. 與 TM4C123GXL Lanchpad 的接腳如下 :


其中 LCD1602 的 Pin 3 接一個 3.9k 歐姆的電阻對地(圖上畫的是 3.3k, 那是因為沒找到 3.9k, 所以用 3.3k 代替) . 這地方是調整對比的 , 如果太暗或太亮造成看不清楚顯示的字, 可以接一個 10k 歐姆的可變電阻來調整.  接法如下 :


接下來寫一個 LCD1602 的 Driver , 方便我們使用 . 參考一下它的 Data Sheet . 在 LCD1602.h 中放我們程式函式的原型. 而 LCD1602.c 則是函式的內容 . 當要使用時 , 在我們的主程式中要加入 #include  "LCD1602.h" , 並且要將 LCD1602.c 加入我們的 Project 中.

LCD1602.h :

/*********************************************************************************
**********************************************************************************
**                                  LCD1602.h                                   **
** 1. Support TIVA-C TM4C123GXL Launchpad                                       **
** 2. PIN ASSIGNMENT --> RW-PA5, RS-PA6, E-PA7,  D0~D7 => PB0~PB7         **
** 3. Michael @2014/05/10                                                       **
** 4. Version 1.0                                                               **
**********************************************************************************    
*********************************************************************************/
/* Hardware connection between Launchpad and LCD1602 module    
||     Lauchpad Pin         <==>      LCD1602 Pin      
||         GND              <==>         Pin1
||         Vcc              <==>         Pin2 (3.3V)
||         GND              <==>         Pin3 (connect a 3.9k to GND) 
||         PA6              <==>         Pin4  (RS)
||         PA5              <==>         Pin5  (R/W, To GND is only enable to write)
||         PA7              <==>         Pin6  (E)
||         PB0              <==>         Pin7
||         PB1              <==>         Pin8
||         PB2              <==>         Pin9
||         PB3              <==>         Pin10
||         PB4              <==>         Pin11
||         PB5              <==>         Pin12
||         PB6              <==>         Pin13
||         PB7              <==>         Pin14                    
||         Vcc              <==>         Pin15,  (A, connect a 330R to Vcc)
||         GND              <==>         Pin16,  (K, connect to Ground) */

#define LINE1 0x80 //First Line of LCD1602
#define LINE2 0xC0 //Second Line of LCD1602
//********************************************************************************
//LCD1602 Initialize: 
//********************************************************************************
void LCD1602_Init(void);

//********************************************************************************
//LCD1602 Clear Screen: 
//********************************************************************************
void LCD1602_Clear(void);

//********************************************************************************
//LCD1602 Display String to Screen: 
//********************************************************************************
void LCD1602_DisplayString(unsigned char *str);

//********************************************************************************
//LCD1602 Display Char to Screen: 
//********************************************************************************
void LCD1602_DisplayChar(unsigned char CHAR);

//********************************************************************************
//LCD1602 Display Decimal number to Screen:
//********************************************************************************
void LCD1602_DisplayDec(unsigned int number);

//********************************************************************************
//LCD1602 Set Cursor Position on the Screen: 
//Line : LINE1 or LINE2
//Digit : from 0 ~ 15
//********************************************************************************
void LCD1602_DisplayPosition(unsigned char Line,unsigned int digit);


LCD1602.c :

/*********************************************************************************
**********************************************************************************
**                                  LCD1602.c                                   **
** 1. Support TIVA-C TM4C123GXL Launchpad                                       **
** 2. PIN ASSIGNMENT --> RW-PA5, RS-PA6, E-PA7,  D0~D7 => PB0~PB7         **
** 3. Michael @2014/05/10                                                       **
** 4. Version 1.0                                                               **
**********************************************************************************    
*********************************************************************************/
/* Hardware connection between Launchpad and LCD1602 module    
||     Lauchpad Pin         <==>      LCD1602 Pin      
||         GND              <==>         Pin1
||         Vcc              <==>         Pin2 (3.3V)
||         GND              <==>         Pin3 (connect a 3.9k to GND) 
||         PA6              <==>         Pin4  (RS)
||         PA5              <==>         Pin5  (R/W, To GND is only enable to write)
||         PA7              <==>         Pin6  (E)
||         PB0              <==>         Pin7
||         PB1              <==>         Pin8
||         PB2              <==>         Pin9
||         PB3              <==>         Pin10
||         PB4              <==>         Pin11
||         PB5              <==>         Pin12
||         PB6              <==>         Pin13
||         PB7              <==>         Pin14                    
||         Vcc              <==>         Pin15,  (A, connect a 330R to Vcc)
||         GND              <==>         Pin16,  (K, connect to Ground) */

#include <stdint.h>
#include <stdio.h>
#include "inc/tm4c123gh6pm.h"
#include "LCD1602.h"
#include "SYSTICK.h"

#define RW 0x20  //PA5
#define RS 0x40  //PA6 
#define E  0x80  //PA7

//********************************************************************************
// LCD1602 Initial GPIO PORTB and PORTA- PA5,PA6,PA7 : GPIO_PortAB_Init()
//********************************************************************************
void GPIO_PortAB_Init(void){
    volatile unsigned long delay;
    //Initial PA7/PA6/PA5
    SYSCTL_RCGC2_R |= 0x00000001;     
    delay = SYSCTL_RCGC2_R;   
    GPIO_PORTA_AMSEL_R &= ~0xE0;      
    GPIO_PORTA_PCTL_R &= ~0xFFF00000;                    
    GPIO_PORTA_DIR_R |= 0xE0;         
    GPIO_PORTA_AFSEL_R &= ~0xE0;           
    GPIO_PORTA_DEN_R |= 0xE0;
    GPIO_PORTA_DR8R_R |= 0xE0;

    //Initial PB7 ~ 0
    SYSCTL_RCGC2_R |= 0x00000002;     
    delay = SYSCTL_RCGC2_R;   
    GPIO_PORTB_AMSEL_R &= ~0xFF;      
    GPIO_PORTB_PCTL_R &= ~0xFFFFFFFF;                    
    GPIO_PORTB_DIR_R |= 0xFF;         
    GPIO_PORTB_AFSEL_R &= ~0xFF;           
    GPIO_PORTB_DEN_R |= 0xFF;
    GPIO_PORTB_DR8R_R |= 0xFF;
}

//********************************************************************************
//LCD1602 Write Commend to LCD module
//********************************************************************************
void Write_Command(unsigned char LCD_Comment){
    GPIO_PORTA_DATA_R &= ~(RS+RW+E);     //RS=0,RW=0,E=0
    GPIO_PORTB_DATA_R = LCD_Comment;         //Write Command    
    GPIO_PORTA_DATA_R |= E;                  //RS=0,RW=0,E=1
    GPIO_PORTA_DATA_R &= ~(RS+RW);
    SysTick_Delay(19);                       //Enable width 230 ns
    GPIO_PORTA_DATA_R &= ~(RS+RW+E);     //RS=0,RW=0,E=0
    SysTick_Delay1ms(1);                     //Delay 1 ms
}
//********************************************************************************
//LCD1602 Write DATA to LCD module
//********************************************************************************
void Write_Data(unsigned char LCD_Data){
    GPIO_PORTB_DATA_R = LCD_Data;            //Write Data
    GPIO_PORTA_DATA_R |= RS+E;               //RS=1,RW=0,E=1
    GPIO_PORTA_DATA_R &= ~RW;
    SysTick_Delay(19);                       //Enable width 230 ns
    GPIO_PORTA_DATA_R &= ~(RS+RW+E);     //RS=0,RW=0,E=0
    SysTick_Delay1ms(1);                     //Delay 1 ms
}     
//********************************************************************************
//LCD1602 Initialize: 
//********************************************************************************
void LCD1602_Init(){        
    GPIO_PortAB_Init();
    SysTick_Delay1ms(15);                    //Delay 15ms   
    Write_Command(0x38);
    SysTick_Delay1ms(5);                     //Delay 5ms  
    Write_Command(0x38);
    SysTick_Delay1us(150);                   //Delay 150us      
        
    Write_Command(0x0C);            
    Write_Command(0x01);            
    Write_Command(0x06);
            
    SysTick_Delay1ms(50);                    //Delay 50ms    
}      
//********************************************************************************
//LCD1602 Clear Screen:
//********************************************************************************
void LCD1602_Clear(){
    Write_Command(0x01);    
}

//********************************************************************************
//LCD1602 Display String to Screen: 
//********************************************************************************
void LCD1602_DisplayString(unsigned char *str){      
    while(*str != 0){
        Write_Data(*str++);                  
    }    
}

//********************************************************************************
//LCD1602 Display Char to Screen: 
//********************************************************************************
void LCD1602_DisplayChar(unsigned char CHAR){      
    Write_Data(CHAR);      
}

//********************************************************************************
//LCD1602 Display Decimal number to Screen: 
//********************************************************************************
void LCD1602_DisplayDec(unsigned int number){   
    if(number >=10){
        LCD1602_DisplayDec(number/10);
        number = number%10;
    }
    LCD1602_DisplayChar(number+'0');   
}
//********************************************************************************
//LCD1602 Set Cursor Position on the Screen: 
//********************************************************************************
void LCD1602_DisplayPosition(unsigned char Line,unsigned int digit){   
    Write_Command(Line + digit);
}
//********************************************************************************
//LCD1602 support printf() function
//********************************************************************************
int fputc(int ch, FILE *f){
    if((ch == 10) || (ch == 13) || (ch == 27)){
        LCD1602_DisplayChar(13);
        LCD1602_DisplayChar(10);
        return 1;
    }
    LCD1602_DisplayChar(ch);
    return 1;
}


接下來也整理一下之前寫過的 PLL 和 SYSTICK 部分 , 把它們改為模組化 , 如 PLL_80MHZ.c 和 PLL_80MHZ.h 以及 SYSTICK.c 和 SYSTICK.h , 那麼以後要用到就可直接加入 Project 中, 不用再重寫了 . 如下 :

PLL_80MHZ.h :

/*********************************************************************************
**********************************************************************************
**                                  PLL_80MHZ.h                                 **
** 1. Board : TIVA-C TM4C123GXL Launchpad                                       **
** 2. Use PLL to set System Clock to 80MHZ                                      **
** 3. Michael @2014/05/10                                                       **
** 4. Version 1.0                                                               **
**********************************************************************************    
*********************************************************************************/

//********************************************************************************
//                  USE PLL to set system clock to 80MHZ
//********************************************************************************
//Initial PLL 80MHZ
void PLL_Init80MHZ(void);



PLL_80MHZ.c :

/*********************************************************************************
**********************************************************************************
**                                  PLL_80MHZ.c                                 **
** 1. Board : TIVA-C TM4C123GXL Launchpad                                       **
** 2. Use PLL to set System Clock to 80MHZ                                      **
** 3. Michael @2014/05/10                                                       **
** 4. Version 1.0                                                               **
**********************************************************************************    
*********************************************************************************/

#include <stdint.h>
#include "inc/tm4c123gh6pm.h"
#include "PLL_80MHZ.h"

//********************************************************************************
//                  USE PLL to set system clock to 80MHZ
//********************************************************************************
//Initial PLL 80MHZ
void PLL_Init80MHZ(void)
{    
    SYSCTL_RCC2_R |=  0x80000000;   
    SYSCTL_RCC2_R |=  0x00000800;  
    SYSCTL_RCC_R = (SYSCTL_RCC_R &~0x000007C0)   
                 + 0x00000540;   
    SYSCTL_RCC2_R &= ~0x00000070;  
    SYSCTL_RCC2_R &= ~0x00002000;  
    SYSCTL_RCC2_R |= 0x40000000;   
    SYSCTL_RCC2_R = (SYSCTL_RCC2_R&~ 0x1FC00000)  
                  + (4<<22);  
    while((SYSCTL_RIS_R&0x00000040)==0){};  
    SYSCTL_RCC2_R &= ~0x00000800;
}


SYSTICK.h

/*********************************************************************************
**********************************************************************************
**                                  SYSTICK.h                                   **
** 1. Support TIVA-C TM4C123GXL Launchpad                                       **
** 2. Assume System Clock is 80MHZ                                              **
** 3. Michael @2014/05/10                                                       **
** 4. Version 1.0                                                               **
**********************************************************************************    
*********************************************************************************/

//********************************************************************************
//Systick Initialize: 
//********************************************************************************
void SysTick_Init(void);

//********************************************************************************
//Systick 12.5 ns Delay: 
//********************************************************************************
void SysTick_Delay(unsigned long delay);

//********************************************************************************
//Systick 1 us Delay: 
//********************************************************************************
void SysTick_Delay1us(unsigned long delay);

//********************************************************************************
//Systick 1 ms Delay: 
//********************************************************************************
void SysTick_Delay1ms(unsigned long delay);


SYSTICK.c

/*********************************************************************************
**********************************************************************************
**                                  SYSTICK.c                                   **
** 1. Support TIVA-C TM4C123GXL Launchpad                                       **
** 2. Assume System Clock is 80MHZ                                              **
** 3. Michael @2014/05/10                                                       **
** 4. Version 1.0                                                               **
**********************************************************************************    
*********************************************************************************/

#include <stdint.h>
#include "inc/tm4c123gh6pm.h"
#include "SYSTICK.h"

//********************************************************************************
//                  SysTick Timer Assume System clock is 80MHZ
//********************************************************************************

//********************************************************************************
//Systick Initialize:
//********************************************************************************
//Initial Systick Timer
void SysTick_Init(void){
    NVIC_ST_CTRL_R = 0;               
    NVIC_ST_CTRL_R = 0x00000005;      
}

//********************************************************************************
//Systick 12.5 ns Delay: 
//********************************************************************************
// The delay parameter is 12.5ns in 80 MHz core clock.
void SysTick_Delay(unsigned long delay){
    NVIC_ST_RELOAD_R = delay-1;  
    NVIC_ST_CURRENT_R = 0;       
    while((NVIC_ST_CTRL_R&0x00010000)==0){ 
    }
}

//********************************************************************************
//Systick 1 us Delay: 
//********************************************************************************
// 80*12.5ns => 1 us
void SysTick_Delay1us(unsigned long delay){
    unsigned long i;
    for(i=0; i<delay; i++)    {
       SysTick_Delay(80);                 // wait 1us
    }
}

//********************************************************************************
//Systick 1 ms Delay: 
//********************************************************************************
// 80000*12.5ns => 1 ms
void SysTick_Delay1ms(unsigned long delay){
    unsigned long i;
    for(i=0; i<delay; i++)    {
       SysTick_Delay(80000);              // wait 1ms
    }
}



現在我們來新建一個 LCD1602 的 Project . 如何建立請參考 " 如何使用 Keil 建立一個新 project for TM4C123G Launchpad " 這篇文章 .

這裡有一個地方要注意 , 在 " Option of Target " 的 Target 頁面中 , 要勾選 Use MicroLib . 如下圖所示 . 因為我們要使用 printf 功能 .


記得要將 LCD1602.c , PLL_80MHZ.c  和 SYSTICK.c 加入 Project 中 , 如下 :

底下是 main.c :


/*********************************************************************************
**********************************************************************************
**                             LCD 1602 Module Experiment                       **
**  1. Write a driver of LCD1602 for TM4C123GXL Launchpad                       **
**  2. Modularize for Systick, PLL_80MHZ                                        **
**  3. LCD1602 with Launchpad detail connection please refer to LCD1602.h file  **
**  4. Initial LCD1602 module and display a string and a counter which count    **
**     per second.                                                              **
**  5. Michael @ 2014/05/10                                                     **
**********************************************************************************    
*********************************************************************************/

#include <stdint.h>
#include <stdio.h>
#include "inc/tm4c123gh6pm.h"
#include "LCD1602.h"
#include "SYSTICK.h"
#include "PLL_80MHZ.h"

void PLL_Init(void);

unsigned char msg1[] = {0x54,0x4D,0x34,0x43,0x32,0x33,0x47,0x58,0x4C}; //ASCII code for "TM4C123GXL"
unsigned char *msg[1] = {msg1};

int main(void){
    unsigned int counter=0;
    PLL_Init80MHZ();
    SysTick_Init();
    LCD1602_Init();
    LCD1602_DisplayPosition(LINE1,3);
    LCD1602_DisplayString(msg[0]);
    
    while(1)
    {
        LCD1602_DisplayPosition(LINE2,7);
        LCD1602_DisplayDec(counter);        
        counter ++;
        SysTick_Delay1ms(1000);
    }
}
// Use printf function 
// To use this, please comment out above int main() and re-name below int main1() to int main()
int main1(void){
    unsigned int counter=0;
    PLL_Init80MHZ();
    SysTick_Init();
    LCD1602_Init();
    LCD1602_DisplayPosition(LINE1,3);    
    printf("TM4C123GXL");
    while(1)
    {
        LCD1602_DisplayPosition(LINE2,7);        
        printf("%d",counter);
        counter ++;
        SysTick_Delay1ms(1000);
    }
}


這裡說明一下 , 這裡有兩個 main function . int main() 是用一般的方式顯示字串和計數 . int main1()則是採用 printf() function . 這會方便許多 . 不過注意要加入 #include . 要試第二個也就是 int main1() 時 , 就是把第一個也就是 int main() 全部註解掉 . 然後把 int main1() 改為 int main() 就可以了 . 這一篇就寫到這裡了 . 

參考文獻 :
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

2014年4月25日 星期五

UART

這一篇我們將透過 UART 與電腦互動 . 想法是利用電腦鍵盤輸入 ' 0 ' LED 燈全滅, 輸入 ' 1 ' 點亮紅色 LED 燈 , 輸入 ' 2 ' 點亮藍色 LED 燈 , 輸入 ' 3 ' 點亮綠色 LED 燈 .

TM4C123GH6PM 總共有 8 組 UART . 這次是使用 UART0 , 對應的接腳是 PA0 和 PA1 , 在 Launchpad 上是已經透過 USB 接到電腦了. 所以不需要另外接線 . LEDs 部分 還是直接使用 Launchpad 上的 PF1, PF2 和 PF3 . System Clock 部分還是使用 PLL 設定最高時脈 80MHZ .

這次會需要使用超級終端機來與 Launchpad 互動. 如果你的作業系統是 XP , 則本身就已經有超級終端機了, 如果不是 , 那也可以使用 Putty  這個軟體 .

在使用 UART 之前 , 還是先來了解一下一些相關的暫存器 . 還記得 TM4C123GH6PM 預設所有的 I/O 的 Clock 都是關閉的 , 所以第一部要啟動 UART0 的 Clock 以及其對應的 GPIO Pin, 也就是 PA0 和 PA1 . 先來看一下 RCGC1 .

由上圖中可以看出, UART0 是 bit0, 也就是將 RCGC1 暫存器的 bit0 寫入 1 , 就啟動了 UART0 的 Clock 了 . 還有一點要說明的是也可以使用另外一個暫存器 RCGCUART .

在此暫存器中 , R7 代表 UART7, R6 代表 UART6, R5 代表 UART5, 以此類推 R0 代表 UART0, 所以如果是使用此暫存器 , 也是將 bit0 寫入 1 . 這兩個暫存器的差別只是 RCGC1 是為了與以前的 MCU 相容 , 以方便程式移植 . 如果沒有這方面的考量 , Data Sheet 是建議使用 RCGCUART 這個暫存器 . 然而並非只有UART是如此 , 其他所有的 I/O interface 都是一樣的 , 例如 GPIO, TIMER, I2C, CAN, SSI(SPI), ADC.....等 .

UARTCTL 暫存器是一個控制暫存器 , 我們這次只使用 bit0 , Enable 和 Disable UART. 不過記得設定 UART (例如 Baudrate) 之前要把它Disable , 設定好了再 Enable 它 .


UARTIBRD 和 UARTFBRD 這兩個暫存器是設定 Baudrate 的 . UARTIBRD 是整數部分 . UARTFBRD 是分數部分 . 計算公式如下 :

BRD = BRDI + BRDF = UARTSysClk / (ClkDiv * Baud Rate)
UARTFBRD[DIVFRAC] = integer(BRDF * 64 + 0.5)

BRDI 是整數部分 , BRDF 是分數部分 . UARTSysClk 是連接到UART的系統時脈 , ClkDiv 為 16 或者為 8 . 這部分可在 UARTCTL 暫存器的 bit 5 (HSE) 中設定 , 如果 HSE 這個 bit Clear (0), 則 ClkDiv 為 16 , 如果 HSE 這個 bit Set (1), 則 ClkDiv 為 8 . 在這裡我們使用 16 . 第二個算式是取分數的部分 , 舉例來說 : 如果系統時脈為 80MHZ, BaudRate 為 115200 bits/Sec. 

80MHZ / (16*115200) = 43.4027  (IBRD = 43)
0.4027 * 64 + 0.5 = 26                  (FBRD = 26)

由以上計算結果就可以設定 UARTIBRD 和 UARTFBRD 這兩個暫存器了 :

UART0_IBRD_R = 43;   
UART0_FBRD_R = 26;                  

UARTLCRH 暫存器是設定資料長度 , Parity, Stop bit 等等. 暫存器的內容如下 :


WLEN ( bit 6 和 bit 5 ) 設置 0x0 是 5 bits , 0x1 是 6 bits , 0x2 是 7 bits , 0x3 是 8 bits . FEN ( bit 6 )是設置是否啟用 FIFO buffer .  STP2 ( bit 3 )  是設置 Stop bit . ' 0 ' 是 One Stop bit , ' 1 ' 是 Two Stop bit . 一般的應用大概會是 8 bits 資料長度 , No Parity , One Stop bit , 再加上起用 FIFO . 也就是 0x0000.0070 .

到了這裡我們來看看 UART0 初始化的程式 :

void UART0_Init(void)
{
    SYSCTL_RCGCUART_R |= 0x00000001;
    SYSCTL_RCGCGPIO_R |= 0x00000001;
    UART0_CTL_R &= ~0x00000001;
    UART0_IBRD_R = 43;
    UART0_FBRD_R = 26;
    UART0_LCRH_R = 0x00000070;
    UART0_CTL_R |= 0x00000001;
    
    GPIO_PORTA_AFSEL_R |= 0x03;
    GPIO_PORTA_DEN_R |= 0x03;
    GPIO_PORTA_PCTL_R = (GPIO_PORTA_PCTL_R&0xFFFFFF00)+0x00000011;
    GPIO_PORTA_AMSEL_R &= ~0x03;
}

除了設定 UART0 之外還設定了 PORTA , 這是因為 PA0 和 PA1 的替代功能是 UART0 , 所以也必須設定啟用這兩個 PINs , 另外也看到了設置替代功能的暫存器也做了些設定 . 這部分有一點複雜 , 這裡稍微說明一下 , 先看下表 ( 這裡只截取一部分 , 完整的表格可以再 Data Sheet GPIO 中找到 ) :


先看 UART0 Rx 和 UART0 Tx 分別對應到 PA0 和 PA1 , 再看對應到上面的數字是 1 . 這就是我們要寫入暫存器 PCTL 的數字 , 那寫在 PCTL 的哪個位置呢 ? 我們再來回顧一下 PCTL 暫存器 :


從 PMC7 ~ PMC0 分別代表著 GPIO Pin 7 ~ GPIO Pin 0. 那我們使用的是 PA0 和 PA1 , 所以就是寫入 PMC1 和 PMC0 這兩個位置 . 那寫入什麼值呢 ? 剛剛提到就是UART0 Rx 和 UART0 Tx 對應到上面的數字 1 . 所以就是 GPIO_PORTA_PCTL_R = 0x0000.0011 , 這樣是不是很清楚了呢 ? 再舉個例子 : 假設我們要使用 CAN1 , 從表中可以看出 GPIO 的部分對應的也是 PA0 和 PA1 , CAN1Rx 和 CAN1Tx 對應到上面的數字是 8 . 所以就是 GPIO_PORTA_PCTL_R = 0x0000.0088 . 或許你想問那 "(GPIO_PORTA_PCTL_R&0xFFFFFF00) " 是做什麼 ? 其實很簡單就是 Friendly Code . 不去動到其他的 bits 並且先把 PMC1 和 PMC0 清為 0 .

UART0 到了這裡算是初始化完成 . 那我要傳送和接收資料怎辦 ? UART 有一個 UART Flag 暫存器 , 我們可以判讀這個暫存器的狀態來做傳送或接收資料 . 以下是 UARTFR 暫存器的內容 :


在這裡我們會用到 TXFF 和 RXFE , TXFF 為 0 時 FIFO 未滿 , 為 1 時 FIFO 已滿 , RXFE 為 0 時 FIFO 不是空的 , 為 1 時 FIFO 是空的 . 根據此特性 , 我們就可以做接收和傳送了. ( 此處採用等待的方式. 比較好的方式是用中斷的方法, 這部分等以後寫到中斷時再說明)

void UartWrite(char *pstr){
    while(*pstr != 0){
      Transmitter(*pstr++);        
    }
}
unsigned char Receiver(void){
    while((UART0_FR_R&0x10) != 0){};
    return UART0_DR_R&0xFF;
}
void Transmitter(unsigned char data){
    while((UART0_FR_R&0x20) != 0){};
    UART0_DR_R = data;
}

UART 部分到這裡總算完成了 , 那接下來就來看一下完整的程式吧 :

#include <stdint.h>
#include "inc/tm4c123gh6pm.h"

void PLL_Init(void);
void UART0_Init(void);
void PortF_Init(void);
void UartWrite(char *pstr);
unsigned char Receiver(void);
void Transmitter(unsigned char data);

int main(void){
    unsigned char command;
    PLL_Init();                                     
    UART0_Init();                               
    PortF_Init();
    
    UartWrite("This program control Leds through UART.\r\n");
    
    while(1)
    {
        UartWrite("Enter Command \'0\',\'1\',\'2\',\'3\':");        
        command = Receiver();
        Transmitter(command);
        UartWrite("\r\n");
        
        switch (command){
            case '0':                
                            GPIO_PORTF_DATA_R = 0x00;
                            break;
            case '1':                
                            GPIO_PORTF_DATA_R = 0x02;
                            break;
            case '2':                
                            GPIO_PORTF_DATA_R = 0x04;
                            break;
            case '3':                
                            GPIO_PORTF_DATA_R = 0x08;
                            break;
            default:
                            UartWrite("Wrong command ! \r\n");                            
                            break;
        }
    }
    
}
void UART0_Init(void)
{
    SYSCTL_RCGCUART_R |= 0x00000001;
    SYSCTL_RCGCGPIO_R |= 0x00000001;
    UART0_CTL_R &= ~0x00000001;
    UART0_IBRD_R = 43;
    UART0_FBRD_R = 26;
    UART0_LCRH_R = 0x00000070;
    UART0_CTL_R |= 0x00000001;
    
    GPIO_PORTA_AFSEL_R |= 0x03;
    GPIO_PORTA_DEN_R |= 0x03;
    GPIO_PORTA_PCTL_R = (GPIO_PORTA_PCTL_R&0xFFFFFF00)+0x00000011;
    GPIO_PORTA_AMSEL_R &= ~0x03;
}
void PLL_Init(void)
{    
  SYSCTL_RCC2_R |=  0x80000000;  
  SYSCTL_RCC2_R |=  0x00000800;  
  SYSCTL_RCC_R = (SYSCTL_RCC_R &~0x000007C0) + 0x00000540;   
  SYSCTL_RCC2_R &= ~0x00000070;  
  SYSCTL_RCC2_R &= ~0x00002000;  
  SYSCTL_RCC2_R |= 0x40000000; 
  SYSCTL_RCC2_R = (SYSCTL_RCC2_R&~ 0x1FC00000) + (4<<22);   
  while((SYSCTL_RIS_R&0x00000040)==0){};   
  SYSCTL_RCC2_R &= ~0x00000800;
}
void PortF_Init(void)
{
  volatile unsigned long delay;
  SYSCTL_RCGC2_R |= 0x00000020;     
  delay = SYSCTL_RCGC2_R;  
  GPIO_PORTF_AMSEL_R &= ~0x0E;      
  GPIO_PORTF_PCTL_R &= ~0x0000FFF0;                  
  GPIO_PORTF_DIR_R |= 0x0E;         
  GPIO_PORTF_AFSEL_R &= ~0x0E;         
  GPIO_PORTF_DEN_R |= 0x0E;         
}
void UartWrite(char *pstr){
    while(*pstr != 0)    {
        Transmitter(*pstr++);        
    }
}
unsigned char Receiver(void){
    while((UART0_FR_R&0x10) != 0){};
    return UART0_DR_R&0xFF;
}
void Transmitter(unsigned char data){
    while((UART0_FR_R&0x20) != 0){};
    UART0_DR_R = data;
}


了解了 UART 的設定後 , 整個程式看起來就簡單多了 . 先初始化 PLL , 80MHZ , 再初始化 UART0 , 最後再初始化 PORTF . 然後就等待輸入 0 ~ 3 , 再用 Switch - Case 來判斷輸入的值並點亮相對應的 LED . 好了 Compiler and Download !

接著 , 我們先查詢一下 Launchpad 連接的 COM Port 是哪一個 . 打開裝置管理員 , 點擊連接埠(COM 和 LPT) , 找到 Stellaris Virtual Serial Port(COMx) 如下圖 : (我的是 COM5)


然後打開 putty , 先點選 Serial , 在 Serial line 輸入你的 COM number , 我的例子是 COM5 , Speed 的地方輸入 115200 . 在 Saved Sessions 的地方可以輸入一個名稱 , 例如 TM4C123GXL Launchpad , 按下 Save . 以後可以直接點選此名稱再按 Load 就好了 .


接著點選左下角的 Serial , 把右邊的 Flow control 選 None . 然後按下 Open , 就開啟 Putty 了 .


接下來按一下 Launchpad 上的 Reset 鍵 . 在Putty 的視窗上出現了 "Enter Command 0,1,2,3:" 此時 Launchpad 正等待你的輸入 , (如下圖)


先輸入 1 , 觀察 Launchpad 上紅色 LED 點亮了 . 再輸入 2 , 點亮了藍色 LED 燈 , 輸入  3  點亮綠色 LED 燈 , 輸入 0 看看, 嗯 ,  LED 燈全滅了 . 輸入 4 看看 ,  Launchpad 回應 Wrong command ! 要我們重新輸入 . 不錯 ! 我們成功了 ! 




參考文獻 :
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

2014年4月5日 星期六

按鍵控制LED燈 -- EK-TM4C123G LaunchPad

上一篇玩過了LED閃爍, 這一次我們 加上按鍵來控制. 想法是 : LaunchPad啟動時紅色LED 不斷閃爍, 間隔 0.5秒, 直到按下按鍵, 黃色 LED點亮2秒, 接著點亮綠色LED , 持續5秒. 之後再進入紅色 LED 不斷閃爍, 間隔 0.5秒 ,  等待下一個按鍵按下, 就像是路口的行人紅綠燈 .

想法有了, 那麼來安排一下硬體的規劃吧 . 我們還是利用 LanchPad 上現有的 LED 和 Button . 還記的 PF0 接 SW2, PF4 接 SW1. 這一次用右下角那顆 PF0 (SW2) , 順便可以練習如何解鎖. LED是PF1(紅色) , PF2(藍色) , PF3(綠色) . 等等 ! 不是說要黃色嗎 ? 這 3 顆 LED 沒有黃色啊 ! 沒關係 , 記得黃色是等於紅色 + 綠色 , 所以我們只要同時點亮 PF1 和 PF3 就可以了 . 詳細的腳位請參考底下這三張圖 .


TM4C123G的 PF4 ~ PF0分別接到SW2, Red LED, Blue LED, Green LED and SW1


RGB LEDs 電路


SW1 & SW2 電路


因為上次使用 TivaWare時, 啟動PLL並設定MCU 的時脈為 40MHZ, 所以這次想用直接控制暫存器的方式起動 PLL (相鎖迴路) , 並控制 MCU 的時脈為 80MHZ (此為TM4C123GH6PM的最大時脈) . 還有, 想改用SysTick的計數來得到更精準的 Delay 時間.

那麼, 先從簡單的來吧 ! 首先, 我們先設定 GPIO PORT F :

void PortF_Init(void)
{
  volatile unsigned long delay;
  SYSCTL_RCGC2_R |= 0x00000020;     
  delay = SYSCTL_RCGC2_R;           
  GPIO_PORTF_LOCK_R = 0x4C4F434B;   
  GPIO_PORTF_CR_R = 0x0F;  
  GPIO_PORTF_AMSEL_R &= ~0x0F;      
  GPIO_PORTF_PCTL_R &= ~0x0000FFFF; 
  GPIO_PORTF_DIR_R &= ~0x01;                
  GPIO_PORTF_DIR_R |= 0x0E;         
  GPIO_PORTF_AFSEL_R &= ~0x0F;      
  GPIO_PORTF_PUR_R |= 0x01;         
  GPIO_PORTF_DEN_R |= 0x0F;         
}

嗯 ! 開始有點熟悉了.... 不過多了幾行設定, 說明如下: (其他的部分, 前一篇已作過說明, 不再贅述)

GPIO_PORTF_LOCK_R = 0x4C4F434B;   
GPIO_PORTF_CR_R = 0x0F;

因為 PF0 預設是鎖住的狀態 , 要使用它之前要先將它解鎖 , 根據 Data Sheet , 也就是對 GPIOLOCK 這個暫存器寫入 0x4C4F434B . GPIOCR 這個暫存器叫做 Commit 暫存器 . 只有對  GPIOLOCK 這個暫存器解鎖了, GPIOCR 這個暫存器的設定才有效 . 然而 GPIOLOCK 的 Pin 腳解鎖了, GPIOCR 這個暫存器相對應的 bit要設定為 1 . 這樣才可以做其他的設定 , 像是 GPIOAFSEL, GPIOPUR, GPIOPDR, GPIODEN .

GPIO_PORTF_PUR_R |= 0x01;

GPIOPUR 這個暫存器是設定 Pin 腳是否接 Pull High 電阻, 當Set (1) 時 , 為 Pull High 電阻 enable , Clear(0)時 , 為 Pull High 電阻 disable . 因為 我們設 PF0 是 input , SW2是負邏輯動作( 0 為 enable ), 為避免SW2未按下時, PF0 的電位不確定而造成誤動作, 所以把它設定在 MCU 內部接 Pull High 電阻 . 另外一個就是GPIOPDR了, 顧名思義也就是 Pull Down, 在使用正邏輯的 Switch 時設定.

這裡補充一下 , 怎麼會有2個 DIR 的設定呢? 第一個 GPIO_PORTF_DATA_R &= ~0x01 是將PF0設為輸入, 所以把 bit 0 寫入 " 0 " , 第二個 GPIO_PORTF_DATA_R |= 0x0E 是將 PF1, PF2, PF3設為輸出 , 所以把 bit 1, 2, 3 寫入 " 1 " . 你可能會說 Data Sheet 上說明 GPIODIR 暫存器的 bit 7 ~ 0 , Reset 後是 0x00, 所以第一個是不是多此一舉呢 ? 這是關於前面提過 Friendly Code 的部分 , 如果確定程式只有這一個地方設定 PORTF, 當然第一個可以不寫 . 然而如果是一個大程式, 而你只負責其中一小部分 , 難保其他人也設定了 PORTF bit 0, 並且設為 " 1 " . 此時第二個  GPIO_PORTF_DATA_R |= 0x0E 並不會把 PF0 更改....那麼...結果就可想而知了.所以養成好習慣還是不錯的 .

PORT F 設定完成了, 那麼開始設定 80MHZ 的 System Clock 吧. 不過在開始之前, 還是先了解一下 TM4C123GH6PM 的 Clock Tree 如下圖 : (設置的路徑會如紅色的標線)

系統時鐘設置部分有兩個暫存器, RCC 和 RCC2 : (如下圖)



如果要設置 80MHZ 要選擇 RCC2 暫存器 . 設置步驟如下 :

  1. 設定 RCC2  -- USERCC2 (bit 31) : 寫入 1 , 也就是 0x8000.0000 .
  2. 設定 RCC2  -- BYPASS2 (bit 11) : 寫入 1 , 也就是 0x0000.0800 .
  3. 設定 RCC   -- XTAL (bit 10 ~ 6) : 寫入 0x15 (16MHZ) , , 也就是 0x0000.0540 . 0x15 等於 10101b, 依序放入 bit 10 ~ bit 6 為 101.0100.0000 就等於 0x540 .
  4. 清除 RCC2  -- OSCSRC2 (bit 6 ~ 4) : 寫入 0x0 (選擇 main OSC). 也就是 ~0x0000.0070 .
  5. 清除 RCC2  -- PWRDN2 (bit 13) : 寫入 0 (啟動 PLL). 也就是 ~0x0000.2000 .
  6. 設定 RCC2  -- DIV400 (bit 30) : 寫入 1. 也就是 0x4000.0000 .
  7. 設定 RCC2  -- SYSDIV2 (bit 28 ~ 22) : 寫入 n. 它會除以 n+1, 以 80MHZ 為例 , 寫入 4, 則 400MHZ/(4+1) = 80 MHZ .
  8. 等待 PLL 穩定 , 讀取 SYSCTL_RIS_R 的 Bit 6, 如果是 1 就表示穩定了. 
  9. 清除 RCC2  -- BYPASS2 (bit 11) : 寫入 0. Enable 使用 PLL.

第三步驟提到 16MHZ 是寫入 0x15 那如果外接的 Crystal 是其他的頻率可以查下表 :


我們來看程式的部分 :

void PLL_Init(void)
{    
  SYSCTL_RCC2_R |=  0x80000000;  
  SYSCTL_RCC2_R |=  0x00000800;  
  SYSCTL_RCC_R = (SYSCTL_RCC_R &~0x000007C0) + 0x00000540;   
  SYSCTL_RCC2_R &= ~0x00000070;  
  SYSCTL_RCC2_R &= ~0x00002000;  
  SYSCTL_RCC2_R |= 0x40000000; 
  SYSCTL_RCC2_R = (SYSCTL_RCC2_R&~ 0x1FC00000) + (4<<22);   
  while((SYSCTL_RIS_R&0x00000040)==0){};   
  SYSCTL_RCC2_R &= ~0x00000800;
}

那如果想要設置成 50MHZ 就直接把 4 改成 7 , 400MHZ/(7+1) = 50MHZ, 所以想要更換頻率只需要更改 SYSDIV2 的值就可以了. 不過記得 TM4C123GH6PM 最大頻率為 80MHZ.

接下來說一下 SysTick 計數. SysTick 是一個 24 bits 的下數計數器, 這次我們沒有用到中斷部分 , 以下是這次使用到的暫存器 :

  • NVIC_ST_CTRL_R 
  • NVIC_ST_RELOAD_R
  • NVIC_ST_CURRENT_R
NVIC_ST_CTRL_R 暫存器是設定 Enable/Disable, Interrupt 及 flag 狀態 , 詳細如下 :

因為這次我們沒有用到中斷部分 , 所以設成 0x05 . 另外 NVIC_ST_RELOAD_R 暫存器 , 顧名思義 , 就是我們要計數器從甚麼值開始下數 , 最大值為 0x00FFFFFF. NVIC_ST_CURRENT_R 暫存器 , 當計數到 0 時 ,  NVIC_ST_CTRL_R 暫存器 bit 16 (count) 會設成 1 . 下一個 Clock 會把 NVIC_ST_RELOAD_R 暫存器的值重新載入. 此時NVIC_ST_CTRL_R 暫存器 bit 16 (count) 會清為 0 . 以下是 SysTick Delay 的程式內容 :

void SysTick_Init(void)
{
  NVIC_ST_CTRL_R = 0;               
  NVIC_ST_CTRL_R = 0x00000005;     
}

void SysTick_Wait(unsigned long delay)
{
  NVIC_ST_RELOAD_R = delay-1;  
  NVIC_ST_CURRENT_R = 0;       
  while((NVIC_ST_CTRL_R&0x00010000)==0){ }
}

void SysTick_Wait10ms(unsigned long delay)
{
  unsigned long i;
  for(i=0; i<delay; i++){
    SysTick_Wait(800000);  
  }
}

SysTick_Wait() 是由 delay 這個變數來決定 delay 的時間 , 以 80MHZ 來說, 一個 Clock 是 12.5ns (1/80MHZ) , 800000 x 12.5ns = 10ms. 所以在 SysTick_Wait10ms() 中是帶入 800000 這個值.

以下是完整的程式 :

#include <stdint.h>
#include "inc/tm4c123gh6pm.h"


void PLL_Init(void);
void SysTick_Init(void);
void SysTick_Wait(unsigned long delay);
void SysTick_Wait10ms(unsigned long delay);
void PortF_Init(void);

int main(void)
{        
    PLL_Init();                                     
    SysTick_Init();                                
    PortF_Init();                                
    
    while(1)
    {                        
        GPIO_PORTF_DATA_R ^= 0x02;
        SysTick_Wait10ms(50); 
        
        if ((GPIO_PORTF_DATA_R&0x01) == 0x00)
        {
            SysTick_Wait10ms(1);
            if ((GPIO_PORTF_DATA_R&0x01) == 0x00){
                GPIO_PORTF_DATA_R = 0x0A;
                SysTick_Wait10ms(200); 
                GPIO_PORTF_DATA_R &= ~0x02;            
                SysTick_Wait10ms(500); 
                GPIO_PORTF_DATA_R &= ~0x08;
            }
        }
    }
}

void PLL_Init(void)
{    
  SYSCTL_RCC2_R |=  0x80000000;  
  SYSCTL_RCC2_R |=  0x00000800;  
  SYSCTL_RCC_R = (SYSCTL_RCC_R &~0x000007C0) + 0x00000540;   
  SYSCTL_RCC2_R &= ~0x00000070;  
  SYSCTL_RCC2_R &= ~0x00002000;  
  SYSCTL_RCC2_R |= 0x40000000; 
  SYSCTL_RCC2_R = (SYSCTL_RCC2_R&~ 0x1FC00000) + (4<<22);   
  while((SYSCTL_RIS_R&0x00000040)==0){};   
  SYSCTL_RCC2_R &= ~0x00000800;
}

void SysTick_Init(void)
{
  NVIC_ST_CTRL_R = 0;               
  NVIC_ST_CTRL_R = 0x00000005;     
}

void SysTick_Wait(unsigned long delay)
{
  NVIC_ST_RELOAD_R = delay-1;  
  NVIC_ST_CURRENT_R = 0;       
  while((NVIC_ST_CTRL_R&0x00010000)==0){ }
}

void SysTick_Wait10ms(unsigned long delay)
{
  unsigned long i;
  for(i=0; i<delay; i++){
    SysTick_Wait(800000);  
  }
}

void PortF_Init(void)
{
  volatile unsigned long delay;
  SYSCTL_RCGC2_R |= 0x00000020;     
  delay = SYSCTL_RCGC2_R;           
  GPIO_PORTF_LOCK_R = 0x4C4F434B;   
  GPIO_PORTF_CR_R = 0x0F;   
  GPIO_PORTF_AMSEL_R &= ~0x0F;      
  GPIO_PORTF_PCTL_R &= ~0x0000FFFF; 
  GPIO_PORTF_DIR_R &= ~0x01;                
  GPIO_PORTF_DIR_R |= 0x0E;         
  GPIO_PORTF_AFSEL_R &= ~0x0F;      
  GPIO_PORTF_PUR_R |= 0x01;         
  GPIO_PORTF_DEN_R |= 0x0F;         
}


好了, 按下 F7 編譯 然後下載到 LaunchPad , 按一下 Reset 鍵 , 看到紅色 LED 在閃爍了 , 按一下 SW2 , 嗯 , 如所預期的 黃燈 2 秒, 綠燈 5 秒 ....就在此刻心中有種莫名的快樂與感動....


參考文獻 :
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

2014年3月23日 星期日

TM4C123G LaunchPad 第一個程式 - LED 閃爍

在上一篇中我們在Keil中建立了一個新的專案. 但是還沒開始進行Blinky的程式. 這裡我們把這個程式完成, 我們先用直接控制暫存器的方法, 控制LED閃爍, 第二部分再用TIveWare裡的API來實現.

在開始進行程式之前, 先來了解一下LaunchPad的GPIO Ports. TM4C123GH6PM總共有6個GPIO Port, 43Pins. PortA(PA0~PA7), PortB(PB0~PB7), PortC(PC0~PC7), PortD(PD0~PD7), PortE(PE0~PE5) 和 PortF(PF0~PF4). 但在LaunchPad上並不是每個腳位都有接出來(LaunchPad只有40 Pins). 有些腳位除了GPIO的功能外 , 還有替代功能 (Alternate Function) 像是 UART , ADC , SSI(SPI) , CAN , I2C , USB...等 . 詳細部分可以參考TM4C123GH6PM 的 data sheet .

TM4C123GH6PM 腳位

在LaunchPad上下端有兩個Switch, SW1和SW2, SW1是位於左下角, 連接PF4, SW2是位於右下角,連接PF0. 這兩個都是負邏輯, 也就是平常是接3.3V(邏輯1), 按下按鍵時是接地(邏輯0). 

另外在 J4 和 J2 的上方, Reset 按鍵的下方 , 有顆高亮度的三色LED(RGB) . 分別接到 PF1 (紅色 LED) , PF2 (藍色 LED) , PF3 (綠色 LED) . 這也是我們等一下要控制的 LED , 就不需要外接了 .

有個部分要注意的是有6支腳平常是鎖住的 : 
PD7, PF0 , PC0 ~PC3 . 解開要對LOCK暫存器下 0x4C4F434B .
PC0 ~ PC3 : 這部分是給JTAG/SWD使用, 負責程式下載到Launchpad和Debug用的 . 建議除非腳位真的不夠用了, 否則還是不要用這幾腳 , 免得操作不當 , 屆時無法下載和Debug .

底下是LaunchPad的 J1 , J2 , J3 , J4 的腳位說明 , 注意在左邊的是 J1 和 J3 , 右邊的是 J4 和 J2 . 這是LaunchPad上的排列. 


J1
J3
J4
J2
1
3.3V
VBUS
PF2
GND
2
PB5
GND
PF3
PB2
3
PB0
PD0
PB3
PE0
4
PB1
PD1
PC4
PF0
5
PE4
PD2
PC5
RST
6
PE5
PD3
PC6
PB7
7
PB4
PE1
PC7
PB6
8
PA5
PE2
PD6
PA4
9
PA6
PE3
PD7
PA3
10
PA7
PF1
PF4
PA2
               LaunchPad的 J1 , J2 , J3 , J4 的腳位

在使用GPIO之前要先打開它的Clock, 平常是關閉的, 這樣可以節省電源的消耗 . 然而 Clock 的部分還分PIOSC , MAINOSC , LFIOSC , 休眠Clock Source  , PLL 等 . 這部分這裡先不說了 , 不過記得內部OSC 是16MHz +/- 1%.

直接控制暫存器 :

現在來看看程式的部分 ~~

#include <stdint.h>
#include "inc/tm4c123gh6pm.h"

//延遲100ms
void Delay(void){
  volatile unsigned long time;
  time = 145448;  
  while(time){
    time--;
  }
}
int main(){
    volatile unsigned long delay;
    SYSCTL_RCGC2_R |= 0x20;            //啟動PORTF的Clock
    delay = SYSCTL_RCGC2_R;            //3~5 Clock等待PORTF的Clock穩定
    GPIO_PORTF_AMSEL_R &= ~0x0E;        //Disable PA3-PA1的Analog功能
    GPIO_PORTF_PCTL_R &= ~0x0000FFF0;    //設定PA3-PA1為GPIO功能
    GPIO_PORTF_DIR_R |= 0x0E;        //設定PA3-PA1為輸出
    GPIO_PORTF_AFSEL_R &= ~0x0E;        //Disable PA3-PA1的Alternate功能
    GPIO_PORTF_DEN_R |= 0x0E;        //Enable PA3-PA1的Digital I/O功能
    
    while(1){
        GPIO_PORTF_DATA_R |= 0x02;      //點亮紅色LED
        Delay();
        GPIO_PORTF_DATA_R &= ~0x02;     //熄滅紅色LED
        Delay();
    }
}


還記得前面說過預設所有的GPIO Clock都是關掉的. 所以我們要使用任何一個PORT之前都要先將它打開, 也就是

SYSCTL_RCGC2_R |= 0x20;            //啟動PORTF的Clock

RCGC2是一個控制GPIO Clock的暫存器, 請看下圖:

我們要啟動PORTF的Clock, PortF是在 bit 5, 也就是 b0010.0000 (放了一個 " . "是為了方便閱讀) . 所以就等於 0x20. 如果要啟動PORTA的話, 就是 b0000.0001 = 0x01. 那為什麼是 " |= " 而不是 " = " 就好 ? 我們稱這個叫 " Friendly code ", 當我們使用 " |= " 時 , 只會改變我們設定的 bit , 其他的 bit 維持原來的設定, 這樣的好處是當你有很多的bit要獨立控制時就不會互相影響. 另外還有一個 " &= " 這是把相對應的bit清為 0 , 記得要clear的腳位要加個反向符號 " ~ " .

啟動PORTF的Clock之後, 需要等待3~5個Clock, 也就是給Clock時間穩定.

delay = SYSCTL_RCGC2_R;            //3~5 Clock等待PORTF的Clock穩定

而我們這次是把 PortF 當數位腳使用, 所以Clear AMSEL 暫存器 : (Clear意思是把 bit 設為 0, Set的意思是把 bit 設為 1 .) AMSEL 暫存器相對應的 bit 如果是 0 : Disable Analog Function , 如果是 1 : Enable Analog Function .

GPIO_PORTF_AMSEL_R &= ~0x0E;

PCTL 暫存器是搭配 AFSEL使用. 當你使用替代功能時(Alternate Function) 設定腳位的.這個例子中並未使用替代功能, 所以把相對應的腳為Clear. (比較特殊是它是4個bits為一個腳位.所以你會看到它是 0x0000.FFF0.如下圖)

GPIO_PORTF_PCTL_R &= ~0x0000FFF0;

PMC7 : Pin 7, PMC6 :Pin 6.......PMC0 : Pin 0.

AFSEL 暫存器是替代功能(Alternate Function) 設定, (Pin7 ~ Pin0), 一樣 , 這個例子中並未使用替代功能, 所以把相對應的腳為Clear.

GPIO_PORTF_AFSEL_R &= ~0x0E;

DIR 暫存器是設定I/O Pin 輸入或輸出. 0 為輸入 , 1 為輸出 . 因為PF3,PF2,PF1要控制LED, 所以設為輸出.

GPIO_PORTF_DIR_R |= 0x0E;

DEN 暫存器是Enable 數位I/O功能 ,  0 為Disable , 1 為Enable 在此我們Enable PF3,PF2,PF1三個腳位.

GPIO_PORTF_DEN_R |= 0x0E;

到這裡算是把 PORTF 的 PF3, PF2, PF1設定完成. 當然GPIO的暫存器不只這些, 例如 : PUR (PULL UP 暫存器) , PDR(PULL DOWN 暫存器), R2R 暫存器, R4R 暫存器, R4R 暫存器, LOCK 暫存器....嗯..慢慢來...

設定好了後, 現在我們利用 DATA 暫存器來控制 PF1(也就是紅色LED). 加了 Delay()是讓我們能看清楚一亮一滅 .

while(1){
        GPIO_PORTF_DATA_R |= 0x02;      //點亮紅色LED
        Delay();
        GPIO_PORTF_DATA_R &= ~0x02;     //熄滅紅色LED
        Delay();
    }

好啦, 存檔並按下 Build (F7), 然後下載到 LaunchPad, 還記得下載的按鈕嗎?


按一下Launchpad的reset 鍵, 這時就看到了紅色LED快速的閃爍了. 希望閃爍的速度慢一點可以修改Delay()函式裡 time = 145448  數字增加就變慢了. 也可以點亮其他的LED,  藍色LED是 PF2, 所以是 0x04, 綠色是PF3, 所以是 0x08, 黃色 ? 紅色+綠色 = 0x0A, 白色? 紅色+綠色+藍色 = 0x0E...可以多試試其他顏色.

我想說明一下那些SYSCTL_RCGC2_R, GPIO_PORTF_DATA_R ....等等. 其實這些就是暫存器位址. 已經預先定義好在tm4c123gh6pm.h裡面了. 例如:

#define GPIO_PORTF_DATA_R       (*((volatile unsigned long *)0x400253FC))
#define GPIO_PORTF_DIR_R        (*((volatile unsigned long *)0x40025400))
#define GPIO_PORTF_AFSEL_R      (*((volatile unsigned long *)0x40025420))
#define GPIO_PORTF_PUR_R        (*((volatile unsigned long *)0x40025510))
#define GPIO_PORTF_DEN_R        (*((volatile unsigned long *)0x4002551C))
#define GPIO_PORTF_LOCK_R       (*((volatile unsigned long *)0x40025520))
#define GPIO_PORTF_CR_R         (*((volatile unsigned long *)0x40025524))
#define GPIO_PORTF_AMSEL_R      (*((volatile unsigned long *)0x40025528))
#define GPIO_PORTF_PCTL_R       (*((volatile unsigned long *)0x4002552C))
#define SYSCTL_RCGC2_R          (*((volatile unsigned long *)0x400FE108))

這些可以在data sheet裡查到, 比如說 GPIO_PORTF_DIR_R, 在data sheet(March 03, 2014)的663頁 中, PORTF(APB) BASE的位址是0x4002.5000 , DIR 暫存器位址是offset 0x400 , 也就是BASE + OFFSET 等於 0x4002.5400 . 這樣就能更了解與使用了.

可以實驗一下, 把剛剛程式 include 的部分註解掉, 把上面這些定義複製貼到include的下方, int main()之前, 存檔並按下 Build (F7), 然後下載到 LaunchPad . 按一下Launchpad的reset 鍵 , 結果是一樣的.

TivaWare的API :

那接下來說明一下使用TivaWare的API.
先建立一個新的Project , 例如 : B_TivaWare . 如何建立與設定參考前一篇 . 這裡有個地方要注意, 因為我們調用TivaWare的driverlib API, 需要把一些Driver(xxx.C)加到Project裡. 不過TivaWare 提供更方便的做法, 只需要加入driverlib.lib到Project裡就可以了. 方法如同加入system_TM4C123.c一樣. driverlib.lib的路徑如下:
C:\ti\TivaWare_C_Series-2.1.0.12573\driverlib\rvmdk\driverlib.lib

然後輸入以下的程式碼 :

#include <stdint.h>
#include <stdbool.h>
#include "inc/hw_memmap.h"
#include "inc/hw_types.h"
#include "driverlib/sysctl.h"
#include "driverlib/gpio.h"

int main(void)
{
    SysCtlClockSet(SYSCTL_SYSDIV_5|SYSCTL_USE_PLL|SYSCTL_XTAL_16MHZ|SYSCTL_OSC_MAIN);
    SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOF);
    GPIOPinTypeGPIOOutput(GPIO_PORTF_BASE, GPIO_PIN_1|GPIO_PIN_2|GPIO_PIN_3);
    
    while(1)
    {
        GPIOPinWrite(GPIO_PORTF_BASE, GPIO_PIN_1|GPIO_PIN_2|GPIO_PIN_3,0x02);
        SysCtlDelay(2000000);
        GPIOPinWrite(GPIO_PORTF_BASE, GPIO_PIN_1|GPIO_PIN_2|GPIO_PIN_3, 0x00);
        SysCtlDelay(2000000);
    }    
}


對於System Clock的部分和之前控制暫存器時不一樣, 之前控制暫存器時並未使用PLL, 只用系統的預設16MHz, 而這裡是啟動PLL, 設定40MHz.如何得知是40MHz ? TM4C123GH6PM的PLL是400MHz, 經過一個除以2的除法器, 再除以5 (SYSCTL_SYSDIV_5), 就等於40MHz. 所以你如果要50MHz, 只要修改為SYSCTL_SYSDIV_4就可以了.

接著就對PORTF enable. 再設定為輸出.

然後底下這行就是對GPIO_PORTF_DATA_R 寫入0x02, 點亮PF1(紅色LED).

GPIOPinWrite(GPIO_PORTF_BASE, GPIO_PIN_1|GPIO_PIN_2|GPIO_PIN_3,0x02);


很簡單吧. 所以如果懂了如何使用暫存器, 對於解讀這些API應該不難. 可以直接打開這些檔案參考或是參考User Guide, 當你安裝好TivaWare, 在Doc資料夾裡可以找到. 例如 :
C:\ti\TivaWare_C_Series-2.1.0.12573\docs

好啦, 一樣 , 存檔並按下 Build (F7), 然後下載到 LaunchPad, 按一下Launchpad的reset 鍵 , 可以看到紅色 LED在閃爍了! 這次閃爍的比較慢, 因為Delay的時間比較長.

~~~~~~~~~~~~~~~~~休息一下吧 ! ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

參考文獻 :
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