Copyright (c) Hyperion Entertainment and contributors.

Gameport Device

From AmigaOS Documentation Wiki
Jump to navigation Jump to search
Warning.png This page is not yet fully updated to AmigaOS 4.x some of the information contained here may not be applicable in part or totally.

Gameport Device

The gameport device manages access to the Amiga gameport connectors for the operating system. It enables the Amiga to interface with various external pointing devices like mice (two and three button), joysticks, trackballs and light pens. There are two units in the gameport device, unit 0 and unit 1.

Amiga Gameport Controllers

Unit 0 Unit 1
A3000 Front Connector Back Connector
A2000 Left Connector Right Connector
A1000 1 2
A500 1 JOYSTICK 2 JOYSTICK

Gameport Device Commands and Functions

Command Command Operation
CMD_CLEAR Clear the gameport input buffer.
GPD_ASKCTYPE Return the type of gameport controller being used.
GPD_ASKTRIGGER Return the conditions that have been preset for triggering.
GPD_READEVENT Read one or more gameport events.
GPD_SETCTYPE Set the type of the controller to be used.
GPD_SETTRIGGER Preset the conditions that will trigger a gameport event.
Who Runs The Mouse?
When the input device or Intution is operating, unit 0 is usually dedicated to gathering mouse events. The input device uses the gameport device to read the mouse events. (For applications that take over the machine without starting up the input device or Intuition, unit 0 can perform the same functions as unit 1.) See Input Device for more information on the input device.

Device Interface

The gameport device operates like the other Amiga devices. To use it, you must first open the gameport device, then send I/O requests to it, and then close it when finished. See Exec Device I/O for general information on device usage.

The I/O request used by the gameport device is called IOStdReq.

struct IOStdReq
{
    struct  Message io_Message;
    struct  Device  *io_Device;     /* device node pointer  */
    struct  Unit    *io_Unit;       /* unit (driver private)*/
    UWORD   io_Command;             /* device command */
    UBYTE   io_Flags;
    BYTE    io_Error;               /* error or warning num */
    ULONG   io_Actual;              /* actual number of bytes transferred */
    ULONG   io_Length;              /* requested number bytes transferred*/
    APTR    io_Data;                /* points to data area */
    ULONG   io_Offset;              /* offset for block structured devices */
};

See the include file exec/io.h for the complete structure definition.

Opening the Gameport Device

Three primary steps are required to open the gameport device:

  • Create a message port using CreatePort(). Reply messages from the device must be directed to a message port.
  • Create an I/O request structure of type IOStdReq. The IOStdReq structure is created by the CreateExtIO() function. CreateExtIO() will initialize the I/O request with your reply port.
  • Open the gameport device. Call OpenDevice(), passing the I/O request and and indicating the unit you wish to use.
struct MsgPort *GameMP;   /* Message port pointer */
struct IOStdReq *GameIO;  /* I/O request pointer */

  /* Create port for gameport device communications */
if (!(GameMP = CreatePort("RKM_game_port",0)))
    cleanexit(" Error: Can't create port\n",RETURN_FAIL);

  /* Create message block for device I/O */
if (!(GameIO = CreateExtIO(GameMP,sizeof(struct IOStdReq))))
    cleanexit(" Error: Can't create I/O request\n",RETURN_FAIL);

  /* Open the right/back (unit 1, number 2) gameport.device unit */
if (error=OpenDevice("gameport.device",1,GameIO,0))
    cleanexit(" Error: Can't open gameport.device\n",RETURN_FAIL);

The gameport commands are unit specific. The unit number specified in the call to OpenDevice() determines which unit is acted upon.

Gameport Device Controllers

The Amiga has five gameport device controller types.

Gameport Device Controllers

Controller Type Description
GPCT_MOUSE Mouse controller
GPCT_ABSJOYSTICK Absolute (digital) joystick
GPCT_RELJOYSTICK Relative (digital) joystick
GPCT_ALLOCATED Custom controller
GPCT_NOCONTROLLER No controller

To use the gameport device, you must define the type of device connected to the gameport and define how the device is to respond. The gameport device can be set up to return the controller status immediately or only when certain conditions have been met.

When a gameport device unit reponds to a request for input, it creates an input event. The contents of the input event will vary based on the type of device and the trigger conditions you have declared.

  • A mouse controller can report input events for one, two, or three buttons and for positive or negative (x,y) movements. A trackball controller or car-driving controller is generally of the same type and can be declared as a mouse controller.
  • An absolute joystick reports one single event for each change of its current location. If, for example, the joystick is centered and the user pushes the stick forward and holds it in that position, only one single forward-switch event will be generated.
  • A relative joystick, on the other hand, is comparable to an absolute joystick with “autorepeat” installed. As long as the user holds the stick in a position other than centered, the gameport device continues to generate position reports.
  • There is currently no system software support for proportional joysticks or proportional controllers (e.g., paddles). If you write custom code to read proportional controllers or other controllers (e.g., light pen) make certain that you issue GPD_SETCTYPE (explained below) with controller type GPCT_ALLOCATED to insure that other applications know the connector is being used.
GPCT_NOCONTROLLER The controller type GPCT_NOCONTROLLER is not a controller at all, but a flag to indicate that the unit is not being used at the present time.

Closing the Gameport Device

Each OpenDevice() must eventually be matched by a call to CloseDevice().

All I/O requests must be complete before CloseDevice(). If any requests are still pending, abort them with AbortIO() and remove them with WaitIO().

if (!(CheckIO(GameIO)))
    {
    AbortIO(GameIO);  /* Ask device to abort request, if pending */
    }
WaitIO((GameIO);   /* Wait for abort, then clean up */
CloseDevice(GameIO);

Gameport Events

A gameport event is an InputEvent structure which describes the following:

  • The class of the event - always set to IECLASS_RAWMOUSE for the gameport device.
  • The subclass of the event - 0 for the left port; 1 for the right port.
  • The code - which button and its state. (No report = 0xFF)
  • The qualifier - only button and relative mouse bits are set.
  • The position - either a data address or mouse position count.
  • The time stamp - delta time since last report, returned as frame count in tv_secs field.
  • The next event - pointer to next event.
struct InputEvent GameEV
{
    struct InputEvent *ie_NextEvent;  /* next event */
    UBYTE    ie_Class;                /* input event class */
    UBYTE    ie_SubClass;             /* subclass of the class */
    UWORD    ie_Code;                 /* input event code */
    UWORD    ie_Qualifier;            /* event qualifiers in effect */
       union
       {
         struct
         {
          WORD   ie_x;                /* x position for the event */
          WORD   ie_y;                /* y position for the event */
         }  ie_xy;
         APTR ie_addr;
       } ie_position;
    struct timeval ie_TimeStamp;      /* delta time since last report
}

See the include file devices/inputevent.h for the complete structure definition and listing of input event fields.

Reading Gameport Events

You read gameport events by passing an I/O request to the device with GPD_READEVENT set in io_Command, the address of the InputEvent structure to store events set in io_Data and the size of the structure set in io_Length.

struct InputEvent  GameEV;
struct IOStdRequest *GameIO;  /* Must be initialized prior to using */

void send_read_request()
{
GameIO->io_Command = GPD_READEVENT;  /* Read events */
GameIO->io_Length = sizeof (struct InputEvent);
GameIO->io_Data = (APTR)&GameEV;     /* put events in GameEV*/
SendIO(GameIO);                      /* Asynchronous */
}

Setting Gameport Event Trigger Conditions

You set the conditions that can trigger a gameport event by passing an I/O request to the device with GPD_SETTRIGGER set in io_Command and the address of a GamePortTrigger structure set in io_Data.

The information needed for gameport trigger setting is placed into a GamePortTrigger data structure which is defined in the include file devices/gameport.h.

struct GamePortTrigger
{
    UWORD    gpt_Keys;      /* key transition triggers */
    UWORD    gpt_Timeout;   /* time trigger (vertical blank units) */
    UWORD    gpt_XDelta;    /* X distance trigger */
    UWORD    gpt_YDelta;    /* Y distance trigger */
}

A few points to keep in mind with the GPD_SETTRIGGER command are:

  • Setting GPTF_UPKEYS enables the reporting of upward transitions. Setting GPTF_DOWNKEYS enables the reporting of downward transitions. These flags may both be specified.
  • The field gpt_Timeout specifies the time interval (in vertical blank units) between reports in the absence of another trigger condition. In other words, an event is generated every gpt_Timeout ticks. Vertical blank units may differ from country to country (e.g 60 Hz NTSC, 50 Hz PAL). To find out the exact frequency use this code fragment:
#include <exec/execbase.h>
extern struct ExecBase *SysBase;

UBYTE get_frequency(void)
{
  return((UBYTE)SysBase->VBlankFrequency);
}
  • The gpt_XDelta and gpt_YDelta fields specify the x and y distances which, if exceeded, trigger a report.

For a mouse controller, you can trigger on a certain minimum-sized move in either the x or y direction, on up or down transitions of the mouse buttons, on a timed basis, or any combination of these conditions.

For example, suppose you normally signal mouse events if the mouse moves at least 10 counts in either the x or y directions. If you are moving the cursor to keep up with mouse movements and the user moves the mouse less than 10 counts, after a period of time you will want to update the position of the cursor to exactly match the mouse position. Thus the timed report of current mouse counts would be preferred. The following structure would be used:

#define XMOVE 10
#define YMOVE 10

struct GamePortTrigger GameTR =
{
    GPTF_UPKEYS | GPTF_DOWNKEYS,   /* trigger on all key transitions */
    1800,                          /* and every 36(PAL) or 30(NTSC) seconds */
    XMOVE,                         /* for any 10 in an x or y direction */
    YMOVE
};

For a joystick controller, you can select timed reports as well as button-up and button-down report trigger conditions. For an absolute joystick specify a value of one (1) for the GameTR_XDelta and GameTR_YDelta fields or you will not get any direction events. You set the trigger conditions by using the following code or its equivalent:

struct IOStdReq *GameIO;

void set_trigger_conditions(struct GamePortTrigger *GameTR)
{
GameIO->io_Command = GPD_SETTRIGGER;    /* set trigger conditions */
GameIO->io_Data = (APTR)GameTR;         /* from GameTR */
GameIO->io_Length = sizeof(struct GamePortTrigger);
DoIO(GameIO);
}
Triggers and Reads. If a task sets trigger conditions and does not ask for the position reports the gameport device will queue them up anyway. If the trigger conditions occur again and the gameport device buffer is filled, the additional triggers will be ignored until the buffer is read by a device read request (GPD_READEVENT) or a system CMD_CLEAR command flushes the buffer.

Determining the Trigger Conditions

You determine the conditions required for triggering gameport events by passing an I/O request to the device with GPD_ASKTRIGGER set in io_Command, the length of the GamePortTrigger structure set in io_Length and the address of the structure set in io_Data. The gameport device will respond with the event trigger conditions currently set.

struct IOStdReq *GameIO;  /* Must be initialized prior to using */

struct GamePortTrigger GameTR;

void get_trigger_conditions(struct GamePortTrigger *GameTR)
{
GameIO->io_Command = GPD_ASKTRIGGER;    /* get type of triggers */
GameIO->io_Data = (APTR)GameTR;         /* place data here */
GameIO->io_Length= sizeof(GameTR);
DoIO(GameIO);
}

Setting and Reading the Controller Type

Determining the Controller Type

You determine the type of controller being used by passing an I/O request to the device with GPD_ASKCTYPE set in io_Command, 1 set in io_Length and the number of the unit set in io_Unit. The gameport device will respond with the type of controller being used.

struct IOStdReq *GameIO;  /* Must be initialized prior to using */

BYTE GetControllerType()
{
BYTE controller_type = 0;

GameIO->io_Command = GPD_ASKCTYPE;         /* get type of controller */
GameIO->io_Data = (APTR)&controller_type;  /* place data here */
GameIO->io_Length = 1;
DoIO(GameIO);
return (controller_type);
}

The BYTE value returned corresponds to one of the five controller types noted above.

Setting the Controller Type

You set the type of gameport controller by passing an I/O request to the device with GPD_SETCTYPE set in io_Command, 1 set in io_Length and the address of the byte variable describing the controller type set in io_Data.

The gameport device is a shared device; many tasks may have it open at any given time. Hence, a high level protocol has been established to prevent multiple tasks from reading the same unit at the same time.

Three Step Protocol for Using the Gameport Device

Send GPD_ASKCTYPE to the device and check for a GPCT_NOCONTROLLER return. Never issue GPD_SETCTYPE without checking whether the desired gameport unit is in use.

If GPCT_NOCONTROLLER is returned, you have access to the gameport. Set the allocation flag to GPCT_MOUSE, GPCT_ABSJOYSTICK or GPCT_RELJOYSTICK if you use a system supported controller, or GPCT_ALLOCATED if you use a custom controller.

struct IOStdReq *GameIO;  /* Must be initialized prior to using */

BOOL set_controller_type(type)
BYTE type;
{

BOOL success = FALSE;
BYTE controller_type = 0;

Forbid();                           /*critical section start */
GameIO->io_Command = GPD_ASKCTYPE;  /* inquire current status */
GameIO->io_Length = 1;
GameIO->io_Flags = IOF_QUICK;
GameIO->io_Data = (APTR)&controller_type; /* put answer in here */
DoIO(GameIO);

/* No one is using this device unit, let's claim it */
if (controller_type == GPCT_NOCONTROLLER)
    {
    GameIO->io_Command = GPD_SETCTYPE;/* set controller type */
    GameIO->io_Length = 1;
    GameIO->io_Data = (APTR)&type;  /* set to input param */
    DoIO( GameIO);
    success = TRUE;
    UnitOpened = TRUE;
    }
Permit(); /* critical section end */

/* success can be TRUE or FALSE, see above */
return(success);
}

The program must set the controller type back to GPCT_NOCONTROLLER upon exiting your program:

struct IOStdReq *GameIO;  /* Must be initialized prior to using */

void free_gp_unit()
{
BYTE type = GPCT_NOCONTROLLER;
GameIO->io_Command = GPD_SETCTYPE;  /* set controller type */
GameIO->io_Length = 1;
GameIO->io_Data = (APTR)&type;      /* set to unused */
DoIO( GameIO);
}

This three step protocol allows applications to share the gameport device in a system compatible way.

A Word About The Functions. The functions shown above are designed to be included in any application using the gameport device. The first function, set_controller_type(), would be the first thing done after opening the gameport device. The second function, free_gp_unit(), would be the last thing done before closing the device.

Joystick Example Program

/*
 * Absolute_Joystick.c
 *
 * Gameport device absolute joystick example
 *
 * Compile with SAS 5.10  lc -b1 -cfistq -v -y -L
 *
 * Run from CLI only
 */

#include <exec/types.h>
#include <exec/io.h>
#include <exec/memory.h>
#include <intuition/intuition.h>
#include <exec/exec.h>
#include <dos/dos.h>
#include <devices/gameport.h>
#include <devices/inputevent.h>

#include <clib/exec_protos.h>
#include <clib/alib_protos.h>
#include <clib/dos_protos.h>
#include <clib/intuition_protos.h>

#include <stdio.h>

#ifdef LATTICE
int CXBRK(void) { return(0); }     /* Disable SAS CTRL/C handling */
int chkabort(void) { return(0); }  /* really */
#endif

#define JOY_X_DELTA (1)
#define JOY_Y_DELTA (1)
#define TIMEOUT_SECONDS (10)

extern struct ExecBase *SysBase;

/*-----------------------------------------------------------------------
** Routine to print out some information for the user.
*/
VOID printInstructions(VOID)
{
printf("\n >>> gameport.device Absolute Joystick Demo <<<\n\n");

if (SysBase->VBlankFrequency==60)
    printf(" Running on NTSC system (60 Hz).\n");
else if (SysBase->VBlankFrequency==50)
    printf(" Running on PAL system (50 Hz).\n");

printf( " Attach joystick to rear connector (A3000) and (A1000).\n"
        " Attach joystick to right connector (A2000).\n"
        " Attach joystick to left connector (A500).\n"
        " Then move joystick and click its button(s).\n\n"
        " To exit program press and release fire button 3 consecutive times. \n"
        " The program also exits if no activity occurs for 1 minute.\n\n");
}


/*-----------------------------------------------------------------------
** print out information on the event received.
*/
BOOL check_move(struct InputEvent *game_event)
{
WORD xmove, ymove;
BOOL timeout=FALSE;

xmove = game_event->ie_X;
ymove = game_event->ie_Y;

if (xmove == 1)
    {
    if (ymove == 1) printf("RIGHT DOWN\n");
    else if (ymove == 0) printf("RIGHT\n");
    else if (ymove ==-1) printf("RIGHT UP\n");
    else printf("UNKNOWN Y\n");
    }
else if (xmove ==-1)
    {
    if (ymove == 1) printf("LEFT DOWN\n");
    else if (ymove == 0) printf("LEFT\n");
    else if (ymove ==-1) printf("LEFT UP\n");
    else printf("UNKNOWN Y\n");
    }
else if (xmove == 0)
    {
    if (ymove == 1) printf("DOWN\n");
    /* note that 0,0 can be a timeout, or a direction release. */
    else if (ymove == 0)
        {
        if (game_event->ie_TimeStamp.tv_secs >=
                        (UWORD)(SysBase->VBlankFrequency) * TIMEOUT_SECONDS)
            {
            printf("TIMEOUT\n");
            timeout=TRUE;
            }
        else printf("RELEASE\n");
        }
    else if (ymove ==-1) printf("UP\n");
    else printf("UNKNOWN Y\n");
    }
else
    {
    printf("UNKNOWN X ");
    if (ymove == 1) printf("unknown action\n");
    else if (ymove == 0) printf("unknown action\n");
    else if (ymove ==-1) printf("unknown action\n");
    else printf("UNKNOWN Y\n");
    }

return(timeout);

}

/*-----------------------------------------------------------------------
** send a request to the gameport to read an event.
*/
VOID send_read_request( struct InputEvent *game_event,
                        struct IOStdReq *game_io_msg)
{
game_io_msg->io_Command = GPD_READEVENT;
game_io_msg->io_Flags   = 0;
game_io_msg->io_Data    = (APTR)game_event;
game_io_msg->io_Length  = sizeof(struct InputEvent);
SendIO(game_io_msg);  /* Asynchronous - message will return later */
}

/*-----------------------------------------------------------------------
** simple loop to process gameport events.
*/
VOID processEvents( struct IOStdReq *game_io_msg,
                    struct MsgPort  *game_msg_port)
{
BOOL timeout;
SHORT timeouts;
SHORT button_count;
BOOL  not_finished;
struct InputEvent game_event;   /* where input event will be stored */

/* From now on, just read input events into the event buffer,
** one at a time.  READEVENT waits for the preset conditions.
*/
timeouts = 0;
button_count = 0;
not_finished = TRUE;

while ((timeouts < 6) && (not_finished))
    {
    /* Send the read request */
    send_read_request(&game_event,game_io_msg);

    /* Wait for joystick action */
    Wait(1L << game_msg_port->mp_SigBit);
    while (NULL != GetMsg(game_msg_port))
        {
        timeout=FALSE;
        switch(game_event.ie_Code)
            {
            case IECODE_LBUTTON:
                printf(" FIRE BUTTON PRESSED \n");
                break;

            case (IECODE_LBUTTON | IECODE_UP_PREFIX):
                printf(" FIRE BUTTON RELEASED \n");
                if (3 == ++button_count)
                    not_finished = FALSE;
                break;

            case IECODE_RBUTTON:
                printf(" ALT BUTTON PRESSED \n");
                button_count = 0;
                break;

            case (IECODE_RBUTTON | IECODE_UP_PREFIX):
                printf(" ALT BUTTON RELEASED \n");
                button_count = 0;
                break;

            case IECODE_NOBUTTON:
                /* Check for change in position */
                timeout = check_move(&game_event);
                button_count = 0;
                break;

            default:
                break;
            }

        if (timeout)
            timeouts++;
        else
            timeouts=0;
        }
    }
}

/*-----------------------------------------------------------------------
** allocate the controller if it is available.
** you allocate the controller by setting its type to something
** other than GPCT_NOCONTROLLER.  Before you allocate the thing
** you need to check if anyone else is using it (it is free if
** it is set to GPCT_NOCONTROLLER).
*/
BOOL set_controller_type(BYTE type, struct IOStdReq *game_io_msg)
{
BOOL success = FALSE;
BYTE controller_type = 0;

/* begin critical section
** we need to be sure that between the time we check that the controller
** is available and the time we allocate it, no one else steals it.
*/
Forbid();

game_io_msg->io_Command = GPD_ASKCTYPE;    /* inquire current status */
game_io_msg->io_Flags   = IOF_QUICK;
game_io_msg->io_Data    = (APTR)&controller_type; /* put answer in here */
game_io_msg->io_Length  = 1;
DoIO(game_io_msg);

/* No one is using this device unit, let's claim it */
if (controller_type == GPCT_NOCONTROLLER)
    {
    game_io_msg->io_Command = GPD_SETCTYPE;
    game_io_msg->io_Flags   = IOF_QUICK;
    game_io_msg->io_Data    = (APTR)&type;
    game_io_msg->io_Length  = 1;
    DoIO( game_io_msg);
    success = TRUE;
    }

Permit(); /* critical section end */
return(success);
}

/*-----------------------------------------------------------------------
** tell the gameport when to trigger.
*/
VOID set_trigger_conditions(struct GamePortTrigger *gpt,
                            struct IOStdReq *game_io_msg)
{
/* trigger on all joystick key transitions */
gpt->gpt_Keys   = GPTF_UPKEYS | GPTF_DOWNKEYS;
gpt->gpt_XDelta = JOY_X_DELTA;
gpt->gpt_YDelta = JOY_Y_DELTA;
/* timeout trigger every TIMEOUT_SECONDS second(s) */
gpt->gpt_Timeout = (UWORD)(SysBase->VBlankFrequency) * TIMEOUT_SECONDS;

game_io_msg->io_Command = GPD_SETTRIGGER;
game_io_msg->io_Flags   = IOF_QUICK;
game_io_msg->io_Data    = (APTR)gpt;
game_io_msg->io_Length  = (LONG)sizeof(struct GamePortTrigger);
DoIO(game_io_msg);
}

/*-----------------------------------------------------------------------
** clear the buffer.  do this before you begin to be sure you
** start in a known state.
*/
VOID flush_buffer(struct IOStdReq *game_io_msg)
{
game_io_msg->io_Command = CMD_CLEAR;
game_io_msg->io_Flags   = IOF_QUICK;
game_io_msg->io_Data    = NULL;
game_io_msg->io_Length  = 0;
DoIO(game_io_msg);
}

/*-----------------------------------------------------------------------
** free the unit by setting its type back to GPCT_NOCONTROLLER.
*/
VOID free_gp_unit(struct IOStdReq *game_io_msg)
{
BYTE type = GPCT_NOCONTROLLER;

game_io_msg->io_Command = GPD_SETCTYPE;
game_io_msg->io_Flags   = IOF_QUICK;
game_io_msg->io_Data    = (APTR)&type;
game_io_msg->io_Length  = 1;
DoIO(game_io_msg);
}

/*-----------------------------------------------------------------------
** allocate everything and go.  On failure, free any resources that
** have been allocated.  this program fails quietly--no error messages.
*/
VOID main(int argc,char **argv)
{
struct GamePortTrigger   joytrigger;
struct IOStdReq         *game_io_msg;
struct MsgPort          *game_msg_port;

/* Create port for gameport device communications */
if (game_msg_port = CreatePort("RKM_game_port",0))
    {
    /* Create message block for device IO */
    if (game_io_msg = (struct IOStdReq *)
                      CreateExtIO(game_msg_port,sizeof(*game_io_msg)))
        {
        game_io_msg->io_Message.mn_Node.ln_Type = NT_UNKNOWN;

        /* Open the right/back (unit 1, number 2) gameport.device unit */
        if (!OpenDevice("gameport.device",1,game_io_msg,0))
            {
            /* Set controller type to joystick */
            if (set_controller_type(GPCT_ABSJOYSTICK,game_io_msg))
                {
                /* Specify the trigger conditions */
                set_trigger_conditions(&joytrigger,game_io_msg);

                printInstructions();

                /* Clear device buffer to start from a known state.
                ** There might still be events left
                */
                flush_buffer(game_io_msg);

                processEvents(game_io_msg,game_msg_port);

                /* Free gameport unit so other applications can use it ! */
                free_gp_unit(game_io_msg);
                }
            CloseDevice(game_io_msg);
            }
        DeleteExtIO(game_io_msg);
        }
    DeletePort(game_msg_port);
    }
}

Additional Information on the Gameport Device

Additional programming information on the gameport device can be found in the include files and the Autodocs for the gameport and input devices. Both are contained in the SDK.

Includes
devices/gameport.h
devices/inputevent.h
AutoDocs
gameport.doc