MezData-Logo Creative Commons License 467 :AVR: Adressierung von Daten bei AVR Mikrokontrollern

Schlüsselwörter: Atmel, AVR, Assembler, TGIT, Mikrokontroller, Adressierung

Einführung Indirektes Adressieren

QR-Code

Auf YouTube mit Erklärungen ansehen..

 

Im Browser ohne Ton durchklicken...

(Mit Pfeiltasten steuern)

Atmel-RISC-8Bit Speicherkonzept am Beispiel des ATtiny2313

Der Speicher ist gemäß der Harvard-Architektur in einen Programm- und einen Datenspeicher unterteilt. Der Programmspeicher ist wortweise (16 Bit) organisiert, d.h. Den 2 KiByte Flash-Speicher stehen nur 1 Ki Adressen $000..$3FF gegenüber. Der Datenspeicher ist byteweise organisiert:

  • $00 .. $1F sind die Register R0 bis R31
  • $20 .. $5F ist den I/O Komponenten (Ports, Timer, UART, usw.) zugeordnet
  • $60 .. $DF sind 128 Byte SRAM

Anmerkung: Das EEPROM (ohne Abbildung) wird über einen besonderen Mechanismus angesprochen, es gibt spezielle Befehle..

Wichtig: Zur Vereinfachung und zum Einsparen von Programmspeicher werden den Registern und I/O Adressen besondere Kurz-Zugriffe zugeordnet.

Beispiel: PORTB hat innerhalb des I/O Blocks die Adresse $18, bezogen auf den geammten Datenbereich die Adresse $38.
D.h. für manche Daten-Adressen gibt es zwei Zugriffsmöglichkeiten.

Praktisch werden die symbolischen Namen wie R16 oder PIND verwendet, somit muss man sich um die genauen Adressen keinen Kopf machen, sie sind in der jeweiligen *.inc Datei vordefiniert.

Direkte Adressierung im RAM-Bereich

Register Direkt, einfach Register Direkt, mit zwei Registern
Beispiel: inc R16 ; erhöhe Register 16 um eins
Man beachte die Anzahl der Bits für die Kodierung des Operanden.
Beispiel: add R16, R17 ; R16 <- R16 + R17
Die Befehle sind meist genau 16 Bit lang.
I/O Direkt Daten Direkt
Beispiel: in R16, PIND ; Lade PIND in Register 16
6Bit -> 64 Port-Adressen!
Beispiel: lds R16, 0x60 ; Lade Inhalt des ersten SRAM-Bytes in R16
Dieser Befehl braucht zwei Wörter Platz wegen der 16 Bit SRAM Adresse
(es gibt auch grössere Controller)

Indirekte Daten-Adressierung im RAM-Bereich (SRAM)

Die Adresse des Operanden steht nicht direkt im Befehl sondern in Register (bzw. in zwei Registern, da ein 8Bit-Register für 16Bit-Adresse zu wenig ist).
Wozu ist das praktisch? Betrachte folgende Gedankenreise, C wird in Assembler umgesetzt wir spielen Compiler:

Wir möchten das SRAM als Datenspeicher nutzen und es beginne mit Adresse $60 = 0x60

char a = 5;

Der Compiler merkt sich in einer Tabelle den Bezeichner (engl. Identifier) a und legt fortan für ihn die Adresse $60 fest, d.h. der Compiler assoziiert mit a die Adresse $60. Somit übersetzt er:

ldi R16, 5
sts $60, R16

Hierbei handelt es sich noch um direkte Adressierung.

Nun möchten wir zusätzlich ein char-Array mit dem Text "ABI" erzeugen:

char s[] = "ABI";

Der Text passt nun nicht mehr in ein Byte, er ist vielmehr eine Folge von Buchstaben, die mit '\0', der 0 terminiert ist, s zeigt also auf einen Speicherbereich mit mehreren Bytes:

Worin besteht nun der Unterschied? Wie kann man auf das 'B' zugreifen? Wenn s so wie a wäre könnte man nur auf das 'A' zugreifen, daher brauchen wir eine andere Konstruktion: s muss variabler werden, z.B. ein Register:

Also assoziieren wir mit s ein Register, da unser 8Bit-Controller nur 8Bit-Register besitzt, von der Architektur aber mehr als 256 (Daten)RAM-Adressen vorgesehen sind benötigen wir zwei Register für den Zeiger auf die Adresse.

Der so adressierte Speicher wird nun nicht mehr direkt sondern indirekt über Register angesprochen.

Die Entwickler des AVR haben dafür drei Registerpaare vorgesehen: (X=R27,R26; Y=R29,R28; Z=R31,R30)

X
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0
R27 = high Byte R26 = low Byte
Daten Indirekt Mögliche Realisierung von char s[] = "ABI";
In das Register X alias R27 und R26 muss $61 geladen werden:
ldi R27,0
ldi R26, $61

Oder vornehmer:

ldi R27, high($61) ; ist 0
ldi R26, low($61) ; ist $61

Nun noch die Daten reinschaufeln:

ldi R16, 'A'
st X, R16

So, nun noch das X um 1 erhöhen für das 'B', das würde nun aber aufwändig werden, erst das R26, bedenke den möglichen Überlauf, das Carry-Bit und dann das R27... Viel zu umständlich dafür gibt es schon bessere Befehle:

Daten Indirekt mit Inkrement und Dekrement

Daten Indirekt mit Post-Inkrement Daten Indirekt mit Prä-Dekrement
Das bessere Daten reinschaufeln:
ldi R16, 'A'
st X+, R16
ldi R16, 'B'
st X+, R16
usw...
Gibt es in allen 4 Variationen Prä-Post-Inkrement-Dekrement..

Schau in Deine Formelsammlung RTFM!

Daten Indirekt mit Displacement

Daten Indirekt mit Displacement
Bei komplexeren Datenstrukturen z.B. Listen von Records bzw. Objekten wäre es praktisch zu der Basisadresse des Objekts (Zeiger, Pointer auf Recotd bzw. Objekt) ein bestimmtes Feld bzw. Attribut ansprechen zu können. Der Versatz der Feld- bzw. Attributadresse wird auch als "Displacement" bezeichnet.
Beachte: Displacement hat nur 6Bit -> 0 .. 64

Beispiel für Array of Integer mit 16Bit:

int a,b[] = {1000,2000,3000}; // a zeigt auf $60, b zeigt auf $80
char i = 1; // i ist R20
a = b[i];

Die Adresse b[i] lässt sich mit Basisadresse b berechnen: b[i] = b + i*2, somit die Zuweisung a = b[i]:

ldi R29, high($80)
ldi R28, low($80)
lsl R20; nach links schieben ist i*2 nehmen
add R28, R20; b+ i*2 da es keinen Überlauf gibt kann man sich Rest sparen
ld R16, Y; hol das  low-Byte
sts $60, R16
ld R16, Y+1; hol das high-Byte
sts $61, R16

Zugriff auf Programmspeicher

Programm-Speicher-Zugriff auf Konstanten
Im Programm-Speicher können Konstanten abgelegt werden mit der Assembler-Direktive .db. Mit dem LPM-Befehl kann ein Byte aus dem Programmspeicher via Z-Register in ein Register kopiert werden. Da der Programmspeicher 16 Bit organisiert ist wird mit dem LSB des Z-Registers zwichen dem High- und Low-Byte einer Programm-Speicher-Adresse unterschieden.
ldi R31, high(kzeiger) ; High-Byte in Z
ldi R30, low(kzeiger) ; Low-Byte in Z
lpm R16, Z+ ; lade erste Konstante
; usw.
kzeiger: .db "ABI",0 ; die Konstante
Programm-Speicher-Zugriff mit Post-Inkrement
Siehe oben.
Direkte Sprünge auf Programm-Adressen mit JMP und CALL
Long Jump und Long Call, sind für grosse Sprünge sinnvoll 22-Bit Adressen.

Indirekte Sprünge

Indirekte Sprünge auf Programm-Adressen mit IJMP und ICALL
Praktisch für Case-Anweisungen, bzw. Sprung-Tabellen.
Relative Sprünge auf Programm-Adressen mit RJMP und RCALL
Relativer Sprung