Timery w Arduino
Arduino posiada clock (zegar kwarcowy / oscylator) o częstotliwości 16Mhz. Każdy cykl zegara odbywa się co 1/16000000s czyli co 62.5ns.
Timery 0, 1 i 2
ATmega328P posiada 3 timery[0, 1, 2], które zliczają cykle zegara.
Timer0: 8-bit (0-255), używany do delay, millis, micros, itp.
Timer1: 16-bit (0-65535), używany w bibliotekach (np. servo)
Timer2: 8-bit (0-255), używany w funkcji tone()
Prescalery
Timery działają na rejestrach 8 i 16 bitowych co oznacza, że mogą zliczać maksymalnie do liczby 255 lub 65535. Po przekroczeniu tej liczby counter się resetuje i generuje przerwanie.
Aby uzyskać dłuższe czasy można zmienić multiplicator (prescaler), który będzie zliczał cykle w pewnych odstępach np. x1, x8, x64, x256, x1024. Multiplikator znajduje się pomiędzy clockiem, a timerem. Domyślnie wszystkie timery zliczają cykle z prescalarem x64.
Timer0 z prescalerem x64 generuje przerwanie co ok.1ms, dzięki czemu stosuje się go w metodzie millis().
256 [ilość cykli] x 64[prescaler] x 62.5ns =1024000ns = 1,024ms
Zmiana prescalera
Prescalery mogą być ustawiane za pomocą rejestrów dla każdego z timerów:
Timer0 – TCCR0B
Timer1 – TCCR1B
Timer2 – TCCR2B
Rejestr TCCR1B odpowiada za wybór trybu pracy timera (zliczanie, CTC, PWM), a także za konfigurację preskalera, który określa częstotliwość zliczania.
Zmiana prescalera odbywa się na 3 pierwszych bitach rejestru TCCR1B (CS10, CS11, CS12)
TCCR1B | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
ICNC1 | ICES1 | – | WGM13 | WGM12 | CS12 | CS11 | CS10 |
CS12 | CS11 | CS10 | USE |
0 | 0 | 0 | No Clock Timer STOP |
0 | 0 | 1 | 1 (No Prescaling) |
0 | 1 | 0 | 8 (From Prescaler) |
0 | 1 | 1 | 64 (From Prescaler) |
1 | 0 | 0 | 256 (From Prescaler) |
1 | 0 | 1 | 1024 (From Prescaler) |
1 | 1 | 0 | External clock source on T1 Pin. Clock on falling edge |
1 | 1 | 1 | External Clock source on T1 pin. Clock on rising edge. |
TCCR1B = 0; // reset rejestru
TCCR1B |= B00000100; // prescaler 256
TCCR1B |= B00000101; // prescaler 1024
TCCR1B |= (1 << CS12) | (1 << CS10); //prescaler 1024
Wzór na obliczanie wartości prescalera i czasu dla timera1
Rejestr TCNT1 zwraca aktualną ilość cykli (od 0 do 65535). Można go łatwo zresetować manualnie przypisując do niego 0. Prescaler wpływa na szybkość inkrementacji wartości, jakie są zwracane przez TCNT1 (im większy prescaler, tym dłużej trwa inkrementacja cykli timera).
Rejestr OCR1A (Output Compare Register 1A) w Arduino służy do ustawiania wartości porównawczej dla timera 1 w trybach pracy z porównaniem wyjściowym, po spełnieniu równości rejestr TCNT generuje przerwanie.
ISR(TIMER1_COMPA_vect) {}
Rejestr TIMSK1 służy do włączania i wyłączania przerwań związanych z Timer1. Włączenie odpowiednich bitów w tym rejestrze pozwala na wywoływanie przerwań przy określonych zdarzeniach, takich jak osiągnięcie wartości porównawczej lub przepełnienie licznika. Przerwania te są użyteczne do wykonywania działań w precyzyjnych odstępach czasowych lub w odpowiedzi na zmiany wartości licznika.
Przykład programu Blink LED co 1s
#include <avr/io.h>
void setup() {
TCCR1A = 0; //reset timera1
TCCR1B = 0; //reset prescalera
TCCR1B |= B00000100; // prescaler x256
pinMode(LED_BUILTIN, OUTPUT);
}
void loop() {
if (TCNT1 > 62500) {
TCNT1 = 0; //reset licznika
digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN));
}
}
// Identyczny program co powyżej, ale z użyciem prescalera 1024
void setup() {
TCCR1A = 0; //reset timera1
TCCR1B = 0; //reset prescalera
TCCR1B |= B00000101; // prescaler x1024
pinMode(LED_BUILTIN, OUTPUT);
}
void loop() {
if (TCNT1 > 15625) {
TCNT1 = 0; //reset licznika
digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN));
}
}
Przykład Blink LED co 0.5s z użyciem przerwania
int led = LED_BUILTIN;
bool LED_STATE = true;
void setup() {
pinMode(led, OUTPUT);
cli(); //stop interrupts for till we make the settings
TCCR1A = 0; // Reset entire TCCR1A to 0
TCCR1B = 0; // Reset entire TCCR1B to 0
TCCR1B |= B00000100; //Set CS12 to 1 so we set prescaler to 256
TIMSK1 |= B00000010;
OCR1A = 31250; //set compare register A to this value
sei(); //Enable back the interrupts
}
void loop() {}
ISR(TIMER1_COMPA_vect) {
TCNT1 = 0;
LED_STATE = !LED_STATE;
digitalWrite(led, LED_STATE);
}
Obsługa timera1 do zmierzenia czasu wywołania funkcji.
void setup() {
Serial.begin(115200);
TCCR1B = bit(CS10); // ustawia mnożnik timera1 na x1
TCNT1 = 0; // resetuje licznik timera na 0
// Funkcja do zmierzenia
DDRB = B00111000; // zamiennik pinMode dla wielu pinów 8-13
PORTB = B00111000; // zamiennik digitalWrite() dla wielu pinów jednocześnie
// Koniec funkcji do zmierzenia
unsigned long value = TCNT1; // zczytuje wartość licznika timera
Serial.print("Cycles: ");
Serial.println(value - 1);
Serial.print("Microseconds: ");
Serial.println((float)(value - 1) / 16);
}
void loop() {}
Biblioteka TimerOne
Link do biblioteki TimerOne na github
Za pomocą biblioteki TimerOne można stworzyć przerwania wewnętrzne do wykonywania operacji cyklicznych.
#include <TimerOne.h>
int ledPin = 10;
void ledBlink() {
digitalWrite(ledPin, !digitalRead(ledPin));
}
void setup() {
pinMode(ledPin, OUTPUT);
Timer1.initialize(1000000); //in microseconds
Timer1.attachInterrupt(ledBlink);
}
void loop() {}
Zobacz: Rejestry portów w Arduino