Navigation

Skip Navigation Links
Home
Contact
ElectronicsExpand Electronics
SoftwareExpand Software

 Login

USB Joystick

Introduction

With the phasing out of game, serial and parallel ports from modern computers and the ever increasing popularity of USB, it makes sense that hobbyists start getting to grips working with USB. Unfortunately, USB is not a simple protocol and can be daunting to implement. Luckily, there are several solutions on the market that can make implementing a USB device much easier.

 

This project focuses on the use of a USB PIC and the mikroC compiler to convert an old game port joystick to utilize USB. One of the advantages of the mikroC compiler are the built USB HID libraries that make creating a USB HID device a doddle. When writing USB code using the mikroC compiler, the USB device produced is a generic HID device that can be used to transfer data to and from the PIC. However, it is possible to modify the USB descriptor generated by mikroC so that it produces a specific USB HID device, like a keyboard, mouse, joystick or graphics tablet.

Compiler

This project uses the mikroC v8 compiler. However, the methods used will most probably be relevant to other PIC language compilers that can generate HID code.

The Joystick

The joystick that I used for this project is an old IBM 76H1571, photographed below:

 

The 76H1571 is a 4-button joystick with a throttle and POV hat. What’s interesting to note is that you can’t use the throttle and POV hat at the same time – you can only use one or neither. The two slide switches on the front of the joystick are used to switch the throttle and POV hat on and off, so that you could choose which one you wanted to use.

 

Because there is no point for this restriction with USB, the converted joystick will be able to use both the throttle and POV hat at the same time. This then makes the two switches at the front redundant, so why not add those as two extra buttons?

 

In summary, the converted joystick will have the following specifications:

  • 2-axis joystick
  • Throttle
  • 4 direction POV hat
  • 6 buttons

HID Report Descriptor

When the compiler generates the USB HID code, it creates a descriptor that is sent to the USB host that tells it what type of USB device it is. A HID device descriptor is slightly different as it has an extra descriptor embedded in it that specifies the type of HID device and how it is used. It is this section that will be modified to change our device into a joystick

Creating the Descriptor

The USB IF website contains a useful tool that greatly facilitates the creation of HID report descriptors. It’s called the HID Descriptor Tool and can be downloaded for free from the HID Tools page. Once downloaded, extract the archive and run Dt.exe.

 

Using the tool, it is possible to create your own report descriptor for your joystick (or any other HID device), specifying the number of axis and buttons it has and any other features (rudder pedals, throttles etc). However, the tool also comes with some premade descriptors that you can immediately use or modify to suit your needs. They are found in the same folder as the executable and are distinguishable by their .hid extension. The premade joystick descriptor is called joystk.hid and this is the one I used. Once loaded, you’ll be presented with the following screen:

 

I’ve highlighted the important sections in red. From these sections you can deduce the following specs:

  • 1 throttle, described by an 8 bit value from -127 to 127
  • X and Y axes
  • 1 POV hat, taking 4 positions (0-3) and representing the angle 0-270, described by a 4-bit value
  • 4 buttons, with each button described by 1 bit

You’ll notice that the REPORT_SIZE  property defines the width of the data used to represent the parameter and the REPORT_COUNT property determines how many reports are sent to represent the parameter.

Modifying the Descriptor

Unfortunately, at the time of writing, I have not yet managed to modify the report descriptor and get it to work with my modifications (i.e. 6 buttons instead of the default 4) and so I have used the default joystick descriptor that comes with the HID Tool. If you know how, I’d be very interested if you drop me an email.

Adding the Descriptor to your Code

With your report descriptor created you need to export it into C code. To do that, click on File->Save As in the HID Descriptor Tool. In the Save dialog that appears, change the file type to Header File (*.h).

 

This will create a C header file that you can then add into your project. 

mikroC Intergration

Adding the header file into the descriptor generated by mikroC requires a little bit of work. If you have a look at the mikroC descriptor, you’ll notice that every byte is followed by ‘,0’ (ignoring the quotes, that’s comma zero). You will need to modify the report descriptor you generated to include this padding. After doing that you’ll end up with a descriptor that look something like this:

 

0x05, 0,

0x01, 0,                   // USAGE_PAGE (Generic Desktop)

0x15, 0,

0x00, 0,                   // LOGICAL_MINIMUM (0)

0x09, 0,

0x04, 0,                   // USAGE (Joystick)

0xa1, 0,

0x01, 0,                   // COLLECTION (Application)

0x05, 0,

0x02, 0,                   //   USAGE_PAGE (Simulation Controls)

0x09, 0,

0xbb, 0,                   //   USAGE (Throttle)

0x15, 0,

0x81, 0,                   //   LOGICAL_MINIMUM (-127)

0x25, 0,

0x7f, 0,                   //   LOGICAL_MAXIMUM (127)

0x75, 0,

0x08, 0,                   //   REPORT_SIZE (8)

0x95, 0,

0x01, 0,                   //   REPORT_COUNT (1)

0x81, 0,

0x02, 0,                   //   INPUT (Data,Var,Abs)

0x05, 0,

0x01, 0,                   //   USAGE_PAGE (Generic Desktop)

0x09, 0,

0x01, 0,                   //   USAGE (Pointer)

0xa1, 0,

0x00, 0,                   //   COLLECTION (Physical)

0x09, 0,

0x30, 0,

/////////////////////////////////////////

                           //     USAGE (X)

0x09, 0,

0x31, 0,                   //     USAGE (Y)

0x95, 0,

0x02, 0,                   //     REPORT_COUNT (2)

0x81, 0,

0x02, 0,                   //     INPUT (Data,Var,Abs)

0xc0, 0,                   //   END_COLLECTION

0x09, 0,

/////////////////////////////////////////////

0x39, 0,                   //   USAGE (Hat switch)

0x15, 0,

0x00, 0,                   //   LOGICAL_MINIMUM (0)

0x25, 0,

0x03, 0,                   //   LOGICAL_MAXIMUM (3)

0x35, 0,

0x00, 0,                   //   PHYSICAL_MINIMUM (0)

0x46, 0,

0x0e, 0,

0x01, 0,                   //   PHYSICAL_MAXIMUM (270)

0x65, 0,

0x14, 0,                   //   UNIT (Eng Rot:Angular Pos)

0x75, 0,

0x04, 0,                   //   REPORT_SIZE (4)

0x95, 0,

0x01, 0,                   //   REPORT_COUNT (1)

0x81, 0,

0x02, 0,                   //   INPUT (Data,Var,Abs)

0x05, 0,

/////////////////////////////////////

0x09, 0,                   //   USAGE_PAGE (Button)

0x19, 0,

0x01, 0,                   //   USAGE_MINIMUM (Button 1)

0x29, 0,

0x04, 0,                   //   USAGE_MAXIMUM (Button 4)

0x15, 0,

0x00, 0,                   //   LOGICAL_MINIMUM (0)

0x25, 0,

0x01, 0,

                           //   LOGICAL_MAXIMUM (1)

0x75, 0,

0x01, 0,                   //   REPORT_SIZE (1)

0x95, 0,

0x04, 0,                   //   REPORT_COUNT (4)

0x55, 0,

0x00, 0,                   //   UNIT_EXPONENT (0)

0x65, 0,

0x00, 0,                   //   UNIT (None)

0x81, 0,

0x02, 0,                   //   INPUT (Data,Var,Abs)

0xc0, 0                    // END_COLLECTION

 

Now that the descriptor has been padded, the next step is to delete the report descriptor generated by mikroC and replace it with your one. To do that, first create the mikroC descriptor using the mikroC HID tool, then open it up in the editor.

The actual descriptor data itself is stored completely within the DescTables array. The bottom 50 or so lines in the array are the report descriptor (lines 109-160). Delete those lines, and then paste in the new descriptor in its place. Now, the following modifications need to be made to the USBdsc.c file:

  • Modify line 23 to correspond to the unpadded size of the report descriptor (77 bytes in the case of the default joystick descriptor):
  • unsigned char const HID_ReportDesc_len = 77;
  • Delete the array bounds for the DescTables array on line 36:
  • unsigned char const DescTables[] = {

And that’s it. The descriptor should now be correctly mofied to behave as a USB joystick. The easiest way to test this out is to compile the code on to a PIC, plug it into a USB port on your PC and make sure that it is recognized correctly by the PC. Then, go to Control Panel and open up the Game Controllers options dialog. You should be able to see your joystick listed.

 

Sending Data to the PC

With the PIC recognized as a USB joystick, the hard part is over. Sending joystick data to the PC is a simple matter. When we created the descriptor earlier, we could work out most of the data format from looking at the descriptor. Some extra experimentation has determined the following specification:

 

Value

Value

Throttle

-127 (min) to 127 (max)

X axis

-127 to 127

Y axis

-127 to 127

POV Hat Up

0

POV Hat Right

1

POV Hat Down

2

POV Hat Left

3

POV Hat Neutral

4

Button 1

0-1

Button 2

0-1

Button 3

0-1

Button 4

0-1

 

The throttle, X and Y values are all 8-bit values. However, the POV and button values are 4-bits each and so they are packed into one byte. The data format is shown below:

 

Bit position

7

6

5

4

3

2

1

0

Throttle

X-axis

Y-axis

Button 4

Button 3

Button 2

Button 1

POV Hat

 

With the data format determined, it is a simple matter to write some firmware that interfaces with the some buttons and potentiometers and sends the data to the PC, to confirm that the firmware is working correctly. The behaviour of the PIC joystick can be examined using the Game Controllers options dialog in Control Panel.

The Hardware

With the USB firmware tested and running fine, it is time to move on to the actual joystick conversion. The first thing that needs to be done is to open up the joystick and remove the existing circuitry and the gameport cable:

 

Switch Setup

The next step is to work out how all the switches and potentiometers are wired together. The potentiometers for the joystick axes and throttle will probably be simple to spot and simple to wire as well as all they need is power, ground and a voltage out to the PIC. The switches might prove to be a little tricky, depending on the joystick. By tracing the tracks on the PCB in the stick, it has been determined that the switches are connected according to the following diagram:

 

 

The interesting one to note is the POV hat. Instead of being 4 separate switches, the POV hat is wired up as an analogue system, where the resistance across the green and orange wires determines which button was pressed. The following table shows the resistance of each switch:

Switch Position

Resistance (Ohms)

Neutral

80k

Up

200

Right

20k

Down