Aus RN-Wissen.de
Wechseln zu: Navigation, Suche
Rasenmaehroboter Test

K (Input Capture)
(Normaler Modus (Normal Mode))
 
(28 dazwischenliegende Versionen von 6 Benutzern werden nicht angezeigt)
Zeile 2: Zeile 2:
 
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 [[Timer/Counter (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. Über den Prescaler kann auch der Timer angehalten werden. 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 der Timer weiterzählt.
+
Das obere Diagramm zeigt den Betrieb ohne Prescaler, das untere mit Prescaler (:2). Die gestrichelte Linie zeigt, wann der Timer weiterzählt.
  
 
Im Teil [[Timer/Counter (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.
Zeile 27: Zeile 28:
  
 
=== Normaler Modus (Normal Mode) ===
 
=== Normaler Modus (Normal Mode) ===
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, 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 [[Timer/Counter (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.
  
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. Besonders bei kleinen Prescalern kann es dadurch zu Fehlern in der Zeit kommen. Wenn möglich wird für die Erzeugung einer konstanten Interruptrate deshalb besser der CTC Moduls benutzt.
+
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 Beispielprogramm:
+
Zusammenfassend ein Code-Beispiel (kein vollständiges Programm):
  
 
<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 87: 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>
  
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.  
+
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 ====
 
==== Input Capture ====
  
Die 16 Bit Timer habe eine "Input Capture" Funktion. Dieser Hardwareteil dient zur genauen Zeitmessung. 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 die 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.
+
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.
  
Die typische Anwendung ist die Messung von kurze Zeiten, wie z.B. die Zeit für eine Motorumdrehung. 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) berechent. Dazu wird jeweils die vorherige Zeit im RAM zwischengespeichert. Wenn man bei der Rechnung 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.
+
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 man den Timer-überlauf benutzen, 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 Interrrupt fast gleichzeitig ausgelöst werden. Es kann passieren das der ICP Interrupt aufgerufen wird, obwohl eigentlich erst der Overflow Interrupt dran gewesen wäre. Dieser seltene Fall läßt sich daran erkennen das der Overflow-Interrupt Flag gesetzt ist und der Wert im ICP Register klein ist (high Byte < 128, meisten 0).
+
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):
 
Beispielpropgramm (für GCC):
Zeile 111: Zeile 112:
 
// mit leichten Anpassungen auch für Tiny2313, Mega16, Mega32,...
 
// mit leichten Anpassungen auch für Tiny2313, Mega16, Mega32,...
  
#include <stdlib.h>         // für utoa
+
#include <stdlib.h>         // für utoa
 
#include <avr/io.h>
 
#include <avr/io.h>
 
#include <avr/interrupt.h>
 
#include <avr/interrupt.h>
#include <avr/sleep.h>     // unterstützung für sleep mode
+
#include <avr/sleep.h>       // Unterstützung für sleep mode
  
// #define F_CPU  1000000UL  
+
#define F_CPU  1000000UL     // Definition der Frequenz, ist ggf. im makefile
// Definition von Frequenz ist normal in makefile
+
 
#define BAUD        19200UL
 
#define BAUD        19200UL
 
#define UBRR_BAUD  ((F_CPU/(16UL*BAUD))-1)
 
#define UBRR_BAUD  ((F_CPU/(16UL*BAUD))-1)
  
typedef union {  // union erlaubt einen effectiven, seperaten Zugriff auf Teile der Variable
+
typedef union {  // union erlaubt einen effektiven, separaten Zugriff auf Teile der Variable
 
         unsigned long i32;
 
         unsigned long i32;
         struct {uint8_t i8l; // low
+
         struct {uint8_t i8l;       // low
                 uint8_t i8m; // mid
+
                 uint8_t i8m;       // mid
unsigned int high; // high, soft timer                 
+
unsigned int high; // high, soft timer                 
 
               };
 
               };
 
               } convert32to8;
 
               } convert32to8;
  
volatile convert32to8 timestamp;    // volatile wegen Zugriff im Interrrupt
+
volatile unsigned long timestamp;    // volatile wegen Zugriff im Interrupt
 
volatile unsigned int softtimer;
 
volatile unsigned int softtimer;
 
volatile unsigned long zeitdifferenz;
 
volatile unsigned long zeitdifferenz;
 
unsigned long zeit;
 
unsigned long zeit;
char puffer[12];       // puffer für Ausgabe als Ascii
+
char puffer[12];           // Puffer für Ausgabe als Ascii
  
ISR(TIMER1_OVF_vect)   // Timer Überlauf
+
ISR(TIMER1_OVF_vect)     // Timer1 Überlauf
 
{  
 
{  
   ++softtimer; // zählen der Überläufe
+
   ++softtimer;   // zählen der Überläufe
 
}  
 
}  
  
ISR(TIMER1_CAPT_vect)   //  Flanke an ICP pin
+
ISR(TIMER1_CAPT_vect)     //  Flanke an ICP pin
 
{  
 
{  
   convert32to8 cap;     // Variablendeklaration
+
   convert32to8 cap;       // Variablendeklaration
 
    
 
    
   cap.i8l = ICR1L;     // low-byte zuerst, high Byte wird gepuffert
+
   cap.i8l = ICR1L;         // low Byte zuerst, high Byte wird gepuffert
 
   cap.i8m = ICR1H;   
 
   cap.i8m = ICR1H;   
 
   // overflow verpasst, wenn ICR1H klein und wartender Overflow Interrupt
 
   // overflow verpasst, wenn ICR1H klein und wartender Overflow Interrupt
 
   if ((cap.i8m < 128) && (TIFR1 & (1<<TOV1)))
 
   if ((cap.i8m < 128) && (TIFR1 & (1<<TOV1)))
   {// wartenden timer overflow Interrupt vorziehen
+
   {   // wartenden timer overflow Interrupt vorziehen
++softtimer;         
+
    ++softtimer;         
     TIFR1 = (1<<TOV1); // timer overflow int. löschen, da schon hier ausgeführt
+
     TIFR1 = (1<<TOV1);   // timer overflow int. löschen, da schon hier ausgeführt
 
   }
 
   }
   cap.high = softtimer; // obere 16 Bit aus Software Zähler
+
   cap.high = softtimer;   // obere 16 Bit aus Software Zähler
   zeitdifferenz = cap.i32 - timestamp.i32;
+
   zeitdifferenz = cap.i32 - timestamp;
   timestamp.i32 = cap.i32; // Zeit merken
+
   timestamp = cap.i32;     // Zeit merken
 
}
 
}
  
void uart_init(void)   // USART initialisieren (Mega48 etc.)
+
void uart_init(void)       // USART initialisieren (Mega48 etc.)
 
{
 
{
 
// Baudrate einstellen (Normaler Modus)
 
// Baudrate einstellen (Normaler Modus)
// kann bei älteren AVR Typen etwas anders sein (kein UBRR0H, dafür prescaler)!
+
// kann bei älteren AVR Typen etwas anders sein (kein UBRR0H, dafür prescaler)
   UBRR0H = (uint8_t) (UBRR_BAUD>>8);
+
   UBRR0H = (uint8_t) (UBRR_BAUD>>8);             // bei Mega32  anders !
   UBRR0L = (uint8_t) (UBRR_BAUD & 0x0ff);
+
   UBRR0L = (uint8_t) (UBRR_BAUD & 0x0ff);       // bei Mega32 UBRRL
   UCSR0B = (1<<TXEN0); // Aktivieren von transmitter:
+
   UCSR0B = (1<<TXEN0);   // Aktivieren des Senders, bei Mega32 UCSRB
   UCSR0C = (1<<UCSZ01)|(1<<UCSZ00)|(1<<USBS0);  
+
   UCSR0C = (1<<UCSZ01)|(1<<UCSZ00)|(1<<USBS0);   // bei Mega32 UCSRC
// Einstellen des Datenformats: 8 Datenbits, 2 Stoppbit:
+
// Einstellen des Datenformats: 8 Datenbits, 2 Stopbit:
 
}
 
}
  
Zeile 178: Zeile 178:
 
  unsigned char i;
 
  unsigned char i;
 
// Datenrichtungen:
 
// Datenrichtungen:
  DDRB = 0; // inputs , PB0 ist ICP
+
  DDRB = 0;   // Alles Eingänge, PB0 ist ICP
  PORTB = 0xFF - (1<<PB0);// pullups an Eingängen außer ICP
+
  PORTB = 0xFF - (1<<PB0);         // Pullups an Eingängen außer ICP
  DDRC = 0;     // inputs
+
  DDRC = 0;       // Eingänge
  PORTC = 0xFF; // pullups an Eingängen  
+
  PORTC = 0xFF;   // Pullups an Eingängen  
  DDRD = (1<<PD1);       // inputs  außer PD1 = Tx (UART)
+
  DDRD = (1<<PD1);               // Eingänge, außer PD1 = Tx (UART)
  PORTC = 0xFF- (1<<PD1); // pullups an alle Eingängen (außer TX)
+
  PORTD = 0xFF- (1<<PD1);         // Pullups an alle Eingängen (außer TX)
 
// Timer1 initialisieren:
 
// Timer1 initialisieren:
  TCCR1A = 0;   // normal mode, keine PWM Ausgänge
+
  TCCR1A = 0;                     // normal mode, keine PWM Ausgänge
  TCCR1B = (1<< ICNC1) + (1<<CS10)// start timer mit systemtakt
+
  TCCR1B = (1<< ICNC1) + (1<<CS10)   // start Timer mit Systemtakt
  TIMSK1 = (1<<TOIE1) + (1<<ICIE1);  // Input-capture und
+
          + (1 << ICES1);            // steigende Flanke auswählen
  TIFR1 = (1<<TOIE1) + (1<<ICIE1);    // Schon aktive interrrupts löschen
+
  TIMSK1 = (1<<TOIE1) + (1<<ICIE1);  // overflow und Input-capture aktivieren, Mega32: TIMSK
// UART initialisieren
+
  TIFR1 = (1<<TOIE1) + (1<<ICIE1);    // Schon aktive Interrupts löschen, Mega32: TIFR
  uart_init();
+
// UART initialisieren:
 +
  uart_init();              
  
 
  zeitdifferenz = 0;
 
  zeitdifferenz = 0;
  softtimer = 0;   // wird für Zeitdifferenzmessung nicht mal gebraucht,
+
  softtimer = 0;         // wird für Zeitdifferenzmessung nicht mal gebraucht,
                  // denn Differenz geht auch über Überlauf bei Softtimer
+
                        // denn Differenz geht auch über Überlauf bei Softtimer
 
  set_sleep_mode (SLEEP_MODE_IDLE);  // idle Mode: timer läuft weiter, int zum aufwachen
 
  set_sleep_mode (SLEEP_MODE_IDLE);  // idle Mode: timer läuft weiter, int zum aufwachen
  
  sei();     // Interrupts erlauben: Messung startet
+
  sei();                 // Interrupts erlauben: Messung startet
 
  while (1)
 
  while (1)
 
  {
 
  {
   sleep_enable ();      // enable sleep
+
   sleep_enable ();      // Sleep Befehl freigeben
 
   sei();
 
   sei();
 
   sleep_cpu();          // wartet auf irgendeinen Interrupt, z.B. ICP, timer_ovr,...
 
   sleep_cpu();          // wartet auf irgendeinen Interrupt, z.B. ICP, timer_ovr,...
   sleep_disable();      // disable sleep
+
   sleep_disable();      // Sleep Befehl sperren
   cli(); // Interrupt sperren wegen Zugriff auf volatile Variablen
+
   cli(); // Interrupt sperren wegen Zugriff auf volatile Variable
 
   zeit = zeitdifferenz;
 
   zeit = zeitdifferenz;
 
   zeitdifferenz = 0;  // als Markierung für ungültigen Wert
 
   zeitdifferenz = 0;  // als Markierung für ungültigen Wert
Zeile 210: Zeile 211:
 
   if (zeit > 0)
 
   if (zeit > 0)
 
   {  
 
   {  
     utoa(zeit,puffer,10); // nach ASCII umwandeln
+
     ultoa(zeit,puffer,10); // nach ASCII umwandeln
 
     i = 0;
 
     i = 0;
 
     while (puffer[i])
 
     while (puffer[i])
Zeile 216: Zeile 217:
 
       putser(puffer[i++]);  // Ausgabe
 
       putser(puffer[i++]);  // Ausgabe
 
     }
 
     }
     putser(13);           // Zeilenumbruch senden
+
     putser(13); putser(10); // Zeilenumbruch senden
    putser(10);
+
 
   }
 
   }
 
  }  // Ende von While-schleife
 
  }  // Ende von While-schleife
Zeile 224: Zeile 224:
  
 
=== CTC Modus (Clear Timer on Compare Match mode) ===
 
=== CTC Modus (Clear Timer on Compare Match mode) ===
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. Man kann also die maximalen Zählergrenze selber definieren.
+
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.
 
Dieses Diagramm veranschaulicht den CTC Modus.
  
Zeile 232: Zeile 232:
  
 
<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 von 100000 Hz (oder 100 kHz). Der Timer wird auf einen Prescaler von 1 und einem OCR2-Wert von 73 konfiguriert.*/
+
  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
 
volatile uint8_t countTimer2; // Speichert den aktuellen Zählerwert
Zeile 244: 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 273: Zeile 275:
 
=== PWM ===
 
=== 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. Man muss nur die PWM aktivieren 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.  
+
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 ====
 
==== nutzbare Pins am AVR ====
Die Hardware-PWM-Funktion steht nur an bestimmten Pins zur Verfügung. In der Pinbelegungsübersicht im Datenblatt erkennt man sie daran, dass als Sonderfunktion in Klammern "OC..." angegeben ist. Beim Mega32 sind dies zb. OC0 an PB0, 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.
+
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!'''
 
'''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!'''
Zeile 292: Zeile 294:
 
<pre>
 
<pre>
 
#include <avr/io.h>
 
#include <avr/io.h>
#include <util/delay.h> //Warteschleife für die Demo-Ausgabe. Für die eigentliche PWM nicht benötigt!   
+
#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)
 
/*  PWM-Beispiel für Mega16/32 (beiden haben den gleichen Timer)
Zeile 303: Zeile 305:
 
 
 
// 2. Den Timer in den Fast PWM Mode, 8 Bit schalten
 
// 2. Den Timer in den Fast PWM Mode, 8 Bit schalten
//ACHTUNG: Die WGM-Bits sind auf beide Konfigurationsregister verteiltt!
+
//   ACHTUNG: Die WGM-Bits sind auf beide Konfigurationsregister verteilt!
 
TCCR1A |= (1<<WGM10);
 
TCCR1A |= (1<<WGM10);
 
TCCR1B |= (1<<WGM12);
 
TCCR1B |= (1<<WGM12);
 
 
// 3. Compare Output mode einstellen: Pin geht auf high bei Compare match, auf low bei Überlauf. Ergibt nichtinvertierte PWM.
+
// 3. Compare Output mode einstellen: Pin geht auf high bei Compare match, auf low bei Überlauf.  
 +
//    Ergibt nichtinvertierte PWM.
 
TCCR1A |= (1<<COM1A1) | (1<<COM1B1) ;
 
TCCR1A |= (1<<COM1A1) | (1<<COM1B1) ;
 
 
Zeile 314: Zeile 317:
 
OCR1B = 0;
 
OCR1B = 0;
  
// 4. Zuletzt die Pins als Ausgänge konfigurieren. Erst jetzt liegt das PWM-Signal an den Pins an!
+
// 4. Die Pins als Ausgänge konfigurieren. Erst jetzt liegt das PWM-Signal an den Pins an!
 
DDRD |= (1<<PD4) | (1<< PD5);
 
DDRD |= (1<<PD4) | (1<< PD5);
  
/*Nun ist der PWM-Modus aktiv! Der Ausgangswert kann nun über die Register OCR1A und OCR1B vorgegeben werden.  
+
/*Nun ist der PWM-Modus aktiv! Der Ausgangswert kann nun über die Register OCR1A und OCR1B
Man könnte ihnen per define noch einen Zweitnamen verpassen, zb */
+
  vorgegeben werden. Man könnte ihnen per define noch einen Zweitnamen verpassen, zb */
 
#define MotorLinks OCR1A
 
#define MotorLinks OCR1A
 
#define MotorRechts OCR1B
 
#define MotorRechts OCR1B
Zeile 325: Zeile 328:
 
MotorRechts = 127;
 
MotorRechts = 127;
 
//seinen Roboter mit halber Kraft vorwärts fahren lassen.
 
//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.  
 
/*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. */
+
Ein angeschlossener Motor wird beschleunigen und dann wieder abbremsen. */
 
uint8_t wert;
 
uint8_t wert;
 
while(1)
 
while(1)
Zeile 383: 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)]
  

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