← ST-Computer 01 / 1990

Joystick-Abfrage in Modula

Programmierpraxis

Okay, es dreht sich mal wieder um den leidigen Joystick. Ist es Ihnen noch nicht so gegangen: Man möchte gerne in einem Programm den Joystick abfragen und stösst auf eine der zahlreichen Assemblerprogramme, die in diversen Zeitungen abgedruckt worden sind. Im Prinzip ist das ja auch ganz nett, aber eben Assembler.

Und da gibt es ein paar Gründe, weshalb man mit dieser Lösung nicht zufrieden sein könnte:

  1. Die Programmiersprache, mit der man arbeitet, erlaubt keine Assembler-Einbindungen.

  2. Die Assembler-Routine arbeitet nicht genauso, wie man es benötigt, und Änderungen möchte man nicht vornehmen.

  3. Man möchte gerne verstehen, wie man im allgemeinen den Joystick abfragt und eine Lösung parat haben, die prinzipiell in jeder Sprache funktioniert.

Und da (trara) biete ich eine optimale Lösung an: Mein Modula-2-Modul “JoyEvent” beinhaltet 10 Bytes Maschinenbefehle und ist ansonsten reines Modula. Die Schnittstelle ist äußerst portabel angelegt und arbeitet nur über eine Variable. Und jetzt erkläre ich, wie es geht:

Im XBIOS gibt es eine Routine Kbdvbase, mit der man sich eine Tabelle einiger wichtiger Vektoren ausgeben lassen kann. Unter anderem steht hier auch der Vektor joyvec. An der Adresse joyvec liegt ein Unterprogramm, das jedesmal, wenn ein Kontakt am Joystick geöffnet oder geschlossen wird, angesprungen wird. Beim Ansprung dieses Unterprogramms übergibt XBIOS im Register A0 und auf dem Stack einen Pointer auf einen Speicherbereich, in dem die aktuellen Joystick-Daten abgelegt worden sind. Im ersten Byte steht, um welchen Port es sich handelt, im zweiten ein Wert, dessen Bits angeben, welche Bewegung stattgefunden hat.

Mich interessierte natürlich zunächst mal, was die Routine, die normalerweise vom XBIOS angesprungen wird, mit den Daten macht. Ich habe also den besagten Speicherbereich disassembliert und festgestellt: RTS. Die Routine macht also nichts. Ein sehr gutes Zeichen, denn das bedeutet ja schließlich, daß man, wenn man keine Register ändert und den Stack in Ruhe läßt, dort machen kann, was man will. Die einfachste und auch von mir verwendete Lösung ist also Joyvec auf eine Routine zu verbiegen, die so aussieht:

move.b 2(A0), Wert PTR rts

Dieses Unterprogramm dereferenziert die Adresse, die in A0 steht (plus einem Offset von 2, da wir ja das zweite Byte des gegebenen Speicherbereichs auslesen wollen), und schreibt die sich dort befindliche Zahl an eine Adresse, die wir im Augenblick Wert PTR nennen wollen. Wenn man dieses kurze Stück Programm assembliert, ergibt sich folgende Speicheraufteilung:

13E8 0002 Wert PTR 4E75

Es springt einem förmlich ins Auge, daß man daraus eine Struktur machen kann von folgendem Typ:

RoutineRec = RECORD Opcode : CARDINAL; Offset : CARDINAL; Adresse : ADDRESS; Return : CARDINAL; END;

Folgendes Programmfragment ergibt sich quasi automatisch :

VAR Routine : RoutineRec; Wert : BITSET; BEGIN Routine.Opcode := 13E8H; Routine.Offset := 2; Routine.Adresse:= ADR(Wert); Routine.Return := 4E75H; ... END;

Jetzt muß man nur noch an joyvec die Adresse von Routine.Opcode zuweisen, und schon hat man in seinem Modula-Source ein Stück Maschinensprache geschrieben, das von XBIOS bei jeder Joystick-Operation angesprochen wird und somit einen Spiegel des Wertes beschafft, der eine Aussage über die genaue Joystick-Bewegung macht. Die restlichen Anweisungen, die im Modul JoyEvent noch gemacht werden, sind nur eingefügt, um das Bild abzurunden und im Prinzip so verwendbar. Nur braucht man jetzt keine einzige Zeile Assembler oder gar Maschinensprache mehr, um die Joystickbewegungen zu interpretieren.

JoyEvent liefert auch noch die Angabe, ob der Feuerknopf gedrückt wurde. Die Antwort darauf sagt mir das VDI, da der Firebutton genauso behandelt wird wie der rechte Mausknopf. Alles andere sollte aus dem Source klar hervorgehen und in jede gewünschte Sprache portierbar sein.

(*----------------------------------------------*) (*--- Module JoyEvent ---*) (*--- --------------- ---*) (*--- Modul Joystick- und Button-Abfrage ---*) (*--- (c) MAXON Computer GmbH ---*) (*--- Programmiersprache: SPC-Modula-2 V1.3 ---*) (*--- Computersystem : ATARI 1040 ST ---*) (*--- Autor : Uwe A. Ruttkamp & Clemens Fehr ---*) (*--- Datum : 24.09.1988 ---*) (*----------------------------------------------*) DEFINITION MODULE JoyEvent; TYPE JoyEventTyp = ( Right, Left, Up, Down, None ); PROCEDURE InitJoyEvent; (* InitJoyEvent dient zum Initialisieren des JoyEvent Moduls. Von jetzt ab wird bei jeder Joystickbewegung intern ein Wert modifiziert. Deshalb ist es auch notwendig die TermJoyEvent Prozedur aufzurufen, um dieses wieder abzuschalten. *) PROCEDURE Joystick( VAR Event : JoyEventTyp ) : BOOLEAN; (* Dies ist die eigentliche Kernroutine dieses Moduls. Durch einen Aufruf der Prozedur Joystick erfährt das aufrufende Programm die aktuell vom Joystick gemeldete Bewegung. Wenn Event gleich None ist, so befindet dich der Joystick im Ruhezustand. Entsprechend den anderen möglichen Werten wird der Joystick im Augenblick bewegt. Der Rückgabewert entspricht einem gedrückten Firebutton: TRUE : Firebutton gedrückt FALSE : Firebutton nicht gedrückt Joystick liefert nur sinnvolle Werte, wenn zuvor ein Aufruf von InitJoyEvent stattgefunden hat. *) PROCEDURE TermJoyEvent; (* Diese Prozedur muß spätstens vor Beendigung des laufenden Programmes aufgerufen werden. Sollte dies nicht geschehen, kann ein Systemabsturz im weiteren Verlauf Ihrer Sitzung am Atari die Folge sein. Nach einem Aufruf von TermJoyEvent liefert Joystick keine sinnvollen Werte mehr! *) END JoyEvent. (*----------------------------------------------*) (*--- Implementations Module JoyEvent ---*) (*--- ------------------------------- ---*) (*--- Modul zur Joystick- und Buttonabfrage ---*) (*--- (C) MAXON Computer GmbH *) (*--- Programmiersprache: SPC-Modula-2 V1.3 ---*) (*--- Computersystem : ATARI 1040 ST ---*) (*--- Autor : Uwe A. Ruttkamp & Clemens Fehr ---*) (*--- Datum : 24.09.1988 ---*) (*----------------------------------------------*) IMPLEMENTATION MODULE JoyEvent; IMPORT XBIOS; FROM SYSTEM IMPORT ADR, ADDRESS; FROM VDIControls IMPORT OpenVirtualWorkstation, CloseVirtualWorkstation, WorkstationInitRec, WorkstationDescription; FROM VDIInputs IMPORT MouseState, MouseCodes, SampleMouseButton; FROM VDIOutputs IMPORT Coordinate; CONST MoveA0 = 13E8H; RTS = 4E75H; TYPE RoutineRec = RECORD Opcode : CARDINAL; Offset : CARDINAL; Adresse : ADDRESS; Return : CARDINAL; END; VAR WorkIn : WorkstationInitRec; WorkOut : WorkstationDescription; Handle : INTEGER; PStatus : MouseState; Location : Coordinate; Routine : RoutineRec; Vector : XBIOS.KBDVECSPtr; OldVec : ADDRESS; Wert : BITSET; PROCEDURE InitJoyEvent; VAR i : CARDINAL; BEGIN OpenVirtualWorkstation( WorkIn, Handle, WorkOut ); FOR i:=0 TO 15 DO EXCL(Wert, i); END; Routine.Opcode := MoveA0; Routine.Offset := 2; Routine.Adresse := ADR(Wert); Routine.Return := RTS; (* Ab der Adresse ADR(Routine.Opcode) steht nun, in assemblierter Form natürlich, folgende Befehlsfolge : move.b 2(a0), Wert rts *) Vector := XBIOS.Kbdvbase(); OldVec := Vector^.joyvec; Vector^.joyvec := ADR(Routine.Opcode); (* Jetzt haben wir den Pointer, der auf die Routine zeigt, die bei jeder Joystickaktion angesprungen wird verbogen auf unsere, oben beschriebene, Routine. Diese besteht nur aus der Anweisung den Wert auf den A0 zeigt (plus einem Offset von 2) an der Stelle abzulegen, wo die globale Variable Wert steht. *) END InitJoyEvent; PROCEDURE Joystick( VAR Event : JoyEventTyp ) : BOOLEAN; BEGIN IF 11 IN Wert THEN Event := Right ELSIF 10 IN Wert THEN Event := Left ELSIF 9 IN Wert THEN Event := Down ELSIF 8 IN Wert THEN Event := Up ELSE Event := None END; SampleMouseButton( Handle, PStatus, Location ); RETURN (RightButton IN PStatus); END Joystick; PROCEDURE TermJoyEvent; BEGIN Vector := XBIOS.Kbdvbase(); Vector^.joyvec := OldVec; CloseVirtualWorkstation( Handle ); END TermJoyEvent; END JoyEvent.
Uwe A. Ruttkamp