AVR-Tutorial: Vergleiche

Aus der Mikrocontroller.net Artikelsammlung, mit Beiträgen verschiedener Autoren (siehe Versionsgeschichte)
Wechseln zu: Navigation, Suche

Vergleiche und Entscheidungen sind in jeder Programmiersprache ein zentrales Mittel, um den Programmfluss abhängig von Bedingungen zu kontrollieren. In einem AVR spielen dazu vier Komponenten zusammen:

  • Vergleichsbefehle,
  • die Flags im Statusregister,
  • bedingte Sprungbefehle,
  • andere Befehle, die die Flags im Statusregister beeinflussen, wie z. B. die meisten arithmetischen Funktionen.

Der Zusammenhang ist dabei folgender: Die Vergleichsbefehle führen einen Vergleich durch, zum Beispiel zwischen zwei Registern oder zwischen einem Register und einer Konstanten. Das Ergebnis des Vergleiches wird in den Flags abgelegt. Die bedingten Sprungbefehle werten die Flags aus und führen bei einem positiven Ergebnis den Sprung aus. Besonders der erste Satzteil ist wichtig! Den bedingten Sprungbefehlen ist es nämlich völlig egal, ob die Flags über Vergleichsbefehle oder über sonstige Befehle gesetzt wurden. Die Sprungbefehle werten einfach nur die Flags aus, wie auch immer diese zu ihrem Zustand kommen.

Flags

Die Flags sind Bits im Statusregister SREG. Ihre Aufgabe ist es, das Auftreten bestimmter Ereignisse, die während Berechnungen eintreten können, festzuhalten. Speicherbefehle (LD, LDI, ST, MOV, …) haben auf dem AVR grundsätzlich keinen Einfluss auf das Statusregister. Will man den Inhalt eines Registers explizit testen (z. B. nach dem Laden aus dem SRAM), so kann man hierfür den TST-Befehl verwenden.


Bits im SREG
I T H S V N Z C

Carry (C)

Das Carry-Flag hält fest, ob es bei der letzten Berechnung einen Über- oder Unterlauf gab. Aber Achtung: Nicht alle arithmetischen Befehle verändern tatsächlich das Carry-Flag. So haben z. B. die Inkrementier- und Dekrementierbefehle keine Auswirkung auf dieses Flag.

Zero (Z)

Das Zero-Flag hält fest, ob das Ergebnis der letzten 8-Bit-Berechnung gleich 0 war oder nicht.

Negative (N)

Spiegelt den Zustand des höchstwertigen Bits (Bit 7) der letzten 8-Bit-Berechnung wider. In 2er-Komplement-Arithmetik bedeutet ein gesetztes Bit 7 eine negative Zahl, das Bit kann also dazu genutzt werden um festzustellen, ob das Ergebnis einer Berechnung im Sinne einer 2er-Komplement-Arithmetik positiv oder negativ ist.

Overflow (V)

Dieses Bit wird gesetzt, wenn bei einer Berechnung mit 2er-Komplement-Arithmetik ein Überlauf (Unterlauf) stattgefunden hat. Dies entspricht einem Überlauf von Bit 6 ins Bit 7.

Der Übertrag, der bei der Addition/Subtraktion von Bit 6 auf Bit 7 auftritt, zeigt daher – wenn er vorhanden ist – an, dass es sich hier um einen Überlauf (Overflow) des Zahlenbereichs handelt und das Ergebnis falsch ist. Das ist allerdings nicht der Fall, wenn auch der Übertrag von Bit 7 nach Bit 8 (Carry) aufgetreten ist. Daher ist das Overflow-Flag die XOR-Verknüpfung aus dem Übertrag von Bit 6 nach Bit 7 und dem Carry.

Beispiele für die Anwendung des V-Flags finden sich in saturierter Arithmetik.

Signed (S)

Das Signed-Bit ergibt sich aus der Antivalenz (exklusives Oder) der Flags N und V, also S = N XOR V. Mit Hilfe des Signed-Flags können vorzeichenbehaftete Werte miteinander verglichen werden. Ist nach einem Vergleich zweier Register S=1, so ist der Wert des ersten Registers kleiner dem zweiten (in der Signed-Darstellung). Damit entspricht das Signed-Flag gewissermaßen dem Carry-Flag für Signed-Werte. Es wird hauptsächlich für „Signed“-Tests benötigt. Daher auch der Name.

Half Carry (H)

Das Half-Carry-Flag hat die gleiche Aufgabe wie das Carry Flag, nur beschäftigt es sich mit einem Überlauf von Bit 3 nach Bit 4, also dem Übertrag zwischen dem oberen und dem unteren Nibble. Wie beim Carry-Flag gilt, dass das Flag nicht durch Inkrementieren bzw. Dekrementieren ausgelöst werden kann. Das Haupteinsatzgebiet ist der Bereich der BCD-Arithmetik (binär codierte Dezimalzahlen), bei der jeweils 4 Bits eine Stelle einer Dezimalzahl repräsentieren.

Transfer (T)

Das T-Flag ist kein Statusbit im eigentlichen Sinne. Es steht dem Programmierer als 1-Bit-Speicher zur Verfügung. Der Zugriff erfolgt über die Befehle Bit Load (BLD), Bit Store (BST), Set (SET) und Clear (CLT) und wird sonst von keinen anderen Befehlen beeinflusst. Damit können Bits von einer Stelle schnell an eine andere kopiert oder getestet werden.

Interrupt (I)

Das Interrupt-Flag fällt hier etwas aus dem Rahmen; es hat nichts mit Berechnungen zu tun, sondern steuert, ob Interrupts im Controller zugelassen sind (siehe AVR-Tutorial: Interrupts).

Vergleiche

Um einen Vergleich durchzuführen, wird intern eine Subtraktion der beiden Operanden vorgenommen. Das eigentliche Ergebnis der Subtraktion wird allerdings verworfen, es bleibt nur die neue Belegung der Flags übrig, die in weiterer Folge ausgewertet werden kann.

CP – Compare

Vergleicht den Inhalt zweier Register miteinander. Prozessorintern wird dabei eine Subtraktion der beiden Register durchgeführt. Das eigentliche Subtraktionsergebnis wird allerdings verworfen, das Subtraktionsergebnis beeinflusst lediglich die Flags.

CPC – Compare with Carry

Vergleicht den Inhalt zweier Register, wobei das Carry-Flag in den Vergleich mit einbezogen wird. Dieser Befehl wird für Arithmetik mit großen Variablen (16 oder 32 Bit) benötigt. Siehe AVR-Tutorial: Arithmetik.

CPI – Compare Immediate

Vergleicht den Inhalt eines Registers mit einer direkt angegebenen Konstanten. Der Befehl ist nur auf die Register r16…r31 anwendbar.

Bedingte Sprünge

Die bedingten Sprünge werten immer bestimmte Flags im Statusregister (SREG) aus. Es spielt dabei keine Rolle, ob dies nach einem Vergleichsbefehl oder einem sonstigen Befehl gemacht wird. Entscheidend ist einzig und allein der Zustand des abgefragten Flags. Die Namen der Sprungbefehle wurden allerdings so gewählt, daß sich im Befehlsnamen die Beziehung der Operanden direkt nach einem Compare-Befehl widerspiegelt. Zu beachten ist auch, daß die Flags nicht nur durch Vergleichsbefehle verändert werden, sondern auch durch arithmetische Operationen, Schiebebefehle und logische Verknüpfungen. Da diese Information wichtig ist, ist auch in der bei Atmel/Microchip erhältlichen Übersicht über alle Assemblerbefehle bei jedem Befehl angegeben, ob und wie er Flags beeinflusst. Ebenso ist dort eine kompakte Übersicht aller bedingten Sprünge zu finden. Beachten muss man jedoch, dass die bedingten Sprünge maximal 64 Worte weit springen können.

Bedingte Sprünge für vorzeichenlose Zahlen

BRSH – Branch if Same or Higher
Der Sprung wird durchgeführt, wenn das Carry-Flag (C) nicht gesetzt ist. Wird dieser Branch direkt nach einer CP-, CPI-, SUB- oder SUBI-Operation eingesetzt, so findet der Sprung dann statt, wenn der erste Operand größer oder gleich dem zweiten Operanden ist. BRSH ist identisch mit BRCC (Branch if Carry Cleared).
BRLO – Branch if Lower
Der Sprung wird durchgeführt, wenn das Carry-Flag (C) gesetzt ist. Wird dieser Branch direkt nach einer CP-, CPI-, SUB- oder SUBI-Operation eingesetzt, so findet der Sprung dann statt, wenn der erste Operand kleiner dem zweiten Operanden ist. BRLO ist identisch mit BRCS (Branch if Carry Set).

Bedingte Sprünge für vorzeichenbehaftete Zahlen

BRGE – Branch if Greater or Equal
Der Sprung wird durchgeführt, wenn das Signed-Flag (S) nicht gesetzt ist. Wird dieser Branch direkt nach einer CP-, CPI-, SUB- oder SUBI-Operation eingesetzt, so findet der Sprung dann und nur dann statt, wenn der erste Operand größer oder gleich dem zweiten Operanden ist.
BRLT – Branch if Less Than
Der Sprung wird durchgeführt, wenn das Signed-Flag (S) gesetzt ist. Wird dieser Branch direkt nach einer CP-, CPI-, SUB- oder SUBI-Operation eingesetzt, so findet der Sprung dann und nur dann statt, wenn der erste Operand kleiner als der zweite Operand ist.
BRMI – Branch if Minus
Der Sprung wird durchgeführt, wenn das Negativ-Flag (N) gesetzt ist, das Ergebnis der letzten Operation also negativ war.
BRPL – Branch if Plus
Der Sprung wird durchgeführt, wenn das Negativ-Flag (N) nicht gesetzt ist, das Ergebnis der letzten Operation also positiv war (einschließlich null).

Sonstige bedingte Sprünge

BREQ – Branch if Equal
Der Sprung wird durchgeführt, wenn das Zero-Flag (Z) gesetzt ist. Ist nach einem Vergleich das Zero-Flag gesetzt, lieferte die interne Subtraktion also 0, so waren beide Operanden gleich.
BRNE – Branch if Not Equal
Der Sprung wird durchgeführt, wenn das Zero-Flag (Z) nicht gesetzt ist. Ist nach einem Vergleich das Zero-Flag nicht gesetzt, lieferte die interne Subtraktion also nicht 0, so waren beide Operanden verschieden.
BRCC – Branch if Carry Flag is Cleared
Der Sprung wird durchgeführt, wenn das Carry-Flag (C) nicht gesetzt ist. Dieser Befehl wird oft für Arithmetik mit großen Variablen (16 oder 32 Bit) bzw. im Zusammenhang mit Schiebeoperationen verwendet. BRCC ≡ BRSH
BRCS – Branch if Carry Flag is Set
Der Sprung wird durchgeführt, wenn das Carry-Flag (C) gesetzt ist. Die Verwendung ist sehr ähnlich zu BRCC. BRCS ≡ BRLO

Selten verwendete bedingte Sprünge

BRHC – Branch if Half Carry Flag is Cleared
Der Sprung wird durchgeführt, wenn das Half-Carry-Flag (H) nicht gesetzt ist.
BRHS – Branch if Half Carry Flag is Set
Der Sprung wird durchgeführt, wenn das Half-Carry-Flag (H) gesetzt ist.
BRID – Branch if Global Interrupt is Disabled
Der Sprung wird durchgeführt, wenn das Interrupt-Flag (I) nicht gesetzt ist.
BRIE – Branch if Global Interrupt is Enabled
Der Sprung wird durchgeführt, wenn das Interrupt-Flag (I) gesetzt ist.
BRTC – Branch if T Flag is Cleared
Der Sprung wird durchgeführt, wenn das T-Flag nicht gesetzt ist.
BRTS – Branch if T Flag is Set
Der Sprung wird durchgeführt, wenn das T-Flag gesetzt ist.
BRVC – Branch if Overflow Cleared
Der Sprung wird durchgeführt, wenn das Overflow-Flag (V) nicht gesetzt ist.
BRVS – Branch if Overflow Set
Der Sprung wird durchgeführt, wenn das Overflow-Flag (V) gesetzt ist.

Beispiele

Entscheidungen

In jedem Programm kommt früher oder später das Problem, die Ausführung von Codeteilen von irgendwelchen Zahlenwerten, die sich in anderen Registern befinden, abhängig zu machen. Sieht beispielsweise die Aufgabe vor, daß Register r18 auf 0 gesetzt werden soll, falls im Register r17 der Zahlenwert 25 enthalten ist und in allen anderen Fällen soll r18 auf 123 gesetzt werden, dann lautet der Code:

    cpi     r17, 25         ; vergleiche r17 mit der Konstante 25
    brne    nicht_gleich    ; wenn nicht gleich, dann mach bei nicht_gleich weiter
    ldi     r18, 0          ; hier stehen nun Anweisungen für den Fall,
                            ; dass R17 gleich 25 ist
    rjmp    weiter          ; meist will man den anderen Zweig nicht durchlaufen, darum der Sprung
nicht_gleich:
    ldi     r18, 123        ; hier stehen nun Anweisungen für den Fall,
                            ; dass R17 ungleich 25 ist
weiter:                     ; hier geht das Programm weiter

In ähnlicher Weise können die anderen bedingten Sprungbefehle eingesetzt werden, um die üblicherweise vorkommenden Vergleiche auf Gleichheit, Ungleichheit, „größer als“, „kleiner als“ zu realisieren.

Schleifenkonstrukte

Ein immer wiederkehrendes Muster in der Programmierung ist eine Schleife. Die einfachste Form einer Schleife ist die Zählschleife. Dabei wird ein Register von einem Startwert ausgehend eine gewisse Anzahl erhöht, bis ein Endwert erreicht wird.

    ldi     r17, 10         ; der Startwert sei in diesem Beispiel 10
loop:
                            ; an dieser Stelle stehen die Befehle, welche innerhalb der Schleife
                            ; mehrfach ausgeführt werden sollen

    inc     r17             ; erhöhe das Zählregister
    cpi     r17, 134        ; mit dem Endwert vergleichen
    brne    loop            ; und wenn der Endwert noch nicht erreicht ist,
                            ; wird bei der Marke loop ein weiterer Schleifendurchlauf ausgeführt

Sehr oft ist es auch möglich, das Konstrukt umzukehren. Anstatt von einem Startwert aus zu inkrementieren genügt es, die Anzahl der gewünschten Schleifendurchläufe in ein Register zu laden und dieses Register zu dekrementieren. Dabei kann man von der Eigenschaft der Dekrementieranweisung Gebrauch machen, das Zero-Flag (Z) zu beeinflussen. Ist das Ergebnis des Dekrements gleich 0, so wird das Zero-Flag gesetzt, welches wiederum in der nachfolgenden BRNE-Anweisung für einen bedingten Sprung benutzt werden kann. Das vereinfacht die Schleife und spart eine Anweisung sowie einen Takt Ausführungszeit.

    ldi     r17, 124        ; Die Anzahl der Wiederholungen in ein Register laden
loop:
                            ; an dieser Stelle stehen die Befehle, welche innerhalb der Schleife
                            ; mehrfach ausgeführt werden sollen

    dec     r17             ; Schleifenzähler um 1 verringern, dabei wird das Zero-Flag beeinflusst
    brne    loop            ; wenn r17 noch nicht 0 geworden ist -> Schleife wiederholen

Literatur