Arduino UNO R3的主處理器ATMega328P擁有3個定時/計數器,它們分別是Timer0,Timer1和Timer2;它們都經過對來自內部或外部的脈衝信號進行計數的方式完成基本的定時/計數功能以及一些其餘的功能。git
Timer0和Timer2是8位定時/計時器,Timer1是16位定時/計數器;下面以Timer2爲例討論定時/計數器子系統的典型應用,這些內容一樣適用於Timer0和Timer1。函數
在前面的例子中,已經使用了一些與精準延時相關的Arduino庫函數:oop
delay(ms):延遲一段時間ui
ms:延遲的時長,單位是毫秒spa
請注意,上面的Arduino庫函數使用了Timer0的中斷,所以不要在任何中斷服務程序中調用它,不然程序可能沒法正常工做。code
若是須要在延遲的同時,Arduino能夠執行其餘程序,這須要藉助第三方庫MsTimer2;你能夠在http://playground.arduino.cc/Main/MsTimer2下載到它,把下載到的MsTimer2.zip解壓到Arduino IDE安裝目錄下的libraries文件夾裏就能夠使用了,它包含了一個示例:blog
1 // FlashLed.ino 2 #include <MsTimer2.h> 3 4 void flash() { 5 static boolean output = HIGH; 6 7 digitalWrite(13, output); 8 output = !output; 9 } 10 11 void setup() { 12 pinMode(13, OUTPUT); 13 14 MsTimer2::set(500, flash); 15 MsTimer2::start(); 16 } 17 18 void loop() { 19 }
這個示例的效果與Blink示例基本相同,MsTimer2庫提供了3個庫函數:ip
MsTimer2::set(interval, function):每隔一段時間執行指定函數開發
interval:間隔時長,單位是毫秒get
function:指定執行函數的名稱
MsTimer2::start():開始每隔一段時間執行指定函數
MsTimer2::stop():結束每隔一段時間執行指定函數
Timer2有2個控制寄存器:TCCR2A和TCCR2B,它們的結構以下圖所示:
COM2A1 |
COM2A0 |
COM2B1 |
COM2B0 |
|
|
WGM21 |
WGM20 |
FOC2A |
FOC2B |
|
|
WGM22 |
CS22 |
CS21 |
CS20 |
其中WGM2[2:0]位用於設置模式,CS2[2:0]位用於設置時鐘源,以下表所示:
WGM2[2:0] |
模式 |
計數上限 |
|
CS2[2:0] |
時鐘源 |
000 |
正常 |
0xFF |
|
000 |
無 |
001 |
相位校訂脈寬調製 |
0xFF |
|
001 |
系統時鐘 |
010 |
比較匹配時清零 |
OCR2A |
|
010 |
系統時鐘8分頻 |
011 |
快速脈寬調製 |
0xFF |
|
011 |
系統時鐘32分頻 |
100 |
(保留) |
|
|
100 |
系統時鐘64分頻 |
101 |
相位校訂脈寬調製 |
OCR2A |
|
101 |
系統時鐘128分頻 |
110 |
(保留) |
|
|
110 |
系統時鐘256分頻 |
111 |
快速脈寬調製 |
OCR2A |
|
111 |
系統時鐘1024分頻 |
其他位暫且設置爲0。
精準延時能夠採用正常模式。正常模式下,Timer2不斷從0x00計數到0xFF;當定時計數器寄存器TCNT2每一次返回0x00時,若Timer2的中斷屏蔽寄存器TIMSK2的溢出中斷使能位TOIE2爲1,則產生中斷,它的結構以下圖所示:
|
|
|
|
|
OCIE0B |
OCIE0A |
TOIE0 |
經過直接訪問寄存器改寫以上程序爲:
1 // FlashLed_reg.ino 2 void flash() { 3 PORTB ^= (1 << PB5); 4 } 5 void setup() { 6 DDRB |= (1 << PB5); 7 8 // 正常模式,系統時鐘256分頻,計數初值爲6 9 TCCR2A = 0x00; 10 TCCR2B = 0x06; 11 TCNT2 = 0x06; 12 13 TIMSK2 |= (1 << TOIE2); 14 sei(); 15 } 16 17 void loop() { 18 } 19 20 ISR(TIMER2_OVF_vect) { 21 static volatile int iTimes = 0; 22 23 // 中斷125次爲500毫秒 24 if (++iTimes == 125) { 25 flash(); 26 iTimes = 0; 27 } 28 // 從新賦初值6 29 TCNT2 = 0x06; 30 }
程序中的「中斷125次爲500毫秒」是這樣計算出來的:Arduino UNO R3開發板使用16MHz的系統時鐘,Timer2使用系統時鐘256分頻,每記256-6=250個脈衝溢出一次,則每秒溢出16000000÷256÷250=250次,所以每溢出125次爲500毫秒。
脈寬調製的一個典型應用是控制直流電機速度。將直流電機兩極分別鏈接到直流電源的正負兩極上,電機會以最快速度運行;要調整電機速度,一個很容易想到的方法是調整直流電源的功率,但在數字系統中還有一個更簡單的方式:使用高低電平寬度不同的脈衝信號快速開關直流電機;由於慣性的做用,電機不會以最快的速度運行。通常來講,高電平在一個脈衝週期中所佔寬度更寬時,直流電機速度越快;咱們把高電平所佔一個脈衝週期的寬度稱爲佔空比。
如圖所示鏈接電路,11(PB3/OC2A)引腳經過三極管間接控制直流電機:
用Arduino庫函數輸出一個脈寬調製信號十分簡單,下面的示例使得直流電機由慢到快,又由快到慢反覆運行:
1 // FadingMotor.ino 2 int motor = 11; 3 int speed = 0; 4 int fadeAmount = 5; 5 6 void setup() { 7 pinMode(motor, OUTPUT); 8 } 9 10 void loop() { 11 analogWrite(motor, speed); 12 13 speed = speed + fadeAmount; 14 if (speed <= 0 || speed >= 255) { 15 fadeAmount = -fadeAmount; 16 } 17 18 delay(30); 19 }
與脈寬調製相關的Arduino庫函數有:
analogWrite(pin, value):在指定引腳上輸出一個指定佔空比脈寬調製信號
pin:指定引腳
value:脈寬調製信號的佔空比;0爲0%,255爲100%
Timer2擁有2個輸出比較寄存器OCR2A和OCR2B,它們經過與TCNT2寄存器發生比較匹配時對引腳置位、清零或取反來完成脈寬調製信號的輸出。TCCR2A寄存器中的COM2A[1:0]位用於設置OCR2A寄存器發生比較匹配時的行爲,以下表所示:
COM2A[1:0] |
行爲 |
00 |
正常的端口操做,OC2A未鏈接 |
01 |
WGM22=0,正常的端口操做,OC2A未鏈接 WGM22=1,發生比較匹配時OC2A取反 |
10 |
發生比較匹配時OC2A清零,計數到下限時OC2A置位 |
11 |
發生比較匹配時OC2A置位,計數到下限時OC2A清零 |
經過直接訪問寄存器改寫以上程序爲:
1 // FadingMotor_reg.ino 2 int speed = 0; 3 int fadeAmount = 5; 4 5 void setup() { 6 DDRB |= (1 << PB3); 7 8 // 快速脈寬調製,OC2A比較匹配置位,下限清零,系統時鐘 9 TCCR2A = 0x83; 10 TCCR2B = 0x01; 11 } 12 13 void loop() { 14 OCR2A = speed; 15 16 speed = speed + fadeAmount; 17 if (speed <= 0 || speed >= 255) { 18 fadeAmount = -fadeAmount; 19 } 20 21 delay(30); 22 }
3. 輸入捕獲*
輸入捕獲用來計算外部輸入信號的週期,它是Timer1的特有功能,Arduino官網提供了一個輸入捕獲的示例:
1 // PulseIn.ino 2 const int pin = 8; 3 unsigned long duration; 4 5 void setup() { 6 pinMode(pin, INPUT); 7 } 8 9 void loop() { 10 duration = pulseIn(pin, HIGH); 11 }
與輸入捕獲相關的Arduino庫函數有:
pulseIn(pin, value):計算指定引腳輸入外部信號的週期
pin:指定引腳
value:捕獲脈衝的類型,LOW(低電平,0V)或HIGH(高電平,5V)
函數返回外部輸入信號的週期,是一個unsigned long類型的整數,單位爲微秒
Timer1的控制寄存器與Timer2的不盡相同,請參閱ATMega328P芯片手冊16-bit Timer/Counter1 with PWM章的Register Description小節進行設置。經過直接訪問寄存器改寫以上程序爲:
1 // PulseIn_reg.ino 2 unsigned long duration; 3 unsigned long timer1_pulse_in(); 4 5 void setup() { 6 DDRB &= ~(1 << PB0); 7 PORTB &= ~(1 << PB0); 8 9 // 正常模式,系統時鐘8分頻 10 // 噪聲抑制關閉,降低沿觸發輸入捕獲 11 TCCR1A = 0x00; 12 TCCR1B = 0x42; 13 TCCR1C = 0x00; 14 TIMSK1 |= (1 << ICIE1) | (1 << TOIE1); 15 sei(); 16 } 17 18 void loop() { 19 duration = timer1_pulse_in(); 20 } 21 22 volatile int iOvf = 0; 23 volatile bool isCap = false; 24 volatile uint16_t iCap = 0; 25 volatile uint16_t iLastCap = 0; 26 27 unsigned long timer1_pulse_in() { 28 unsigned long duration; 29 30 while (!isCap); 31 duration = 0xffff - iCap 32 + 0xffff * (iOvf - 1) 33 + iLastCap; 34 35 isCap = false; 36 return duration; 37 } 38 39 ISR(TIMER1_CAP_vect) { 40 iCap = iLastCap; 41 iLastCap = (ICR1H << 8) | ICR1L; 42 43 isCap = true; 44 } 45 46 ISR(TIMER1_OVF_vect) { 47 iOvf += 1; 48 }
事實上,本章僅僅討論了定時/計數器子系統的典型應用,這僅僅是定時/計數器子系統強大功能的冰山一角,因爲篇幅關係沒有說起的部分,另請參閱ATMega328P芯片手冊。