Pfad: Home => AVR-Überblick => Programmiertechniken => Timer    (This page in English: Flag EN) Logo

Programmiertechnik für Anfänger in AVR Assemblersprache

Aufbau und Programmierung von AVR-Timern in Assembler

Die am häufigsten (nach den Ports) genutzte interne AVR-Hardware sind Timer/Counter. Her kannst Du lernen, wie man
  1. diese startet (und anhält) und was Normal-Modus ist,
  2. Pins ein- und ausschaltet und was Vergleicher tun,
  3. Timer an verschiedene Taktfrequenzen anpasst,
  4. Timer im CTC-Modus betreibt,
  5. Timer im PWM-Modus betreibt,
  6. 16-Bit-Timer verwendet,
  7. Timer mit Interrupts betreibt, und
  8. wie man Counter zählen lässt.
Alle Berechnungsblätter in der LibreOffice-Calc-Datei timer.ods gibt es hier, alle Zeichnungen als LibreOffice-Draw-Datei gibt es hier.

1 Start und Stop des Timers in Assembler

Der folgende Quellcode startet den 8-Bit Timer 0:

  ldi R16,1<<CS00 ; Setze Bit CS00 auf 1 (eine 1 null mal nach links geschoben)
  out TCCR0B,R16 ; Schreiben in TC0-Kontrollregister TCCR0B

Der Vorteiler in einem 8-Bit-TC Was damit ausgelöst wird: der Vorteiler-Multiplexer vom Timer wird auf 0b001 gesetzt, das Prozessortakt-Signal (standardmäßig 1 MHz) kommt an den Ausgang des Multiplexers und taktet den Timer, der Timer zählt bei jedem Taktimpuls in seinem TCNT0-Portregister Eins aufwärts.

Diese zwei Zeilen Code sind es schon: von nun an läuft der Timer. Wenn Du ihn wieder stoppen willst: lösche das gesetzte CS00-Bit (und alle anderen gesetzten CS-Bits), was den Multiplexer-Ausgang auf Stumm schaltet und den Zähler sofort anhält.

Der Timer TC0 ist gestartet Der Timer TC0 hat seinen TOP-Wert erreicht Im Simulator avr_sim sieht das dann so aus. Links ist der Timer am Laufen, TCNT0 erhöht sich bei jedem Takt. Rechts hat der Zähler seinen höchsten Wert erreicht: alle Bits in TCNT0 sind Eins, mehr geht nicht mehr.

Was dann? Er beginnt einfach wieder bei Null. Und so weiter, und so weiter ...

Um seinen Zählerstand zu lesen, braucht es folgende Instruktion:

  in R16,TCCNT0 ; Lese 8-Bit-Zaehler TC0

Aber Obacht! Es macht keinerlei Sinn einen Timer/Counter lesen zu wollen. Und zwar fast nie. Vergesse also das Lesen, bis Du mehr über Timer, ihre Eigenschaften und ihre Handhabung gelernt hast.

Wie schnell zählt der Zähler und nach welcher Zeit läuft er über? Das hängt nicht zuletzt von seiner Taktfrequenz ab:

Tabelle 1.1, auch als Rechenblatt "normal" in der LibreOffice-Calc-Datei timer.ods
Ueberlauf in Timern bei 1 MHz Takt Wie die Tabelle für ein MHz Takt ausweist, erfolgt der Überlauf mit einer Frequenz von 3.906,25 Hz oder nach jeweils 0,256 Milli-Sekunden.

Aber gemach: wie das Bild oben schon gezeigt hat, hat der Timer nicht nur ein Bit, sondern drei, und der Vorteiler kann auch das durch 8, 64, 256 oder 1,024 geteilte Taktsignal auf den Multiplexer-Ausgang bringen. 1,024 kriegt man zum Beispiel so:

  ldi R16,(1<<CS02)|(1<<CS00) ; Setze Bits CS02 und CS00 auf 1, | ist ein binäres ODER
  out TCCR0B,R16 ; Schreibe in das Timer-Kontrollregister TCCR0B

Nun läuft der Timer mit 3,81 Hz oder alle 262,1 ms über. Das ist langsam genug, dass wir das mit einer angeschlossenen LED sehen könnten. Aber das ist der Stoff für das nächste Kapitel.

2 Pins ein- und auschalten mit dem Timer

Jeder Timer hat zwei assoziierte Pins, die der Timer einschalten, ausschalten oder taumeln lassen kann. Diese Pins heißen OCnA und OCnB für den Timer n. In einem ATtiny24 in PDIP sind diese für TC0 an den Pins PB2 (OC0A) und PA7 (OC0B) lokalisiert.

Die Kontrollbits, die den Schaltmodus bestimmen, sind im Timer/Counter Kontrollregister TCCRnA beheimatet. Für TC0 sind das die Bits COM0A1 und COM0A0 für OC0A sowie COM0B1 und COM0B0 für OCR0B. Sind beide Bits Null, dann sind die angesclossenen Pins inaktiv. Wenn nur COMnx0 gesetzt ist, dann torkelt der Portpin von Null auf Eins, dann von Eins auf Null usw. Wenn nur COMnx1 gesetzt ist, dann wird das Portbit auf Null gesetzt, sind beide gesetzt, dann wird das Portpin auf Eins gesetzt.

Aber wann passiert das denn nun alles? Das ist die Aufgabe der zwei Vergleichsregister (Compare A und Compare B). Wann immer der Timer die darin gespeicherten Werte erreicht, dann führt der Timer beim nächsten eintreffenden Zählerereignis den aktivierten Schaltvorgang aus.

Wenn wir den Compare-A-Wert auf Null setzen und den Compare-Wert B auf 127 sowie die Schaltungsart auf Torkeln, dann kriegen wir an OC0A und OC0B einen schönen Flip-Flop hin:

  sbi DDRB,DDB2 ; Setze PB2 (OC0A) als Ausgang
  sbi DDRA,DDA7 ; Setze PA7 (OC0B) als Ausgang
  ldi R16,0 ; Compare A auf Null
  out OCR0A,R16 ; In das Compare A Portregister
  ldi R16,127 ; Compare B auf 127
  out OCR0B,R16 ; In das Compare B Portregister
  ldi R16,(1<<COM0A0)|(1<<COM0B0) ; Toggle A und B
  out TCCR0A,R16 ; In das Timer/Counter-Kontrollregister A
  ldi R16,(1<<CS02)|(1<<CS00) ; Vorteiler = 1,024
  out TCCR0B,R16 ; In das Timer/Counter-Kontrollregister B
Loop: ; Die endlose Schleife
  rjmp Loop

Setzen der Richtungsbits des OC0-Ausganges Die ersten beiden Instruktionen setzen die Datenrichtungsbits der beiden Portpins als Ausgänge.

Die folgenden vier Instruktionen bringen die beiden Compare-Register auf ihre zwei unterschiedlichen Werte, so dass sie bei verschiedenen Zählerständen torkeln.

Die dann folgenden zwei Instruktionen versetzen beide OC-Pins in den Torkel-Modus, wenn der Vergleichswert erreicht wird. Bitte beachten, dass diese Bits im PWM-Modus des Timers eine andere Funktion hätten (siehe weiter unten).

Die beiden letzten Instruktionen starten den Timer mit einem Vorteiler von 1.024.

OC0A und OC0B mit der Zeit Das ist das was jetzt abgeht:
  1. erreicht der Timer die 1, dann torkelt OC0A (blau) und macht den Ausgang Eins oder Null,

    OC0A schaltet auf High

  2. erreicht der Timer 128, dann torkelt OC0B (rot) und macht den Ausgang High,

    OC0B schaltet auf High

  3. wenn der Timer wieder 1 erreicht (nach dem Überlauf von 255 auf 0), dann torkelt wieder OC0A,
  4. wenn der Timer wieder 128 erreicht, torkelt wieder OC0B,
  5. und so weiter und so weiter und so weiter ...
Nun nehmen wir an, wir hätten eine rot-grüne Duo-LED an die beiden Pins angeschlossen, wie es hier zu sehen ist.

Eine Duo-LED an den zwei OC-Pins Phasen beim OC-Torkeln In Phase 1, mit OC0A auf High und OC0B auf Low, ist die rote LED an. In Phase 2, wenn beide Ausgänge auf High sind, ist die LED aus. In Phase 3, wenn OC0A Low und OC0B High ist, ist dann die grüne LED an. Gefolgt von einer Pause in Phase 4, bei der beide Ausgänge Low und die LED aus ist.

Bitte beachten, dass der Timer nun automatisch das alles immer und wieder wiederholt. Es ist kein weiterer Programmeingriff nötig, um das Ganze am Leben, Schalten und Leuchten zu halten. Der Timer macht das nun ganz alleine, der Controller könnte nun schlafen gelegt werden.

Ein netter rot/grüner Blinker mit Pausen dazwischen.

Einige Fragen:
Wie würde es denn blinken, wenn wir OCR0B auf 31 oder auf 1 setzen würden? Du kannst das Rechenblatt "rotgrün" in der LibreOffice-Calc-Datei timer.ods verwenden, um das herauszufinden: nur den Wert von Compare B verändern. Und: was wäre denn einzustellen, wenn ein pausenloses Blinken veranstaltet werden soll?

3 Timer bei verschiedenen Frequenzen

Wenn Du mal einen Blick zurück auf Tabelle 1.1 wirfst, siehst Du, dass die Frequenzen bei verschiedenen Vorteilern recht krumme Werte haben: sie haben keine Ganzzahlenwerte, sondern alle was hinter dem Komma. Das kommt natürlich daher, weil das Teilen durch Zweierpotenzen kaum einen Rest von 0,000 lässt, wenn Deine Ausgangsfrequenz 1 MHz ist. Sogar wenn wir den Takt auf 8 MHz erhöhen, indem wir die CLKDIV8-Fuse des ATtiny24 löschen, hilft das nicht viel weiter: nur der Vorteiler von 1 führt zu einer Ganzzahl, alle anderen haben wieder irgendwas hinter dem Komma.

Das kann man nur ändern, wenn die Taktfrequenz selber eine Zweier-Potenz ist, z. B. 2,048 MHz. Wenn Du das in das Rechenblatt "normal" in der LibreOffice-Calc-Datei timer.ods einträgst, dann siehst Du, dass Vorteiler bis hinauf zu 64 jetzt zu einer Ganzzahl führen (125 Hz).

Ein Quarz als Taktgeber So kann man den ATtiny24 mit einem Quarz takten, der eine Zweierpotenz an Frequenz hat: die beiden Pins PB0 und PB1 wechseln ihre Funktion, wenn man die entsprechenden Fuses setzt. Bitte nicht vergessen, die CLKDIV8-Fuse ebenfalls zu löschen.

Kommerziell verfuegbare Quarze Dieser Auszug aus dem Rechenblatt "quarze" der LibreOffice- Calc-Datei timer.ods zeigt alle kommerziell verfügbaren Quarze, ihre Frequenzen, ihr Gehäuse, und die Frequenzen an, mit denen Timer bei den Vorteilerwerten von 8, 64, 256 und 1.024 getaktet werden.

Eine Menge an Quarzen liefert schöne Frequenzen, mit vielen Nullen hinter dem Komma (grün hinterlegt). Aber welche davon liefern sogar nach dem weiteren Teilen durch 256 im Timer auch noch Ganzzahlen?

8-Bit- und 10-Bit-TC-Frequenzen Hier sind die Frequenzen nach dem Teilen durch 256 (8-Bit) und 1.024 (10-Bit) im Timer. Nur die Quarze mit 2,097152, 3,93216, 4,194304 und 6,5536 MHz liefern ganzzahlige Frequenzen bei allen möglichen Vorteilern.

4 Timer im CTC-Modus

Timer können aber nicht nur immer von Null bis 255 zählen, sie können auch bis weniger zählen und vorzeitig wieder von vorne beginnen. Um das zu können, muss man die Obergrenze des Zählens, den TOP-Wert, herabsetzen. Der ist standardmäßig bei 255, kann aber durch Schreiben des Compare-A-Wertes und durch Setzen des CTC-Modus (CTC = Clear-Timer-on-Compare = Timer-Rücksetzen-bei-Compare) dazu gebracht werden, schon bei weniger Schluss zu machen.

Der CTC-Modus des Timers Um den 8-Bit-Timer TC0 in diesen Modus zu bringen, müssen wir die Wave-Generation-Mode-Bits (WGM, = Wellen-Erzeugungs.Modus-Bits) auf 0b010 und den Compare-A-Wert im Portregister OCR0A auf den gewünschten Wert der Obergrenze minus 1 setzen.

Aus historischen Gründen sind die WGM-Bits je nach AVR-Typ über die Portregister TCCR0A und TCCR0B verteilt. Im ATtiny24 sind diese im TCCR0A (WGM01 und WGM00), wo auch die COM-Bits liegen, und in TCCR0B (WGM02), wo auch die CS-Bits liegen, untergebracht.

Wir benutzen das Rechenblatt "ctc" in der LibreOffice-Calc-Datei timer.ods, um unsere Frequenzen im CTC-Modus zu berechnen. Bei 1 MHz und einem Vorteilerwert von 1.024 setzen wir einen Teilerwert von 122. Dieser Wert, minus 1, geht in das Compare-A-Register. Die resultierende Frequenz ist dann 8,004 Hz. Da das Torkeln immer zwei Compare-Matches braucht, ist die Vollschwingung bei 4,002 Hz. Der gesamte Assembler-Quellcode:

  sbi DDRB,DDB2 ; Setze PB2 (OC0A) als Ausgabe-Pin
  sbi DDRA,DDA7 ; Setze PA7 (OC0B) als Ausgabe-Pin
  sbi PORTA,PORTA7 ; Setze PA7 High zu Beginn
  ldi R16,121 ; Compare A auf 121
  out OCR0A,R16 ; In das Compare A Portregister
  out OCR0B,R16 ; Und in das Compare B Portregister
  ldi R16,(1<<COM0A0)|(1<<COM0B0)|(1<<WGM01) ; Toggle A und B im CTC-Modus
  out TCCR0A,R16 ; In das Timer/Counter Kontrollregister A
  ldi R16,(1<<CS02)|(1<<CS00) ; Vorteiler = 1,024
  out TCCR0B,R16 ; In das Timer/Counter Kontrollregister B
Loop: ; Endlosschleife
  rjmp Loop 

CTC-Code-Simulation Der Code läuft wie geschmiert: die beiden Pins produzieren eine Schwingung von 4,002 Hz und einer Pulsweite von 50%.

CTC-Modus-Rechenblatt Um mit Teilerwerten, Compare A und Quarzfrequenzen etwas herum zu spielen, kann man im Rechenblatt "quarze" in der LibreOffice-Calc-Datei timer.ods in den Spalten N bis R etwas herummachen. Hier kann man sich was Passendes aussuchen, um seinen Quarz auf genau ein, zehn oder 100 Hertz zu ziehen.

Den großartigen Effekt des CTC-Modus kann man am Besten bei den 2,048- und 4,096 MHz-Quarzen sehen: mit Teilerwerten von 250 (Compare A = 249) statt 256 liefern diese famose Ganzzahlen-Frequenzen. Der geringe Unterschied von nur 6 macht einen immensen Effekt beim Teilen.

Und nochmal: keine weiteren Controller-Aktivitäten sind nötig, alles ist automatisch und fertig eingestellt.

5 Timer in den PWM-Modi

Ein PWM-Signal PWM heißt Puls-Weiten-Modus: ein Signalausgang geht auf Eins, nach einer vordefinierten Zeit auf Null (An-Zeit) und nach einer weiteren vordefinierten Zeit wieder auf High (Aus-Zeit).

Auch das invertierte Signal ist möglich: ist der Timer auf Null, wird der Ausgang auf Null gesetzt. Wird der Vergleichswert in Compare A erreicht, dann setzt der nächste Timer-Impuls den Ausgang auf High. Das nennt man dann ein invertiertes PWM-Signal.

Für was kann man denn so was brauchen? Nun: wenn wir eine LED mit einem Vorwiderstand an einen solchen Ausgang anschließen, geht sie an und aus. Wenn die PWM-Frequenz hoch genug ist, sehen wir sie aber nicht an- und ausgehen. Wir sehen dann nur den Mittelwert, mit dem die LED an und aus ist. Ist die AN-Zeit kurz, ist die LED weniger hell. Und zwar vollkommen linear und nicht mit Eins durch, wie bei einem Poti als Vorwiderstand.

Selbiges passiert, wenn wir an den Ausgang einen Treibertransistor anschließen und damit einen Motor ein- und ausschalten. Der träge Motor sieht nur den Mittelwert an Leistung: je weniger AN-Zeit, desto weniger Drehleistung.

5.1 Zeiten im PWM-Modus

PWM-Signalzeiten Das hier sind die Zeitbeziehungen eines PWM-Signals.

Die PWM-Zyklus-Zeit ist tPWM = 256 * NVorteiler / Takt für eine 8-Bit-PWM. Mit einem Vorteilerwert von 1 und einer Taktfrequenz von 1 MHz sind das 0,256 ms (entsprechend einer PWM-Frequenz von 3,9 kHz. So schnell guckt unser Auge nicht (ca. 30 Hz), während unser Ohr durchaus 3,9 kHz als ein Brummen des Motors hören kann.

Wenn der Vergleichswert im Compare A 127 wäre, hätte die Pulsweite genau 50% (bei (127 + 1) / 256 = 0,50). Die kürzeste AN-Zeit wäre bei einem Compare-Wert von 0 (0 + 1) / 256 = 0,39%.

Bitte beachten, dass die TCNT-Werte sich nicht linear bewegen, wie das das Diagramm suggeriert. Es sind 256 einzelne Stufen, nur war ich etwas faul mit dem Zeichnen.

5.2 Die COM-Bits

Es gibt aber nicht nur einen PWM-Modus des Timers. Neben dem schon beschriebenen Modus gibt es noch weitere. Bei allen Modi haben die COM-Bits, die die beiden Ausgänge ansteuern, unterschiedliche Bedeutung und Funktion.

5.2.1 Die COM-Bits im Fast-PWM-mode

Der Fast-PWM-mode wurde bereits oben beschrieben. Der Timer zählt aufwärts und schaltet beim Vergleichswert plus Eins. Es gibt aber zwei Fast-PWM-Modi:
  1. TOP = 255: der Timer hat 256 Stufen, beide Vergleichswerte können zum Schalten von OCnA und OCnB benutzt werden,
  2. TOP = OCRnA: der Timer hat (OCRnA + 1) Stufen und setzt dann wieder auf Null, nur der Vergleichswert B kann benutzt werden, um OCnB zu schalten, B muss dabei aber kleiner oder gleich A bleiben.
Im zweiten Fall ist die PWM-Frequenz höher, denn der Zähler zählt nur noch bis OCRnAQ+1. Setzt man OCRnA auf 31, dann ist die PWM-Frequenz 8 mal höher, also bei 1 MHz 31,25 kHz. Jetzt hört man auch kein Motorbrummen mehr, nur die Fledermäuse werden nun von den falschen Echo-Signalen irritiert. Natürlich machen nun nur noch Vergleichswerte B zwischen 0 und 31 Sinn.

Die COM-Bits in den beiden Fast-PWM-Modi haben die folgende Bedeutung:
COMnA/B1COMnA/B0BottomCompare Match A/BAnmerkung
00OCnA/B unbeeinflusst-
10Setze OCnA/BLösche OCnA/BNicht-invertierender PWM-Modus
11Lösche OCnA/BSetze OCnA/BInvertierender PWM-Modus

5.2.2 Die COM-Bits im Phasen-korrekten PWM-Modus

Phasen-korrekter Modus Invertierter Phasen-korrekter Modus Im Phasen-korrekten PWM-Modus
  1. zählt der Timer zuerst aufwärts bis zum TOP-Wert, dann abwärts bis auf Null ("bottom"),
  2. wird beim Aufwärtszählen der Vergleichswert A oder B erreicht (plus Eins), dann wird der Ausgang High gesetzt (Normalmodus) oder Low gelöscht (invertierter Modus),
  3. erfolgt die Gleichheit beim Abwärtszählen, dann wird der Ausgang gelöscht (Normalmodus) bzw. gesetzt (invertierter Modus),
  4. Änderungen der Vergleichswerte im laufenden Betrieb werden erst aktiv, wenn der Zähler Null erreicht.
Dieser Modus macht Sinn, wenn Vergleichswerte häufig wechseln. Dabei wird durch die Zwischenspeicherung der Vergleichswerte und ihr Aktivieren beim Zähler-Null erreicht, dass alle Impulse eine korrekte Dauer aufweisen und dass kein Compare-Match verpasst wird (was der Fall bei sofortiger Anwendung der Vergleichswerte sein könnte, wenn der neue Vergleichswert unter dem aktuellen Zählerstand läge: der Vergleichswert wird dann verpasst).

Das Auf- und Abwärtszählen reduziert die PWM-Frequenz um den Faktor zwei, diese Modi sollten daher "Slow PWM mode" genannt werden.

Die COM-Bits im Phasen-korrekten PWM-Modus haben die folgende Bedeutung:
COMnA/B1COMnA/B0AufwärtsAbwärtsAnmerkung
00OCnA/B unbeeinflusst-
10Lösche OCnA/BSetze OCnA/BInvertierender Phasen-korrekter PWM-Modus
11Setze OCnA/BLösche OCnA/BNicht-invertierender Phasen-korrekter PWM-Modus

5.2.3 Die COM-Bits im Phasen- und Frequenz-korrekten PWM-Modus

Beim frequenz-korrekten PWM-Modus wird der TOP-Wert des Timers erst dann verändert, wenn der Zähler den Bottom (Null) erreicht. Den TOP-Wert kann man folgendermaßen verstellen:
  1. bei TOP = OCRnA-Modi durch Schreiben in das OCRnA-Register, oder
  2. bei TOP = ICRn-Modi (16-Bit-Zähler) durch Schreiben in das ICRn-Registerpaar, oder
  3. bei 8-/9-10-Bit-PWM-Modi: durch Schreiben der WGM-Mode-Bits im TCCRnA- und TCCRnB-Register.
Bei allen diesen Änderungen des TOP-Wertes werden diese erst beim Bottom ausgeführt.

Dieser Modus stellt sicher, dass nur komplette PWM-Zyklen auftreten können und keine fehlerhaften Pulsdauern passieren (wenn z. B. TOP- und Vergleichswert gar nicht zueinander passen).

Das ist ebenfalls ein "slow motion"-Modus.

6 16-Bit-Timer

16-Bit-Timer/Counter mit Vorteiler 16-Bit-Timer/Counter sind ihren 8-Bit-Pendants sehr ähnlich. Ihr Vorteiler sieht genauso aus und funktioniert auch identisch, nur hat der Zähler jetzt 16 Bits und kann daher von 0 bis 65.535 (= 216 - 1) zählen.

Weil AVRs 8-Bit-Mikrocontroller sind und keine Möglichkeit besteht, 16 Bits gleichzeitig in Portregister-Paare zu schreiben oder daraus zu lesen, muss der Zugriff darauf in zwei Portionen erfolgen. Alle 16-Bit-Register (TCNT, OCRnA/B, etc.) haben daher zwei Portregister, die intern als Paar geschaltet sind. Den Namen wird ein "L" für das LSB und ein "H" für das MSB angehängt. Daher hat TCNT1 ein TCNT1L- und ein TCNT1H-Portregister.

Zwei spezielle Handhabungsvorgänge sind bei 16-Bit-Timern spezifisch:
  1. der 8-Bit-Zugriff erfordert eine besondere Handhabung zur Vermeidung von Zugriffsfehlern, und
  2. da 16-Bit-PWMs keinen Sinn machen, haben sie ein Extra- Registerpaar, das als zusätzliches Vergleichsregister sowie als CTC-Register dienen kann.
Siehe die folgenden Kapitel zu diesen beiden Themen.

6.1 16-Bit-Portregisterpaar-Zugriffe

8-Bit-Controller können nicht in einem einzigen Schritt auf 16-Bit-Portregisterpaare zugreifen, sie brauchen dafür zwei Instruktionen: das MSB und das LSB separat. Da der Timer zwischen zwei Instruktionen aber weiterdreht, kann es in seltenen Fällen dazu kommen, dass sich LSB und MSB beide gleichzeitig ändern, und das auch noch nach dem ersten und vor dem zweiten Zugriff.

Beim Lesen führt das dazu, dass LSB und MSB gar nicht zusammen passen. Beim Schreiben, z. B. eines Vergleichswerts, würde schon nach dem ersten Schreiben ein Compare Match auftreten können (nicht im PWM-Modus, aber sonst).

Damit das nicht passiert, wird beim Lesen eines LSBs das zugehörige MSB in einen Zwischenspeicher bugsiert. Erfolgt danach das Lesen des MSBs, dann greift der Controller gar nicht auf den echten MSB des Registerpaars zu, sondern auf diesen Zwischenspeicher.

Umgekehrt beim Schreiben: zuerst wird das MSB geschrieben, aber erst mal in einen Zwischenspeicher abgelegt. Wird dann das LSB geschrieben, dann werden beide Werte gleichzeitig in das Registerpaar geschrieben.

Wenn man diese Reihenfolgen nicht einhält, kriegt man lustige Effekte: liest man nur das MSB eines Registerpaars, kriegt man immerzu den Wert des MSB beim letzten LSB-Zugriff. Schreibt man nur das MSB des Registerpaars, dann kommt beim Registerpaar gar nix an.

Bitte beachten, dass in PWM-Timer-Modi geschriebene Vergleichswerte gar nicht direkt dort ankommen, sondern erst beim ersten Bottom-Ereignis (TCNTn=0). Daher kann es beim Simulieren zu erstaunlichen Fehlansichten kommen. Nicht verzagen, der Vergleichswert kommt schon dort an, wo er hin soll, nur halt erst später.

6.2 Das Input-Capture-Register in 16-Bit TCs

Modes in 16-Bit-Timer/Countern In Timern, die anstelle von einem, zwei oder drei ganze vier Mode-Kontroll-Bits WGM haben (normalerweise haben 16-Bit-Zähler so viele), gibt es ein zusätzliche Timer-Modi und ein zusätzliches Vergleichsregister:
  1. die WGM-Modus-Bits WGM können auf eine PWM mit 8-, 9- oder 10-Bit eingestellt werden, was TOP-Werte von 255, 511 oder 1.023 zur Folge hat,
  2. sie haben ein Input Capture Register (ICR), welches sowohl als drittes Vergleichsregister (aber ohne OC-Pins) als auch als TOP-Quelle verwendet werden kann, als solches kann damit die PWM-Frequenz sehr feinfühlig zwischen 0 und 65.535 eingestellt werden.
Da mit ICRn die Einstellung des TOP-Wertes mit OCRnA nicht mehr nötig ist, kann OC1A als PWM-Ausgang dienen.#

Damit ist man dann flexibel genug, um die Auflösung Deiner PWM auf genau Deine Bedürfnisse einzustellen.

6.3 Eine Beispielanwendung

Zwei Timer mit zwei Duo-LEDs Das hier ist ein Beispiel mit zwei aktiven Timern als Paar: TC0 als 8-Bit-Timer und TC1 als 16-Bit-Timer. Beide blinken unabhängig voneinander mit ihrer eigenen Blinkfrequenz.

Ds hier ist der Quellcode:

; ***********************************************
; * Zwei Timer in Normalmodus in einem ATtiny24 *
; * Version 1 vom August 2022                   *
; * (C)2022 by Gerhard Schmidt                  *
; ***********************************************
;
.nolist
.include "tn24adef.inc" ; Definiere ATtiny24A
.list
;
; Frequenzen, exportiert aus LibreOffice-Rechenblatt
.equ clock = 1000000 ; Taktfrequenz in Hz
; TC0
.equ cF8 = 1000 ; Frequenz in Hz
.equ cPresc8 = 8 ; Vorteilerwert
.equ cCs8 = 1<<CS01 ; Vorteiler-Bits
.equ cDiv8 = 63 ; Teilerwert
.equ cCmp8 = 62 ; Compare-Wert fuer CTC
; TC1
.equ cF16 = 200000 ; Frequenz in mHz
.equ cPresc16 = 1 ; Vorteilerwert
.equ cCs16 = 1<<CS10 ; Vorteiler-Bits
.equ cDiv16 = 2500 ; Teilerwert
.equ cCmp16 = 2499 ; Compare-Wert fuer CTC
;
.cseg
.org 000000
; Init TC0
  sbi DDRB,DDB2 ; Setze PB2 als Ausgangs-Pin OC0A
  sbi DDRA,DDA7 ; Setze PA7 als Ausgangs-Pin OC0B
  sbi PORTA,PORTA7 ; Setze PA7 auf High
  ldi R16,cCmp8 ; Compare A
  out OCR0A,R16 ; In Compare A Portregister
  out OCR0B,R16 ; In Compare B Portregister
  ldi R16,(1<<COM0A0)|(1<<COM0B0)|(1<<WGM01) ; Toggle A und B, CTC-Modus
  out TCCR0A,R16 ; In Timer/Counter Kontrollregister A
  ldi R16,cCs8 ; Vorteiler CS-Bits
  out TCCR0B,R16 ; In Timer/Counter Kontrollregister B
; Init TC1
  sbi DDRA,DDA6 ; Setze PA6 als Ausgangspin OC1A
  sbi DDRA,DDA5 ; Setze PA5 als Ausgangspin OC1B
  sbi PORTA,PORTA6 ; Setze PA6 auf High
  ldi R16,High(cCmp16) ; Compare A, MSB zuerst
  out OCR1AH,R16 ; In Compare A, MSB
  ldi R16,Low(cCmp16) ; dto., LSB
  out OCR1AL,R16 ; In compare A, LSB und MSB
  ldi R16,High(cCmp16) ; Compare B, MSB zuerst
  out OCR1BH,R16 ; In compare B, MSB
  ldi R16,Low(cCmp16) ; dto., LSB
  out OCR1BL,R16 ; In compare B, LSB und MSB
  ldi R16,(1<<COM1A0)|(1<<COM1B0)
  out TCCR1A,R16
  ldi R16,(1<<WGM12)|cCs16
  out TCCR1B,R16
Loop:
	rjmp loop
;
; Ende Quellcode

Zwei Timer mit zwei Duo-LEDs gestapelt Bitte beachten, dass die Zeile

  ldi R16,(1<<WGM12)|cCs16

das WGM12-Bit mit der CS-Bit-Kombination verheiratet, die aus dem Import aus dem Rechenblatt "ctc" in der LibreOffice-Calc- Datei timer.ods stammt.

Wenn Du den Import aus dem Rechenblatt vermeiden willst: den optimalen Vorteiler aus der Frequenz in Assembleresprache zu errechnen ist nicht so ganz einfach, weil Du ja fünf Vorteilerwerte nacheinander durchprobieren musst. Mit .if, .else und .endif geht das dann so (hier für den 16-Bit-Timer TC1):

.equ clock = 1000000 ; Die Taktfrequenz in Hz
.equ cFreq = 1000 ; Die gewuenschte Frequenz in Hz
.equ cTcTop1 = 65536 ; 256 fuer 8-Bit, 65536 fuer 16-Bit
.equ cFreq2 = 2 * cFreq
.if clock / cFreq2 <= cTcTop1
  .equ cPresc = 1
  .equ cCs = 1<<CS10
  .else
  .if clock / cFreq2 / 8 <= cTcTop1
    .equ cPresc = 8
	.equ cCs = 1<<CS11
	.else
    .if clock / cFreq2 / 64 <= cTcTop1
      .equ cPresc = 64
	  .equ cCs = (1<<CS11)|(1<<CS10)
	  .else
      .if clock / cFreq2 / 256 <= cTcTop1
        .equ cPresc = 256
	    .equ cCs = 1<<CS12
	    .else
        .if clock / cFreq2 / 1024 <= cTcTop1
          .equ cPresc = 1024
	      .equ cCs = (1<<CS12)|(1<<CS10)
	      .else
	      .error "Vorteiler-Wert groesser als 1.024!"
		  .endif
		.endif
	  .endif
	.endif
  .endif
; Berechne Vergleichswert mit Runden
.equ cDiv = ((clock + cFreq2 / 2) / cFreq2 + cPresc / 2) / cPresc
.equ cCmp = cDiv - 1

Ziemlich heftiger Code, funktioniert aber perfekt.

7 Timer-Interrupts

Wenn Du die Interrupt-Enable-Bits in TIMSK oder TIMSKn gesetzt hast, funktioniert jeder Überlauf, Vergleichs-Match oder, in 16-Bit-Timern, ICR-Match mit einem Interrupt. Das macht jedes Polling des Timers überflüssig.

Wie bei jedem anderen Interrupt, Die Interruptvektoren des ATtiny24 sehen so aus:

	rjmp Main ; Reset Vektor
	reti ; EXT_INT0
	reti ; PCI0
	reti ; PCI1
	reti ; WATCHDOG
	reti ; ICP1: Timer 1 compare match ICR
	reti ; OC1A: Timer 1 compare match A
	reti ; OC1B: Timer 1 compare match B
	reti ; OVF1: Timer 1 overflow
	reti ; OC0A: Timer 0 compare match A
	reti ; OC0B: Timer 0 compare match B
	reti ; OVF0: Timer 0 overflow
	reti ; ACI
	reti ; ADCC
	reti ; ERDY
	reti ; USI_STR
	reti ; USI_OVF

Mehr als die Hälfte aller Interrupts im ATtiny24 befasst sich mit Timern als Quelle.

Der 16-Bit-Timer 1 hat eine höhere Priorität als der 8-Bit-Timer 0.

Der ATtiny24 hat zwei Timer-Interrupt-Masken-Register: TIMSK0 für Timer 0 und TIMSK1 für Timer 1. Die Interrupt-Enable-Bits für Timer 0 sind OCIE0A, OCIE0B und TOIE0, für Timer 1 dasselbe nur mit 1 statt 0 sowie zusätzlich noch ICIE1 für den Capture-Interrupt.

Innerhalb der Interrupt-Service-Routine von Timern kann und darf alles gemacht werden: Flaggen setzen für außerhalb der Routine, Softwarezähler in Registern oder im SRAM abwärts zählen, Port-Pins ein- und ausschalten die nicht OC heißen, usw. usf. In der Routine können beliebige Pins manipuliert werden. Man kann sogar den Timer abschalten, durch Nullsetzen der CS-Bits, wenn er nicht mehr gebraucht wird (z. B. für einen Software-Einzel-Puls).

Ein Timer fuer lange Zeiten Der folgende Quellcode zeigt die Erzeugung eines einzigen sehr, sehr, sehr langen Impulses: nach dem Drücken eines Tasters geht ein Ausgangspuls hoch und bleibt für eine lange Zeit an. Er kann den Timer TC0 verwenden (minimum Zeit 0,26 Sekunden, maximum Zeit 4,77 Stunden) und benutzt den Überlauf-Interrupt, um einen 16-Bit-Zähler in R25:R24 von der Konstante cDelay herabzuzählen. Die Konstante kann im Rechenblatt "langzeit" der LibreOffice-Calc-Datei timer.ods berechnet werden. Wenn Du ein 15-Minuten-Signal für die Treppenhausbeleuchtung brauchst: setze cDelay auf 3.433. Der Quellcode kann hier heruntergeladen werden.

Ein 260ms-Einzelpuls

;
; ***********************************
; * Einzelpuls vom INT0-Eingang     *
; * ATtiny24A, Version 1.0          *
; * (C)2022 by Gerhard Schmidt      *
; ***********************************
;
.nolist
.include "tn24adef.inc" ; Definiere Device ATtiny24A
.list
;
; **********************************
;    V E R Z O E G E R U N G
; **********************************
;
.equ cDelay = 1 ; Kann zwischen 0 und 65535 sein
;
; **********************************
;       R E G I S T E R
; **********************************
;
; frei: R0 bis R15
; benutzt: R16 als Vielzweckregister
; frei: R17 bis R23
.def rCntL = R24
.def rCntH = R25
; frei: R26 bis R31
;
.cseg
.org 000000
;
; **********************************
; R E S E T  &  I N T - V E C T O R E N
; **********************************
	rjmp Main ; Reset-Vektor
	rjmp ExtInt0Isr ; EXT_INT0
	reti ; PCI0
	reti ; PCI1
	reti ; WATCHDOG
	reti ; ICP1
	reti ; OC1A
	reti ; OC1B
	reti ; OVF1
	reti ; OC0A
	reti ; OC0B
	rjmp Ovf0Isr ; OVF0
	reti ; ACI
	reti ; ADCC
	reti ; ERDY
	reti ; USI_STR
	reti ; USI_OVF
;
; **********************************
;  I N T - S E R V I C E   R O U T .
; **********************************
;
ExtInt0Isr:
  sbi PORTB,PORTB0 ; Setze den Ausgangspin
  ldi rCntH,High(cDelay)
  ldi rCntL,Low(cDelay)
  ldi R16,(1<<CS02)|(1<<CS00) ; Vorteiler = 1024
  out TCCR0B,R16
  ldi R16,1<<TOIE0
  out TIMSK0,R16
  reti
;
Ovf0Isr:
  sbiw rCntL,1
  brne Ovf0IsrReti
  cbi PORTB,PORTB0
  clr R16
  out TCCR0B,R16
Ovf0IsrReti:
  reti
;
; **********************************
;  H A U P T P R O G R A M M   I N I T
; **********************************
;
Main:
.ifdef SPH
  ldi R16,High(RAMEND)
  out SPH,R16
  .endif
  ldi R16,Low(RAMEND)
  out SPL,R16 ; Initiere LSB Stapelzeiger
  ; Init PB0 als Signalausgangspin
  sbi DDRB,DDB0 ; Pin PB0 als Ausgang
  ; Init des INT0-Pins
  sbi PORTB,PB2 ; Pull-up-Widerstand ein
  ldi R16,1<<ISC01 ; Interrupt bei fallender Flanke
  out MCUCR,R16
  ldi R16,1<<INT0
  out GIMSK,R16
  sei ; Enable Interrupt
;
; **********************************
;  P R O G R A M M S C H L E I F E
; **********************************
;
Loop:
	rjmp loop
;
; Ende Quellcode
;
; Copyright information
; .db "(C)2022 by Gerhard Schmidt  " ; Quellcode-lesbar
; .db "C(2)20 2ybG reahdrS hcimtd  " ; Maschinencode-lesbar
;

Wenn Du noch längere Zeiten brauchst: der 16-Bit-TC1 verlängert die Zeiten um das 256-fache, bis mehr als einen Monat lang. Nur einfach ein paar Zeilen im Code ändern und den Interrupt OVF1 verwenden. Das gibt dann 67 Sekunden für cDelay = 1 und das Längste sind 50,9 Tage für cDelay = 0.

8 Zähler

Impulse mit T0 und T1 zaehlen Einige Timer/Counter können auch als Zähler für Impulse dienen. Nur TC0 und TC1 können das, TC2 meistens nicht.

Timer/Counter, die als Zähler eingesetzt werden, haben einen Puls-Eingabepin, der sich "Tn" nennt. Wenn Du In allen Fällen muss die Dauer der Pulse mindestens vier Taktzyklen sein. Das ist der Grund, warum man bei 1 MHz Takt nur maximal 250 kHz zählen kann, während man bei 20 MHz noch 4 MHz hinkriegt.

Aber: erwarten nicht, dass dieses Beispiel korrekt funktioniert. Alle Schalter dieser Welt torkeln, wenn sie gesclossen und geöffnet werden. Und die vier Taktzyklen sind bei weitem nicht lkang genug, um 10 Millisekunden langes Torkeln abzufangen. Verwende also besser ein kleines RC-Netzwerk, um die 10- oder 20-Millisekunden-Torkelphase auszubügeln. Oder halt einen weiteren Timer mit einem Einzelschuss.

&Uuuml;berlauferkennung, Verrgleicher, die COM-Pins und, in 16-Bit-TCs, das Input-Capture funktionieren auch in diesem Modus wie gehabt und können mit Interrupts abgefangen werden.

9 Schlussfolgerungen

Timer/Counter in AVRs sind vielseitige Hardware-Einzelstücke. Man kann sie für jedes noch so kurze wie lange Vergnügen einsetzen, für das man Zeiten braucht oder zählen muss. Egal, ob es einmalig oder wiederkehrend sein soll. Ich verspreche, dass Du niemals wieder langweilige Zählschleifen einsetzt, wenn Du mal den Umgang mit Timern erlernt hast. Und andauernd TCNT lesen wirst Du dann auch nicht mehr tun.

Zum Seitenanfang

©2022 by http://www.avr-asm-tutorial.net