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.

 

Zurück zur Startseite.