PAPs in Assembler umsetzen
Ist eine Lösung bereits als PAP umgesetzt worden, so muss nun dieser PAP in einem letzten Schritt in ein Assembler-Programm übersetzt werden.
Hierbei ist die Umsetzung umso einfacher, je einfacher Operationen und Verzweigungen in dem PAP sind. Sind z.B. bereits alle Bedingungen atomar
(also keine Und- bzw. Oder-Verknüpfungen enthalten) ist deren Umsetzung deutlich leicher zu bewerkstelligen. Für Operationen gilt das selbe, die
Operation A=10
lässt sich eins zu eins in Assembler umsetzen, während „Schalte Motor eine Position weiter
“
zusätzliches Wissen über den betreffenden Motor (Gleichstrom-, Servo- oder Schrittmotor), die Art der Verschaltung von Motor und µC
(Belegung der Portpins, High-/Low-Active, etc.) u.s.w. erfordert.
Vorbemerkungen
Unterprogrammaufrufe in einem PAP setzt man in Assembler mit
call
Befehlen um. Um den PAP aus
Abbildung 1
in Assembler zu überführen wird aus dem ersten Unterprogrammaufruf
call warte
und aus dem zweiten
call output
.

Abb. 1: PAP-Fragment 1
Bei der Operation „Erhöhe X um 1“ ist die Umsetzung nicht so leicht, wie es auf den ersten Blick scheint. Hier ist zuerst einmal
zu klären, welcher Art X
überhaupt ist. Ist es eine 8-Bit oder eine 16-Bit Ganzzahl, ist es eine in BCD codierte Zahl oder gar eine
Kommazahl oder etwas ganz anderes?
Dem PAP oder der Aufgabenbeschreibung sollte hier entnommen werden können, welcher dieser Fälle zutrifft. Z.B. könnte dem PAP hier ein Kommentar
angefügt werden.
Zusätzlich muss festgelegt sein, wo die Zahl X
abgelegt ist, d.h. ihre Adresse und der Speicher (RAM, SFR, XMem, ...) muss bekannt sein.
Hier wollen wir uns die Zeit nehmen und ein paar alternative Umsetzungen für die Operation „Erhöhe X um 1“ ansehen:
Wir gehen davon aus, dass die Zahl
X
als 8-Bit Zahl im RAM oder SFR liegt und direkt adressierbar ist.
Des weiteren sei für die Zahl ein Name definiert worden, z.B. mit
X DATA 073h
.
Code für eine 8-Bit Zahl:
inc X
Alternativ zum
inc
Befehl kann auch der
add
Befehl verwendet werden. Hierfür muss
X
zuerst in den
Akkumulator transferiert werden:
mov A, X
add A,#1
mov X,A
Ist
X
eine 8-Bit BCD Zahl, so muss zwingend der
add
Befehl verwendet werden. Dies liegt daran, dass eine 8-Bit Zahl
zwei 4-Bit BCD-Stellen beinhaltet. Kommt es zum Übertrag von der hinteren auf die fordere Stelle (z.B. bei 19+1=20), so muss sie nach der Additon
wieder mittels
DA A
in eine gültige BCD-Zahl überführt werden.
Code für eine 8-Bit BCD-Zahl:
mov A, X
add A, #1
da A
mov X,A
Ist
X
eine 16-Bit Zahl, wobei der niederwertige Teil in dem Byte
X0
und das höherwertige Byte in
X1
steht,
so muss man eine 16-Bit Addition verwenden. Hierbei muss zum niederwertigen Byte 1 hinzuaddiert werden und der evtl. auftretende Übertrag
zum höherwertigen Byte hinzuaddiert werden:
mov A, X0
add A, #1
mov X0, A
mov A, X1
addc A, #0
mov X1,A
Sind die Anweisungen im PAP ausreichend genau angegeben ist die Umsetzung von Operationen normalerweise einfach. Bei einem „schlechten“
PAP ist die Umsetzung ungemein schwerer.
Es muss jedoch beachtet werden,
dass man nicht alle Befehle mit allen Parametern ausführen kann. So wird in
Abbildung 2 eine Operation angegeben, in welcher
der Inhalt von Port 0 um 1 Bit nach links rotiert werden soll.

Abb. 1: PAP-Fragment 2
Es ist allerdings nicht möglich
rl P0
zu verwenden, da nur
rl A
möglich ist. Somit muss P0 zuerst in den Akkumulator
kopiert werden, dieser nach links rotiert werden und dann das Ergebnis aus dem Akkumulator wieder in Port 0 zurückgeschrieben werden:
Operationen umsetzen
Operationen beinhalten Zuweisungen, Berechnungen, das Setzen und Löschen von Bits und Initialisierungen. Operationen wie „Warte 10s“
oder „drehe Motor um 90°“ werden hier nicht betrachtet, da diese zuerst in genauere Operationsfolgen umgesetzt werden müssen,
bevor sie in Assembler realisiert werden können.
Zuweisungen
Abbildung 3 zeigt einige mögliche Zuweisungen. Für diese werden im folgenden Assemblerprogramm-Fragmente angegeben und kurz
erwähnt auf was hierbei zu achten ist. In den abgebildeten Zuweisungen ist die Speicherzelle immer genau spezifiziert. Sollte in einem
PAP eine Zuweisung der Form „x=y“ vorkommen muss (falls nicht vorgegeben) den Variablen
x
und
y
zuerst ein freier Speicherplatz zugewiesen werden.

Abb. 3: Bsp. für Zuweisungen
Abbildung 3 a) kann über einen einzelnen
mov
Befehl umgesetzt werden:
mov A, #10h
.
Hierbei ist lediglich zu beachten, dass man den Quelloperand als imediate-adressiert (Konstante) kennzeichnet.
Alternativ kann natürlich auch die Adresse des Akkumulators verwendet werden:
mov 0E0h, #10h
. Ebenso ist es selbstredend möglich die
Konstante
10h
dezimal (#16) oder binär (#10000b) anzugeben.
Die Umsetzung von
Abbildung 3 b) erfolgt über
mov A, P0
oder
mov 0E0h, 080h
, da hier der
Inhalt einer direkt adressierbaren Speicherzelle in eine andere kopiert wird.
Abbildung 3 c) sieht im PAP sehr ähnlich wie a) oder b) aus. Dennoch ist hier ein Fallstrick verborgen: Die Speicherzelle
92h
im internen RAM ist nämlich nicht direkt adressierbar!
Somit muss die Speicherzelle indirekt adressiert werden, die führt z.B. zu folgendem Code:
mov R0, #92h ; Adresse der Speicherzelle in R0 laden
mov @R0, #3 ; Den Wert 3 nach @R0, also in 92h schreiben
Je nach dem Rest des Programms muss der Inhalt von
R0
vor der Zuweisung gesichert und danach wieder hergestellt werden. Ist
der Inhalt von
R0
allerdings im restlichen Programm nicht relevant, so ist dies nicht nötig.
In
Abbildung 3 d) ist die genaue Speicherzelle nicht angegeben, allerdings weißt die Operation darauf hin, dass die
Adresse des Ziels bereits in
R1
steht. Somit ist die Umsetzung mit
mov @R1, P0
möglich.
In
Abbildung 3 e) wird der Inhalt der Speicherzelle
30h
aus dem Erweiterungsspeicher an den
Port 1 kopiert.
Da der Erweiterungsspeicher nur indirekt über
R0
,
R1
oder
DPTR
adressiert werden kann muss zuerst die
Adresse
30h
in eine dieser Speicherzellen geschrieben werden (da
30h
<255 ist sind alle drei möglich). Danach
muss der Wert aus dem Erweiterungsspeicher in den Akkumulator gelesen werden. Zum Schluss wird der Wert vom Akku in P1 kopiert.
mov R0, #30h
movx A, @R0
mov P1, A
Berechnungen umsetzen
Komplexe Terme wie A= P1*#10/(#20+R1) & 0Fh
sind in Assembler nur Stück für Stück umsetzbar. Ist ein PAP bereits so maschinennah, dass
jede Operation nur eine arithmetische oder logische Operation beinhaltet, fällt die Umsetzung leichter.
Den oberen Term könnte man so umsetzen, dass man zuerst P1*#10
berechnet und zwischenspeichert. Ebenso verfährt man mit
#20+R1
. Dann teilt man diese Ergebnise durcheinander und zum Schluss setzt man das bitweise Und um. Diesen Wert muss man ggf. noch
in den Akkumulator kopieren.
Doch auch hier sind selbst einfache Berechnungen nicht immer mit nur einer Assembler-Anweisung erledigt, wie wir im Folgenden sehen werden.

Abb. 4: Bsp. für Berechnungen
Die Operation aus
Abbildung 4 a) ist mittels
add A, #10h
umsetzbar. Natürlich sind auch andere
Umsetzungen denkbar, wie etwa 10 aufeinanderfolgende
inc A
Befehle.
Abbildung 4 b) teilt den Inhalt von Register 1 durch den Inhalt von Port 0. Das Ergebnis wird in
Register 0 gespeichert. Da hier geteilt wird benötigt man den
div
Befehl. Dieser erlaubt jedoch nur die Parameterkombination
AB
. Somit muss zuerst
R1
in
A
und
P0
in
B
gespeichert werden.
Das Divisionsergebnis muss aus dem Akkumulator zum Schluss noch in
R0
kopiert werden.
mov A, R1
mov B, P0
div AB
mov R0, A
Abbildung 4 c) verlangt das Löschen der ersten 3 Bits in R7. Dies ist auf unterschiedlichste Weise zu realisieren.
Die einfachste Lösung in Assembler ist ein logisches Und mit der Maske
#00011111b
. Allerdings kann
R7
nicht mit einer
Konstanten verundet werden. Somit muss hier der Umweg über eine direkt-adressierbare Speicherzelle oder den Akkumulator gegangen werden.
mov A,R7
anl A, #00011111b
mov R7, A
Alternativ könnten die ersten 3 Bits seperat mit
clr
Anweisungen gelöscht werden. Doch auch hier wieder die Stolperstelle, dass
R7
nicht bit-adressierbar ist. Somit wäre eine Lösung:
mov 20h, R7
clr 20h.7
clr 20h.6
clr 20h.5
Weitere Alternativen wären die Verwendung von 3 mal
rlc
gefolgt von 3 mal
rr
vobei das Carry jeweils gelöscht wird.
Auch der Teilungsrest von
R7/00100000b
wäre das gewünschte Ergebnis.
Abbildung 4 d) ist am leichtesten zu lösen mit:
mov A,R1
xrl A, #00000111b
mov R1, A
Die Multiplikation in
Abbildung 4 e) kann entweder mittels
mul AB
oder mit 2 maligem Linksschieben
(
rlc A
) realisiert werden, wobei vor dem Schieben immer das Carry gelöscht werden muss.
In
Abbildung 4 f) ist ein kleiner Fallstrick:
subb A, #10h
subtrahiert zwar
10h
vom
Akkumulator, allerdings wird hier auch das Carry mit abgezogen. Somit muss das Carry vor dem
subb
-Befehl gelöscht werden.
Verzweigungen umsetzen
Gleichheit und Ungleichheit
Bedingungen die den Inhalt von Speicherzellen mit einer Konstanten oder dem Inhalt einer anderen Speicherzelle verleichen, kommen häufig in Verzweigungen vor.
Abbildung 5 zeigt einige Beispiele für derartige Verzweigungen. In den Bildern sind die Übergangsstellen
M0
und
M1
zu sehen.
M1
stellt hierbei den Code direkt nach der Verzweigung dar, wird also erreicht, wenn nicht gesprungen wird.
M0
verweist auf
ein Sprungziel, welches bei erfüllter Bedingung angesprungen wird.

Abb. 5: Verzweigung bei Un-/Gleichheit
Eine generelle Möglichkeit bei Gleichheit zu springen bietet der
jz rel
Befehl, welcher springt, wenn der Akkumulator 0 ist. Um hier mit einer
allgemeinen Zahl zu vergleichen zieht man vor dem Sprungbefehl die Zahl vom Akkumulator ab. Somit steht im Akkumulaor 0, wenn beide Zahlen gleich sind.
Für einen Sprung bei Ungleichheit verwendet man hier den Befehl
jnz rel
, welcher springt wenn derr Akkumulator nicht 0 ist. Durch eine vorhergehende
Subtraktion kann auch hier mit einer beliebigen Zahl verglichen werden.
In
Abbildung 6 sind die PAPs für die Verzweigungen aus
Abbildung 5 so verändert worden, dass sie leicht mit den Befehlen
jz
und
jnz
umgesetzt werden können.

Abb. 6: Verzweigung bei Un-/Gleichheit
Der Assemblercode für die Beispiele aus
Abbildung 6:
a) clr c
subb A, #5
jz M0
M1: ...
M0: ...
b) mov A, 70h
clr c
subb A, #10h
jz M0
M1: ...
M0: ...
c) mov A, R7
jz M0
M1: ...
M0: ...
d) mov A, R0
clr c
subb A, #3
jnz M0
M1: ...
M0: ...
Alternativern für (Un)Gleichheit
Die oben erläuterte Variante funktioniert, ist allerdings etwas umständlich, da die Speicherzelle immer in den Akkumulator kopiert werden muss, dass Carry gelöscht und
eine Subtraktion durchgeführt werden muss, bevor der eigentliche Vergleich durchgeführt werden kann.
Der Befehl cjne
führt einen Vergleich sofort durch und springt bei Ungleichheit. Hier kann der Akkumulator mit einer Speicherzelle oder Konstanten verglichen
werden. Es ist aber auch möglich Register mit Konstanten zu vergleichen und sogar indirekt adressierte Speicherzellen mit Konstanten zu vergleichen.
Somit ist dieser Befehl weit mächtiger als jz
und jnz
. Um bei Gleichheit zu springen muss hier allerdings mehr Aufwand betrieben werden, wie die
Codebeispiele gleich zeigen. Wenn es möglich ist sollten die Codeblöcke so umgeordnet werden, dass immer bei Ungleichheit gesprungen wird.
a) cjne A,#5, M0
jmp M1
M0: ...
M1: ...
b) mov R0, #70h
cjne @R0,#10h, M0
jmp M1
M0: ...
M1: ...
oder
mov R1,70h
cjne R1,#5, M0
jmp M1
M0: ...
M1: ...
c) cjne R7,#0, M0
jmp M1
M0: ...
M1: ...
d) cjne R0,#3, M0
M0: ...
M1: ...
Größer und Kleiner für vorzeichenlose Zahlen
Der Vergleich zwei Zahlen auf größer und kleiner kann auf unterschiedliche Arten erfolgen. Eine Weg dies zu erreichen ist, dass man die Zahlen
voneinander subtrahiert auf überprüft, ob ein Überlauf aufgetreten ist. Ein anderer Weg ist die Verwendung von cjne
welcher diese
Arbeit intern auf gleiche Weise erledigt.
Werden zwei
vorzeichenlosen 8-Bit Zahlen
a
und
b
voneinander subtrahiert, so wird das Carry-Flag gesetzt, wenn
a-b<0
und es wird gelöscht, wenn
a-b>=0
ist. Somit gilt Carry=1, falls
a<b
und Carry=0, wenn
a>=b
.

Abb. 7: Vergleich auf größer/kleiner/gleich
Vergleich von vorzeichenlosen 8-Bit Zahlen mit
subb
Die Zahlen stehen in
70h
und
71h
mov A, 70h
subb A, 71h
jz gleich
jc kleiner
groesser: ...
jmp ende
kleiner: ...
jmp ende
gleich: ...
jmp ende
ende:
Vergleich von vorzeichenlosen 8-Bit Zahlen mit
cjne
Die Zahlen stehen in
70h
und
71h
mov A, 70h
cjne A, 71h, ungleich
gleich: ...
jmp ende
ungleich:
jc kleiner
groesser: ...
jmp ende
kleiner: ...
jmp ende
ende:
Handelt es sich um vorzeichenlose Zahlen, so gibt es viele Spezialfälle, z.B.
A>0
ist hier das selbe wie
A!=0
, da
vorzeichenlose Zahlen nur 0 oder >0 sein können.
Vorzeichenlose 16-Bit Zahlen müssen byteweise betrachtet werden. Das Vorgehen ist allerdings gleich. Ist das höherwertige Byte von
a
kleiner als das höherwertige Byte von b
, so ist a
kleiner b. Ebenso ist a
größer als
b
, falls das höherwertige Byte von a
größer als das von b
ist.
Im Fall, dass die höherwertigen Bytes beider Zahlen gleich sind, entscheidet der Vergleich der niederwertigen Bytes.

Abb. 8: Vergleich auf größer/kleiner/gleich bei 16-Bit Zahlen
Vergleich von vorzeichenlosen 16-Bit Zahlen
Die erste Zahl stehen in
70h
und
71h
(70h ist das niederwertige Byte, 71h das höherwertige)
Die zweite Zahl stehen in
60h
und
61h
(60h ist das niederwertige Byte, 61h das höherwertige)
mov A, 71h
cjne A, 61h, ungleich ; Vergleich der höherwertigen Bytes
hgleich: ; höherwertigen Bytes waren gleich,
; daher werden hier die niederwertigen verglichen
mov A, 70h
cjne A, 60h, ungleich ; Vergleich der niederwertigen Bytes
gleich ...
jmp ende
ungleich:
jc kleiner
groesser: ...
jmp ende
kleiner: ...
jmp ende
ende:
Größer und Kleiner für Zahlen mit Vorzeichen
Um vorzeichenbehaftete Zahlen zu vergleichen muss man berücksichtigen, dass hier das Zweierkomplement zur Darstellung verwendet wird. Ist eine
8-Bit Zahl 1111 1111b
so wäre dies vorzeichenlos der Wert 255. Vorzeichenbehafte ist 1111 1111b
allerdings -1
.
Somit gilt bei vorzeichenlosen Zahlen 1111 1111b>2
und bei vorzeichenbehafteten Zahlen 1111 1111b<2
.
Somit können die Vorgehen zum Vergleich von oben nicht so ohne weiteres auf vorzeichenbehaftete Zahlen übertragen werden.
Vor einem Vergleich von vorzeichenbehafteten Zahlen muss das höchstwertigste Bit invertiert werden. Danach können die Zahlen wie vorzeichenlose
Zahlen verglichen werden.
Abbildung 9 zeigt exemplarisch für 4-Bit Zahlen, dass durch Invertierung des vordersten Bits
die Reihenfolge der Werte wie bei vorzeichenlosen Zahlen ist.
Zweierkomplement | Wert | Zweierkomplement mit invertiertem Vorzeichen |
0111 | 7 | 1111 |
0110 | 6 | 1110 |
0101 | 5 | 1101 |
0100 | 4 | 1100 |
0011 | 3 | 1011 |
0010 | 2 | 1010 |
0001 | 1 | 1001 |
0000 | 0 | 1000 |
1111 | -1 | 0111 |
1110 | -2 | 0110 |
1101 | -3 | 0101 |
1100 | -4 | 0100 |
1011 | -5 | 0011 |
1010 | -6 | 0010 |
1001 | -7 | 0001 |
1000 | -8 | 0000 |
Abb. 9: Zweierkomplement mit invertiertem ersten Bit
sorgt für richtige Anordnung
Vergleich von vorzeichenbehafteten 8-Bit Zahlen
Die Zahlen stehen in
70h
und
71h
mov A, 70h
xrl A, #80h ; vorderstes Bit invertieren
mov B, 71h
xrl B, #80h ; vorderstes Bit invertieren
cjne A, B, ungleich
gleich: ...
jmp ende
ungleich:
jc kleiner
groesser: ...
jmp ende
kleiner: ...
jmp ende
ende:
Vergleich von vorzeichenbehafteter 16-Bit Zahlen
Die erste Zahl stehen in
70h
und
71h
(70h ist das niederwertige Byte, 71h das höherwertige)
Die zweite Zahl stehen in
60h
und
61h
(60h ist das niederwertige Byte, 61h das höherwertige)
mov A, 71h
xrl A, #80h ; vorderstes Bit invertieren
mov B, 61h
xrl B, #80h ; vorderstes Bit invertieren
cjne A, B, ungleich ; Vergleich der höherwertigen Bytes
hgleich: ; höherwertigen Bytes waren gleich,
; daher werden hier die niederwertigen verglichen
mov A, 70h
cjne A, 60h, ungleich ; Vergleich der niederwertigen Bytes
gleich ...
jmp ende
ungleich:
jc kleiner
groesser: ...
jmp ende
kleiner: ...
jmp ende
ende:
Abschlussbeispiel
Der Selektionsort-Algorithmus ist ein (langsames) Sortierverfahren mit 2 inneinander Verschachtelten Schleifen. In der
Abbildung ist sowohl das Struktogramm als auch ein PAP des Algorithmuses dargestellt. Das PAP läßt sich nicht ganz eins
zu eins in Assembler umsetzen.

Abb: Selektionsort
Feld liegt in von 80h bis EFh im internen RAM. Für i wird
R0
und für j
R1
verwendet.
Das Feld enthält vorzeichenlose 8-Bit Zahlen. Neben
R0
und
R1
werden auch
70h
,
71h
und
A
verändert.
Der Assemblercode:
; R0 auf Arrayanfang
mov R0, #80h
loop1:
cjne R0, #0EFh, loop2
sjmp ende
loop2:
; R1 = R0+1
mov 71h, R0
mov R1, 71h
inc R1
; Rumpf der inneren Schleife
rumpf2:
; wenn R1<=0E0h dann zum if
cjne R1, #0F0h, if
; sonst innere Schleife zu Ende
inc R0
jmp loop1
if:
mov A, @R0
mov 70h, @R1
cjne A, 70h, ungl
jmp endif
ungl:
jc endif
; if
mov A, @R0
xch A, @R1
xch A, @R0
endif:
inc R1
jmp rumpf2
ende:
In
SelektionSort.a51 befindet sich der Assembler-Code inklusive einer Array-Initialisierung und unter
SelektionSort.hex das assemblierte Hex-File.