Im folgenden stellen wir Ihnen einige Z80-Routinen vor, die häufig vorkommende Probleme auf elegante Weise lösen.
Bei der Ausgabe von Texten hat sich dieses Unterprogramm bewährt:
OUTTEXT:
EX (SP),HL
PUSH AF
OUTT1:
LD A,(HL)
INC HL
CP ETX ; ein frei definiertes
; Ende-Zeichen
JR Z,OUTT2 ;
CALL OUTCHAR ; Ausgabe d. ASCII-Zeichens im Akku
JR OUTT1 ;
OUTT2:
POP AF
EX (SP),HL
RET
Der Aufruf der Routine muß dann so aussehen:
:
CALL OUTTEXT
DEFM 'Textzeichen',ETX
:
Das ETX-(End of Text)-Zeichen ist dann natürlich dasselbe wie bei OUTTEXT, also zum Beispiel ASCII-ETX (03hex) oder CR (0D hex), und darf innerhalb des Textes nicht vorkommen.
Die Routine OUTTEXT läuft dann folgendermaßen:
Durch
EX (SP),HL
wird die Rückkehradresse (sie ist die Anfangsadresse des Textes!) vom Stack nach HL geladen und gleichzeitig der Inhalt von HL gerettet.
Dann wird der Text zeichenweise ausgegeben und dabei HL jeweils erhöht.
Nachdem das Textende an ETX erkannt worden ist, zeigt HL auf das erste Byte hinter dem Text.
Dies muß nun der nächste Befehl im Programm sein.
Durch
EX (SP),HL
wird diese Adresse als Return-Adresse auf dem Stack abgelegt.
RET
führt also zum Rücksprung zu dem auf ETX folgenden Befehl.
Wer Texte lieber durch eine Längenangabe begrenzt, dem hilft folgende Routine:
OUTCOUNTED:
EX (SP),HL
PUSH AF
PUSH BC
LD B,(HL) ; Zähler nach
; Register B
INC HL ;
OUTC1:
LD A,(HL)
INC HL
CALL OUTCHAR ; Ausgabe des ASCII-
; Zeichens im Akku
DJNZ OUTC1 ;
POP BC
POP AF
EX (SP),HL
RET
Der Aufruf muß so aussehen:
:
CALL OUTCOUNT
DEFB TEND-TANF
; Differenz zwischen
; Ende und Anfang,
; also Textlänge
TANF:
DEFM 'Textzeichen'
;
TEND:
:
('Textzeichen' ist natürlich in beiden Fällen durch den auszugebenden Text zu ersetzen!)
Steht das Hauptprogramm (der Text) noch dazu im RAM, so lassen sich z.B. Fehler- oder Zeilennummern sehr leicht ausgeben:
:
CALL OUTTEXT
DEFM 'Fehler: xx',ETX
ERRCODE EQU $-3
:
ERRCODE EQU $-3 gibt dem Assembler an, daß die Speicherzelle drei Byte vor der, auf die der aktuelle Programmzähler zeigt, ERRCODE heißen soll.
Es kann also das erste 'x' als ERRCODE adressiert werden.
Indem man nun vor dem Aufruf von OUTTEXT die beiden 'x' durch die Fehlernummer überschreibt, wird die Ausgabe von Fehlermeldungen ein Klacks.
Wer eine Routine benötigt, die feststellt, in welchen Speicherbereich sie geladen wurde, der kommt mit folgendem Programmstück schnell zum Ziel:
RELOC:
CALL RETURN
REL1:
DEC SP
DEC SP
EX (SP),HL
;
; HL enthält nun die Adresse von REL1
;
:
die eigentliche Routine
POP HL
RETURN:
RET
Durch den Befehl
CALL RETURN
wird der Prozessor veranlaßt, die Adresse des folgenden Befehles, hier
DEC SP
mit der symbolischen Adresse REL1, auf dem Stack abzulegen (für ein späteres RETURN).
Nach der Rückkehr steht diese Adresse immer noch auf dem Stack, nur zeigt der Stackpointer SP nun auf das nächsthöhere Element auf dem Stack.
Durch die beiden „
DEC SP
"-Befehle wird der Stackpointer nun auf dieses Element gerichtet.
Der Befehl
EX (SP),HL
sichert nun HL auf dem Stack, gleichzeitig steht in HL die Adresse von REL1.
Wer das ganze öfter benötigt, kommt mit:
RELOC:
CALL RETURN
REL1:
; HL = REL1
:
die benötigte Routine
:
POP HL
RET
;
RETURN:
EX (SP),HL
PUSH HL
RET
noch günstiger weg.
Hier wird die Adresse von REL1 schon in der Routine RETURN ermittelt.
Um den Rücksprung an die richtige Stelle zu ermöglichen, muß die Adresse aus HL auf den Stack gebracht werden (sie wurde durch
EX (SP),HL
entfernt!).
Die weitere Adressenrechnung kann nun wie in
mc 1/81, Seite 24, beschrieben erfolgen, nur muß bei Angabe der Distanz darauf geachtet werden, daß HL nicht die Adresse von RELOC, sondern die Adresse von REL1, also RELOC + 3, enthält.