[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

joystick tutorial and code



here's a quick tutorial on how to code joystick support on linux 2.0.x, linux
2.2.x, and freebsd (I think). Also two little code snippets incase they belong
in the code sup^H^H^Hrepository

I tried to use the template, but I couldn't get my netscape to view it right,
so I had no way of checking. Sorry

        -Erik <br0ke@math.smsu.edu> [http://shells.clipboard.com/~br0ke]

The opinions expressed by me are not necessarily opinions. In all
probability, they are random rambling, and to be ignored. Failure to ignore
may result in severe boredom or confusion. Shake well before opening. Keep
Refrigerated.
        
/* this is the linux 2.2.x way of handling joysticks. It allows an arbitrary
 * number of axis and buttons. It's event driven, and has full signed int
 * ranges of the axis (-32768 to 32767). It also lets you pull the joysticks
 * name. The only place this works of that I know of is in the linux 1.x 
 * joystick driver, which is included in the linux 2.2.x kernels
 */

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/joystick.h>

#define JOY_DEV "/dev/js0"

int main()
{
	int joy_fd, *axis=NULL, num_of_axis=0, num_of_buttons=0, x;
	char *button=NULL, name_of_joystick[80];
	struct js_event js;

	if( ( joy_fd = open( JOY_DEV , O_RDONLY)) == -1 )
	{
		printf( "Couldn't open joystick\n" );
		return -1;
	}

	ioctl( joy_fd, JSIOCGAXES, &num_of_axis );
	ioctl( joy_fd, JSIOCGBUTTONS, &num_of_buttons );
	ioctl( joy_fd, JSIOCGNAME(80), &name_of_joystick );

	axis = (int *) calloc( num_of_axis, sizeof( int ) );
	button = (char *) calloc( num_of_buttons, sizeof( char ) );

	printf("Joystick detected: %s\n\t%d axis\n\t%d buttons\n\n"
		, name_of_joystick
		, num_of_axis
		, num_of_buttons );

	fcntl( joy_fd, F_SETFL, O_NONBLOCK );	/* use non-blocking mode */

	while( 1 ) 	/* infinite loop */
	{

			/* read the joystick state */
		read(joy_fd, &js, sizeof(struct js_event));
		
			/* see what to do with the event */
		switch (js.type & ~JS_EVENT_INIT)
		{
			case JS_EVENT_AXIS:
				axis   [ js.number ] = js.value;
				break;
			case JS_EVENT_BUTTON:
				button [ js.number ] = js.value;
				break;
		}

			/* print the results */
		printf( "X: %6d  Y: %6d  ", axis[0], axis[1] );
		
		if( num_of_axis > 2 )
			printf("Z: %6d  ", axis[2] );
			
		if( num_of_axis > 3 )
			printf("R: %6d  ", axis[3] );
			
		for( x=0 ; x<num_of_buttons ; ++x )
			printf("B%d: %d  ", x, button[x] );

		printf("  \r");
		fflush(stdout);
	}

	close( joy_fd );	/* too bad we never get here */
	return 0;
}
	
/* This is the old way of reading joysticks, it works with linux 2.0.x and
 * suppoedly with freebsd. It also works with linux 2.2.x, but there's a better
 * way now. This method is limited to reading 2 axis and 2 buttons, and the axis
 * are from 0 to 255. Note that the buttons are read using a triary command,
 * which is basically a shorthand way of doing if/else. This is just so both
 * buttons show '1' if pressed. If you don't like that, change the lines to
 * something like     ,js.buttons & 2     and recompile. That'll give a '1'
 * in the B1 field if the trigger is pressed, and a '2' in the B2 field if the
 * thumb button is pressed. This driver does not do 3rd axis, 4th axis, 3rd
 * button, or 4th button. I don't know if it does the hat, possable in the
 * js.buttons field? *shrug* :)
 */

#include <stdio.h>
#include <fcntl.h>

#ifdef linux
#include <linux/joystick.h>
#define JOY_DEV "/dev/js0"
#endif

#ifdef __FREE_BSD_
#include <machine/joystick.h>
#define JOY_DEV "/dev/joy0"
#endif

int main()
{
	int joy_fd;
	struct JS_DATA_TYPE js;

		/* open the joystick */
	if( ( joy_fd = open( JOY_DEV, O_RDONLY ) ) < 0 )
	{
		printf( "Couldn't open joystick device %s\n", JOY_DEV );
		return -1;
	}

	printf( "Here we go, hit ^c to quit\n\n" );

	while( 1 )	/* infinite loop */
	{
			/* read the joystick state into js */
		if( read( joy_fd, &js, JS_RETURN ) != JS_RETURN )
		{
			printf( "\nFailed read\n" );
		}

			/* print the results */
		printf("X: % 4d  Y: % 4d  B1: %1d  B2: %1d  \r"
				,js.x	/* X axis */
				,js.y	/* Y axis */
				,(js.buttons & 1) ? 1 : 0	/* button 1 */
				,(js.buttons & 2) ? 1 : 0 );	/* button 2 */
	}

	close(joy_fd);	/* too bad we never get here */
	return 0;
}
Joysticks; Implementation and Portability

Whether you're racing you're racing down a track in a high speed hovercraft, bea
ring down on your enemy in a dogfight, or gently re-aligning a pod to dock with 
a space station, you want a joystick in  your hand. Joysticks have been a part o
f computer games since the beginning, with the first arcade games. The modern jo
ystick can be an intimidating peice of hardware to code for. Fortunantly, it's v
ery easy on linux. My goal here is to give you the ability to implement joystick
s into your videogames in an effecient and relatively portable manner. My emphas
is will be on Linux (2.0.x and 2.2.x), and FreeBSD (I think).

Linux 2.0.x and FreeBSD handle the joystick in the same manner. They both create
 a file descriptor (fd from here on out) and the kernel queries the joystick and
 produces the state when that fd is read. It's a blocking operation, but it's ex
tremely effecient and causes nominal overhead. The name of the device file and t
he path to the header file differ between Linux 2.0.x and FreeBSD, but they are 
identicle otherwise. In Linux, the device is /dev/js0 and the header is /usr/inc
lude/linux/joystick.h. FreeBSD uses /dev/joy0 for the device, and puts the heade
r in /usr/include/machine/joystick.h. The struct contains 3 variables. The axis 
are represented by the 'x' and 'y' elements. The buttons are packed into 'button
s' bitwise, so you need to AND to get the specific buttons state. Note that butt
on 2 will return a 2 if it's pressed, so we might want to test for that and set 
it to 1. This method works for FreeBSD and Linux, but it only allows 2 axis and 
2 buttons. The axes are scaled from 0 to 255. The following implementation works
 for Linux 2.0.x, 2.2.x, and SHOULD work for FreeBSD.

/* This is the old way of reading joysticks, it works with linux 2.0.x and
 * suppoedly with freebsd. It also works with linux 2.2.x, but there's a better
 * way now. This method is limited to reading 2 axis and 2 buttons, and the axis
 * are from 0 to 255. Note that the buttons are read using a triary command,
 * which is basically a shorthand way of doing if/else. This is just so both
 * buttons show '1' if pressed. If you don't like that, change the lines to
 * something like     ,js.buttons & 2     and recompile. That'll give a '1'
 * in the B1 field if the trigger is pressed, and a '2' in the B2 field if the
 * thumb button is pressed. This driver does not do 3rd axis, 4th axis, 3rd
 * button, or 4th button. I don't know if it does the hat, possable in the
 * js.buttons field? *shrug* :)
 */

#include <stdio.h>
#include <fcntl.h>

#ifdef linux
#include <linux/joystick.h>
#define JOY_DEV "/dev/js0"
#endif

#ifdef __FREE_BSD_
#include <machine/joystick.h>
#define JOY_DEV "/dev/joy0"
#endif

int main()
{
        int joy_fd;
        struct JS_DATA_TYPE js;

                /* open the joystick */
        if( ( joy_fd = open( JOY_DEV, O_RDONLY ) ) < 0 )
        {
                printf( "Couldn't open joystick device %s\n", JOY_DEV );
                return -1;
        }

        printf( "Here we go, hit ^c to quit\n\n" );

        while( 1 )      /* infinite loop */
        {
                        /* read the joystick state into js */
                if( read( joy_fd, &js, JS_RETURN ) != JS_RETURN )
                {
                        printf( "\nFailed read\n" );
                }

                        /* print the results */
                printf("X: % 4d  Y: % 4d  B1: %1d  B2: %1d  \r"
                                ,js.x   /* X axis */
                                ,js.y   /* Y axis */
                                ,(js.buttons & 1) ? 1 : 0       /* button 1 */
                                ,(js.buttons & 2) ? 1 : 0 );    /* button 2 */
        }

        close(joy_fd);  /* too bad we never get here */
        return 0;
}

In the linux 2.2.x kernel, there's a new method for handling joysticks. A file d
escriptor to /dev/js0 is still opened, but instead of reading a struct, it reads
 signals. An arbitrary number of axes and buttons can be addressed, and ioctl si
gnals let us get the number of axes and buttons, as well as the name of the joys
tick. The axes are scaled between -32768 and 32767 allowing much more control if
 your hardware is accurate enough. The signals are returned in the js_event stru
ct, containing 'number' and 'value.' 

If you're wondering how to get x/y/z/r values from *axis, here's the translation
s
X axis = axis[0]
Y axis = axis[1]
Z axis = axis[2]
R axis = axis[3]


And here is a quick and dirty implementation of the Linux 2.2.x style joystick h
andling

/* this is the linux 2.2.x way of handling joysticks. It allows an arbitrary
 * number of axis and buttons. It's event driven, and has full signed int
 * ranges of the axis (-32768 to 32767). It also lets you pull the joysticks
 * name. The only place this works of that I know of is in the linux 1.x 
 * joystick driver, which is included in the linux 2.2.x kernels
 */

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/joystick.h>

#define JOY_DEV "/dev/js0"

int main()
{
        int joy_fd, *axis=NULL, num_of_axis=0, num_of_buttons=0, x;
        char *button=NULL, name_of_joystick[80];
        struct js_event js;

        if( ( joy_fd = open( JOY_DEV , O_RDONLY)) == -1 )
        {
                printf( "Couldn't open joystick\n" );
                return -1;
        }

        ioctl( joy_fd, JSIOCGAXES, &num_of_axis );
        ioctl( joy_fd, JSIOCGBUTTONS, &num_of_buttons );
        ioctl( joy_fd, JSIOCGNAME(80), &name_of_joystick );

        axis = (int *) calloc( num_of_axis, sizeof( int ) );
        button = (char *) calloc( num_of_buttons, sizeof( char ) );

        printf("Joystick detected: %s\n\t%d axis\n\t%d buttons\n\n"
                , name_of_joystick
                , num_of_axis
                , num_of_buttons );

        fcntl( joy_fd, F_SETFL, O_NONBLOCK );   /* use non-blocking mode */

        while( 1 )      /* infinite loop */
        {

                        /* read the joystick state */
                read(joy_fd, &js, sizeof(struct js_event));
                
                        /* see what to do with the event */
                switch (js.type & ~JS_EVENT_INIT)
                {
                        case JS_EVENT_AXIS:
                                axis   [ js.number ] = js.value;
                                break;
                        case JS_EVENT_BUTTON:
                                button [ js.number ] = js.value;
                                break;
                }

                        /* print the results */
                printf( "X: %6d  Y: %6d  ", axis[0], axis[1] );
                
                if( num_of_axis > 2 )
                        printf("Z: %6d  ", axis[2] );
                        
                if( num_of_axis > 3 )
                        printf("R: %6d  ", axis[3] );
                        
                for( x=0 ; x<num_of_buttons ; ++x )
                        printf("B%d: %d  ", x, button[x] );

                printf("  \r");
                fflush(stdout);
        }

        close( joy_fd );        /* too bad we never get here */
        return 0;
}
        
That is all there is to implementation. We've already explored portability betwe
en Linux 2.0.x and FreeBSD, now lets look at portability issues with Linux 2.2.x
 and other os's. The easiest solution is to make several .c files, each with the
ir own joystick target, as Xracer does. An uglier solution is to use #ifdef like
 I did in the Linux 2.0.x/FreeBSD routine. Recently, I added support for Linux 2
.0.x, FreeBSD, and Linux 2.2.x in addition to the existing Win32 support in a ga
me called Orbit. My biggest stumbling block was getting the 2 linux methods and 
the freebsd method to work together without a lot of code duplication. This incr
edibly ugly precompiler abuse was what I came up with:

#ifdef __linux__
 #include <linux/joystick.h>
 #if JS_VERSION >= 0x010000     /* >= JS_VERSION 1.0.0 */
  #define NEW_LINUX_joy         /* linux 2.1, 2.2, and 2.3 use new api */
 #else
  #define BSD_joy               /* linux 2.0 uses bsd-ish api */
 #endif 
 #define JOYDEV "/dev/js0"      /* but both use this device */

#elif __FREE_BSD_
 #define BSD_joy
 #define JOY_DEV "/dev/joy0"    /* bsd uses this other device */
 #include "machine/joystick.h"  /* and puts machine specific headers here */
#endif

Later in the source, I have #ifdefs for various targets, includeing NEW_LINUX_jo
y, BSD_joy, and WIN32 (the original code). Aside from what this precompiler bloc
k sets up, it should be pretty standard to set up alternative OS's, just #ifdef 
and/or #elif the section in and make sure the api is the same and they return va
lues in the same range. I personally like to use floats and scale the axis betwe
en -1.0 and 1.0 and the buttons either 1 or 0.

Both programs are available at http://shells.clipboard.com/~br0ke/gamedev/joysti
ck/ as plain C files and as syntax highlighted html files.