Recently we used an Xbox One Controller to send commands to an Arduino. Since a few people asked how this was done, I thought I’d write it up.
The code that was used on that particular project can be found here, for those that just wanted to jump straight into the source code.
It was a pretty simple setup. At a high level, the controller talks to a laptop (or any other computer) via Bluetooth and the laptop interprets this into commands to send over Serial to the Arduino.
So firstly we need to connect the controller to the computer. This is pretty straight forward on a Windows or OSX so I won’t go too much into it, basically just connect it via Bluetooth like you would for a mouse or a keyboard.
For those on Ubuntu or other Linux Distros, firstly connect it via Bluetooth and you may notice that the light on your controller is still blinking and if you keep an eye out on your Bluetooth settings, the controller keeps connecting and disconnecting. To fix this issue, firstly install sysfsutils
if you don’t already have it.
sysfsutils
/module/bluetooth/parameters/disable_ertm=1
to /etc/sysfs.conf
(remember to do this as an admin, e.g. use sudo)Now when you re-connect the controller, it should give you a solid light.
We would need to setup Serial on the Arduino. There are plenty of tutorials on how this works so please refer to those. For the purposes of this exercise we will use the code below. This code will receive a value for each colour and will write that value to the LEDs.
/*
Turns on the led using serial
*/
// Initialize the LEDs
int blue = 9;
int green = 10;
int red = 11;
// Initialize the integers to keep the values of the leds
int blue_value;
int red_value;
int green_value;
// the setup routine runs once when you press reset:
void setup() {
// initialize the pin as an output.
pinMode(blue, OUTPUT);
pinMode(red, OUTPUT);
pinMode(green, OUTPUT);
// Start serial
Serial.begin(9600);
}
void loop() {
// Wait until we receive the 0xFF Character
if (Serial.read() == 0xFF) {
// Wait until we have 3 bytes available
while (!(Serial.available() > 3)) Serial.println("wait");
blue_value = Serial.read();
red_value = Serial.read();
green_value = Serial.read();
// Echo back what we just received for debugging
char sendBuffer[3];
sprintf(sendBuffer, "%d%d%d", blue_value, red_value, green_value);
Serial.println(sendBuffer);
// Write the LED Values to show the colours
analogWrite(blue, blue_value);
analogWrite(green, green_value);
analogWrite(red, red_value);
}
}
As you can see, on line 27, we are waiting on an 0xFF
character before we even think about the rest of the items that is being sent via serial. And we then read the next 3 bytes after that value and set it as the LED values. So when we setup our Serial code on the laptop, we need to make sure that we are sending the same items.
Once the controller is connected, we would need to read those inputs so we can use it for our applications. To do so, we will use the awesome gamepad library. You can follow the documentation to receive inputs, this library makes it very straightforward to deal with the controller inputs.
To send the Serial output, we will need two packages serialport
and struct
.
Using struct
we define the data structure that we will be sending. As mentioned above, we will be sending an initial byte of 0xFF
and then our data, which in this case are all signed integers. So we define it as follows:
const data = Struct()
.word8Sle('start')
.word8Sle('blue')
.word8Sle('red')
.word8Sle('green');
data.allocate();
const proxy = data.fields;
// Define default values
proxy.start = 0xFF;
proxy.blue = 0;
proxy.red = 0;
proxy.green = 0;
Once we define the data structure, we can open the serial port and send the data we want to send.
const port = new SerialPort(
'/dev/ttyACM0',
{ baudRate: 9600 }
);
const buffer = data.buffer();
port.write(buffer, function (err) {
if (err) return console.log('Error on write: ', err.message);
console.log('Data sent successfully');
});
/dev/ttyACM0
would need to be replaced with the serial path you are usingAnd that’s it, combining the two, you can use the values you receive from the controller and pass that along through to the Arduino using Serial.
One thing to note, it is a good practice to only send the Serial data on a certain interval. This allows the application to be more predictable and also, depending on what your Arduino is doing, you may need to insure that there is enough time in each loop to perform the necessary tasks before receiving the next lot of Serial data. Here is an example of what you can do.
function callbackFromJoystick(button, value) {
if (button === 'blue') {
proxy.blue = value;
} else if (button === 'red') {
proxy.red = value;
} else if (button === 'green') {
proxy.green = value;
}
}
setInterval(function() {
const buffer = data.buffer();
port.write(buffer, ... );
}, 50);