Introduction: Using an Arduino Uno R3 As a Game Controller

2016 UPDATE: I will no longer provide support in the comments section. This is an outdated and obsolete method that has very little reason to be used anymore. Only keeping it up for information purposes.

Naturally, the Uno does not natively support keyboard strokes, unlike it's Leonardo brother. Most of Google will tell you you need to do some firmware workarounds and ATMEGA reprogramming just to get it working and the entire ordeal is a mess. If you're like me, you've found that out just after buying one. Here is how to get around all that using a bit of Java coding. The ideal solution would be to purchase an arcade or flight sim joystick button board or an Arduino Leonardo for a few bucks from China (DX.com).

You will need:
Arduino Uno
Any sort of button or switch (or multiple switches, but start off simple)
10kΩ resistor(s) (one for each button)
Eclipse (the front end for Java programming - instructions later on for install)
Arduino Software (the front end for Arduino programming)
Breadboard
Jumper wires
Patience
Any video game that you may want physical switches for (racing/flight sim)

Step 1: Installing & Testing the Software

First of, you will need Eclipse (http://www.eclipse.org/downloads/). For this tutorial, I will be using Eclipse Classic 4.2.2 - 64 Bit. If you're running into problems during the install, refer to either Google or the FAQ. Now that Eclipse is installed, you will also need the Arduino program (http://arduino.cc/en/main/software). I'll be using the stable 1.0.5 version.

You can skip this step if you know how to upload sketches to the Arduino. Now that you have all the software installed, we're going to go ahead and setup Arduino first. In the Arduino software, click tools and make sure the board and port number make sense according to Device Manager. To test the board, go File>Examples>Basic>Blink then hit the arrow in the top left that says upload. If all goes right, the SMD on pin 13 should be blinking. Refer to THIS if you're having difficulties.

Step 2: Wiring Up a Basic Switch & Programming

In this step, you will be hooking up a basic switch to pin 5. Why pin 5? I have no idea but here's the wiring diagram (Made using Fritzing). There's two diagrams, one for (1) 2 pin switch and one for (4) 4 pin switches.

Now that you have a switch hooked onto pin 5, we begin programming in Arduino. The code I used is attached and below:

// *********************************************
// this constant won't change:
const int  buttonPin = 5; // the pin that the pushbutton is attached to

// Variables will change:
int buttonState = 0;         // current state of the button
int lastButtonState = 0;     // previous state of the button

void setup() {
  // Initialize the button pin as an input:
  pinMode(buttonPin, INPUT);
  // Initialize serial communication:
  Serial.begin(9600);
}

void loop() {
  // Read the pushbutton input pin:
  buttonState = digitalRead(buttonPin);

    if (buttonState == HIGH) {
      // If the current state is HIGH then the button
      // Send to serial that the engine has started:   
      Serial.println("Start Engine");
      delay (100);
  }
  // Save the current state as the last state,
  // for next time through the loop
  lastButtonState = buttonState;
}
// *********************************************

When uploaded to the Arduino, you can open up the serial monitor (Tools>Serial Monitor) and press the button. It should display "Start Engine" as long as you're pressing the button. You may fiddle with the delay later on to suit your liking but please note this may cause issues in-game. You are now sending a serial string through tactile feedback. This is great!


Step 3: Setting Up Eclipse and Installing Libraries

Now we need to do something with that serial string. A program needs to pick it up and translate it into keystrokes. This is where Eclipse comes in and things get messy. Off the bat, you will need to download and install the RXTXcomm library (http://rxtx.qbang.org/wiki/index.php/Download) into Eclipse. I used the rxtx 2.1-7r2 (stable) version. Place RXTXcomm.jar into Eclipse's [Your workspace]/lib/jars folder as well as placing the rxtxParallel.dll and rxtxSerial.dll files into [Your workspace]/natives-win. This is where most people will go wrong if they do so feel free to ask if you have questions.

Now that you've installed the RXTXcomm library, we can begin coding in Eclipse. Open it up and go File>New>Project...>Java Project. For the sake of simplicity and ease, name this project SerialTest. While this may not be the proper method, there's no reason for it not to work this way. Click finish. Under the package explorer sidebar, right click on the SerialTest folder and go New>Class we will also name this SerialTest. Make sure public static void main(string[] args) is checked on. This is a Java program in its simplest, however, we still need to import those libraries from earlier. Right click on the same project folder and go Build Path>Configure Build Path. Under the Libraries tab, click Add External JARs and navigate to where you put the RXTXcomm.jar ([Your workspace]/lib/jars) and open it. Click OK. You are now ready to begin programming Java using the serial communicating library.

If you've made it past this part, congratulations. You didn't spend hours trying to figure it out like I did.

Step 4: Programming in Java to Interpret Incoming Serial & Testing

Now, bare with me because I know nobody likes working with other people's code. It just sucks. You may have to modify the COM channel, parity, stop bits and baud (data) rate settings in the code (all of which can be determined through Device Manager but should be the same as the code). The code below works with Linux, Mac OS X and Windows. My Arduino is on the default COM1 at 9600b/s. I accidentally left some experimental code in there while making a GUI but they won't affect anything so try to ignore understanding some of the library imports. The SerialTest folder is also attached for reference.

// *********************************************

import java.awt.AWTException;
import java.awt.Robot;
import java.awt.event.KeyEvent;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStream;
import gnu.io.CommPortIdentifier;
import gnu.io.SerialPort;
import gnu.io.SerialPortEvent;
import gnu.io.SerialPortEventListener;
import java.util.Enumeration;
import javax.swing.*;
import javax.swing.event.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.JFrame;
import java.awt.Color;

public class SerialTest implements SerialPortEventListener {
SerialPort serialPort;
/** The port we're normally going to use. */
private static final String PORT_NAMES[] = { "/dev/tty.usbserial-A9007UX1", // Mac OS X
"/dev/ttyUSB0", // Linux
"COM1", // Windows
};
/**
* A BufferedReader which will be fed by a InputStreamReader converting the
* bytes into characters making the displayed results codepage independent
*/
private BufferedReader input;
/** The output stream to the port */
private OutputStream output;
/** Milliseconds to block while waiting for port open */
private static final int TIME_OUT = 2000;
/** Default bits per second for COM port. */
private static final int DATA_RATE = 9600;

public void initialize() {
CommPortIdentifier portId = null;
Enumeration portEnum = CommPortIdentifier.getPortIdentifiers();

// First, Find an instance of serial port as set in PORT_NAMES.
while (portEnum.hasMoreElements()) {
CommPortIdentifier currPortId = (CommPortIdentifier) portEnum
.nextElement();
for (String portName : PORT_NAMES) {
if (currPortId.getName().equals(portName)) {
portId = currPortId;
break;
}
}
}
System.out.println("Port ID: ");
System.out.println(portId);
System.out.println("");
if (portId == null) {
System.out.println("Could not find COM port.");
return;
}

try {
// open serial port, and use class name for the appName.
serialPort = (SerialPort) portId.open(this.getClass().getName(),
TIME_OUT);

// set port parameters
serialPort.setSerialPortParams(DATA_RATE, SerialPort.DATABITS_8,
SerialPort.STOPBITS_1, SerialPort.PARITY_NONE);

// open the streams
input = new BufferedReader(new InputStreamReader(
serialPort.getInputStream()));
output = serialPort.getOutputStream();

// add event listeners
serialPort.addEventListener(this);
serialPort.notifyOnDataAvailable(true);
} catch (Exception e) {
System.err.println(e.toString());
}
}

/**
* This should be called when you stop using the port. This will prevent
* port locking on platforms like Linux.
*/
public synchronized void close() {
if (serialPort != null) {
serialPort.removeEventListener();
serialPort.close();
}
}

/**
* Handle an event on the serial port. Read the data and print it.
*/
public synchronized void serialEvent(SerialPortEvent oEvent) {
if (oEvent.getEventType() == SerialPortEvent.DATA_AVAILABLE) {
try {
String inputLine = input.readLine();
// ENGINE START
if (inputLine.equals("Start Engine")) {
System.out.println("Engine Start Engaged");
try {
Robot robot = new Robot();
robot.keyPress(KeyEvent.VK_S);
robot.delay(500);
robot.keyRelease(KeyEvent.VK_S);
} catch (AWTException e) {
e.printStackTrace();
}
}
// WINDSHIELD WIPERS
if (inputLine.equals("Windshield Wipers")) {
System.out.println("Windshield Wipers Engaged");
try {
Robot robot = new Robot();
robot.keyPress(KeyEvent.VK_W);
robot.delay(500);
robot.keyRelease(KeyEvent.VK_W);
} catch (AWTException e) {
e.printStackTrace();
}
}
// PIT SPEED LIMITER
if (inputLine.equals("Pit Speed Limiter")) {
System.out.println("Pit Limiter Engaged");
try {
Robot robot = new Robot();
robot.keyPress(KeyEvent.VK_P);
robot.delay(500);
robot.keyRelease(KeyEvent.VK_P);
} catch (AWTException e) {
e.printStackTrace();
}
}
// HEADLIGHTS
if (inputLine.equals("Headlights")) {
System.out.println("Headlights Engaged");
try {
Robot robot = new Robot();
robot.keyPress(KeyEvent.VK_H);
robot.delay(500);
robot.keyRelease(KeyEvent.VK_H);
} catch (AWTException e) {
e.printStackTrace();
}
}
} catch (Exception e) {
System.err.println(e.toString());
}
}
if (oEvent.getEventType() == SerialPortEvent.DATA_AVAILABLE) {

}
// Ignore all the other eventTypes, but you should consider the other
// ones.
}

public static void main(String[] args) throws Exception {
SerialTest main = new SerialTest();
main.initialize();
Thread t = new Thread() {
public void run() {
// the following line will keep this app alive for 1000 seconds,
// waiting for events to occur and responding to them (printing
// incoming messages to console).
try {
Thread.sleep(1000000);
} catch (InterruptedException ie) {
}
}
};
t.start();
System.out.println("- Started -");
System.out.println("");
}
}

// *********************************************

As you can see, there are 3 other buttons hooked up. Whenever Java sees the serial input of "Start Engine" , it will send out a keystroke of S. The output console will throw IO.Exceptions at you but don't fear, it's fixable by commenting out both System.err.println(e.toString()); lines. These errors do not interfere with anything so they're nothing to worry about to begin with. To change what each switch does, simply change the variables that are getting written to serial in Arduino and change the respective conditional statements in Java for when it receives those serial strings. To see the list of available commands for robot in Eclipse, type robot. and a little box will pop up showing various functions. Robot is amazing, you can even assign it to move the mouse based on Arduino input.

Step 5: Compile .jar

You now need to compile and convert your exported .jar into something useful (like a .exe that opens to look like the command prompt). I used the free version of http://www.jar2exe.com/ but I'm sure there are much better alternatives out there. This process is pretty straight forward but varies on each program and will allow you to open the program outside of Eclipse/Java. If you're on a Mac, you could try http://sourceforge.net/projects/jarbundler/ . I can't confirm or deny this even works though.

While in Eclipse:

Right click your project and click "Refresh". Alternatively you can right click and hit F5 on your keyboard. This is to ensure all your code is up to date and won't conflict when trying to export.

Right click your project and click "Export".

Expand the "Java" folder and double click the "Runnable JAR file" option.

Configure the specifications of the JAR file. The first thing you should do is select the main class (class with the main method) from the drop down menu under "Launch configuration".Secondly, select the "Export destination" via the "Browse..." button or by manually typing the location. Lastly, ensure the "Extract required libraries into generated JAR" radio button is selected. Do not worry about the rest of the menu. Click "Finish" when you are satisfied with your selections.

While in Jar2Exe:

- You'll have a 30 day trial (which resets if you reinstall it). Ignore the first screen, click next

- Click browse JAR and locate your recently exported JAR file. the two options for JRE version can be ignored. Next.

- You want the 'Console Application' option selected. Next.

- It should automatically select the main class for you (so continue on) but if you're having issues, try selecting others.

- I'd only really suggest considering the system tray option on this page, the rest aren't needed to be on.

- Again, nothing to see on this page. Next.

- Browse to where you'd like to save the .exe program, everything else can be ignored for now as it's just secondary preference.

- After it finishes creating the .exe, you can browse to where you saved it and give it a test run.

Step 6: Assigning the Hotkeys In-game

I built this setup to integrate into my steering wheel rig so that I can control multiple in-game features with physical toggle switches on a bank. Arduinos will allow you to display any sort of telemetry to an LCD screen or seven segment (providing the game allows you). I'm using Project CARS, a game still in its Pre-Alpha stage and is coming along very nicely. Simply map the keys that Java is pressing to functions in the game. For example, I have S for Start Engine and W for Wipers.

Step 7: What Now?

Well, you can start hooking up more switches to different ports. I'll be getting the handbrake from a car and installing a button that's activated when the lever is pulled. You could swap out the pushbutton start for a real car ignition and key. Heck, you could even have a seatbelt that won't allow you to drive until you've buckled in. Backup all code before experimenting and have fun!

Toy Contest

Participated in the
Toy Contest

Kit Contest

Participated in the
Kit Contest

Arduino Contest

Participated in the
Arduino Contest