4 Joysticks am C64
Falls 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.
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.
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.
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.
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.
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.
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…
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.
!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.