AVR-Tutorial: Timer

Wechseln zu: Navigation, Suche

Timer sind eines der Hauptarbeitspferde in unserem Mikrocontroller. Mit ihrer Hilfe ist es möglich, in regelmäßigen Zeitabständen Aktionen zu veranlassen. Aber Timer können noch mehr!

  • Timer können mit einem externen Pin hochgezählt werden
  • Es gibt Möglichkeiten, bei bestimmten Zählerständen einen Interrupt auslösen zu lassen
  • Timer können aber auch völlig selbstständig Signale an einem Ausgabepin erzeugen
  • ...

Aber der Reihe nach und kurz zur Erinnerung: Die Beispiele passen zu einem ATmega8 und ggf. einer Reihe anderer AVR, können bei einigen Typen aber abweichen.

Was ist ein Timer?[Bearbeiten]

Ein Timer ist im Grunde nichts anderes als ein bestimmtes Register im Mikrocontroller, das hardwaregesteuert fortlaufend um 1 erhöht (oder verringert) wird (statt um 1 erhöhen sagt man auch inkrementieren, und das Gegenstück, dekrementieren, bedeutet um 1 verringern). Anstatt also Befehle im Programm vorzusehen, die regelmäßig ausgeführt werden und ein Register inkrementieren, erledigt dies der Mikrocontroller ganz von alleine. Dazu ist es möglich, den Timer mit dem Systemtakt zu verbinden und so die Genauigkeit des Quarzes auszunutzen, um ein Register regelmäßig und vor allen Dingen unabhängig vom restlichen Programmfluss (!) hochzählen zu lassen.

Davon alleine hätte man aber noch keinen großen Gewinn. Nützlich wird das Ganze erst dann, wenn man bei bestimmten Zählerständen eine Aktion ausführen lassen kann. Einer der 'bestimmten Zählerstände' ist zum Beispiel der Overflow. Das Zählregister eines Timers kann natürlich nicht beliebig lange inkrementiert werden – z. B. ist der höchste Zählerstand, den ein 8-Bit-Timer erreichen kann, 28 – 1 = 255. Beim nächsten Inkrementierschritt tritt ein Überlauf (engl. Overflow) auf, der den Timerstand wieder zu 0 werden lässt. Und hier liegt der springende Punkt. Wir können uns nämlich an diesen Overflow "anhängen" und den Controller so konfigurieren, dass beim Auftreten des Timer-Overflows ein Interrupt ausgelöst wird.


Die kann auch alles nochmal im Datenblatt von Atmel zu einigen ATMega Datenblättern nachgelesen werden: http://www.atmel.com/Images/doc8161.pdf

Der Vorteiler (Prescaler)[Bearbeiten]

Wenn also der Quarzoszillator mit 4 MHz schwingt, dann würde auch der Timer 4 Millionen mal in der Sekunde erhöht werden. Da der Timer jedes Mal von 0 bis 255 zählt, bevor ein Overflow auftritt, heißt das auch, dass in einer Sekunde 4000000 / 256 = 15625 Overflows vorkommen. Ganz schön schnell! Nur: Oft ist das nicht sinnvoll. Um diese Raten zu verzögern, gibt es den Vorteiler, oder auf Englisch, Prescaler. Er kann z.B. auf die Werte 1, 8, 64, 256 oder 1024 eingestellt werden, je nach Timer (Bitte Datenblatt konsultieren!). Seine Aufgabe ist es, den Systemtakt um den angegebenen Faktor zu teilen. Steht der Vorteiler also auf 1024, so wird nur bei jedem 1024-ten Impuls vom Systemtakt das Timerregister um 1 erhöht. Entsprechend weniger häufig kommen dann natürlich die Overflows. Der Systemtakt sei wieder 4000000. Dann wird der Timer in 1 Sekunde 4000000 / 1024 = 3906.25 mal erhöht. Da der Timer wieder jedesmal bis 255 zählen muss bis ein Overflow auftritt, bedeutet dies, dass in 1 Sekunde 3906.25 / 256 = 15.25 Overflows auftreten.

Systemtakt: 4Mhz

 Vorteiler    Overflows/Sekunde      Zeit zwischen
                                     2 Overflows [s]
     1            15625                0.000064
     8             1953.125            0.000512
    64              244.1406           0.004096
   256               61.0351           0.016384
  1024               15.2587           0.065536

Die Zeit zwischen 2 Overflows lässt sich sehr leicht berechnen: t={\frac  {2^{{\text{Bit des Timers}}}\cdot {\text{Vorteiler}}}{{\text{Systemtakt in Hz}}}}{\text{s}}.

Erste Tests[Bearbeiten]

Ein Programm, das einen Timer Overflow in Aktion zeigt, könnte z. B. so aussehen:

 
.include "m8def.inc"
 
.def temp = r16
.def leds = r17
 
.org 0x0000
        rjmp    main                  ; Reset Handler
.org OVF0addr
        rjmp    timer0_overflow       ; Timer Overflow Handler
 
main:
        ; Stackpointer initialisieren
        ldi     temp, HIGH(RAMEND)
        out     SPH, temp
        ldi     temp, LOW(RAMEND)     
        out     SPL, temp
 
        ldi     temp, 0xFF            ; Port B auf Ausgang
        out     DDRB, temp
 
        ldi     leds, 0xFF
 
        ldi     temp, (1<<CS00)       ; CS00 setzen: Teiler 1
        out     TCCR0, temp
 
        ldi     temp, (1<<TOIE0)      ; TOIE0: Interrupt bei Timer Overflow
        out     TIMSK, temp
 
        sei
 
loop:   rjmp    loop
 
timer0_overflow:                      ; Timer 0 Overflow Handler
        out     PORTB, leds
        com     leds
        reti

Das Programm beginnt mit der Interrupt-Vektoren-Tabelle. Dort ist an der Adresse OVF0Addr ein Sprung zur Marke timer0_overflow eingetragen. Wenn also ein Overflow Interrupt vom Timer 0 auftritt, so wird dieser Interrupt durch den rjmp weitergeleitet an die Stelle timer0_overflow.

Das Hauptprogramm beginnt ganz normal mit der Belegung des Stackpointers. Danach wird der Port B auf Ausgang geschaltet, wir wollen hier wieder die LED anschliessen.

Durch Beschreiben von TCCR0 mit dem Bitmuster 0b00000001, hier ausgedrückt durch (1<<CS00), wird der Vorteiler auf 1 gesetzt. Für die ersten Versuche empfiehlt es sich, das Programm mit dem AVR-Studio zunächst zu simulieren. Würden wir einen größeren Vorteiler benutzen, so müsste man ziemlich oft mittels F11 einen simulierten Schritt ausführen, um eine Änderung im Timerregister zu erreichen.

Die nächsten Anweisungen setzen im TIMSK Register das TOIE0 Bit. Sinn der Sache ist es, dem Timer zu erlauben, bei Erreichen eines Overflow einen Interrupt auszulösen.

Zum Schluss noch die Interrupts generell mittels sei freigeben. Dieser Schritt ist obligatorisch. Im Mikrocontroller können viele Quellen einen Interrupt auslösen. Daraus folgt: Für jede mögliche Quelle muss festgelegt werden, ob sie einen Interrupt erzeugen darf oder nicht. Die Oberhoheit hat aber das globale Interrupt Flag. Mit ihm können alle Interrupts, egal von welcher Quelle sie kommen, unterdrückt werden.

Damit ist die Initialisierung beendet und das Hauptprogramm kann sich schlafen legen. Die loop: rjmp loop Schleife macht genau dieses.

Tritt nun ein Overflow am Timer auf, so wird über den Umweg über die Interrupt Vektor Tabelle der Programmteil timer0_overflow angesprungen. Dieser gibt einfach nur den Inhalt des Registers leds am Port B aus. Danach wird das leds Register mit einer com Operation negiert, so dass aus allen 0 Bits eine 1 wird und umgekehrt. Die Overflow Behandlung ist damit beendet und mittels reti wird der Interrupt Handler wieder verlassen.

Simulation im AVR-Studio[Bearbeiten]

Es lohnt sich, den ganzen Vorgang im AVR-Studio simulieren zu lassen. Dazu am besten in der linken I/O View Ansicht die Einträge für PORTB und TIMER_COUNTER_0 öffnen. Wird mittels F11 durch das Programm gegangen, so sieht man, dass ab dem Moment, ab dem der Vorteiler auf 1 gesetzt wird, der Timer 0 im TCNT0 Register zu zählen anfängt. Mit jedem Druck auf F11 erhöht sich der Zählerstand. Irgendwann ist dann die Endlosschleife loop erreicht. Drücken Sie weiterhin F11 und beobachten sie, wie TCNT0 immer höher zählt, bis der Overflow erreicht wird. In dem Moment, in dem der Overflow erreicht wird, wird der Interrupt ausgelöst. Mit dem nächsten F11 landen sie in der Interrupt Vektor Tabelle und von dort geht es weiter zu timer_0_overflow. Weitere Tastendrücke von F11 erledigen dann die Ausgabe auf den Port B, das Invertieren des Registers r17 und der Interrupt ist damit behandelt. Nach dem reti macht der Microcontroller genau an der Stelle weiter, an der er vom Interrupt unterbrochen wurde. Und der Timer 0 hat in der Zwischenzeit weitergezählt! Nach exakt weiteren 256 Schritten, vom Auftreten des ersten Overflows an gerechnet, wird der nächste Overflow ausgelöst.

Wie schnell schaltet denn jetzt der Port?[Bearbeiten]

Eine berechtigte Frage. Dazu müssen wir etwas rechnen. Keine Angst, es ist nicht schwer, und wer das Prinzip bisher verstanden hat, der sollte keine Schwierigkeiten haben, die Berechnung nachzuvollziehen.

Der Quarzoszillator schwingt mit 4 MHz. Das heißt, in 1 Sekunde werden 4000000 Taktzyklen generiert. Durch die Wahl des Vorteilers von 1 bedeutet das auch, dass der Timer 4000000 mal in der Sekunde erhöht wird. Von einem Overflow zum nächsten muss der Timer 256 Zählvorgänge ausführen. Also werden in 1 Sekunde 4000000 / 256 = 15625 Overflows generiert. Bei jedem Overflow schalten wir die LEDs jeweils in den anderen Zustand. D.h die LEDs blinken mit einer Frequenz von 7812.5 Hz. Das ist zuviel als dass wir es noch sehen könnten.

Was können wir also tun, um diese Blinkfrequenz zu verringern? Im Moment ist unsere einzige Einflussgröße der Vorteiler. Wie sieht die Rechnung aus, wenn wir einen Vorteiler von 1024 wählen?

Wiederrum: Der Systemtakt sei 4 Mhz. Durch den Vorteiler von 1024 werden daraus 4000000 / 1024 = 3906.25 Pulse pro Sekunde für den Timer. Der zählt wiederum 256 Zustände von einem Overflow zum nächsten. 3906.25 / 256 = 15.2587. Und wiederum: Im Overflow werden die LEDs ja abwechselnd ein und ausgeschaltet, also dividieren wir noch durch 2: 15.2587 / 2 = 7.629. Also knapp 7 Hz. Diese Frequenz müsste man schon mit freiem Auge sehen. Die LEDs werden ziemlich schnell vor sich hin blinken.

Reicht diese Verzögerung noch immer nicht, dann haben wir 2 Möglichkeiten:

  • Entweder wir benutzen einen anderen Timer. Timer 1 beispielsweise ist ein 16 Bit Timer. Der Timer zählt also nicht von 0 bis 255 sondern von 0 bis 65535. Bei entsprechender Umarbeitung des Programms und einem Vorteiler von 1024 bedeutet das, dass die LEDs einen Ein/Aus Zyklus in 33 Sekunden absolvieren.
  • Oder wir schalten die LEDs nicht bei jedem Timer Overflow um. Man könnte zum Beispiel in einem Register bis 7 zählen und nur dann, wenn dieses Register 7 erreicht hat, wird
    • das Register wieder auf 0 gesetzt und
    • die LEDs umgeschaltet.

Timer 0[Bearbeiten]

Timer 0 ist ein 8 Bit Timer.

  • Overflow Interrupt

TCCR0[Bearbeiten]

TCCR - Timer/Counter Control Register[Bearbeiten]

TCCR0
CS02 CS01 CS00


CS02/CS00 - Clock Select[Bearbeiten]

CS02 CS01 CS00 Bedeutung
0 0 0 keine (Der Timer ist angehalten)
0 0 1 Vorteiler: 1
0 1 0 Vorteiler: 8
0 1 1 Vorteiler: 64
1 0 0 Vorteiler: 256
1 0 1 Vorteiler: 1024
1 1 0 Vorteiler: Externer Takt vom Pin T0, fallende Flanke
1 1 1 Vorteiler: Externer Takt vom Pin T0, steigende Flanke

TIMSK[Bearbeiten]

TIMSK
OCIE0 TOIE0


TOIE0 - Timer 0 Overflow Interrupt Enable[Bearbeiten]

Ist dieses Bit gesetzt, so wird beim Auftreten eines Overflows am Timer ein Interrupt ausgelöst.

Anstatt der Schreibweise

 
        ldi     temp, 0b00000001      ; TOIE0: Interrupt bei Timer Overflow
        out     TIMSK, temp

ist es besser, die Schreibweise

 
        ldi     temp, 1 << TOIE0
        out     TIMSK, temp

zu wählen, da hier unmittelbar aus dem Ladekommando hervorgeht, welche Bedeutung das gesetzte Bit hat. Die vorher inkludierte m8def.inc definiert dazu alles Notwendige.

Timer 1[Bearbeiten]

Timer 1 ist ein 16 Bit Timer

  • Overflow Interrupt
  • Clear Timer on Compare Match
  • Input Capture
  • 2 Compare Einheiten
  • div. PWM Modi

TCCR1B[Bearbeiten]

TCCR1B
ICNC1 ICES1 WGM13 WGM12 CS12 CS11 CS10


CS12/CS10 - Clock Select[Bearbeiten]

CS12 CS11 CS10 Bedeutung
0 0 0 keine (Der Timer ist angehalten)
0 0 1 Vorteiler: 1
0 1 0 Vorteiler: 8
0 1 1 Vorteiler: 64
1 0 0 Vorteiler: 256
1 0 1 Vorteiler: 1024
1 1 0 Vorteiler: Externer Takt vom Pin T1, fallende Flanke
1 1 1 Vorteiler: Externer Takt vom Pin T1, steigende Flanke

ICES1 - Input Capture Edge Select[Bearbeiten]

ICES1 = 0 , falling edge ICES1 = 1 , rising edge

ICNC1 - Input Capture Noise Canceler[Bearbeiten]

TCCR1A[Bearbeiten]

TCCR1A
COM1A1 COM1A0 COM1B1 COM1B0 FOC1A FOC1B WGM11 WGM10


OCR1A[Bearbeiten]

 


 


OCR1B[Bearbeiten]

 


 


ICR1[Bearbeiten]

 


 


TIMSK1[Bearbeiten]

TIMSK
TICIE1 OCIE1A OCIE1B TOIE1


TICIE1 - Timer 1 Input Capture Interrupt Enable[Bearbeiten]

OCIE1A - Timer 1 Output Compare A Match Interrupt Enable[Bearbeiten]

OCIE1B - Timer 1 Output Compare B Match Interrupt Enable[Bearbeiten]

TOIE1 - Timer 1 Overflow Interrupt Enable[Bearbeiten]

Timer 2[Bearbeiten]

Timer 2 ist ein 8 Bit Timer.

  • Overflow Interrupt
  • Compare Match Interrupt
  • Clear Timer on Compare Match
  • Phasen korrekte PWM

TCCR2[Bearbeiten]

TCCR2
FOC2 WGM20 COM21 COM20 WGM21 CS22 CS21 CS20


WGM21 - Waveform Generator Mode[Bearbeiten]

Ist diese Option Aktiviert dann wird das Zählregister des Timers (TCNT2) wieder auf 0 gesetzt, sobal das Register as Output Compare Register (OCR2) und TCNT2 übereinstimmen. Zu beachten ist dabei das wenn das Register kleiner als 255 gestellt ist, wird der Timer nicht mehr überschritten und der Interrupt für den Overflow (TIMER2_OVF_vect) nicht mehr ausgelöst. Stattdessen gibt es den Interrupt für Compare Match (TIMER2_COMP_vect) Die Frequenz für die Interruptauslösung lässt sich dann wie folgt berechnen: ISR_Freq = F_CPU : ( Prescaler * ORC2 )

CS22/CS20 - Clock Select[Bearbeiten]

CS22 CS21 CS20 Bedeutung
0 0 0 keine (Der Timer ist angehalten)
0 0 1 Vorteiler: 1
0 1 0 Vorteiler: 8
0 1 1 Vorteiler: 32
1 0 0 Vorteiler: 64
1 0 1 Vorteiler: 128
1 1 0 Vorteiler: 256
1 1 1 Vorteiler: 1024

OCR2[Bearbeiten]

 


TIMSK2[Bearbeiten]

TIMSK
OCIE2 TOIE2


OCIE2 - Timer 2 Output Compare Interrupt Enable[Bearbeiten]

TOIE2 Timer 2 Overflow Interrupt Enable[Bearbeiten]

Was geht noch mit einem Timer?[Bearbeiten]

Timer sind sehr universelle Microcontroller Bestandteile. Für weitergehende Studien ist es daher unerlässlich, das entsprechende Datenblatt des Microcontrollers zu studieren. Oft ist es z. B. möglich, dass der Timer bei erreichen von bestimmten Zählerständen einen Ausgabepin von sich aus ein-/aus-/umschaltet. Er erledigt dann das, was wir oben noch mit einem Interrupt gemacht haben, eigenständig komplett in Hardware. Bei einigen Timern ist es möglich, damit eine PWM (Pulsweiten-Modulation) aufzubauen.

Ein paar der Timermodule lassen sich auch als Counter verwenden. Damit kann man z. B. die Anzahl externer Ereignisse wie Schaltvorgänge eines Inkrementalgebers bestimmen.

Andere bieten die Möglichkeit, über einen externen Uhrenquarz getaktet zu werden (Anwendung z. B. eine "Echtzeituhr" oder als "Weckfunktion" aus einem Standby/Powerdownmodus).

Durch geschickte Umprogrammierung in Echtzeit, lässt sich mit einem Timer eine PLL aufbauen, die sich fortwährend auf einen Eingangstakt synchronisiert. Damit wird vermieden, dass der Controller ein Signal ständig abpollen muss, sondern das Signale wird per Timer-Interrupt verarbeitet. Massgeblich ist die Messung der aktuellen- und Schätzung der kommenden Flanke mithilfe eines einstellbaren Taktteiler-verhältnisses.

Weblinks[Bearbeiten]