Aus RN-Wissen.de
Wechseln zu: Navigation, Suche
Laderegler Test Tueftler Seite

K (Fehler in der Rechnung mit 10-Potenzen behoben.)
(Normaler Modus (Normal Mode))
 
(47 dazwischenliegende Versionen von 7 Benutzern werden nicht angezeigt)
Zeile 1: Zeile 1:
Die Mikrocontroller der AVR-Familie besitzen je nach Typ eine unterschiedliche Anzahl an programmierbaren [[Timer|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 Mikrocontroller der AVR-Familie besitzen je nach Typ eine unterschiedliche Anzahl an programmierbaren [[Timer|Timern]]. Bei den aktuellen ATmegas sind das mindestens ein 8-Bit Timer und ein 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.
 
Die Konfigurationsmöglichkeiten sind von Timer zu Timer unterschiedlich.
  
'''Hinweis:''' Die folgenden Code-Beispiele sind in C programmiert und wurden für einen [[ATMega32|ATmega32]] entwickelt. Sie lassen sich also ohne große Änderungen auch auf anderen Mikrocontrollern der AVR-Familie einsetzen.
+
'''Hinweis:''' Die folgenden Code-Beispiele sind in C programmiert und wurden für einen [[ATMega32|ATmega32]] entwickelt. Sie lassen sich also ohne große Änderungen auch auf anderen Mikrocontrollern der AVR-Familie einsetzen. Allerdings hat Atmel bei den neueren µCs (etwa Mega88, Mega324 und fast alle der aktuellen Tiny) die Namen für die Register vielfach geändert.  
  
 
== Allgemeine Funktionsweise ==
 
== 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.
 
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 [[Avr#Normaler Modus (Normal Mode)|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).
+
Angenommen, der Timer arbeitet im einfachsten Betriebsmodus, dem [[Timer/Counter (Avr)#Normaler Modus (Normal Mode)|Normalen Modus]]. Die Zählrichtung des Timers ist aufsteigend gerichtet. Je nach Auflösung, also 8-Bit oder 16-Bit, folgt auf den maximalen Zählerstand wieder die Null. Wenn z.B. bei einem 8-Bit Timer der Wert 255 inkrementiert wird folgt die Null (siehe Grafik).
  
 
[[Bild:AbstrakterZaehlvorgang.png]]
 
[[Bild:AbstrakterZaehlvorgang.png]]
  
 
== Der Prescaler ==
 
== 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:
+
Der Prescaler (eng. = Vorteiler) kann der Takt für den Timer herunter geteilt werden. Oft hat man Faktoren von 1, 8, 64 ,256 oder 1024 zur Auswahl. Über das selbe Register kann der Timer auch ganz angehalten werden oder ein externer Takt ausgewählt werden. Ein externer Takt darf dabei höchstens halb so hoch wie der Prozezessortakt sein.
 +
Hier eine Grafik die den Prescaler veranschaulicht:
  
 
[[Bild:Prescaler.png]]
 
[[Bild:Prescaler.png]]
  
Das obere Diagramm zeigt den Betrieb ohne Prescaler, das untere mit Prescaler. Die gestrichelte Linie zeigt, wann ein Interrupt eintritt.
+
Das obere Diagramm zeigt den Betrieb ohne Prescaler, das untere mit Prescaler (:2). Die gestrichelte Linie zeigt, wann der Timer weiterzählt.
  
Im Teil [[Avr#Die Betriebsmodi|Die Betriebsmodi]] wird weiter auf die praktische Verwendung des Prescalers eingegangen.
+
Im Teil [[Timer/Counter (Avr)#Die Betriebsmodi|Die Betriebsmodi]] wird weiter auf die praktische Verwendung des Prescalers eingegangen.
  
 
== Die Betriebsmodi ==
 
== Die Betriebsmodi ==
Zeile 27: Zeile 28:
  
 
=== Normaler Modus (Normal Mode) ===
 
=== Normaler Modus (Normal Mode) ===
Der einfachste Betriebsmodus ist der normale Modus. Er funktioniert wie im Abschnitt  "[[Avr#Allgemeine Funktionsweise|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 einfachste Betriebsmodus ist der normale Modus. Er funktioniert wie im Abschnitt  "[[Timer/Counter (Avr)#Allgemeine Funktionsweise|Allgemeine Funktionsweise]]" beschrieben. Die Zählrichtung des Timers ist immer aufsteigend, bis zum Überlauf - da fängt der Zähler wieder bei 0 an. Der Überlauf kann einen Interrupt (Timer-Overflow) auslösen. Im einfachsten Fall kann dieser Modus im folgendem Diagramm dargestellt werden:
  
 
[[Bild:NormalerModus_1.png]]
 
[[Bild:NormalerModus_1.png]]
  
 
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.
 
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:
+
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 zugegriffen werden und ihn vorladen, bevor dieser wieder vom eigentlichen Timer hochgezählt wird. Dies veranschaulicht folgendes Diagramm:
  
 
[[Bild:NormalerModus_1_Vorladen.png]]
 
[[Bild:NormalerModus_1_Vorladen.png]]
  
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 [[Avr#Weblinks|Weblinks Java Applet]].
+
Dadurch kann eingestellt werden, wie lange es dauert, bis ein Overflow auftritt. Um zu berechnen, welchen Wert wir vorladen müssen, kann auch ein Java-Applet genutzt werden, siehe unter [[Timer/Counter (Avr)#Weblinks|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):
+
Natürlich kann das auch "von Hand" berechnet werden. 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>
 
# <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.
 
# 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>).
 
# 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>.
 
# 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>).
+
# Nun wird mathematisch überprüft, ob der errechnete Wert aus dem vierten Punkt kleiner als der maximale Zählerwert ist. Trifft dies zu, so wird der errechneten Wert vom maximalen Zählerwert subtrahiert(<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.
 
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.
+
Zwischen dem Timer Overflow und dem tatsächlichen Aufrufen der ISR mit dem Nachladen des Timers ergibt sich eine kleine Verzögerung, die nicht einmal immer gleich ist. Bei einem genügend großen Prescaler (z.B. 64) kommt durch die Verzögerung kein zusätzlicher Timerschritt zustande, und auch die Methode mit dem Nachladen liefert exakte Ergebnisse. Bei kleinen Prescalern kommt  es durch die Verzögerung zu längeren und nicht immer gleichen Zeitabständen. Wenn möglich wird für die Erzeugung einer konstanten Interruptrate deshalb besser der CTC Moduls benutzt.
  
Die Fehlerrate kann natürlich auch ausgerechnet werden. Hier die Rechenschritte (sie sind erweiternd zu der oberen Berechnung):
+
Zusammenfassend ein Code-Beispiel (kein vollständiges Programm):
# 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:
+
  
 
<pre>
 
<pre>
 
/* Es wird der Timer2 (8-Bit) eines ATmega32 verwendet, der mit einem Quarz mit 7,3728 MHz
 
/* Es wird der Timer2 (8-Bit) eines ATmega32 verwendet, der mit einem Quarz mit 7,3728 MHz
betrieben wird. Im Abstand von 0,01 ms erzeugt der Timer einen Interrupt, also eine
+
betrieben wird. Im Abstand von etwa 0,1 ms erzeugt der Timer einen Interrupt, also eine
Frequenz von 100000 Hz (oder 100 kHz). Der Timer wird auf einen Prescaler von 1 und
+
Frequenz von 10000 Hz. Der Timer wird auf einen Prescaler von 64 und
einem Preloader von 183 konfiguriert.*/
+
einem Preloader von 244 konfiguriert.*/
  
 
volatile uint8_t countTimer2; // Speichert den aktuellen Zählerwert
 
volatile uint8_t countTimer2; // Speichert den aktuellen Zählerwert
  
 
// ISR zum auffangen der Interrupts:
 
// ISR zum auffangen der Interrupts:
SIGNAL(SIG_SIG_OVERFLOW2)
+
SIGNAL(SIG_SIG_OVERFLOW2)      // alter Form, für neuere GCC Versionen:  ISR(TIMER2_OVF_vect)
 
{
 
{
 +
TCNT2 = 244; // Nachladen
 
countTimer2++;
 
countTimer2++;
TCNT2 = 183; // Nachladen
 
 
}
 
}
  
 
// Initialisierung:
 
// Initialisierung:
TCCR2 = (1<<CS22); // Prescaler von 1
+
TCCR2 = (1<<CS22); // Prescaler von 64 und damit Timer starten
TCNT2  = 183; // Vorladen
+
TCNT2  = 244; // Vorladen
TIMSK |= (1<<TOIE2); // Interrupts aktivieren und damit Timer starten
+
TIMSK |= (1<<TOIE2); // Interrupts aktivieren  
 
sei();
 
sei();
  
 
// Funktionen zum benutzen der Timer:
 
// Funktionen zum benutzen der Timer:
 
/** Diese Funktion nicht aufrufen. Wird von sleep_millisec aufgerufen.
 
/** Diese Funktion nicht aufrufen. Wird von sleep_millisec aufgerufen.
Bei t=100 schläft die Funktion 1 ms. */
+
Bei t=10 schläft die Funktion 1 ms. */
 
inline void sleep (uint8_t t)
 
inline void sleep (uint8_t t)
 
{
 
{
 
// countTimer2 wird in der ISR oben inkrementiert
 
// countTimer2 wird in der ISR oben inkrementiert
countTimer2 = 0;
+
        countTimer2 = 0;         // 1 Byte Typ, daher kein cli()... sei() nötig
 
while (countTimer2 < t);
 
while (countTimer2 < t);
 
}
 
}
Zeile 95: Zeile 88:
 
uint16_t i;
 
uint16_t i;
 
for(i=0; i<msec; i++) {
 
for(i=0; i<msec; i++) {
sleep(100);
+
sleep(10);
 
}
 
}
 
}
 
}
 
</pre>
 
</pre>
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.  
+
Dieses Beispiel zeigt nicht unbedingt eine vorbildliche Nutzung des Timers. Eine ISR einfach nur zum schnellen Hochzählen der Zeit verbraucht recht viel Rechenzeit und sollte sonst eher vermieden werden. Das Hochzählen der "Zeit" ist eigentlich genau das, was der Timer in Hardware macht - nur halt nicht immer in geraden Zeitschritte, wie 0,1 ms, sondern halt in den Schritten, die der Prescaler vorgibt (z.B. 256 Takte). Die Umrechnung kann man aber gut auch bei der Wartezeit vorher, oder bei einer gemessenen Zeit nachher machen.
  
Hinweis: Siehe CTC Modus unten. Dieser wird benötigt um den Timer entsprechend im "Compare Output"-Betriebsmodus vernünftig zu betreiben.
+
==== Input Capture ====
  
Hier ein typisches Diagramm dieses Betriebsmodus (Hinweis: Das Diagramm wurde unter Verwendung des CTC Modus erstellt. Für Begriffserklärung siehe CTC Modus):
+
Die 16 Bit Timer haben eine "Input Capture" Funktion. Dieser Hardwareteil dient zur genauen Zeitmessung. Die typische Anwendung ist die Messung von kurze Zeiten, wie z.B. die Zeit für eine Motorumdrehung. Außer in einigen PWM Betriebsarten, wo das ICP Register als TOP-wert für den Timer benutzt wird, ist die ICP-Funktion immer aktiv. Wenn am ICP Pin die über das Bit "ICESx" eingestellte Flanke auftritt, wird der aktuelle Zählerstand in das ICP Register kopiert. Außerdem kann ein Interrupt ausgelöst werden. Der Interrupt wird, wie die anderen Timer Interrupts, in den Registern TIMSK und TIFR an- oder abgestellt. Man kann zwar die ICP-Funktion selber nicht ohne weiteres abschalten, aber natürlich den dazugehörigen Interrupt.  Als eine spezielle Funktion ("Noise Cancler") gibt es die Möglichkeit sehr kurze Pulse (unter 4 Zyklen) zu unterdrücken. In der Regel kann man diese Funktion angestellt lassen, denn so schnell kann man die Daten ohnehin nicht verarbeiten.
  
[[Bild:NormalerModus_CompareMatch.png]]
+
Solange die 16 Bit des Timers ausreichen ist die Benutzung ganz einfach: Der Timer wird mit dem gewünschten Vorteiler im normalen Modus gestartet. Im ICP-Interrupt wird die Differenz aus zwei aufeinanderfolgenden Zeiten (Werte in ICP-Register) berechnet. Dazu wird jeweils die vorherige Zeit im RAM zwischengespeichert. Wenn man bei der Rechnung (vorzeichenlose 16 Bit Zahlen) eventuelle Überläufe ignoriert, bekommt man die richtige Zeitdifferenz, auch wenn der Timer während der Messzeit einen Überlauf hatte. Das funktioniert so einfach, denn wenn noch weitere (höherwertige) Bytes vorhanden wären, damit es keinen Überlauf gibt, würde man genau so die unteren Bits berechnen.
  
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.
+
Etwas komplizierter wird es, wenn die 16 Bit Auflösung nicht mehr ausreicht. Dann kann der Timer-Überlauf benutzt werden, um auch längere Zeiten mit voller Auflösung zu messen. Die wesentliche Schwierigkeit ist es, den Fall zu berücksichtigen, dass ein Überlauf Interrupt und der ICP Interrupt fast gleichzeitig ausgelöst werden. Es kann passieren, dass der ICP-Interrupt aufgerufen wird, obwohl eigentlich erst der Overflow Interrupt dran gewesen wäre. Dieser seltene Fall lässt sich daran erkennen, dass das Overflow-Interrupt Flag gesetzt ist und der Wert im ICP Register klein ist (high Byte < 128, meistens 0).
  
Wiederum zusammenfassend ein Beispielprogramm:
+
Beispielpropgramm (für GCC):
 +
(Bisher nur im Simulator getestet)
 +
<pre>
 +
// Beispielprogramm für Zeitmessung mit ICP-Funktion
 +
// Erweiterung des Timers auf 32 Bit durch Software
 +
// Es wird die Periodendauer am ICP-Eingang gemessen und als ASCII via UART ausgegeben
 +
// Code für Mega48 / Mega88 / Mega 168 / ...
 +
// mit leichten Anpassungen auch für Tiny2313, Mega16, Mega32,...
 +
 
 +
#include <stdlib.h>          // für utoa
 +
#include <avr/io.h>
 +
#include <avr/interrupt.h>
 +
#include <avr/sleep.h>      // Unterstützung für sleep mode
 +
 
 +
#define F_CPU  1000000UL    // Definition der Frequenz, ist ggf. im makefile
 +
#define BAUD        19200UL
 +
#define UBRR_BAUD  ((F_CPU/(16UL*BAUD))-1)
 +
 
 +
typedef union {  // union erlaubt einen effektiven, separaten Zugriff auf Teile der Variable
 +
        unsigned long i32;
 +
        struct {uint8_t i8l;        // low
 +
                uint8_t i8m;        // mid
 +
unsigned int high;  // high, soft timer               
 +
              };
 +
              } convert32to8;
 +
 
 +
volatile unsigned long timestamp;    // volatile wegen Zugriff im Interrupt
 +
volatile unsigned int softtimer;
 +
volatile unsigned long zeitdifferenz;
 +
unsigned long zeit;
 +
char puffer[12];          // Puffer für Ausgabe als Ascii
 +
 
 +
ISR(TIMER1_OVF_vect)    // Timer1 Überlauf
 +
{
 +
  ++softtimer;   // zählen der Überläufe
 +
}
 +
 
 +
ISR(TIMER1_CAPT_vect)      //  Flanke an ICP pin
 +
{
 +
  convert32to8 cap;        // Variablendeklaration
 +
 
 +
  cap.i8l = ICR1L;        // low Byte zuerst, high Byte wird gepuffert
 +
  cap.i8m = ICR1H; 
 +
  // overflow verpasst, wenn ICR1H klein und wartender Overflow Interrupt
 +
  if ((cap.i8m < 128) && (TIFR1 & (1<<TOV1)))
 +
  {  // wartenden timer overflow Interrupt vorziehen
 +
    ++softtimer;       
 +
    TIFR1 = (1<<TOV1);    // timer overflow int. löschen, da schon hier ausgeführt
 +
  }
 +
  cap.high = softtimer;    // obere 16 Bit aus Software Zähler
 +
  zeitdifferenz = cap.i32 - timestamp;
 +
  timestamp = cap.i32;    // Zeit merken
 +
}
 +
 
 +
void uart_init(void)      // USART initialisieren (Mega48 etc.)
 +
{
 +
// Baudrate einstellen (Normaler Modus)
 +
// kann bei älteren AVR Typen etwas anders sein (kein UBRR0H, dafür prescaler)
 +
  UBRR0H = (uint8_t) (UBRR_BAUD>>8);            // bei Mega32  anders !
 +
  UBRR0L = (uint8_t) (UBRR_BAUD & 0x0ff);        // bei Mega32 UBRRL
 +
  UCSR0B = (1<<TXEN0);  // Aktivieren des Senders, bei Mega32 UCSRB
 +
  UCSR0C = (1<<UCSZ01)|(1<<UCSZ00)|(1<<USBS0);  // bei Mega32 UCSRC
 +
// Einstellen des Datenformats: 8 Datenbits, 2 Stopbit:
 +
}
 +
 
 +
void putser(char c)  // sende ein Byte via UART
 +
{
 +
  while ( !( UCSR0A & (1<<UDRE0)) ) ; // Warten bis der Sendepuffer frei ist
 +
  UDR0 = c;
 +
}
 +
 
 +
void main(void)
 +
{
 +
unsigned char i;
 +
// Datenrichtungen:
 +
DDRB  = 0;    // Alles Eingänge, PB0 ist ICP
 +
PORTB = 0xFF - (1<<PB0);        // Pullups an Eingängen außer ICP
 +
DDRC  = 0;      // Eingänge
 +
PORTC = 0xFF;    // Pullups an Eingängen
 +
DDRD  = (1<<PD1);                // Eingänge, außer PD1 = Tx (UART)
 +
PORTD = 0xFF- (1<<PD1);          // Pullups an alle Eingängen (außer TX)
 +
// Timer1 initialisieren:
 +
TCCR1A = 0;                      // normal mode, keine PWM Ausgänge
 +
TCCR1B = (1<< ICNC1) + (1<<CS10)    // start Timer mit Systemtakt
 +
          + (1 << ICES1);            // steigende Flanke auswählen
 +
TIMSK1 = (1<<TOIE1) + (1<<ICIE1);  // overflow und Input-capture aktivieren, Mega32: TIMSK
 +
TIFR1 = (1<<TOIE1) + (1<<ICIE1);    // Schon aktive Interrupts löschen, Mega32: TIFR
 +
// UART initialisieren:
 +
uart_init();               
 +
 
 +
zeitdifferenz = 0;
 +
softtimer = 0;        // wird für Zeitdifferenzmessung nicht mal gebraucht,
 +
                        // denn Differenz geht auch über Überlauf bei Softtimer
 +
set_sleep_mode (SLEEP_MODE_IDLE);  // idle Mode: timer läuft weiter, int zum aufwachen
 +
 
 +
sei();                // Interrupts erlauben: Messung startet
 +
while (1)
 +
{
 +
  sleep_enable ();      // Sleep Befehl freigeben
 +
  sei();
 +
  sleep_cpu();          // wartet auf irgendeinen Interrupt, z.B. ICP, timer_ovr,...
 +
  sleep_disable();      // Sleep Befehl sperren
 +
  cli(); // Interrupt sperren wegen Zugriff auf volatile Variable
 +
  zeit = zeitdifferenz;
 +
  zeitdifferenz = 0;  // als Markierung für ungültigen Wert
 +
  sei();
 +
  if (zeit > 0)
 +
  {
 +
    ultoa(zeit,puffer,10);  // nach ASCII umwandeln
 +
    i = 0;
 +
    while (puffer[i])
 +
    {
 +
      putser(puffer[i++]);  // Ausgabe
 +
    }
 +
    putser(13); putser(10); // Zeilenumbruch senden
 +
  }
 +
}  // Ende von While-schleife
 +
}
 +
</pre>
 +
 
 +
=== CTC Modus (Clear Timer on Compare Match mode) ===
 +
Viele Timer haben "Output-Compare" Register: OCRx oder OCRAx,OCRBx. Wenn der Zähler den darin eingestellten Wert erreicht hat, kann ein Interrupts ausgelöst werden. Der CTC Modus ist eine Erweiterung des "Output-Compare"-Funktion. Der CTC Modus eignet sich besonders, um einen mit konstanter Frequenz wiederkehrenden Interrupt zu erzeugen. Wie im normalen Modus zählt der Timer hoch. Wenn der Wert im OCRx Register erreicht wird, wird zusätzlich zum möglichen Interrupt der Zähler wieder auf 0 gesetzt. Es kann also die maximalen Zählergrenze selber definiert werden.
 +
Dieses Diagramm veranschaulicht den CTC Modus.
 +
 
 +
[[Bild:NormalerModus_CompareMatch.png]]
 +
 
 +
Beispielprogramm:
  
 
<pre>
 
<pre>
/* Es wird der Timer2 (8-Bit) eines ATmega32 verwendet, der mit einem Quarz mit 7,3728 MHz
+
/* Es wird der Timer2 (8-Bit) eines ATmega32 verwendet, der mit einem Quarz  
betrieben wird. Im Abstand von 0,01 ms erzeugt der Timer einen Interrupt, also eine Frequenz
+
  mit 7,3728 MHz betrieben wird. Im Abstand von 0,01 ms erzeugt der Timer  
von 100000 Hz (oder 100 kHz). Der Timer wird auf einen Prescaler von 1 und einem OCR2-Wert
+
  einen Interrupt, also eine Frequenz von 100000 Hz (oder 100 kHz).  
von 73 konfiguriert.*/
+
  Der Timer wird auf einen Prescaler von 1 und einem OCR2-Wert von 73 konfiguriert. */
  
 
volatile uint8_t countTimer2; // Speichert den aktuellen Zählerwert
 
volatile uint8_t countTimer2; // Speichert den aktuellen Zählerwert
Zeile 128: Zeile 246:
  
 
// Initialisierung:
 
// Initialisierung:
TCCR2 = (1<<CS22) | (1<<WGM21); // Prescaler von 1 | CTC-Modus (siehe unten für Beschreibung)
+
TCCR2 = (1<<CS20) | (1<<WGM21); // Prescaler von 1 | CTC-Modus (siehe unten für Beschreibung)
 
OCR2  = 73; // Vergleichswert
 
OCR2  = 73; // Vergleichswert
 
TIMSK |= (1<<OCIE2); // Interrupts aktivieren und damit Timer starten
 
TIMSK |= (1<<OCIE2); // Interrupts aktivieren und damit Timer starten
Zeile 153: Zeile 271:
 
</pre>
 
</pre>
  
=== 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.
+
 
+
[[Bild:NormalerModus_CompareMatch.png]]
+
  
 
=== PWM ===
 
=== PWM ===
Für PWM siehe [[Pwm]].
+
 
 +
Eine häufige Aufgabe für Mikrocontroller ist die Erzeugung von [[PWM]]-Signalen, zum Beispiel für Motorsteuerungen. Daher sind in den meisten [[AVR|AVRs]] PWM-Einheiten als Hardware vorhanden. Sie sind direkt mit den Timern verbunden und nutzen diese als Taktquelle. Die Hardware-PWM-Einheiten haben den Vorteil, sehr wenig Rechenzeit in Anspruch zu nehmen. Es muss nur die PWM aktiviert werden und bei Änderungen den gewünschten Wert in ein Register schreiben. Der Rest läuft automatisch und unabhängig vom restlichen Programm, ohne den AVR ständig zu beschäftigen wie bei einer PWM-Lösung in Software. Allerdings stehen meist nur zwei bis drei solcher PWM-Kanäle zur Verfügung, die außerdem an bestimmte Pins gebunden sind. Für die meisten Roboter mit zwei Antriebsmotoren reicht dies aber für gewöhnlich aus.
 +
 
 +
==== nutzbare Pins am AVR ====
 +
Die Hardware-PWM-Funktion steht nur an bestimmten Pins zur Verfügung. In der Pinbelegungsübersicht im Datenblatt ist erkenntbar, dass als Sonderfunktion in Klammern "OC..." angegeben ist. Beim Mega32 sind dies zb. OC0 an PB3, OC1A an PD5, OC1B an PD4 und OC2 an PD7. Der Mega32 hat also insgesamt vier Hardware-PWM-Kanäle. Die Zahl hinter dem "OC" gibt an, zu welchem der Timer dieser PWM-Kanal gehört. Wenn noch ein Buchstabe dahinter kommt, dann gehören mehrere PWMs zu diesem Timer. Beim Mega32 sind also OC1A und OC1B demselben Timer, nämlich Timer1, zugeordnet.
 +
 
 +
'''Zu beachten ist, dass die für die PWM benutzten Pins zuvor explizit als Ausgang konfiguriert werden müssen! Ansonsten gelangt das PWM-Signal nicht nach draußen!'''
 +
 
 +
==== Funktionsprinzip ====
 +
Das "OC" in den Pinbezeichnungen steht für "Output Compare Unit", also frei übersetzt Ausgangs-Vergleicher-Einheit. Dies beschreibt die Funktionsweise der PWM-Kanäle: der Zählerstand des Timers wird fortlaufend mit einen einstellbaren Referenzwert verglichen, und wenn beide Werte übereinstimmen, kann ein Ausgangspin des AVRs automatisch geschaltet werden (und ein Interrupt ausgelöst werden, was allerdings für die PWM-Funktion nicht relevant ist). Dies entspricht dem Verfahren im [[PWM#PWM_per_Software|Beispiel zur Software-PWM]]. Es läuft nun allerdings vollautomatisch im Hintergrund, sodass der Controller nicht damit belastet wird.
 +
 
 +
==== Die verschiedenen PWM-Modi ====
 +
Es gibt -je nach AVR und Timer- etliche Betriebsarten, in denen die PWM-Einheit betrieben werden kann. Sie unterscheiden sich vor allem darin, wie schnell und mit welchen Nebeneffekten sich Änderungen des Sollwertes auf das Ausgangssignal auswirken. Für den Anfang sind diese Unterschiede erst einmal nebensächlich, und für eine einfache Motorsteuerung meist auch irrelevant. Daher wird hier zunächst der "Fast PWM Mode" ("Schneller PWM Modus", weil hier die größte Ausgangsfrequenz möglich ist) beschreiben, welcher der einfachste von allen ist.
 +
 
 +
Hierbei zählt der Timer immer von Null an aufwärts, bis er den Maximalwert (teilweise einstellbar) erreicht hat. Dann läuft er über und fängt von vorne an. Wie schnell dies geschieht, wird, wie im normalen Modus, über den Prescaler eingestellt.
 +
Der gewünschte PWM-Ausgangswert wird im "OCRn"-Register abgelegt. Er darf zwischen Null und dem Maximalwert des Timers liegen. Er wird nun mit dem Timer-Wert verglichen. Was dann passiert, regeln die "COM..."-Bits. Sie bestimmen, wie der Ausgang geschaltet wird. Die übliche Konfiguration ist, dass bei Erreichen des Sollwertes die Ausgänge auf high geschaltet werden, und beim Überlauf auf low. Damit ergibt sich ein nichtinvertiertes PWM-Signal. Schließt man (über einem passenden Motortreiber!) einen Motor an, dreht er sich bei einem Sollwert von 0 gar nicht und beim Maximalwert mit voller Geschwindigkeit.
 +
 
 +
Beispielcode für den Timer1 des Mega16/32 (und vieler anderer AVRs):
 +
<pre>
 +
#include <avr/io.h>
 +
#include <util/delay.h> // Warteschleife für die Demo. Für die eigentliche PWM nicht benötigt! 
 +
 
 +
/*  PWM-Beispiel für Mega16/32 (beiden haben den gleichen Timer)
 +
Benutzt wird Timer1 im Fast PWM Mode, 8 Bit Auflösung
 +
Die PWM-Signale liegen auf PD5/OC1A und PD4/OC1B
 +
*/
 +
 
 +
// 1. Den Prescaler einstellen, der die Frequenz festlegt
 +
TCCR1B |= (1<<CS12); //Prescaler 256
 +
 +
// 2. Den Timer in den Fast PWM Mode, 8 Bit schalten
 +
//    ACHTUNG: Die WGM-Bits sind auf beide Konfigurationsregister verteilt!
 +
TCCR1A |= (1<<WGM10);
 +
TCCR1B |= (1<<WGM12);
 +
 +
// 3. Compare Output mode einstellen: Pin geht auf high bei Compare match, auf low bei Überlauf.
 +
//    Ergibt nichtinvertierte PWM.
 +
TCCR1A |= (1<<COM1A1) | (1<<COM1B1) ;
 +
 +
// In diesen Registern wird der gwünschte PWM-Wert abgelegt. Erlaubter Bereich: 0 bis 255.
 +
OCR1A = 0;
 +
OCR1B = 0;
 +
 
 +
// 4. Die Pins als Ausgänge konfigurieren. Erst jetzt liegt das PWM-Signal an den Pins an!
 +
DDRD |= (1<<PD4) | (1<< PD5);
 +
 
 +
/*Nun ist der PWM-Modus aktiv! Der Ausgangswert kann nun über die Register OCR1A und OCR1B
 +
  vorgegeben werden. Man könnte ihnen per define noch einen Zweitnamen verpassen, zb */
 +
#define MotorLinks OCR1A
 +
#define MotorRechts OCR1B
 +
//Und nun kann man per
 +
MotorLinks = 127;
 +
MotorRechts = 127;
 +
//seinen Roboter mit halber Kraft vorwärts fahren lassen.
 +
 
 +
/*PWM-Demo: Die PWM-Werte werden erst bis zum Maximalwert erhöht und dann wieder verringert.
 +
Ein angeschlossener Motor wird beschleunigen und dann wieder abbremsen. */
 +
uint8_t wert;
 +
while(1)
 +
{
 +
for (wert=0; wert<255; wert++)
 +
{
 +
OCR1A = wert;
 +
OCR1B = wert;
 +
_delay_ms(10);
 +
}
 +
 +
for (wert=255; wert>0; wert--)
 +
{
 +
OCR1A = wert;
 +
OCR1B = wert;
 +
_delay_ms(10);
 +
}
 +
}
 +
</pre>
 +
 
 +
Es sind also vier Einstellungen zu treffen:
 +
# Takt anlegen per Prescaler
 +
# PWM-Modus wählen
 +
# Ausgangs-Aktion festlegen
 +
# Pins als Ausgänge schalten
 +
 
 +
Diese Schritte müssen bei jedem AVR-PWM-Kanal ausgeführt werden. Die genauen Registernamen und Werte können sich jedoch je nach Timer-Ausführung etwas unterscheiden.
  
 
== Registerübersicht ==
 
== Registerübersicht ==
Zeile 189: Zeile 385:
 
* [[Atmel]]
 
* [[Atmel]]
 
* [[HEX Beispiel-Dateien für AVR]]
 
* [[HEX Beispiel-Dateien für AVR]]
 
+
* [[Bascom_und_Timer]]
  
 
== Weblinks ==
 
== Weblinks ==
  
 
* [http://www.atmel.com/dyn/products/devices.asp?family_id=607 Die Datenblätter zu Atmel Controllern]
 
* [http://www.atmel.com/dyn/products/devices.asp?family_id=607 Die Datenblätter zu Atmel Controllern]
* [https://mpg.dnsalias.com/~magerlu/rn-wiki/avrtimer_applet Java Applet Timer Berechnung]  
+
* [http://frank.circleofcurrent.com/cache/avrtimercalc.htm  Javascript-Toll zur Timerberechnung]
 
* [http://www.roboternetz.de/phpBB2/dload.php?action=file&file_id=169 AvrTimer Windows Berechnungstool (für Bascom, nur nach Anmeldung)]
 
* [http://www.roboternetz.de/phpBB2/dload.php?action=file&file_id=169 AvrTimer Windows Berechnungstool (für Bascom, nur nach Anmeldung)]
  
Zeile 202: Zeile 398:
 
[[Kategorie:Elektronik]]
 
[[Kategorie:Elektronik]]
 
[[Kategorie:Abkürzung|AVR]]
 
[[Kategorie:Abkürzung|AVR]]
 +
[[Kategorie:Quellcode C]]

Aktuelle Version vom 8. November 2014, 12:35 Uhr

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 ein 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. Allerdings hat Atmel bei den neueren µCs (etwa Mega88, Mega324 und fast alle der aktuellen Tiny) die Namen für die Register vielfach geändert.

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. Die Zählrichtung des Timers ist aufsteigend gerichtet. Je nach Auflösung, also 8-Bit oder 16-Bit, folgt auf den maximalen Zählerstand wieder die Null. Wenn z.B. bei einem 8-Bit Timer der Wert 255 inkrementiert wird folgt die Null (siehe Grafik).

AbstrakterZaehlvorgang.png

Der Prescaler

Der Prescaler (eng. = Vorteiler) kann der Takt für den Timer herunter geteilt werden. Oft hat man Faktoren von 1, 8, 64 ,256 oder 1024 zur Auswahl. Über das selbe Register kann der Timer auch ganz angehalten werden oder ein externer Takt ausgewählt werden. Ein externer Takt darf dabei höchstens halb so hoch wie der Prozezessortakt sein. Hier eine Grafik die den Prescaler veranschaulicht:

Prescaler.png

Das obere Diagramm zeigt den Betrieb ohne Prescaler, das untere mit Prescaler (:2). Die gestrichelte Linie zeigt, wann der Timer weiterzählt.

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, bis zum Überlauf - da fängt der Zähler wieder bei 0 an. Der Überlauf kann einen Interrupt (Timer-Overflow) auslösen. Im einfachsten Fall kann dieser Modus im folgendem Diagramm dargestellt werden:

NormalerModus 1.png

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 zugegriffen werden und ihn vorladen, bevor dieser wieder vom eigentlichen Timer hochgezählt wird. Dies veranschaulicht folgendes Diagramm:

NormalerModus 1 Vorladen.png

Dadurch kann eingestellt werden, wie lange es dauert, bis ein Overflow auftritt. Um zu berechnen, welchen Wert wir vorladen müssen, kann auch ein Java-Applet genutzt werden, siehe unter Weblinks Java Applet.

Natürlich kann das auch "von Hand" berechnet werden. 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):

  1. [math]Prescale = Frequenz * 1000000 [Hz] = 8000000[/math]
  2. 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.
  3. Nun wird die Variable Prescale (s.o.) durch den verwendeten Prescaler (64) geteilt ([math]8000000 Hz / 64 = 125000[/math]).
  4. Als nächstes wird der im dritten Punkt errechnete Wert durch die gesuchte Frequenz geteilt [math]=125000 / 1000Hz = 125[/math].
  5. Nun wird mathematisch überprüft, ob der errechnete Wert aus dem vierten Punkt kleiner als der maximale Zählerwert ist. Trifft dies zu, so wird der errechneten Wert vom maximalen Zählerwert subtrahiert([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.

Zwischen dem Timer Overflow und dem tatsächlichen Aufrufen der ISR mit dem Nachladen des Timers ergibt sich eine kleine Verzögerung, die nicht einmal immer gleich ist. Bei einem genügend großen Prescaler (z.B. 64) kommt durch die Verzögerung kein zusätzlicher Timerschritt zustande, und auch die Methode mit dem Nachladen liefert exakte Ergebnisse. Bei kleinen Prescalern kommt es durch die Verzögerung zu längeren und nicht immer gleichen Zeitabständen. Wenn möglich wird für die Erzeugung einer konstanten Interruptrate deshalb besser der CTC Moduls benutzt.

Zusammenfassend ein Code-Beispiel (kein vollständiges Programm):

/* Es wird der Timer2 (8-Bit) eines ATmega32 verwendet, der mit einem Quarz mit 7,3728 MHz
betrieben wird. Im Abstand von etwa 0,1 ms erzeugt der Timer einen Interrupt, also eine
Frequenz von 10000 Hz. Der Timer wird auf einen Prescaler von 64 und
einem Preloader von 244 konfiguriert.*/

volatile uint8_t countTimer2;	// Speichert den aktuellen Zählerwert

// ISR zum auffangen der Interrupts:
SIGNAL(SIG_SIG_OVERFLOW2)       // alter Form, für neuere GCC Versionen:   ISR(TIMER2_OVF_vect)
{
	TCNT2 = 244;		// Nachladen
	countTimer2++;
}

// Initialisierung:
TCCR2 = (1<<CS22);		// Prescaler von 64 und damit Timer starten
TCNT2  = 244;			// Vorladen
TIMSK |= (1<<TOIE2);		// Interrupts aktivieren 
sei();

// Funktionen zum benutzen der Timer:
/** Diese Funktion nicht aufrufen. Wird von sleep_millisec aufgerufen.
Bei t=10 schläft die Funktion 1 ms. */
inline void sleep (uint8_t t)
{
	// countTimer2 wird in der ISR oben inkrementiert
        countTimer2 = 0;         // 1 Byte Typ, daher kein cli()... sei() nötig
	while (countTimer2 < t);
}

/** Schläft x-Millisekunden. */
inline void sleep_millisec(uint16_t msec)
{
	uint16_t i;
	for(i=0; i<msec; i++) {
		sleep(10);
	}
}

Dieses Beispiel zeigt nicht unbedingt eine vorbildliche Nutzung des Timers. Eine ISR einfach nur zum schnellen Hochzählen der Zeit verbraucht recht viel Rechenzeit und sollte sonst eher vermieden werden. Das Hochzählen der "Zeit" ist eigentlich genau das, was der Timer in Hardware macht - nur halt nicht immer in geraden Zeitschritte, wie 0,1 ms, sondern halt in den Schritten, die der Prescaler vorgibt (z.B. 256 Takte). Die Umrechnung kann man aber gut auch bei der Wartezeit vorher, oder bei einer gemessenen Zeit nachher machen.

Input Capture

Die 16 Bit Timer haben eine "Input Capture" Funktion. Dieser Hardwareteil dient zur genauen Zeitmessung. Die typische Anwendung ist die Messung von kurze Zeiten, wie z.B. die Zeit für eine Motorumdrehung. Außer in einigen PWM Betriebsarten, wo das ICP Register als TOP-wert für den Timer benutzt wird, ist die ICP-Funktion immer aktiv. Wenn am ICP Pin die über das Bit "ICESx" eingestellte Flanke auftritt, wird der aktuelle Zählerstand in das ICP Register kopiert. Außerdem kann ein Interrupt ausgelöst werden. Der Interrupt wird, wie die anderen Timer Interrupts, in den Registern TIMSK und TIFR an- oder abgestellt. Man kann zwar die ICP-Funktion selber nicht ohne weiteres abschalten, aber natürlich den dazugehörigen Interrupt. Als eine spezielle Funktion ("Noise Cancler") gibt es die Möglichkeit sehr kurze Pulse (unter 4 Zyklen) zu unterdrücken. In der Regel kann man diese Funktion angestellt lassen, denn so schnell kann man die Daten ohnehin nicht verarbeiten.

Solange die 16 Bit des Timers ausreichen ist die Benutzung ganz einfach: Der Timer wird mit dem gewünschten Vorteiler im normalen Modus gestartet. Im ICP-Interrupt wird die Differenz aus zwei aufeinanderfolgenden Zeiten (Werte in ICP-Register) berechnet. Dazu wird jeweils die vorherige Zeit im RAM zwischengespeichert. Wenn man bei der Rechnung (vorzeichenlose 16 Bit Zahlen) eventuelle Überläufe ignoriert, bekommt man die richtige Zeitdifferenz, auch wenn der Timer während der Messzeit einen Überlauf hatte. Das funktioniert so einfach, denn wenn noch weitere (höherwertige) Bytes vorhanden wären, damit es keinen Überlauf gibt, würde man genau so die unteren Bits berechnen.

Etwas komplizierter wird es, wenn die 16 Bit Auflösung nicht mehr ausreicht. Dann kann der Timer-Überlauf benutzt werden, um auch längere Zeiten mit voller Auflösung zu messen. Die wesentliche Schwierigkeit ist es, den Fall zu berücksichtigen, dass ein Überlauf Interrupt und der ICP Interrupt fast gleichzeitig ausgelöst werden. Es kann passieren, dass der ICP-Interrupt aufgerufen wird, obwohl eigentlich erst der Overflow Interrupt dran gewesen wäre. Dieser seltene Fall lässt sich daran erkennen, dass das Overflow-Interrupt Flag gesetzt ist und der Wert im ICP Register klein ist (high Byte < 128, meistens 0).

Beispielpropgramm (für GCC): (Bisher nur im Simulator getestet)

// Beispielprogramm für Zeitmessung mit ICP-Funktion
// Erweiterung des Timers auf 32 Bit durch Software
// Es wird die Periodendauer am ICP-Eingang gemessen und als ASCII via UART ausgegeben
// Code für Mega48 / Mega88 / Mega 168 / ...
// mit leichten Anpassungen auch für Tiny2313, Mega16, Mega32,...

#include <stdlib.h>          // für utoa
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/sleep.h>       // Unterstützung für sleep mode

#define F_CPU  1000000UL     // Definition der Frequenz, ist ggf. im makefile
#define BAUD        19200UL
#define UBRR_BAUD   ((F_CPU/(16UL*BAUD))-1)

typedef union {  // union erlaubt einen effektiven, separaten Zugriff auf Teile der Variable
        unsigned long i32;
        struct {uint8_t i8l;        // low
                uint8_t i8m;        // mid
		unsigned int high;  // high, soft timer                
               };
              } convert32to8;

volatile unsigned long timestamp;    // volatile wegen Zugriff im Interrupt
volatile unsigned int softtimer;
volatile unsigned long zeitdifferenz;
unsigned long zeit;
char puffer[12];           // Puffer für Ausgabe als Ascii

ISR(TIMER1_OVF_vect)   	   // Timer1 Überlauf
{ 
  ++softtimer; 		   // zählen der Überläufe
} 

ISR(TIMER1_CAPT_vect)      //  Flanke an ICP pin
{ 
  convert32to8 cap;        // Variablendeklaration
   
  cap.i8l = ICR1L;         // low Byte zuerst, high Byte wird gepuffert
  cap.i8m = ICR1H;  
  // overflow verpasst, wenn ICR1H klein und wartender Overflow Interrupt
  if ((cap.i8m < 128) && (TIFR1 & (1<<TOV1)))
   {   // wartenden timer overflow Interrupt vorziehen
     ++softtimer;         
     TIFR1 = (1<<TOV1);    // timer overflow int. löschen, da schon hier ausgeführt
   }
  cap.high = softtimer;    // obere 16 Bit aus Software Zähler
  zeitdifferenz = cap.i32 - timestamp;
  timestamp = cap.i32;     // Zeit merken
}

void uart_init(void)       // USART initialisieren (Mega48 etc.)
{
// Baudrate einstellen (Normaler Modus)
// kann bei älteren AVR Typen etwas anders sein (kein UBRR0H, dafür prescaler)
  UBRR0H = (uint8_t) (UBRR_BAUD>>8);             // bei Mega32  anders !
  UBRR0L = (uint8_t) (UBRR_BAUD & 0x0ff);        // bei Mega32 UBRRL
  UCSR0B = (1<<TXEN0);   // Aktivieren des Senders, bei Mega32 UCSRB
  UCSR0C = (1<<UCSZ01)|(1<<UCSZ00)|(1<<USBS0);   // bei Mega32 UCSRC
// Einstellen des Datenformats: 8 Datenbits, 2 Stopbit:
}

void putser(char c)  // sende ein Byte via UART
{
  while ( !( UCSR0A & (1<<UDRE0)) ) ; // Warten bis der Sendepuffer frei ist
  UDR0 = c;
}

void main(void)
{
 unsigned char i;
// Datenrichtungen:
 DDRB  = 0;  			  // Alles Eingänge, PB0 ist ICP
 PORTB = 0xFF - (1<<PB0);         // Pullups an Eingängen außer ICP
 DDRC  = 0;     		  // Eingänge
 PORTC = 0xFF;  		  // Pullups an Eingängen 
 DDRD  = (1<<PD1);                // Eingänge, außer PD1 = Tx (UART)
 PORTD = 0xFF- (1<<PD1);          // Pullups an alle Eingängen (außer TX)
// Timer1 initialisieren:
 TCCR1A = 0;                      // normal mode, keine PWM Ausgänge
 TCCR1B = (1<< ICNC1) + (1<<CS10)    // start Timer mit Systemtakt
          + (1 << ICES1);            // steigende Flanke auswählen
 TIMSK1 = (1<<TOIE1) + (1<<ICIE1);   // overflow und Input-capture aktivieren, Mega32: TIMSK
 TIFR1 = (1<<TOIE1) + (1<<ICIE1);    // Schon aktive Interrupts löschen, Mega32: TIFR
// UART initialisieren:
 uart_init();                

 zeitdifferenz = 0;
 softtimer = 0;         // wird für Zeitdifferenzmessung nicht mal gebraucht,
                        // denn Differenz geht auch über Überlauf bei Softtimer
 set_sleep_mode (SLEEP_MODE_IDLE);  // idle Mode: timer läuft weiter, int zum aufwachen

 sei();                 // Interrupts erlauben: Messung startet
 while (1)
 {
  sleep_enable ();      // Sleep Befehl freigeben 
  sei();
  sleep_cpu();          // wartet auf irgendeinen Interrupt, z.B. ICP, timer_ovr,...
  sleep_disable();      // Sleep Befehl sperren
  cli();		// Interrupt sperren wegen Zugriff auf volatile Variable
  zeit = zeitdifferenz;
  zeitdifferenz = 0;   	// als Markierung für ungültigen Wert
  sei();
  if (zeit > 0)
  { 
    ultoa(zeit,puffer,10);  // nach ASCII umwandeln
    i = 0;
    while (puffer[i])
     {
      putser(puffer[i++]);  // Ausgabe
     }
    putser(13); putser(10); // Zeilenumbruch senden
  }
 }  // Ende von While-schleife
}

CTC Modus (Clear Timer on Compare Match mode)

Viele Timer haben "Output-Compare" Register: OCRx oder OCRAx,OCRBx. Wenn der Zähler den darin eingestellten Wert erreicht hat, kann ein Interrupts ausgelöst werden. Der CTC Modus ist eine Erweiterung des "Output-Compare"-Funktion. Der CTC Modus eignet sich besonders, um einen mit konstanter Frequenz wiederkehrenden Interrupt zu erzeugen. Wie im normalen Modus zählt der Timer hoch. Wenn der Wert im OCRx Register erreicht wird, wird zusätzlich zum möglichen Interrupt der Zähler wieder auf 0 gesetzt. Es kann also die maximalen Zählergrenze selber definiert werden. Dieses Diagramm veranschaulicht den CTC Modus.

NormalerModus CompareMatch.png

Beispielprogramm:

/* Es wird der Timer2 (8-Bit) eines ATmega32 verwendet, der mit einem Quarz 
   mit 7,3728 MHz betrieben wird. Im Abstand von 0,01 ms erzeugt der Timer 
   einen Interrupt, also eine Frequenz von 100000 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<<CS20) | (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);
	}
}

PWM

Eine häufige Aufgabe für Mikrocontroller ist die Erzeugung von PWM-Signalen, zum Beispiel für Motorsteuerungen. Daher sind in den meisten AVRs PWM-Einheiten als Hardware vorhanden. Sie sind direkt mit den Timern verbunden und nutzen diese als Taktquelle. Die Hardware-PWM-Einheiten haben den Vorteil, sehr wenig Rechenzeit in Anspruch zu nehmen. Es muss nur die PWM aktiviert werden und bei Änderungen den gewünschten Wert in ein Register schreiben. Der Rest läuft automatisch und unabhängig vom restlichen Programm, ohne den AVR ständig zu beschäftigen wie bei einer PWM-Lösung in Software. Allerdings stehen meist nur zwei bis drei solcher PWM-Kanäle zur Verfügung, die außerdem an bestimmte Pins gebunden sind. Für die meisten Roboter mit zwei Antriebsmotoren reicht dies aber für gewöhnlich aus.

nutzbare Pins am AVR

Die Hardware-PWM-Funktion steht nur an bestimmten Pins zur Verfügung. In der Pinbelegungsübersicht im Datenblatt ist erkenntbar, dass als Sonderfunktion in Klammern "OC..." angegeben ist. Beim Mega32 sind dies zb. OC0 an PB3, OC1A an PD5, OC1B an PD4 und OC2 an PD7. Der Mega32 hat also insgesamt vier Hardware-PWM-Kanäle. Die Zahl hinter dem "OC" gibt an, zu welchem der Timer dieser PWM-Kanal gehört. Wenn noch ein Buchstabe dahinter kommt, dann gehören mehrere PWMs zu diesem Timer. Beim Mega32 sind also OC1A und OC1B demselben Timer, nämlich Timer1, zugeordnet.

Zu beachten ist, dass die für die PWM benutzten Pins zuvor explizit als Ausgang konfiguriert werden müssen! Ansonsten gelangt das PWM-Signal nicht nach draußen!

Funktionsprinzip

Das "OC" in den Pinbezeichnungen steht für "Output Compare Unit", also frei übersetzt Ausgangs-Vergleicher-Einheit. Dies beschreibt die Funktionsweise der PWM-Kanäle: der Zählerstand des Timers wird fortlaufend mit einen einstellbaren Referenzwert verglichen, und wenn beide Werte übereinstimmen, kann ein Ausgangspin des AVRs automatisch geschaltet werden (und ein Interrupt ausgelöst werden, was allerdings für die PWM-Funktion nicht relevant ist). Dies entspricht dem Verfahren im Beispiel zur Software-PWM. Es läuft nun allerdings vollautomatisch im Hintergrund, sodass der Controller nicht damit belastet wird.

Die verschiedenen PWM-Modi

Es gibt -je nach AVR und Timer- etliche Betriebsarten, in denen die PWM-Einheit betrieben werden kann. Sie unterscheiden sich vor allem darin, wie schnell und mit welchen Nebeneffekten sich Änderungen des Sollwertes auf das Ausgangssignal auswirken. Für den Anfang sind diese Unterschiede erst einmal nebensächlich, und für eine einfache Motorsteuerung meist auch irrelevant. Daher wird hier zunächst der "Fast PWM Mode" ("Schneller PWM Modus", weil hier die größte Ausgangsfrequenz möglich ist) beschreiben, welcher der einfachste von allen ist.

Hierbei zählt der Timer immer von Null an aufwärts, bis er den Maximalwert (teilweise einstellbar) erreicht hat. Dann läuft er über und fängt von vorne an. Wie schnell dies geschieht, wird, wie im normalen Modus, über den Prescaler eingestellt. Der gewünschte PWM-Ausgangswert wird im "OCRn"-Register abgelegt. Er darf zwischen Null und dem Maximalwert des Timers liegen. Er wird nun mit dem Timer-Wert verglichen. Was dann passiert, regeln die "COM..."-Bits. Sie bestimmen, wie der Ausgang geschaltet wird. Die übliche Konfiguration ist, dass bei Erreichen des Sollwertes die Ausgänge auf high geschaltet werden, und beim Überlauf auf low. Damit ergibt sich ein nichtinvertiertes PWM-Signal. Schließt man (über einem passenden Motortreiber!) einen Motor an, dreht er sich bei einem Sollwert von 0 gar nicht und beim Maximalwert mit voller Geschwindigkeit.

Beispielcode für den Timer1 des Mega16/32 (und vieler anderer AVRs):

#include <avr/io.h>
#include <util/delay.h> // Warteschleife für die Demo. Für die eigentliche PWM nicht benötigt!  

/*  PWM-Beispiel für Mega16/32 (beiden haben den gleichen Timer)
	Benutzt wird Timer1 im Fast PWM Mode, 8 Bit Auflösung
	Die PWM-Signale liegen auf PD5/OC1A und PD4/OC1B
*/	

// 1. Den Prescaler einstellen, der die Frequenz festlegt
	TCCR1B |= (1<<CS12); //Prescaler 256
	
// 2. Den Timer in den Fast PWM Mode, 8 Bit schalten
//    ACHTUNG: Die WGM-Bits sind auf beide Konfigurationsregister verteilt!
	TCCR1A |= (1<<WGM10);
	TCCR1B |= (1<<WGM12);
	
// 3. Compare Output mode einstellen: Pin geht auf high bei Compare match, auf low bei Überlauf. 
//    Ergibt nichtinvertierte PWM.	
	TCCR1A |= (1<<COM1A1) | (1<<COM1B1) ;	
	
// In diesen Registern wird der gwünschte PWM-Wert abgelegt. Erlaubter Bereich: 0 bis 255.
	OCR1A =	0;
	OCR1B = 0;	

// 4. Die Pins als Ausgänge konfigurieren. Erst jetzt liegt das PWM-Signal an den Pins an!	
	DDRD |= (1<<PD4) | (1<< PD5);

/*Nun ist der PWM-Modus aktiv! Der Ausgangswert kann nun über die Register OCR1A und OCR1B
  vorgegeben werden. Man könnte ihnen per define noch einen Zweitnamen verpassen, zb */
#define MotorLinks OCR1A
#define MotorRechts OCR1B
//Und nun kann man per 
MotorLinks = 127;
MotorRechts = 127;
//seinen Roboter mit halber Kraft vorwärts fahren lassen.

/*PWM-Demo: Die PWM-Werte werden erst bis zum Maximalwert erhöht und dann wieder verringert. 
Ein angeschlossener Motor wird beschleunigen und dann wieder abbremsen. */
	uint8_t wert;
	while(1)
	{
		for (wert=0; wert<255; wert++)
			{
				OCR1A =	wert;
				OCR1B = wert;
				_delay_ms(10);
			}
	
		for (wert=255; wert>0; wert--)
			{
				OCR1A =	wert;
				OCR1B = wert;
				_delay_ms(10);
			}	
	}

Es sind also vier Einstellungen zu treffen:

  1. Takt anlegen per Prescaler
  2. PWM-Modus wählen
  3. Ausgangs-Aktion festlegen
  4. Pins als Ausgänge schalten

Diese Schritte müssen bei jedem AVR-PWM-Kanal ausgeführt werden. Die genauen Registernamen und Werte können sich jedoch je nach Timer-Ausführung etwas unterscheiden.

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.

Bit: 7 6 5 4 3 2 1 0
Name: OCIE2 TOIE2 TICIE1 OCIE1A OCIE1B TOIE1 OCIE0 TOIE0
  • OCIE2 (Timer/Counter2 Output Compare Match Interrupt Enable)
    Wenn dieses Bit gesetzt wird, wird der Timer/Counter2 Compare Match Interrupt aktiviert (vorrausgesetzt die Interrupts sind global aktiviert).
  • TOIE2 (Timer/Counter2 Overflow Interrupt Enable)
    Wenn dieses Bit gesetzt wird, wird der Timer/Counter2 Overflow Interrupt aktiviert (vorrausgesetzt die Interrupts sind global aktiviert).
  • TICIE1 (Timer/Counter1, Input Capture Interrupt Enable)
    Wenn dieses Bit gesetzt wird, wird der Timer/Counter1 Input Capture Interrupt aktiviert (vorrausgesetzt die Interrupts sind global aktiviert).
  • OCIE1A (Timer/Counter1 Output Compare A Match Interrupt Enable)
    Wenn dieses Bit gesetzt wird, wird der Timer/Counter1 Output Compare A Match Interrupt aktiviert (vorrausgesetzt die Interrupts sind global aktiviert).
  • OCIE1B (Timer/Counter1 Output Compare B Match Interrupt Enable)
    Wenn dieses Bit gesetzt wird, wird der Timer/Counter1 Output Compare B Match Interrupt aktiviert (vorrausgesetzt die Interrupts sind global aktiviert).
  • TOIE1 (Timer/Counter1 Overflow Interrupt Enable)
    Wenn dieses Bit gesetzt wird, wird der Timer/Counter1 Overflow Interrupt aktiviert (vorrausgesetzt die Interrupts sind global aktiviert).
  • OCIE0 (Timer/Counter0 Output Compare Match Interrupt Enable)
    Wenn dieses Bit gesetzt wird, so wird der Timer/Counter0 Compare Match Interrupt aktiviert (vorrausgesetzt die Interrupts sind global aktiviert).
  • TOIE0 (Timer/Counter0 Overflow Interrupt Enable)
    Wenn dieses Bit gesetzt wird, so wird der Timer/Counter0 Overflow Interrupt aktiviert (vorrausgesetzt die Interrupts sind global aktiviert).


Siehe auch

Weblinks


LiFePO4 Speicher Test