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.