Joystick-Abfrage in Modula

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
Links

Copyright-Bestimmungen: siehe Über diese Seite