Tuesday, February 15, 2011

Project: DIY Bluetooth Joystick

This fairly simple project was built out of my need to control a windows forms application remotely for an art project but this build could be used for any number of applications involving close dependable remote control.
Bluetooth Joystick
 Possible applications could include:
  • robotics
  • animatronics
  • remote software control
  • just for the heck of building something cool
  • etc.


What I needed:

I built this joystick with relatively few required parts consisting of:
  1. Joystick: I used an old analog Microsoft game port style joystick I bought at a junk store. As far as gaming technology is concerned the Microsoft game port has gone the way of the Dodo but for these kinds of purposes these things are really pretty cool. For those who don’t know the Microsoft game port was a DA15 type connector card in PCs circa 1995ish. The joysticks for this type of port were electronically ‘dumb’ consisting only of a couple of pots (one for each axis of control) and a switch for each button. The game port card took care of the analog to digital conversion and other bells and whistles. It is the simplicity of the joystick that lends itself so nicely to modification and repurposing.
  2. Arduino: In this case I used a Duemilanove.
  3. BlueSMiRF Bluetooth Modem
  4. PC Bluetooth dongle
  5. Project Box

Build Process:

1. Exploration:
The build started by opening up the joystick to see how it works. On the joystick model I used I simply removed the screws holding on the bottom and was able to see everything I needed to map the color coded wires to their individual control functions.

Joystick Bottom
In this view you can clearly see the 3 potentiometers that provide the sticks position feedback as position transducers. In this case 2 pots are for the x and y axis and a third is attached to a thumb-wheel. As per the standard for all game port style controllers the potentiometers resistance ranges from 0-100kΩ. It can be seen that all three control pots have a common wire attached to their wipers. Not easily seen in this photo you can make out 3 wires (blue, violet and black) that seem to disappear in the center of the photo. These three wires go to the two trigger buttons on the end of the joystick. By testing continuity with my multi-meter I was able to determine that the black wire is the common to the two buttons.
Now we have all the information we need to make a map of the function of each colored wire leaving the joystick:

Wire Color: Function:
red x,y and z axis common
black button 1 and 2 common
white z axis
orange y axis
gray x axis
blue button 1
violet button 2
2. Hook up the stick to the Arduino:
Because this joystick is destined to be connected via Blue Tooth I don’t really need or want a bunch of extra wire dangling around so I went ahead and cut the the connector plug end off of the joysticks connection cable leaving only enough cable remaining to reach to the Arduino’s permanent home in a small project box velcroed to the bottom of the joystick (I tried to fit the Arduino inside the stick body itself but the clearances where just to tight).
Using the above wiring map I connected the joystick to the Arduino according to the following diagram:

Joystick To Arduino
In this arrangement both the red and black common wires are attached to the +5v pin. White, orange and gray are connected to analog pins 0,1 and 2 respectively and also attached to the ground pin via a 100kΩ resistor (R1 – R3). The purpose of these 100kΩ resistors is to complete the second half of a voltage divider circuit the first half of which is the positional potentiometer in the joystick. I was clued in to the need for these resistors in this arrangement by an article on the very excellent blog Built to Spec. This article also shows how to linearize the values from the analog pins by converting the output voltage at the pin (in a range of 0-1023) to the actual resistance of the potentiometer in the joystick using Ohms Law with the formula: R = 102300/V-100. The blue and violet wires are connected to digital pins 3 and 4 with a 10kΩ pull down resistor for each (R4 and R5). This arrangement of connecting buttons to an Arduino with a pull down resistor is covered thoroughly on the Arduino web site.
3. Arduino Code
In my Arduino code I followed the example from the Arduino button debounce tutorial which adds a small time delay to make sure that buttons are actually being pressed rather than reading false positive button pushes as a result of noise.
//Declare some needed variables
long x, y, z;
int b1, b2;
//Button 1 on digital pin 3
const int b1_pin = 3;
//Button 2 on digital pin 4
const int b2_pin = 4; 
int b1_lastState = LOW;
int b2_lastState = LOW;
long b1_LDBT = 0;
long b2_LDBT = 0;
long b1_DBD = 10;
long b2_DBD = 10;
int x_in, y_in, z_in;

//Approximate number of refreshes per second
int refSec;

void setup() 
x = 0;
y = 0;
z = 0;

pinMode(b1_pin, INPUT);
pinMode(b2_pin, INPUT);

b1 = LOW;
b2 = LOW;
refSec = 25;

void loop () 

//Read All Joystick controls
x_in = analogRead(0);
y_in = analogRead(1);
z_in = analogRead(2);

//Set output values
x=102300/x_in - 100;
y=102300/y_in - 100;
z=102300/z_in - 100;

//Read and debounce buttons
//Button b1 with debounce
int b1_reading = digitalRead(b1_pin);
if(b1_reading != b1_lastState)
b1_LDBT = millis(); 
if((millis() -  b1_LDBT) > b1_DBD)
b1 = b1_reading;
b1_lastState = b1_reading;

//Button b2 with debounce
int b2_reading = digitalRead(b2_pin);
if(b2_reading != b2_lastState)
b2_LDBT = millis(); 
if((millis() -  b2_LDBT) > b2_DBD)
b2 = b2_reading;
b2_lastState = b2_reading;

//Serial Output
Serial.print(x, DEC);
Serial.print(y, DEC);
Serial.print(z, DEC);

At this point I was able to upload the code to the Arduino and view the serial output I was getting back from the joystick using the Arduino IDE’s serial monitor while connected to the USB cable. The output looks like this:
Serial Monitor
The output is a comma delimited string of values staring with ‘#JOY’. I added this to serve as a control check for the software that will be receiving this data. Before using the joystick data I can check that I have a 6 element string that starts with ‘#JOY’. If that is not the case I can just ignore the string and wait for a properly formatted string to arrive. After #JOY the values are as follows: ‘X-Axis Value’, ‘Y-Axis Value’, ‘Z-Axis Value’, ‘Button 1 Value’, ‘Button 2 Value’.
Now I only have one problem to deal with. The ranges of the values for the X, Y and Z axis are not very useful for my receiving application. To correct for this I assume that the output from the joystick potentiometers is at least approximately linear and use our good friend the ‘two point’ form of the linear equation. First, with the serial monitor open, I deflected the joystick fully through it’s range and created the following table of range values.

Axis Output Range:
X 0 - 139
Y 124 - 0
Z 129 - 0

The X axis values from far left to far right are 0-139 and I would like the output to be –100 through 100 from left to right for the X axis. Solving the two point form of the linear equation given points (A and B) in this situation follows:
This process is than repeated verbatim for the remaining Y and Z axis.
The follow section from the above code:
//Set output values
x = 102300/x_in - 100;
y = 102300/y_in - 100;
z = 102300/z_in - 100;
is now replaced with this new section of code that normalizes the ranges and check for out of range values:
//Set output values
x = (-243.89 * (x_in - 603.54))/x_in;
y = (261.29 * (y_in - 631.48))/y_in;
z = (255.04 * (z_in - 621.88))/z_in;

//Check and fix ranges
if(x < -100){x = -100;}
if(x > 100){x = 100;}
if(y < -100){y = -100;}
if(y > 100){y = 100;}
if(z < -100){z = -100;}
if(z > 100){z = 100;}
4. Adding in Bluetooth
Adding the Bluetooth module was probably the easiest part of this entire project. The SparkFun BlueSMiRF is an amazing little radio that is extremely simple to include into your project. I only needed to hook up 4 wires to the radio and I was off and running. 1. Ground is attached to the Arduino ground, 2. Power is attached to Arduino 5v, 3. TX is attached to RX on Arduino (pin 0) and 4. RX is attached to TX on Arduino (pin 1). Note: watch out for those last two. Most of the issues I hear people having with the BlueSMiRF on online forums is hooking the TX and RX in reverse. It makes sense that you would naturally want to hook up TX to TX and RX to RX. After all you hook up ground to ground and power to power right? In this case TX needs to go to RX because there is a communication going one between the two devices whereas each is receiving on RX what the other is sending on TX. Make sense?
Another word of caution: Use an easily removable connector to the BlueSMiRF in your final build. The Arduino’s RX and TX pins are also the same lines used to program the Atmega on the Arduino so you MUST remove the radio if you intend to upload new code to the finished joystick.
Here are a couple of photos of the interior of my final build. Notice that I placed all of my needed resistors onto a little proto board that is attached to the base of the project box with 2 sided tape. The Arduino is attached to the top of the box and the BlueSMiRF is held into the box by the friction of the attached antenna. All of the elements are attached via removable connectors so that I can easily swap components from one project to another. The joystick is great but I am too cheap to permanently dedicate an Arduino or a BlueSMiRF to a single project. I can always re-upload the right code and pop it back into the joystick next time I need it.


The only remaining thing to do is to start communicating with another device. I bought a cheapo USB Bluetooth dongle for my PC. The driver for the dongle I got (and I’m sure may others work the same) allows you to setup the dongle as a virtual COM port. Once installed you may look up what COM port was installed for the dongle in device manager and use this COM port for receiving data from the joystick in software. To connect to the Bluetooth dongle in my windows code I only needed to turn on the joystick power shortly after opening a serial port based on the virtual COM port number and the dongle and BlueSMiRF took care of the rest.