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