AVR-Tutorial: Vergleiche
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.
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
- AVR Instruction Set Manual: Übersicht aller AVR-Befehle mit Angaben zur Beeinflussung der Statusbits