PIC18x Timer 分享 III
經過 Timer0 的洗禮,會不會覺得 8-bit 的計數太 Low end 呢?還得自己數 125 次才能得到 1ms。接下來讓我們看看 Timer1 + CCP 模組的威力吧!
這是 Timer1 16-bit 讀寫模式的方塊圖,紅色的部份是需求的功能,藍色的部份是對應的暫存器設定。我們只是很單純地使用內部 Clock (Fosc / 4) 計數 1000 次輸出 (需要 16-bit)
對應的暫存器設定如上,只要設定 T1CON = 0x01 就可以將 Timer1 打開了,簡單吧
上圖為 CCP 模組 (Capture / Compare / PWM 的縮寫) 在比較模式 (Compare) 的方塊圖。目前的需求是將 CCPR1 (分成 CCPR1H 與 CCPR1L) 與 TMR1 (分成 TMR1H 與 TMR1L) 做比較,若兩者的值相等則設定 CCP1IF 旗標,同時將 Timer1 歸零 (在 Mode 0x0B 的情況下)。後面還可以有 CCP1 的輸出,不過等我們用到時再說吧
這裡也很簡單,只要設定 CCP1CON = 0x0B 為 Reset Timer1 的功能就可以了。
另外在方塊圖中的 T3CCP2 (位於 T3CON) 是用來選擇 Timer1 或 Timer3 與 CCP1 做比較的,原本應該設定為 0,只是 Power-on Reset 時初始值為 0,所以就不用設定了。
至於 CCP1IF 則是位於 PIR1 bit 2,其中要特別注意的是:當這個旗標觸發後,必須以軟體的方式清除
有了這些資訊後,我們就可以開始來觀察了
請依照先前設定的方式將下列暫存器抓進來,並設定初始值以便觀察
T1CON = 0x01
CCP1CON = 0x0B
CCPR1 = 0x02
TMR1 = 0x00
PIR1 = 0x00
每次按單步執行可以發現 TMR1 + 1,當 TMR1 到 0x02 時會自動歸零,同時設定 PIR1 bit2 (CCP1IF)
將 PIR1 bit2 設為 0 後,繼續執行則會重覆上述的動作。有了這些觀察結果後,就可以開始撰寫程式碼了
跟先前的程式碼比起來是不是精簡許多,初始化完後,只要判斷 CCP1IF 旗標就可以了。
在 p18f4520.h 中並未定義 TMR1,所以要分別對 TMR1H 與 TMR1L 做歸零的動作。
另外這裡用了一個寫程式的小技巧,就是將
PP++;
if (PP == 1000)
合併成 (記得要用前置式)
if (++PP == 1000)
讓程式更容易閱讀。最後就來看一下執行的結果吧
先在第 21 行設定中斷點,按 Reset 回到程式的開頭後,先按 Stopwatch 的 Zero 將時間歸零,然後執行到中斷點後的時間為 1.027ms,其中 27us 是前面初始化所花費的時間,之後再執行就可以發現每次都是很精確的 1ms,這就是硬體計時的好處,不像軟體計時還要考慮指令週期的問題。
又完成一次艱鉅的任務了,在這次任務中我們認識了 CCP 模組,後面將有更多關於它的介紹,敬請期待。。。
PIC18F4520 Data Sheet (Page 130)
這是 Timer1 16-bit 讀寫模式的方塊圖,紅色的部份是需求的功能,藍色的部份是對應的暫存器設定。我們只是很單純地使用內部 Clock (Fosc / 4) 計數 1000 次輸出 (需要 16-bit)
(Page 129)
對應的暫存器設定如上,只要設定 T1CON = 0x01 就可以將 Timer1 打開了,簡單吧
(Page 144)
上圖為 CCP 模組 (Capture / Compare / PWM 的縮寫) 在比較模式 (Compare) 的方塊圖。目前的需求是將 CCPR1 (分成 CCPR1H 與 CCPR1L) 與 TMR1 (分成 TMR1H 與 TMR1L) 做比較,若兩者的值相等則設定 CCP1IF 旗標,同時將 Timer1 歸零 (在 Mode 0x0B 的情況下)。後面還可以有 CCP1 的輸出,不過等我們用到時再說吧
(Page 141)
這裡也很簡單,只要設定 CCP1CON = 0x0B 為 Reset Timer1 的功能就可以了。
另外在方塊圖中的 T3CCP2 (位於 T3CON) 是用來選擇 Timer1 或 Timer3 與 CCP1 做比較的,原本應該設定為 0,只是 Power-on Reset 時初始值為 0,所以就不用設定了。
(Page 98)
至於 CCP1IF 則是位於 PIR1 bit 2,其中要特別注意的是:當這個旗標觸發後,必須以軟體的方式清除
有了這些資訊後,我們就可以開始來觀察了
請依照先前設定的方式將下列暫存器抓進來,並設定初始值以便觀察
T1CON = 0x01
CCP1CON = 0x0B
CCPR1 = 0x02
TMR1 = 0x00
PIR1 = 0x00
每次按單步執行可以發現 TMR1 + 1,當 TMR1 到 0x02 時會自動歸零,同時設定 PIR1 bit2 (CCP1IF)
將 PIR1 bit2 設為 0 後,繼續執行則會重覆上述的動作。有了這些觀察結果後,就可以開始撰寫程式碼了
#include <myapp.h> void main(void) { unsigned int PP; // 1ms 的計數器 unsigned char SS, MM, HH; // 秒、分、時 PP = SS = MM = HH = 0; // 初始值歸零 CCP1CON = 0x0B; // 設定 CCP1 工作在自動 Reset Timer1 的模式 CCPR1 = 1000; // 設定計數 1000 次 TMR1H = TMR1L = 0; // Timer1 歸零 T1CON = 0x01; // 開始 Timer1 while (1) { CLR(PIR1, BIT2); // 將 CCP1IF 旗標歸零 while (!GET(PIR1, BIT2)) // 等待計數結束 { } if (++PP == 1000) // 若 1ms 計數 1000 次結束 { PP = 0; // 則將 PP 歸零,同時將 SS + 1 if (++SS == 60) // 若 1 秒鐘計數 60 次結束 { SS = 0; // 則將 SS 歸零,同時將 MM + 1 if (++MM == 60) // 若 1 分鐘計數 60 次結束 { MM = 0; // 則將 MM 歸零,同時將 HH + 1 if (++HH == 24) // 若 1 小時計數 24 次結束 { HH = 0; // 則將 HH 歸零 } } } } // 顯示時間 } }
跟先前的程式碼比起來是不是精簡許多,初始化完後,只要判斷 CCP1IF 旗標就可以了。
在 p18f4520.h 中並未定義 TMR1,所以要分別對 TMR1H 與 TMR1L 做歸零的動作。
另外這裡用了一個寫程式的小技巧,就是將
PP++;
if (PP == 1000)
合併成 (記得要用前置式)
if (++PP == 1000)
讓程式更容易閱讀。最後就來看一下執行的結果吧
先在第 21 行設定中斷點,按 Reset 回到程式的開頭後,先按 Stopwatch 的 Zero 將時間歸零,然後執行到中斷點後的時間為 1.027ms,其中 27us 是前面初始化所花費的時間,之後再執行就可以發現每次都是很精確的 1ms,這就是硬體計時的好處,不像軟體計時還要考慮指令週期的問題。
又完成一次艱鉅的任務了,在這次任務中我們認識了 CCP 模組,後面將有更多關於它的介紹,敬請期待。。。
留言
我快變成你的小粉絲了
我原本都看不懂怎樣對應data sheet
真的萬分感謝
我想照老師說的方式:挑需求的重點看
這樣應該就足夠了