2. Unterprogramme
Bis jetzt können Sie
Ihre Programme nur Schritt für Schritt abarbeiten, oder aber mit Hilfe von bedingten
Sprüngen kleinere Schleifen ausführen. In der Tat können Sie auf diese Weise
beliebig komplexe Programme erzeugen, aber irgendwann wird die Sache
unübersichtlich und fehleranfällig. Wenn es so weit ist, und Sie den Überblick
verloren haben, können (und sollten) Sie versuchen, Dinge, die Sie immer wieder
in identischer Weise eintippen, in Unterprogramme auszulagern. Ein populäres
Beispiel ist z.B. die Ausgabe eines Zeichens an der Cursorposition, die bereits
in den Indexregistern steht. Im nächsten Beispiel sollen die Koordinaten in den
Indexregistern stehen (X=Bildschirmspalte, Y=Bildschirmzeile),
und das Zeichen selbst als PETSCII-Wert im Akkumulator. Wenn Sie nun mehrere
Zeichen (z.B. einen String) ausgeben wollen, dann ist es sinnvoll, ein
Unterprogramm wie CHAROUT zu verwenden. Das BASIC-Pendant sieht
beispielsweise so aus:
10 X=10:Y=10:A=1
20
GOSUB 10000
30 END
10000
REM *** CHAROUT ***
10010 AD=1024+(40*Y)+X
10020 POKE AD,A
10030
RETURN
2.1 Eigene
Unterprogramme erstellen
Im Endeffekt dürfte die
Assembler-Variante ähnlich einfach zu programmieren sein, denn wie man mit dem
Akkumulator rechnet, und wie man die Bildschirm-Basisadresse durch Zeiger
darstellt, wissen Sie bereits. Leider kann der 6510-Prozessor die Multiplikation
in der BASIC-Zeile 10010 nur durch zeitaufwendige Algorithmen (die
sogenannte ägyptische Multiplikationsmethode) berechnen, und diese ist
(da sie bitweise arbeitet) in Assembler nicht schneller, als in BASIC. Sie
müssen sich also an dieser Stelle (zumindest, wenn Sie Assembler als
Programm-Beschleuniger sehen) einen anderen Weg ausdenken. Zum Glück greift
Ihnen auch hier Hypra-Ass unter die Arme. Sie können
nämlich in einfacher Weise eine Tabelle erstellen, die sämtliche 25 Zeigerwerte
für den Anfang der einzelnen Bildschirmzeilen in Form eines Hi- und Lo-Bytes
enthält. Auf diese Werte können Sie dann (da die Tabelle an einer absoluten
Adresse steht) auch direkt durch ein Indexregister zugreifen (Sie benutzen also
hier die indizierte absolute Adressierung). Dies geht wie folgt (die Werte in X,
Y und A seien bereits korrekt zugewiesen):
PHA
TYA
ASL
TAY
LDA LINETAB+1,Y
STA 249
LDA LINETAB,Y
STA 248
TXA
TAY
PLA
STA
(248),Y
Da der
Akkumulator-Inhalt sich während der Berechnungen ändert, muss das darin
enthaltene Zeichen, das Sie am Ende ausgeben wollen, zunächst auf dem Stack
zwischengespeichert werden. Anschließend muss der Zeiger, der die Startadresse
auf die Zeile enthält, den Sie im Y-Register übergeben haben,
zusammengesetzt werden. Hierzu muss der richtige Zeigerindex aus dem Array LINETAB
ausgewählt werden, und anschließend in die Adressen 248 und 249 übertragen
werden. Leider haben Zeiger, die Sie für die Y-indizierte indirekte
Adressierung verwenden, 2 Bytes, das heißt, Sie müssen den Inhalt des Y-Registers
um 1 Bit nach links verschieben, um den richtigen Address-Offset
in das Array LINETAB zu erhalten. Da es den Befehl YSL (Y shift left) nicht gibt, müssen Sie zunächst mit TYA (transfer Y to A) Y nach A
kopieren. Anschließend können Sie ASL ausführen. Allerdings müssen Sie
danach den Akkumulator wieder mit TAY (transfer
A to Y) in das Y-Register zurückkopieren. Nun
enthält Y den korrekten Offset und Sie können den richtigen
2-Byte-Zeiger durch die absolute indizierte Adressierung aus dem Array LINETAB
auswählen. Der Zeiger in den Adressen 248/249 enthält nun den Anfang der
gewünschten Bildschirmzeile Nr. 10. Nun müssen Sie nur noch die X-Position in
das Y-Register schreiben, um mittels
STA
(248),Y
auf die Adresse
zuzugreifen, in der Sie das gewünschte Zeichen ablegen wollen. Leider steht der
Offset für die X-Position auch im X-Register, und natürlich gibt es
keinen Befehl der Form TXY. Sie müssen also auch hier zunächst mittels TXA
das X-Register in den Akkumulator kopieren, und anschließend den
Akkumulator in das Y-Register. Nun können Sie endlich das Zeichen, das
Sie eigentlich auf dem Bildschirm ausgeben wollten, vom Stack ziehen und an die
korrekte Position schreiben. Wenn Sie nun noch zusätzlich das Label CHAROUT
definieren, und Ihr Unterprogramm korrekt mit RTS beenden, dann können
Sie Ihr Unterprogramm auch mit
JSR
CHAROUT
aufrufen. Leider hat CHAROUT
die unangenehme Eigenschaft, den Inhalt Ihrer Index-Register zu verändern - und
diese bleiben auch nach Rückkehr durch RTS verändert. Sie müssen die
Index-Register also direkt beim Eintritt in Ihr Unterprogramm sichern, und direkt
vor der Rückkehr wieder herstellen. Dies können Sie auf unterschiedliche Art
und Weise erledigen, z.B. über den Stack:
PHA
TXA
PHA
TYA
PHA
PHP
…
Ihre
Routine CHAROUT in der ursprünglichen Form ohne das abschließende RTS
…
PLP
PLA
TAY
PLA
TAX
PLA
RTS
Hier werden zusätzlich
zu den Standardregistern auch die Flags gesichert (durch PHP und PLP).
Dies kann wichtig sein, wenn Sie CHAROUT innerhalb einer Schleife mit
bedingten Sprüngen und Vergleichen aufrufen. Dass Sie hier den Akkumulator
zweimal sichern, ist kein so großer Nachteil. Allerding kann es zu Problemen
kommen, wenn Sie den Stack am Ende falsch aufräumen,
denn dann findet der Prozessor nach dem RTS nicht mehr nach Hause
zurück: Er verirrt sich quasi und stürzt auch irgendwann ab. Da der 6510 nicht
so viele wichtige Register hat, können Sie die Registerinhalte auch in
bestimmten Speicherstellen sichern, z.B. so:
STA 1017
STX 1018
STY 1019
PHP
…
PLP
LDY 1019
LDX
1018
LDA
1017
Auf diese Weise
benötigen Sie nur noch zwei zusätzliche Stack-Aufrufe. Wie Sie vorgehen, ist im
Endeffekt Geschmackssache. BASIC verfährt übrigens beim SYS-Befehl auch so, wie
im letzten Beispiel, allerdings werden hier A, X und Y in
den Adressen 780, 781 und 782 gesichert. Nun haben Sie alles zusammen, um ein
laufendes Programm zu erstellen, das CHAROUT benutzt, um an der Position
{10,10} das Wort „HALLO“ auszugeben.
05-STRINGOUT
10
- .BA 49152
20
- LDX #10
30
- LDY #10
40
- LDA #0
50
- STA 1000
60
-READCHAR STX 1001
70
- LDX 1000
80
- LDA HALLO,X
90
- CMP #0
100
- BEQ EXIT
110
- LDX 1001
120
- JSR CHAROUT
130
- INX
140
- INC 1000
150
- JMP READCHAR
160
-EXIT LDX 1001
170
- RTS
180 -CHAROUT STA 1017
190
- STX 1018
200
- STY 1019
210
- PHP
220
- PHA
230
- TYA
240
- ASL
250
- TAY
260
- LDA LINETAB+1,Y
270
- STA 248
280
- LDA LINETAB,Y
290
- STA 249
300
- TXA
310
- TAY
320
- PLA
330
- STA (248),Y
340
- PLP
350
- LDA 1017
360 - LDX 1018
370 - LDY 1019
380 - RTS
1000 -LINETAB
.BY $04,$00,$04,$28,$04,$50,$04,$78,$04,$A0,$04,$C8
1010 -
.BY $04,$F0,$05,$18,$05,$40,$05,$68,$05,$90,$05,$B8
1020 -
.BY $05,$E0,$06,$08,$06,$30,$06,$58,$06,$80,$06,$A8
1030 -
.BY $06,$D0,$06,$F8,$07,$20,$07,$48,$07,$70,$07,$98
1040 -
.BY $07,$C0
1050 -HALLO .TX “HALLO“
1060 - .BY 0
Wie Sie im letzten
Beispiel sehen, können Sie die Byte-Werte für das Zeiger-Array LINETAB
mit .BY in den Speicher schreiben. Hypra-Ass
setzt automatisch die richtige absolute Adresse ein, wenn Sie das Label LINETAB
im Programmcode benutzen. Den Text „HALLO“ können Sie mit .TX
Byte für Byte im Programmspeicher ablegen, allerdings wird in dem letzten
Beispiel der auszugebende String „HALLO“ noch durch ein Nullbyte
abgeschlossen (dies nennt man auch einen nullterminierten String).
Leider benutzt Hypra-Ass die ASCII-Codierung, was
bedeutet, dass der Text in Groß/Kleinschrift ausgegeben wird (also vor SYS
49152 bitte POKE 53272,22 eingeben, oder aber noch vor Zeile 90 ein AND
#$3F einfügen).
Der Rest ist nun nicht
mehr so schwer zu verstehen. Das Hauptprogramm initialisiert zunächst die
Ausgabeposition mit Bildschirmzeile 10 und Bildschirmspalte 10 in den
Indexregistern. Nun muss noch die aktuelle Leseposition im String HALLO
auf 0 gesetzt werden, allerdings kann hierfür keines der Indexregister mehr
benutzt werden. Deshalb wird die aktuelle Leseposition im String HALLO
in der Adresse 1000 abgelegt. Zusätzlich wird das Indexregister, das in Zeile
80 verändert wird, in der Adresse 1001 zwischengespeichert, da das Sichern
auf dem Stack den Inhalt des Akkumulators verändern würde, in dem stets das
zuletzt aus dem String gelesene Zeichen stehen muss. Dieses Zeichen ist in den
ersten 4 Fällen ungleich 0, deshalb führt der CMP-Befehl und der
anschließende BEQ-Befehl in Zeile 100 auch nicht zum Label EXIT,
sondern zum Aufruf von CHAROUT. Allerdings muss in diesem Fall das X-Register
wieder hergestellt werden, da dieses die aktuelle X-Position der Zeichenausgabe
enthält. Nach der Zeichenausgabe muss sowohl der Inhalt des X-Registers
um 1 erhöht werden (mit INX), als auch der Inhalt der Speicheradresse
1000. Auch hierfür gibt es einen Befehl, nämlich den Befehl INC (increment). INC (increment)
und DEC (decrement) sind die einzigen Befehle,
die Speicherinhalte direkt verändern können, allerdings funktionieren INC
und DEC nur zusammen mit absoluten Adressen oder absoluten Adressen
zusammen mit dem X-Register. Folgendes ist also erlaubt:
INC
1000
INC 1000,X
Folgendes ist dagegen
nicht erlaubt:
INC 1000,Y
INC (248,X)
INC (248),Y
2.2
Assembler-Unterprogramme in BASIC einbinden
Manchmal wollen Sie einfach
Assembler verwenden, um Ihre langsamen BASIC-Programme an einigen Stellen zu
„tunen“. Vielleicht wollen Sie ja nur einen einfachen Texteditor erstellen,
stellen aber fest, dass das Scrolling zu langsam arbeitet. Wenn dies die
einzige Sache ist, die Sie wurmt, dann kommen Sie vielleicht auf die Idee, den
eigentlichen Text in einem bestimmten Offscreen-Speicherbereich
abzulegen, und für das Scrolling einfach eine Assembler-Routine zu verwenden,
die einen Teil des Textes ausliest, und in korrekter Weise neu in den
Bildschirmspeicher kopiert.
Inzwischen wissen Sie
schon, wie man unter BASIC Maschinenprogramme in den Speicher schreibt, nämlich
mit READ und DATA. Zunächst müssen Sie aber die DATA-Zeilen
erzeugen. Bei kurzen Programmen geht dies auch ganz einfach, Sie müssen nur
unter Hypra-Ass RUN eingeben, und anschließend
den Resetschalter drücken. Dadurch wird nur BASIC neu gestartet, Ihr
Maschinenprogramm bleibt jedoch im Speicher erhalten. Anschließend können Sie
sich Ihre Bytes in der folgenden Weise auf dem Bildschirm ausgeben lassen (SA=Startadresse,
EA=Endadresse):
FOR I=SA TO EA:PRINT PEEK(I);:NEXT
I
Leider müssen Sie nun
die Zahlen, die auf dem Bildschirm stehen, per Hand in DATA-Zeilen
umwandeln. Bei kleinen Programmen ist dies auch wie gesagt kein großes Problem,
aber bei längeren Programmen kann man sich schon leicht vertun, besonders, wenn
die Ausgabe mehrere Bildschirmseiten füllt (Sie müssen dann quasi stückweise
arbeiten). Sie können nun entweder den schon aus dem Kapitel „Datei-I/O“
bekannten „Saver“ dazu benutzen, um Ihre Module auf
Diskette auszulagern und später einfach nachzuladen. Vielleicht wollen Sie aber
auch erreichen, dass Ihre Anwendung aus nur einer einzigen Datei besteht, die
Sie dann unter Umständen auch mit einem Compiler wie Austro-Comp
beschleunigen wollen. In diesem Fall können Sie mein Programm DATAGEN
aus dem Kapitel „Datei-I/O“ des BASIC-Kurses benutzen. DATAGEN erzeugt
automatisch aus den Daten im Speicher ein BASIC-Listing, das die richtigen DATA-Zeilen
enthält.