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 4 Komponenten zusammen:

  • Vergleichsbefehle
  • die Flags im Statusregister
  • bedingte Sprungbefehle
  • andere Befehle, die die Flags im Statusregister beeinflussen, wie zb 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 Konstante. 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 zb 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 0 war oder nicht.

Negative (N)

Spiegelt den Zustand des höchstwertigen Bits (Bit 7) der letzten 8-Bit-Berechnung wieder. In 2-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 2-Komplement Arithmetik positiv oder negativ ist.

Overflow (V)

Dieses Bit wird gesetzt, wenn bei einer Berechnung mit 2-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 den Ü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 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 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, 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 durchgeführt. 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/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 alleine 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 wiederspiegelt. 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 dieses Information wichtig ist, ist auch in der bei Atmel 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 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 (einschieß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 grossen Variablen (16/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 beispielweise die Aufgabe vor, daß Register r18 auf 0 gesetzt werden soll, wenn im Register r17 der Zahlenwert 25 enthalten ist und in allen anderen Fällen soll r18 auf 123 gesetzt werden, dann lautet der Code

<avrasm>

   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 </avrasm>

In ähnlicher Weise können die anderen bedingten Sprungbefehle eingesetzt werden, um die üblicherweise vorkommenden Vergleiche auf Gleichheit, Ungleichheit, Größer, Kleiner 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.

<avrasm>

   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 Zaehlregister
   cpi     r17, 134        ; mit dem Endwert vergleichen
   brne    loop            ; und wenn der Endwert noch nicht erricht ist
                           ; wird bei der Marke loop ein weiterer Schleifendurchlauf ausgeführt

</avrasm>

Sehr oft ist es auch möglich das Konstrukt umzudrehen. 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 0, so wird das Zero Flag (Z) gesetzt, welches wiederum in der nachfolgenden BRNE Anweisung für einen bedingen Sprung benutzt werden kann. Das vereinfacht die Schleife und spart eine Anweisung sowie einen Takt Ausführungzeit.

<avrasm>

   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

</avrasm>

Literatur

AVR Instruction Set