AVR-Tutorial: Timer
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 über einen Pin extern 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?
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.
Detaillierte Informationen findet man im Datenblatt des Controllers (PDF; 6,3 MB) sowie in der Application Note AVR130: Setup and Use of AVR Timers (PDF; 512 KB).
Der Vorteiler (Prescaler)
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 4.000.000 / 256 = 15.625 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-sten Impuls vom Systemtakt das Timerregister um 1 erhöht. Entsprechend weniger häufig kommen dann natürlich die Overflows. Der Systemtakt sei wieder 4.000.000 Hz. Dann wird der Timer in 1 Sekunde 4.000.000 / 1.024 = 3.906,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: 4 MHz Vorteiler Overflows/Sekunde Zeit zwischen zwei Overflows 1 15625 0,000064 s = 64 µs 8 1953,125 0,000512 s = 512 µs 64 244,1406 0,004096 s ≈ 4,1 ms 256 61,0351 0,016384 s ≈ 16,4 ms 1024 15,2587 0,065536 s ≈ 65,5 ms
Die Zeit (in Sekunden) zwischen zwei Overflows lässt sich sehr leicht berechnen: [math]\displaystyle{ t=\frac{2^\text{Bit des Timers} \cdot \text{Vorteiler}}{\text{Systemtakt in Hz}} }[/math]
Erste Tests
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 anschließen.
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 Register TIMSK das Bit TOIE0. Sinn der Sache ist es, dem Timer zu erlauben, bei Erreichen eines Overflows 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 Schleife loop: rjmp loop
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
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 (hierfür muss für das Debuggen im AVR-Studio „Mask interrupts while stepping“ deaktiviert (false) sein, zu finden in Tools → Options -> Tools -> Tool settings ). 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 Mikrocontroller 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 der Port?
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 4.000.000 Taktzyklen generiert. Durch die Wahl des Vorteilers von 1 bedeutet das auch, dass der Timer 4.000.000 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 4.000.000 / 256 = 15.625 Overflows generiert. Bei jedem Overflow schalten wir die LEDs jeweils in den anderen Zustand. D. h. die LEDs blinken mit einer Frequenz von 15.625 / 2 = 7812,5 Hz. Das ist zu viel, 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 1.024 wählen?
Wiederum: Der Systemtakt sei 4 MHz. Durch den Vorteiler von 1.024 werden daraus 4.000.000 / 1.024 = 3.906,25 Pulse pro Sekunde für den Timer. Der zählt wiederum 256 Zustände von einem Overflow zum nächsten. 3.906,25 / 256 = 15,2587. Und wiederum: Im Overflow werden die LEDs ja abwechselnd ein- und ausgeschaltet, also dividieren wir noch durch zwei: 15,2587 / 2 = 7,629. Also rund 8 Hz. Diese Frequenz müsste man schon mit bloßem Auge sehen. Die LEDs werden ziemlich schnell vor sich hin blinken.
Reicht diese Verzögerung noch immer nicht, dann haben wir zwei 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 65.535. Bei entsprechender Umarbeitung des Programms und einem Vorteiler von 1.024 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
Timer 0 ist ein 8-Bit-Timer.
- Overflow-Interrupt
TCCR0
TCCR – Timer/Counter Control Register
CS02 | CS01 | CS00 |
---|
CS02/CS00 – Clock Select
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
TIMSK – Timer/Counter Interrupt Mask Register
OCIE0 | TOIE0 |
---|
TOIE0 – Timer 0 Overflow Interrupt Enable
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
Timer 1 ist ein 16-Bit-Timer.
- Overflow-Interrupt
- Clear Timer on Compare Match
- Input Capture
- 2 Compare-Einheiten
- diverse PWM-Modi
TCCR1B
ICNC1 | ICES1 | WGM13 | WGM12 | CS12 | CS11 | CS10 |
---|
CS12/CS10 – Clock Select
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
ICES1 = 0 , falling edge ICES1 = 1 , rising edge
ICNC1 – Input Capture Noise Canceler
TCCR1A
COM1A1 | COM1A0 | COM1B1 | COM1B0 | FOC1A | FOC1B | WGM11 | WGM10 |
---|
OCR1A
OCR1B
ICR1
TIMSK1
TICIE1 | OCIE1A | OCIE1B | TOIE1 |
---|
TICIE1 – Timer 1 Input Capture Interrupt Enable
OCIE1A – Timer 1 Output Compare A Match Interrupt Enable
OCIE1B – Timer 1 Output Compare B Match Interrupt Enable
TOIE1 – Timer 1 Overflow Interrupt Enable
Timer 2
Timer 2 ist ein 8-Bit-Timer.
- Overflow Interrupt
- Compare Match Interrupt
- Clear Timer on Compare Match
- Phasenkorrekte PWM
TCCR2
FOC2 | WGM20 | COM21 | COM20 | WGM21 | CS22 | CS21 | CS20 |
---|
WGM21 – Waveform Generator Mode
Ist diese Option аktiviert, dann wird das Zählregister des Timers (TCNT2) wieder auf 0 gesetzt, sobald das Output Compare Register (OCR2) und TCNT2 übereinstimmen. Zu beachten ist dabei, dass, wenn das Register kleiner als 255 gestellt ist, der Timer nicht mehr überschritten und der Interrupt für den Overflow (TIMER2_OVF_vect) nicht mehr ausgelöst wird. 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: [math]\displaystyle{ \text{ISR-Frequenz} = \frac{\text{F_CPU}}{\text{Prescaler} \cdot \text{OCR2}} }[/math]
CS22/CS20 – Clock Select
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
TIMSK2
OCIE2 | TOIE2 |
---|
OCIE2 – Timer 2 Output Compare Interrupt Enable
TOIE2 – Timer 2 Overflow Interrupt Enable
Was geht noch mit einem Timer?
Timer sind sehr universelle Mikrocontroller-Bestandteile. Für weitergehende Studien ist es daher unerlässlich, das entsprechende Datenblatt des Mikrocontrollers zu studieren. Oft ist es z. B. möglich, dass der Timer bei Erreichen von bestimmten Zählerständen einen Ausgabe-Pin 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 Zähler (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. als „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 abfragen („pollen“) muss, sondern das Signal wird per Timer-Interrupt verarbeitet. Maßgeblich ist die Messung der aktuellen und Schätzung der kommenden Flanke mithilfe eines einstellbaren Taktteiler-Verhältnisses.
Literatur
- Datenblatt des Mikrocontrollers, hier z. B. ATmega8 (PDF; 6,3 MB)
- Application Note AVR130: Setup and Use of AVR Timers (PDF; 512 KB)
Weblinks
- Timer/Counter und PWM beim ATMega16 Mikrocontroller Proseminar von Marcel Jakobs, September 2006 (PDF)
- AVR Timer Calculator