Assembler für Conrad C-Control

Im Conrad C-Control leistet ein Prozessor Motorola 68HC05B6 seinen Dienst. Dies ist keine vollständige Beschreibung des Prozessors, sondern soll als Arbeitsgrundlage bei der Programmierung des Conrad C-Control in Assembler dienen. Es erklärt die Grundlagen der Programmierungin Assembler, alle Register, Befehle und die wichtigsten Adressen. Einige ausführliche Dokumente findet man im Motorola-Produkt-Katalog für den 68HC05B6.

Die Assembler-Programmierung des C-Control ist nur eingeschränkt möglich, denn die mitgelieferte im ROM gespeicherte Firmware kontrolliert selbst die Verarbeitung von Interrupts. Darum ist uns die Interupt-Verarbeitung verschlossen. Eine in der Assembler-Programmierung verbreitete Technik ist de Modifizierung des Codes währen der Laufzeit, dies ist auf dem C-Control nicht sinnvoll, weil das Programm in einem nicht änderbaren Speicherbereich steht.

Die beste Möglichkeit Assembler zu lernen, ist mit Hilfe dieses Dokuments zu versuchen, bestehende Assembler-Programme zu verstehen, z.B. mein Programm zur Steuerung von Servos.

Dieses Dokument stellt meinen momentanen Wissensstand dar. Weitere Hinweise und Anregungen sind willkommen. Bitte sende sie an Kontaktformular.

Inhalt

Grundlagen
Einbettung
Register
Adressen
Adressierungsarten
Anweisungs-Tabelle
Compiler-Anweisungen
Lade- und Speicher-Anweisungen
Arithmetische und logische Anweisungen
Schiebe-Anweisungen
Vergleichs-Anweisungen
Bit-Anweisungen
Verzweigungs-Anweisungen
Unterprogramm-Anweisungen
Andere Anweisungen

Grundlagen

Ein Bit ist die kleinste Informationseinheit. Sie kann den Wert Falsch = 0 (Spannung ausgeschaltet) oder Wahr = 1 (Spannung eingeschaltet) annehmen.

Acht Bits werden zu einem Byte zusammengefaßt. In einem Byte werden Zahlenwerte im binären Zahlensystem gespeichert. Im folgenden wird das Zahlensystem durch eine tiefgestellte Ziffer angezeigt. 2 ist das Binärsystem und 10 ist unser Dezimalsystem. Die niedrigste rechte Stelle hat den Wert 20 = 110 und die rechteste Stelle den Wert 27 = 2*2*2*2*2*2*2 = 12810.
Beispiel: 000101012 = 24 + 22 + 20 = 32 + 4 + 1 = 3710.
Das Byte kann dann Werte zwischen 0 (alle Bits = 0) und 255 (alle Bits = 1) annehmen.

Die Darstellung erfolgt oft auch im Hexadezimalsystem. Dabei werden Ziffern von 0 bis 15 verwendet, die je in vier Bits, also einem Halbbyte gespeichert werden können. Die Ziffern größer neun werden als Buchstaben dargestellt.
Beispiel: 7B16 = 7*1610 + 11*110 = 12310.

Insgesamt ergibt sich folgende Tabelle.

DezimalBinärHexadezimal
01000002016
11000012116
21000102216
31000112316
41001002416
51001012516
61001102616
71001112716
81010002816
91010012916
101010102A16
111010112B16
121011002C16
131011012D16
141011102E16
151011112F16

Ein Prozessor in der sogennannten van-Neumann-Architektur (zu der fast alle heutigen Prozessoren gehören) besteht aus einem Speicher und einem Prozessor, der auf diesem Speicher arbeitet.

Der Speicher (engl.: Memory) besteht aus Speicherzellen, die meistens die Größe eines Bytes haben. Die Speicherzellen sind durchnummeriert von 0 bis zur Größe des Speichers. Diese Nummer nennt man Adresse. Über sie werden die Speicherzellen vom Prozessor angesprochen, dass heisst "adressiert". Im Speicher sind das Programm und die Daten gespeichert. Programme werden als folgen von Bytes gespeichert, deren Werte Operationen, gefolgt von Zahlenwerten oder Adressen sind. Z.B. ist

Der Speicher kanntechnisch aus änderbaren (RAM, Random Access Memory) oder aus nicht änderbaren (ROM, Read Only Memory) Modulen aufgebaut sein. RAM wir bei Abschalten des Stroms i.A. gelöscht, ROM nicht. Dazwischen gibt es Mischformen, bei denen die Änderung des Speichers aufwendig ist, aber der Speicher bei Abschalten des Stroms erhalten bleibt, wie z.B. das EEPROM (Electrical Erasable Programmable Read Only Memory).

Der Prozessor besteht intern aus einer Recheneinheit und Registern, die Zwischenwerte festhalten. DieRecheneinheit ist bei der Programmierung nicht direkt sichtbar. Es gibt Architekturen mit sehr vielen Registern, aber unser Prozessor beschränkt sich auf das unbedingt nötige.

Inhalt

Einbettung

Ein Assemblerprogramm wird als separater Source-Code in einer Datei progname.asm geschrieben. Er wird mit dem Programm TASM in das S-19-Format übersetzt. Eine funktionierender Aufruf lautet: tasm -05 -x progname.asm -g2. Es kann nötig sein, dass die Datei TASM05.TAB im Verzeichnis des Aufrufs liegt. Man kann eine Shareware-Version von TASM für DOS und Linux aus dem Web laden.

Der übersetzte Code wird dann nach der End-Anweisung des Basic-Programms (noch nach allen Tabellendefinitionen) durch die Anweisung syscode "progname.obj" geladen.

Der Aufruf erfolgt im Basic-Code mit der Anweisung sys &H0101. Dabei kann man auch andere Adressen als &H0101 angeben, wenn man sein Assembler-Programm auf anderer Adresse beginnt (siehe .org-Anweisung) oder mehrere Assembler-Routinen hat.

Inhalt

Register

Es gibt 5 interne Register. Die Nummern in den Tabellen unten sind die Durchnummerierung der Bits.

Akkumulator (A)
76543210

Im Akkumulator werden Zwischenergebnisse zur Verarbeitung festgehalten. Alle Rechenoperationen laufen zwischen dem Akkumulator und dem Speicher ab.

Index-Rregister (X)
76543210

Das Index-Register dient dazu Speicherzellen flexibler zu adressieren. Man kann dabei zu einer im Befehl angegebenen Adresse den Inhalt des Index-Registers addieren.

Program counter (PC)
1514131211109876543210

Der Programmzähler enthält die Adresse des als nächstes auszuführenden Befehls. Nach jeder Befehlsausführung wird er hochgezählt und zeigt dann auf den nächsten Befehl. Er wird durch uns indirekt kontrolliert, indem er durch Verzweigungsoperationen und den Aufruf von Unterprogrammen gesetzt wird.

Stackpointer (SP)
1514131211109876543210

Der Stackzeiger ist ein Bereich im Speicher, in dem bei Unterprogrammaufrufen die Rücksprungadresse abgespeichert wird. Nach dem Abspeichern wird der Stackzeiger hochgezählt, sodaß die nächste Rücksprungadresse gespeichert werden kann. Der Stackpointer kann nur Werte zwischen C016 und FF16 annehmen.

Condition Code Register (CCR)
76543210
111HINZC

Die einzelnen Bits in diesem Register zeigen den Status der Verarbeitung an. Die Bedeutung der Bits ist:

BitBeschreibung
CDas Carry-Bit wird bei einem Überlauf gesetzt, d.h. wenn z.B. eine Addition einen Wert ergibt, der nicht in den Akkumulator paßt. Es wird auch bei Schiebe- und Vergleichsbefehlen verändert.
ZDies ist das Zero-Bit. Es wird gesetzt, wenn eine Operation Null ergibt.
NDieses ist das Negativ-Bit. Es wird gesetzt, wenn eine Subtraktion einen Wert kleiner Null ergibt
IDies ist die Interrupt-Maske. Durch dieses Bit kann bestimmt werden, ob Interrupts verarbeitet werden sollen. Es ist sehr sinnvol, dieses Bit zu löschen, wenn eine Zeitkritische Operation durchgeführt werden soll. Es muß am Ende eines Programms aber unbedingt wieder gesetzt werden, sonst hängt sich der Prozessor auf!
HDieses ist das Halbbyte-Carry. Es wird bei ADD und ADC-Befehlen gesetzt, wenn ein berlauf aus dem hinteren Halbbyte in das vordere Halbbyte erfolgt. Dies ist sinnvoll für das Rechnen mit Dezimalzahlen.

Inhalt

Adressen

Der 68HC05 enthält nicht nur den Prozessor selbst, sondern auch mehrere Zusatzbausteine in einem Gehäuse. Die Zusatzbausteine werden über Speicheradressen angesprochen. Die wichtigsten Adressen liste ich hier auf. Ein großes Verzeichnis von Adressen findet sich im Speicherbelegungsplan auf der Webseite C-Control intern.

AdresseInhalt
$0000Port A Datenregister
$0001Port B Datenregister
$0004Port A Richtungsregister
$0005Port B Richtungsregister
$0008Analog/Digital Datenregister
$0009Analog/Digital Status- und Kontrollregister
$000APulslängenmodulation A
$000BPulslängenmodulation B
$000CSonstige Flags
$00A1Beginn der Basic-Variablen
$0101Programmbeginn für Assembler-Programme

Die zwei verfügbaren Datenregister werden bitweise als Ein- oder Ausgabe gesteuert. Wenn ein Bit im Datenregister als Ausgabe dienen soll, muß das entsprechende Bit im Richtungsregister auf 1 gesetzt werden.

Das Analog/Digital Status und Kontrollregister hat folgende Bits:

BitKurzbez.Bedeutung
7COCO?
6ADRC?
5ADONAnalog/Digital Konverter enable: 1 = eingeschaltet, 0 = ausgeschaltet
4n.b. 
3CH3Kanalnummer Bit 3
2CH2Kanalnummer Bit 2
1CH1Kanalnummer Bit 1
0CH0Kanalnummer Bit 0

Die Sonstigen Flags melden und steuern einige der Prozessor-Funktionen.

BitKurzbez.Bedeutung
7PORPower on Reset: nicht nutzbar
6INTPInterrupt-Steuerung: nicht nutzbar
5INTNInterrupt-Steuerung: nicht nutzbar
4INTEInterrupt-Enable-Bit: nicht nutzbar
3SFASlow-Fast-Mode für PLM A. 1 = 4096 x Timer, 0= 256 x timer
2SFBSlow-Fast-Mode für PLM B. 1 = 4096 x Timer, 0= 256 x timer
1SMWenn dieses Bit gesetzt wird, wird der Slow-Mode eingeschaltet. Der Prozessor arbeitet dann 16xlangsamer und verbraucht weniger Strom.
0WDOGWatchdog-Timer Enable: nicht nutzbar

Inhalt

Adressierungsarten

Der Prozessor 68HC05 ist eine reine Einadressmaschine. Das heißt, er kann maximal eine Speicheradresse in einem Befehl enthalten. Alle Operationen laufen also auf einer Speicherzelle Speicher, zwischen einem Register und einer Speicherzelle oder nur auf ein Register.

Bei Direkt (D)-Adressierung wird direkt auf eine bestimmte Speicheradresse zugegriffen. Man erkennt sie durch die Angabe einer Adresse.

Bei Immediate (I)-Adressierung wird anstatt einer Adresse ein Wert mitgegeben. Damit kann man ein Register oder eine Speicherstelle mit einem Wert initialisieren. Konstanten werden durch ein vorangestelltes #-Zeichen gekennzeichnet.

Bei Indexed (X)-Adressierung wird auf die mitgegebene Adresse der Inhalt des X-Registers addiert. Man kann damit auf ein Array von Werten zugreifen.

Einige Anweisungen benötigen die Angabe eines Bits, das behandelt werden soll. Das Bit wird durch eine Positionszahl zwischen 0 und 7 adressiert.

Bei Branch-Anweisungen wird die Relative (R) Adressierung verwendet. Es wird in der Anweisung der

Inhalt

Anweisungstabelle

Der Befehlssatz umfaßt folgende Befehle mit den Adessierungsarten Direct(D),Immediate(I),Indexed(X) und Relative(R). Einige Befehle sind sowohl allein für eine Speicherzelle, als auch mit einem angehängten vierten Buchstaben A oder X für den Akkumulator und das Index-Register verfügbar und beziehen sich dann auf das A-Register oder das X-Register. Sie sind in der Tabelle durch ein angehängtes (A,X) gekennzeichnet.

Die Befehle sind hier Gruppen (G) zugeordnet, die unten dann weiter erklärt werden.

NameGIDXRBeschreibung
ADCAXXX Addieren with Carry
ADDAXXX Add
ANDAXXX Logical and
ASL(A,X)S XX Arithmetic shift left
ASR(A,X)S XX Arithmetic shift right
BCCJ   XBranch if carry clear
BCLRB   XBit clear
BCSJ   XBranch if Carry Set
BEQJ   XBranch if equal
BHCCJ   XBranch if half carry clear
BHCSJ   XBranch if half carry set
BHIJ   XBranch if Higher
BHSJ   XBranch if Higher or Same
BIHJ   XBranch on interrupt line high
BILJ   XBranch on interrupt line low
BITBXXX Bit test
BLOJ   XBranch if lower
BLSJ   XBranch if lower or same
BMCJ   XBranch on interrupt mask clear
BMIJ   XBranch if minus
BMSJ   XBranch on interrupt mask set
BNEJ   XBranch if not Equal
BPLJ   XBranch if plus
BRAJ   XBranch always
BRCLRB   XBranch if bit is clear
BRNJ   XBranch never
BRSETB   XBranch if bit is set
BSETB   XBit set
BSRJ   XBranch to subroutine
CLCF    Clear Carry
CLIF    Clear Interrupt Flag
CLR(A,X)A XX Clear
CMPAXXX Compare
CMPXAXXX Compare X
COM(A,X)AXXX Complement
CPXAXXX Arithmetic Compare X
DEC(A,X)A XX Dekrementieren
EORAXXX Exklusives Oder
INC(A,X)AXXX Incrementieren
JMPJ XX Springen
JSRU XX Verzweigen in Unterprogramm
LDALXXXXLoad A
LDXLXXXXLoad X
LSL(A,X)S XX Logical Shift left
LSR(A,X)S XX Logical Shift right
MULA XX Multiply
NEG(A,X)A XX Negate
NOPO    No operation
ORAAXXX Logical or
ROL(A,X)S XX Rotate left
ROR(A,X)S XX Rotate right
RSPA    Reset stack pointer
RTIU    Rückkehren aus Interrupt-Routine
RTSU    Rückkehr aus Unterprogramm
SBCAXXX Subtract with Carry
SECA    Set carry bit
SEIA    Set interruptmask bit
STAL XX Speichern des Akkumulators
STOPO    Anhalten des Prozessors
STXL XX Speichern des Index-Registers
SUBAXXX Subtract
SWIU    Software interrupt
TAXA    Transfer A to X
TST(A,X)A XX Test for negative or zero
TXAA    Transfer X to A
WAITO    Warten auf Interrupt

Inhalt

Compiler-Anweisungen

Compiler-Anweisungen beginnen mit einem Punkt. Es gibt folgende Compiler-Anweisungen:

AnweisungBedeutung
.equDefinition einer Symbolischen Konstanten
.orgDefinition der nächsten zu verwendenden Speicherstelle

Beispiel:
portb .equ 1
...
lda portb

Dies bewirkt, das in den Akkumulator eine 1 geladen wird.

Eine vollständige Beschreibung aller Compiler-Anweisungen findet sich im TASM User's Manual.

Anweisungstabelle
Inhalt

Lade- und Speicher-Anweisungen (L)

Die Lade (LA, LX) -Befehle dienen dazu, einen konstanten Wert oder eine Speicherzelle in ein Register zu laden. Die Speicherbefehle(STA,STX) speichern einen Wert in den Speicher.

Will man Werte vom Akkumulator in das Index-Register stellen, dann geht das mit TAX. Der umgekehrte Weg geht mit TXA.

Die Clear-Befehle (CLR,CLRA,CLRX) setzen eine Speicherstelle oder ein Register auf Null.

Anweisungstabelle
Inhalt

Arithmetische und logische Anweisungen (A)

Decrement (DEC, DECA, DECX) erniedrigt einen Inhalt um 1. Inkrement (INC, INCA, INCX) erhöht einen Inhalt um 1.

ADD addiert zum A-Register einen Wert (Speicherstelle oder Konstante). ADC addiert zusätzlich noch das Carry-Bit dazu, sodaß durch mehrfache Addition lange Zahlen addiert werden können. SUB subtrahiert einen Wert. MUL Multipliziert den Akkumulator mit einem Wert und schreibt das Ergebnis in das X-Register (höherwertiger Teil) und das A-Register. NEG negiert eine Zahl, aus +7 wird -7, man nennt das auch ein 2er-Komplement.

AND verknüpft das A-Register mit einem Wert bitweise durch eine logischen UND-Operation. ORA verknüpft mit einer logischen OR-Operation und EOR verknüpft mit einer Exklusiv-Oder-Operation. COM, COMA, COMX negieren bitweise (NOT-Operation, 1er-Komplement) und haben nur einen Operanden. Das Ergebnis-Bit ergibt sich wie folgt (Bei COM habe ich das A-Register verwendet):

A-RegisterWertANDORAEORCOMA
000001
010111
100110
111100

Bei allen Arithmetischen und logischen Operationen werden auch die Flags gesetzt.

Anweisungstabelle
Inhalt

Schiebe-Anweisungen (S)

Es gibt drei Arten von Shift-Befehlen: Logisches Shift (LS?), Arithmetisches Shift (AS?) und Rotate (RO?). Alle gibt es mit schieben nach links (??L) und schieben nach rechts (??R)

Der Logische Shift ( LSL, LSLA, LSLX, LSR, LSRA, LSRX) schiebt den Inhalt des Ziels um eine Stelle nach rechts oder links und füllt dann das freigewordene Bit mit einer 0 auf.

Der Arithmetische Shift (ASL, ASLA, ASLX, ASR, ASRA, ASRX) schiebt das herausgeschobene Bit in den Carry, sodass auf Grund dieses Bits verzweigt werden kann. Das freigewordene Bit wird mit einer 0 aufgefüllt.

Das Rotate ( ROL, ROLA, ROLX, ROR, RORA, RORX) schiebt das herausgeschobene Bit in das freigewordene Bit. Aus 0b00000001 wird nach einem ROR also 0b10000000.

Shift-Operationen werden vor allem verwendet, um einzelne Bits zu verarbeiten. Wenn von vornherein bekann ist, welches Bit man braucht, kann man die Abfrage oder Änderung auch mit den Bit-Anweisungen durchführen. Wenn das Bit jedoch ein Parameter ist, kann man im X-Register einen Zähler bis 0 herunterzählen und die Bits durch Akkumulator und Carry schieben.

Anweisungstabelle
Inhalt

Vergleichs-Anweisungen (C)

Die Vergleichs-Anweisungen werden fast immer im Zusammenhang mit den Verzweigungs-Anweisungen verwendet.

Mit CMP kann man einen Wert im Akkumulator mit einer Konstante oder einer Speicherzelle vergleichen. Dasselbe kann man mit CPX mit dem Index-Register anstatt dem Akkumulator tun.

Mit TST, TSTX, TSTA dagegen testet man eine Speicherzelle, X, oder A auf 0 oder negativ .

Mit BIT kann man den Akkumulator bitweise gegen eine Speicherzelle testen.

Anweisungstabelle
Inhalt

Bit-Anweisungen (B)

Mit einigen Befehlen kann man direkt einzelne Bits im Akkumulator oder im Speicher verändern. Ein Bit wird durch einen zweiten Operanden mit der Nummer des Bits ausgewählt.

Mit BIT kann man ein Bit testen. Das Ergebnis wird durch das Z-Register angezeigt. Mit BSET wird ein Bit auf 1 gesetzt und mit BCLR wird es auf 0 gelöscht. Als zweiter Operand wird immer eine Maske angegeben.

Mit BRSET und BRCLR kann man auch abhängig von einzelnen Bits verzweigen.

Anweisungstabelle
Inhalt

Verzweigungs-Anweisungen (J)

Es gibt diverse Branch-Befehle, die entweder ohne jede Bedingung (immer: BRA, nie:BNE) oder auf Grund der Bits im CCR verzweigt wird. Der Sprung erfolgt relativ zur aktuellen Ausführungsadresse, kann also nicht weiter als 129 Bytes vorwärts oder 126 Bytes rückwärts erfolgen; für weitere Sprünge muß man die Branch-Anweisungen mit JMP-Anweisungen kombinieren.

Einige Anweisungen beziehen sich darauf, ob einzelne Bits, gesetzt sind: Carry(BCC, BCS), HalfCarry(BHCC, BHCS), Interrupt-Mask (BMC, BMS). Andere Anweisungen dienen dazu, nach einem Vergleich mit CMP oder CPX benutzt zu werden:

=BEQ
<>BNE
<BLO
<=BLS
>BHI
>=BHS

Nach einer Subtraktion kann man abfragen, ob das Ergebnis größer oder kleiner 0 ist mit BPL oder BMI.

Auch bitweise Branchanweisungen gibt es (siehe Bit-Anweisungen).

Man kann auch Subroutinen mit kleiner Reichweite, aber weniger Bytes für den Aufruf per Branch erreichen mit BSR.

Ein Sonderfall ist die Abfrage der Hardware-Interrupt-Leitungen mit BIL und BIH.

Mit JMP wird ebenfalls ohne Bedingung an eine bestimmte Speicherstelle gesprungen.

Anweisungstabelle
Inhalt

Unterprogramm-Anweisungen (U)

Unterprogramme werden aufgerufen durch den Befehl JSR. Dabei wird die aktuelle Adresse automatisch auf den Stack geschrieben. Aus einem aufgerufenen Subprogramm kehrt man mit RTS zurück, die Rückkehradresse wird dabei vom Stack geholt.

Interrupt-Routinen werden entweder durch die Auslösung eines Hardware-Interrupts oder durch die Anweisung SWI ausgelöst. Aus Interrupt-Routinen wird durch die Anweisung RTI wieder zurückgekehrt. Interessant ist bei Interrupts, das nicht nur derProgrammzähler auf den Stack gerettet wird, sondern auch Der Akkumulator, das Index-Register und das Condition-Code-Register.

Anweisungstabelle
Inhalt

Sonstige Anweisungen (O)

Mit NOP wird einfach nichts ausgeführt, man kann es benutzen, um den zeitlichen Ablauf zu verlangsamen, die Speicherung auf einem bestimmten Platz im Speicher zu erzwingen, oder um in einem kompilierten Code nachträglich Befehle zu entfernen.

Mit WAIT wird der Prozessor angehalten, bis ein Interrupt erfolgt.

Mit STOP wird der Prozessor endgültig angehalten, bis ein Reset erfolgt.

Anweisungstabelle
Inhalt
Copyright Mario Boller-Olfert 2000
Komm in Marios Welt
EMail: Kontaktformular