Saturday, March 28, 2015

Add USB Game Controller to Arduino Leonardo or Micro

IMPORTANT NOTE: This article is for Arduino IDE version 1.6.5 (or below). To add a USB Game Controller to an Arduino Leonardo or Micro using Arduino IDE version 1.6.6 (or above) see the Arduino Joystick Library post.

Introduction

ArduinoLeonardoFrontOut of the box the Arduino Leonardo and the Arduino Micro appear to the host computer as a generic keyboard and mouse. This article discusses how the Arduino Leonardo and the Arduino Micro can also appear as a generic Game Controller. This project will only work with Arduino products based on the ATmega32u4 microcontroller (i.e. the Arduino Leonardo and the Arduino Micro). It will not work with the Arduino Uno, because it is based on the ATmega328 microcontroller.

ArduinoMicroFrontThe Arduino generic Game Controller provides the following:

  • X, Y, and Z axis
  • 32 buttons
  • X, Y, and Z axis rotation
  • Rudder
  • Throttle
  • 2 Point of View Hat Switches

Updating the Arduino Code

First make a backup copy of the following two files in the “%PROGRAMFILES%\Arduino\hardware\arduino\avr\cores\arduino” folder:

  • USBAPI.h
  • HID.cpp

Replace these two files with the following to add a generic Game Controller to the Arduino Leonardo and the Arduino Micro:

Running the Test Sketch

Compile and upload the UsbJoystickTest.ino sketch file onto the Arduino Leonardo or the Arduino Micro using the Arduino Software (IDE). I have tested this using version 1.6.1 through 1.6.5 of the software.

NOTE (added 11/19/2015): Due to a change in how the USB functionality is implemented in version 1.6.6 and above, please see Arduino Joystick Library if you are using Arduino IDE version 1.6.6 and above.

The following steps are for Windows 7. If you have a different version of Windows or a different operating system, these steps may differ.

Go to the Windows Control Panel and select “Hardware and Sound”.

Control Panel

Then select “Devices and Printers”.

Hardware and Sound
 
The Arduino Micro or Arduino Leonardo should appear in the list of devices.

Devices
 
Next right mouse click on the Arduino Leonardo or Arduino Micro to display the settings menu.

Drop Down Settings
 
Select “Game controller settings” to get to the “Game Controllers” dialog.

Game Controllers
 
The Arduino Micro or Arduino Leonardo should appear in the list of installed game controllers. Select the Arduino Micro or Arduino Leonardo and click the Properties button to display the game controller test dialog.

Arduino Game Controller Settings

While this dialog has focus, ground pin A0 on the Arduino to activate the test script. The test script will test the game controller functionality in the following order:

  • 32 buttons
  • throttle and rudder
  • X and Y Axis
  • Z Axis
  • 2 Hat Switches
  • X and Y Axis Rotation

Joystick Library

Now that the Arduino Leonardo or Arduino Micro has the Joystick library, the Arduino can be used for custom game controller projects. The following describes the Joystick library that is included in the updated USBAPI.h and HID.cpp files.

Joystick.begin(bool initAutoSendState)

Starts emulating a game controller connected to a computer. By default all methods update the game controller state immediately. If initAutoSendState is set to false, the Joystick.sendState method must be called to update the game controller state.

Joystick.end()

Stops the game controller emulation to a connected computer.

Joystick.setXAxis(byte value)

Sets the X axis value. Range -127 to 127 (0 is center).

Joystick.setYAxis(byte value)

Sets the Y axis value. Range -127 to 127 (0 is center).

Joystick.setZAxis(byte value)

Sets the Z axis value. Range -127 to 127 (0 is center).

Joystick.setXAxisRotation(int value)

Sets the X axis rotation value. Range 0° to 360°.

Joystick.setyAxisRotation(int value)

Sets the Y axis rotation value. Range 0° to 360°.

Joystick.setZAxisRotation(int value)

Sets the Z axis rotation value. Range 0° to 360°.

Joystick.setButton(byte button, byte value)

Sets the state of the specified button. The button is the 0-based button number (i.e. button #1 is 0, button #2 is 1, etc.). The value is 1 if the button is pressed and 0 if the button is released.

Joystick.pressButton(byte button)

Press the indicated button. The button is the 0-based button number (i.e. button #1 is 0, button #2 is 1, etc.).

Joystick.releaseButton(byte button)

Release the indicated button. The button is the 0-based button number (i.e. button #1 is 0, button #2 is 1, etc.).

Joystick.setThrottle(byte value)

Sets the throttle value. Range 0 to 255.

Joystick.setRudder(byte value)

Sets the rudder value. Range 0 to 255.

Joystick.setHatSwitch(byte hatSwitch, int value)

Sets the value of the specified hat switch. The hatSwitch is 0-based (i.e. hat switch #1 is 0 and hat switch #2 is 1). The value is from 0° to 360°, but in 45° increments. Any value less than 45° will be rounded down (i.e. 44° is rounded down to 0°, 89° is rounded down to 45°, etc.).

Joystick.sendState()

Sends the updated joystick state to the host computer. Only needs to be called if AutoSendState is false (see Joystick.begin for more details).

10 comments:

patufet99 said...

I have been using your joystick library and it works well (with an Arduino Micro under Ubuntu). There is something that I do not understand well:

In the HID descriptor:

// X, Y, and Z Axis
0x15, 0x00, // LOGICAL_MINIMUM (0)
0x26, 0xff, 0x00, // LOGICAL_MAXIMUM (255)
0x75, 0x08, // REPORT_SIZE (8)
0x09, 0x01, // USAGE (Pointer)
0xA1, 0x00, // COLLECTION (Physical)
0x09, 0x30, // USAGE (x)
0x09, 0x31, // USAGE (y)
0x09, 0x32, // USAGE (z)
0x09, 0x33, // USAGE (rx)
0x09, 0x34, // USAGE (ry)
0x09, 0x35, // USAGE (rz)
0x95, 0x06, // REPORT_COUNT (6)
0x81, 0x02, // INPUT (Data,Var,Abs)
0xc0, // END_COLLECTION

signals are defined as 8 bit integers. While this is the case for X, Y and Z, for the rotation, the variables are defined as 16 bit:

void setXAxis(int8_t value);
void setYAxis(int8_t value);
void setZAxis(int8_t value);

void setXAxisRotation(int16_t value);
void setYAxisRotation(int16_t value);
void setZAxisRotation(int16_t value);

How does this work?


I am trying to adapt "joystick" as the signals that I use have more than 8 bit, but for instance it does not work. I have modified the HID descriptor as here:

// 8 bit Throttle and Steering
0x05, 0x02, // USAGE_PAGE (Simulation Controls)
0x15, 0x00, // LOGICAL_MINIMUM (0)
0x27, 0xff, 0xff, 0x00, 0x00, // LOGICAL_MAXIMUM (65535)
0xA1, 0x00, // COLLECTION (Physical)
0x09, 0xBB, // USAGE (Throttle)
0x09, 0xBA, // USAGE (Steering)
0x75, 0x10, // REPORT_SIZE (16)
0x95, 0x02, // REPORT_COUNT (2)
0x81, 0x02, // INPUT (Data,Var,Abs)
0xc0, // END_COLLECTION


void Joystick_::setXAxis(int16_t value)
{
xAxis = value;
if (autoSendState) sendState();
}
...

and


// X, Y, and Z Axis
0x15, 0x00, // LOGICAL_MINIMUM (0)
0x27, 0xff, 0xff, 0x00, 0x00, // LOGICAL_MAXIMUM (65535)
0x75, 0x10, // REPORT_SIZE (16)
0x09, 0x01, // USAGE (Pointer)
0xA1, 0x00, // COLLECTION (Physical)
0x09, 0x30, // USAGE (x)
0x09, 0x31, // USAGE (y)
0x09, 0x32, // USAGE (z)
0x09, 0x33, // USAGE (rx)
0x09, 0x34, // USAGE (ry)
0x09, 0x35, // USAGE (rz)
0x95, 0x06, // REPORT_COUNT (6)
0x81, 0x02, // INPUT (Data,Var,Abs)
0xc0, // END_COLLECTION

and in the jostick.h:

int16_t xAxis;
int16_t yAxis;
int16_t zAxis;
int16_t xAxisRotation;
int16_t yAxisRotation;
int16_t zAxisRotation;
uint32_t buttons;
uint16_t throttle;
uint16_t rudder;

void setXAxis(int16_t value);
void setYAxis(int16_t value);
void setZAxis(int16_t value);

void setThrottle(uint16_t value);
void setRudder(uint16_t value);

Any hints?

Matthew Heironimus said...

The setXAxisRotation, setYAxisRotation, and setZAxisRotation values use a 16-bit integer because the range of values those functions take is 0-360, but an 8-bit integer goes from -128 to 127. The sendState function converts this value into the 8-bit equivalent the USB Report is expecting.

I have had quite a few people ask how to increase the precision on the X axis, Y axis, Z axis, Throttle, Rudder, etc., but I have not had the time to update the library yet. I hope to get to this eventually, but it may be a few months before I can get around to it.

In addition to the things you modified, you will also need to increase the size of the USB report (i.e. the data array declared in the sendState function. If you get this working, I would love to see what you changed.

patufet99 said...

Thank you for the feedback.

|The setXAxisRotation, setYAxisRotation, and setZAxisRotation values use a 16-bit integer |because the range of values those functions take is 0-360, but an 8-bit integer goes from |-128 to 127. The sendState function converts this value into the 8-bit equivalent the USB |Report is expecting.

I see now. The 360 degrees are multiplied by 0.708 so that each increment correponds to 1.41 degrees.

|In addition to the things you modified, you will also need to increase the size of the USB |report (i.e. the data array declared in the sendState function. If you get this working, I |would love to see what you changed.

Additionally to that, in the sendState function shouldn't the 2 byte axes be decomposed?

data[4] = throttle & 0xff;
data[5] = throttle >> 8;
data[6] = rudder & 0xff;
data[7] = rudder >> 8;

...

I do not know then if one should send first the msb or the lsb?

I will make some test. If I succeed i will report.
Thanks.

patufet99 said...

It seems to work.
Just to test I have changed the throttle and steering to 16 bit. I have changed in joystick.cpp:

#define JOYSTICK_STATE_SIZE 15 // two more bytes for throttle and steering

// 16 bit Throttle and Steering
0x05, 0x02, // USAGE_PAGE (Simulation Controls)
0x15, 0x00, // LOGICAL_MINIMUM (0)
0x27, 0xff, 0xff, 0x00, 0x00, // LOGICAL_MAXIMUM (65535)
0xA1, 0x00, // COLLECTION (Physical)
0x09, 0xBB, // USAGE (Throttle)
0x09, 0xBA, // USAGE (Steering)
0x75, 0x10, // REPORT_SIZE (16)
0x95, 0x02, // REPORT_COUNT (2)
0x81, 0x02, // INPUT (Data,Var,Abs)
0xc0, // END_COLLECTION

void Joystick_::setThrottle(uint16_t value)
{
throttle = value;
if (autoSendState) sendState();
}
void Joystick_::setRudder(uint16_t value)
{
rudder = value;
if (autoSendState) sendState();
}

data[4] = throttle && 0xff; //lsb
data[5] = throttle >> 8; //msb
data[6] = rudder && 0xff;
data[7] = rudder >> 8;


and in jostick.h:

uint16_t throttle;
uint16_t rudder;

void setThrottle(uint16_t value);
void setRudder(uint16_t value);


Matthew Heironimus said...

Glad to see you got it working. Thanks for sharing your code.

Miles Daddona said...

Thank you so much for this code, I have been trying for weeks with no success to increase the resolution to 10 or 12 bit. I just seem unable to get the hid descriptor right. Any chance of a update in the future.
Thanks again

Miles

Matthew Heironimus said...

@Miles Daddona

At some point in the future I do plan on doing this since so many people have asked for it. Unfortunately I have been pretty busy lately, so I have not had anytime to devote to this. If anyone out there wants to tackle this, the code it out on GitHub (https://github.com/MHeironimus/ArduinoJoystickLibrary).

Alexsandrocaires said...

Hi there.
I've followed the instructions and my leonardo seems to be ok. Now I need to find a code source for a joystick basically with 32 buttons. I don't mind about the axis but I need the board to work as button for my flight simulator project. I know Nothing about programming and I wold appreciate if someone can provide the code for what I need.
I appreciate any help related to this.

Alexsandrocaires said...

Hi there.
I've followed the instructions and my leonardo seems to be ok. Now I need to find a code source for a joystick basically with 32 buttons. I don't mind about the axis but I need the board to work as button for my flight simulator project. I know Nothing about programming and I wold appreciate if someone can provide the code for what I need.
I appreciate any help related to this.

Matthew Heironimus said...

@Alexsandrocaires - I have a new version of this library in beta testing right now (https://github.com/MHeironimus/ArduinoJoystickLibrary/tree/version-2.0) that allows you to have 32 buttons (or more).