Erstellt: 12. Juli 2015 (zuletzt geändert: 27. Februar 2018)

4-Spieler Adapter (vier Joysticks)

4 Joysticks am C64

C64 Studio & AMCEFalls ihr eine tolle Idee, für einen Mehrspielertitel habt, der sogar drei oder vier Spieler gleichzeitig erlaubt, dann vermisst ihr beim C64 bestimmt die Möglichkeit weitere Joysticks anschließen zu können. Ihr müsst aber nicht verzweifeln 😉 , denn die gewünschten Ports können durch einen kleinen Adapter für den Userport zur Verfügung gestellt werden.

Der 4-Spieler Adapter

Einen von Individual Computers professionell gefertigten Adapter, könnt ihr z. B. für knapp 14,- € direkt bei Individual Computers oder für 15,- € bei Protovision erwerben.

Der 4-Spieler Adapter von Individual Computers
Der 4-Spieler Adapter von Individual Computers

Wer basteln möchte, kann sich einen solchen Adapter auch selbstbauen. Protovision war so freundlich und hat dazu eine Anleitung veröffentlicht.
Habt ihr wenig Erfahrung mit solchen Basteleien, dann rate ich euch unbedingt zum fertigen Adapter!
Der Userport ist direkt mit den CIA-Bausteinen des C64 verbunden und diese sind äußerst empfindlich! Solltet ihr (im wahrsten Sinne des Wortes) „mistbauen“, dann ist es ganz schnell um einen dieser Bausteine geschehen und ihr könnt euch nach neuen CIAs umschauen!!

Wer es dennoch wagen möchte, braucht:

  • 1x Userportstecker ca. 2,50€
  • 2x 9-pol. D-Sub-Stecker (je nach Ausführung jeweils ab ca. 0,20€)
  • 1x 74LS257 inkl. Sockel ca. 1,00€
  • 1x kleine Streifen- oder Lochrasterplatine (ab ca. 0,50€)
  • den üblichen Kleinkram wie z. B. Litze und falls gewünscht ein Gehäuse

Die Bauanleitung findet ihr dann, wie oben bereits erwähnt, bei Protovision.

Technische Funktionsweise

Bevor wir die Joysticks per Assembler auslesen, erstmal ein paar Worte zur Funktionsweise des Adapters.

74LS257
74LS257

Das Herzstück ist der 74LS257.

Dies ist ein elektronischer Umschalter, der insgesamt vier getrennte Schalter mit je zwei Eingängen (A & B) und einem Ausgang (Y) zur Verfügung stellt. Über Pin-1 (A/B) wird gesteuert, ob der Eingang A oder B zu Y durchgeschaltet wird.

A/B, so wie die vier Y-Ausgänge werden mit dem Userport verbunden. Da für die beiden Feuerknöpfe kein Schalter im 74LS257 mehr zur Verfügung steht, sind diese direkt mit dem Userport verbunden.

Die Beschaltung des Userports (Teil der Anleitung von Protovision) findet ihr auf dem folgenden Bild.

Userport-Belegung (Quelle: Protovision)

Wir können nun über den CIA auf den Userport zugreifen und somit über A/B (SJ – Selekt Joystick) zunächst den Joystick auswählen. Danach brauchen wir nur noch die Richtungen (Y-Ausgänge, UP, DN, LF, RT) und den Feuerknopf (F1, F2) zu prüfen, um unsere Eingaben zu erhalten.

Auslesen der „neuen“ Joysticks

Habt ihr keinen 4-Spieler Adapter, dann könnt ihr euch trotzdem mit seiner Programmierung beschäftigen.

Dockingstation für TC64
Dockingstation für TC64

Die Docking-Station für das Turbo Chameleon 64 bietet ebenfalls die Möglichkeit, vier Joysticks zu betreiben und ist zum eben gezeigten Adapter kompatibel.

Alternativ könnt ihr auch VICE verwenden!
Unter Einstellungen => Joystick Einstellungen => Userport Joystick Einstellungen… könnt ihr VICE entsprechend konfigurieren. Es öffnet sich ein neues Fenster Extra Joystick Einstellungen . Dort wird der CGA Userport Joystick Adapter benötigt. Wählt dann noch aus, welcher Joystick in welchem Port „stecken“ soll: Joystick in extra Port #1 dies ist der dritte und Joystick in extra Port #2 ist der vierte Joystick. Dann kann es auch schon losgehen.

4-Spieler unter VICE
4-Spieler unter VICE
Userport Joystick Adapter
Userport Joystick Adapter

Außerdem müsst ihr noch unter Einstellungen => Control Port Einstellungen… für die beiden Userport Joystick Adapter Port 1/2 Gerät-Einträge, jeweils den Punkt Joystick auswählen.

Ein Testprogramm erstellen

Auch die Routine zur Abfrage der zwei weiteren Joysticks findet ihr bei Protovision bzw. Individual Computers. Eigentlich braucht ihr mich also gar nicht.

Ich bette hier die Funktion aber mal in ein kleines Programm ein, mit dem ihr die vier Joysticks direkt testen könnt.

CHAROUT             = $ffd2         ;Kernalfunktion -> Zeichenausgeben
ZP_HELP1            = $02           ;Hilfsvariable auf der Zeropage
COLOR_UNUSED        = 11            ;dunkel grau
COLOR_JOY_ACTIVE    = 7             ;gelb
COLOR_JOYDIR        = 5             ;grün
COLOR_FIRE          = 2             ;rot



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

main
 lda #0                             ;schwarz für
 sta $d020                          ;Rahmen und
 sta $d021                          ;Hintergrund
 jsr drawMask                       ;Maske ausgeben
 jsr init4PlayerInterface           ;4-Spieler-Adapter initialisieren
mainLoop
 jsr checkInput                     ;auf Eingaben prüfen
 jmp mainLoop                       ;wieder zu .loop springen


 
!zone drawMask
drawMask
 rts                                ;zurück zum Aufrufer



!zone checkInput
checkInput
 rts                                ;zurück zum Aufrufer



!zone init4PlayerInterface
init4PlayerInterface
 rts                                ;zurück zum Aufrufer

Dies ist unser Hauptprogramm. Wie ihr seht, nichts wirklich aufregendes. Zu Beginn definieren wir einige Konstanten und sorgen für unsere Bildschirmmaske jsr drawMask. Danach initialisieren wir den 4-Spieler Adapter jsr init4PlayerInterface. In unserer Hauptschleife ab mainLoop, prüfen und verarbeiten wir die Joystickeingaben jsr checkInput. Außerdem habe ich die drei verwendeten Funktionen drawMask, checkInput und init4PlayerInterface bereits als leere Dummies angelegt, damit ihr das Programm jederzeit übersetzen und ausführen könnt.

Zu diesem Zeitpunkt bringt ein Start natürlich noch nichts, außer der Erkenntnis, dass es hoffentlich 😉 keine Fehler gibt.

Um die Maske anzuzeigen, verwende ich einfach die bekannte Kernalfunktion CHAROUT, die ihr an der Adresse $ffd2 findet. Wir lesen in drawMask gleich einfach die PETSCII-Codes für unsere Maske ein und geben sie über $ffd2 aus. Dabei verwenden wir auch einige der Steuercodes, die CHAROUT verarbeiten kann. Sollten euch diese nicht geläufig sein, dann werft vorher besser kurz einen Blick auf den PETSCII-Beitrag.

Ergänzt nun drawMask mit den folgenden Zeilen:

!zone drawMask
drawMask
 ldx #0                             ;Schleifenzähler auf 0
.loop
 lda mask,X                         ;Zeichen von mask in den Akku holen
 beq .exit                          ;falls 0 sind wir am Enden, dann raus -> .exit
 jsr CHAROUT                        ;Zeichen mittels Kernal-Routine ($FFD2) ausgeben
 inx                                ;Schleifenzähler erhöhen
 bne .loop                          ;und wieder zum Anfang -> .loop
.exit                               ;das BNE ist kürzer als ein JMP und verhindert eine Endlosschleife
 rts                                ;zurück zum Aufrufer

Wie ihr seht, setzen wir erstmal den Schleifenzähler auf 0, um mit dem ersten Zeichen zu beginnen. Dann holen wir uns hinter .loop das Zeichen. Falls es eine 0 ist, haben wir das Textende erreicht und springen zum Ausgang .exit. Anderenfalls rufen wir CHAROUT auf. Diese Kernalfunktion gibt das Zeichen aus dem Akku, an der aktuellen Cursorposition aus. Sobald wir von $ffd2 zurückkommen, erhöhen wir den Schleifenzähler. Dann verwenden wir ein bne .loop, damit die Schleife von Neuem beginnt. Dies hat den Vorteil, dass wir ein Byte sparen. Ein jmp .loop würde drei Bytes, statt der zwei von bne benötigen. Außerdem verhindert es eine Endlosschleife, falls kein Textende gefunden wird. Der Nachteil ist freilich, dass nach spätestens 256 Zeichen Schluß ist. Dies reicht für unsere Maske aber. Sobald wir beim .exit angelangt sind, geht es zurück zum Aufrufer.

Die Daten für die Maske fügen wir nun am Programmende, hinter dem Label mask ein.

mask
 !text 147,156
 !text $75,$63,$63,$63,$63,$63,$63,$63,$63,$63,$63,$63,$63,$63,$63,$63,$63,$63,$63,$63,$63,$63,$63,$69,13
 !text $62,"JOY1  JOY2  JOY3  JOY4",$62,13
 !text $62," ",18,$e9,$df,146,"    ",18,$e9,$df,146,"    ",18,$e9,$df,146,"    ",18,$e9,$df,146," ",$62,13
 !text $62,18,$e9,146,$ac,$bb,18,$df,146,"  ",18,$e9,146,$ac,$bb,18,$df,146,"  ",18,$e9,146,$ac,$bb,$12,$df,146,"  ",18,$e9,146,$ac,$bb,18,$df,146,$62,13
 !text $62,$df,$bc,$be,$e9,"  ",$df,$bc,$be,$e9,"  ",$df,$bc,$be,$e9,"  ",$df,$bc,$be,$e9,$62,13
 !text $62," ",$df,$e9,"    ",$df,$e9,"    ",$df,$e9,"    ",$df,$e9," ",$62,13
 !text $6a,$63,$63,$63,$63,$63,$63,$63,$63,$63,$63,$63,$63,$63,$63,$63,$63,$63,$63,$63,$63,$63,$63,$6b,13
 !byte $00

Den lesbaren Text habe ich hier direkt in “…“ angegeben. Zur besseren Unterscheidung, habe ich alle Steuerzeichen dezimal und alle grafischen Zeichen als Hexzahlen aufgeführt. So löscht ihr durch Ausgabe von 147 z. B. den Bildschirm oder setzt mit 156 die Zeichenfarbe auf lila. Um alle Zeichen zu entschlüsseln, werft einfach einen Blick auf die Bilder / Tabellen unter PETSCII.

Wenn ihr das Programm jetzt startet, dann solltet ihr einen ersten Blick auf die Maske werfen können. Allerdings ist diese noch komplett in lila gehalten.

Die Maske…
Die Maske…

Kümmern wir uns nun um die Initialisierung. Ergänzt dazu init4PlayerInterface um die folgenden Anweisungen.

!zone init4PlayerInterface
init4PlayerInterface
 lda #%10000000                     ;Bit-7 auf Ausgabe (OUT) / BIT 6-0 auf Eingang (IN)
 sta $dd03                          ;für CIA-2 Port-B stellen
 lda $dd01               
 sta $dd01                          ;Port freigeben
 rts                                ;zurück zum Aufrufer

Die Routine sorgt dafür, dass wir über Bit-7 den Joystick wählen können, daher wird diese „Leitung“ auf Ausgabe gestellt. Die restlichen Bits liefern uns die Joystickeingaben zurück und stehen daher auf Eingang.

Wir nähern uns langsam dem Kern unseres Programmes. Wenden wir uns nun der Eingaberoutine checkInput zu, die in der Endlosschleife des Haupprogrammes laufend aufgerufen wird.

!zone checkInput
checkInput
 jsr readJoysticks                  ;alle 4 Joysticks auslesen
 lda #3                             ;Schleife für die 4 Sticks
 sta ZP_HELP1                       ;über die Zeropage
.loop
 jsr performInput                   ;Joystickeingaben auswerten
 ldy ZP_HELP1                       ;Stick-Nr. ins Y-Reg.
 lda offset,Y                       ;Offset für die Farbausgabe holen
 tay                                ;und ins Y-Register
 jsr drawColors                     ;Farben setzen
 dec ZP_HELP1                       ;Stick-Schleifenzähler verringern
 bpl .loop                          ;solange NICHT negativ -> .loop
 rts                                ;sonst zurück zum Aufrufer

Die Funktion springt zunächst nach readJoysticks, um die aktuellen Eingaben der vier Sticks zu lesen. Dann verarbeiten wir in einer Schleife die Werte der vier Joysticks. Dazu verwenden wir die Zeropageadresse ZP_HELP1 (die wir oben bereits festgelegt haben) als Schleifenzähler und initialisieren diese mit 3. Bei .loop beginnt dann unser Schleife. Dort verarbeiten wir die Eingaben, des über ZP_HELP1 gewählten Joysticks, zunächst bei performInput. Holen uns danach die aktuelle Stick-Nr. ins Y-Register und damit dann einen Offset für die Farbausgabe aus der Tabelle offset in den Akku. Da wir diesen Offset gleich im Y-Register benötigen kopieren wir ihn mit tay dort hin. Damit rufen wir die Funktion drawColors auf, um die Farben für den jeweiligen Joystick in der Maske zu setzen. Abschließend verringern wir ZP_HELP1 um eins und springen, solange der Wert nicht negativ ist, wieder nach .loop.

Die Mini-Tabelle offset könnt ihr am Programmende einfügen.

offset
 !byte 0,6,12,18

Diese Tabelle sorgt dafür, dass unsere Farbausgabe für jeden Joystick um sechs Zeichen nach rechts verschoben wird.

Fügt jetzt noch die drei neuen Dummy-Funktionen vor mask hinzu…

!zone readJoysticks
readJoysticks
 rts                                ;zurück zum Aufrufer



!zone performInput
performInput
 rts                                ;zurück zum Aufrufer



!zone drawColors
drawColors
 rts                                ;zurück zum Aufrufer

Ihr könntet das Programm jetzt zwar wieder ausführen, es zeigt sich aber noch keine Änderung zum vorherigen Lauf.

Weiter geht es mit dem eigentlichen Herzstück, der Ausleseroutine readJoysticks. Da diese gleich den Status der vier Sticks liest, brauchen wir noch einen Platz, an dem wir die ermittelten Werte erstmal ablegen können. Fügt dazu am Programmende die Tabelle joyValues hinzu.

joyValues
 !byte $00, $00, $00, $00

Nun kommen wir zur eigentlichen Funktion:

!zone readJoysticks
readJoysticks
 lda $dc01                          ;Joystick-1 lesen (Joystickbuchse 1)
 and #%00011111                     ;nur die unteren 5-Bits behalten
 sta joyValues                      ;und in der Tabelle joyvalues speichern

 lda $dc00                          ;Joystick-2 lesen (Joystickbuchse 2)
 and #%00011111                     ;nur die unteren 5-Bits behalten
 sta joyValues+1                    ;und in der Tabelle joyvalues speichern

Die ersten Zeilen sollten euch eigentlich bekannt sein.
Falls nicht, werft mal einen Blick in die Tastaturmatrix, dort findet ihr auch einige Infos zu den Joysticks. Habt ihr größeren Informationsbedarf, dann beschäftigt euch am besten erstmal mit L.O.V.E.
Hier lesen wir die beiden standard Joysticks in Port-1 & 2 des C64 aus. Wir bereinigen die Daten etwas, da wir nur die unteren fünf Bits benötigen und merken uns die Werte in der eben angelegten Tabelle joyValues.

Lesen wir nun die Werte für den dritten Joystick aus, der ja am 4-Spieler Adapter hängt.

 lda #%10000000                     ;CIA-2 Port-B Bit-7 auf 1
 sta $dd01                          ;setzen
 lda $dd01                          ;Joystick-3 lesen (Userportadapter Buchse 1)
 and #%00011111                     ;nur die unteren 5-Bits behalten
 sta joyValues+2                    ;und in der Tabelle joyvalues speichern

Wir setzen im CIA-2 für Port-B $dd01 das Bit-7 auf 1, um die entsprechende Joystickbuchse am Adapter zu wählen. Danach lesen wir diesen Port einfach und erhalten unsere Joystickwerte. Auch hier benötigen wir nur die unteren fünf Bits und speichern das Ergebnis wieder unter joyValues.

Der vierte und letzte Joystick ist etwas tricky. Wenn ihr oben auf die Userportbelegung schaut, dann erkennt ihr, dass der letzte Feuerknopf nicht direkt neben den Richtungen liegt. Das kommt, wie bereits erklärt, daher, dass der 74LS257 nur vier elektronische Schalter bietet und die Feuerknöpfe deshalb direkt mit dem Userport verbunden sind. Das Problem ist aber nicht allzu groß, wir müssen das eine Bit nur um eine Stelle verschieben.

 lda #%00000000                     ;CIA-2 Port-B Bit-7 auf 0
 sta $dd01                          ;setzen
 lda $dd01                          ;Joystick-4 lesen (Userportadapter Buchse 2)
                                    ;Achtung: Den Feuerknopf vom 4. Joystick
                                    ;         findet ihr in Bit-5 NICHT 4!!!
                                    ;         Also verschieben wir das Bit.
 pha                                ;aktuellen Wert auf dem Stack merken
 and #%00001111                     ;die unteren 4-Bit stimmen
 sta joyValues+3                    ;diese direkt schonmal in der Tabelle joyvalues speichern
 pla                                ;Ursprünglichen Wert vom Stack holen
 and #%00100000                     ;Bit-5 maskieren
 lsr                                ;um ein Bit nach rechts verschieben
 ora joyValues+3                    ;per ODER mit dem eben gespeicherten verknüpfen
 sta joyValues+3                    ;und wieder in der Tabelle joyvalues speichern
 rts                                ;zurück zum Aufrufer

Dieses Mal wird das Bit-7 im CIA-2 auf 0 gesetzt, um den vierten Joystick zu wählen. Danach lesen wir, wie eben, $dd01 einfach wieder aus. Jetzt haben wir im Akku zwar die Werte des 4. Joysticks, da das Bit für den Feurknopf aber verschoben ist, sollten wir dies noch korrigieren, damit alle vier Werte identisch aufgebaut sind. Wir merken uns den Akku erstmal auf dem Stack, blenden die oberen vier Bits aus und speichern das Ergebnis schonmal bei joyValues. Dort stehen jetzt nur die Richtungsinformationen! Um das Bit für den Feuerknopf an die gewünschte Position zu bekommen, holen wir den ursprünglichen Wert wieder vom Stack. Dann blenden wir alles, bis auf das Bit für die Feuertaste aus und verschieben den Akkuinhalt um ein Bit nach rechts. Jetzt müssen wir nur noch den Akku mit dem unter joyValues gemerkten Wert ODER-Verknüpfen und das Ergebnis abschließend wieder dort ablegen.

Somit finden wir die Richtungs- & Feuerinformationen von allen vier Joysticks in einem einheitlichen Format unter joyValues wieder.

!zone readJoysticks
readJoysticks
 lda $dc01                          ;Joystick-1 lesen (Joystickbuchse 1)
 and #%00011111                     ;nur die unteren 5-Bits behalten
 sta joyValues                      ;und in der Tabelle joyvalues speichern

 lda $dc00                          ;Joystick-2 lesen (Joystickbuchse 2)
 and #%00011111                     ;nur die unteren 5-Bits behalten
 sta joyValues+1                    ;und in der Tabelle joyvalues speichern
 lda #%10000000                     ;CIA-2 Port-B Bit-7 auf 1
 sta $dd01                          ;setzen
 lda $dd01                          ;Joystick-3 lesen (Userportadapter Buchse 1)
 and #%00011111                     ;nur die unteren 5-Bits behalten
 sta joyValues+2                    ;und in der Tabelle joyvalues speichern

 lda #%00000000                     ;CIA-2 Port-B Bit-7 auf 0
 sta $dd01                          ;setzen
 lda $dd01                          ;Joystick-4 lesen (Userportadapter Buchse 2)
                                    ;Achtung: Den Feuerknopf vom 4. Joystick
                                    ;         findet ihr in Bit-5 NICHT 4!!!
                                    ;         Also verschieben wir das Bit.
 pha                                ;aktuellen Wert auf dem Stack merken
 and #%00001111                     ;die unteren 4-Bit stimmen
 sta joyValues+3                    ;diese direkt schonmal in der Tabelle joyvalues speichern
 pla                                ;Ursprünglichen Wert vom Stack holen
 and #%00100000                     ;Bit-5 maskieren
 lsr                                ;um ein Bit nach rechts verschieben
 ora joyValues+3                    ;per ODER mit dem eben gespeicherten verknüpfen
 sta joyValues+3                    ;und wieder in der Tabelle joyvalues speichern
 rts                                ;zurück zum Aufrufer

Auch jetzt lohnt es sich nur, das Programm zu erstellen, wenn ihr prüfen möchtet, ob es sich fehlerfrei übersetzen läßt.

Zur Entspannung kümmern wir uns erstmal um drawColors. Diese Routine setzt die Farben für den jeweiligen Joystick. Dazu wird wieder eine kleine Tabelle, mit dem passenden Namen colors, benötigt. Fügt diese einfach am Ende ein.

colors
    !BYTE COLOR_UNUSED, COLOR_UNUSED, COLOR_UNUSED, COLOR_UNUSED, 16
    !BYTE COLOR_UNUSED, COLOR_UNUSED, COLOR_UNUSED, COLOR_UNUSED, 16
    !BYTE COLOR_UNUSED, COLOR_UNUSED, COLOR_UNUSED, COLOR_UNUSED, 16
    !BYTE COLOR_UNUSED, COLOR_UNUSED, COLOR_UNUSED, COLOR_UNUSED, 16
    !BYTE COLOR_UNUSED, COLOR_UNUSED, COLOR_UNUSED, COLOR_UNUSED, 255

Als Ausgangsfarbe verwende ich die oben festgelegte Konstante COLOR_UNUSED. Da die Farben nur von 0 bis 15 gehen, wird das Zeilenende durch 16 definiert. Mit 255 legen wir das Ende der gesamten Tabelle fest. Wir werden diese Tabelle gleich über performInput füllen. Dort ermitteln wir, für den jeweils aktuellen Joystick, immer die benötigten Zeichenfarben und speichern sie hier.

Schreiben wir jetzt aber erstmal die Funktion, die diese Tabelle verarbeitet.

!zone drawColors
drawColors
 ldx #0                             ;Schleifenzähler auf 0
.loop
 lda colors,X                       ;Farbe aus der Tabelle colors in den Akku holen
 bmi .exit                          ;falls negativ -> .exit
 cmp #16                            ;sonst, prüfen ob es ein Umbruch ist
 bne .skip                          ;nein? -> weiter bei .skip
 tya                                ;Offset in den Akku
 clc                                ;Carry für Addition löschen
 adc #40-4                          ;eine Zeile addieren
 tay                                ;und den neuen Offset ins Y-Reg.
 jmp .skip1                         ;weiter bei -> .skip1
.skip
 sta $d800+41,Y                     ;Die Farbe ins Farb-RAM kopieren
 iny                                ;Offset um eins erhöhen
.skip1
 inx                                ;Schleifenzähler erhöhen
 bne .loop                          ;und wieder zum Anfang -> .loop
.exit                               ;das BNE ist kürzer als ein JMP und verhindert eine Endlosschleife
 rts                                ;zurück zum Aufrufer

Der Routine wird im Y-Register der aktuelle Offset übergeben. Das X-Register nutzen wir als Schleifenzähler und initialisieren es mit 0. Hinter .loop beginnen wir damit, aus der Tabelle colors, das über X angegebene Byte zulesen. Sollte es negativ sein, dann sind wir am Ende der Tabelle angelangt und verlassen die Funktion über einen Sprung nach .exit. Anderenfalls prüfen wir noch, ob es sich um einen Zeilenumbruch handelt. Sollte im Akku keine 16 stehen, dann springt das Programm nach .skip, sonst läuft es weiter und sorgt für einen Zeilenvorschub. Für den Umbruch addieren wir einfach 40 (Anzahl der Zeichen für eine Zeile auf dem Bildschirm) minus 4 (also 36) auf den Offset im Y-Register. Wir müssen 4 weniger addieren, da ein Umbruch immer am Ende einer Zeile aus colors erfolgt und wir vier Bytes je Zeile benötigen. Nach dem Umbruch geht es bei .skip1 weiter. Stand keine 16 im Akku, dann wird hinter .skip die im Akku befindliche Farbe ins Farb-RAM geschrieben. Das Y-Register bestimmt dabei unseren Offset vom linken Rand. Die Adresse $d800+41 zeigt direkt das erste Zeichen von JOY1. Anschließend erhöhen wir den Offset im Y-Register und auch unseren Schleifenzähler im X-Register jeweils um eins. Das bne spart wieder ein Byte und verhindert eine Endlosschleife.

Jetzt solltet ihr das Programm mal wieder starten…

Alle vier Joysticks werden als inaktiv angezeigt.
Alle vier Joysticks werden als inaktiv angezeigt.

Die Ausgabe hat sich etwas geändert. Nun werden alle Joysticks als inaktiv (dunkelgrau) angezeigt. Ihr könnt die Farbe ganz einfach über die Konstante COLOR_UNUSED ändern.

Haltet durch, es fehlt nur noch die Funktion performInput.

!zone performInput
performInput
 ldy ZP_HELP1                       ;aktuelle Joystick-Nr. ins Y-Reg.
 lda joyValues,Y                    ;Eingaben des Sticks in den Akku
 cmp #%00011111                     ;gibt es überhaupt eine Eingabe?
 beq .oben                          ;falls nicht -> .oben
 ldx #COLOR_JOY_ACTIVE              ;sonst Farbe für JOYx ins X-Reg.
 jmp .oben1                         ;weiter bei .oben1
.oben                       
 ldx #COLOR_UNUSED                  ;Farbe für inaktiv, falls keine Eingabe holen
.oben1
 stx colors                         ;die gewählte Farbe unter
 stx colors+1                       ;colors für alle Zeichen 'JOYx'
 stx colors+2                       ;ablegen
 stx colors+3

Die Routine holt sich die aktuelle Joystick-Nr. von ZP_HELP1 ins Y-Register und lädt damit die zum Joystick gehörenden Eingaben von joyValues in den Akku. Danach kontrollieren wir, ob überhaupt eine Eingabe vorliegt, falls nicht, springen wir direkt nach .oben, wo das Grau aus COLOR_UNUSED für den inaktivien Joystick ins X-Register geladen wird. Beachtet, dass die Joystickwerte low aktiv sind! Eine 1 bedeutet also keine Eingabe und eine 0, dass die Richtung bzw. der Feuerknopf gedrückt wurde! Sollte eine Eingabe vorliegen, dann laden wir COLOR_JOY_ACTIVE (gelb) ins X-Register und müssen die Zeile mit COLOR_UNUSED überspringen, daher jmp .oben1. Hinter .oben1 setzen wir in unserer Farbtabelle colors jetzt einfach die Farbe für die vier Zeichen der JOYx-Überschrift.

Ich möchte hier mal einen kleinen Trick zeigen. Mit dem ihr zwei Bytes sparen könnt, allerdings kostet er euch einen Taktzyklus und ändert das Statusregister (SR)!
Ihr werdet evtl. häufiger in die Verlegenheit kommen, dass ihr wie eben, entweder Anweisung-A oder Anweisung-B ausführen möchtet. Das führt häufig zu dem gerade gesehenen Konstrukt:

 ldx #COLOR_JOY_ACTIVE   ;sonst Farbe für JOYx ins X-Reg.
 jmp .oben1
.oben                    ;mit $2c (Bit <absolut>) einen Sprung einsparen
 ldx #COLOR_UNUSED       ;Farbe für inaktiv, falls keine Eingabe holen
.oben1

Es wäre doch schön, wenn wir uns den Sprung jmp .oben1 und das dazugehörige Label sparen könnten. OK, auf das Label ließe sich leicht durch jmp *+5 verzichten. Es bleibt aber noch der Sprung. Hier können wir den absoluten BIT-Befehl (OpCode $2c) zweckentfremden!
Ersetzt ihr jmp .oben1 durch !byte $2c, dann wird ldx #COLOR_UNUSED als Adresse der BIT-Anweisung interpretiert und das Programm läuft automatisch beim jetzt überflüssigen Label .oben1 weiter. Damit man die Bedeutung der Anweisung im Quellcode leichter erkennt, können wir beim C64 Studio ein Macro anlegen.
Fügt am Anfang des Quellcodes, hinter den Konstanten, die folgenden Zeilen ein:

!macro FAKE_BIT {
  !byte $2c
}

Jetzt könnt ihr im Source mit +FAKE_BIT dieses Macro aufrufen, sobald ihr einen falschen BIT-Befehl benötigt. Dieses Vorgehen klappt natürlich nur, wenn ihr exakt zwei Bytes überspringen müsst. Anderenfalls benötigt ihr doch wieder einen Sprung! Beachtet außerdem, dass sich auch das Statusregister (SR) durch diesen Befehl ändert.

Unser Block von eben sähe dann so aus…

!zone performInput
performInput
 ldy ZP_HELP1                       ;aktuelle Joystick-Nr. ins Y-Reg.
 lda joyValues,Y                    ;Eingaben des Sticks in den Akku
 cmp #%00011111                     ;gibt es überhaupt eine Eingabe?
 beq .oben                          ;falls nicht -> .oben
 ldx #COLOR_JOY_ACTIVE              ;sonst Farbe für JOYx ins X-Reg.
 +FAKE_BIT                          ;das LDX hinter .oben ignorieren!
.oben                       
 ldx #COLOR_UNUSED                  ;Farbe für inaktiv, falls keine Eingabe holen
 stx colors                         ;die gewählte Farbe unter
 stx colors+1                       ;colors für alle Zeichen 'JOYx'
 stx colors+2                       ;ablegen
 stx colors+3

Lasst uns dieses Vorgehen für die restliche Funktion beibehalten. Verwendet ihr einen anderen Assembler, dann könnt ihr auch einfach direkt !byte $2c, statt dem Macro verwenden.

Eben haben wir nur geprüft, ob es überhaupt Eingaben gibt. Jetzt kontrollieren wir die einzelnen Richtungen und den Feuerknopf. Im Akku befindet sich immer noch die Eingabe des aktuellen Joysticks. Wir verschieben diese jetzt bitweise nach rechts und schauen, ob eine Eingabe vorliegt. Los geht es mit OBEN…

;*** OBEN prüfen
 lsr                                ;Akku um ein Bit nach rechts verschieben
 bcs .unten                         ;falls C=1 -> .unten
 ldx #COLOR_JOYDIR                  ;sonst Farbe für die Richtung holen
 +FAKE_BIT                          ;FAKE BIT-Mnemonic
.unten
 ldx #COLOR_UNUSED                  ;wenn nicht oben, Farbe für inaktiv holen
 stx colors+6                       ;Farben in die Tabelle colors
 stx colors+7                       ;schreiben

Wie angekündigt, verschieben wir den Akkuinhalt um ein Bit nach rechts. Dabei landet das rausgefallene Bit im Carry-Flag. Sollte es gesetzt sein, dann fand keine Eingabe statt (low aktiv) und es geht bei .unten weiter. Anderenfalls holen wir uns die Farbe COLOR_JOYDIR (grün) für die Richtungen ins X-Register. Dann folgt unser Macro +FAKE_BIT, mit dem wir den ersten 2-Byte-Befehl hinter .unten einfach auslassen. Wurde die Richtung OBEN nicht betätig, dann landen wir direkt bei .unten und holen wieder COLOR_UNUSED ins X-Register! Anschließend schreiben wir die ermittelte Farbe einfach in unsere Tabelle colors. Das Dreieck nach oben besteht nur aus zwei Zeichen, für das wir hier die Farbwerte setzen.

Da die restliche Funktion den eben gezeigten Block eigentlich nur für die anderen Richtungen und den Feuerknopf wiederholt, hier einfach die komplette Routine performInput. Die Kommentare sollten zur Erklärung reichen.

!zone performInput
performInput
 ldy ZP_HELP1                       ;aktuelle Joystick-Nr. ins Y-Reg.
 lda joyValues,Y                    ;Eingaben des Sticks in den Akku
 cmp #%00011111                     ;gibt es überhaupt eine Eingabe?
 beq .oben                          ;falls nicht -> .oben
 ldx #COLOR_JOY_ACTIVE              ;sonst Farbe für JOYx ins X-Reg.
 +FAKE_BIT                          ;das LDX hinter .oben ignorieren!
.oben                       
 ldx #COLOR_UNUSED                  ;Farbe für inaktiv, falls keine Eingabe holen
 stx colors                         ;die gewählte Farbe unter
 stx colors+1                       ;colors für alle Zeichen 'JOYx'
 stx colors+2                       ;ablegen
 stx colors+3
;*** OBEN prüfen
 lsr                                ;Akku um ein Bit nach rechts verschieben
 bcs .unten                         ;falls C=1 -> .unten
 ldx #COLOR_JOYDIR                  ;sonst Farbe für die Richtung holen
 +FAKE_BIT                          ;FAKE BIT-Mnemonic
.unten
 ldx #COLOR_UNUSED                  ;wenn nicht oben, Farbe für inaktiv holen
 stx colors+6                       ;Farben in die Tabelle colors
 stx colors+7                       ;schreiben
;*** UNTEN?
 lsr                                ;Akku um ein Bit nach rechts verschieben
 bcs .links                         ;falls C=1 -> .links
 ldx #COLOR_JOYDIR                  ;sonst, Farbe für Joystickrichtung ins X-Reg.
 +FAKE_BIT                          ;FAKE BIT-Mnemonic
.links
 ldx #COLOR_UNUSED                  ;falls nicht unten, Farbe für inaktiv
 stx colors+21                      ;Farben in die Tabelle colors
 stx colors+22                      ;schreiben
;*** LINKS?
 lsr                                ;Akku nach rechts verschieben
 bcs .rechts                        ;falls C=1 -> .rechts
 ldx #COLOR_JOYDIR                  ;sonst, Farbe für Joystickrichtung ins X-Reg.
 +FAKE_BIT                          ;FAKE BIT-Mnemonic
.rechts
 ldx #COLOR_UNUSED                  ;wenn nicht links, Farbe für inaktiv
 stx colors+10                      ;Farben in die Tabelle colors
 stx colors+15                      ;schreiben
;*** RECHTS?
 lsr                                ;Akku um ein Bit nach rechts verschieben
 bcs .feuer                         ;falls C=1 -> .feuer
 ldx #COLOR_JOYDIR                  ;sonst, Farbe für Joystickrichtung ins X-Reg.
 +FAKE_BIT                          ;FAKE BIT-Mnemonic
.feuer
 ldx #COLOR_UNUSED                  ;wenn nicht rechts, dann inaktiv
 stx colors+13                      ;Farbe in die Tabelle colors
 stx colors+18                      ;schreiben
;*** FEUER?
 lsr                                ;ein letztes Mal den Akku nach rechts verschieben
 bcs .exit                          ;falls C=1 -> .exit
 ldx #COLOR_FIRE                    ;sonst, Farbe für Feuer ins X-Reg.
 +FAKE_BIT                          ;FAKE BIT $0000
.exit
 ldx #COLOR_UNUSED                  ;falls kein Feuer, Farbe für inaktiv
 stx colors+11                      ;Farbe in die Tabelle colors
 stx colors+12                      ;schreiben
 stx colors+16
 stx colors+17
 rts                                ;zurück zum Aufrufer

Jetzt ist unser kleines Testprogramm fertig und eure Joystickeingaben werden entsprechend angezeigt.

Die vier Joysticks testen.
Die vier Joysticks testen.
!macro FAKE_BIT {
  !byte $2c
}


CHAROUT             = $ffd2         ;Kernalfunktion -> Zeichenausgeben
ZP_HELP1            = $02           ;Hilfsvariable auf der Zeropage
COLOR_UNUSED        = 11            ;dunkel grau
COLOR_JOY_ACTIVE    = 7             ;gelb
COLOR_JOYDIR        = 5             ;grün
COLOR_FIRE          = 2             ;rot



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

main
 lda #0                             ;schwarz für
 sta $d020                          ;Rahmen und
 sta $d021                          ;Hintergrund
 jsr drawMask                       ;Maske ausgeben
 jsr init4PlayerInterface           ;4-Spieler-Adapter initialisieren
mainLoop
 jsr checkInput                     ;auf Eingaben prüfen
 jmp mainLoop                       ;wieder zu .loop springen


 
!zone drawMask
drawMask
 ldx #0                             ;Schleifenzähler auf 0
.loop
 lda mask,X                         ;Zeichen von mask in den Akku holen
 beq .exit                          ;falls 0 sind wir am Enden, dann raus -> .exit
 jsr CHAROUT                        ;Zeichen mittels Kernal-Routine ($FFD2) ausgeben
 inx                                ;Schleifenzähler erhöhen
 bne .loop                          ;und wieder zum Anfang -> .loop
.exit                               ;das BNE ist kürzer als ein JMP und verhindert eine Endlosschleife
 rts                                ;zurück zum Aufrufer



!zone checkInput
checkInput
 jsr readJoysticks                  ;alle 4 Joysticks auslesen
 lda #3                             ;Schleife für die 4 Sticks
 sta ZP_HELP1                       ;über die Zeropage
.loop
 jsr performInput                   ;Joystickeingaben auswerten
 ldy ZP_HELP1                       ;Stick-Nr. ins Y-Reg.
 lda offset,Y                       ;Offset für die Farbausgabe holen
 tay                                ;und ins Y-Register
 jsr drawColors                     ;Farben setzen
 dec ZP_HELP1                       ;Stick-Schleifenzähler verringern
 bpl .loop                          ;solange NICHT negativ -> .loop
 rts                                ;sonst zurück zum Aufrufer



!zone init4PlayerInterface
init4PlayerInterface
 lda #%10000000                     ;Bit-7 auf Ausgabe (OUT) / Bit 6-0 auf Eingang (IN)
 sta $dd03                          ;für CIA-2 Port-B stellen
 lda $dd01               
 sta $dd01                          ;Port freigeben
 rts                                ;zurück zum Aufrufer



!zone readJoysticks
readJoysticks
 lda $dc01                          ;Joystick-1 lesen (Joystickbuchse 1)
 and #%00011111                     ;nur die unteren 5-Bits behalten
 sta joyValues                      ;und in der Tabelle joyvalues speichern

 lda $dc00                          ;Joystick-2 lesen (Joystickbuchse 2)
 and #%00011111                     ;nur die unteren 5-Bits behalten
 sta joyValues+1                    ;und in der Tabelle joyvalues speichern
 lda #%10000000                     ;CIA-2 Port-B Bit-7 auf 1
 sta $dd01                          ;setzen
 lda $dd01                          ;Joystick-3 lesen (Userportadapter Buchse 1)
 and #%00011111                     ;nur die unteren 5-Bits behalten
 sta joyValues+2                    ;und in der Tabelle joyvalues speichern

 lda #%00000000                     ;CIA-2 Port-B Bit-7 auf 0
 sta $dd01                          ;setzen
 lda $dd01                          ;Joystick-4 lesen (Userportadapter Buchse 2)
                                    ;Achtung: Den Feuerknopf vom 4. Joystick
                                    ;         findet ihr in Bit-5 NICHT 4!!!
                                    ;         Also verschieben wir das Bit.
 pha                                ;aktuellen Wert auf dem Stack merken
 and #%00001111                     ;die unteren 4-Bit stimmen
 sta joyValues+3                    ;diese direkt schonmal in der Tabelle joyvalues speichern
 pla                                ;Ursprünglichen Wert vom Stack holen
 and #%00100000                     ;Bit-5 maskieren
 lsr                                ;um ein Bit nach rechts verschieben
 ora joyValues+3                    ;per ODER mit dem eben gespeicherten verknüpfen
 sta joyValues+3                    ;und wieder in der Tabelle joyvalues speichern
 rts                                ;zurück zum Aufrufer



!zone performInput
performInput
 ldy ZP_HELP1                       ;aktuelle Joystick-Nr. ins Y-Reg.
 lda joyValues,Y                    ;Eingaben des Sticks in den Akku
 cmp #%00011111                     ;gibt es überhaupt eine Eingabe?
 beq .oben                          ;falls nicht -> .oben
 ldx #COLOR_JOY_ACTIVE              ;sonst Farbe für JOYx ins X-Reg.
 +FAKE_BIT                          ;das LDX hinter .oben ignorieren!
.oben                       
 ldx #COLOR_UNUSED                  ;Farbe für inaktiv, falls keine Eingabe holen
 stx colors                         ;die gewählte Farbe unter
 stx colors+1                       ;colors für alle Zeichen 'JOYx'
 stx colors+2                       ;ablegen
 stx colors+3
;*** OBEN prüfen
 lsr                                ;Akku um ein Bit nach rechts verschieben
 bcs .unten                         ;falls C=1 -> .unten
 ldx #COLOR_JOYDIR                  ;sonst Farbe für die Richtung holen
 +FAKE_BIT                          ;FAKE BIT-Mnemonic
.unten
 ldx #COLOR_UNUSED                  ;wenn nicht oben, Farbe für inaktiv holen
 stx colors+6                       ;Farben in die Tabelle colors
 stx colors+7                       ;schreiben
;*** UNTEN?
 lsr                                ;Akku um ein Bit nach rechts verschieben
 bcs .links                         ;falls C=1 -> .links
 ldx #COLOR_JOYDIR                  ;sonst, Farbe für Joystickrichtung ins X-Reg.
 +FAKE_BIT                          ;FAKE BIT-Mnemonic
.links
 ldx #COLOR_UNUSED                  ;falls nicht unten, Farbe für inaktiv
 stx colors+21                      ;Farben in die Tabelle colors
 stx colors+22                      ;schreiben
;*** LINKS?
 lsr                                ;Akku nach rechts verschieben
 bcs .rechts                        ;falls C=1 -> .rechts
 ldx #COLOR_JOYDIR                  ;sonst, Farbe für Joystickrichtung ins X-Reg.
 +FAKE_BIT                          ;FAKE BIT-Mnemonic
.rechts
 ldx #COLOR_UNUSED                  ;wenn nicht links, Farbe für inaktiv
 stx colors+10                      ;Farben in die Tabelle colors
 stx colors+15                      ;schreiben
;*** RECHTS?
 lsr                                ;Akku um ein Bit nach rechts verschieben
 bcs .feuer                         ;falls C=1 -> .feuer
 ldx #COLOR_JOYDIR                  ;sonst, Farbe für Joystickrichtung ins X-Reg.
 +FAKE_BIT                          ;FAKE BIT-Mnemonic
.feuer
 ldx #COLOR_UNUSED                  ;wenn nicht rechts, dann inaktiv
 stx colors+13                      ;Farbe in die Tabelle colors
 stx colors+18                      ;schreiben
;*** FEUER?
 lsr                                ;ein letztes Mal den Akku nach rechts verschieben
 bcs .exit                          ;falls C=1 -> .exit
 ldx #COLOR_FIRE                    ;sonst, Farbe für Feuer ins X-Reg.
 +FAKE_BIT                          ;FAKE BIT $0000
.exit
 ldx #COLOR_UNUSED                  ;falls kein Feuer, Farbe für inaktiv
 stx colors+11                      ;Farbe in die Tabelle colors
 stx colors+12                      ;schreiben
 stx colors+16
 stx colors+17
 rts                                ;zurück zum Aufrufer



!zone drawColors
drawColors
 ldx #0                             ;Schleifenzähler auf 0
.loop
 lda colors,X                       ;Farbe aus der Tabelle colors in den Akku holen
 bmi .exit                          ;falls negativ -> .exit
 cmp #16                            ;sonst, prüfen ob es ein Umbruch ist
 bne .skip                          ;nein? -> weiter bei .skip
 tya                                ;Offset in den Akku
 clc                                ;Carry für Addition löschen
 adc #40-4                          ;eine Zeile addieren
 tay                                ;und den neuen Offset ins Y-Reg.
 jmp .skip1                         ;weiter bei -> .skip1
.skip
 sta $d800+41,Y                     ;Die Farbe ins Farb-RAM kopieren
 iny                                ;Offset um eins erhöhen
.skip1
 inx                                ;Schleifenzähler erhöhen
 bne .loop                          ;und wieder zum Anfang -> .loop
.exit                               ;das BNE ist kürzer als ein JMP und verhindert eine Endlosschleife
 rts                                ;zurück zum Aufrufer



mask
 !text 147,156
 !text $75,$63,$63,$63,$63,$63,$63,$63,$63,$63,$63,$63,$63,$63,$63,$63,$63,$63,$63,$63,$63,$63,$63,$69,13
 !text $62,"JOY1  JOY2  JOY3  JOY4",$62,13
 !text $62," ",18,$e9,$df,146,"    ",18,$e9,$df,146,"    ",18,$e9,$df,146,"    ",18,$e9,$df,146," ",$62,13
 !text $62,18,$e9,146,$ac,$bb,18,$df,146,"  ",18,$e9,146,$ac,$bb,18,$df,146,"  ",18,$e9,146,$ac,$bb,$12,$df,146,"  ",18,$e9,146,$ac,$bb,18,$df,146,$62,13
 !text $62,$df,$bc,$be,$e9,"  ",$df,$bc,$be,$e9,"  ",$df,$bc,$be,$e9,"  ",$df,$bc,$be,$e9,$62,13
 !text $62," ",$df,$e9,"    ",$df,$e9,"    ",$df,$e9,"    ",$df,$e9," ",$62,13
 !text $6a,$63,$63,$63,$63,$63,$63,$63,$63,$63,$63,$63,$63,$63,$63,$63,$63,$63,$63,$63,$63,$63,$63,$6b,13
 !byte $00

offset
 !byte 0,6,12,18

joyValues
 !byte $00, $00, $00, $00

colors
 !byte COLOR_UNUSED, COLOR_UNUSED, COLOR_UNUSED, COLOR_UNUSED, 16
 !byte COLOR_UNUSED, COLOR_UNUSED, COLOR_UNUSED, COLOR_UNUSED, 16
 !byte COLOR_UNUSED, COLOR_UNUSED, COLOR_UNUSED, COLOR_UNUSED, 16
 !byte COLOR_UNUSED, COLOR_UNUSED, COLOR_UNUSED, COLOR_UNUSED, 16
 !byte COLOR_UNUSED, COLOR_UNUSED, COLOR_UNUSED, COLOR_UNUSED, 255

Damit solltet ihr für eure kommenden Multiplayerspiele gewappnet sein. Der hier beschriebene 4-Spiele Adapter wurde übrigens ursprünglich für Bomb Mania entwickelt. Mittlerweile unterstützen ihn aber eine Reihe weiterer Spiele, wie z. B. IK+ Gold, M.U.L.E. oder Marble Madness. Damit stellt er wohl so eine Art Standard dar.


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

Loading...


Zurück

Schreibe einen Kommentar

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

Protected by WP Anti Spam