Click here if you are stuck in someone else's frames.
Joystick Software Routines

If we were to simply read the joystick port and look at its bits, they would tell us very little about the state of the joystick pots.  That's because the bits themselves only tell us whether or not the capacitors in series with the joystick pots are fully charged.  They will tell us also whether or not any of the buttons are pressed.  To see how this is done, let's look at some code.  These examples assume a little prior knowledge in binary numbers and their hexadecimal equivelent as well as bit manipulation using AND, OR, and NOT operators.

The first example reads the port and displays the status of the joysticks. 

     /* JOYBITS.C ---- Polls joystick status bits.
      * by Gary Neal, Jr. -- garyneal_71@yahoo.com
      */

     #include <conio.h>          /* Console input/output */

     /* Joystick Pots */
     #define Bit0 0x01           /* Joystick A X-axis */
     #define Bit1 0x02           /* Joystick A Y-axis */
     #define Bit2 0x04           /* Joystick B X-axis */
     #define Bit3 0x08           /* Joystick B Y-axis */

     /* Joystick Buttons */
     #define Bit4 0x10           /* Joystick A Button 1 */
     #define Bit5 0x20           /* Joystick A Button 2 */
     #define Bit6 0x40           /* Joystick B Button 1 */
     #define Bit7 0x80           /* Joystick B Button 2 */

     int main(void)
     {
         int joyState;           /* Port value */

         /* Some joystick cards may require a write to this port first
          * in order to function properly.  Try enabling the following
          * line if you suspect that to be the case on your system.
          */
         /* outp(0x201, 0); */

         joyState = inp(0x201);  /* Read joystick port */

         /* Check joystick pot status */
         cprintf("Capacitor for Joystick A's X-axis is %s charged.\r\n",
                 ((joyState & Bit0) ? "not" : "fully"));
         cprintf("Capacitor for Joystick A's Y-axis is %s charged.\r\n",
                 ((joyState & Bit1) ? "not" : "fully"));
         cprintf("Capacitor for Joystick B's X-axis is %s charged.\r\n",
                 ((joyState & Bit2) ? "not" : "fully"));
         cprintf("Capacitor for Joystick B's Y-axis is %s charged.\r\n",
                 ((joyState & Bit3) ? "not" : "fully"));

         /* Check joystick buttons status */
         cprintf("\r\nButton 1 on Joystick A is %spressed.\r\n",
                 ((joyState & Bit4) ? "not " : ""));
         cprintf("Button 2 on Joystick A is %spressed.\r\n",
                 ((joyState & Bit5) ? "not " : ""));
         cprintf("Button 1 on Joystick B is %spressed.\r\n",
                 ((joyState & Bit6) ? "not " : ""));
         cprintf("Button 2 on Joystick B is %spressed.\r\n",
                 ((joyState & Bit7) ? "not " : ""));

         /* Finish up */
         return 0;
     }

Compile and run this program and it'll display the status of the joysticks by reading Port 201h and individually checking its bits.  Each time you run the program, it displays the current status of the joysticks.  After running it a few times and pressing one or more of the joystick buttons you can see that it is very useful for checking their status.  Please note that when you're checking the port for the buttons' status, the bits returned are high if the joystick button is not pressed and low it it is.  To see what I mean, compile and run the next example.

     /* JOYBITS2.C ---- Polls and displays bits of port 201h.
      * by Gary Neal, Jr. -- garyneal_71@yahoo.com
      */

     #include <conio.h>  /* Console specific I/O */

     int main(void)
     {
         int joyState, bitMask;

         clrscr();                        /* Blank the screen */
         _setcursortype(_NOCURSOR);       /* Specific to Turbo C, okay to delete */
         gotoxy(10, 7);                   /* Set screen position */
         cputs("Port 201h status bits:");
         gotoxy(15, 9);                   /* Set screen position */
         cputs("7 6 5 4 3 2 1 0");
         gotoxy(15, 13);                  /* Set screen position */
         cputs("^ ^ ^ ^ ^ ^ ^ ^");
         gotoxy(15, 14);                  /* Set screen position */
         cputs("| | | | | | | +--- Capacitor state for Joystick A X-axis");
         gotoxy(15, 15);                  /* Set screen position */
         cputs("| | | | | | +----- Capacitor state for Joystick A Y-axis");
         gotoxy(15, 16);                  /* Set screen position */
         cputs("| | | | | +------- Capacitor state for Joystick B X-axis");
         gotoxy(15, 17);                  /* Set screen position */
         cputs("| | | | +--------- Capacitor state for Joystick B Y-axis");
         gotoxy(15, 18);                  /* Set screen position */
         cputs("| | | +----------- Switch state for Joystick A Button 1");
         gotoxy(15, 19);                  /* Set screen position */
         cputs("| | +------------- Switch state for Joystick A Button 2");
         gotoxy(15, 20);                  /* Set screen position */
         cputs("| +--------------- Switch state for Joystick B Button 1");
         gotoxy(15, 21);                  /* Set screen position */
         cputs("+----------------- Switch state for Joystick B Button 2");

         while (kbhit()) getch();         /* Clear the keyboard */

         while (!kbhit()) {

             /* Some joystick cards may require a write to this port first
              * in order to function properly.  Try enabling the following
              * line if you suspect that to be the case on your system.
              */
             /* outp(0x201, 0); */

             joyState = inp(0x201);       /* Read the joystick port */
             gotoxy(15, 11);              /* Screen position for bits */
             for (bitMask = 0x80; bitMask; bitMask >>= 1) {
                 if (joyState & bitMask)  /* Mask bit and display it */
                     putch('1');
                 else
                     putch('0');
                 putch(' ');
             }
         }

         while (kbhit()) getch();         /* Clear the keyboard */
         _setcursortype(_NORMALCURSOR);   /* Restore the cursor */
         return 0;
     }

As you can see, as long as the joystick button is not pressed, its corresponding bit is high (logic 1).  It goes low (becomes zero) when the button is pressed.  This must be taken into account when functions are written that employ their use.  Now let's look at the last four bits (the ones under the 3, 2, 1, and 0 columns).  These bits tell us if the joystick caps are fully charged or not.  If they are fully charged, the corresponding bit is low.  If the capacitor is still charging, or is otherwise uncharged, the bit will be high.  Currently, all bits should be either a one or a zero.  If they remain a one, chances are good that no joystick is plugged in for that port.  The caps in this case cannot charge because there is no joystick pot in series with that cap.  (Cap, by the way, is a lazy way electronics and electrical people refer to capacitors just as they refer to potentiometers, variable resistors, as pots.)

Of course, this still tells us very little about the position of the joystick.  It may be nice to know whether the caps themselves are charge or not, but we really want to know the resistance of the joystick pots that are in series with those caps.  By measuring their resistances, we can measure the positions of the X and Y axes and use that information to determine where the joystick is being deflected.  So, what we need to do is discharge those caps and time how long it takes before they become fully charged.  You see, because the joystick pots themselves vary their resistance according to the position in which the stick is being held (as illustrated earlier), the time in which the capacitors take to charge back up will also vary accordingly.  If you happen to be studying (or have studied) basic electronics, you would've immediately recognized the circuit as a basic RC circuit.

So the first thing we need to do is have the joystick card discharge its capacitors.  This is done rather simply, by writing a value to port address 201h.  The value that you choose to write to this address is irrelevant, as long as you are writing something.  Most programmer's write zero to this address, so we won't break with tradition; here is the code that will write a zero to this port.

     outp(0x201, 0);  /* Discharge the capacitors */

However, any value can be written, the hardware discharges all the caps when something is written to this port address, regardless of what value is actually being written.

Next we need to time how long it takes for the capacitors to charge back up.  This is done in software using a simple counting loop with a test on the bit value of the joystick pot of interest.  Say we want the pot value for the X-axis on joystick A.  The loop for computing that would look similar to the following:

     #define Bit0 0x01  /* Cap for X-Axis on Joystick A */
     #define Bit1 0x02  /* Cap for Y-Axis on Joystick A */
     #define Bit2 0x04  /* Cap for X-Axis on Joystick B */
     #define Bit3 0x08  /* Cap for Y-Axis on Joystick B */

     int potVal = 0;

     outp(0x201, 0);    /* Discharge the capacitors */

     /* Add one to potVal while the capacitor is recharging */
     while (inp(0x201) & Bit0)
         potVal++;

Once that loop finishes, the variable called potVal will be assigned the joystick pot value for the joystick pot of interest (denoted by the bit number used in the while loop).  One thing you may not have realized is that when you write to port 201h, all the capacitors will be discharged.  With that in mind, it may make more sense to check all the joystick pot values at once, at least that's my preferred method.  To see what I mean, check out the following code that illustrates how this can be done.

     /* JOYPOTS.C -- Samples joystick pots and retreives their values.
      * by Gary Neal, Jr. -- garyneal_71@yahoo.com
      */

     #include <conio.h>                       /* Console specific I/O */

     int main(void)
     {
         unsigned int joyState;               /* Bit status of Port 201h */
         unsigned short joyPotVal[4], limit;  /* Loop counters & counting limit */

         clrscr();                            /* Clear the screen */
         gotoxy(10, 9);                       /* Set screen position */
         cputs("Joystick potentiometer values:");

         /* Initialize loop counters */
         joyPotVal[0] = joyPotVal[1] = joyPotVal[2] = joyPotVal[3] = limit = 0;

         outp(0x201, 0);                      /* Discharge capacitors */

         do {
             joyState = inp(0x201);           /* Get capacitor status bits */

             /* Increase pot value count until cap fully charges */
             joyPotVal[0] += ((joyState & 0x01) != 0);
             joyPotVal[1] += ((joyState & 0x02) != 0);
             joyPotVal[2] += ((joyState & 0x04) != 0);
             joyPotVal[3] += ((joyState & 0x08) != 0);

             /* Increase loop limit count */
             limit += 1;

             /* Continue until all caps are fully charged or limit is reached */
         } while ((joyState & 0x0F) && limit);

         /* Write joystick pot values on screen */
         gotoxy(15, 11);
         cprintf("Joystick A's X-axis = %u", joyPotVal[0]);
         gotoxy(15, 12);
         cprintf("Joystick A's Y-axis = %u", joyPotVal[1]);
         gotoxy(15, 13);
         cprintf("Joystick B's X-axis = %u", joyPotVal[2]);
         gotoxy(15, 14);
         cprintf("Joystick B's Y-axis = %u", joyPotVal[3]);

         return 0;
     }

This is basically how joystick enabling software works.  If we examined the lines of code, we can see that after initializing our counters to zero, we discharge the capacitors.  After that, we then increment all the counters while the corresponding caps are charging.  When all the caps are fully charged (joyState & 0xF will equal zero) OR when the limit has been reached (when limit equals zero again, timing out the loop) the counter values are then displayed as joystick pot values.  It is that simple...

When limit equals zero again??!!
The code above initializes limit to zero and then begins incrementing it in the loop.  Conceptually, once you start counting up from zero, you will never see zero again.  However, short integers have a counting range of 0 to 65535.  When the maximum value of a short integer is reached, then the next count will cause it to roll back to zero.  That's how the code above times out the loop.

Bug alert
Some compilers may complain if your program tries to exceed the range of values in any given variable.  No C compiler that I've ever worked with does this, but some other language compilers might.  If you are translating the above code to QuickBASIC, you will certainly get an overflow error if you run it with no joysticks plugged in.

Previous Page | Main Page | Next page

Send your questions, comments, or ideas here.