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"

Start:
    ldi     r16,0xFF
    out     DDRD,r16

Schleife:
    ldi     r16,0b00000001
    out     PORTD,r16

    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"

Start:
    ldi     r16,0xFF
    out     DDRD,r16

Schleife:
    ldi     r16,0b00000001
    out     PORTD,r16

    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"

Start:
    ldi     r16,0xFF
    out     DDRD,r16

Schleife:
    ldi     r16,0b00000001
    out     PORTD,r16

    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"

Start:
    ldi     r16,0xFF
    out     DDRD,r16

Schleife:
    ldi     r16,0b00000001
    out     PORTD,r16

    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.

 

 

Zurück zur Auswahlseite            Zur Hauptseite