Byteoperationen, Logik und Arithmetik
Bevor wir diesen Themenkomplex bearbeiten möchte ich ein weiteres Register ins Spiel bringen, nämlich das
Statusregister SREG.
Während wir uns bisher diverse Steuerregister (z.B. DDRx) immer als Byte angesehen haben, so müssen wir das SREG (und noch viele andere) bitweise betrachten. So besitzt also jedes der 8 Bit des Statusregisters eine eigene Funktion und wird von verschiedenen Befehlen beeinflusst, von anderen herangezogen.
Stellen wir uns das SREG zur Vereinfachung als "Einmerker" vor, quasi als Knoten im Taschentuch. Kommt bei einer Berechnung etwas bestimmtes heraus machen wir uns einen Knoten an einer bestimmten Stelle. Beim 8085 und vermutlich anderen hiess das SREG übrigens Flag-Register, die einzelnen Bits hiessen Flags (Flaggen... Fähnchen).
Damit das Ganze ein wenig klarer wird betrachten wir mal ein Bit dieses Statusregisters, nämlich das Bit1, namentlich das "Zero-Flag".
Ergibt z.B. eine Rechenoperation den Wert 0x00, so wird das Z-Flag gesetzt. Dies wiederum kann von anderen Befehlen interpretiert und ausgewertet werden, dazu ein Beispiel im Vorgriff:
Code: Alles auswählen
sub r16, r17 ; Inhalt von r17 von r16 abziehen und Ergebnis nach r16 schreiben
breq Ende ; Springe zum Label Ende wenn das Zeroflag gesetzt ist
Dieses Beispiel beginnt mit einer Subrtraktion zweier Register. Steht in beiden das gleiche drin ist das Ergebnis 0x00 und das Zeroflag wird gesetzt. Der unmittelbar danach folgende Sprungbefehl (das Thema kommt noch) frägt das Z-Flag ab und springt zum angegebenen Label, wenn Z gesetzt ist.
Eine Beschreibung des SREG und deren Bits findet Ihr im
Datenblatt auf Seite 11. Wenn Ihr Euch in der
Befehlsübersicht z.B. die Seite 152 anschaut, da geht es um den Befehl SUB aus meinem Beispiel. Bei jedem Befehl steht dabei, welche Bits des SREG dieser Befehl beeinflusst.
Mit dem Wissen um das Statusregister kommen wir nun zur
Arithmetik und damit zu ein paar Befehlen, die wir schon gesehen haben, ADD und Sub.
Wahnsinnig viel gibt es dazu an und für sich nicht zu sagen, mit ADD werden die Inhalte zweier Register addiert, mit SUB eben subrtahiert. Wichtig ist, dass das Zielregister zuerst genannt werden muss, "add r16, r17" könnte man auch so schreiben: r16=r16+r17. Gleiches gilt auch beim Befehl SUB "sub r16, r17" entspricht r16=r16-r17.
Interessant ist, dass es für die Subtraktion auch einen Befehl mit Konstante gibt, für das Addieren leider nicht. So wird mit "subi r16, 0x05" der Wert 0x05 vom Inhalt des Registers r16 abgezogen. Zum Addieren braucht man leider zwei Register.
Erwähnenswert und evtl. auch wichtig sind die Flags, die durch Add, Sub und Subi beeinflusst werden, ganz wichtig hier die Flags "Zero", "Carry" und "Sign". Nachdem wir das Z-Flag bereits behandelt haben, schauen wir kurz auf das C-Flag. Dieses wird gesetzt, wenn ein Übertrag stattfindet, es handelt sich im Prinzip um ein neuntes Bit für den eben ermittelten Registerinhalt. So würde z.B. nach einer Addition von 0xFF und 0x01 ein Übertrag stattfinden, das C-Flag würde gesetzt. Im Übrigen würde, auch wenn im Register damit 0x00 stünde, das Z-Flag geöscht werden, das Ergebnis ist ja nicht 0

.
Das Sign-Flag zeigt das Vorzeichen an. Ist das Ergebnis einer Subtraktion negativ wird das S-Flag gesetzt.
Wenn wir schon so schön beim Rechnen sind sollten wir nun zu zwei einfacheren und weit öfter benutzten Befehlen kommen. Auch wenn man es vermutlich nicht glauben mag, add und sub werden wir vermutlich in diesem Workshop eher selten bis gar nicht mehr benötigen.
Will man ein Register einfach nur um 1 hochzählen oder um 1 zurückzählen kann man sich die Verwendung weiterer Register und Konstanten sparen. Zum
Inkrementieren und Dekrementieren gibt es eigene und vor allem übersichtliche Befehle:
INC R
DEC R
Während z.B. "inc r16" beim Aufruf zum Inhalt von r16 1 dazuzählt zieht "dec r16" den Wert 1 ab. INC und DEC finden wir in fast allen Zähl- und Zeitschleifen und die werden wir noch sehr oft zu sehen bekommen. Als Beispiel dafür eine einfaches Endloszählwerk:
Code: Alles auswählen
ldi r16, 0x00 ; Register 16 löschen
Zählwerk:
inc r16 ; r16 inkrementieren
out Portb, r16 ; r16 ausgeben
rjmp Zählwerk ; Springe zum Beginn des Zählwerks
Hier wird also r16 mit 0 vorgeladen und bei jedem Schleifendurchlauf eines dazuaddiert, das Ganze endlos. Falls Ihr das in Euere Boards programmiert werdet Ihr nicht viel sehen, ausser 8 leuchtenden LEDs. Das Zählen geht nämlich zu schnell für das menschliche Auge
Wenn ich jetzt behaupte, dass das Thema Arithmetik damit abeschlossen ist, dann würde ich lügen. Wir haben das eine oder andere Flag vernachlässigt und es gibt auch noch weitere Befehle, die wir gar nicht angesprochen haben. Aus zumindest meiner persönlichen Praxis aber reicht das bisher besprochene. Ich selbst habe mir übrigens vor einiger Zeit die Befehlsliste ausgedruckt und einfach mal so erforscht, was es so alles gibt. Damit habe ich den einen oder anderen durchaus interessanten Befehl gefunden, der z.B. auch im Tutorial nicht angesprochen ist. Also, auch wenn es zur Arithmetik sicher noch Seiten zu schreiben gibt, wir wechseln damit zu einem ähnlichen Feld, der Logik
Vorweg möchte ich noch die drei elementaren
logischen Verknüpfungen ansprechen, da wären also UND, ODER und EXclusivODER.
Wir stellen uns mal jeweils einen Baustein mit zwei Ein- und einem Ausgang vor und nennen den erstmal UND-Gatter:
Damit der Ausgang "1" wird müssen Eingang 1 UND Eingang 2 "1" sein. In einer Logiktabelle sieht das dann so aus:
E E A
0 0 0
0 1 0
1 0 0
1 1 1
Beim ODER-Gatter reicht es, wenn einer der Eingänge "1" ist, es können aber auch beide "1" sein, damit der Ausgang "1" wird.
E E A
0 0 0
0 1 1
1 0 1
1 1 1
Das EXclusiv-ODER verlangt für eine "1" am Ausgang dafür, dass nur einer der beiden Eingänge "1" ist:
E E A
0 0 0
0 1 1
1 0 1
1 1 0
Hier haben wir jeweils 2 Bit miteinander verknüpft, man kann das natürlich auch mit Bytes machen. Als Beispiel mal eine UND-Verknüpfung zweier Bytes:
0b00001111
0b00111100
=
0b00001100
Beide Bytes mit ODER verknüpft:
0b00001111
0b00111100
=
0b00111111
Oder mit XOR:
0b00001111
0b00111100
=
0b00110011
Evtl. stellt sich jetzt die Frage, wozu man das braucht und wenn ich behaupte, dass man das fast ständig braucht wird der eine oder andere vielleicht ungläubig schauen
In erster Linie werden wir die logischen Verknüpfungen zum Maskieren von Registern benötigen. Klingt komisch, ist aber so und damit es verständlicher wird hier ein Praxisbeispiel:
Wir lesen den kompletten PortD ein und wissen, dass wir nur an PD2 und PD3 ein Taster angeschlossen ist. Zwar haben wir unsere PullUps gesetzt, dennoch könnte sich ja ein Signal durch einen der unbelegten Pins durchschmuggeln und unser Ergebnis verfälschen. Ausserdem will ich, dass alle LEDs, ausser die den Tastern zugewiesenen, dauerhaft aus sind...
Dazu muss ich die Bits 2 und 3 maskieren, sinnigerweise mit UND:
0bxxxxxxxx
0b00001100
=
0b0000xx00
Ihr seht, dass mit der Maske 0b00001100 alle Bits im Register gelöscht werden, ausser Bit 2 und 3, welche ihren Zustand behalten.
Will ich alle nicht benötigten Bits setzen, dann muss ich eine negative Maske mit ODER über das Byte legen:
0bxxxxxxxx
0b11110011
=
0b1111xx11
Was wir hier in der Therorie sehr ausführlich durchgekaut haben schaut in der Praxis des Assemblers wieder recht einfach aus. Wir können entweder zwei Register miteinander logisch verknüpfen oder wir maskieren ein Register mit einer Konstante:
AND r16, r17
legt r17 als UND-Maske über das Register r16, maskiert also den Inhalt von r16 mit dem Inhalt von r17
OR r16, r17
macht das gleiche, nur halt mit ODER
EOR r16, r17
ermöglicht eine Abfrage, welche Bits bei den beiden Registern unterschiedlich sind.
Meistens geht es beim Maskieren aber um eine feste, vielleicht auch nur für den Programmteil feste Bitfolge, die darübergelegt werden soll. Damit wir hier keine Register verschwenden müssen gibt es die Kommandos auch mit Konstante:
ANDI r16, 0b00001111
Die Bits 4-7 in Register 16 werden gelöscht, Bit0-3 bleiben unberührt
ORI r16, 0x02
Bit 0 und 1 werden gesetzt, der Rest bleibt unverändert.
Erinnert Ihr Euch an die letzte
Aufgabe? Eine Lösung könnte sein, das Register, welches zum Einlesen verwendet wurde, zu maskieren:
Hauptprogramm:
in r16, pind ; Eingaberegister C nach r16
andi r16, 0b00001100 ; Bit 2 und 3 maskieren
out portb, r16 ; Ausgabe von r16 nach Port B
rjmp Hauptprogramm ; Endlosschleife
Auch wenn die Aufgabe noch kommt, Ihr könnt gerne mit dieser Maske experimentieren. Macht ein OR daraus, spielt Euch gerne mit EOR.
Kommen wir nun zum dritten Begriff der Überschrift, den
Byteoperationen. Eigentlich ist der Begriff Käse, letztendlich handelt der ganze Beitrag von "Byteoperationen" aber für die folgende fällt mir kein besserer Überbegriff ein:
Es kommt mitunter vor, dass es notwendig ist, dass wir Bits innerhalb eines Registers verschieben müssen. Das Thema S88 wurde ja schonmal angesprochen, das könnte so funktionieren: Ich lese die Zustände von 8 Eingangspins ein und schiebe die Inhalte zur Seite aus dem Register. Beim ersten "Schubs" bekomme ich den Zustand von Eingang 1, beim nächsten den von Eingang 2 usw. "Schieberegister" ist hier der passende Begriff und wie wir hier herumschieben wollen wir uns mal in der Praxis ansehen:
Aus welchem Grund auch immer hängen unsere Taster an PD2 und PD3. Wenn ich mit diesen beiden Tastern jedoch die Zahlen 0x00-0x03 darstellen will muss ich die eingelesenen Bits um zwei Stellen nach rechts schieben.
Dies funktioniert mit dem Befehl "ROR", also Rotate Right oder in die andere Richtung "ROL" Rotate Left, gefolgt vom Register, welches rotiert werden soll. Pro ROR (oder ROL) Kommando werden die Bits um eine Stelle verschoben.
Angenommen, unser Register r16 hat folgenden Inhalt:
0b00001100
jetzt kommt der Befehl ror r16
0b00000110
dann kommt noch einmal ror r16
0b00000011
Wir können oben genanntes Beispiel wie folgt ergänzen:
Hauptprogramm:
in r16, pind ; Eingaberegister C nach r16
andi r16, 0b00001100 ; Bit 2 und 3 maskieren
ror r16 ; Registerinhalt um eine Stelle nach rechts schieben
ror r16 ; Registerinhalt um eine Stelle nach rechts schieben
out portb, r16 ; Ausgabe von r16 nach Port B
rjmp Hauptprogramm ; Endlosschleife
Wenn wir nun in die heilige
Fibel der Befehle auf Seite 119 schauen stellen wir fest, dass ich etwas verschwiegen habe. Der Befehl lautet "Rotate Right through Carry" oder eben "Rotate Left through Carry". Es wird also das Carryflag im SREG mit eingebunden, und was da passier möchte ich mit einem Beispiel belegen. Wir nehmen wieder ein blebiebiges Register, das etwas abgesetzte neunte Bit in jeder Zeile ist das Carryflag. Ich verzichte der Übersicht wegen auf die korrekte Schreibweise, bitte verfolgt das gesetzte Bit, sowie das C-Flag nach jedem Rotate-Befehl:
00000100 x
ror
00000010 0
ror
00000001 0
ror
00000000 1
ror
10000000 0
ror
01000000 0
Der Registerinhalt wird also, durch das C-Flag, im Kreis geschoben, mit ROL dreht sich das natürlich in die andere Richtung.
Kommen wir nun zur Aufgabe für die nächsten 2 oder auch 3 Wochen:
Wir lesen unsere beiden Taster lowaktiv an PD2 und PD3 ein.
Ich möchte ein Programm, dass diese beiden Taster mit einem EXOR logisch auswertet und das Ergebnis an PB0 (also die erste LED) ausgibt.
LED1 darf also nur leuchten wenn einer der beiden Taster gedrückt ist. Sind beide gedrückt oder beide nicht gedrückt bleibt die LED dunkel.
Bitte überlegt Euch genau die Schritte, die zu machen sind. Wir werden mit einer Kopie des Registers arbeiten müssen, dies geht mit dem Befehl MOV (z.B. mov r17, r16).
Natürlich stehe ich gerne für Fragen zur Verfügung, diese am besten hier im Forum. Die Ergebnisse dann aber bitte als Mail, hier bitte entweder den Text oder das ASM-File.
Viel Spass,
Thomas
PS: Da es heute zeitlich gut geklappt hat habe ich das, was ich am Wochenende machen wollte, um zwei Tage vorgeschoben. Das bereitet mir ein freues Wochenende

und Euch mehr Zeit
