Der ATmega8 verfügt insgesamt über 3 Timer. Zwei arbeiten mit 8 Bit und einer mit 16 Bit.
8-Bit Timer/Counter0
16-Bit Timer/Counter1
8-Bit Timer/Counter2
Die Funktionsweise der diversen Modi der einzelnen Timer
würde hier den Rahmen sprengen, weshalb ich hier nur ein paar allgemeine
Informationen geben möchte.
Standardmäßig werden alle Timer über den internen Takt gesteuert. Dieser kann je
nach Anforderung über so genannte Vorteiler herunter skaliert werden. Es sind je
nach Timer zum Beispiel Vorteiler von 8, 64, 256 und 1024 verfügbar. So würde
zum Beispiel bei einer Taktfrequenz des MC mit 8 MHz und einem gewählten
Prescaler von 256 der Timer mit 8MHz/256 = 31,25kHz laufen.
Der Start
der Timer erfolgt jeweils durch die Auswahl des Vorteilers und das Schreiben der
CSxy-Bits. Hierbei steht x für den gewählten Timer (also 0, 1 oder 2) und y für
das anzusprechende Clock-Select-Bit. Alternativ kann man je nach Timer auch
einen extern zugeführten Takt verwenden. Nachfolgend ist die Tabelle für Timer0
angegeben.
CS02 | CS01 | CS00 | Beschreibung |
0 | 0 | 0 | Timer aus |
0 | 0 | 1 | Taktfrequenz des MC |
0 | 1 | 0 | Clk/8 |
0 | 1 | 1 | Clk/64 |
1 | 0 | 0 | Clk/256 |
1 | 0 | 1 | Clk/1024 |
1 | 1 | 0 | ext. Takt an T0 -> fallende Flanke |
1 | 1 | 1 | ext. Takt an T0 -> steigende Flanke |
Zu finden sind diese Bits in den
Timer/Counter-Control-Registern (TCCR0, TCCR1B, TCCR2). Sind nun die
entsprechenden Bits gesetzt, so beginnt der Timer/Counter zu zählen. Timer0 und
Timer2 zählen hierbei bis 2^8=256, Timer1 bis 2^16=65536.
Der aktuelle
Zählerstand steht in den Zählregistern (TCNT0, TCNT1, TCNT2). Ist die Obergrenze
erreicht, läuft der der Timer über und beginnt wieder bei Null.
Aktiviert man
einen Timer-Overflow-Interrupt, so wird beim Überlauf in die
Interrupt-Service-Routine gesprungen (TOIE0:2). Zusätzlich müssen natürlich Interrupts auch global erlaubt sein (sei ()).
Auch verfügen manche Timer
über Compare-Register. Bei deren Verwendung wird der aktuelle Wert in TCNTx
ständig mit diesem Register verglichen. Sind beide gleich, kann ein
Compare-Match-Interrupt ausgelöst werden. Ob nach dem Compare Match der Timer
zurückgesetzt wird oder er einfach weiterzählt kann durch das Setzen
entsprechender Bits festgelegt werden. Auch kann zusätzlich je nach Timer noch
eine Obergrenze vorgegeben werden. So kann man die gewünschten
Interruptintervalle innerhalb bestimmter Grenzen fein staffeln.
Da in Internetforen sehr oft die Frage kommt "Wie kann ich eine Zeitbasis von 1, 2, 3 usw. Sekunden erzeugen?", möchte ich hier einmal ein paar Möglichkeiten mit meiner Meinung nach ausreichenden Erklärungen präsentieren.
Die Erzeugung von einer Sekunde:
Die Basis jeder Berechnung der Timerparameter bildet immer der vom Anwender eingesetzte Quarz. Ich nehme jetzt einmal exemplarisch 8MHz an. Nun muss ein geeigneter Vorteiler gewählt werden. Dieser ist mit steigender gewünschter Zeitdauer immer größer anzusetzen. Oft ist es möglich, verschiedene Prescaler für ein und die selbe Zielzeit einzusetzen. Hier soll einmal ein Prescaler von 1024 angesetzt werden, da die Zielzeit von einer Sekunde aus Sicht des MC doch schon ziemlich groß ist. Es ergibt sich damit eine Timerfrequenz von 8MHz/1024 = 7812,5Hz. Somit dauert nun ein Zählschritt des Timers 1/7812,5Hz = 0,000128s = 128µs. Für eine Sekunde müssten also 1s/128µs = 7812 Zählschritte ausgeführt werden. Will man nun ohne zusätzlichen Aufwand auf eine Zeitspanne von einer Sekunde kommen, so muss der 16 Bit Timer verwendet werden, da die 8 Bit Timer nur 256 Zählschritte bis zu einem Überlauf machen können. Bei Verwendung des 16 Bit Timers muss nun dessen Zählregister nach jedem Überlauf mit 65536-7812 = 57724 vorgeladen werden. Die Software dafür könnte z.B. so aussehen (Timer1):
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/signal.h>
void timer_init (void) {
TCCR1B = (1<<CS12) | (1<<CS10);
TIMSK |= (1<<TOIE1);
TCNT1 = 57724;
sei ();
}
SIGNAL (SIG_OVERFLOW1) {
//do something
useful :)
TCNT1 = 57724;
}
Für einen Prescaler von 256 müsste die Initialisierung des Timers so aussehen:
TCCR1B |= (1<<CS12);
TIMSK |= (1<<TOIE1);
TCNT1 = 34286;
Zuletzt möchte ich das ganze noch für einen 8 Bit Timer
demonstrieren. Es wird eine Quarzfrequenz von 4 MHz angenommen. Es soll eine
Interruptfrequenz von 50Hz erzeugt werden, also alle 20ms ein
Timer-Overflow-Interrupt erzeugt werden. Es muss gerechnet werden: 4MHz/1024 =
3906,25. Die Zählschrittdauer ist 256µs. Die Anzahl der Zählschritte ergibt sich
zu 20ms/256µs = 78. Der Zähler müsste also ständig mit 256-78 = 178 vorgeladen
werden. Die Software könnte so aussehen (Timer2):
TCCR2 = (1<<CS22) | (1<<CS21) | (1<<CS20);
TIMSK |= (1<<TOIE2);
TCNT2 = 178;
Will man hieraus nun auch noch eine Sekunde erzeugen, so geht das nur über ein
Hilfsvariable. Da die ISR 50 mal in der Sekunde aufgerufen wird, muss sich
mittels einer Hilfsvariable gemerkt werden, wie viele Interrupts bereits
aufgetreten sind. Dies könnte beispielsweise so erfolgen:
volatile unsigned char hilfsvariable = 0;
SIGNAL (SIG_OVERFLOW2) {
hilfsvariable++;
if (hilfsvariable == 50) {
//do something useful
hilfsvariable = 0;
}
}
Es soll jedoch nicht verschwiegen werden, dass noch
weitere Möglichkeiten bestehen. So kann man z.B. je nach Timer auch noch die
Compare-Match-Funktionalität benutzen. Dies bedeutet, dass ständig der Inhalt
des Zählregisters eines Timers mit einem Compare-Register verglichen wird.
Dessen Wert wird durch die Software des Anwender festgelegt. Sind beide Register
gleich, wird ein Interrupt ausgelöst.
Hier alle Timer-Modi zu erläutern ist allerdings für mich ein hoffnungsloses
Unterfangen. Wer weitere Modi in Anwendung sehen möchte, dem empfehle ich einen
Blick in die Rubrik Projekte. Dort wurden Timer zu verschiedensten
Zwecken eingesetzt, beispielsweise als Trägerfrequenz für die
Infrarotübertragung, als Ultraschallfrequenz, ...
Hier alle Modi der Timer und deren Anwendung zu erläutern würde den Rahmen sprengen. Spezielle Anwendungsfälle sind bei den Beispielprogrammen bzw. unter der Rubrik PWM erläutert.