Impressum
< Umsetzen von Kontrollstrukturen Inhalt Zeitschleifen >

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.
 PAP-Fragment 1
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.
 PAP-Fragment 2
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:
mov A, P0
rl A
mov P0, A

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.
 Bsp. für Zuweisungen
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.
 Bsp. für Berechnungen
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.
Verzweigung bei Un-/Gleichheit
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.
 Verzweigung bei Un-/Gleichheit
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.
 Vergleich auf größer/kleiner/gleich
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.
 Vergleich auf größer/kleiner/gleich bei 16-Bit Zahlen
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.
ZweierkomplementWertZweierkomplement mit invertiertem Vorzeichen
0111 71111
0110 61110
0101 51101
0100 41100
0011 31011
0010 21010
0001 11001
0000 01000
1111-10111
1110-20110
1101-30101
1100-40100
1011-50011
1010-60010
1001-70001
1000-80000
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.
Selektionsort
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.