ndr-nkc.de ndr-nbc.de
  
Startseite
News
 
NDR-NKC
Geräte Z80
Geräte 68000
Geräte 8088
 
NKC Emulator
 
Z80 Section
Baugruppen
ROM's
Software
68000 Section
Baugruppen
ROM's
PASCAL/S
Software
CP/M 68K
8088 Section
Baugruppen
Downloads
 
Bussysteme
Stromversorgung
Input / Output
Grafikkarten
Speicherkarten
Massenspeicher
Weitere Baugruppen
 
Projekte
 
Dokumentation
Datenblätter
Glossar
Portraits
Links

Impressum

 

CP/M 68K Programmierkurs - 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.

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