|
|
8051 Timer 0/1
[Bearbeiten] Timer 0/1 bei 8051-Controllern[Bearbeiten] EinleitungDie Timer 0 und 1 sind bei jedem 8051-Kompatiblen vorhanden, bei allen gleich, und sie unterscheiden sich nur gering (Mode 3). Der Timer 2 ist erst ab 8052 enthalten und kann abweichende Register und Funktionen haben. Die Timer 0/1 sind Aufwärtszähler. Bei Zählerüberlauf wird ein Flag gesetzt (TF0 bzw. TF1), das aber nicht unbedigt ausgewertet werden muss (z.B Baudratengenerator). Die Timer laufen nach Überlauf einfach weiter. Der Zählerstand kann jederzeit gelesen und auch geändert werden. Beim 16-Bit Mode muss der Zählerstand aus 2 Bytes TH0:TL0 gebildet werden. Im 8-Bit Mode erfolgt ein automatischer Reload aus dem TH-Register in das TL-Register. Die Quelle der Timer ist umschaltbar zwischen Oszillator/12 (Maschinentakte) und externem Eingang. Dies geschieht mit dem Bit C/T im TMOD-Register. C/T auf 0 bedeutet Timer-Betrieb (bekannte Frequenz wird gezählt -> Zeit), C/T auf 1 bedeutet Counter-Betrieb (externe Ereignisse werden gezählt -> z.B. Abfüllen von Werkstücken, Besucherzähler etc.) Die Timer müssen gestartet werden. Dazu muss das TR0 (bzw. TR1) Bit gesetzt werden. Sie können jederzeit angehalten und wieder gestartet werden. Der Zählerstand bleibt gespeichert. Mit gesetztem Gate-Bit läuft der Timer nur dann, wenn zusätzlich der Gate-Eingang auf 1 liegt. Damit können z.B: Impulslägen gemessen werden. Praktischerweise ist dieser Eingang auch gleichzeitig ein Interrupt-Eingang, so dass man die fallende Flanke (Puls zu Ende, Timer steht, Ergebnis kann gelesen werden) per Interrupt auswerten kann. [Bearbeiten] BetriebsartenDas Einstellen der Betriebsart erfolgt in einem Register (TMOD) für beide Timer! Geiz ist Geil... Die unteren 4 Bit gelten für Timer 0, die oberen für Timer 1. Die Bedeutung ist für beide gleich (ausser Mode 3).
Erklärung: [Bearbeiten] Timer oder CounterMit den Bits C/T wird zwischen Timer-Betrieb und Counter-Betrieb umgeschaltet. Als Timer (C/T = 0) zählt die Baugruppe Maschinenzyklen, daher entspricht der Zählerstand einer Zeit. Als Counter (C/T = 1) wird ein Signal von einem externen Zähleingang (P3.4 für Timer 0 und P3.5 für Timer 1) gezählt. [Bearbeiten] Gate-BetriebWenn das Bit 0 ist, läuft der entsprechende Timer, wenn das zugehörige Timer-Run-Flag gesetzt ist (TR0 bzw. TR1). Wenn das Bit 1 ist muss zusätzlich noch der Gate-Eingang auf 1 sein. Dies ist P3.2 für Timer 0 bzw. P3.3 für Timer 1. Damit kann man einfach Pulsdauern ausmessen oder auch Betriebsstunden (mit Hilfszählern für die Überläufe). [Bearbeiten] Timer-Kontroll-RegisterDie Überlauf-Flags und die Run-Bits der beiden Timer befinden sich im Register TCON. Dort sind zusätzlich noch je 2 Bits für die Konfiguration der beiden externen Interrupts untergebracht (der reinste Gemischtwarenladen...)
Beim Überlauf (im 8-Bit Mode nach 2^8 = 256 Maschinenzyklen, im 16-Bit Mode nach 2^16 = 65536 Maschinenzyklen) wird das Überlauf-Flag TF0 bzw. TF1 gesetzt. Damit erkennt man, dass der Timer überglaufen ist. Bei der Bearbeitung muss man das Flag zur Bestätigung löschen, im Interrupt-Betrieb werden die Flags automatisch gelöscht. [Bearbeiten] Schnelleinstieg für ungeduldigeVon den Timern beim 8051 nimmst Du am Anfang nur Timer0/1 im 16-Bit Mode.
=> TMOD = 0x11; setzt beide Timer in den Mode 1. Nur wenn du serielle
Schnittstelle verwendest, brauchst Du evtl. den Timer 1 für die
Baudrate.
Wenn Du keinen Startwert und keinen Reload-Wert setzt, dann läuft der
Timer 65536 Maschinenzyklen (also bei 12MHz ohne X2-Mode 65,5ms). Bei
jedem Timerüberlauf musst Du das Timer-Flag wieder löschen (entfällt im
Interrupt-Betrieb, aber zunächst erstmal ohne) und dann kann man etwas tun. [Bearbeiten] Grundgerüst
Für den Timer 1 änderst/ergänzt Du TR0 -> TR1, TF0 -> TF1. [Bearbeiten] Variable TimerzeitAuf Dauer ist das Geblinke aber langweilig... Also müssen die Zeiten verändert werden.
Durch diese Schreibweise sieht man sofort die erzeugte Timerzeit und die Umrechnung erfolgt durch den Compiler (es wird also auf dem Controller keine Berechnung mehr durchgeführt). Etwas anders ist es, wenn keine Konstanten sondern Variablen dastehen. Dann sollte man etwas anders schreiben:
Auf den ersten Blick etwas umständlich, aber so erkennt der Compiler, dass nur eine Aufteilung in Low- und High-Byte gewünscht ist und weist einfach die Register zu statt Division und Modulo zu berechnen. Bei der Programmierung in Assembler muss man hier 16-Bit Arithmetik berechnen, für Anfänger hört hier der Spass auf (=> erstmal bei konstanten Zeiten bleiben). [Bearbeiten] Längere Wartezeiten erzeugen(zum größten Teil aus meinen Beiträgen hier übernommen: http://www.mikrocontroller.net/topic/264647#2752818 )
Bei jedem Überlauf wird Ticks erhöht und wenn die benötigte Anzahl (=Zeit in s / 65,5ms) erreicht ist, machst Du was und setzt Ticks wieder auf 0.
Damit kannst Du zunächst einen Vorgang realisieren. Wenn Du die Ticks einfach weiterlaufen lässt, dann kann man auch sowas machen:
Das funktioniert mit (fast) beliebig vielen Signalen und es ist auch kein Problem, wenn es einen Überlauf gibt. Wenn Ticks z.B. auf 65000 steht und du willst eine Dauer von 60 Sekunden (= 60000ms/65,536ms = 915 Ticks), dann ergibt die Addition für die Ausschaltzeit 379 (gleicher Datentyp für alle Schaltzeiten!). [Bearbeiten] kurze Zeiten erzeugenDie Timer zählen nach dem Überlauf sofort ab 0x0000 weiter. Also muss man wieder einen Startwert (Reload) laden, so dass der Timer nach einer kürzeren Zeit überläuft. Die Formeln im Quelltext kann man problemlos mit Konstanten füllen, die Division und Modulo wird dabei nicht wirklich berechnet, sondern der Compiler fügt die Ergebniswerte in den Code ein. (anders sieht es für variable Zeiten aus, dazu später mehr) Für einen Grundtakt von 1ms schreibt man also einfach nach jedem Überlauf wieder in die Zählregister:
Da der 8051 bei den Timern 0 und 1 keine getrennten Zähl- und Reload-Register hat, muss man diesen Wert nach jedem Überlauf möglichst schnell wieder in die Register schreiben (daher nimmt man da dann meist Interrupts).
Bei einer Millisekunde läuft die Variable Ticks bereits nach 65536ms, also etwa einer Minute über. Wenn man längere Zeiten benötigt, dann entweder die Ticks alle 10, 20 oder 50ms einstellen oder (in C ja kein echtes Problem) unsigned long Ticks; verwenden. Damit kannst Du in ms-Auflösung Zeiten bis 1193 Stunden erzeugen, bevor der Zähler überläuft... Braucht aber natürlich mehr Speicher (auch für die Schaltzeiten!). Ohne Interrupts kann es aber passieren, dass die Bearbeitung des restlichen Programms länger als die Timer-Zeit dauert. Dann werden die Zeiten sehr ungenau. Also erstmal nicht wundern... [Bearbeiten] Reload mit variablen ZeitenFür variable Zeiten (z.B. PWM für Modellbau-Servo mit 1-2ms Impuls und 20ms Periodendauer) darf man die (16Bit) Variablen nicht einfach in die obige Zeile einsetzen. Dabei wird sonst die Division und die Modulo-Operation wirklich ausgeführt (die sich ergebenden Zeiten sind dann für die Tonne...). Hier muss man (am besten eine Phase vorher, wenn der Timer gerade übergelaufen war) die Variable berechnen.
[Bearbeiten] Timer-Interrupt ohne SchreckenMit Interrupt vermeidet man die Ungenauigkeiten beim Timer-Reload. Sobald der Timer bei aktiviertem Interrupt überläuft, springt der Prozessor in die Interrupt-Service-Routine und führt den dortigen Code aus. Danach geht es an der alten Stelle im Hauptprogramm weiter. (der sollte daher möglichst kurz sein)
Interrupts aktiviert man erstmal einzeln für jede Quelle, danach noch den Hauptschalter EA.
Die Interrupt-Service-Routine ersetzt die if(TF0)-Abfrage aus dem Hauptprogramm. Das ist kein Hexenwerk, es kommt nur ein Schlüsselwort hinter den Funktionsnamen und eine Nummer, die die Quelle angibt. (gilt für den Compiler RC51 aus RIDE)
Ticks muss jetzt als globale Variable deklariert werden, damit sowohl die ISR als auch main() darauf zugreifen können. Erbsenzähler rechnen natürlich noch aus, wieviele µs es bis zum Relaod dauert und korrigieren damit den Reload-Wert:
Es ist aber nicht immer möglich, exakt auf 1µs genau zu arbeiten (ein
laufender ASM-Befehl kann nicht unterbrochen werden und dauert 1, 2 oder
4 MZ). Aber zuviel überlegungen nutzen eh nichts, weil der Quarztakt ja
auch nicht exakt ist. (eine Uhr mit Timer-Interrupt läuft aber trotzdem
mit einer Abweichung von wenigen Sekunden/Woche) [Bearbeiten] Timer im 8-Bit ModusToDo. [Bearbeiten] Counter-BetriebToDo. [Bearbeiten] Kritische Betrachtung, FallstrickeJetzt werden viele "Aber..." rufen. Wenn man genau hinschaut, gibt es natürlich den Fall, dass das Low-Byte übergelaufen ist, bis man das High-Byte geschrieben hat. Ebenso können die Timer-Zeiten daneben liegen, wenn in der Hauptschleife andere zeitaufwändige Aktionen drinstehen. Daher kann man in kritischen Fällen den Timer von dem Reload stoppen und anschliessend wieder starten. In dieser einfachen Einführung habe ich auf solche Details aber für die Übersicht verzichtet... |