Pfad:
Home =>
AVR-Überblick =>
Programmiertechniken => SRAM-Verwendung
(This page in English:
)
Programmiertechnik für Anfänger in AVR Assemblersprache
Verwendung von SRAM in AVR Assembler
Alle AVR-Typen verfügen in gewissem Umfang über Statisches RAM (SRAM)
an Bord. Bei sehr einfachen Assemblerprogrammen kann man es sich im Allgemeinen leisten,
auf die Verwendung dieser Hardware zu verzichten und alles in Registern unterzubringen.
Wenn es aber eng wird im Registerbereich, dann sollte man die folgenden Kenntnisse haben,
um einen Ausweg aus der Speicherenge zu nehmen.

SRAM sind Speicherstellen, die im Gegensatz zu Registern nicht direkt in die Recheneinheit
(Arithmetic and Logical Unit ALU, manchmal aus historischen Gründen auch
Akkumulator genannt) geladen und verarbeitet werden können. Ihre Verwendung ist
daher auf den Umweg über ein Register angewiesen. Im dargestellten Beispiel wird
ein Wert von der Adresse im SRAM in das Register R2 geholt (1.Instruktion), irgendwie mit
dem Inhalt von Register R3 verknüpft und das Ergebnis in Register R3 gespeichert
(Instruktion 2). Im letzten Schritt kann der geänderte Wert auch wieder in das SRAM
geschrieben werden (3.Instruktion).
Es ist daher klar, dass SRAM-Daten langsamer zu verarbeiten sind als Daten in
Registern. Dafür besitzt schon der zweitkleinste AVR immerhin 128 Bytes an
SRAM-Speicher. Da passt schon einiges mehr rein als in 32 popelige Register.
Die größeren AVR ab AT90S8515 aufwärts bieten neben den eingebauten
512 Bytes zusätzlich die Möglichkeit, noch externes SRAM anzuschließen.
Die Ansteuerung in Assembler erfolgt dabei in identischer Weise wie internes RAM.
Allerdings belegt das externe SRAM eine Vielzahl von Portpins für Adress- und
Datenleitungen und hebt den Vorteil der internen Bus-Gestaltung bei den AVR wieder
auf.
Zum Seitenanfang
SRAM bietet über das reine Speichern von Bytes an festen Speicherplätzen noch
ein wenig mehr. Der Zugriff kann nicht nur mit festen Adressen, sondern auch mit
Zeigervariablen erfolgen, so dass eine fließende Adressierung der Zellen
möglich ist. So können z.B. Ringpuffer zur Zwischenspeicherung oder
berechnete Tabellen verwendet werden. Das geht mit Registern nicht, weil die immer eine
feste Adresse benötigen.
Noch relativer ist die Speicherung über einen Offset. Dabei steht die Adresse in
einem Pointerregister, es wird aber noch ein konstanter Wert zu dieser Adresse addiert
und dann erst gespeichert oder gelesen. Damit lassen sich Tabellen noch raffinierter
verwenden.
Die wichtigste Anwendung für SRAM ist aber der sogenannte Stack oder Stapel, auf
dem man Werte zeitweise ablegen kann, seien es Rücksprungadressen beim Aufruf von
Unterprogrammen, bei der Unterbrechung des Programmablaufes mittels Interrupt oder
irgendwelche Zwischenwerte, die man später wieder braucht und für die ein
extra Register zu schade ist.
Zum Seitenanfang
Um einen Wert in eine Speicherstelle im SRAM abzulegen, muss man seine Adresse
festlegen. Das verwendbare SRAM reicht von Adresse 0x0060 bis zum jeweiligen Ende des
SRAM-Speichers (beim AT90S8515 ist das ohne externes SRAM z.B. 0x025F).
Mit dem Befehl
STS 0x0060, R1
wird der Inhalt des Registers R1 in die Speicherzelle im SRAM kopiert. Mit
LDS R1, 0x0060
wird vom SRAM in das Register kopiert. Das ist der direkte Weg mit einer festen Adresse,
die vom Programmierer festgelegt wird.
Um das Hantieren mit festen Adressen und deren möglicherweisen späteren
Veränderung bei fortgeschrittener Programmierkunst sowie das Merken der Adresse
zu erleichtern empfiehlt der erfahrene Programmierer wieder die Namensvergabe, wie
im folgenden Beispiel:
.EQU MeineLieblingsSpeicherzelle = 0x0060
STS MeineLieblingsSpeicherzelle, R1
Aber auch das ist noch nicht allgemein genug. Mit
.EQU MeineLieblingsSpeicherzelle = SRAM_START
.EQU MeineZweiteLieblingsSpeicherzelle = SRAM_START + 1
ist die in der Include-Datei eingetragene Adresse der SRAM-Speicherzellen noch
allgemeingültiger angegeben.
Zugegeben, kürzer ist das alles nicht, aber viel leichter zu merken.
Organisation als Datensegment
Bei etwas komplexeren Datenstrukturen empfiehlt sich das Anlegen in einem Datensegment.
Eine solche Struktur sieht dann z. B. so aus:
.DSEG ; das ist der Beginn des Datensegments, die folgenden Einträge organisieren SRAM
.ORG SRAM_START ; an den Beginn des SRAM legen.
;
EinByte: ; ein Label als Symbol für die Adresse einer Speicherzelle
.BYTE 1 ; ein Byte dafuer reservieren
;
ZweiBytes: ; ein Label als Symbol für die Adresse zweier aufeinander folgender Speicherzellen
.BYTE 2 ; zwei Bytes reservieren
;
.EQU Pufferlaenge = 32 ; definiert die Laenge eines Datenpuffers
Buffer_Start: ; ein Label fuer den Anfang des Datenpuffers
.BYTE Pufferlaenge ; die folgenden 32 Speicherzellen als Datenpuffer reservieren
Buffer_End: ; ein Label fuer das Ende des Datenpuffers
;
.CSEG ; Ende des Datensegments, Beginn des Programmsegments
Das Datensegment enthält also ausschließlich Labels, über die die
entsprechenden reservierten Speicherzellen später adressiert werden können,
und .BYTE-Direktiven, die die Anzahl der zu reservierenden Zellen angeben. Ziel der
ganzen Angelegenheit ist das Anlegen einer flexibel änderbaren Struktur für
den Zugriff über Adresssymbole. Inhalte für diese Speicherzellen werden dabei nicht
erzeugt, es gibt auch keine Möglichkeit, den Inhalt von SRAM-Speicherzellen beim
Brennen des Chips zu manipulieren.
Zugriffe auf das SRAM über Pointer
Eine weitere Adressierungsart für SRAM-Zugriffe ist die Verwendung von Pointern,
auch Zeiger genannt. Dazu braucht es zwei Register, die die Adresse enthalten.
Wie bereits in der Pointer-Register-Abteilung
erläutert sind das die Registerpaare X mit XL/XH (R26, R27), Y mit YL/YH (R28, R29)
und Z mit ZL und ZH (R30, R31). Sie erlauben den Zugriff auf die jeweils addressierte
Speicherzelle direkt (z.B. ST X, R1), nach vorherigem Vermindern der Adresse um Eins
(z.B. ST -X,R1) oder mit anschliessendem Erhöhen um Eins (z.B. ST X+, R1). Ein
vollständiger Zugriff auf drei Zellen sieht also etwa so aus:
.EQU MeineLieblingsZelle = 0x0060
.DEF MeinLieblingsRegister = R1
.DEF NochEinRegister = R2
.DEF UndNochEinRegister = R3
LDI XH, HIGH(MeineLieblingszelle)
LDI XL, LOW(MeineLieblingszelle)
LD MeinLieblingsregister, X+
LD NochEinRegister, X+
LD UndNochEinRegister, X
Sehr einfach zu bedienen, diese Pointer. Und nach meinem Dafürhalten genauso
einfach (oder schwer) zu verstehen wie die Konstruktion mit dem Dach in gewissen
Hochsprachen.
Indizierte Zugriffe über Pointer
Die dritte Konstruktion ist etwas exotischer und nur erfahrene Programmierer greifen
in ihrer unermesslichen Not danach. Nehmen wir mal an, wir müssen sehr oft und
an verschiedenen Stellen eines Programmes auf die drei Positionen im SRAM zugreifen,
weil dort irgendwelche wertvollen Informationen stehen. Nehmen wir ferner an, wir
hätten gerade eines der Pointerregister so frei, dass wir es dauerhaft für
diesen Zweck opfern könnten. Dann bleibt bei dem Zugriff nach ST/LD-Muster
immer noch das Problem, dass wir das Pointerregister immer anpassen und nach dem
Zugriff wieder in einen definierten Zustand versetzen müssten. Das ist eklig.
Zur Vermeidung (und zur Verwirrung von Anfängern) hat man sich den Zugriff mit
Offset einfallen lassen (deutsch etwa: Ablage). Bei diesem Zugriff wird das eigentliche
Zeiger-Register nicht verändert, der Zugriff erfolgt mit temporärer Addition
eines festen Wertes. Im obigen Beispiel würde also folgende Konstruktion beim
Zugriff auf die Speicherzelle 0x0062 erfolgen. Zuerst wäre irgendwann das
Pointer-Register zu setzen:
.EQU MeineLieblingsZelle = 0x0060
.DEF MeinLieblingsRegister = R1
LDI YH, HIGH(MeineLieblingszelle)
LDI YL, LOW(MeineLieblingszelle)
Irgendwo später im Programm will ich dann auf Zelle 0x0062 zugreifen:
STD Y+2, MeinLieblingsRegister
Obacht! Die zwei werden nur für den Zugriff addiert, der Registerinhalt von Y
wird nicht verändert. Zur weiteren Verwirrung des Publikums geht diese
Konstruktion nur mit dem Y- und dem Z-Pointer, nicht aber mit dem X-Pointer!
Der korrespondierende Befehl für das indizierte Lesen eines SRAM-Bytes
LDD MeinLieblingsRegister, Y+2
ist ebenfalls vorhanden.
Das war es schon mit dem SRAM, wenn da nicht der Stack noch wäre.
Zum Seitenanfang
Die häufigste und bequemste Art der Nutzung des SRAM ist der Stapel, englisch
stack genannt. Der Stapel ist ein Türmchen aus Holzklötzen. Jedes
neu aufgelegte Klötzchen kommt auf den schon vorhandenen Stapel obenauf, jede
Entnahme vom Turm kann immer nur auf das jeweils oberste Klötzchen zugreifen,
weil sonst der ganze schöne Stapel hin wäre. Das kluge Wort für
diese Struktur ist Last-In-First-Out (LIFO) oder schlichter: die Letzten
werden die Ersten sein.
Zur Verwirrung des Publikums wächst der Stapel bei fast allen Mikroprozessoren
aber nicht von der niedrigsten zu höheren Adressen hin, sondern genau umgekehrt.
Wir könnten sonst am Anfang des SRAMs unsere schönen Datenstrukturen nicht
anlegen.
Einrichten des Stapels
Um vorhandenes SRAM für die Anwendung als Stapel herzurichten ist zu allererst
der Stapelzeiger einzurichten. Der Stapelzeiger ist ein 16-Bit-Zeiger, der als Port
ansprechbar ist. Das Doppelregister heißt SPH:SPL. SPH nimmt das obere Byte der
Adresse, SPL das niederwertige Byte auf. Das gilt aber nur dann, wenn der Chip über
mehr als 256 Byte SRAM verfügt. Andernfalls fehlt SPH und kann/muss nicht verwendet
werden. Wir tun im nächsten Beispiel so, als ob wir mehr als 256 Bytes SRAM haben.
Zum Einrichten des Stapels wird der Stapelzeiger mit der höchsten verfügbaren
SRAM-Adresse bestückt. (Der Stapel oder Turm wächst nach unten, d.h. zu
niedrigeren Adressen hin.)
.DEF MeinLieblingsRegister = R16
LDI MeinLieblingsRegister, HIGH(RAMEND) ; Oberes Byte
OUT SPH,MeinLieblingsRegister ; an Stapelzeiger
LDI MeinLieblingsRegister, LOW(RAMEND) ; Unteres Byte
OUT SPL,MeinLieblingsRegister ; an Stapelzeiger
Die Größe RAMEND ist natürlich prozessorspezifisch und steht
in der Include-Datei für den Prozessor. So steht z.B. in der Datei 8515def.inc
die Zeile
.equ RAMEND =$25F ;Last On-Chip SRAM Location
Die Datei 8515def.inc kommt mit der Assembler-Direktive
.INCLUDE "C:\irgendwo\8515def.inc"
irgendwo am Anfang des Assemblerprogrammes hinzu.
Damit ist der Stapelzeiger eingerichtet und wir brauchen uns im weiteren nicht mehr
weiter um diesen Zeiger kümmern, weil er ziemlich automatisch manipuliert wird.
Die Verwendung des Stapels ist unproblematisch. So lassen sich Werte von Registern
auf den Stapel legen:
PUSH MeinLieblingsregister ; Ablegen des Wertes
Wo der Registerinhalt abgelegt wird, interessiert uns nicht weiter. Dass dabei der
Zeiger automatisch erniedrigt wird, interessiert uns auch nicht weiter. Wenn wir den
abgelegten Wert wieder brauchen, geht das einfach mit:
POP MeinLieblingsregister ; Rücklesen des Wertes
Mit POP kriegen wir natürlich immer nur den Wert, der als letztes mit
PUSH auf den Stapel abgelegt wurde. Wichtig: Selbst wenn der Wert vielleicht
gar nicht mehr benötigt wird, muss er mit Pop wieder vom Stapel! Das Ablegen
des Registers auf den Stapel lohnt also programmtechnisch immer nur dann, wenn
- der Wert in Kürze, d.h. ein paar Befehle weiter im Ablauf, wieder gebraucht
wird,
- alle Register in Benutzung sind und,
- keine Möglichkeit zur Zwischenspeicherung woanders besteht.
Wenn diese Bedingungen nicht vorliegen, dann ist die Verwendung des Stapels ziemlich
nutzlos und verschwendet bloss Zeit.
Stapel zum Ablegen von Rücksprungadressen
Noch wertvoller ist der Stapel bei Sprüngen in Unterprogramme, nach deren
Abarbeitung wieder exakt an die aufrufende Stelle im Programm zurück gesprungen
werden soll. Dann wird beim Aufruf des Unterprogrammes die Rücksprungadresse
auf den Stapel abgelegt, nach Beendigung wieder vom Stapel geholt und in den
Programmzähler bugsiert. Dazu dient die Konstruktion mit dem Befehl
RCALL irgendwas ; Springe in das UP irgendwas
[...] hier geht es normal weiter im Programm
Hier landet der Sprung zum Label irgendwas irgendwo im Programm,
irgendwas: ; das hier ist das Sprungziel
[...] Hier wird zwischendurch irgendwas getan
[...] und jetzt kommt der Rücksprung an den Aufrufort im Programm:
RET
Beim RCALL wird der Programmzähler, eine 16-Bit-Adresse, auf dem Stapel abgelegt.
Das sind zwei PUSHs, dann sind die 16 Bits auf dem Stapel. Beim Erreichen des
Befehls RET wird der Programmzähler mit zwei POPs wieder hergestellt
und die Ausführung des Programmes geht an der Stelle weiter, die auf den RCALL
folgt.
Damit braucht man sich weiter um die Adresse keine Sorgen zu machen, an der der
Programmzähler abgelegt wurde, weil der Stapel automatisch manipuliert wird.
Selbst das vielfache Verschachteln solcher Aufrufe ist möglich, weil jedes
Unterprogramm, das von einem Unterprogramm aufgerufen wurde, zuoberst auf dem Stapel
die richtige Rücksprungadresse findet.
Unverzichtbar ist der Stapel bei der Verwendung von Interrupts. Das sind Unterbrechungen
des Programmes aufgrund von äußeren Ereignissen, z.B. Signale von der
Hardware. Damit nach Bearbeitung dieser äußeren "Störung"
der Programmablauf wieder an der Stelle vor der Unterbrechung fortgesetzt werden kann,
muss die Rücksprungadresse bei der Unterbrechung auf den Stapel. Interrupts
ohne Stapel sind also schlicht nicht möglich.
Fehlermöglichkeiten beim (Hoch-)Stapeln
Für den Anfang gibt es reichlich Möglichkeiten, mit dem Stapeln üble
Bugs zu produzieren.
Sehr beliebt ist die Verwendung des Stapels ohne vorheriges Setzen des Stapelzeigers.
Da der Zeiger zu Beginn bei Null steht, klappt aber auch rein gar nix, wenn man den
ersten Schritt vergisst.
Beliebt ist auch, irgendwelche Werte auf dem Stapel liegen zu lassen, weil die Anzahl
der POPs nicht exakt der Anzahl der PUSHs entspricht. Das ist aber schon
seltener. Es kommt vorzugsweise dann vor, wenn zwischendurch ein bedingter Sprung nach
woanders vollführt wurde und dort beim Programmieren vergessen wird, dass der
Stapel noch was in Petto hat.
Noch seltener ist ein Überlaufen des Stapels, wenn zuviele Werte abgelegt werden
und der Stapelzeiger sich bedrohlich auf andere, am Anfang des SRAM abgelegten Werte
zubewegt oder noch niedriger wird und in den Bereich der Ports und der Register
gerät. Das hat ein lustiges Verhalten des Chips, auch äußerlich, zur
Folge. Kommt aber meistens fast nie vor, nur bei vollgestopftem SRAM.
Zum Seitenanfang
©2002-2010 by http://www.avr-asm-tutorial.net