Die Timer des ATMega128 – CTC Modus

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 diese Artikelserie 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

Im letzten Artikel habe ich über die Overflow-Methode geschrieben und auch den Reload des Timers näher beleuchtet. Dieses Mal geht es darum den sogenannten Clear-Timer-on-Compare Modus zu verwenden, bei dem der Timer selbstständig den Zählerwert mit einem Vergleichwert vergleicht.

Die Funktionsweise

Im letzten Timer-Artikel wurde der Timer vorgeladen, damit der Overflow Interrupt schneller ausgelöst wurde. Also in etwa so, als wenn Wasser in ein Glas fließt bis es überläuft. Ist dieses zu Beginn halb voll, geschieht das Überlaufen schneller, als wenn es leer gewesen wäre. Es ist aber auch möglich den Moment zu erkennen, wenn der Zähler des Timers einen bestimmten Wert erreicht hat.

Timer - Vergleich des Zählwerts

Timer – Vergleich des Zählwerts

In der Grafik startet der Timer (hier ein 16-Bit Timer) und zählt die Schritte hoch. An dem Punkt, an dem der bestimmte Wert erreicht wird (rote Linie), erfolgt ein Rücksetzen des Timers und eine Aktion kann erfolgen. Die gestrichelte Linie zeigt den Timerverlauf ohne Aktion bei erreichen des Vergleichswert.

Der Vergleichswert

Angenommen es wird eine Timerfrequenz von 200Hz benötigt, dann beträgt die Timerzeit 0,005 Sekunden (Timerzeit = 1/Frequenz). Der Timer wiederum zählt, bei einem Vorteiler von 64, mit einer Frequenz von 250kHz (16MHz/64). Dies entspricht einer Taktzeit von 0,000004  Sekunden. Laut der folgenden Formel muss die Rechnung nun lauten:

Timer - Formel des Timerzählers

Timer - Formel des Timerzählers2

Zum Vergleich zeige ich das Ergebnis der AVR-Timer-Calculator – das Ergebnis ist identisch. Zusätzlich habe ich aber noch eine weitere Linie eingezeichnet. Diese zeigt die Grenze zwischen Werten für 8-Bit und 16-Bit Timer auf. Alles oberhalb eines Compare-Wert von 256 kann nur mit einem 16-Bit Timer erreicht werden. Leider zeigt der Eintrag für den 1024 Vorteiler einen Fehler von -8µs pro Durchlauf an, wodurch der 8-Bit Timer ausfällt.

Timer - Vergleich mit AVR-Timer-Calculator

Timer – Vergleich mit AVR-Timer-Calculator

Abfrage des Zählregisters

Der Zählwert für den Timer, um die 5ms exakt zu treffen, ist nun mit 1249 bekannt. Daher ist es möglich den Zählwert im Timer/Counter Register TCNT1 (diesmal der TCNT1, da wir ja einen 16-Bit Timer verwenden müssen, also Timer1) mit einem Vergleichswert zu abzugleichen. Bei TCNT1 >= 1249 soll etwas geschehen und der Timer wieder von 0 starten.

Eine Methode ist, das Register über das Programm abzufragen und mit einer Variablen zu vergleichen. Stimmen Register und Variable überein, wird das Ereignis ausgelöst. Ein möglicher Code sieht folgendermaßen aus:

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

volatile uint16_t comp;         // Vergleichsvariable

int main(void) {
  comp = 1249;                  // Setzen des Vergl.werts
  DDRC = (1 << DDC3);           // PortC3 als Ausgang
  TCCR1B = (1<<CS10)|(1<<CS12); // Prescaler 1024

  while(1) {
    if ( TCNT1 >= comp ) {      // Wenn Register den Vergl.wert erreicht, ...
      PORTC ^= (1 << PC3);      // Ausgang umschalten
      TCNT1 = 0;                // Zählregister auf 0 setzen
    }
  }
}

Diese Version funktioniert, hat aber den Nachteil, dass keinerlei Interrupt ausgelöst wird. Der Timer muss durch das Programm ständig überwacht werden. Der CTC-Modus übernimmt diese Überwachung und gibt lediglich ein Interrupt aus, wenn der Vergleichswert erreicht wurde. Dieser Interrupt löst wie gewohnt einen Sprung zu einer ISR aus, in der ein Code ausgeführt wird. Danach springt der Mikrocontroller wieder in das ursprüngliche Programm zurück.

Der CTC-Modus

In diesem Beispiel soll eine LED alle 0,5 Sekunden ein-, bzw. ausgeschaltet werden. Mir der weiter oben verwendeten Formel:

Timer - Formel des Timerzählers

habe ich mal eine Tabelle aufgebaut um zu zeigen, wie sich verschiedene Vorteiler auf den Timerzähler auswirken. Dabei bleibt der Mikrocontrollertakt und die benötigte Taktzeit gleich.

                        Vorteiler        Timerzähler
                        1                7999999
                        8                 999999
                        64                124999
                        256                31249
                        1024                7811,5

Die Ergebnisse der Vorteiler 1, 8 und 64 liegen über der maximalen Höhe von 65536 Schritten, die ein 16-Bit Timer zur Verfügung stellt. Mit dem Vorteiler von 1024 wird ein Kommawert erzeugt, der mit einem Timer nicht ohne weiteres zu erreichen ist. In diesem Fall bleibt also nur ein Vorteiler von 64 und einem Timerzähler von 31249.

Die Register

Um den CTC-Modus zu nutzen zeige ich nochmal die Timer-Register. Es kommen zwar die gleichen Register wie bei Overflow-Timer zur Anwendung, jedoch werde ich nun andere Bits setzen.

Timer/Counter Control Register A & B

Waveform Generation Mode Bits

Diesmal werden beide TCCR Register benötigt, da die „Waveform Generation Mode“ Bits ins Spiel kommen. Diese Bits versetzen den Timer in unterschiedliche Verhalten, diesmal ist nur das CTC-Verhalten interessant. Ein Blick in die WGM-Tabelle zeigt verschiedene Einstellmöglichkeiten und die beiden relevanten habe ich farbig umrandet. Ich greife hier mal vor und verrate, dass die rot umrandete Einstellung für dieses Beispiel verwendet wird. Den Begriff OCR1A kommt später noch vor. 😉

Timer - Waveform Generation Mode Bit Beschreibung

Timer – Waveform Generation Mode Bit Beschreibung

Clock Select Bits

Diese Bits sind für den richtigen Wert des Vorteilers zuständig. Für den benötigten Vorteiler von 64 müssen die Bits CS10 und CS11 auf High gesetzt werden

Timer - Clock Select Bits

Timer – Clock Select Bits

Die dazugehörenden Register schauen wie folgt aus:

Timer - TCCR1A un TCCR1B

Timer – TCCR1A un TCCR1B

Die Bits werden später wie folgt gesetzt:

// In diesem Register werden diesmal keine Bits gesetzt
TCCR1A = 0;

// WGM12 = CTC Modus, CS10+CS11 für Vorteiler
TCCR1B |= (1 << WGM12)|(1 << CS11)|(1 << CS10);
Output Compare Register

Dieses Register wird mit dem Wert Timerzähler geladen, den ich weiter oben ausgerechnet habe. Es handelt sich um ein 16-Bit Register und wird daher in ein H (=High) und L (=Low) Byte unterteilt. Es gibt zu jedem Timer zwei OCRn-Kanäle – OCRnA und OCRnB, wobei ich hier den Kanal A benutzen werde.

Timer - Die Register OCRnAH und OCRnAL

Timer – Die Register OCRnAH und OCRnAL

Im CTC Modus vergleicht der Mikrocontroller ständig das OCRnA, bzw. OCRnB Register mit dem Zählwert des Timers. Sind beide Werte gleich, wird das Interrupt ausgelöst. Der Code zum laden des Registers lautet wie folgt:

// Laden des Vergleichswerts
OCR1A = 31249;
Timer/Counter Interrupt Mask Register

Auch beim Overflow Timer musste die Interrupt-Funktion eingeschaltet werden, indem ein bestimmtes Bit im Timer/Counter Interrupt Mask Register (TIMSK) gesetzt wurde. Beim CTC Interrupt ist es ganz genauso, nur das Bit ist ein anderes. Und der Name des Bits ist deutlich komplizierter: „OCIE1A = Timer/Counter1, Output Compare A Match Interrupt Enable“, welches auf High gesetzt wird. Zu erkennen ist auch, dass ich hier wieder den Kanal A nutze.

Timer - Das Timer/Counter Interrupt Mask Register

Timer – Das Timer/Counter Interrupt Mask Register

// CTC Interrupt einschalten
TIMSK1 |= (1 << OCIE1A);

Der Code

Um die Aufgabenstellung zu lösen (eine LED alle 0,5 Sekunden ein-, bzw. ausschalten) sind alle notwendigen Schritte durchlaufen worden. Der Timerzähler wurde berechnet und die Register und zu setzenden Bits habe ich auch gezeigt. Jetzt fehlt noch ein lauffähiges Programm. Dieses habe ich hier mal zusammengestellt um zu zeigen, wie die Einzelteile ineinander greifen und ein Ganzes ergeben.

#ifndef F_CPU
	#define F_CPU 16000000
#endif

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

void timer1_init() {
  // In diesem Register werden diesmal keine Bits gesetzt
  TCCR1A = 0;

  // WGM12 = CTC Modus, CS10+CS11 für Vorteiler
  TCCR1B |= (1 << WGM12)|(1 << CS11)|(1 << CS10);

  // Initialisiere Timer
  TCNT1 = 0;

  //Laden des Vergleichswerts
  OCR1A = 31249;

  // CTC Interrupt einschalten
  TIMSK1 |= (1 << OCIE1A);

  // Global Interrupts einschalten
  sei();
}

ISR (TIMER1_COMPA_vect) {
  // LED umschalten
  PORTC ^= (1 << 3);
}

int main(void) {
  // Port C3 für LED definieren
  DDRC |= (1 << 3);

  // Timer initialisieren
  timer1_init();

  // Hauptprogramm
  while(1) {
    ÜBRIGER PROGRAMMCODE
  }
}

Die Output Compare Pins

Wer gedacht hat, dass das Thema CTC abgeschlossen ist, der lag daneben 🙂 Jetzt möchte ich noch etwas über die Output Compare (OC) Pins schreiben. Ein Blick auf die Pin Konfiguration zeigt, dass es einige OC Pins am ATMega1284P gibt, nämlich genau acht Stück – für jeden der vier Timer je zwei. Diese Ausgänge können so konfiguriert werden, dass sie bei einem Übereinstimmen von Vergleichswert und Timerwert umschalten (toggeln). Das bedeutet jedoch, dass die LED auch an dem entsprechenden Pin angeschlossen wird. Für den weiter oben benutzten OCR1A muss die Diode am Pin PD5 (OC1A) angeschlossen werden.

Timer - Die Output Compare Pins

Timer – Die Output Compare Pins

Das Register

Um die Toggle-Funktion zu aktivieren müssen im Register TCCR1A die Compare Output Mode Bits des Kanal A eingestellt werden. In der folgenden Übersicht habe ich die Toggle-Funktion rot markiert. Sie sorgt dafür, dass der entsprechende Ausgang hin und her geschaltet wird.

Timer - Compare Output Mode für den CTC-Modus

Timer – Compare Output Mode für den CTC-Modus

Im Register TCCR1A sind die Compare Output Mode Bits zu erkennen. Da der OCR1A genutzt wird, kommt nur das Bit COM1A1 und COM1A0 in Frage. Letzteres wird auf 1 gesetzt.

Timer - TCCR1A un TCCR1B

Timer – TCCR1A un TCCR1B

Der Code

Wird nun eine LED am Pin PD5 angeschlossen, kann der Code wie folgt aussehen:

#ifndef F_CPU
	#define F_CPU 16000000
#endif

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

void timer1_init() {
  // Einschalten des OC1A Pins
  TCCR1A |= (1 << COM1A0);

  // WGM12 = CTC Modus, CS10+CS11 für Vorteiler
  TCCR1B |= (1 << WGM12)|(1 << CS11)|(1 << CS10);

  // Initialisiere Timer
  TCNT1 = 0;

  //Laden des Vergleichswerts
  OCR1A = 31249;
}

int main(void) {
  // Port D5 für LED definieren
  DDRD |= (1 << 5);

  // Timer initialisieren
  timer1_init();

  // Hauptprogramm
  while(1) {
    ÜBRIGER PROGRAMMCODE
  }
}

In diesem Code fällt die Interrupt Service Routine vollkommen weg. Durch das automatische Schalten des OC1A, bzw. Pin PD5 wird die ISR schlichtweg überflüssig. Ist doch super was der Mikrocontroller so alles für uns macht!

Schlusswort

Das Thema CTC ist damit abgeschlossen und ich freue mich schon auf den nächsten Teil – die Pulsweitenmodulation oder auch PWM genannt. Ich bin mal gespannt, ob der Artikel dann noch länger wird.

An dieser Stelle möchte ich auch noch einen großen Dank an Mayank Prasad senden, der mir mit seinem Tutorial mächtig unter die Arme gegriffen hat (Thank you, Mayank!).

Ich freue mich immer über Kommentare, Anregungen und Fragen (gerne auch hier über die Kommentarfunktion). Das ist wie der Applaus für einen Musiker 🙂

Alles Gute, Euer Timo

8 Gedanken zu „Die Timer des ATMega128 – CTC Modus

  1. Hallo,

    vielen dank für den Artikel, sehr hilfreich!

    Eine Frage habe ich noch:
    Was ist der Unterschied zwischen TIMSK0 und TCCR0A? Beide scheinen irgendwie ja den CTC Modus nur zu aktivieren?

    Viele Grüße

    1. Hallo lieber Namensvetter 😉

      Der Unterschied liegt darin, das TIMSK0 „lediglich“ dafür zuständig ist, dass der Timer sein entsprechendes Interrupt auslöst, das du mit einer ISP abfangen kannst. Mit dem Register TCCR0A wiederum bestimmst du die Funktion des Timers. Also ist es z.B. ein Overflow- oder CTC-Timer, außerdem wir hier auch der Vorteiler gesetzt.

      Kurz: Ohne TIMSK0 läuft der Timer ab und du erhälst kein Interrupt!

      Ich wünsche dir noch ein paar ruhige Tage in 2014 und jetzt schon einen guten Rutsch!

      Alles Gute, Timo

  2. Hi Timo, super Beitrag. Auch ich habe lange Websites durchforstet um die Timer einigermaßen zu verstehen. Ich habe in Deinem Beitrag einige Fehler entdeckt, die solltest Du nochmal korrigieren. Zwei Schreibfehler :
    „Bei TCNT1 >= 1249 soll etwas ges ch ehen“ fehlendes ch
    “ das Register über das Programm abzu f ragen“ fehlendes f
    Beim Setzen von TCCR1B = (1<CS12) | (1<<CS12); setzt Du zweimal CS12 statt einmal CS10!
    Dann im Programm setzt Du die CPU Frequenz auf 16MHz, Deine Berechnungen hast Du aber mit 8 MHz gemacht.
    #ifndef F_CPU
    #define F_CPU 16000000
    #endif
    Ansonsten super Beitrag, danke und weiter so.
    Gruß Pit

    1. Hallo Pit,
      danke für die Korrekturen! Das waren ja ziemlich dämliche Fehler, vor allem das falsche Setzen der Vorteiler!!
      Aber ich weiß nicht so recht, wo du einen Fehler bei der Berechnung gefunden hast. Ich bin mir ziemlich sicher, dass die Vergleichswerte auf einem Takt von 16MHz basieren. Hab ich Tomaten auf den Augen? (Ausschließen würde ich das bei mir in keinem Fall 😉 )

      Viele Grüße, Timo

  3. Hi Timo, kein Problem 🙂
    Ich glaube in der Tabelle ist ein Fehler, hier rechnest Du mit 8MHz oder?
    Vorteiler Timerzähler
    1 7999999
    8 999999
    64 124999
    256 31249
    1024 7811,5
    Gruß Pit

  4. Hi Timo,
    ich noch mal, Du hast Recht, diesmal war es mein Fehler. Die Tabelle stimmt doch, hatte die 0,5s nicht gelesen, damit stimmt es natürlich wieder, alse den Punkt hab ich falsch gelesen sorry.
    Gruß Pit

  5. Hallo Timo,
    ich beschäftige mich gerade mit Timern und habe einen kleinen Fehler bei CTC entdeckt:
    „In diesem Fall bleibt also nur ein Vorteiler von 64 und einem Timerzähler von 31249.“
    Richtig müsste hier der Vorteiler 256 sein.

    Ansonste eine gute Seite. Weiter so.

    Grüße,
    Joe

Schreibe einen Kommentar

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