Go to english version.
Copyright (C) 2019 Dr. Thomas Redelberger, redethogmx.de.
Die Information hier wird ohne jegliche Gewährleistung und ohne jegliche Garantie gegeben. Der Code kann im jedem Projekt verwendet werden. Die einzige Bedingung ist, dass mein Name und meine E-Mail-Adresse in einem frei zugänglichen "Credits"-Abschnitt des Projekts und/oder des Produkts erscheinen muss.
AVRASM2 ist ein kompletter Makro-Assembler. Ihm fehlen aber einige nützliche Funktionen:
if ... then ... else
oder switch
-Konstrukte wie in C.
Zusätzlich gibt es einige Dinge, die man in den meisten Projekten braucht, wie:
Ich habe eine Reihe von Makros geschrieben, um diese Dinge abzubilden. Sie können in AVR-Assembler-Projekten verwendet werden. Ich habe dokumentiert, wie man den AVRASM-Assembler für komplexe Projekte einsetzen kann.
Oft muss man ein Register-Paar mit einem 16‑Bit Wert laden, zum Beispiel einer RAM-Adresse:
ldi zl, LOW(address) ldi zh, HIGH(address)
Das Makro ldiw
erlaubt, das kürzer zu schreiben:
ldiw z, address
Das schaut dann so aus, als ob der Prozessor eine ldiw
Instruktion
hat, aber nach der Makro-Ersetzung werden das natürlich wieder
dieselben beiden Prozessor-Instruktionen wie oben.
Es ist nicht zugesichert, dass das RAM eines AVR-Prozessors nach dem Einschalten Null ist, geschweige denn nach einem Reset oder einer anderen Neustart-Bedingung (z.B. "brown-out"). Daher muss man Variablen im RAM meistens ausdrücklich auf Null setzen.
MtrClrMem
löscht ein bis 256 Bytes.
MtrClrMem CONSTANT
AVRASM2 berechnet das verwendete RAM ausschließlich auf Basis der
.byte
Assembler-Anweisung. Das kann man im Assembler-Listing
ausdrücklich genau so lesen. Um aber Variablen mittels Makros
anzulegen ‑ was für objektorientierte Programmierung unerlässlich
ist ‑ benutze ich oft die .org
Assembler-Anweisung.
Indem ich
MtrCheckDSEGspace
ans Ende des Source-Codes setze, prüfe ich auf das verbleibende RAM ab. Das Makro gibt eine Fehlermeldung aus, falls zu viel RAM verbraucht wurde.
If ... Then ... Else
sind in Assembler leicht zu erstellen, Switch
Case-Konstrukte sind dagegen schwierig. Deshalb habe ich einen Weg
gesucht, wie man das mit Assembler-Makros nachbauen kann.
In Assembler möchte ich die gleiche Funktionalität zur Verfügung haben, wie in C:
Ein typischer Anwendungsfall sind einfache Fall-Unterscheidungen, wie z.B. die verschiedenen Status-Register-Fälle beim AVR-TWI-Modul.
Es gibt verschiedene Möglichkeiten, Switch-Konstrukte umzusetzen. Am schnellsten aber auch aufwändigsten sind Sprungtabellen. Die einfachsten sind Abfragen mit Vergleichs- und Sprung-Anweisungen.
Ich habe mich für letzteres entschieden, auch wenn das bei komplexen Fällen langsamer ist, als mit Sprungtabellen.
Es folgt Beispiel-C-Source-Code für ein Switch mit drei Fällen und Default-Zweig: (NA, NB, NC seien symbolische Konstanten, die man zum Beispiel mit dem C-Prä-Prozessor definieren kann).
switch variable { case NA: ...Statements A...; break: case NB: ...Statements B...; break; case NC: ...Statements C...; break; default: ...default case Statements...; }
Eine naive Implementierung in Assembler könnte so aussehen:
cmp r24, NA breq A cmp r24, NB breq B cmp r24, NC breq C ...default case Statements... rjmp ends A: ...Statements A... rjmp ends B: ...Statements B... rjmp ends C: ...Statements C... ; rjmp ends ends:
Diese Implementierung ist einfach und übersichtlich. Sie funktioniert aber nur, solange die Code-Teile durch die breq-Anweisungen erreichbar sind und das sind nur 64 Byte.
Falls der Code länger ist, könnte man schreiben
cmp r24, NA brne PC+2 rjmp A
Aber dadurch verliert man an Geschwindigkeit.
Um das zu vermeiden, könnte man schreiben
cmp r24, NA brne tB ...Statements A... rjmp ends tB: cmp r24, NB brne tC ...Statements B... rjmp ends tC: cmp r24, NC brne default ...Statements C... rjmp ends default: ...default case Statements... ends:
Diese Variante ist meines Erachtens aber viel schlechter zu lesen, besonders, wenn die Code-Stücke länger sind. Denn die Label bezeichnen jetzt nicht mehr die Code-Alternativen, sondern die Vergleichs-Anweisungen.
Es wird noch viel unübersichtlicher, wenn man mehrere Switch-Konstrukte im selben Programm-Teil hat oder gar verschachtelte Switch-Konstrukte.
Also musste eine Makro-Lösung her, die die Übersicht gewährleistet. Für obiges Beispiel sieht meine Lösung so aus:
MtrSwitch Ident cpi r24, NA MtrCaseEq Ident ...Statements A... MtrCaseEqEndBrk Ident cpi r24, NB MtrCaseEq Ident ...Statements B... MtrCaseEqEndBrk Ident cpi r24, NC MtrCaseEq Ident ...Statements C... MtrCaseEqEndBrk Ident ...default case Statements... MtrSwitchEnd Ident
Es gibt hier keine Labels mehr, die den Code unübersichtlich machen. Die Labels werden von den Makros automatisch erzeugt.
Ein Bezeichner (das muss ein Literal sein, hier "Ident") identifiziert das Switch-Konstrukt. Damit sind auch geschachtelte Switch-Konstrukte möglich. Der Bezeichner muss im Source-Code eindeutig sein.