| |
CP/M 68K - Nützliche Unterprogramme
Es ist schon erstaunlich, wie viele Fähigkeiten bei reiner Assembler-Programmierung selbst umgesetzt werden müssen. Kein Vergleich zu Hochsprachen wie C, PASCAL, BASIC oder selbst dem Grundprogramm im ROM.
An dieser Stelle muss ich also ein Kapitel einschieben, um einige nützliche Unterprogramme vorzustellen, die wir in den folgenden Beiträgen benötigen werden. Die Beispiele erheben nicht den Anspruch, die kürzeste oder schnellste Umsetzung darzustellen, sie sollen hingegen so leicht verständlich wie möglich sein. Die Unterprogramme sind so ausgerichtet, dass Inhalte der benutzten Register nach Abschluss wiederhergestellt werden, außer natürlich bei erwarteten Ergebnissen einer Funktion.
Zurück zum Inhaltsverzeichnis
Themen dieses Kapitels
- Ausgabe hexadezimaler Werte
- Beschreiben eines Puffers
- Ausgabe dezimaler Werte
- 32 Bit Division
Hexadezimale Zahlenausgabe
Mit der Routine PRINT8X kann ein hexadezimaler 32-Bit Wert in Register D0 auf der Konsole ausgegeben werden.
CONOUT EQU 2 * BDOS Funktionsnummer Konsolausgabe
START: * Zum Test des Unterprogrammes
MOVE.L #$12345678, D0 * Testwert
BSR PRINT8X * ausgeben
RTS * Zurück zu CP/M
* Ausgabe des Inhaltes von D0.L als 8 Hexadezimalziffern
PRINT8X:
MOVEM.L D0-D3, -(A7) * Registerinhalte auf Stack sichern
MOVE.L D0, D2 * auszugebenden Wert kopieren
MOVE #7, D3 * Anzahl der auszugebenden Stellen
PRT8X1: * Schleife über 8 Stellen
ROL.L #4, D2 * Mit MSB beginnen
BSR PRTNIB * Eine Stelle ausgeben
DBRA D3, PRT8X1 * Solange bis alle Stellen ausgegeben
MOVEM.L (A7)+, D0-D3 * Registerinhalte wiederherstellen
RTS * Rücksprung zum aufrufenden Programm
PRTNIB:
MOVE.B D2, D1 * Stelle für BDOS-Aufruf kopieren
AND.B #$0F, D1 * untere 4 Bit maskieren
CMP.B #$0A, D1 * Kleiner als 10 ?
BLT PRTNIB1 * Ja, ausgeben
ADD.B #7, D1 * ASCII Korrektur für A bis F
PRTNIB1:
ADD.B #’0’, D1 * ASCII Versatz addieren
MOVE #CONOUT, D0 * BDOS Funktion
TRAP #2 * CONOUT aufrufen
RTS * Fertig
Mit nur einer kleinen Änderung bei der Ausgabe in PRTNIB1 kann man die Routine so abändern, dass die Zahl nicht direkt auf der Konsole ausgegeben wird, sondern an einer bestimmten Stelle in einem Textpuffer abgelegt wird. Im Anschluss kann der Inhalt des Puffers mit der BDOS Funktion Print String (Funktionsnummer 9) gemeinsam mit Text ausgegeben werden. Die relevanten Änderungen sind fettgedruckt.
PRINTSTR EQU 9 * BDOS Funktionsnummer Print String
TEXT * Start des Textsegments
START:
MOVE.L #$12345678, D0 * Testwert
MOVE.L #VARIABLE, A0 * Adresse des Platzhalters
BSR PRINT8X * in Platzhalter schreiben
MOVE #PRINTSTR, D0 * Funktionsnummer BDOS
MOVE.L #BUFFER, D1 * Adresse des auszugebenden Strings
TRAP #2 * und aufrufen
RTS * Zurück zu CP/M
* Ausgabe des Inhaltes von D0.L als 8 Hexadezimalziffern
PRINT8X:
MOVEM.L D0-D3, -(A7) * Registerinhalte auf Stack sichern
MOVE.L D0, D2 * auszugebenden Wert kopieren
MOVE #7, D3 * Anzahl der auszugebenden Stellen
PRT8X1: * Schleife über 8 Stellen
ROL.L #4, D2 * Mit MSB beginnen
BSR PRTNIB * Eine Stelle ausgeben
DBRA D3, PRT8X1 * Solange bis alle Stellen ausgegeben
MOVEM.L (A7)+, D0-D3 * Registerinhalte wiederherstellen
RTS * Rücksprung zum aufrufenden Programm
PRTNIB:
MOVE.B D2, D1 * Stelle für BDOS-Aufruf kopieren
AND.B #$0F, D1 * untere 4 Bit maskieren
CMP.B #$0A, D1 * Kleiner als 10 ?
BLT PRTNIB1 * Ja, ausgeben
ADD.B #7, D1 * ASCII Korrektur für A bis F
PRTNIB1:
ADD.B #’0’, D1 * ASCII Versatz addieren
MOVE.B D1,(A0)+ * Ablegen und nächste Adresse
RTS * Fertig
DATA * Start des Datensegements
BUFFER:
DC.B 'Inhalt des Registers D0: '
VARIABLE:
DC.B '-------- $'
END
Mit wenig Aufwand lässt sich die Ausgabe von Zahlen erweitern, so dass auch 16 Bit und 8 Bit Werte ausgegeben werden können. Dabei wird der größte Teil der Routine PRINT8X wiederverwendet.
* Ausgabe des Inhaltes von D0.B als 2 Hexadezimalziffern
PRINT2X:
MOVEM.L D0-D3, -(A7) * Registerinhalte auf Stack sichern
MOVE.L D0, D2 * auszugebenden Wert kopieren
ROR.L #8, D2 * Byte nach vorne bringen
MOVE #1, D3 * Anzahl der auszugebenden Stellen
BRA PRT8X1 * dort einspringen
* Ausgabe des Inhaltes von D0.W als 4 Hexadezimalziffern
PRINT4X:
MOVEM.L D0-D3, -(A7) * Registerinhalte auf Stack sichern
MOVE.L D0, D2 * auszugebenden Wert kopieren
SWAP D2 * Wort nach vorne bringen
MOVE #3, D3 * Anzahl der auszugebenden Stellen
BRA PRT8X1 * dort einspringen
Dezimale Zahlenausgabe
Die Ausgabe von Dezimalzahlen ist dagegen deutlich komplizierter, da alle Mikroprozessoren zunächst einmal im Binärsystem arbeiten. Die folgende Routine teilt die auszugebende Zahl jeweils durch 10, um dann den Rest bei den Divisionen auszugeben. Eine zusätzliche Schwierigkeit ist, dass der 68000 nicht direkt 32 Bit Divisionen unterstützt. Dazu ist eine weitere Routine notwendig.
Die Routine gibt die Zahl im Register D0 in den Pufferspeicher ab der Adresse in A0 aus. Die Belegung der Register ist
- D0 = auszugebende Zahl
- D1 = Konstante 10 für die notwendigen Divisionen
- D6 = Flag zum Erkennen führender Nullen
- D7 = Schleifenzähler
- A0 = Adresse im Puffer für die Ziffernausgabe
PRINTD:
TST.L D0 * Wenn Zahl 0 ist
BEQ PRTD0 * dann sofort ausgeben und Ende
MOVEM.L D1/D6/D7, -(A7) * REGS sichern
MOVE.L #10, D1 * Konstante 10
MOVE.L #9, D7 * 10 Schleifendurchgänge
CLR.B D6 * Flag löschen
PRTD1:
BSR DIVU32 * Division, Rest im Bereich 0-9
MOVE.B D2, -(A7) * Rest auf Stack
DBRA D7, PRTD1 * wiederholen
MOVE.L #9, D7 * 10 Schleifendurchgänge
PRTD3:
MOVE.B (A7)+, D1 * umgekehrte Reihenfolge
BEQ PRTD4 * bei 0 Flag testen
BRA PRTDX * Ziffer ausgeben
PRTD2:
DBRA D7, PRTD3 * Bis alle Stellen fertig
MOVEM.L (A7)+, D1/D6/D7 * REGS wiederherstellen
RTS * zurück zum Aufrufenden
PRTD4:
TST.B D6 * Flag testen
BEQ PRTD2 * nicht ausgeben, solange Flag=0
PRTDX:
ADDQ.B #1, D6 * Flag setzen
ADD.B #$30, D1 * ASCII Korrektur
MOVE.B D1, (A0)+ * Ziffer in Buffer
BRA PRTD2 * weiter
PRTD0:
MOVE.B #$30, (A0)+ * '0' in Buffer
RTS * sofort zurück
Innerhalb von PRINTD wird die Routine DIVU32 aufgerufen, welche die erforderliche 32 Bit Division ausführt. Die Belegung der Register ist
- D0 = Dividend und ganzzahliges Ergebnis der Division
- D1 = Divisor
- D2 = Rest bei der Division
- D3 = temporär genutzt
DIVU32:
CLR.L D2 * Temp Register löschen
MOVE.W D0, D2 * D0 LSW sichern
CLR.W D0 * D0 LSW löschen
SWAP D0 * MSW nach LSW
DIVU D1, D0 * MSW zuerst dividieren
MOVE.L D0, D3 * Ergebnis kopieren
CLR.W D3 * Rest in D3 MSW
SWAP D0 * Ergebnis nach MSW
CLR.W D0 * Rest entfernen
ADD.L D3, D2 * Rest zu LSW addieren
DIVU D1, D2 * LSW dividieren
MOVE.W D2, D0 * Ergebnis zusammensetzen
SWAP D2 * Rest nach D2 LSW
RTS
Hinweise
Die hier vorgestellten Unterprogramme werden in den kommenden Beispielen genutzt, aber dort nicht mehr erneut aufgelistet. Das bedeutet, dass die kommenden Beispiele nicht ohne die Ergänzung durch manche der hier wiedergegebenen Unterprogramme ablauffähig sind.
Gegen Ende der Artikelserie werden wir die gesammelten Unterprogramme zu einem Modul zusammensetzen und als getrennte Objektdatei ablegen, die mit Hilfe des Linkers einfach zu einem Gesamtprogramm zusammengesetzt werden können.
Weiter zu Teil 6 der Serie: Wichtige Datenstrukturen
Zurück zu Teil 4 der Serie: Die BDOS-Funktionen
|