READING THE CONTROLLERS

What's the purpose of writing a game if you can't interact
with the applcation. Might as well watch a video then.
After all, a good game involves human input/feedback.
In this document, my aim is to show how to read input from
standard and analogue controllers.

Any suggestions or criticisms on this document welcome.
May I apologise for any errors that may occur, since
I don't have the original analogue controller, only the
Dual Shock which I am working from.

James Chow aka jc             3 June 1998
james@chowfam.demon.co.uk

6 July 1998 - Slight correction to paragraph Understanding the
              System Buffers. By Mike Kavallierou. Thanks.
---------------------------------------------------------------


Overview
--------
During the vertical retrace, the status of the controllers
is read into the system buffers, and this is utilised in
our game.
The status of the controllers includes, whether the
a controller is present or not, the type of the controller
present, and of course which buttons have been pressed and
the position of the joysticks if any.

Understanding the System Buffers
--------------------------------
The system buffers can hold up 34 bytes of information,
regarding the status of the controllers.

Byte    Information
0       Whether controller is connected or not.
1       Upper 4 bits - type of controller
        Lower 4 bits - half the number of bytes of received data
2-33    Received data

Byte 0 holds 0x00 if the controller is connected, and 0xff is not.
Byte 1, upper 4 bits holds a number to uniquely identify the
type of controller connected.
Standard controllers have a value of 0x4.
Analogue pads, including the Dual Shock, have the same id number
as that of standard controllers when the analogue mode light
is off. But in analogue mode, when the light is red, the controller
has an id value of 0x7.
Analogue joysticks, which the non-Dual Shock analogue pad
supports, when the analogue mode light is green, the id
value is 0x5.
Personally, I've never used the lower 4 bits of byte 1 to
tell me how much received data there has been.
To configure our game to whichever controller, we already know
which it will be and so that information becomes redundant.

For the standard controller and analogue controllers, bytes 2
and 3 hold the information regarding button presses. And for
the analogue controller, bytes 4 to 7 holds the information
regarding the position of the joysticks.
The bit positions corresponding to the buttons can be found
in the User Guide.
For the analogue controller:
Byte     Information
4        Right Joystick Horizontal position
5        Right Joystick Vertical position
6        Left Joystick Horizontal position
7        Left Joystick Vertical position

A value of 0 for vertical positions represents UP
on the joystick, increasing to 255 for DOWN.
A value of 0 for horizontal postions represents LEFT
on the joystick, increasing to 255 for RIGHT.

Reading the Received Data
-------------------------
By an unfortunate quirk of fate (or simply because it is easier
to implement in hardware), a bit value of 1 indicates a button
is not pressed, and a bit value of 0 indicates a button press.
However, this is easy to overcome. We can simply invert the
bit values.
To extract byte 2 and 3 to occupy a single variable, and
invert them so 1 indicates button press, we do -

  //bb0 initialised to point to controller
  //buffer in system memory already

  int padstate = ~(*(bb0+3) | *(bb0+2)<<8);

To read whether a particular button is pressed, we use the
corresponding bit mask (with nice names - fortunately),
already defined in the pad.h files located on the
development CD-ROM.

   if (padstate & PADstart) {
     //start pressed
     //process as appropriate
   }

Recommendations
---------------
Unfortunately, simply reading the button presses/joystick
positions is not good enough. Suppose, we are reading the
analogue values for our game, when the controller is removed.
The system buffers do NOT clear the analogue values, and
so we will reading non-existent input.
The Quality Assurance Group which test the games, specify
that certain conditions be met before a game is released.
As game programmers, even at the hobbyist level, it is
good practice to follow these guidelines.
This information can be found in the Peripherals web page
under Manual Additions.
But briefly -
A game must be played with the standard controller, (from
controller port 1) for a single player game.
Input from controllers not supported should be ignored.
Controllers should be able to be identified from option screens.
Games should go into pause state if diconnected during play.

This leads to code which follows such as

  if (pad connected)
    if (pad supported)
      process input
    else
      ignore input
  else
    no input

Demonstration
-------------
For demonstration purposes, the recommendations will be
ignored (!). But this should be clear as to the purpose
of the demo. Another thing to notice, is that I'm using
my own pad reading code, so the pad.h file is not that
on the CD-ROM, (I've added my own stuff).
Game Overview: To display the controller information
               on screen. Exit when Start and Select
               pressed on controller 1.

/*********/
/* pad.h */
/*********/

#define PADLup     (1<<12)
#define PADLdown   (1<<14)
#define PADLleft   (1<<15)
#define PADLright  (1<<13)
#define PADRup     (1<<4)
#define PADRdown   (1<<6)
#define PADRleft   (1<<7)
#define PADRright  (1<<5)
#define PADstart   (1<<11)
#define PADselect  (1<<8)
#define PADL1      (1<<2)
#define PADL2      (1<<0)
#define PADL3      (1<<9)
#define PADR1      (1<<3)
#define PADR2      (1<<1)
#define PADR3      (1<<10)

#define PADin   0x00
#define PADout  0xff

#define PADstandard  0x4
#define PADanalogue  0x5
#define PADdualshck  0x7

void initPads(void);
int padConnected(int port);
int padType(int port);
int padState(int port);
int padLHorz(int port);   //L analogue X value
int padLVert(int port);   //L analogue Y value
int padRHorz(int port);   //R analogue X value
int padRVert(int port);   //R analogue Y value

//Horz analogue values: 0 left, 255 right
//Vert analogue values: 0 up, 255 down

/*********/
/* pad.c */
/*********/

#include "libps.h"
#include "pad.h"

volatile u_char *bb[2];

void initPads(void) { GetPadBuf(&bb[0],&bb[1]); }
int padConnected(int port) { return *bb[port]; }
int padType(int port) { return (*(bb[port]+1))>>4; }
int padState(int port) { return ~(*(bb[port]+3) | *(bb[port]+2)<<8); }
int padLHorz(int port) { return *(bb[port]+6); }
int padLVert(int port) { return *(bb[port]+7); }
int padRHorz(int port) { return *(bb[port]+4); }
int padRVert(int port) { return *(bb[port]+5); }

/**********/
/* main.c */
/**********/

#include "libps.h"
#include "pad.h"

#define OTLEN   (1)
#define SCNW    (320)
#define SCNH    (240)

//utility function for printing out in binary
char* toBinary(long var,int len,char* text) {
  long mask = 1;
  int i;
  for (i=len-1; i>=0; i--) {
    if (var&mask)
      text[i] = '1';
    else
      text[i] = '0';
    mask = mask << 1;
  }
  text[len] = '\0';
  return text;
}

void initGame(void) {
  FntLoad(960,256);
  FntOpen(0,0,SCNW,SCNH,0,512);
  initPads();
}

int updateGame(void) {
  if (padConnected(0) == PADin)
    if ((padType(0) == PADstandard)||
        (padType(0) == PADanalogue)||
        (padType(0) == PADdualshck))
      if ((padState(0) & PADstart)&&
          (padState(0) & PADselect))
        return 1;
  return 0;
}

void drawGame(void) {
  int i;
  char text[33];
  FntPrint("             BINARY   INTEGER\n\n");
  for (i=0;i<2;i++) {
    FntPrint("CONTROLLER %d\n",i+1);
    FntPrint("CONNECTED?   %s  %d\n",
             toBinary(padConnected(i),8,text),padConnected(i));
    FntPrint("TYPE?            %s  %d\n",
             toBinary(padType(i),4,text),padType(i));
    FntPrint("BUTTON VALS: %s\n",toBinary(padState(i),16,text));
    FntPrint("LEFT HORZ:   %s  %d\n",
             toBinary(padLHorz(i),8,text),padLHorz(i));
    FntPrint("LEFT VERT:   %s  %d\n",
             toBinary(padLVert(i),8,text),padLVert(i));
    FntPrint("RIGHT HORZ:  %s  %d\n",
             toBinary(padRHorz(i),8,text),padRHorz(i));
    FntPrint("RIGHT VERT:  %s  %d\n",
             toBinary(padRVert(i),8,text),padRVert(i));
    FntPrint("\n\n");
  }
  FntFlush(-1);
}

void main(void) {
  GsOT     ot[2];
  GsOT_TAG ottags[2][1<