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
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
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!
Attachments
Step 3: Setting Up Eclipse and Installing Libraries
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.
Attachments
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.