2009年12月24日 星期四

[PIC] Blinking a LED

Introduction

本文說明如何控制 LED 讓燈號閃爍。

Highlights

本文討論下列項目:

  • 控制 LED 讓燈號閃爍
  • 使用 C18 Delay Functions 延遲時間
  • 使用 Timer 與 Interrupt 控制 LED 燈號

看完本文後,你將可以完成下列工作:

  • 使用 C18 Delay Functions 延遲時間
  • 使用 MPLAB SIM 計算程式執行的時間
  • 計算 Timer 計數值
  • 撰寫中斷處理函式 - ISR (Interrupt Service Routine)

建立 Project

底下有數個版本的 LED 燈號控制程式範例,請參考 [PIC] Create a New Project with MPLAB 建立各個專案。

以 LED Version 1 為例,使用 MPLAB IDE 的 Project Wizard 建立 LED1 Project 的步驟如下:

  • Project Wizard Step One 視窗: 在 “Device” 下列選單中選擇 MCU,例如“PIC18F46K20”
  • Project Wizard Step Three 視窗: 在 “Create New Project File” 欄位中輸入 C:\MyProjects\LED1\LED1
  • 在 Summary 視窗上按下完成,一個新的 Project 跟 Workspace 就產生了
  • 點 Configure>Configuration Bits,確認 Configuration bits 的設定是正確的
  • 點 File>New 建立一個新檔
  • 點 File>Save As… 把檔案存起來,命名為 LED.c,放在 C:\MyProjects\LED1 資料夾裏
  • 將範例程式碼複製到 LED.c 裏
  • 在 Project Window 中,將 LED.c 檔加到 Project 的 source directory 裏
  • 在 Program Tool Bar 上,點 Program target device 鈕將程式燒錄到 MCU 中,並點 Release from Reset 鈕執行範例程式
  • 若 MCU 燈號有在閃爍,代表程式有成功執行。

LED 燈號控制 Version 1

程式範例

程式說明:

  • 定義 nMCU_LED 燈號在 RA4 腳位上
  • 進入 while 無窮迴圈,利用 for 迴圈延遲一段時間,然後切換 nMCU_LED 燈號的開關

LED 燈號控制 Version 2

程式範例

程式說明:

  • 利用 Delay10KTCyx(160) 產生 100ms 的延遲
  • Delay10KTCYx() 是 C18 Library 的一個 Delay function,可以用來產生 10K 個 instruction cycles 的延遲。除了 Delay10KTCYx() 外,C18 還有其它 delay functions:

image

  • 為什麼 Delay10KTCYx(160) 可以產生 100ms 的延遲?答:
    • 以 PIC18F46K20 為例,它的 Fosc 是 64MHz,執行一個指令所需時間為: 1/Fosc/4 = 1/64MHz/4 = 0.0625us。
    • 160 * 10K = 1600K 個 Instruction cycles
    • 所以 Delay10KTCYx(160) 的執行時間為: 1600K * 0.0625us = 100000us = 100ms
什麼是 MPLAB SIM
  • MPLAB SIM(Simulator) 可以模擬各種 PIC/dsPIC microcontrollers
  • 使用 MPLAB SIM 可以幫助你:
    • 計算程式執行所花的時間
    • 測試程式運作是否正確,當你手邊沒有板子可用時
利用 MPLAB SIM 計算程式執行的時間
  • 按 F10 編譯程式
  • 點 Debugger>Select Tool>MPLAB SIM 啟動 MPLAB SIM
  • 點 Debugger>Settings 打開 Simulator Settings 視窗
  • 在 Processsor Frequency 欄輸入 64,將 MCU 頻率設為 64MHz,如下圖:

image

  • 點 Debugger>Stop Watch,Stop Watch 視窗就會顯示在畫面上:

image

  • 在 Delay10KTCYx(160); 這行按右鍵,點 Set BreakPoint,此時程式碼視窗左方會出現一個紅色小圖示,上面寫個 B,表示這邊有個 BreakPoint,如下圖:

image

  • 按下 Debug Toolbar 上的 Run 鈕執行程式:

image

  • 程式會執行到 Delay10KTCYx(160) 這行後停下來,如下圖,Stop Watch 視窗會顯示截至目前為止執行了多少條指令,以及所花費的執行時間:

image

  • 點一下 Stop Watch 視窗的 Zero 鈕
  • 按 F8 或點 Debugger>Step Over,讓 MCU 往下走執行完 Delay10KTCYx(160) 這一行
  • 觀察 Stop Watch 視窗,看看 StopWatch 執行的 Instruction Cycles 是否為 16000000 條指令,Time(mSec) 是否為 100ms,正常來說結果應如下圖:

image

LED 燈號控制 Version 3

  • 前兩版的程式有個嚴重的瑕疵,每當執行到迴圈中的 delay 程式,MCU 就在那裏 busy wait,什麼事也不做,很浪費資源
  • 要避免浪費 MCU 資源,可以利用 Timer 和 Interrupt
範例程式

程式說明:

  • PIC18 microcontrollers 的 high interrupt vector 位於 000000008h,利用 #pragma 將 high_interrupt() function 放到 0×08 的位址上
  • 利用 #pragma interrupt timer_isr 將 timer_isr() 定義為一個 high priority interrupt service routine. 這麼做的目的是告訴 Compiler 為 timer_isr() 產生一個 RETFIE 指令而不是產生一般的 RETURN 指令。
  • 使用 OpenTimer0() 啟動 TMR0 overflow interrupt, 並將 TMR0 設為 internal 16-bit clock, prescale 1:256.
  • WriteTimer0 (-(T0_TICKS_PER_100MS)); 這行將 TMR0 計數值設為 -6250。
Timer 計數值的計算
  • TOSC = 1/(FOSC/4)
  • P18F46K20 為 64MHz, 所以 TOSC = 1/(64MHz/4) = 0.0625us
  • 在 1:256 prescaler 下,要讓 Timer0 每 100ms 產生一次 overflow 中斷,須將 Timer0 計數的次數 設為: 100ms/256 * 0.0625us = 6250 Ticks
  • 因為 Timer 是累加到超過 0xFFFF 才發生中斷,所以必須寫入 -6250 到 TMR0

LED 燈號控制 Version 4

範例程式

程式說明:

  • Timer0 預設是 high priority interrupt
  • 如果要改為 low priority interrupt,除了改寫 low interrupt vector 並使用 #pragma interruptlow 定義 ISR 外,還必須進行底下幾個動作:

LED 燈號控制 Version 5

範例程式

程式說明:

  • LED Version 5 與 LED Version 4 最大的不同,是利用下列這段程式碼自動算出 Timer0 的計數值,得到的計數值一樣是 -6250:

練習一: 控制燈號速度
  • 將底下程式片段加入 LED.c

  • 使用 volatile 宣告變數,目的是告訴 Compiler 不要做最佳化,避免變數狀態不同步。(註:程式主體跟 ISR 都會用到的變數,盡量宣告成 volatile。)
  • 使用 #pragma 和 near 宣告變數,目的是告訴 Compiler 將變數放在 Access Bank,這樣可以加速變數的存取速度。
  • 將 timer_isr() 改成底下的版本:
  • C 的 « bitwise shift operator 會透過 carry Flag,所以改用組語 RLNCF 指令迴轉變數內容,RLNCF 不會經過 Carry Flag。
  • 將底下這行程式加入 main() 的起始位置,這行程式目的是設定 MCU 燈號速度:
  • 編譯程式,並燒錄到 Device 上。此時 MCU 燈號如果有正常閃爍,代表程式有成功執行。
  • 將 MCU 燈號速度改成 FASTEST :
  • 編譯程式,並燒錄到 Device 上。此時 MCU 燈號如果閃爍的非常快,代表程式有成功執行。
  • 寫一個 setLED() function, 用來設定燈號的速度。
  • 完成 setLED() 後,將 main() 設定燈號速度的程式改成底下這樣:
  • 燒錄程式,確認燈號是否為正常的閃爍速度。
  • 將 setLED(NORMAL) 改成其它速度,如 setLED(FASTEST),執行程式,確認燈號是否以最快的速度閃爍。
練習二: 控制燈號速度
  • 增加一個 nMCU_LED_period 全域變數,nMCU_LED_period 用來控制燈號速度的執行週期
  • 修改 setLED(),為 setLED() 增加一個 period 參數,把值寫到 nMCU_LED_period 變數
  • 修改 timer_isr(),檢查 nMCU_LED_period 是否非零,否是則倒數 nMCU_LED_period,直到變成 0 時,就將燈號速度還會為 NORMAL
  • 將 main() 設定燈號速度的程式改成底下這樣:
  • 執行程式,觀查 MCU 燈號的狀態,確認是否「一開始閃爍非常快,過數秒後又跳回正常的閃爍速度」
  • 可以按 Reset 鍵重開機器,觀察燈號閃爍的變化。

參考資料

0 意見: