Erstellt: 29. September 2013 (zuletzt geändert: 13. Januar 2022)

REU Programmierung

Mit Assembler auf den Speicher der REU zugreifen

C64 Studio & AMCE

Da ich mittlerweile selbst im Besitz zweier REUs 1764 bin, wollte ich natürlich auch gleich mit einem eigenen Programm deren Funktion testen. Wer selbst etwas über die Programmierung der REU erfahren möchte, ist hier also an der richtigen Stelle.

Habt ihr bisher noch keine REU, so könnt ihr euch dank Emulatoren oder dem TC64 aber trotzdem mit der Programmierung dieser Erweiterung beschäftigen:

VICE
Startet VICE und wählt den Menüpunkt „Einstellungen -> Erweiterungsmodul I/O Einstellungen -> REU Einstellungen…“ aus. Setzt den Haken bei „RAM Speichererweiterung aktivieren“ und wählt 256KB (entspricht der 1764) als Größe. Am besten speichert ihr die Einstellungen erstmal. Damit wäre auch alles vorbereitet. Ich selbst habe meine Programme auch mit WinVICE und aktivierter REU entwickelt. Anschließend aber auch auf dem Turbo Chameleon 64 und einem echten C64 getestet. Wie sich rausstellte war das auch notwendig (s. ganz unten).

Turbo Chameleon 64
Ruft das Menü auf und wählt „Options“ an, hier etwas nach unten scrollen und unter „REU size“ 256KB einstellen. Im Hauptmenü wirkt sich der Punkt [F2] Clear ALL Memory nun auch auf den REU-Speicher aus. Auch hier solltet ihr die Einstellungen erstmal speichern.
Wer sich keine echte REU anschaffen möchte, der ist mit der simulierten REU des TC64 sehr gut bedient. Sie funktioniert bisher einwandfrei und ihr kommt so in den Genuß von bis zu 16MB zusätzlichen Speicher! Modifizierte REUs mit mehr als 2MB sind da schon schwerer zu finden.

Funktionsweise

Die REU ist, ähnlich wie das GeoRAM, in Speicherbänke (banks) unterteilt. Allerdings hat bei der REU eine Bank 64KB (beim GeoRAM waren es 16KB). Da wir wieder ein Byte zur Auswahl einer Bank haben, kommen wir so (in der Theorie) auf einen max. Speicher von 64KB * 256 = 16384KB = 16MB.
Wir können (im Gegensatz zum GeoRAM) nicht direkt auf den Speicher der REU zugreifen! Für die Kommunikation verfügt die REU über den RAM Expansion Controller (REC). Diesen blendet die REU, wie von anderen Modulen gewohnt, ab der Adresse $df00 ein.

Funktionsprinzip der REU (Grafik aus dem 1764-Handbuch)
Funktionsprinzip (Grafik aus dem 1764-Handbuch)

 

Wie bei „RAM Expansion Unit (REU)“ beschrieben, werden die Daten eigenständig und schnell per DMA kopiert, ausgetauscht und verglichen, aber wo Licht ist, ist auch Schatten (jedenfalls etwas).

Während des DMA Zugriffs wird die CPU des C64 angehalten! Die anderen Chips laufen allerdings weiter, d. h. dass z. B. der TIMER unerkannt über einen Punkt hinweg laufen kann, den ihr eigentlich abfangen möchtet. Dies mag in der Praxis eher selten ein Problem sein, aber behaltet es im Hinterkopf.
Außerdem kann man, im Gegensatz zum GeoRAM, kein Programm direkt im „neuen“ Speicher ausführen. Bei der REU müssen die Bytes immer per DMA zum C64 übertragen werden. Das GeoRAM blendet dagegen je 256 Bytes im Speicher des C64 ein, somit kann der C64 auch direkt auf dort befindliche Programmteile zugreifen. Ihr müsst also beim GeoRAM nicht zwingend kopieren.

Die Befehle des RAM Expansion Controllers

Der RECinfoInfo(R)AM (E)xpansion (C)ontroller bietet uns vier einfache Befehle, mit denen wir auf die Daten zugreifen können:

  • STASH: Daten vom C64 zur REU kopieren
  • FETCH: Daten aus der REU zum C64 kopieren
  • SWAP: Daten zwischen C64 und REU austauschen
  • VERIFY: Daten im C64 und in der REU vergleichen

Um diese Befehle nun zu nutzen, finden wir, wie eben erwähnt, ab der Adresse $df00 die insg. 11 Register des RECinfoInfo(R)AM (E)xpansion (C)ontroller:

AdresseBezeichnungFunktion
$DF00Status-RegisterDer Status kann nur gelesen werden!
BIT
7 : Interrupt aufgetreten (= 1)
6 : Transfer beendet (= 1)
5 : Verify-Fehler (= 1)
4 : Größe (0 = 128KB / 1=ab 256KB)
3-0 : Version (???)

Die BITs 7-5 werden beim Lesen gelöscht!
Die Größe und Version wurde nie richtig implementiert und ist somit kaum zu gebrauchen.
$DF01Kommando-RegisterHier erwartet die REU den Befehl, der ausgeführt werden soll. Dieses Register sollte (in der Regel) erst ganz am Ende geschrieben werden, da die REU sofort mit der Ausführung beginnt (einzige Ausnahme, wenn BIT 4 gesetzt ist). Das Verhalten der Hauptbefehle (BIT 1-0) kann durch die anderen BITs beeinflusst werden.
BIT
7 : Befehl ausführen (immer 1)
6 : <<< unbenutzt >>>
5 : AUTOLOAD
(= 1 -> Register $DF02-$DF08 NACH der
Befehlsausführung wiederherstellen)
4 : aufs Schreiben nach $FF00 warten
(0=warten / 1=sofort ausführen)
3-2 : <<< unbenutzt >>>
1-0 : Der gewünschte Befehl:
00 = STASH (C64 --> REU)
01 = FETCH (REU --> C64)
10 = SWAP (C64 <-> REU)
11 = VERIFY (C64 ??? REU)

Damit die REU das Kommando akzeptiert, muss immer das höchste BIT gesetzt sein!
$DF02C64 AdresseLSB
$DF03C64 AdresseMSB
$DF04REU AdresseLSB
$DF05REU AdresseMSB
$DF06REU Bank64KB Bank festlegen (je nach Speichergröße $00 - $FF)
$DF07Anzahl der BYTESinfoInfoWieviele BYTES sollen kopiert, getauscht oder verglichen werden? LSB
$DF08Anzahl der BYTESMSB
$DF09Interrupt MaskeDer REU ermöglichen Interrupts auszulösen
BIT
7: Interrupts aktivieren (= 1)
6: Interrupt, wenn Aktion beendet (= 1)
5: Interrupt, wenn beim VERIFY ein Unterschied
gefunden wurde (= 1)
4-0: <<< unbenutzt >>>
$DF0AAdresskontrollregisterMan kann das Hochzählen der Adressen bei der Befehlsausführung verhindern.
BIT
7-6: 00 - beide hochzählen (Standard)
01 - REU Adresse nicht erhöhen
10 - C64 Adresse nicht erhöhen
11 - REU & C64 Adresse nicht erhöhen
5-0: <<< unbenutzt >>>

Ein Blick auf die Register

Bevor wir nun tatsächlich loslegen, noch ein abschließender Blick auf die Register. Dabei bedürfen $df02$df08 wohl keiner weiteren Erklärung. Dort stehen ja nur Bytes für Speicheradressen, Bank und Anzahl der Bytes.

Die ersten und letzten beiden Register, sollten wir aber nochmal etwas genauer betrachten.

$df00 – Das Statusregister

Hier können verschiedene Infomationen zur REU und unseren Aktionen ausgelesen werden. Das Register kann nur gelesen werden, dabei werden die Bits 7-5 gelöscht! Daher solltet ihr das Statusregister vor einer Aktion einmal lesen, wenn ihr es anschließend prüfen möchtet.

  • Bit 7: Steht ein Interrupt von der REU an und wurde dieser noch nicht bearbeitet, dann ist dieses Bit auf 1 gesetzt.
  • Bit 6: Dieses Bit wird am Ende eines Befehls (STASH, FETCH, SWAP, VERIFY) auf 1 gesetzt. Da die CPU aber angehalten wird, wissen wir auch so, wann der Transfer beendet ist, nämlich sobald unser Programm weiterläuft. 😉
  • Bit 5: Vergleichen wir mit dem VERIFY-Befehl zwei Speicherbereiche, dann können wir hier ablesen, ob diese identisch (0) oder unterschiedlich (1) sind.
  • Bit 4: Mit einem Bit kann die Größe natürlich nicht korrekt angezeigt werden, bei 128KB steht hier eine 0, ab 256KB eine 1infoInfoWir können also nur feststellen, ob 128KB oder mehr Speicher vorhanden ist. Die exakte Größe ab 256KB können wir hier nicht auslesen..
  • Bit 3-0: Hier soll eigentlich eine Version stehen, aber sowohl die original Anleitung, als auch „das Internet“ schweigen sich zu diesen Bits aus. Ich nehme daher an, dass die Version nie wirklich implementiert wurde.

$df01 – Das Kommandoregister

Über dieses Register bringen wir die REU dazu, den von uns gewünschten Befehl auszuführen.

  • Bit 7: Mit dem höchsten Bit teilen wir dem RECinfoInfo(R)AM (E)xpansion (C)ontroller mit, dass er unseren Befehl, jetzt ausführen soll. Normalerweise wird der Befehl sofort verarbeitet, eine Ausnahme gibt es aber, wenn Bit-4 gelöscht ist.
  • Bit 6: << unbenutzt >>
  • Bit 5: Ist dieses Bit gesetzt, dann wird das sog. AUTOLOAD ausgeführt. Hierbei werden nach Ausführung eines Befehls die Register $df02 bis $df08 wieder mit ihren Ausgangswerten gefüllt. Das spart Zeit und Arbeitsspeicher (wir benötigen ja keine weiteren Befehle, um die Werte erneut zu setzen), wenn man häufig die selben Adressen verwendet (so wie gleich in unserem Beispiel) ist das durchaus sinnvoll. Normalerweise werden diese Register während der Verarbeitung hochgezählt und müssten so erneut initialisiert werden.
    ACHTUNG: Verwendet man AUTOLOAD beim VERIFY, dann geht bei einem gefundenen Unterschied die Adresse verloren, in der dieser aufgetreten ist!
  • Bit 4: Steht dieses Bit auf 1, dann wird der Befehl sofort ausgeführt. Mit einer 0 weisen wir den REC an, solange zu warten, bis ein Schreibzugriff auf die Adresse $ff00 stattfindet. Richtig $ff00!
    Wer sich nach dem Sinn fragt:
    Stellt euch vor, ihr möchtet auf den RAM-Speicher von $df00-$dfff zugreifen. Dann muss der IO-Bereich ausgeblendet werden. Nur befinden sich dort leider auch unsere REC-Register und ein Schreiben nach $df01 landet dann natürlich nicht mehr beim REC!! Mit diesem Bit habt ihr nun die Möglichkeit, den IO-Bereich zu deaktivieren und sobald ihr etwas nach $ff00 schreibt, startet der REC den Befehl.
  • Bit 3-2: << unbenutzt >>
  • Bit 1-0: Die letzten beiden BITs bestimmen den eigentlichen Befehl.
    • 00: STASH (C64 –> REU)
      vom C64 zur REU kopieren
    • 01: FETCH (REU –> C64)
      von der REU zum C64 kopieren
    • 10: SWAP (C64 <-> REU)
      Speicher zwischen C64 und REU austauschen
    • 11: VERIFY (C64 ??? REU)
      Einen Speicherbereich im C64, mit dem in der REU vergleichen. Im Statusregister $df00 zeigt Bit-5 an, ob die Speicherbereiche identisch (0) oder verschieden (1) sind. Außerdem wird der DMA-Zugriff bei einem Unterschied beendet und man kann in den Adress- und Bank-Registern die Speicherstelle finden, an der der erste Unterschied auftrat. Die Adressen findet man wie eben erwähnt nur, wenn man auf das AUTOLOAD verzichtet!

$df09 – Die Interruptmaske

Auf Wunsch kann die REU einen IRQ auslösen, wenn eine Aktion beendet oder ein Unterschied beim VERIFY gefunden wurde. Dies ist ein ganz normaler Interrupt, der über $0314/$0315 verarbeitet werden kann. Natürlich müsst ihr in dem Fall noch mit $df00 kontrollieren, ob der IRQ wirklich von der REU kam.

  • Bit 7: Um überhaupt Interrupts von der REU zu erlauben, muss hier eine 1 stehen.
  • Bit 6: = 1, falls ein IRQ am Ende einer Aktion gewünscht wird
  • Bit 5: = 1, um einen Interrupt auszulösen, wenn beim VERIFY ein Unterschied vorliegt
  • Bit 4-0: << unbenutzt >>

$df0A – Das Adresskontrollregister

Wie beim AUTOLOAD erklärt, werden normalerweise die Adressen, die wir dem REC mitgeteilt haben, hochgezählt. Hier könnt ihr festlegen, was höchgezählt werden soll.

  • Bit 7-6: Mit diesen beiden Bits bestimmt ihr das Hochzählen.
    • 00: beide Adressen hochzählen (dies ist der Standard)
    • 01: Adresse für die REU festsetzen, nur die für den C64 hochzählen. Dies macht Sinn, wenn man z. B. einen Speicherbereich im C64 löschen möchte. Dann benötigt man nur ein Byte in der REU.
    • 10: Adresse für den C64 festsetzen, nur die für die REU hochzählen. Das Handbuch meint, man könne so z. B. Daten über IO ausgeben. Fraglich ist nur, welches C64 Gerät 1MB/Sek. verarbeiten kann. Man kann so aber auch den Speicher der REU schnell löschen und benötigt (wie eben) wieder nur ein Byte.
    • 11: Beide fixieren!?? Was man damit wohl anfangen soll???
  • Bit 5-0: << unbenutzt >>

Ein kleines Testprogramm

Wie schon beim GeoRAM , besteht die erste Herausforderung darin festzustellen, ob überhaupt eine REU vorhanden ist. Man kann nun entweder den User danach fragen oder versucht es per Programm herauszufinden. Wir wollen es per Programm ermitteln. Dazu schreiben wir einfach in die Register der REU Testwerte und prüfen, ob diese auch wieder gelesen werden konnten. Der Bereich ab $df00 ist ja für Erweiterungen gedacht. Steckt kein Modul im C64, dann gehen unsere Daten, die wir dorthin schreiben, verloren. Ist ein Modul vorhanden, dann wird es am häufigsten wohl ein ROM-Modul sein, auch da bleibt unser Schreiben erfolglos. Durch dieses Vorgehen kann es allerdings durchaus zu Problemen kommen. Angefangen bei einer fälschlich erkannten REU, bis hin zum Absturz kann vieles passieren. Da es aber kein eindeutiges Erkennungsmerkmal gibt, bleiben uns wenige Alternativen, um automatisch eine REU zu erkennen.

Prüfen wir also mal, ob eine REU vorhanden ist…

;*******************************************************************************
;*** REU-Register
;*******************************************************************************
REUSTATUS           = $df00         ;Statusregister (nur lesen, wird dann gelöscht!)
REUCOMMAND          = $df01         ;Befehlsregister
REUC64RAM           = $df02         ;RAM-Adresse im C64 (LSB/MSB)
REURAM              = $df04         ;Speicher Adresse in der REU (LSB/MSB)
REUBANK             = $df06         ;Bank in der REU
REUBYTES            = $df07         ;Anzahl der betroffenen BYTES (LSB/MSB)
REUIRQMASK          = $df09         ;Interruptmaske
REUADRCONTROL       = $df0a         ;Adress-Kontroll-Register

;*******************************************************************************
;*** REU-Befehle
;*******************************************************************************

;*** Standardbefehle mit AUTOLOAD, ohne $ff00
REU_STASH_A__       = $fc           ;kopiere C64 -> REU
REU_FETCH_A__       = $fd           ;kopiere REU -> C64
REU_SWAP_A__        = $fe           ;Speicherbereich tauschen
REU_VERIFY_A__      = $ff           ;Speicherbereich vergleichen

;*** mit AUTOLOAD und mit $ff00
REU_STASH_A_F       = $ec           ;kopiere C64 -> REU
REU_FETCH_A_F       = $ed           ;kopiere REU -> C64
REU_SWAP_A_F        = $ee           ;Speicherbereich tauschen
REU_VERIFY_A_F      = $ef           ;Speicherbereich vergleichen

;*** ohne AUTOLOAD, ohne $ff00
REU_STASH____       = $dc           ;kopiere C64 -> REU
REU_FETCH____       = $dd           ;kopiere REU -> C64
REU_SWAP____        = $de           ;Speicherbereich tauschen
REU_VERIFY____      = $df           ;Speicherbereich vergleichen

;*** ohne AUTOLOAD, mit $ff00
REU_STASH___F       = $cc           ;kopiere C64 -> REU
REU_FETCH___F       = $cd           ;kopiere REU -> C64
REU_SWAP___F        = $ce           ;Speicherbereich tauschen
REU_VERIFY___F      = $cf           ;Speicherbereich vergleichen


!zone main
;*** Startadresse 
*=$0801
;** BASIC-Zeile: 2018 SYS 2062
 !word main-2, 2018 
 !byte $9e
 !text " 2062"
 !byte $00,$00,$00

main
 jsr checkForREU                    ;prüfen ob eine REU vorhanden ist
 cpy #$00                           ;Y-Reg. = 0?
 beq found                          ;wenn ja, weiter bei found:
 lda #$02                           ;sonst rot
 sta $d020                          ;in den Rahmen
 rts                                ;zurück zum BASIC
found
 lda #$05                           ;REU gefunden, also grün
 sta $d020                          ;in den Rahmen
 rts                                ;zurück zum BASIC

Am Anfang gibt es wieder jede Menge Variablen / Konstanten. In main springen wir aktuell nur nach checkForREU, um festzustellen, ob überhaupt eine REU angeschlossen ist. Falls ja, wird der Rahmen grün, sonst rot eingefärbt. Da endet das Programm dann auch schon wieder.

Hinter dem rts kommt nun unsere Prüfroutine. Diese funktioniert NICHT so wie die im BASIC-Programm von der originalen Commodore REU-1764-Demo-Disk. Das Statusregister $df00 liefert den Wert 0, bei 128KB. Das ergibt sich auch aus der Doku, Bit-4 ist erst ab 256KB gesetzt. Die Prüfung der Demo-Disk baut aber darauf auf, dass das Register niemals null sein kann (bei größeren REUs stimmt das auch).

Daher habe ich meine Prüfung etwas anders angelegt.

!zone checkForREU
;*******************************************************************************
;*** Prüfen, ob eine REU angeschlossen ist. 
;*** Um auch die 1700 zu erkennen wird NICHT so, wie auf der 1764 Demo-Disk mit
;*** #$00 geprüft. Bei der 1700 kann $df00 den Wert #$00 annehmen!!
;*******************************************************************************
;*** Übergabe: -
;*******************************************************************************
;*** Rückgabe: Y = 0 REU vorhanden, sonst nicht!
;*******************************************************************************
;*** ändert  : A, X, Y, SR
;*******************************************************************************
checkForREU
 ldy #$ff                           ;Erstmal von keiner REU ausgehen
 lda REUSTATUS                      ;Status-Register lesen, um Bit 7-5 zu löschen
 sty REUSTATUS                      ;schreiben wir zum Test mal #$ff hinein
 cpy REUSTATUS                      ;und schauen, ob der Wert erhalten bleibt
 beq .exit                          ;wenn JA, KEINE REU!!!
 ldx #$04                           ;Wir testen nur Register 2-5 
.loop
 txa
 sta REUCOMMAND,X                   ;Testwert speichern
 cmp REUCOMMAND,X                   ;gleicher Wert?
 bne .exit                          ;wenn nicht, KEINE REU!!!
 dex                                ;Schleifenzähler verringern
 bne .loop                          ;wenn > 0, nochmal
 ldy #$00                           ;sonst wurde eine REU gefunden
.exit
 rts                                ;zurück

Im Y-Register geben wir eine 0 zurück, wenn eine REU gefunden wurde. Zu Beginn gehen wir aber erstmal davon aus, dass es keine gibt und füllen Y mit #$ff. Dann lesen wir das Statusregister, dadurch werden die Bits 7-5 gelöscht. Da der Status nicht geschrieben werden kann, muss das anschließende Speichern von #$ff im Statusregister wirkungslos bleiben. Falls das erneute Auslesen des Status aber wieder den selben Wert #$ff zurückgibt, wissen wir, dass keine REU vorhanden sein kann. Um jetzt noch etwas sicherer zu sein, schreiben wir in einige Schreib- / Leseregister der REU, Testwerte und prüfen, ob diese erhalten bleiben.

Wichtig ist, dass wir nicht alle 11 Register der REU zum Testen verwenden können. $df01 lassen wir z. B. gleich mal aus, wir wollen ja nicht versehentlich einen Befehl abschicken.
Hier könnt ihr jetzt erkennen, warum solche Prüfungen durchaus gefährlich sind. Würde ein Programm diese Stellen, so wie wir, zum Testen auf ein anderes Modul füllen, könnte es passieren, dass ein Rechner mit einer REU abstürzt. Bei einer REU würde ein Schreiben nach $df01 evtl. einen Kopiervorgang auslösen und könnte dabei wichtige Speicherbereiche „zerstören“ (überschreiben).
Wir prüfen jetzt nur noch $df02 – $df05. Die restlichen Register (mit Außnahme von $df07 & $df08) liefern nach dem Schreiben nicht unbedingt den hineingeschriebenen Werte wieder zurück.

Also schreiben wir in einer Schleife einfach unseren Schleifenzähler (im X-Register) in das jeweilige Register. Dann vergleichen wir es mit unserem X-Register, wenn der Wert übereinstimmt ist alles OK und wir prüfen das nächste Register, sonst liegt keine REU vor und wir brechen ab. Wurden alle vier Register erfolgreich geprüft, laden wir eine Null ins Y-Register und kehren zum Aufrufer zurück.

Ein Start sollte mit einem grünen Rahmen anzeigen, dass eine REU gefunden wurde, sonst ist der Rahmen rot.

Die REU in Aktion

Achtung: Es wird ein Interrupt verwendet, schaut euch bei Bedarf bitte zumindest den Beitrag Interrupts vorher einmal an.

Da wir jetzt wissen, dass vermutlich eine REU vorhanden ist, was können wir nun mit ihr anfangen? Eine REU ist natürlich perfekt für einen schnellen Datenaustausch geeignet. Ihr könnt z. B. alle einmal geladenen Teile eures Spiels oder einer Demo dort ablegen. Diese können dann bei Bedarf blitzschnell erneut geladen werden. Daher ist auch ein häufiges Einsatzgebiet eine RAM-Disk. Stellt euch vor, ihr möchtet am C64 entwickeln. Nun braucht ihr einen Assembler, Sprite-Editor, Character-Editor, ein Grafik- und Soundprogramm. All dies könntet ihr in die REU packen und dann per Tastendruck oder Befehl das jeweilige Programm laden. Es ist dann praktisch sofort verfügbar.
Wir machen dies in unserem kommenden Beispiel mal in einer vereinfachten Form. Wir werden ein Programm schreiben, dass den aktuellen Bildschirminhalt speichern, wiederherstellen, austauschen und vergleichen kann. Das Programm läuft neben dem BASIC und wird über die Funktions-Tasten bedient.

Wie ihr am Anfang bereits erfahren habt und auch der Tabelle, sowie dem ersten Listing entnehmen könnt, bietet uns die REU im Grunde nur vier einfache Kommandos: STASH, FETCH, SWAP und VERIFY. Wie es der Zufall mal wieder so will, entsprechen diese vier Befehle exakt unseren vier Funktionen von eben. Also Zufälle gibt es… 😉

Fügt doch bitte noch diese Konstante zum Programm hinzu:

SCREENRAM           = $0400         ;Adresse des Bildschirmspeichers

Dann ersetzt alles zwischen *=$0801 und dem rts mit unserem neuen Programmbeginn.

!zone main
;*** Startadresse 
*=$0801
;** BASIC-Zeile: 2018 SYS 2064:NEW
 !word main-2, 2018 
 !byte $9e
 !text " 2064:"
 !byte $a2,$00,$00,$00

;*** ab hier folgt das eigentliche Programm
main
 jsr checkForREU                    ;prüfen ob eine REU vorhanden ist
 cpy #$00                           ;Y-Reg. = 0?
 bne .error                         ;wenn nein, weiter bei error:
 sei                                ;IRQs sperren  
 lda #<myIRQ                        ;unsere IRQ-Routine eintragen
 sta $0314
 lda #>myIRQ
 sta $0315
 cli                                ;IRQs wieder freigeben

 ;*** Da wir nur AUTOLOAD verwenden, bleiben die REC-Register unverändert.
 ;*** Es reicht also, diese einmalig am Beginn zu setzen.
 lda #<SCREENRAM                    ;Beginn des Bildschirmspeichers der  
 sta REUC64RAM                      ;REU als C64-Speicherbereich mitteilen
 lda #>SCREENRAM
 sta REUC64RAM+1

 lda #<(25*40)                      ;Die Anzahl der BYTEs (25*40 = 1000 = $03E8)
 sta REUBYTES                       ;je Aktion der REU bekannt machen
 lda #>(25*40)
 sta REUBYTES+1

 lda #$00                           ;wir verwenden die 
 sta REUBANK                        ;1. Bank (lfd.-Nr. 0)
 sta REURAM                         ;und beginnen auch direkt ab Adresse $0000
 sta REURAM+1                       ;in der Bank

 lda #$05                           ;REU gefunden, also grün
 jmp .exit
 
.error
 lda #$02                           ;keine REU, also rot

.exit
 sta $d020                          ;in den Rahmen
 rts                                ;zurück zum BASIC

Außer der veränderten BASIC-Zeile ist der Anfang nahezu identisch geblieben. Wir prüfen ob eine REU vorhanden ist. Falls nicht springt das Programm zu .error, setzt die Rahmenfarbe auf rot und wird beendet. Ist aber eine REU vorhanden, wird als Erstes der IRQ-RAM-Vektor auf unsere Interruptroutine myIRQ umgebogen. Dann können wir schon mal einige REC-Register füllen. Da wir ausschließlich AUTOLOAD verwenden, bleiben unsere Werte in den Registern erhalten. Außerdem verwenden wir nur einen bestimmten Bereich, mit immer der selben Länge, es reicht diese Werte hier einmalig zu setzen. Also teilen wir der REU erstmal mit, auf welchen Speicherbereich des C64 wir zugreifen wollen. Da wir den Bildschim retten möchten, beginnt der für uns relevante Speicher ab $0400 / SCREENRAM (wir beschränken uns auf den Standard). Danach müssen wir die Anzahl der Bytes setzen, die wir benötigen. Der Bildschirm hat 25 Zeilen zu je 40 Zeichen, also 1000 (oder #$03E8) Zeichen. Jetzt müssen wir noch bestimmen, welchen Speicherbereich in der REU wir verwenden möchten. Dies geschieht einmal über die Bank, wir nehmen die erste mit der Nr. 0 und über die Adresse innerhalb der gewünschten Bank. Diese Adresse setzen wir einfach auf $0000 und beginnen somit mit dem ersten Byte der REU. Genug der Vorbereitungen, jetzt noch den Rahmen grün einfärben und dann zurück zum BASIC.

Kommen wir nun zu unserer Interrupt-Routine. Diese kümmert sich um die Tastaturabfrage (werft ggf. einen Blick auf den Beitrag Tastatusmatrix), löst evtl. die REU-Funktion aus und springt schließlich zur Systemroutine nach $ea31. Fügt sie einfach hinter die hoffentlich nicht gelöschte Funktion checkForREU ein.

!zone myIRQ
*=$c000                             ;Platz für ein BASIC-Programm lassen
myIRQ
 ldx lastInput                      ;letzte Eingabe ins X-Reg.

 lda #%11111110                     ;PA0
 sta $DC00                          ;markieren
 lda $DC01                          ;Tastaturmatrix 'lesen'
 eor #$FF                           ;da low aktiv, umkehren
 and #%01111000                     ;benötigte Bits ausmaskieren
 sta lastInput                      ;als letzte Eingabe merken
 beq .exit                          ;wenn keine gesuchte Taste gedrückt -> .exit
 cpx #$00                           ;prüfen ob letzte Eingabe, KEINE Eingabe war
 bne .exit                          ;falls nicht -> .exit

 asl                                ;sonst Eingabe verarbeiten, 7. Bit kann weg
 asl                                ;prüfen, ob das 6. Bit (F5/F6) gesetzt ist
 bcc .skip1                         ;falls nicht -> nächste Taste prüfen
 ;F5/F6 
 jmp swapScreen                     ;sonst Bildschirm und REU-Speicher tauschen

.skip1 
 asl                                ;5. Bit (F3/F4) gesetzt?
 bcc .skip2                         ;nein -> nächste Tasten
 ;F3/F4 
 jmp restoreScreen                  ;sonst Bildschirm wiederherstellen

.skip2
 asl                                ;4. Bit (F1/F2) gesetzt?
 bcc .skip3                         ;nein -> letzte Möglichkeit prüfen
 ;F1/F2 
 jmp saveScreen                     ;sonst Bildschirm speichern

.skip3
 asl                                ;3. Bit (F7/F8) gesetzt?
 bcc .exit                          ;nein -> .exit
 ;F7/F8
 jmp verifyScreen                   ;sonst vergleichen

.exit
 jmp $ea31                          ;zur Systemroutine

Um Platz für ein BASIC-Programm zu lassen, beginnt unsere Interruptroutine erst bei *=$C000 (das ist natürlich nicht schön gelöst, aber für unser Beispiel soll es jetzt einfach mal so sein). Da wir bei einem Tastendruck nicht dutzendfach eine Funktion auslösen wollen, merken wir uns den letzten Wert in lastInput, diesen holen wir zu Beginn des Interrupts ins X-Register. Dann prüfen wir, ob eine Tastatureingabe vorliegt. Da uns nur die F-Tasten (F1-F7) interessieren maskieren wir PA0 aus. Anschließend schauen wir nach, ob dort eine Taste betätigt wurde. Da die Werte low aktiv sind (0 = Taste gedrückt) kehren wir die zum besseren Verständnis einfach um und maskieren die vier von uns gewünschten Tasten (Bit 6-3) aus. Diesen Wert speichern wir bei lastInput, sollte er null sein, ist aktuell keine Taste gedrückt und die Funktion kann beendet werden. Sonst prüfen wir noch, ob zuletzt eine Taste gedrückt wurde. Ihr erinnert euch, dass wir im X-Register den Wert der letzten Tastatureingabe gespeichert haben? Ist die letzte Eingabe Null, können wir die aktuelle verarbeiten, sonst geht es wieder direkt zum Ende. Das machen wir, da sonst mit einem Tastendruck eine Funktion mehrfach ausgeführt wird. Der IRQ wird immerhin 60 mal in der Sekunde gestartet! Jetzt wird per links-shift das höchste Bit verworfen, bevor durch weitere Shift-Befehle auf die einzelnen Funktionstasten geprüft wird. Wir prüfen hier übrigens nur auf die Taste als solches. Ob F1 oder F2 betätigt wurde merkt das Programm nicht! Stellt das Programm fest, dass eine mögliche Taste gedrückt ist, dann wird zur entsprechenden Funktion gesprungen. Falls nicht, läuft das Programm zum Ende durch und springt schließlich zur Systemroutine nach $ea31.
Das war auch schon unsere Interrupt-Routine.

Vergesst nicht die Variable für die letzte Tastatureingabe. Fügt die einfach direkt hinter der obigen Funktion ein:

lastInput
 !byte $00                          ;letzte Tastatureingabe

Die vier Aktionen

Kommen wir endlich zu den vier Befehlen. Da wir die meisten REC-Register schon beim Programmstart gefüllt haben, bleibt jetzt nur noch wenig Arbeit. Fügt die folgenden Abschnitte einfach jeweils am Programmende ein.

saveScreen
 lda #REU_STASH_A__                 ;Befehl fürs Kopieren vom C64 zur REU
 sta REUCOMMAND                     ;mit AUTOLOAD ausführen
 lda #$0d                           ;hellgrün
 sta $d020                          ;als Rahmenfarbe
 lda #$06                           ;dunkelblau
 sta $d021                          ;als Hintergrund
 jmp $ea31                          ;zur Systemroutine

Wie ihr seht, müssen wir nur noch den Befehl (hier fürs Kopieren vom C64 zur REU) ins Register $df01 schreiben. Da die REU (bei unserem Befehl) sofort loslegt, muss dieser natürlich ganz zum Schluß erfolgen. Wie erwähnt wurden die anderen Register bereits gefüllt, also kann hier einfach kopiert werden. Anschließend wird die Rahmen- und Hintergrundfarbe verändert, bevor es zur Systemroutine geht.

Das Kopieren von der REU zum C64 und der Austausch der Speicherbereiche sind ebenso simpel.

restoreScreen
 lda #REU_FETCH_A__                 ;Befehl fürs Kopieren von der REU zum C64
 sta REUCOMMAND                     ;mit AUTOLOAD ausführen
 lda #$0e                           ;hellblau
 sta $d020                          ;als Rahmenfarbe
 lda #$06                           ;dunkelblau
 sta $d021                          ;als Hintergrundfarbe
 jmp $ea31                          ;zur Systemroutine

swapScreen
 lda #REU_SWAP_A__                  ;Befehl zum Tauschen der Speicherbereiche
 sta REUCOMMAND                     ;mit AUTOLOAD ausführen
 lda #$07                           ;gelb
 sta $d020                          ;als Rahmenfarbe
 lda #$06                           ;dunkeblau
 sta $d021                          ;als Hintergrundfarbe
 jmp $ea31                          ;zur Systemroutine

Es gibt nur andere Befehle und Farben.

Einzig beim Vergleichen müssen wir etwas mehr Arbeit investieren, es bleibt aber pipifax.

verifyScreen
 lda REUSTATUS                      ;Statusregister durchs Lesen löschen
 lda #REU_VERIFY_A__                ;Befehl fürs Vergleichen der Speicherbereiche
 sta REUCOMMAND                     ;mit AUTOLOAD ausführen
 ldx #$05                           ;erstmal dunkel grün (für ist identisch)
 lda REUSTATUS                      ;Status holen
 and #%00100000                     ;Speicher unterschiedlich?
 beq .exit                          ;nein -> .exit
 ldx #$02                           ;sonst rot (für unterschiedlich)
.exit
 stx $d021                          ;Hintergrundfarbe setzen
 jmp $ea31                          ;zur Systemroutine

Vor dem Vergleich löschen wir, durch einfaches Auslesen, das Statusregister. Dann starten wir den Vergleich durch den entsprechenden Befehl im Kommando-Register. Anschließend gehen wir erstmal davon aus, dass die Speicherbereiche identisch sind und laden die Farbe grün ins X-Register. Dann holen wir den Status der REU in den Akku und kontrollieren, ob Bit-5 gesetzt ist. Falls nein, sind die Speicherbereiche identisch und es wird zum Ende gesprungen. Steht das Bit auf 1, dann haben wir unterschiedliche Bereiche und laden rot ins ins X-Reg. Am Ende setzen wir die Hintergrundfarbe auf die Farbe aus dem X-Register und springen zur Systemroutine.

SCREENRAM           = $0400         ;Adresse des Bildschirmspeichers

;*******************************************************************************
;*** REU-Register
;*******************************************************************************
REUSTATUS           = $df00         ;Statusregister (nur lesen, wird dann gelöscht!)
REUCOMMAND          = $df01         ;Befehlsregister
REUC64RAM           = $df02         ;RAM-Adresse im C64 (LSB/MSB)
REURAM              = $df04         ;Speicher Adresse in der REU (LSB/MSB)
REUBANK             = $df06         ;Bank in der REU
REUBYTES            = $df07         ;Anzahl der betroffenen BYTES (LSB/MSB)
REUIRQMASK          = $df09         ;Interruptmaske
REUADRCONTROL       = $df0a         ;Adress-Kontroll-Register

;*******************************************************************************
;*** REU-Befehle
;*******************************************************************************

;*** Standardbefehle mit AUTOLOAD, ohne $ff00
REU_STASH_A__       = $fc           ;kopiere C64 -> REU
REU_FETCH_A__       = $fd           ;kopiere REU -> C64
REU_SWAP_A__        = $fe           ;Speicherbereich tauschen
REU_VERIFY_A__      = $ff           ;Speicherbereich vergleichen

;*** mit AUTOLOAD und mit $ff00
REU_STASH_A_F       = $ec           ;kopiere C64 -> REU
REU_FETCH_A_F       = $ed           ;kopiere REU -> C64
REU_SWAP_A_F        = $ee           ;Speicherbereich tauschen
REU_VERIFY_A_F      = $ef           ;Speicherbereich vergleichen

;*** ohne AUTOLOAD, ohne $ff00
REU_STASH____       = $dc           ;kopiere C64 -> REU
REU_FETCH____       = $dd           ;kopiere REU -> C64
REU_SWAP____        = $de           ;Speicherbereich tauschen
REU_VERIFY____      = $df           ;Speicherbereich vergleichen

;*** ohne AUTOLOAD, ohne $ff00
REU_STASH___F       = $cc           ;kopiere C64 -> REU
REU_FETCH___F       = $cd           ;kopiere REU -> C64
REU_SWAP___F        = $ce           ;Speicherbereich tauschen
REU_VERIFY___F      = $cf           ;Speicherbereich vergleichen


!zone main
;*** Startadresse 
*=$0801
;** BASIC-Zeile: 2018 SYS 2064:NEW
 !word main-2, 2018 
 !byte $9e
 !text " 2064:"
 !byte $a2,$00,$00,$00

;*** ab hier folgt das eigentliche Programm
main
 jsr checkForREU                    ;prüfen ob eine REU vorhanden ist
 cpy #$00                           ;Y-Reg. = 0?
 bne .error                         ;wenn nein, weiter bei error:
 sei                                ;IRQs sperren  
 lda #<myIRQ                        ;unsere IRQ-Routine eintragen
 sta $0314
 lda #>myIRQ
 sta $0315
 cli                                ;IRQs wieder freigeben

 ;*** Da wir nur AUTOLOAD verwenden, bleiben die REC-Register unverändert.
 ;*** Es reicht also, diese einmalig am Beginn zu setzen.
 lda #<SCREENRAM                    ;Beginn des Bildschirmspeichers der  
 sta REUC64RAM                      ;REU als C64-Speicherbereich mitteilen
 lda #>SCREENRAM
 sta REUC64RAM+1

 lda #<(25*40)                      ;Die Anzahl der BYTEs (25*40 = 1000 = $03E8)
 sta REUBYTES                       ;je Aktion der REU bekannt machen
 lda #>(25*40)
 sta REUBYTES+1

 lda #$00                           ;wir verwenden die 
 sta REUBANK                        ;1. Bank (lfd.-Nr. 0)
 sta REURAM                         ;und beginnen auch direkt ab Adresse $0000
 sta REURAM+1                       ;in der Bank

 lda #$05                           ;REU gefunden, also grün
 jmp .exit
 
.error
 lda #$02                           ;keine REU, also rot

.exit
 sta $d020                          ;in den Rahmen
 rts                                ;zurück zum BASIC


!zone checkForREU
;*******************************************************************************
;*** Prüfen, ob eine REU angeschlossen ist. 
;*** Um auch die 1700 zu erkennen wird NICHT so, wie auf der 1764 Demo-Disk mit
;*** #$00 geprüft. Bei der 1700 kann $df00 den Wert #$00 annehmen!!
;*******************************************************************************
;*** Übergabe: -
;*******************************************************************************
;*** Rückgabe: Y = 0 REU vorhanden, sonst nicht!
;*******************************************************************************
;*** ändert  : A, X, Y, SR
;*******************************************************************************
checkForREU
 ldy #$ff                           ;Erstmal von keiner REU ausgehen
 lda REUSTATUS                      ;Status-Register lesen, um Bit 7-5 zu löschen
 sty REUSTATUS                      ;schreiben wir zum Test mal #$ff hinein
 cpy REUSTATUS                      ;und schauen, ob der Wert erhalten bleibt
 beq .exit                          ;wenn JA, KEINE REU!!!
 ldx #$04                           ;Wir testen nur Register 2-5 
.loop
 txa
 sta REUCOMMAND,X                   ;Testwert speichern
 cmp REUCOMMAND,X                   ;gleicher Wert?
 bne .exit                          ;wenn nicht, KEINE REU!!!
 dex                                ;Schleifenzähler verringern
 bne .loop                          ;wenn > 0, nochmal
 ldy #$00                           ;sonst wurde eine REU gefunden
.exit
 rts                                ;zurück


!zone myIRQ
*=$c000                             ;Platz für ein BASIC-Programm lassen
myIRQ
 ldx lastInput                      ;letzte Eingabe ins X-Reg.

 lda #%11111110                     ;PA0
 sta $DC00                          ;markieren
 lda $DC01                          ;Tastaturmatrix 'lesen'
 eor #$FF                           ;da low aktiv, umkehren
 and #%01111000                     ;benötigte Bits ausmaskieren
 sta lastInput                      ;als letzte Eingabe merken
 beq .exit                          ;wenn keine gesuchte Taste gedrückt -> .exit
 cpx #$00                           ;prüfen ob letzte Eingabe, KEINE Eingabe war
 bne .exit                          ;falls nicht -> .exit

 asl                                ;sonst Eingabe verarbeiten, 7. Bit kann weg
 asl                                ;prüfen, ob das 6. Bit (F5/F6) gesetzt ist
 bcc .skip1                         ;falls nicht -> nächste Taste prüfen
 ;F5/F6 
 jmp swapScreen                     ;sonst Bildschirm und REU-Speicher tauschen

.skip1 
 asl                                ;5. Bit (F3/F4) gesetzt?
 bcc .skip2                         ;nein -> nächste Tasten
 ;F3/F4 
 jmp restoreScreen                  ;sonst Bildschirm wiederherstellen

.skip2
 asl                                ;4. Bit (F1/F2) gesetzt?
 bcc .skip3                         ;nein -> letzte Möglichkeit prüfen
 ;F1/F2 
 jmp saveScreen                     ;sonst Bildschirm speichern

.skip3
 asl                                ;3. Bit (F7/F8) gesetzt?
 bcc .exit                          ;nein -> .exit
 ;F7/F8
 jmp verifyScreen                   ;sonst vergleichen

.exit
 jmp $ea31                          ;zur Systemroutine

lastInput
 !byte $00                          ;letzte Tastatureingabe

!zone screenFunctions
saveScreen
 lda #REU_STASH_A__                 ;Befehl fürs Kopieren vom C64 zur REU
 sta REUCOMMAND                     ;mit AUTOLOAD ausführen
 lda #$0d                           ;hellgrün
 sta $d020                          ;als Rahmenfarbe
 lda #$06                           ;dunkelblau
 sta $d021                          ;als Hintergrund
 jmp $ea31                          ;zur Systemroutine

restoreScreen
 lda #REU_FETCH_A__                 ;Befehl fürs Kopieren von der REU zum C64
 sta REUCOMMAND                     ;mit AUTOLOAD ausführen
 lda #$0e                           ;hellblau
 sta $d020                          ;als Rahmenfarbe
 lda #$06                           ;dunkelblau
 sta $d021                          ;als Hintergrundfarbe
 jmp $ea31                          ;zur Systemroutine

swapScreen
 lda #REU_SWAP_A__                  ;Befehl zum Tauschen der Speicherbereiche
 sta REUCOMMAND                     ;mit AUTOLOAD ausführen
 lda #$07                           ;gelb
 sta $d020                          ;als Rahmenfarbe
 lda #$06                           ;dunkeblau
 sta $d021                          ;als Hintergrundfarbe
 jmp $ea31                          ;zur Systemroutine

verifyScreen
 lda REUSTATUS                      ;Statusregister durchs Lesen löschen
 lda #REU_VERIFY_A__                ;Befehl fürs Vergleichen der Speicherbereiche
 sta REUCOMMAND                     ;mit AUTOLOAD ausführen
 ldx #$05                           ;erstmal dunkel grün (für ist identisch)
 lda REUSTATUS                      ;Status holen
 and #%00100000                     ;Speicher unterschiedlich?
 beq .exit                          ;nein -> .exit
 ldx #$02                           ;sonst rot (für unterschiedlich)
.exit
 stx $d021                          ;Hintergrundfarbe setzen
 jmp $ea31                          ;zur Systemroutine

Schon ist unser Programm fertig. Wenn ihr es startet könnt ihr mit F1 den aktuellen Bildschirminhalt retten, F3 holt ihn zurück, F5 tauscht den aktuellen Bildschirm mit dem in der REU und F7 prüft ob der aktuelle BS und der in der REU identisch sind.
Spielt ein wenig damit rum und es wird euch sicher ein Problem auffallen. Der Cursor stört! Er wird mit gerettet, zurückgeholt und verglichen. Um das Programm zu verbessern, sollten wir den Cursor ggf. ausblenden. Wie das geht überlasse ich euch. Eine Möglichkeit ist, sich die Systemroutine bei $ea31 anzuschauen. Diese ist ja auch für das Blinken des Cursors zuständig. Schnappt euch ein kommentiertes ROM-Listing und ihr werdet schnell fündig.


Es gibt wieder ein D64-Image als Download. Dort findet ihr obiges Testprogramm (inkl. der Cursor-Bereinigung).

Als Bonus gibt es zusätzlich den…

REU-Checker V1.0

Der REU-Checker V1.0 prüft REUs von 128KB bis 16MB.
Der REU-Checker V1.0 prüft REUs von 128KB bis 16MB.

Das Programm prüft, ob eine REU vorhanden ist. Falls ja, wird die Größe der REU ermittelt. Da mir noch keine anderen Größen untergekommen sind, erkennt das Programm 128KB, 256KB, 512KB, 1MB, 2MB, 4MB, 8MB und 16MB. Bei anderen Größen wird ein Fehler angezeigt. Im Speicher des C64 wird dann ein 8KB großes Testmuster (immer die Bytes $00 – $ff) erzeugt. Am schönsten wären natürlich 64KB, so groß ist ja eine Bank, aber dazu würden wir den kompletten Speicher des C64 benötigen. Deshalb habe ich mich als Mittelweg für 8KB entschieden. Ein Zeichen im Statusbildschirm entspricht einer 64KB-Bank in der REU. Jede Bank wird komplett mit dem 8KB Muster (also acht Stück davon) gefüllt. Anschließend wird jede Bank wieder in 8KB-Schritten mit dem Muster im Speicher des C64 verglichen. Wie schon beim GeoRAM-Checker, ist der Source wieder sehr gruselig. Daher gibt es nur das ausführbare Programm, es steht euch aber frei (seht es als Übung) das Programm zu disassemblieren. Bei normal großen REUs (128KB – 512KB) ist der Test so schnell durchgelaufen, dass man kaum etwas mitbekommt. Bei größeren REUs (z. B. mit VICE oder dem Turbo Chameleon) dauert der Test auch nicht sehr lange, man sieht dann aber, was geschieht.

Traue keiner Emulation

Ich habe das Programm entwickelt, ohne im Besitz einer echten REU zu sein und mich dabei an der WinVICE-Emulation orientiert. Anschließend testete ich den REU-Checker für alle Größen von 128KB bis 16MB mit WinVICE und dem Turbo Chameleon 64. Alles lief wunderbar. Als ich dann eine umgebaute REU 1764 (512KB / 1MB) testete, klappte auch hier im 512KB-Modus zunächst alles wie erwartet. Bei 1MB versagte das Programm allerdings. Es zeigte nur 128KB statt 1MB an. Meine Befürchtung, dass die REU evtl. defekt ist, konnte ich mit einem kleinen BASIC-Testprogramm zum Glück wiederlegen. Nach einigem Suchen bin ich dann hinter das Problem gekommen. Um die Größe zu ermitteln habe ich in das erste Byte der jeweils höchsten Bank für eine Speichergröße eine laufende Nr. geschrieben. Da es acht mögliche Speicherkonfigurationen gibt, waren es somit die Zahlen 1 (128KB) bis 8 (16MB). Danach habe ich rückwärts kontrolliert, was auf der jeweils höchsten Bank im ersten BYTE steht. Wurde vom REU-Checker versucht auf eine ungültige Bank zuzugreifen, dann liefert WinVICE ein #$ff, sonst kam das von mir gespeicherte Byte zurück. Nun habe ich einfach solange gesucht, bis etwas anderes als #$ff zurück kam und hier liegt das Problem! WinVICE verhält sich anders als das Turbo Chameleon 64 oder eine echte REU (wobei meine umgebaute noch weitere Probleme veruracht)!! Mit einem kleinen BASIC-Programm habe ich die Funktionsweise der REUs getestet. Dazu habe ich in alle 256 theoretisch möglichen Bänke die Nr. der Bank (0-255) geschrieben und anschließend wieder ausgelesen. Das Turbo Chameleon und die unmodifizierte REU verhalten sich gleich. Wie beim GeoRAM werden bei der Bankwahl nur die Bits beachtet, die zur Speichergröße passen. Greift man z. B. bei 512KB auf die nicht vorhandene Bank 11 zu (es gibt ja nur acht Bänke zu je 64KB), dann wird die 4. Bank verwendet (11 = %00001011 da nur drei Bits für 512KB nötig sind, wird auf %00000011 = 3 zugegriffen, die Zählung beginnt bei 0!). Also liefert mein BASIC-Programm hier eine 3 beim TC64 und einer originalen REU, unter WinVICE aber #$ff. Starte ich das BASIC-Programm mit der umgebauten REU klappt es mit 512KB auch wie eben beschrieben, nur 1MB machen Probleme. Bei 1MB kommen total wirre Werte zurück. Beim Zugriff auf Bank 255 liefert die REU eine 1 und somit zeigte der REU-Checker fälschlich 128KB an. Dieses Verhalten liegt daran, dass 1MB (und mehr) nur durch einen massiven Eingriff in die Hardware der REU möglich sind. Sie war nie für soviel Speicher gedacht!

Ende gut, alles gut?!?

Nach dieser kleinen Odyssee habe ich das Programm dann umgebaut, sodass es jetzt sowohl mit WinVICE als auch mit meinen beiden echten REUs und dem Turbo Chameleon 64 funktioniert.
Falls jemand mal eine echte REU 1700 oder 1750 (oder größere Umbauten) testet, wäre eine Rückmeldung, ob das Programm auch dort funktioniert, sehr nett.

Umgebaute REUs (über 512KB) sind also nicht 100%ig kompatibel!

Update vom 20.03.2014

Wie in den Kommentaren gewünscht, folgt hier meine Routine, um die Speichergröße der REU zu ermitteln. Die Routine geht davon aus, dass eine REU vorhanden ist! Ihr solltet also vorher besser prüfen, ob dies der Fall ist, sonst bekommt ihr evtl. ein falsches Ergebnis!!

;*******************************************************************************
;*** REU-Register
;*******************************************************************************
REUSTATUS           = $df00         ;Statusregister (nur lesen!)
REUCOMMAND          = $df01         ;Befehlsregister
REUCOMPUTERRAM      = $df02         ;RAM-Adresse im Computer (LSB/MSB)
REURAM              = $df04         ;Adresse der REU (LSB/MSB)
REUBANK             = $df06         ;Bank in der REU
REUBYTES            = $df07         ;Anzahl der betroffenen Bytes (LSB/MSB)
REUIRQMASK          = $df09         ;Interruptmaske
REUADRCONTROL       = $df0a         ;Adress-Kontroll-Register

;*******************************************************************************
;*** REU-Befehle
;*******************************************************************************

;*** Standardbefehle mit AUTOLOAD, ohne $ff00
REU_STASH_A__       = %10110000     ;kopiere RAM -> REU
REU_FETCH_A__       = %10110001     ;kopiere REU -> RAM
REU_SWAP_A__        = %10110010     ;Speicherbereich tauschen
REU_VERIFY_A__      = %10110011     ;Speicherbereich vergleichen

!zone main
;*** Startadresse 
*=$0801
;** BASIC-Zeile: 2018 SYS 2062
 !word main-2, 2018 
 !byte $9e
 !text " 2062"
 !byte $00,$00,$00

main
 jsr checkREUSize                   ;Größe der REU ermitteln
 tya                                ;in Y steht die Größe (1-8)
 clc
 adc #"0"                           ;für BS-Ausgabe das Zeichen 0 addieren
 sta $0400                          ;Ergebnis anzeigen
 rts                                ;zurück zum BASIC



!zone checkREUSize
;*******************************************************************************
;*** Größe der REU ermitteln
;*** Da man mit dem Statusregister ($df00 / REUSTATUS) die Größe nicht
;*** feststellen kann, gehen wir den 'harten' weg.
;*** Ausgehend davon, dass die REU-Größen sich ab 128KB immer verdoppeln.
;*** Prüfen wir einfach die entsprechenden Pages, ob wir dort etwas ablegen
;*** konnten. 
;*******************************************************************************
;*** Übergabe: -
;*******************************************************************************
;*** Rückgabe: Y = $00: FEHLER / Größe nicht feststellbar!
;***               $01: 128KB
;***               $02: 256KB
;***               $03: 512KB
;***               $04:   1MB
;***               $05:   2MB
;***               $06:   4MB
;***               $07:   8MB
;***               $08:  16MB
;***
;***           REUBankCount: = Anz. der verfügbaren Bänke minus 1
;***                           $00 = FEHLER! 
;*******************************************************************************
;*** ändert  : A, X, Y, SR
;*******************************************************************************
checkREUSize
;*** 1. Schritt auf die jeweils höchste Page ein Kennzeichen speichern
 ldx #<testpattern                  ;Testpattern als Quelle
 stx REUCOMPUTERRAM                 ;LSB
 ldx #>testpattern             
 stx REUCOMPUTERRAM+1               ;MSB

 ldx #$00                           ;immer das erste Byte der Page verwenden
 stx REURAM
 stx REURAM+1
 stx REUIRQMASK                     ;keine IRQs
 stx REUADRCONTROL                  ;beide Adressen hochzählen
 stx REUBankCount                   ;erstmal Anzahl der Bänke auf 0 setzen
 stx REUBYTES+1                     ;wir brauchen nur ein Byte
 ldx #$01                       
 stx REUBYTES
 ldy #$08                           ;Kennzeichen für die Speichergröße 
 lda #$ff                           ;Höchste Bänke von 16MB bis 128KB
.loop
 sta REUBANK                        ;REU-Bank auswählen
 sty testpattern                    ;Testpattern füllen (nur 1 Byte)
 dey                                ;für nächsten Durchlauf verringern!
 ldx #REU_STASH_A__                 ;Einzelnes Byte vom C64 in die REU
 stx REUCOMMAND                     ;kopieren
 lsr                                ;Höchste Bank für nächste Speicherkonfiguration
 bne .loop                          ;wenn größer 0, nächste Bank 
;*** 2. Schritt: Die jeweils höchste Bank testen, ob unser Wert drin steht
;*** Die REU-Parameter bleiben unverändert (s. oben)
 lda #$00                           ;erste 'höchste' Bank (128KB) in den Akku
 ldy #$00
.loop1
 sec
 rol                                ;nächste 'höchste' Bank
 sta REUBANK                        ;Bank auswählen
 ldx #REU_FETCH_A__                 ;Ein Byte von der REU zum C64 
 stx REUCOMMAND                     ;kopieren
 iny
 cpy testpattern
 beq .loop1                         ;wenn die Werte identisch sind, nächst Bank
 dey
 tya                                ;'Größe' in den Akku, um den Wert
 tax                                ;ins X-Register zu bekommen.
 lda #$00                           ;Akku auf null setzen
.loop2
 sec                                ;C-Flag setzen
 rol                                ;und 'reinrollen' ;-)
 dex                                ;Schleifenzähler verringern
 bne .loop2                         ;solange nicht null, nochmal
 sta REUBankCount                   ;Anz. der Bänke-1 merken / #$00 = Fehler!!
 rts                                ;sonst, zurück zum Aufrufer

REUBankCount
 !byte $00                           ;Anzahl der REU-Bänke - 1! #$00 = Fehler!!

;*** Platzhalter für das Testmuster
testpattern

Das Programm geht davon aus, dass eine REU vorhanden ist! Die Routine checkREUSize ermittelt die Größe, indem sie in das erste Byte der jeweils höchsten Page eine laufenden Nummer schreibt (1 = 128KB, 2 = 256KB, 3 = 512KB … 8 = 16MB).

REU: Übersicht der Speicherseiten (pages)Anschließend werden diese Bytes wieder ausgelesen und mit dem erwarteten Ergebnis verglichen. Stimmen die Werte nicht oder sind wir am Ende (bei max. 16MB) angelangt, dann wissen wir, wie groß die REU ist. Zur Kontrolle gibt das Beispiel noch obenlinks die ermittelte REU-Größe als Zahl von 1 bis 8 aus.

Da ein einzelnes Byte als Erkennung evtl. nicht soooo sicher ist, könnt ihr die Routine natürlich auch auf ein größeres Muster umstellen und dann mit VERIFY prüfen, ob das richtige Ergebnis gefunden wurde. Hier wird z. B. immer mit [Nr.]REUSIZE geprüft…

;*******************************************************************************
;*** REU-Register
;*******************************************************************************
REUSTATUS           = $df00         ;Statusregister (nur lesen!)
REUCOMMAND          = $df01         ;Befehlsregister
REUCOMPUTERRAM      = $df02         ;RAM-Adresse im Computer (LSB/MSB)
REURAM              = $df04         ;Adresse der REU (LSB/MSB)
REUBANK             = $df06         ;Bank in der REU
REUBYTES            = $df07         ;Anzahl der betroffenen Bytes (LSB/MSB)
REUIRQMASK          = $df09         ;Interruptmaske
REUADRCONTROL       = $df0a         ;Adress-Kontroll-Register

;*******************************************************************************
;*** REU-Befehle
;*******************************************************************************

;*** Standardbefehle mit AUTOLOAD, ohne $ff00
REU_STASH_A__       = %10110000     ;kopiere RAM -> REU
REU_FETCH_A__       = %10110001     ;kopiere REU -> RAM
REU_SWAP_A__        = %10110010     ;Speicherbereich tauschen
REU_VERIFY_A__      = %10110011     ;Speicherbereich vergleichen

!zone main
;*** Startadresse 
*=$0801
;** BASIC-Zeile: 2018 SYS 2062
 !word main-2, 2018 
 !byte $9e
 !text " 2062"
 !byte $00,$00,$00

main
 jsr checkREUSize                   ;Größe der REU ermitteln
 tya                                ;in Y steht die Größe (1-8)
 clc
 adc #"0"                           ;für BS-Ausgabe das Zeichen 0 addieren
 sta $0400                          ;Ergebnis anzeigen
 rts                                ;zurück zum BASIC



!zone checkREUSize
;*******************************************************************************
;*** Größe der REU ermitteln
;*** Da man mit dem Statusregister ($df00 / REUSTATUS) die Größe nicht
;*** feststellen kann, gehen wir den 'harten' weg.
;*** Ausgehend davon, dass die REU-Größen sich ab 128KB immer verdoppeln.
;*** Prüfen wir einfach die entsprechenden Pages, ob wir dort etwas ablegen
;*** konnten. 
;*******************************************************************************
;*** Übergabe: -
;*******************************************************************************
;*** Rückgabe: Y = $00: FEHLER / Größe nicht feststellbar!
;***               $01: 128KB
;***               $02: 256KB
;***               $03: 512KB
;***               $04:   1MB
;***               $05:   2MB
;***               $06:   4MB
;***               $07:   8MB
;***               $08:  16MB
;***
;***           REUBankCount: = Anz. der verfügbaren Bänke minus 1
;***                           $00 = FEHLER! 
;*******************************************************************************
;*** ändert  : A, X, Y, SR
;*******************************************************************************
checkREUSize
;*** 1. Schritt auf die jeweils höchste Page ein Kennzeichen speichern
 ldx #<testpattern                  ;Testpattern als Quelle
 stx REUCOMPUTERRAM                 ;LSB
 ldx #>testpattern             
 stx REUCOMPUTERRAM+1               ;MSB
 ldx #$00                           ;immer die ersten Bytes der Page verwenden
 stx REURAM
 stx REURAM+1
 stx REUIRQMASK                     ;keine IRQs
 stx REUADRCONTROL                  ;beide Adressen hochzählen
 stx REUBankCount                   ;erstmal Anzahl der Bänke auf 0 setzen
 stx REUBYTES+1                     ;wir brauchen nur 8 Bytes
 ldx #8
 stx REUBYTES
 ldy #$08                           ;Kennzeichen für die Speichergröße 
 lda #$ff                           ;Höchste Bänke von 16MB bis 128KB
.loop
 sta REUBANK                        ;REU-Bank auswählen
 sty testpattern                    ;Testpattern ändern (nur das 1. Byte)
 dey                                ;für nächsten Durchlauf verringern!
 ldx #REU_STASH_A__                 ;Die acht Bytes vom C64 in die REU
 stx REUCOMMAND                     ;kopieren
 lsr                                ;Höchste Bank für nächste Speicherkonfiguration
 bne .loop                          ;wenn größer 0, nächste Bank 
;*** 2. Schritt: Die jeweils höchste Bank testen, ob unser Wert drin steht
;*** Die REU-Parameter bleiben unverändert (s. oben)
 lda #$ff                           ;erste 'höchste' Bank (128KB) in den Akku
 ldy #$08
 sty testpattern
.loop1
 sta REUBANK                        ;Bank auswählen
 ldx #REU_VERIFY_A__                ;Acht Bytes von der REU zum C64 
 stx REUCOMMAND                     ;kopieren
 tax                                ;Akku merken
 lda REUSTATUS                      ;Status in den Akku
 and #%00100000                     ;gab es einen VERIFY-Fehler?
 beq .skip                          ;falls nicht, haben wir die Größe -> .skip
 txa                                ;sonst Akku wiederherstellen
 dec testpattern                    ;Byte für das Muster verringern
 lsr                                ;nächste 'höchste' Bank
 bne .loop1                         ;wenn die Werte identisch sind, nächst Bank
.skip
 ldy testpattern                    ;im 1. Byte steht die REU-'Größe' 
 tya                                ;'Größe' in den Akku, um den Wert
 tax                                ;ins X-Register zu bekommen.
 lda #$00                           ;Akku auf null setzen
.loop2
 sec                                ;C-Flag setzen
 rol                                ;und 'reinrollen' ;-)
 dex                                ;Schleifenzähler verringern
 bne .loop2                         ;solange nicht null, nochmal
 sta REUBankCount                   ;Anz. der Bänke-1 merken / #$00 = Fehler!!                   
 rts                                ;sonst, zurück zum Aufrufer

REUBankCount
 !byte $00                          ;Anzahl der REU-Bänke - 1! #$00 = Fehler!!

;*** Platzhalter für das Testmuster
testpattern
 !byte $00
 !text "REUSIZE"

Schrott!!Naja...Geht so...Ganz gut...SUPER! (5 Bewertungen | Ø 4,80 von 5 | 96,00%)

Loading...


Zurück

6 Gedanken zu „REU Programmierung“

  1. Hallo,
    man findet hier alles, was man braucht, vielen Dank für die vielen Stunden, die du hier reingesteckt hast.

    eine Kleinigkeit ist mir jetzt aufgefallen:
    Ich denke im ersten Source-Code sollte der Kommentar in Zeile 35 lauten:
    ;*** ohne AUTOLOAD, mit $ff00

    (derzeit steht 2x ohne AUTOLOAD, ohne $ff00 drin)

  2. Nanu, hat sich am Aufbau des REC etwas geändert? 🙂
    $df07 – Die Interruptmaske?
    $df08 – Das Adresskontrollregister?

    Ich denke mal, dass sich an der Hardware nichts geändert hat.
    Dann müssen die beiden Überschriften ganz oben falsch sein.

    Übrigens, die Seite ist oberklasse und hat mir schon häufig weitergeholfen. Vorallem, sie ist in deutsch gehalten.

    PS: Wie wäre es mal mir einer Einführung, wie code ich einen Packer/Cruncher?

    Huffman-Codierung
    Lauflängenkodierung (RLE)
    Lempel-Ziv-Welch-Algorithmus (LZW)

    1. Danke für den Hinweis, ich habe es eben korrigiert. In der Liste oben stimmten die Adressen noch, leider waren die Überschriften im Text dann aber falsch.

      Ich glaube nicht, dass ich mal etwas zu Packern schreiben werde. Vorher stünden noch viele, in meinen Augen interessantere Themen an (z. B. Spieletutorial beenden, SID und Floppy).
      Da ich aber seit Jahren in der Schaffenskriese stecke, werden auch diese Themen in absehbarer Zeit nicht von mir angegangen.

      Ich hoffe die Seite hat dennoch einen Nutzen für dich.

      Viel Spaß weiterhin,
      Jörn

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert

Protected by WP Anti Spam