Die Mikrocontroller der AVR-Familie besitzen je nach Typ eine unterschiedliche Anzahl an programmierbaren Timern. Bei den aktuellen ATmegas sind das mindestens ein 8-Bit Timer und bei größeren Ausführungen der Serie auch 16-Bit Timer. Die Timer werden immer Timerx benannt, wobei x für die Timernummer steht (also 0, 1, 2, usw.). Die Konfigurationsmöglichkeiten sind von Timer zu Timer unterschiedlich.
Hinweis: Die folgenden Code-Beispiele sind in C programmiert und wurden für einen ATmega32 entwickelt. Sie lassen sich also ohne große Änderungen auch auf anderen Mikrocontrollern der AVR-Familie einsetzen.
Inhaltsverzeichnis
Allgemeine Funktionsweise
Timer funktionieren nach dem allgemeinen Prinzip, dass sie eine Ganzzahl (im weiteren als Zähler bezeichnet) je nach Betriebsmodus auf- oder abwärtszählen, d.h. inkrementieren bzw. dekrementieren.
Angenommen, der Timer arbeitet im einfachsten Betriebsmodus, dem normalen Modus (siehe Normaler Modus (Normal Mode)). Die Zählrichtung des Timers ist aufsteigend gerichtet. Je nach Auflösung, also 8-Bit oder 16-Bit, erreicht der Zähler irgendwann einen bestimmten Zustand. Möglich wäre, dass er überläuft, wenn z.B. bei einem 8-Bit Timer der Wert 255 inkrementiert wird (siehe Grafik).
Der Prescaler
Der Prescaler (eng. = Vorteiler) kann dazu genutzt werden, den Takt, der den Timern zugeführt wird, zu verkleinern. U.a. kann man damit die Timer so konfigurieren, damit diese in den unterschiedlichsten Frequenzen takten. Hier eine Grafik die den Prescaler veranschaulicht:
Das obere Diagramm zeigt den Betrieb ohne Prescaler, das untere mit Prescaler. Die gestrichelte Linie zeigt, wann ein Interrupt eintritt.
Im Teil Die Betriebsmodi wird weiter auf die praktische Verwendung des Prescalers eingegangen.
Die Betriebsmodi
Die AVR-Timer können in unterschiedlichen Betriebsmodi betrieben werden. Diese sind:
- Normaler Modus
- CTC Modus
- PWM
Normaler Modus (Normal Mode)
Der einfachste Betriebsmodus ist der normale Modus. Er funktioniert wie im Abschnitt "Allgemeine Funktionsweise" beschrieben. Die Zählrichtung des Timers ist immer aufsteigend, und irgendwann kommt es zu dem Interrupt Timer-Overflow (welcher in einer passend ISR aufgefangen werden kann). Im einfachsten Fall kann man diesen Modus in folgendem Diagramm darstellen:
Der Zähler des Timers (im Diagramm oben, die aufsteigende und dann wieder zurückgesetzte Linie) ist in dem Register TCNTx gespeichert, wobei x für eine Zahl steht. Soll z.B. auf den Timer0 (siehe Datenblatt des jeweiligen Controllers) des Controllers zugegriffen werden, so ist an TCNT eine 0 anzuhängen, also TCNT0. Wie lange es braucht, bis der Zähler einen Overflow auslöst, ist von der Taktfrequenz des Controllers, dem eingestellten Prescaler-Wert und von der Timerauflösung abhängig. Nun wäre es ja sehr unpraktisch, wenn wir den Zähler nicht anpassen könnten. Denn sonst müssten wir unsere Software die den Timer benutzt evtl. anpassen und viel rechnen um z.B. für 1000 ms zu schlafen. Deswegen kann auf den Zähler zugreifen und ihn vorladen bevor dieser wieder vom eigentlichen Timer hochgezählt wird. Dies veranschaulicht folgendes Diagramm:
Dadurch kann man den Timer beeinflussen, und beeinflussen wie lange es dauert, bis ein Overflow auftritt. Um zu berechnen, welchen Wert wir vorladen müssen, kann man auch ein Java-Applet nutzen, siehe unter Weblinks Java Applet.
Natürlich kann man das auch "von Hand" rechnen. Die Berechnung des Preloader- sowie Prescalerwerts bei Verwendung der Overflow-Interrupts, eines Prescalers von 64 (nicht alle Prescaler können verwendet werden) und eines Quarzes mit der Frequenz von 8 MHz sieht folgendermaßen aus (gesuchte Frequenz beträgt 1000 Hz unter der Verwendung des Timer0 eines ATmega32):
- [math]Prescale = Frequenz * 1000000 [Hz] = 8000000[/math]
- Wir definieren den maximalen Zählerwert. Dieser ist bei einem 8-Bit Timer 256, bei einem 16-Bit Timer 65536. In unserem Fall ist der maximale Zählerwert 256, weil Timer0 verwendet wird.
- Nun wird die Variable Prescale (s.o.) durch den verwendeten Prescaler (64) geteilt ([math]8000000 Hz / 64 = 125000[/math]).
- Als nächstes wird der im dritten Punkt errechnete Wert durch die gesuchte Frequenz geteilt [math]=125000 / 1000Hz = 125[/math].
- Nun wird mathematisch überprüft, ob der errechnete Wert aus dem vierten Punkt kleiner als der maximale Zählerwert ist. Trifft dies zu, so subtrahiert man den errechneten Wert vom maximalen Zählerwert ([math]= 256 - 125 = 131[/math]).
Damit haben wir den Wert errechnet, der bei jedem Interrupt, den der Timer0 auslöst, in TCNTx (in diesem Fall TCNT0) nachgeladen werden muss, damit die Interrupts in dem gewünschten Zeitabstand von einer Millisekunde ausgelöst werden.
Allerdings bleibt zu bemerken, dass bei der Verwendung einer "ungeraden" Quarzfrequenz (z.B. 7,3728 MHz) der Timer mit einer bestimmten Ungenauigkeit arbeitet. Würden wir z.B. den Quarz oben mit einem Quarz mit 7,3728 MHz austauschen, so wäre die Fehlerrate 0,17%. Diese Ungeauigkeit varriert von verwendetem Prescaler zu Prescaler. D.h. wenn wir einen Prescaler von 1024 (und einer Quarzfrequenz von 8 MHz) verwendet hätten, so hätten wir eine inakzeptable Ungeauigkeit von 11,61%. Deswegen sollte sie eines der genannten Programme unter Avr#Weblinks verwenden, denn diese zeigen nur die bestmögliche Konfiguration an.
Die Fehlerrate kann natürlich auch ausgerechnet werden. Hier die Rechenschritte (sie sind erweiternd zu der oberen Berechnung):
- Als erstes wird mathematisch überprüft, ob der Preloaderwert (siehe fünften Schritt oben) größer als 1 ist.
- Trifft dies zu, so wird als nächstes die resultierende Frequenz errechnet. Die geschieht folgendermaßen: Der errechnete Preloaderwert aus der Rechnung oben wird vom maximalen Zählerwert subtrahiert, anschließend mit dem Prescaler multipliziert und dann das Ganze durch die Variable Prescale geteilt
[math](256 - 131) \cdot 64 / 8\,000\,000 \mathrm{Hz} \cdot 1\,000\,000 = 1000[/math].
- Nun wird die gesuchte Frequenz vom errechneten Wert aus dem dritten Punkt subtrahiert und dann wiederum durch diese geteilt [math](1000-1000) / 1000 = 0[/math]. Damit läuft dieser Timer genau mit einer Fehlerrate von 0 %.
Betreibt man den Timer im Overflow-Modus, so muss man, wie bereits erwähnt, nach/bei jedem Overflow-Interrupt den Timer nachladen. Der Interrupt heißt in diesem Fall SIG_OVERFLOWx (x steht für die Nummer des Timers). Dieser muss in einer ISR abgefangen werden.
Zusammenfassend ein Beispielprogramm:
/* Es wird der Timer2 (8-Bit) eines ATmega32 verwendet, der mit einem Quarz mit 7,3728 MHz betrieben wird. Im Abstand von 0,001 ms erzeugt der Timer einen Interrupt, also eine Frequenz von 1000000 Hz (oder 100 kHz). Der Timer wird auf einen Prescaler von 1 und einem Preloader von 183 konfiguriert.*/ volatile uint8_t countTimer2; // Speichert den aktuellen Zählerwert // ISR zum auffangen der Interrupts: SIGNAL(SIG_SIG_OVERFLOW2) { countTimer2++; TCNT2 = 183; // Nachladen } // Initialisierung: TCCR2 = (1<<CS22); // Prescaler von 1 TCNT2 = 183; // Vorladen TIMSK |= (1<<TOIE2); // Interrupts aktivieren und damit Timer starten sei(); // Funktionen zum benutzen der Timer: /** Diese Funktion nicht aufrufen. Wird von sleep_millisec aufgerufen. Bei t=100 schläft die Funktion 1 ms. */ inline void sleep (uint8_t t) { // countTimer2 wird in der ISR oben inkrementiert countTimer2 = 0; while (countTimer2 < t); } /** Schläft x-Millisekunden. */ inline void sleep_millisec(uint16_t msec) { uint16_t i; for(i=0; i<msec; i++) { sleep(100); } }
Allerdings wird auf diese leicht veraltete Technik nun nicht weiter eingegangen. Der Artikel wendet sich nun dem neueren "Compare Output"-Betriebsmodus zu.
Beim "Compare Output"-Betriebsmodus wird genauso ein Zähler hochgezählt. Allerdings wird der Zählerwert nach jeder Inkrementierung mit einem vom Benutzer festgelegten Wert verglichen. Entspricht der Zählerwert dem gespeicherten Wert, so kommt es zu einem Interrupt. Dieser Wert wird in das Register OCRx (x steht wieder für die Timernummer) gespeichert. Je nach Auflösung des Timers ist dieses Register 8-Bit oder 16-Bit breit.
Hinweis: Siehe CTC Modus unten. Dieser wird benötigt um den Timer entsprechend im "Compare Output"-Betriebsmodus vernünftig zu betreiben.
Hier ein typisches Diagramm dieses Betriebsmodus (Hinweis: Das Diagramm wurde unter Verwendung des CTC Modus erstellt. Für Begriffserklärung siehe CTC Modus):
Das Ausrechnen des Werts, der in OCRx geschrieben werden muss, damit Frequenz x entsteht, ist nicht sonderlich schwer. Man geht wie bei der Berechnung des Werts für den Overflow-Modus vor (s.o.) nur daß man das resultierende Ergebnis vom maximalen Zählerwert (bei 8-Bit Auflösung ist dieser 256, bei 16-Bit Auflösung 65536) subtrahiert. Das Ergebnis wird dann einmalig in das Register OCRx geschrieben. Mann muss also nicht wie beim Overflow-Modus den Timer nach jedem Interrupt nachladen. Der enstehende Interrupt heißt in diesem Fall SIG_OUTPUT_COMPAREx (x steht für die Nummer des Timers). Dieser muss in einer ISR abgefangen werden.
Wiederum zusammenfassend ein Beispielprogramm:
/* Es wird der Timer2 (8-Bit) eines ATmega32 verwendet, der mit einem Quarz mit 7,3728 MHz betrieben wird. Im Abstand von 0,001 ms erzeugt der Timer einen Interrupt, also eine Frequenz von 1000000 Hz (oder 100 kHz). Der Timer wird auf einen Prescaler von 1 und einem OCR2-Wert von 73 konfiguriert.*/ volatile uint8_t countTimer2; // Speichert den aktuellen Zählerwert // ISR zum auffangen der Interrupts: SIGNAL(SIG_OUTPUT_COMPARE2) { countTimer2++; } // Initialisierung: TCCR2 = (1<<CS22) | (1<<WGM21); // Prescaler von 1 | CTC-Modus (siehe unten für Beschreibung) OCR2 = 73; // Vergleichswert TIMSK |= (1<<OCIE2); // Interrupts aktivieren und damit Timer starten sei(); // Funktionen zum benutzen der Timer: /** Diese Funktion nicht aufrufen. Wird von sleep_millisec aufgerufen. Bei t=100 schläft die Funktion 1 ms. */ inline void sleep(uint8_t t) { // countTimer2 wird in der ISR oben inkrementiert countTimer2 = 0; while (countTimer2 < t); } /** Schläft x-Millisekunden. */ inline void sleep_millisec(uint16_t msec) { uint16_t i; for(i=0; i<msec; i++) { sleep(100); } }
CTC Modus (Clear Timer on Compare Match mode)
Der CTC Modus ist eine Erweiterung des "Compare Output"-Betriebsmodus. Der CTC Modus wird z.B. für das obere Beispielprogramm benötigt. Wird der Timer nämlich im normalen Betriebsmodus betrieben, so ist seine Zählergrenze je nach Auflösung 255 oder entsprechend für die 16-Bit Timer. Erst wenn diese Grenze erreicht wurde, wird der Timer zurückgesetzt (also auf 0). Durch den CTC Modus wird der Timer augenblicklich automatisch nachdem ein "Compare Output"-Interrupt auftrat zurückgesetzt. Man kann also die maximalen Zählergrenze selber definieren. Dieses Diagramm veranschaulicht den CTC Modus. Nach dem Interrupt wird der Timer sofort wieder zurückgesetzt.
PWM
Für PWM siehe Pwm.
Registerübersicht
Hinweis: Diese Registertabellen wurden für den aktuellen Atmel Controller Mega16 und Mega32 erstellt. Wenn Sie ein anderes Modell verwenden kann es sein, dass ein oder mehrere Register nicht existieren, oder sie eine andere Bezeichnung haben.
TIMSK | ||||||||||||||||||
Mit diesem Register, der von allen Timern verwendet wird, lässt sich die Interruptausführung und Art des jeweiligen Timers bestimmen.
| ||||||||||||||||||
|