Kleiner Assembler-Kurs

  Befehlsliste   Was ist ein Assembler   Bits und Bytes   Zahlenspiele   Adressierung und Konstanten
Das Erlernen der Assemblersprache ist die ultimative Entscheidung zum erfolgreichen Programmieren eines Microcontrollers. Keine Hochsprache bietet mehr Kontrolle über die Maschine, bessere Performance oder effizientere Ressourcennutzung. Diese Seite bietet eine kleine Anleitung für die ersten Schritte in Assembler für 8051-kompatible Controller. Voraussetzung ist die rudimentäre Kenntnis einer beliebigen Programmiersprache, da grundlegende Kenntnisse über Schleifen, Variablen und Verzweigungen als bekannt angenommen werden.
2051 in der Zange

Befehlsliste

Befehle zum Datentransfer
MOV MOVC SWAP XCH PUSH POP
Unbedingte Sprünge
AJMP LJMP
Bedingte Sprünge
JB JNB JBC JC JNC JZ JNZ CJNE DJNZ JMP
Unterprogramm-Aufrufe
ACALL LCALL RET RETI
Mathematik-Befehle
INC DEC ADD ADD ADDC SUBB MUL DIV
Logische Operationen
CLR SETB CPL ANL ORL XRL RR RRC RL RLC
Keine Zuordnung
NOP

Was ist ein Assembler?

Compiler transformieren ein Hochsprachen-Programm, also einen Quelltext, enthalten in einer einfachen Textdatei, in ein Maschinenprogramm, das die Recheneinheit direkt ausführen kann. Dabei wird der Quelltext analysiert und die komplexen Hochsprachenbefehle in die viel einfacheren Maschinensprache umgesetzt, ohne den Programmierer dabei mit Details zu konfrontieren oder Eingriffsmöglichkeiten zu gestatten.

Im Gegensatz zu Hochsprachen-Programmen besteht ein Assemblerprogramm nicht aus komplexen, mächtigen Befehlen, sondern aus einfachen Mnemonics genannten Kürzeln, die eine direkte Entsprechung in der Maschinensprache des Rechenwerks besitzen.

Darum wird ein Assemblerprogramm bei der Umwandlung in Maschinensprache nicht analysiert und transformiert, sondern im Wesentlichen nur mit einer simplen Umsetzungstabelle in Maschinensprache überführt. Dieser Prozess ist so einfach und direkt, dass man ihn notfalls sogar per Hand ausführen könnte. Das Mnemonic 'NOP' beispielsweise wird in Maschinensprache mit der Zahl 90h kodiert, der Assembler ersetzt also einfach NOP durch 90h.

Dadurch hat der Assembler-Programmierer die volle Kontrolle über die Maschine, ohne den Zwischenschritt über einen Compiler. Das kann auch ein Nachteil sein: es gibt keine Ausreden mehr für abstürzende Programme, da kein Compiler mehr Fehler einbauen könnte, man ist höchst selbst dafür verantwortlich :-)
zurück zum Anfang

Bits und Bytes

Bits sind auch bei Microcontrollern die kleinste darstellbare Informationseinheit. Sie besitzen zwei Zustände: 0 und 1. Werden Bits auf die Ausgänge des Controllers geschrieben, so entsprechen 0V und zumeist 5V diesen beiden Zuständen. Jeweils 8 dieser Bits können zu einem Byte zusammengefasst werden. Damit sind dann Zahlen von 0 bis 28-1, also 0 bis 255, dargestellt werden. Um größere Zahlen abbilden zu können, müssen dann mehrere dieser Bytes logisch zusammengefasst werden. 2 Byte oder 16 Bit können schon Zahlen von 0 bis 2562-1 bzw. 216-1 oder 65535 darstellen. Dieses Zusammenfassen mehrerer Bytes wird von Hochsprachen wie C oder BASIC automatisch erledigt, in Assembler ist dies Aufgabe des Programmierers. Dafür ist es in Assembler aber auch leicht möglich, für die jeweilige Aufgabe optimale Algorithmen zu verwenden und somit die Performance im Vergleich zu C- oder gar BASIC-Programmen um ein Vielfaches zu steigern und gleichzeitig den dafür erforderlichen Speicherplatz sehr gering zu halten. So kennt C üblicherweise keinen 24 Bit-Datentyp, obwohl er an vielen Stellen gut zu gebrauchen ist.
zurück zum Anfang

Zahlenspiele

In der Programmiererei werden verschiedene Zahlensysteme angewendet. Dies hat nicht den Sinn, eventuell mitlesende Kollegen zu verwirren, sondern die Arbeit einfacher zu gestalten, da sich manche Zusammenhänge durch das richtige Zahlenformat leichter erschließen.

Über das dezimale Zahlensystem ist nicht viel zu sagen - es ist jedem seit der Grundschule geläufig. Auch der Assembler kann damit umgehen. Wenn eine Zahl keinen nachgestellten Buchstaben als Anhängsel hat, dann ist damit

eine Zahl des Dezimalsystems gemeint.

Das binäre Zahlenformat besitzt die Basis 2. Daher werden nur die Ziffern 0 und 1 zur Darstellung beliebiger Werte verwendet. Dies funktioniert völlig analog zum gewohnten dezimalen Zahlensystem: Ziffern, die eine Stelle weiter links stehen, haben eine um eine Potenz höhere Wertigkeit als ihr rechter Nachbar. Der Unterschied zum Dezimalsystem besteht nun darin, dass diese Potenz die Basis 2 und nicht wie gewohnt 10 hat.



Eine 21 des Dezimalsystems kann daher so in das Binärsystem umgerechnet werden:
		21 = 2x101+ 1x100
		   = 20   + 1
		   = 16   + 0    + 4    + 0    + 1
		   = 1x24 + 0x23 + 1x22 + 0x21 + 1x20
		   = 10101b
		

Das Binärsystem ist also von der Aussage her völlig identisch mit dem Dezimalsystem - beides sind nur Zahlen. Warum also sollte man sich umgewöhnen, wenn es sich mit Dezimalzahlen viel gewohnter rechnen läßt? Das Binärsystem hat einen ganz einleuchtenden Vorteil, wenn man einzelne Bits gezielt manipulieren möchte. Muss man beispielsweise den externen Interrupt 1 einschalten und dazu die Bits 2 und 7 im IE-Byte setzen, so kann man entweder mühsam ausrechnen, dass man den Wert 132 nach IE schreiben muss, oder einfach 10000100b verwenden. Damit Binärzahlen von anderen Zahlenformaten unterschieden werden können, stellt man ihnen ein kleines 'b' nach.

Das Hexadezimalsystem ist ebenso wie das Binärsystem ein Zahlenformat mit einer anderen Potenz als Basis: der 16. Weil die gewohnten Ziffern von 0 bis 9 nicht mehr ausreichen, um alle Zahlen abzubilden, geht es im Hexadezimalsystem nach der 9 mit A-B-C-D-E-F weiter.

Um Hexadezimalzahlen von anderen Zahlenformaten sowie Symbolen abzugrenzen, stellt man Hexadezimalzahlen eine '0' voran, sofern die erste Ziffer im Intervall von A bis F liegt, und ein kleines 'h' nach. Würde man auf die vorangestellte 0 immer verzichten, käme es zu Verwechslungen mit Labeln oder Assemblersymbolen, die ja schließlich auch aus Buchstaben und Zahlen bestehen dürfen, nur eben nicht mit einer Ziffer von 0 bis 9 beginnen.

Das Hexadezimalsystem hat dann Vorteile, wenn man ein Byte als zwei Hälften (in Fachkreisen 'Nibbles' genannt) betrachten will: in diesem Falle steht jede Hexadezimalziffer für 4 Bit. Wenn man hexadezimal 12h angibt, erkennt man mit ein wenig Übung sofort, dass in dieser Zahl die Bits 4 und 1 gesetzt sind, bzw. im 1. Nibble das 2. Bit und im 2. Nibble das 1. Bit. Aus der dezimalen 18, die die Entsprechung von 12h ist, ist das nur durch eine Rechnung herauszufinden.
zurück zum Anfang



Die Umrechnung der 21 in das Hexadezimalsystem erfolgt analog zur Umrechnung in das Binärsystem, nur mit anderen Faktoren:
		21 = 2x101+ 1x100
		   = 20   + 1
		   = 16   + 5 
		   = 1x161+ 5x160 
		   = 15h
		

Adressierung und Konstanten

Der Assembler für Microcontroller-Dialekte kennt mehrere Möglichkeiten, um Zahlen anzugeben bzw. Speicherstellen zu adressieren, die alle sorgfältig unterschieden werden müssen:

  • Konstanten
    werden mit einem # davor gekennzeichnet.
    #231, #0E7h oder #11100111b sind also Konstanten.

  • Referenzen auf Adressen
    werden ebenso wie Konstanten mit einem # davor angegeben. #Acc ergibt also nicht den Inhalt des A-Registers, sondern die Adresse 0E0h, an der Acc in der SFR steht.
    Auch die Adressen von Sprungzielen können auf diese Weise angegeben werden: #Label ist die Adresse des Sprungziels mit dem Namen 'Label:'.

  • Direkte Adressierung
    Die direkte Adressierung geschieht durch Angabe der Nummer des gewünschten Bytes oder über ein vorher im Assembler definiertes Symbol. Um die Speicherstelle 10 anzusprechen, genügt es also, einfach die Nummer 10 im Programmquelltext anzugeben. Symbol EQU 10 hingegen belegt den Namen Symbol mit der Nummer 10 und ermöglicht dadurch später im Programmquelltext durch die Angabe von Symbol die Speicherstelle 10 zu referenzieren. Für den Assembler ist es dabei völlig gleichgültig, ob eine Adresse als Nummer oder über den Umweg eines Symbols bzw. einer Ersatzvariable angegeben wird. Der Quelltext wird aber sehr viel übersichtlicher, wenn man den Bytes, mit denen man arbeitet und rechnet, vorab aussagekräftige Namen gibt. Zudem braucht man so nur eine Stelle im Quelltext zu ändern, wenn man statt des Bytes 10 nun das Byte 11 benutzen möchte, da der Symbolname unabhängig von dessen Wert bestehen bleibt.

  • Indirekte Adressierung
    ist nur mit den Registern R0, R1 und DPTR und nur bei wenigen Befehlen möglich. Wird @R0 oder @R1 angegeben, so wird damit nicht der Registerinhalt, sondern der Inhalt der Speicherstelle, deren Nummer im Register steht, angesprochen. Enthält R0 beispielsweise 10 und steht in der Speicherstelle 10 der Wert 38, so referenziert @R0 den Wert 38. Mit dieser Adressierungsart können Tabellen im RAM des Controllers gebildet werden, die dann beispielsweise über eine Zählschleife abgefragt werden können.

  • Basisrelative Adressierung
    Diese Adressierung addiert zwei Werte, um dann wie bei der indirekten Adressierung die endgültige Speicherstelle zu ermitteln. Beim MCS-51 kompatiblen steht sie nur in der Form @A+DPTR und @A+PC zur Verfügung und wird nur für die Adressierung des ROM's benötigt. Das RAM kann ja mit 128 Bytes (+ 128 Bytes SFR) bereits über nur ein Byte adressiert werden, für das größere ROM würde ein Byte allein aber nicht mehr ausreichen.


zurück zum Anfang
Startseite Seite vor
Erik Buchmann
EMail an: Owner@ErikBuchmann.de