Warteschleifen - Schwerstarbeit für den AVR
Etwas Hardware
Der Aufbau des Lehrgangs Erste Befehle - Mit Assembler das Laufen lernen reicht hier vollkommen aus.
Dem AVR das Warten lehren
In etlichen verschiedenen Situationen ist es notwendig, den AVR in seinen Tatendrang auszubremsen. Wir wollen z.B. eine Leuchtdiode zum blinken bringen. Hierzu muss die Leuchtdiode eingeschaltet werden, danach wieder aus, wieder an usw. So ein Programm können wir problemlos schon schreiben:
.include
"m8def.inc"
Schleife: ldi r16,0b00000000 out PORTD,r16 rjmp Schleife |
Wird nun das Programm gestartet, gibt es lange Gesichter. Es blinkt nichts. Besser gesagt, wir sehen das Blinken nicht da es so schnell geht, dass wir das Blinken nur als Dauerleuchten sehen.
Wir müssen also nach den Ausgabebefehlen ein wenig warten. Wie machen wir dies? Hierzu müssen wir den AVR irgendwie beschäftigen. Nämlich so etwas wie: Warte ein wenig kennt ein Prozessor nicht. Das gängigste Mittel um den AVR zum Warten zu bringen sind Zählschleifen. Wir lassen ihn eine gewisse Anzahl runterzählen. Damit ist der AVR eine bestimmte Zeit beschäftigt und wir bekommen unsere gewünschte Wartezeit. Also bauen wir so etwas in unser Programm ein:
.include
"m8def.inc"
Schleife: ldi r16,255 Warte1: dec r16 brne Warte1 ldi r16,0b00000000 out PORTD,r16 ldi r16 Warte2: dec r16 brne Warte2 rjmp Schleife |
Selbst mit diesen Warteschleifen, sehen wir die LED immer noch Dauerleuchten. Es wäre jetzt interessant zu wissen, wie lange der AVR nun für so eine Schleife benötigt. Hierzu müssen wir erst einmal verstehen, wie diese Schleife genau funktioniert.
ldi r16,255 Warte1: dec r16 brne Warte1 |
Mit dem ldi-Befehl geben wir den Startwert für die Zählschleife vor. In diesem Fall nutzen wir den größtmöglichen Wert, nämlich 255. Anschließend treten wir in die Zählschleife ein, markiert durch die Sprungmarke 'Warte1' bzw. 'Warte2'.
Dann kommt der Befehl dec. Dieser subtrahiert den Inhalt des angegebenen Registers, hier r16, um den Wert 1. Hierbei werden die Flags entsprechend gesetzt. Für uns ist das Z-Flag interessant. Wurde der Wert 0 erreicht, wird Z gesetzt, andernfalls gelöscht.
Dieses Flag wertet der nächste Befehl aus. Nämlich brne. Ist Z gelöscht, springt er zur angegebenen Sprungmarke. Hier folgt anschließend wieder dec. Dieses Spiel setzt sich fort bis der dec-Befehl das Z-Flag setzt und brne somit nicht mehr springt. Die Schleife also verlässt. Wir wissen nun also, dass die Schleife 254 komplett durchlaufen wird und beim 255. mal verlassen.
In der AVR-Assembler-Referenz können wir sehen, wie lange jeder Befehl für die Ausführung braucht. Schreiben wir dies einmal an unsere Schleife:
ldi r16,255 ; 1 x 1 Takt Warte1: dec r16 ; 255 x 1 Takt brne Warte1 ; 254 x 2 Takte und 1 Takt wenn Schleifenende |
Wenn man dies durchrechnet kommen wir auf insgesamt 765 Takte. Bei 1 MHz sind dies gerade einmal 0,000765 Sekunden. Dies ist viel zu wenig um die LED auch nur annähernd blinken zu sehen.
Ein Mantel für die Schleife
Mit einer einzelnen Schleife schaffen wir also keine vernünftigen Zeiten. Wir müssen die Zeit der vorhandenen Schleife durch eine weitere Schleife vervielfachen. Ändern wir das Programm entsprechend ab:
.include
"m8def.inc"
Schleife: ldi r17,195 Warte12: ldi r16,170 Warte11: dec r16 brne Warte11 dec r17 brne Warte12 ldi r16,0b00000000 out PORTD,r16 ldi r17,195 Warte22: ldi r16,170 Warte21: dec r16 brne Warte21 dec r17 brne Warte22 rjmp Schleife |
Für die umgebende Schleife müssen wir ein anderes Register nehmen. Um die Schleifen einmal deutlicher hervorzuheben, einmal eine etwas andere Darstellung:
ldi r17,195 Warte12: ldi r16,170 Warte11: dec r16 brne Warte11 dec r17 brne Warte12 |
Wird nun dieses Programm ausgeführt können wir die Leuchtdiode nun zumindest schon schnell blinken sehen. Wer sich einmal die Mühe macht und die Laufzeit berechnet kommt auf relativ genau 100,04 mS. Die 0,04 mS können wir aber vernachlässigen.
Soll die LED nun mit 1 Hz blinken, müssen wir diese Schleifen noch einmal mit einer Schleife 5x ausführen lassen. Das Ganze sieht dann so aus:
.include
"m8def.inc"
Schleife: ldi r18,5 Warte13: ldi r17,195 Warte12: ldi r16,170 Warte11: dec r16 brne Warte11 dec r17 brne Warte12 dec r18 brne Warte13 ldi r16,0b00000000 out PORTD,r16 ldi r18,5 Warte23: ldi r17,195 Warte22: ldi r16,170 Warte21: dec r16 brne Warte21 dec r17 brne Warte22 dec r18 brne Warte23 rjmp Schleife |
Nach dem assemblieren und dem übertragen des Programms auf den AVR blinkt die LED, wie gewünscht, im 1 Hz Takt.