Timer - Der AVR und Vater Zeit
Materialbedarf
Anz. | Bezeichnung | Datenblatt |
1 | Batterie/Spannungsquelle 9V | |
1 | Spannungsregler 7805 |
![]() |
1 | ATMega8 AVR-Prozessor |
![]() |
1 | Widerstand 220 Ohm | |
1 | Widerstand 10 kOhm | |
1 | Elektrolytkondensator 100 µF/16V | |
1 | Kondensator 100nF | |
1 | Standard-Leuchtdiode 3mm oder 5mm |
![]() ![]() |
Der AVR zählt die Zeit
Wer mit Mikrokontrollern arbeitet, kommt sehr häufig in die Gelegenheit, dass die erwünschte Steuerung bestimmte zeitliche Abläufe benötigt. Für diesen Zweck gibt es beim ATMega8 so genannte Timer. Der ATMega8 besitzt für solche Anwendungen 2 Timer mit einer Zählerbreite von 8 Bit und 1 Timer mit einer Zählerbreite von 16 Bit.
Streng genommen, kann der AVR gar keine Zeit messen, sondern nur Zeit zählen. Was heißt das nun?
Der Controller arbeitet in unserem Beispiel mit einer Taktfrequenz von 1 MHz. Dieser Takt wird unter anderem auch an die Timer geführt. Hier wird diese Frequenz noch einmal geteilt, so dass der Zähler des Timers bei jedem Impuls, bei jedem 8. Impuls, bei jedem 64 Impuls usw., weiter zählt. Um z.B. eine bestimmte Zeit abzumessen muss man nur errechnen, wie viel Impuls des Taktgenerators vergehen müssen, damit die gewünschte Zeit erreicht ist.
Die beiden 8 Bit-Timer erreichen nach 256 Takten einen Überlauf. Der 16 Bit breite Timer läuft dann erst nach 65536 Impulsen über und kann somit für längere Zeitmessungen eingesetzt werden. Diesen Überlauf kann man dazu nutzen, ein Ereignis, auch Interrupt genannt, auszulösen. Dazu aber später mehr. Wir wollen nun erst einmal versuchen eine Leuchtdiode, mit Hilfe eines Timers, zum blinken zu bringen.
|
Um diese LED nun anzusteuern, schreiben wir hierfür ein entsprechendes kleines Programm:
$regfile "m8def.dat" $crystal = 1000000 Config Portb.0 = Output Config Timer0 = Timer , Prescale = 1024 Enable Timer0 Do If Timer0 > 127 Then Portb.0 = 1 Else Portb.0 = 0 End If Loop |
Wenn wir jetzt dieses Programm auf dem ATMega8 starten, beginnt die Leuchtdiode D1 zu blinken. Um dies genauer zu verstehen, schauen wir uns das Programm näher an. Die ersten Zeilen sind wieder unsere Konfigurationseinstellungen von Bascom. Interessant wird es bei dieser Zeile:
Config Timer0 = Timer , Prescale = 1024 |
Hier teilen wir Bascom mit, dass wir den Timer 0 verwenden möchten und dass der Systemtakt erst durch 1024 geteilt werden soll. Somit erhöht sich der Zählerstand von Timer 0, 976,5625 mal in der Sekunde. Damit der Timer auch läuft, muss er noch gestartet werden. Dafür sorgt diese Anweisung:
Enable Timer0 |
Nun zählt der Timer ständig von 0 bis 255. Bei jedem 256. Takt wird der Zählerwert wieder auf 0 gesetzt.
In der folgenden Schleife fragen wir ständig den aktuellen Zählerstand ab. Hat dieser einen Wert über 127 erreicht wird die Leuchtdiode an Port B eingeschaltet, andernfalls abgeschaltet.
Der Timer-Interrupt
Bei dem letzten Programm haben wir einen ganz entschiedenen Nachteil. Schauen wir uns dazu noch einmal den Hauptteil des Programms an:
Do If Timer0 > 127 Then Portb.0 = 1 Else Portb.0 = 0 End If Loop |
Hier kann man sehr gut erkennen, dass der AVR die ganze Zeit damit beschäftigt ist, den Timerwert abzufragen, auszuwerten und entsprechend den Ausgang mit der LED zu schalten. Bei einer komplexeren Steuerung ist dies aber nicht sehr effektiv. Besonders wenn so etwas, wie das Blinken einer Leuchtdiode, realisiert werden soll.
Wie in der Einleitung schon angedeutet, wird vom Zähler ein so genannter Interrupt ausgelöst, wenn der Zählerstand überläuft, also der Zählerwert wieder auf 0 gesetzt wird. Was hat das mit dem Interrupt auf sich?
Im Normalfall arbeitet der Prozessor ein bestimmtes Programm ab. Tritt aber nun ein Interrupt auf, z.B. von unserem Timer, wird der normale Programmablauf verlassen und ein Programmteil angesprungen, in dem steht, was bei dem entsprechenden Ereignis getan werden soll. Wurde die Routine ausgeführt, macht der Controller einfach an der Stelle weiter, wo er unterbrochen worden ist. So als währe nie etwas geschehen.
Schreiben wir unser Programm einmal um:
$regfile "m8def.dat" $crystal = 1000000 Config Portb.0 = Output Config Timer0 = Timer , Prescale = 1024 Enable Timer0 On Timer0 Timer_interrupt Enable Interrupts Do Loop Timer_interrupt: Toggle Portb.0 Return |
Die Einstellungen für den Timer bleiben hier die Gleichen. Aber mit den nächsten beiden Zeilen ändern wir das Programm entscheidend ab:
On Timer0
Timer_interrupt Enable Interrupts |
Mit der Anweisung 'On Timer0 Timer_interrupt' sagen wir dem AVR, dass er zum Programmpunkt 'Timer_interrupt' springen soll, wenn der Timer0 einen Überlauf meldet. Die nächste Anweisung 'Enable Interrupts' aktiviert den Interrupt. Ab nun wird das Programm so lange weiter angearbeitet bis der Zähler 0 überläuft. In unserem Fall lassen wir nur eine leere Endlosschleife laufen.
Kommt es jetzt zu einem Timer-Überlauf springt der Controller zur folgenden Routine:
Timer_interrupt: Toggle Portb.0 Return |
Hier wird nun einfach mit Hilfe der Anweisung 'Toggle Portb.0' der Zustand der Leuchtdiode umgeschaltet. Die folgende Anweisung 'Return' sorgt dafür, dass der AVR zu dem Programmpunkt zurück kehrt, wo er durch den Interrupt unterbrochen wurde.
Mit Hilfe dieser Interrupt-Routine können wir nun das Blinken der Leuchtdiode 'nebenbei' erledigen und der Controller kann sich hauptsächlich um die eigentliche Aufgabe kümmern.