Pfad: Home => AVR-Überblick => Programmiertechniken => SRAM-Verwendung    (This page in English: Flag EN) Logo

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.

Was ist SRAM?

SRAM-Umwege

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

Wozu kann man SRAM verwenden?

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

Wie verwendet man SRAM?

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

Verwendung von SRAM als Stack

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.

Verwendung des Stapels

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