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:
Die Programmiersprache, mit der man arbeitet, erlaubt keine Assembler-Einbindungen.
Die Assembler-Routine arbeitet nicht genauso, wie man es benötigt, und Änderungen möchte man nicht vornehmen.
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.