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

沒有留言:

張貼留言