Arduino UNO R3的主處理器ATMega328P擁有3個定時/計數器,它們分別是Timer0,Timer1和Timer2;它們都通過對來自內部或外部的脈沖信號進行計數的方式完成基本的定時/計數功能以及一些其他的功能。
Timer0和Timer2是8位定時/計時器,Timer1是16位定時/計數器;下面以Timer2為例討論定時/計數器子系統的典型應用,這些內容同樣適用於Timer0和Timer1。
1. 精准延時
在前面的例子中,已經使用了一些與精准延時相關的Arduino庫函數:
delay(ms):延遲一段時間
ms:延遲的時長,單位是毫秒
請注意,上面的Arduino庫函數使用了Timer0的中斷,因此不要在任何中斷服務程序中調用它,否則程序可能無法正常工作。
如果需要在延遲的同時,Arduino可以執行其他程序,這需要借助第三方庫MsTimer2;你可以在http://playground.arduino.cc/Main/MsTimer2下載到它,把下載到的MsTimer2.zip解壓到Arduino IDE安裝目錄下的libraries文件夾里就可以使用了,它包含了一個示例:
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個庫函數:
MsTimer2::set(interval, function):每隔一段時間執行指定函數
interval:間隔時長,單位是毫秒
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毫秒。
2. 脈寬調制
脈寬調制的一個典型應用是控制直流電機速度。將直流電機兩極分別連接到直流電源的正負兩極上,電機會以最快速度運行;要調整電機速度,一個很容易想到的方法是調整直流電源的功率,但在數字系統中還有一個更簡單的方式:使用高低電平寬度不一樣的脈沖信號快速開關直流電機;因為慣性的作用,電機不會以最快的速度運行。一般來說,高電平在一個脈沖周期中所占寬度更寬時,直流電機速度越快;我們把高電平所占一個脈沖周期的寬度稱為占空比。
如圖所示連接電路,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芯片手冊。