5) Die Praxis - Ein Beginn
Um die REU zu programmieren braucht man genaue Kenntnis der REC-Register, und ich laß keins davon aus, versprochen.
Für unser erstes kleines Experiment sind allerdings nur die folgenden wichtig:
- $DF01 (57088) Kommandoregister
- $DF02/03 Computeradresse (Lo/Hi)
- $DF04/05 REU-Adresse (Lo/Hi)
- $DF06 REU-Bank (siehe unten)
- $DF07/08 Anzahl Bytes die wir übertragen wollen (Lo/Hi)
Der Speicher in der REU ist in Bänken zu je 64 KB organisiert. Maximal sind also ansprechbar:
- REU 1700: 2 Bänke (0 bis 1)
- REU 1764: 4 Bänke (0 bis 3)
- REU 1750: 8 Bänke (0 bis 7)
- REU mit 1 MB: 16 Bänke (0 bis 16)
- REU mit 2 MB: 32 Bänke (0 bis 31)
Für unser erstes kleines Beispiel kopieren wir den Bildschirm in die Bank 0 und holen ihn dort auch wieder zurück.
10 REC=50788
20 PRINTCHR$(147)"BILDSCHIRM 1"
30 POKE REC+2,0:POKE REC+3,4
40 POKE REC+4,0:POKE REC+5,0
50 POKE REC+6,0
60 POKE REC+7,0:POKE REC+8,4
70 POKE REC+9,0:POKE REC+10,0
80 POKE REC+1,252
100 PRINTCHR$(147)"BILDSCHIRM 2"
101 PRINT"TASTE FUER BILDSCHIRM 1"
110 POKE198,0:WAIT198,1
120 POKE REC+2,0:POKE REC+3,4
130 POKE REC+4,0:POKE REC+5,0
140 POKE REC+6,0
150 POKE REC+7,0:POKE REC+8,4
160 POKE REC+1,253
Das sieht jetzt sehr, sehr schlimm aus, ist es aber nicht. Wie Du weißt, liegt der Bildschirmspeicher gemeinhin bei 1024 ($0400) und ist einschließlich der Spritepointer 1024 ($0400) Bytes lang. Das Low-Byte von $0400 ist 00 und das Hi-Byte 04. Und damit kann man nun prima rechnen.
- In Zeile 30 wird die Adresse im C64 festgelegt. Low-Byte=0 und Hi-Byte=4.
- Zeile 40 bestimmt die Adresse in der REU. Wir beginnen am Anfang der Bank, also Adresse 0000 (Lo=0, Hi=0)
- In Zeile 50 steht die Bank, Null ist Null.
- In Zeile 60 legen wir die Anzahl der zu übertragenen Bytes fest. Wir übertragen 1024 Bytes, dies sind 400 Bytes in Hexadezimalschreibweise. Low-Byte ist Null und Hi-Byte ist 4.
- Register 8 und 9 brauchen wir nicht, also setzen wir sie auf Null.
- Zeile 80 startet nun die Übertragung. Dafür sorgt der Wert 252 im Kommandoregister 1.
- Ab Zeile 120 geht's andersrum: Die Adressen in der REU und im Computer bleiben natürlich gleich, die Register 9 und 10 brauchen wir auch nicht mehr zurückzusetzen. Die einzige Veränderung: Ins Kontrollregister kommt der Wert 253, der eine Übertragung von der REU zum Computer startet.
Et voila! Am besten, ihr tippt das mal ab und probiert es selbst aus. Ansonsten fehlt euch das Erfolgserlebnis und die trockene Terrorie hier steigt euch über den Kopf.
Der Übersichtlichkeit wegen hier die wichtigsten Werte für das Kontrollregister 1 ($DF01):
- 252 STASH (C64 -> REU)
- 253 FETCH (REU -> C64)
- 254 SWAP (C64 <> REU)
Es gibt natürlich noch einige mehr, aber seien wir geduldig.
Hier noch ein universelles Basic-Listing, das ihr in eure eigenen Programme einbauen könnt. Ihr müßt nur die Variablen an das Unterprogramm übergeben.
0 RE=57088
10 REM *** HAUPTPROGRAMM ***
20 CS=1024 : REM STARTADRESSE COMPUTER
21 RS=0 : REM STARTADRESSE REU
22 RB=0 : REM BANK IN DER REU
23 AB=1024 : REM ANZAHL DER BYTES
24 CO=252 : REM KOMMANDO STASH
28 GOSUB 10000
30 REM *** WEITER IM PROGRAMM ***
999 REM *** UNSERE SUBROUTINE ***
1000 POKE RE+3,CS/256
1001 POKE RE+2,CS-PEEK(RE+3)*256
1002 POKE RE+5,RS/256
1003 POKE RE+4,RS-PEEK(RE+5)*256
1004 POKE RE+6,RB
1005 POKE RE+8,AB/256
1006 POKE RE+7,AB-PEEK(RE+8)*256
1007 POKE RE+9,0:POKE RE+10,0
1008 POKE RE+1,CO
1009 RETURN
Dieses Beispiel kopiert, wie gehabt, den Bildschirm in die REU-Bank 0. Durch Übergabe anderer Werte an das Unterprogramm habt ihr die REU voll im Griff.
6) Eine weitere Überlegung
Wie wär's denn damit:
Ihr kennt das Problem, ihr habt ein Basic-Programm gecodet, groß und schön, es werden auch viele DATAs eingelesen. Das Programm nimmt seinen Verlauf und am Ende ist ein Neustart erforderlich (z.B. Game Over bei einem Spiel), damit die Variablen wieder die Anfangswerte haben. Also wieder alle DATAs einlesen.
Und warten.
Wir jedoch brauchen dies nicht, denn wir sind klug. Am Programmstart, nachdem alle Variablen ihren Wert besitzen, kopieren wir die ganzen Variablen einfach in die REU und bei einem Neustart holen wir alles wieder zurück. Wir übergeben an die obige Subroutine dazu folgende Werte:
20 CS=PEEK(45)+256*PEEK(46)
21 RS=0
22 RB=1 : REM ZUR ABWECHSLUNG BANK 1
23 AB=40960-(PEEK(45)+256*PEEK(46))
24 CO=252
25 GOSUB 10000
26 CS=45:RS=40960:RB=1:AB=8:CO=252
27 GOSUB 10000
Es wird der gesamte Speicher vom Beginn der Variablen bis zum Ende des Basic-Speichers in REU-Bank 1 übertragen. In Zeile 26 werden die Zeropageadressen der Variablengrenzen in die REU-Bank 1 ab 40960 gebracht. Um alle diese Werte zurückzubekommen ist an das Unterprogramm genau das gleiche abzuliefern, nur mit dem Kommandocode CO=253. Einfacher wäre es, den gesamten Speicher in die REU zu bringen, einschließlich der Zeropage, dann würde unser Programm aber gehörig durcheinandergeraten, da dann z.B. der Zeiger auf das nächste Element im Basic-Text nicht mehr stimmen würde, ebenso der Stack.
Und das ist nicht gut.
Habt ihr es schon bemerkt? Mit dieser Routine ist noch SEHR VIEL MEHR möglich als nur dies.
Denkt mal einen Schritt weiter.
7) Einen Schritt weitergedacht
Ihr schreibt an einem Dateiprogramm, z.B. um eure vielen Freundinnen zu verwalten. Und plötzlich reicht das RAM nicht!
Dann teilt es euch doch folgendermaßen auf:
- Freundinnen A-L laden und String- (bzw. Array-)Speicher in Bank 0 klopfen.
- Freundinnen M-Z laden und ab dafür in Bank 1.
- Je nach Notwendigkeit kommt nun Bank Null oder Bank Eins in den Compi, wird dort begutachtet, eventuell verändert und dann wieder in die REU kopiert.
- Und zum Speichern auf Diskette alles wieder zurück. Und wenn euch 2 Bänke (128 KB) für euer Vorhaben nicht reichen, dann teilt es doch in drei Häppchen, oder vier,...
Das alles in Basic und blitzschnell!
Beachtet aber unbedingt dies: Alle jemals im Programm vorkommenden Variablen, und sei es nur eine pupsige Schleifenvariable, muß vor der ersten REU-Aktion schon definiert sein, zur Not halt mit einem Dummy, z.B. X=0. Ansonsten verschieben sich die Variablen im Speicher und die Pointer zeigen auf ganz falsche Werte.
Die Variablen im Computer werden von denen in der REU natürlich überschrieben. Werte, die sich verändert haben und wichtig sind, z.B. AZ für die Anzahl der Freundinnen sollten vorher in Sicherheit gepoket werden, z.B. in den Kassettenpuffer.
Wenn ihr dies beachtet, dann könnt ihr Programme schreiben, wie noch nie jemand zuvor. Und jetzt denkt noch einmal einen Schritt weiter. Seht euch in der Rolle des Anwenders, der verschiedene Programme in verschiedenen Bänken der REU haben kann und im Direktmodus zwischen diesen hin und herwechselt. Eine Textverarbeitung, ein Sprite-Editor, ein Assembler, alles zusammen in der REU...
Günstigerweise befindet sich irgendwo ein Assemblerprogramm, das alles übernimmt und nur noch aufgerufen werden braucht.
Und das geht so:
£ba $010b ;Diese Startadresse ist $010b=SYS 267
lda #252
£by $2c ;Diese Startadresse ist $010e=SYS 270
lda #253
gemeinsam:
sta werte
jsr $aefd ;prüft auf Komma
jsr $b79e ;holt Bank nach x
stx werte+5 ;ab in Tabelle
sei ;kein Interrupt
lda 1 ;Prozessorport
pha ;merken
lda #$35 ;BASIC & Kernal aus
sta 1
ldx #$09 ;neun Werte
loop1:
lda werte,x ;laden und
sta $df01,x ;in REC speichern
dex
bpl loop1
pla ;alte Speicherkonfig.
sta 1 ;wiederherstellen
cli ;Interrupt freigeben
rts ;Programmende
werte:
£by 252,1,8,0,0,0,$ff,$c7,0,0
Vorteil dieses Programms gegenüber RAMDOS von der Test/Demo-Disk: Es ist viel kürzer, es ist nicht nur schneller geladen (1 Block) sondern auch schneller in der Ausführung. Und es überlebt einen Reset völlig schadlos.
Ein Nachteil ist, daß gepackte Programme denselben Speicherbereich beanspruchen, unser Programm also überschreiben. Aber dagegen ist RAMDOS auch nicht gefeit.
Die Verfahrensweise ist folgende: Zuerst dieses Programm absolut (,8,1) laden. Danach die gewünschten Programme laden und nach jedem Programm
SYS267,Bank
eingeben. Soll das Programm in Bank 3, ist die Eingabe also:
SYS267,3
Um ein Programm zurückzuholen genügt ein:
SYS270,Bank
also zum Beispiel:
SYS270,3
Wird ein gepacktes Programm gestartet, so ist unser Programm bei Bedarf erneut zu laden. Die Daten in der REU bleiben davon unberührt!
Mit dem was wir gelernt haben verstehen wir auch bereits, was geschieht:
- Basic & Kernal werden ausgeblendet. (Adresse 1 auf #$35)
- Dem REC wird mitgeteilt, daß ab $0801, dem Basic-Start, $C7FF Bytes in die gewünschte Bank zu übertragen bzw. von dieser in den C64 zu bringen sind. Die $C7FF Bytes umfassen den Bereich bis $D000.
Wozu soll das nur alles gut sein?
Denkt doch mal nach. Ihr habt die REU im Port also kein Action Replay, Final Cartridge o.ä. Nun codet ihr ein Basic-Programm und wollt euch ein Directory ansehen. Und ihr habt kein Supra-Dos oder Jiffy-Dos.
Schade.
Doch die Qualen sind nun vorbei. Mit SYS267,0 das Programm in die REU geschaufelt, Directory geladen und begutachtet, und mit SYS270,0 die Mühen vieler Stunden wieder zurückgeholt. Für Basic-Coder ist dies ideal,
ABER
erkennt ihr's? Auf diese Art kann man nur 202 Blocks große Programme in die REU bringen. Schalten wir den IO-Bereich auf RAM, können wir dem REC ja keine Mitteilungen mehr machen.
Oder doch?
Damit beschäftigen wir uns jetzt!