Pfad: Home => AVR-Überblick => Programmiertechniken => Register

Programmiertechnik für Anfänger in AVR Assemblersprache

Was ist ein Register?

Register sind besondere Speicher mit je 8 Bit Kapazität. Sie sehen bitmäßig daher etwa so aus:
7654 3210

Man merke sich die Numerierung der Bits: sie beginnt immer bei Null.
In einen solchen Speicher passen Zahlen von 0 bis 255 (Ganzzahl ohne Vorzeichen), von -128 bis +127 (Ganzzahl mit Vorzeichen in Bit 7), ein Acht-Bit- ASCII-Zeichen wie z.B. 'A' oder auch acht einzelne Bits, die sonst nix miteinander zu tun haben (z.B. einzelne Flaggen oder Flags).
Das Besondere an diesen Registern (im Gegensatz zu anderen Speichern) ist, dass sie Es gibt 32 davon in jedem AVR. Sie werden mit R0 bis R31 bezeichnet, man kann ihnen mit einer Assemblerdirektive aber auch einen etwas wohlklingenderen Namen verpassen, wie z.B.

.DEF MeinLieblingsregister = R16

(Mehr Assemblerdirektiven gibt es hier). Statt des Registernamens R16 wird dann fürderhin immer der neue Name verwendet. Das könnte also ein schreibintensives Programm werden.

Mit dem Befehl

   LDI MeinLieblingsRegister, 150

was in etwa bedeutet: Lade die Zahl 150 in das Register R16, aber hurtig, (in englisch: LoaD Immediate) wird ein fester Wert oder eine Konstante in mein Lieblingsregister geladen. Nach dem Übersetzen (Assemblieren) ergibt das im Programmspeicher etwa folgendes Bild:

000000 E906

In E906 steckt sowohl der Load-Befehl als auch das Zielregister (R16) als auch die Konstante 150, auch wenn man das auf den ersten Blick nicht sieht. Zum Glück müssen wir uns um diese Übersetzung nicht kümmern, das macht der Assembler.

In einem Befehl können auch zwei Register vorkommen. Der einfachste Befehl dieser Art ist der Kopierbefehl MOV. Er kopiert den Inhalt des einen Registers in ein anderes Register. Also etwa so:

.DEF MeinLieblingsregister = R16
.DEF NochEinRegister = R15
   LDI MeinLieblingsregister, 150
   MOV NochEinRegister, MeinLieblingsregister


Die ersten beiden Zeilen dieses großartigen Programmes sind Direktiven, die ausschließlich dem Assembler mitteilen, dass wir anstelle der beiden Registernamen R16 und R15 andere Benennungen zu verwenden wünschen. Sie erzeugen keinen Code! Die beiden Programmzeilen mit LDI und MOV erzeugen Code, nämlich:

000000 E906
000001 2F01


Der zweite Befehl schiebt die 150 im Register R16 in das Rechenwerk und kopiert dessen Inhalt in das Zielregister R15. MERKE:

Das erstgenannte Register im Assemblerbefehl ist immer das Zielregister, das das Ergebnis aufnimmt!

(Also so ziemlich umgekehrt wie man erwarten würde und wie man es ausspricht. Deshalb sagen viele, Assembler sei schwer zu lernen!)


Zum Seitenanfang

Unterschiede der Register

Schlaumeier würden das obige Programm vielleicht eher so schreiben:

.DEF NochEinRegister = R15
   LDI NochEinRegister, 150


Und sind reingefallen: Nur die Register R16 bis R31 lassen sich hurtig mit einer Konstante laden, die Register R0 bis R15 nicht! Diese Einschränkung ist ärgerlich, ließ sich aber bei der Konstruktion der Assemblersprache für die AVRs wohl kaum vermeiden.

Es gibt eine Ausnahme, das ist das Nullsetzen eines Registers. Dieser Befehl

    CLR MeinLieblingsRegister

ist für alle Register zulässig.

Diese zwei Klassen von Registern gibt es ausser bei LDI noch bei folgenden Befehlen: Rx muss bei diesen Befehlen ein Register zwischen R16 und R31 sein! Wer also vorhat, solche Befehle zu verwenden, sollte ein Register oberhalb von R15 dafür auswählen. Das programmiert sich dann leichter. Noch ein Grund, die Register mittels .DEF umzubenennen: in größeren Programmen wechselt sich leichter ein Register, wenn man ihm einen besonderen Namen gegeben hat.

Zum Seitenanfang

Pointer-Register

Noch wichtigere Sonderrollen spielen die Registerpaare R26/R27, R28/R29 und R30/R31.Diese Pärchen sind so wichtig, dass man ihnen in der AVR-Assemblersprache extra Namen gegeben hat: X, Y und Z. Diese Doppelregister sind als 16-Bit-Pointerregister definiert. Sie werden gerne bei Adressierungen für internes oder externes RAM verwendet (X, Y und Z) oder als Zeiger in den Programmspeicher (Z).

Bei den 16-Bit-Pointern befindet sich das niedrigere Byte der Adresse im niedrigeren Register, das höherwertige Byte im höheren Register. Die beiden Teile haben wieder eigene Namen, nämlich ZH (höherwertig, R31) und ZL (niederwertig, R30). Die Aufteilung in High und Low geht dann etwa folgendermaßen:

.EQU Adresse = RAMEND ; In RAMEND steht die höchste SRAM-Adresse des Chips
   LDI YH,HIGH(Adresse)
   LDI YL,LOW(Adresse)


Für die Pointerzugriffe selbst gibt es eine Reihe von Spezial-Zugriffs-Kommandos zum Lesen(LD=Load) und Schreiben (ST=Store), hier am Beispiel des X-Zeigers:
ZeigerVorgangBeispiele
XLese/Schreibe von der Adresse X und lasse den Zeiger unverändertLD R1,X
ST X,R1
X+Lese/Schreibe von der Adresse X und erhöhe den Zeiger anschließend um EinsLD R1,X+
ST X+,R1
-XVermindere den Zeiger um Eins und lese/schreibe dann erst von der neuen AdresseLD R1,-X
ST -X,R1
Analog geht das mit Y und Z ebenso.

Für das Lesen aus dem Programmspeicher gibt es nur den Zeiger Z und den Befehl LPM. Er lädt das Byte an der Adresse Z in das Register R0. Da im Programmspeicher jeweils Worte, also zwei Bytes stehen, wird die Adresse mit zwei multipliziert und das unterste Bit gibt jeweils an, ob das untere oder obere Byte des Wortes im Programmspeicher geladen werden soll. Also etwa so:

   LDI ZH,HIGH(2*Adresse)
   LDI ZL,LOW(2*Adresse)
   LPM


Nach Erhöhen des Zeigers um Eins wird das zweite Byte des Wortes im Programmspeicher gelesen. Da die Erhöhung des 16-Bit-Speichers um Eins auch oft vorkommt, gibt es auch hierfür einen Spezialbefehl für Zeiger:

   ADIW ZL,1
   LPM


ADIW heisst soviel wie ADdiere Immediate Word und kann bis maximal 63 zu dem Wort addieren. Als Register wird dabei immer das untere Zeigerregister angegeben (hier: ZL). Der analoge Befehl zum Zeiger vermindern heisst SBIW (SuBtract Immediate Word). Anwendbar sind die beiden Befehle auf die Registerpaare X, Y und Z sowie auf das Doppelregister R24/R25, das keinen eigenen Namen hat und auch keinen Zugriff auf RAM- oder sonstige Speicher ermöglicht. Es kann als 16-Bit-Wert optimal verwendet werden.

Wie bekommt man aber nun die Werte, die ausgelesen werden sollen, in den Programmspeicher? Dazu gibt es die DB- und DW-Anweisungen für den Assembler. Byteweise Listen werden so erzeugt:

.DB 123,45,67,78 ; eine Liste mit vier Bytes
.DB "Das ist ein Text. " ; eine Liste mit einem Text

Auf jeden Fall ist darauf achten, dass die Anzahl der einzufügenden Bytes pro Zeile geradzahlig sein muss. Sonst fügt der Assembler ein Nullbyte am Ende hinzu, das vielleicht gar nicht erwünscht ist.
Das Problem gibt es bei wortweise organisierten Tabellen nicht. Die sehen so aus:

.DW 12345,6789 ; zwei Worte

Statt der Konstanten können selbstverständlich auch Labels (Sprungadressen) eingefügt werden, also z.B. so:

Label1:
[... hier kommen irgendwelche Befehle...]
Label2:
[... hier kommen noch irgendwelche Befehle...]
Sprungtabelle:
.DW Label1,Label2


Beim Lesen per LPM erscheint übrigens das niedrigere Byte der 16-Bit-Zahl zuerst!

Und noch was für Exoten, die gerne von hinten durch die Brust ins Auge programmieren: Die Register sind auch mit Zeigern lesbar und beschreibbar. Sie liegen an der Adresse 0000 bis 001F. Das kann man nur gebrauchen, wenn man auf einen Rutsch eine Reihe von Registern in das RAM kopieren will oder aus dem RAM laden will. Lohnt sich aber erst ab 5 Registern.

Zum Seitenanfang

Empfehlungen zur Registerwahl

  1. Register immer mit der .DEF-Anweisung festlegen, nie direkt verwenden.
  2. Werden Pointer-Register für RAM u.a. benötigt, R26 bis R31 dafür reservieren.
  3. 16-Bit-Zähler oder ähnliches realisiert man am besten in R24/R25.
  4. Soll aus dem Programmspeicher gelesen werden, Z (R30/31) und R0 dafür reservieren.
  5. Werden oft konstante Werte oder Zugriffe auf einzelne Bits in einem Register verwendet, dann die Register R16 bis R23 dafür vorzugsweise reservieren.
  6. Für alle anderen Anwendungsfälle vorzugsweise R1 bis R15 verwenden.


Zum Seitenanfang

©2002 by http://www.avr-asm-tutorial.net