Skip to content

Turning an Arduino Leonardo into a joystick.

2012 November 3
by That Guy

I recently bought a Arduino Leonardo in the interest of updating the interface in an old arcade-style controller I occasionally use to play MAME games.

As such, I needed to figure out how to get the leonardo to properly act like a joystick.
Anyways, this is heavily based on drake250′s work, which is on the freetronics forum here. HelmPCB’s USB Joystick site was also a useful reference.
There is a useful list of HID descriptors here.
Also, the “HID Descriptor Tool” from usb.org is also very useful for seeing how HID descriptors are put together. Get it here.

In the end, I wound up rewriting most of the drake250′s HID descriptor myself, based on some of the USB.org docs, and the HID Descriptor Tool.

Anyways, here are my modified files you would need to turn your own leonardo into a joystick. These files support an 8-axis joystick, with two hat-switches, and 32 buttons.
If you want fewer buttons/axes/whatever, you can either just ignore the things you don’t need, or modify the HID descriptor yourself. As it is, for everything the HID descriptor currently specifies, it only needs 12 bytes per PC-update, so I’m not too worried about the extra axes causing issues or slowing things down.


The windows joystick info dialog only seems to be able to display 7 axes. I’m fairly sure the eighth axis there. However, since the built-in windows dialog works almost the time, I have not found any tools for looking at joystick readouts other then the built-in windows dialog. I mean, how may people have an 8-axis joystick anyways?
Also, the HID documents seem to also support the belief that the HID spec allows a maximum of 63 buttons. However, again, the windows tools for inspecting a joystick only support 32. You could add more quite easily, but you would have to test their functionality yourself.

Modified arduino files (replace the files in {arduino executable dir}\hardware\arduino\cores\arduino\ with these):

USBAPI.h
HID.cpp

And the main source file:

 leoJoy.ino 

It’s contents:

7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
JoyState_t joySt;		// Joystick state structure
 
void setup()
{
	pinMode(13, OUTPUT);
 
	joySt.xAxis = 0;
	joySt.yAxis = 0;
	joySt.zAxis = 0;
	joySt.xRotAxis = 0;
	joySt.yRotAxis = 0;
	joySt.zRotAxis = 0;
	joySt.throttle = 0;
	joySt.rudder = 0;
	joySt.hatSw1 = 0;
	joySt.hatSw2 = 0;
	joySt.buttons = 0;
 
}

The interesting thing here is JoyState_t, which is a struct that stores the state for all the joystick controls.

The struct is defined in USBAPI.h as such:

53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
typedef struct JoyState 		// Pretty self explanitory. Simple state to store all the joystick parameters
{
	uint8_t		xAxis;
	uint8_t		yAxis;
	uint8_t		zAxis;
 
	uint8_t		xRotAxis;
	uint8_t		yRotAxis;
	uint8_t		zRotAxis;
 
	uint8_t		throttle;
	uint8_t		rudder;
 
	uint8_t		hatSw1;			// 0-7 correspond to 0-315° in 45° steps. 8 means the hat-switch is centered
	uint8_t		hatSw2;			// All other values are invalid
 
	uint32_t	buttons;		// 32 general buttons  (Each bit corresponds to a separate button)
 
} JoyState_t;

Continuing on with the contents of leoJoy.ino:

29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
void loop()
{
 
 
 
	joySt.xAxis = random(255);
	joySt.yAxis = random(255);
	joySt.zAxis = random(255);
	joySt.xRotAxis = random(255);
	joySt.yRotAxis = random(255);
	joySt.zRotAxis = random(255);
	//joySt.throttle = random(255);
	joySt.rudder = random(255);
 
	joySt.throttle++;
 
 
	joySt.buttons <<= 1;
	if (joySt.buttons == 0)
		joySt.buttons = 1;
 
	joySt.hatSw1++;
	joySt.hatSw2--;
 
	if (joySt.hatSw1 > 8)
		joySt.hatSw1 = 0;
	if (joySt.hatSw2 > 8)
		joySt.hatSw2 = 8;
 
	delay(100);
 
	if (joySt.throttle > 127)
		digitalWrite(13, HIGH);
	else
		digitalWrite(13, LOW);
 
 
	// Call Joystick.move
	Joystick.setState(&joySt);
 
}

This section just generates random values and sets the analog axes to them. Each bit in buttons is also set in sequence, using bit-shifting.
It also steps through the different hat-switch positions. It steps hatSw1 fowards, and hatSw2 backwards. Lastly, the throttle axis it linearly ramped.

The last (and most important thing) here is

67
	Joystick.setState(&joySt);

This is the function that actually sends the values in the joySt struct to the computer.

At this point, all that is needed to make a useable joystick is to replace the contents of loop() with something that actually reads hardware buttons.


Anyways, everything is under the arduino license (I think it’s GPL, but the only file I edited with a license actually looks to be BSD-ish. Anyways, do whatever, just don’t be an ass).

18 Responses leave one →
  1. Yianni permalink
    November 19, 2012

    hello,

    i just checked the HID.cpp and USBAPI.h and Joystick definitions are there but i get this error:

    any ideas?

    C:\Users\Ark\AppData\Local\Temp\build5316580264749198580.tmp/sketch_nov19a.cpp:70: undefined reference to `Joystick’
    C:\Users\Ark\AppData\Local\Temp\build5316580264749198580.tmp/sketch_nov19a.cpp:70: undefined reference to `Joystick’
    C:\Users\Ark\AppData\Local\Temp\build5316580264749198580.tmp/sketch_nov19a.cpp:70: undefined reference to `Joystick_::setState(JoyState*)’

    Thanks in advance. Yianni

  2. Alex Kuptsov permalink
    November 20, 2012

    I’m new to arduino world

    thanks a lot

  3. November 26, 2012

    @Yianni:

    It sounds like you didn’t properly replace USBAPI.h.
    Both the Joystick class, and Joystick_::setState are both forward-defined there.

  4. Thom-x permalink
    December 31, 2012

    Thank you ! It works great !

  5. José Ferreira permalink
    January 7, 2013

    Can you help me puting the clutch in the code.

    Thanks!!

  6. January 13, 2013

    Uh… What?

    DO you mean you want to add a clutch pedal? If so, just assign it to another analog axis, or a button, depending on how you want it to function.

    You could also tweak the HID descriptor. I think (IIRC), that there is a specific descriptor for a clutch pedal, it shouldn’t be too hard to add it to the complete descriptor.

    Just don’t forget you need to update the USB report size to include the new clutch.

  7. Rômulo permalink
    January 30, 2013

    Hey, I really liked your explanation, but, how could I wire the potentiometers of the throttle, brake and clutch to the axis?

  8. February 1, 2013

    You would need to use the arduino ADCs for that.

    Additionally, since the ATmega32U4 ADCs are 10 bit, you will need to divide the value they return down to the 8-bit size of the analog axes the current HID descriptor allocate.

    Google “Arduino Potentiometer” for more information.

  9. Dave permalink
    February 25, 2013

    If you pack the structure and put it in the same order as the report, you don’t need any extra code to send it — you can just send the whole structure at once.

    Have you tried two joysticks? I can’t seem to get that to work.

    Thanks for the code!

  10. Mason H. permalink
    April 1, 2013

    How would I go about adding two buttons to be bound to a digital read of pins 1 and 2 of a similar board? I’ve got one button working fine, I just can’t figure out a second one…

  11. Mario permalink
    April 8, 2013

    Hello,

    i tried to make a simple button, when i press this button the Leonardo should give a signal to the windows gamepad (Arduino Leonardo) that the button is pressed.

    With this Code i can see in the SerialMonitor if i press the Button.

    void setup(){
    Serial.begin(9600);
    pinMode(2,INPUT);
    }

    void loop(){
    int status;
    status = digitalRead(2);
    Serial.println(status,DEC);
    }

    how would the Code look like when i want to use the leoJoy.ino?

  12. wwAGHww permalink
    April 30, 2013

    Sory for my question, but will it work on an other arduino board ?

  13. That Guy permalink*
    May 5, 2013

    No. Most of the arduinos have another IC between the microprocessor (the ATmega328P) and the computer. The Leonardo is special because it is directly connected over USB.

    You can reflash the ATmega16U2 that is used as the USB-serial converter to act as a HID device, but it no longer behaves as a normal arduino (you need to program it over ICSP at that point).

  14. That Guy permalink*
    May 5, 2013

    The buttons are bit-mapped to the buttons variable in the joySt struct.

    You would need to make it so when the buttons are pressed, it sets the various bits in buttons. Then you simply call Joystick.setState(&joySt); to update the joystick state to the computer.

  15. Edgar permalink
    May 11, 2013

    Hi, how would this work on a board like the nanowii?
    http://flyduino.net/Multikopter-FC-Multiwii_1

    and would it be possible to let the Invensense MPU6050 be input for a 3 axis joystick?

    I have no experience with arduino or any coding at all, but this board looks like a good candidate to make a nice flight simulator yoke.

  16. May 11, 2013

    I don’t see any fundamental issues.

    You would have to figure out how to feed the MPU6050 output into the joystick struct.

    One comment I have is that is that I don’t have a high opinion of using an IMU for a joystick. Actual mechanical position measurement devices are almost invariably nicer.

  17. Félix Dhumes permalink
    May 20, 2013

    Good morning.
    Sorry if my question is obvious, but I’m French and I speack English just a little.
    With this modification and this program, I have just, for example, to modify the void loop like this:

    void loop()
    {
    axisx = analogRead(1);
    map(axisx, 0, 1024, 0, 255);
    joySt.xAxis = axisx;
    }

    Sorry for my bad English, and have a good day.

  18. May 20, 2013

    I’m not sure how map() works, but I don’t think that will work, as map() has no way to change the value of axisx.

    Possibly you need to pass the value you want map to translate as a pointer –

    map(&axisx, 0, 1024, 0, 255);

    But I don’t know if map works that way. It may also return the scaled value, rather then operating on a pointer to the mapped value.

Leave a Reply

Note: You can use basic XHTML in your comments. Your email address will never be published.

Subscribe to this comment feed via RSS