Die Timer des ATMega128 – Timer Overflow

Timer sind für viele Anwendungen unabdingbar, werfen aber immer wieder Fragen auf – auch bei mir. Aus diesem Grund habe ich bereits etliche Stunden vor dem Rechner verbracht und unzählige Websites besucht um meine Wissenslücken zu schließen. Ich möchte diesen Artikel dazu nutzen, dass andere „Unwissende“ weniger suchen müssen und werde an dieser Stelle die verschiedenen Funktionen der Timer beleuchten. Ganz nebenbei gehe ich auch auf die Besonderheiten ein, die es bei den Timer des ATMega128 zu beachten gilt

Die verwendeten Timer der vorangegangenen Artikel haben einen Zähler überlaufen lassen und dieses Ereignis per Interrupt erfasst. In diesem Artikel erkläre ich die „Timer Overflow“-Methoden. Die CTC und PWM-Funktionen folgen in späteren Artikeln.

Timer Overflow

Diese Version habe ich u.a. bereits im Artikel „GREETBoard ATMega128 – Interrupt gesteuerte LED mit 8-Bit-Timer“ verwendet. In diesem Modus wird der Timer gestartet und bis zum Überlauf (8-Bit bis 256, 16-Bit bis 65535) hochgezählt. Der Überlauf löst den Overflow-Interrupt aus und lässt den Mikrocontroller zur entsprechenden ISR (Interrupt Service Routine) springen.

Die Interrupt-Zeit

Läuft der Timer einfach von 0 bis 255, um danach einen Interrupt auszulösen, dann berechnet sich die Zeit wie folgt:

Timer Overflow - Formel Timerfrequenz

Timer Overflow - Rechenbeispiel Timerfrequenz

Timer Overflow - Formel Timerzeit

Timer Overflow - Rechenbeispiel Timerzeit

In dieser Formel gibt es also drei Stellschrauben. Erstens wäre es möglich einen anderen Takt zu nutzen, indem der Quarz getauscht wird. Außerdem kann auch ein 16-Bit Timer mit einer Schrittanzahl von 65535 verwendet werden. Sehr häufig ist jedoch die richtige Wahl des sog. Vorteilers völlig ausreichend. Das GREETBoard ATMega128 nutzt einen 40-poligen ATMega1284P und dessen Datenblatt zeigt auf Seite 107, dass die folgenden Vorteiler verfügbar sind: 1, 8, 64, 256, 1024. Für das obige Beispiel würden sich also folgende Aufstellung ergeben, wenn lediglich die Vorteiler verändert werden:

Vorteiler     Frequenz     Interrupt-Zeit
        1     62500 Hz      0,016 ms
        8      7813 Hz      0,128 ms
       64       977 Hz      1,024 ms
      256       244 Hz      4,096 ms
     1024        61 Hz     16,384 ms

Anhand des folgenden Diagramms ist es vielleicht ein wenig besser vorstellbar, wie der Timer funktioniert. Nachdem der Maximalwert des Timers erreicht wurde, wird er zurückgesetzt und die Interrupt-Routine ausgelöst. Die Millisekunden habe ich einfach mal auf eine Stelle hinter dem Komma gerundet 😉

Diagramm - Timer Overflow

Diagramm – Timer Zeit im Overflow-Modus

Die Register

Um den Timer zu starten muss lediglich der Vorteiler gesetzt werden. Dies geschieht im Register „Timer/Counter Control Register B“ (TCCR0B). Und genau hier liegt eine große Fehlerquelle! Im Unterschied zum ATMega32 besitzt der 1284P auch bei den 8-Bit Timer zwei TCCRn Register.

Das Datenblatt verrät, dass die Bits CS00 und CS02 gesetzt werden müssen um einen Vorteiler von 1024 zu erhalten.

Das TCCR0B Register (Vorteiler = 1024)

Das TCCR0B Register (Vorteiler = 1024)

Damit der Überlauf auch ein Interrupt auslöst, muss Bit 0 (TOIE0) im Timer/Counter Interrupt Mask Register (TIMSK0) aktiviert werden. Übrigens, hier müssen keine zwei Kanäle beachtet werden.

Das TIMSK0 Register (Overflow Interrupt eingeschaltet)

Das TIMSK0 Register (Overflow Interrupt eingeschaltet)

Hier das Code-Beispiel mit einem Prescaler von 256. Da dieser eine Frequenz von 244Hz erzeugt, muss das Overflow-Interrupt 245 mal die Variable tcount erhöhen, damit die LED ein Mal pro Sekunde ein-, bzw. ausgeschaltet wird.

#include <avr/io.>;           // Header-Datei f. IO-Register
#include <avr/interrupt.h>;   // Header-Datei f. Interruptfunktion
#include <stdint.h>;          // Header-Datei f. standard Datentypen

volatile uint8_t tcount;      // Interrupt-Zähler

ISR (TIMER0_OVF_vect) {
  tcount++;                   // tcount +1 pro Timer Interrupt
}

int main(void) {
  DDRC = (1 << DDC3);         // PortC3 als Ausgang für LED1
  TCCR0B = (1<<CS02);         // Prescaler 256
  TIMSK0 |= (1<<TOIE0);       // Overflow Interrupt erlauben
  sei();                      // Global Interrupts aktivieren

  while(1) {
    if ( tcount > 244 ) {     // 1Sek. = 244 Interrupts
      PORTC ^= (1 << PC3);    // LED umschalten
      tcount = 0;             // Zähler wieder auf 0 setzen
    }
  }
}

Timer Overflow mit Reload

Die reine Überlauf-Funktion stellt leider nur eine recht eingeschränkte Auswahl an verschiedenen Frequenzen zur Verfügung. Den Takt des Mikrocontrollers ist normalerweise fix und kann nicht ständig angepasst werden. Dann gibt es die Wahl zwischen 8- und 16-Bit Timer und die verschiedenen Vorteiler. Aber ist es damit möglich einen Timer mit genau 500Hz (Timerzeit = 2ms) zu erstellen? Ich erkläre wie es geht! Es ist gar nicht schwer 🙂

Mit der Timer-Frequenz-Formel:

Timer Overflow - Formel Timerfrequenz

habe ich eine Tabelle für einen 8-Bit und einen 16-Bit Timer (bei 16MHz) erstellt.

              8-Bit----------------------    16-Bit---------------------
Vorteiler     Frequenz     Interrupt-Zeit    Frequenz     Interrupt-Zeit
        1     62500 Hz      0,016 ms         244,00 Hz       4,096ms
        8      7813 Hz      0,128 ms          30,50 Hz      32,768ms
       64       977 Hz      1,024 ms           3,80 Hz     262,144ms
      256       244 Hz      4,096 ms           0,95 Hz    1048,576ms
     1024        61 Hz     16,384 ms           0,24 Hz    4194,304ms

Leider kann eine Frequenz von 500Hz mit keinem der beiden Timer erreicht werden. Am nächsten kommt hier noch der 8-Bit Timer. Die 500Hz müssen bei ihm irgendwo zwischen dem Vorteiler von 64 und 256 liegen.

Angenommen der Vorteiler 256 wird benutzt. Wie schön wäre es, wenn der Timer dann anstatt von 0 bis 4,096 ms, von 2,096ms bis 4,096ms laufen würde. Damit blieben genau die benötigten 2ms Timerzeit übrig. Und hier kommt die Reload-Funktion zum Tragen. Es ist nämlich ohne weiteres möglich, des Timer/Counter Register „vorzuladen“ (TCNT0). Das folgende Diagramm zeigt die generelle Funktionsweise. Die roten Pfeile zeigen den Reload bei jedem neuen Timerzyklus.

Timer Diagramm - Timer Overflow mit Reload

Timer Diagramm – Timer Overflow mit Reload

Die nächste Frage ist nun mit welchem Wert das TCNT0-Register vorgeladen werden muss. Hier hilft mir ein kleines Programm, welches diese Werte für mich berechnet. Der „AVR-Timer-Calculator“ möchte zuerst den Takt des Mikrocontrollers (16000000) und die Timerzeit in µs (2000) wissen. Außerdem ist die Angabe ob ein 8- oder 16-Bit Timer verwendet wird notwendig (8-Bit). Lediglich bei der Wahl des Interrupts bleibt es bei „Overflow (Reload)“.

Timer - AVR-Timer-Calculator

Timer – AVR-Timer-Calculator

Die Tabelle im unteren Bereich zeigt zwei Zeilen in blauer Schrift. Diese Einstellungen führen zu einer exakten Timerzeit von 2ms. Die grau dargestellten Eintragungen führen zu Abweichungen, z.B. würde ein Vorteiler von 1024 und einem Reloadwert von 225 zu einer Abweichung von -16µs pro Timer-Durchgang führen. Der ATMega1284P verfügt über den Vorteiler 256, sodass der Reloadwert von 131 verwendet wird.

Das Register

Der Timer wird genauso gestartet, wie im Beispiel weiter oben. Für das Vorladen ist lediglich ein weiteres Register notwendig, das Timer/Counter Register – TCNT0. In ihm wird von 0 bis 255 (bei 8-Bit Timer) „hochgezählt“. Bei der Timer-Initialisierung und in der Overflow-ISR wird TCNT0 = 131 gesetzt – das war es schon. Der Timer läuft nun von 131 bis 255 was einer Timerzeit von exakt 2ms entspricht.

Timer - TCNT0 Register - TimerCounter Register

Timer – TCNT0 Register – TimerCounter Register

Das Codebeispiel wurde lediglich um zwei Zeilen (9 und 15) ergänzt um den Reload-Wert zu setzen.

#include <avr/io.h>          // Header-Datei f. IO-Register
#include <avr/interrupt.h>   // Header-Datei f. Interruptfunktion
#include <stdint.h>          // Header-Datei f. standard Datentypen

volatile uint8_t tcount;     // Globale Variable (volatile)

ISR (TIMER0_OVF_vect)
{
  TCNT0 = 131;               // Reloadwert erneuern
  tcount++;                  // tcount +1 pro Timer Interrupt (ca.10ms)
}

int main(void) {
  DDRC = (1 << DDC3);         // PortD7 als Ausgang für LED1
  TCNT0 = 131;                // Reloadwert für 500Hz
  TCCR0B = (1<<CS02);         // Prescaler 256
  TIMSK0 |= (1<<TOIE0);       // Overflow Interrupt erlauben
  sei();                      // Global Interrupts aktivieren

  while(1) {
    if ( tcount > 250 ) {     // Alle 0,5s ein/aus
      PORTC ^= (1 << PC3);    // LED umschalten
      tcount = 0;             // Zähler wieder auf 0 setzen
    }
  }
}

Die Overflow-Methode ist eine schöne und einfache Möglichkeit einen Timer zu nutzen. Und da nun bekannt ist, wie das Timer/Counter Register geladen werden kann, ist der Schritt zum Auslesen des Register nicht sehr groß. Genau hier setzt die CTC-Methode, also „Clear Timer on Compare“, an. Dies wird daher auch das nächste Timer-Thema sein.

Hoffentlich habe ich die Anwendung der Overflow-Methode anschaulich und – vor allem – verständlich dargestellt. Sollten Fragen offen geblieben sein, dann würde ich mich über Emails oder Kommentare freuen.

Bis zum nächsten Artikel,
Euer Timo

Ein Gedanke zu „Die Timer des ATMega128 – Timer Overflow

  1. Hi Timo

    Super verständliche und hilfreiche Beschreibung. Davon müsste es im Netz viel mehr geben. Respekt!!

    Vielen Dank dafür. 🙂

    Gruß Boris

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.