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!
Attachments
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.
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.
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!
33 Comments
4 years ago
Did anybody figure out how to use potetionmeters with arduino uno to make a steering wheel or a joystick i really need it please help me
4 years ago
If this process is obsolete what is the best way to make an Arduino Mega 2560 into a 8 axis, 32 button joystick control card?
5 years ago
Looked for an arduino made controller, and abandonned an old project because i did not find a way to communicate with Java via serial ports. I came for copper, and i found gold.
Reply 5 years ago
Hey, thanks man. Glad it helped!
7 years ago
Also i wanted to ask do you make the lib folder and the native-wins folder ourselves in the workspace folder or are there already supposed to be somehwere because i couldnt find them in my workspace
7 years ago
java.lang.UnsatisfiedLinkError: no rxtxSerial in java.library.path thrown while loading gnu.io.RXTXCommDriver
Exception in thread "main" java.lang.UnsatisfiedLinkError: no rxtxSerial in java.library.path
at java.lang.ClassLoader.loadLibrary(Unknown Source)
at java.lang.Runtime.loadLibrary0(Unknown Source)
at java.lang.System.loadLibrary(Unknown Source)
at gnu.io.CommPortIdentifier.<clinit>(CommPortIdentifier.java:83)
at SerialTest.initialize(SerialTest.java:36)
at SerialTest.main(SerialTest.java:123)
Why do i get this error in eclipse when i compile your code ?
please help!!
7 years ago
why not give us your .exe so we don't need to progam it by our self
Reply 7 years ago
Mine's no longer setup like this and when I made it 3 years ago, it was heavily modified and tailored to my setup. It wouldn't have worked on anyone elses. It doesn't even open on my PC anymore.
Besides, this website isn't for free hand-outs, it's to learn how to do things yourself.
Here they are anyways: https://mega.nz/#F!60UHUTiZ!V35saAPgv-HucqbqE--uhw
I haven't touched those in several years and have no idea what they'll do, just a warning.
7 years ago
I did everything according to the steps and completed the setup. When I open notepad and press the switch, the letter s is printed. But when i open a game and then press the button, i get no response. What do I do?
Reply 7 years ago
It might be because the keysend is so fast that the game's internal debounce (typically 50ms to prevent multiple sends from a sensitive switch) is ignoring the key presses all together. Try playing around with the delays between a down press and an up press of the key.
I seem to recall this happening to me in Project CARS but not Assetto Corsa. It's been far too long since I used this setup to remember if this was the correct solution.
7 years ago
java.lang.UnsatisfiedLinkError: no rxtxSerial in java.library.path thrown while loading gnu.io.RXTXCommDriver
Exception in thread "main" java.lang.UnsatisfiedLinkError: no rxtxSerial in java.library.path
at java.lang.ClassLoader.loadLibrary(Unknown Source)
at java.lang.Runtime.loadLibrary0(Unknown Source)
at java.lang.System.loadLibrary(Unknown Source)
at gnu.io.CommPortIdentifier.<clinit>(CommPortIdentifier.java:123)
at SerialTest.initialize(SerialTest.java:42)
at SerialTest.main(SerialTest.java:164)
please help!!
8 years ago
Can you read analog signals and use them with this library ??
For example, to use a steering wheel attached to a potentiometer??
Reply 7 years ago
Did you figure it out? I am also looking for this
8 years ago on Step 2
Change buttonState and lastButtonState to the bool type. If my understanding is correct, it will take up less RAM space.
bool buttonState;
bool lastButtonState;
8 years ago on Introduction
Hi i´m try to make a game controler for a online pc game but i need use two joysticks could you help me?? i don't know anything in eclipse even install it.. can u help me on visual basic??
8 years ago on Introduction
first off thanks for a great diy. second off if any one is having problems after making the jar file into an exe and the console close to fast to read just take the rxtxParallel.dll, rxtxSerial.dll and take the exe and put them all into a fold and it so work. that is what i did
8 years ago on Step 3
I can't find the directory /lib/jars, or the /natives-win directory
Reply 8 years ago
Kevin:
This video explains how to add the archives to your class project
https://www.youtube.com/watch?v=43Vdpz1YmdU
8 years ago on Step 4
I am hoping you'll be willing to help me again, but I'm hardly sure what to ask. I followed what you gave me, and indeed all of the errors are gone. The code seems to run without problems. But, I'm not seeing any response from my button presses. My button presses are bringing up the expected text in the serial monitor window on my PC. I have confirmed that I am using the same string ("GO" and "JUMP") in both the Arduino code and the Java code. I see the Eclipse console showing a Port ID and the string "- Started -". But, nothing else happens. I see no "Engine Start Engaged" string in the console from my button, and no virtual keyboard presses seem to happen.
When I put in a println at the beginning of the method "public synchronized void serialEvent(SerialPortEvent oEvent)" as a test, it did not show up either, seeming to suggest that the whole method is being skipped altogether.
Can you suggest what my problem might be here?
Sincere thanks.
8 years ago on Step 4
I would very much appreciate some help. I pasted your code into Eclipse as-is and I got many, many errors (34 of them, in fact) right out of the gate.
Is it a version problem? I am working with JavaSE v1.8.
Or perhaps, is there any organization of the code (pasting it NOT as-is) that I should be doing, and about which I would know if I were not just a very, very early beginner with Java programming?
Thank you.