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:
- 為什麼 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,如下圖:
-
點 Debugger>Stop Watch,Stop Watch 視窗就會顯示在畫面上:
-
在 Delay10KTCYx(160); 這行按右鍵,點 Set BreakPoint,此時程式碼視窗左方會出現一個紅色小圖示,上面寫個 B,表示這邊有個 BreakPoint,如下圖:
-
按下 Debug Toolbar 上的 Run 鈕執行程式:
-
程式會執行到 Delay10KTCYx(160) 這行後停下來,如下圖,Stop Watch 視窗會顯示截至目前為止執行了多少條指令,以及所花費的執行時間:
- 點一下 Stop Watch 視窗的 Zero 鈕
- 按 F8 或點 Debugger>Step Over,讓 MCU 往下走執行完 Delay10KTCYx(160) 這一行
- 觀察 Stop Watch 視窗,看看 StopWatch 執行的 Instruction Cycles 是否為 16000000 條指令,Time(mSec) 是否為 100ms,正常來說結果應如下圖:
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 鍵重開機器,觀察燈號閃爍的變化。
沒有留言:
張貼留言
注意:只有此網誌的成員可以留言。