Add Buttons to Your X52-Pro Joystick With an Arduino Micro




Introduction: Add Buttons to Your X52-Pro Joystick With an Arduino Micro

I have a Saitek X52-Pro HOTAS setup which I use for Elite:Dangerous. It's a fantastic joystick but it could do with just a few more buttons. Using an Arduino Micro you can extend this (or any other) joystick for any game that will support multiple devices.

The Arduino allows you to configure it as a HID-compliant device so you can get it to show up as a keyboard, mouse, joystick or gamepad without the hassle of writing device drivers. You will need to modify some files in your Arduino IDE and write a sketch to handle the buttons, but once you've got the right details it's simple enough.

You should also be able to use this guide to create an independent joystick, gamepad or keypad to suit your purposes.

I spent a very long time reading a number of other tutorials which gave me enough detail to replicate their work, but not quite enough to do it the way I'd like, so in this tutorial I hope to give you the knowledge and links to create a setup of your own.

Step 1: Work Out What You Want to Add and Where You Will Fit the Buttons.

Depending on your setup you might simply want to add a button or two, or you may fancy something more complicated such as a thumb-stick, a D-pad and 24 buttons. I decided to add the capability for a 4-way hat switch and 8 buttons. This is probably overkill for the X52-Pro, but I'd rather have unused buttons than too few. That being said, it would be possible to add them after the event if necessary, but it's tidier to get it all done at once.

It's a good idea to play with layouts. Try fitting button caps (or even shirt buttons) using Blu-tac. You can reposition them and fiddle around until you have a layout that fits your hand. Above are a couple of examples from my upgrade.

Think of a few alternatives - Once you've got the stick open you may find that there isn't room for your components.

Step 2: Prototype Your Design on a Breadboard

Before you dive in with a Dremel, ensure that you can get the electronics working. Spend some time with a breadboard so you can get the system working before you touch your expensive joystick.

I created a simple setup with 8 tactile switches for buttons 1-8 and another 4 small switches to represent the hat switch. The Arduino can set pull-up resistors internally so you simply need to connect one side of each switch to the Ground rail and the other side to one of the input pins. I connected the buttons to pins 0-7 and the 4 hat-switch buttons to pins 8-11. I also added an LED with a limiting resistor to pin 12. This can be mounted in the joystick to indicate that the Arduino has power and is running the code successfully.

Step 3: Set Up the Arduino IDE

If you haven't already done so, download the Arduino IDE from and get it installed.

If you're not already familiar with Arduino then try out a test sketch to make sure you've got it set up. If you know what you're doing then just skip to the next step.

Connect your Arduino to your PC (or Mac) using a micro-B USB cable and load up the IDE. You then need to select the board type and the COM port for your Arduino. In my case it was 'Arduino Micro' and '/dev/tty.usbmodem1411'. Windows will show the COM ports as COM7 or similar.

Once you've done this select File >> Examples >> 01.Basics >> Blink. This will load up a simple example sketch (Arduino programs are known as sketches) that will blink an LED. The example uses pin 13, which on most boards is connected to an on-board LED. Click the 'Upload using Programmer' (->) button to load and run the sketch. This should program the Arduino and the on-board LED should blink steadily. This lets you know that the Arduino is OK and you have the IDE configured correctly.

If you need to learn more about the Arduino, take a look at the site, there is a lot of basic documentation there.

Step 4: Write Your Test Sketch

You should now write a sketch on the Arduino to test your buttons. You want to get the code to write debug output to the serial port to show you that each button is working as expected. In essence, for each input pin you simply need to set the pin to accept input and watch the pin to see when it goes from HIGH (+5v/Vcc) to LOW (0v/Gnd). Do this 12 times and you're golden, right? Unfortunately life isn't that simple.

Firstly you need to ensure that when the button is not pressed, the pin gets a steady 5v, and when it's pressed it gets 0v. This requires the use of a pull-up resistor. If you're new to electronics this may sound a little odd but it makes sense when you think about it. Learn more about pull-up resistors here: Fortunately the Arduino Micro has the ability to enable internal pull-up resistors so you don't have to do it yourself. For each input pin, set the pin like so in the void setup() function:

pinMode(2, INPUT_PULLUP); // The number represents the pin number

So in my case I loop through an array of pins as follows:

const int switchCount = 12; void setup() { for (int i=0; i <= (switchCount -1); i++) { // Make the pushButton pin an input pinMode(i, INPUT_PULLUP); } }

Now you need to read the state of the buttons. In theory this is a simple. To read the state of pin 2 you just use the digitalRead() function:

switch2State = digitalRead(2);

However, when a button is pressed you generally get a bit of noise for a few milliseconds and this can appear as if the button is being rapidly pressed. This can make life difficult to say the least. What you need to do is to 'debounce' the button. Essentially you read the state of the button, if the state has changed then you wait a bit before reading again. This should give you the actual state of the button without it flapping madly. See for more info.

This gets a little hairy for more than a couple of buttons so I made use of the Bounce2 library to simplify things. See for more details. All you need to do is download the file from the previous link and drop it in the Arduino Libraries folder before starting the IDE. Then add this line to to top of your sketch:

#include <Bounce2.h> //

You can then attach a bouncer object to each pin to debounce it and get a clean reading when the button is pressed or released.

// create a bouncer object
Bounce tempBouncer = Bounce(); tempBouncer.attach(2); // Attach pin 2 to the Bouncer object tempBouncer.interval(10); // Set the DeBounce timeout in milliseconds

Now you can cleanly read each button you just need to keep a note of it's state each time you read the pin and do something if the state changes. The attached sketch will allow you to read 12 buttons and will output a debug line each time a button is pressed or released. Just upload the sketch to the board and once you see the LED light press CMD+SHIFT+M (Mac) or CTRL+SHIFT+M (Win) to open the Serial Monitor. There are plenty of comments inline to let you know what's going on.

Step 5: Create the HID Descriptor for Your New Device

Now you've tested your prototype setup you can start to turn your Arduino into a HID-Compatible device. The Arduino environment is already set up to allow it to function as a keyboard and a mouse, but we need to add another device type into the mix.

The HID specifications are designed to allow anyone to create a USB class device or application without the need to create custom drivers. This is very handy, but does mean that the specification is pretty complex as it is trying to be all things to all men. Essentially you specify the type of device that you are creating - Keyboard, Joystick, Mouse, Gamepad, Steering Wheel, etc - and then specify one or more collections of inputs of various types. These settings and the type and size of the input data are specified with a long series of arcane codes and do not make for easy reading. Fortunately most descriptors I've found are swimming with plain-text comments which make life a little easier. There are also a few good guides and usage tables on the web. They're a little dry but they will help you to find what you need. Here are a couple I found handy:

HID Tutorial:

HID Usage Table:

You can also find more detailed info and a (pretty clunky) HID descriptor tool at Descriptor Tool

Here is the HID descriptor I am using for my setup:

0x05, 0x01, 	// USAGE_PAGE (Generic Desktop)

0x09, 0x05,		// USAGE (Game Pad)
0xa1, 0x01,		// COLLECTION (Application)
	0x85, 0x03,	// REPORT_ID (3)  (This is important when HID_SendReport() is called)
	0xA1, 0x00, 	// COLLECTION (Physical)
		// 8 buttons
		0x05, 0x09, 	// USAGE_PAGE (Button)
		0x19, 0x01, 	// USAGE_MINIMUM (Button 1)
		0x29, 0x08, 	// USAGE_MAXIMUM (Button 8)
		0x15, 0x00, 	// LOGICAL_MINIMUM (0)
		0x25, 0x01, 	// LOGICAL_MAXIMUM (1)
		0x95, 0x08, 	// REPORT_COUNT (8)
		0x75, 0x01, 	// REPORT_SIZE (1)
		0x81, 0x02, 	// INPUT (Data,Var,Abs)
<br>		// 1 Hat Switch
		0x05, 0x01,	// USAGE_PAGE (Generic Desktop)
		0x09, 0x39,	// USAGE (Hat switch)
		0x15, 0x00,	// LOGICAL_MINIMUM (0)
		0x25, 0x07,	// LOGICAL_MAXIMUM (7)
		0x35, 0x00,	// PHYSICAL_MINIMUM (0)
		0x46, 0x3B, 0x01,	// PHYSICAL_MAXIMUM (315)
		0x65, 0x14,	// UNIT (Eng Rot:Angular Pos)
		0x75, 0x04,	// REPORT_SIZE (4)
		0x95, 0x01,	// REPORT_COUNT (1)
		0x81, 0x02,	// INPUT (Data,Var,Abs)
<br>		// Padding (4 bytes)
		0x75, 0x04,	// REPORT_SIZE (4)
		0x95, 0x01,	// REPORT_COUNT (1)
		0x81, 0x03,	// INPUT (Cnst,Var,Abs)

		// 1 D-pads - Dummy so Elite:Dangerous recognises it
		0x05, 0x01, 	// USAGE_PAGE (Generic Desktop)
		0x09, 0x30,     // USAGE (X)
		0x09, 0x31,     // USAGE (Y)
		0x15, 0x81,     // LOGICAL_MINIMUM (-127)
		0x25, 0x7f,      // LOGICAL_MAXIMUM (127)
		0x75, 0x08, 	// REPORT_SIZE (8)
		0x95, 0x02, 	// REPORT_COUNT (2)
		0x81, 0x02, 	// INPUT (Data,Var,Abs)

To get here I looked over the USAGE_PAGE and USAGE specifications and decided that Game Pad was the best description to use as a Joystick technically has at least 2 analog axes plus at least two buttons. The game pad can either have analog X/Y or a digital 4-way switch plus at least two buttons, so I could fit my extension into this category. After that we specify a collection for the application and give it a report ID. This helps us identify the data later.

Now we specify a physical collection. This holds our buttons. You can have more collections, nested or side-by-side, but I didn't feel the need. Inside the physical collection you now specify the inputs. To start with I list the button usage page and then specify the minimum and maximum values, in this case 1 and 8 as I have 8 buttons. I then specify the Logical minimum and maximum for each of those buttons. As they are simple switches I only have 1 and 0 for pressed and not pressed. Then I specify the number of items in the report and the number of reports for this usage page. Finally I specify the input type for this value - Data, which is Variable, and consists of absolute vaules, rather than values that are relative to the last reported values.

The hat switch is somewhat different. A hat switch requires a value from 0 to 7 which specifies the direction of the hat in 45 degree increments - 0 = up, 1 = up/right, 2 = right, 3 = down/right etc. This means that we specify the logical min/max as 0 and 7 and the physical min/max as 0 and 315. We don't need to go any further as 360 degrees is equivalent to 0 degrees. We also specify the Unit as an angular rotation position. As 0-7 can be contained in four bits the report size is 4 this time.

So, 8 buttons and a hat, we're done? Not quite. You need to transfer a whole number of bytes for each report to the USB host so we need to add some padding. You could do this by adding another phantom hat that is never used, but it's cleaner to simply add another report of 4 bits and set the input type to constant. Once that's done you can close off both the physical and application collections and you're done.

Unfortunately, whilst Windows recognises the device just fine you will find that some games won't as they expect to find an X/Y axis so as you can see from the above file I've added dummy X/Y axes. These will appear in the data transferred, but as we never change the values from 0 then the dummy stick will never appear to move.

Step 6: Add the HID Code to Your Arduino Environment Files

Now you have your descriptor you need add it to your Arduino environment so it can program it on to your device. To do this you will need to modify a couple of files. Navigate to the Documents/Arduino/hardware/arduino/cores/arduino (Mac) or C:\Program Files (x86)\Arduino\hardware\arduino\cores\arduino (Win) directory. In there take backup copies of the HID.cpp and USBAPI.h files, just in case.

Open HID.cpp in your favourite text editor (EditPlus 2 or Notepad++ are good for Windows, Text Wrangler is useful for Mac) and look near the top for the following section:

#if defined(USBCON)

// Singletons for mouse and keyboard Mouse_ Mouse; Keyboard_ Keyboard;

You now need to add a definition and a singleton variable as follows:

#if defined(USBCON)


// Singletons for mouse and keyboard Mouse_ Mouse; Keyboard_ Keyboard;

// And now a joystick object too Joystick_ Joystick;

Then look further down for the RAWHID section:

// RAW HID 0x06, LSB(RAWHID_USAGE_PAGE), MSB(RAWHID_USAGE_PAGE), // 30 0x0A, LSB(RAWHID_USAGE), MSB(RAWHID_USAGE), 0xA1, 0x01, // Collection 0x01 0x85, 0x03, // REPORT_ID (3) 0x75, 0x08, // report size = 8 bits 0x15, 0x00, // logical minimum = 0 0x26, 0xFF, 0x00, // logical maximum = 255 0x95, 64, // report count TX 0x09, 0x01, // usage 0x81, 0x02, // Input (array) 0x95, 64, // report count RX 0x09, 0x02, // usage 0x91, 0x02, // Output (array) 0xC0 // end collection #endif };

Now modify this to change the #if to #ifdef and insert your new HID descriptor inside its own #ifdef section

// RAW HID 0x06, LSB(RAWHID_USAGE_PAGE), MSB(RAWHID_USAGE_PAGE), // 30 0x0A, LSB(RAWHID_USAGE), MSB(RAWHID_USAGE), 0xA1, 0x01, // Collection 0x01 0x85, 0x03, // REPORT_ID (3) 0x75, 0x08, // report size = 8 bits 0x15, 0x00, // logical minimum = 0 0x26, 0xFF, 0x00, // logical maximum = 255 0x95, 64, // report count TX 0x09, 0x01, // usage 0x81, 0x02, // Input (array) 0x95, 64, // report count RX 0x09, 0x02, // usage 0x91, 0x02, // Output (array) 0xC0 // end collection #endif

// *** Here is where the RAW_HID has been converted to a Game Pad device // *** Inspired by // *** Check out for more than you'll ever need to know about USB HID // *** HID descriptor created using the HID descriptor tool from

#ifdef JOYHID_ENABLED 0x05, 0x01, // USAGE_PAGE (Generic Desktop) 0x09, 0x05, // USAGE (Game Pad) 0xa1, 0x01, // COLLECTION (Application) 0x85, 0x03, // REPORT_ID (3) (This is important when HID_SendReport() is called) 0xA1, 0x00, // COLLECTION (Physical)

// 8 buttons 0x05, 0x09, // USAGE_PAGE (Button) 0x19, 0x01, // USAGE_MINIMUM (Button 1) 0x29, 0x08, // USAGE_MAXIMUM (Button 8) 0x15, 0x00, // LOGICAL_MINIMUM (0) 0x25, 0x01, // LOGICAL_MAXIMUM (1) 0x95, 0x08, // REPORT_COUNT (8) 0x75, 0x01, // REPORT_SIZE (1) 0x81, 0x02, // INPUT (Data,Var,Abs)

// 1 Hat Switch 0x05, 0x01, // USAGE_PAGE (Generic Desktop) 0x09, 0x39, // USAGE (Hat switch) 0x15, 0x00, // LOGICAL_MINIMUM (0) 0x25, 0x07, // LOGICAL_MAXIMUM (7) 0x35, 0x00, // PHYSICAL_MINIMUM (0) 0x46, 0x3B, 0x01, // PHYSICAL_MAXIMUM (315) 0x65, 0x14, // UNIT (Eng Rot:Angular Pos) 0x75, 0x04, // REPORT_SIZE (4) 0x95, 0x01, // REPORT_COUNT (1) 0x81, 0x02, // INPUT (Data,Var,Abs)

// Padding (4 bytes) 0x75, 0x04, // REPORT_SIZE (4) 0x95, 0x01, // REPORT_COUNT (1) 0x81, 0x03, // INPUT (Cnst,Var,Abs)

// 1 D-pads - Dummy so Elite:Dangerous recognises it 0x05, 0x01, // USAGE_PAGE (Generic Desktop) 0x09, 0x30, // USAGE (X) 0x09, 0x31, // USAGE (Y) 0x15, 0x81, // LOGICAL_MINIMUM (-127) 0x25, 0x7f, // LOGICAL_MAXIMUM (127) 0x75, 0x08, // REPORT_SIZE (8) 0x95, 0x02, // REPORT_COUNT (2) 0x81, 0x02, // INPUT (Data,Var,Abs)


Now that's done, we need to add some code to receive the data from our sketch we created earlier and send the report. Look further down for the following code:

//============================================================================= // Mouse Mouse_::Mouse_(void) : _buttons(0) { }

Directly above this add a new chunk of code to handle the data as follows

//============================================================================= // Joystick // Usage: Joystick.move(inputs go here) // // The report data format must match the one defined in the descriptor exactly // or it either won't work, or the pc will make a mess of unpacking the data //

Joystick_::Joystick_() { }

#define joyBytes 4 // should be equivalent to sizeof(JoyState_t)

void Joystick_::setState(JoyState_t *joySt) { uint8_t data[joyBytes]; data[0] = joySt->buttons & 0xFF; data[1] = joySt->hatSw1; data[2] = joySt->left_x; data[3] = joySt->left_y;

//HID_SendReport(Report number, array of values in same order as HID descriptor, length) // The joystick is specified as using report 3 in the descriptor. That's where the "3" comes from HID_SendReport(3, data, joyBytes); }

Here we define the number of bytes that are being sent. In this case 2 - 1 byte for the buttons and 1 byte for the hat, including padding. Then we have the simple function to accept the data sent from the sketch, chop it up and then spit it out using the HID_SendReport function. This one is very simple, but you can find more complicated ones elsewhere. This site has a good example of an 8-axis, 32-button, 1 hat setup. This helped me a lot but was far too complicated for what I wanted so I simplified it as much as I could.

Once you've done all that, save the file and open USBAPI.h. In this file we will create a struct and class for the joystick object. Find the following code:

//========================================================================== // Mouse #define MOUSE_LEFT 1 #define MOUSE_RIGHT 2

As before, insert this code directly above this

//============================================================================= // Joystick // Implemented in HID.cpp // The list of parameters here needs to match the implementation in HID.cpp

typedef struct JoyState // Pretty self explanitory. Simple state to store all the joystick parameters { uint8_t buttons; uint8_t hatSw1; int8_t left_x; int8_t left_y; } JoyState_t;

class Joystick_ { public: Joystick_();

void setState(JoyState_t *joySt);

}; extern Joystick_ Joystick;

Save the file and you're done. Again, if you have more a demanding application simply add code to handle the extra hardware. The above mentioned site will again be useful.

Step 7: Expand Your Sketch to Send the Data

Now your environment is configured you can add the code to your test script to make use of the HID descriptor.

Take a copy of your test script and modify it as follows. First, at the top of the script you need to add a variable to hold the state of the joystick and another to keep track of the hat switch

// Create a variable to hold the entire state of the device to pass over USB to the HID driverJoyState_t joySt;
// A variable to help us manage the 4 hat buttons to make an 8-way hat if we want
byte hatButtons;

Then add a line at the bottom of the setup() function to set the initial state:

joySt.buttons = 0; // Set the initial state of the buttons

In this example the first 8 buttons are just simple buttons so we can simply count through all the buttons, less the last four for the hat., and set or clear the appropriate bit on the joySt.buttons variable.

if (i < (switchCount - 4)) {
// We're looking at all but the last 4 pins as simple buttons in this section

// if the button state has changed and it's currently pressed if ((buttonState != buttonLastState[i]) && (buttonState == LOW)) { // Set the button bit joySt.buttons = bitSet(joySt.buttons, i); }

// if the button state has changed and it's currently released if ((buttonState != buttonLastState[i]) && (buttonState == HIGH)) { // Unset the buttonbit joySt.buttons = bitClear(joySt.buttons, i); } // save the current button state for comparison next time: buttonLastState[i] = buttonState;

} else { ...

We are treating the last four buttons as a hat switch. There are a couple of ways of handling a hat switch. You can treat it as a simple 4-way hat (North, East, South, West), or you can go for an 8-way hat (N, NE, E, SE, S, SW, W, NW). A 4-way hat is quite simple to handle so I'll show you the 8-way version here. The 8-way functionality means that you need to handle more than one button being pressed so you can't set things as you go along as with the buttons. So, following on from the previous code, for the last four buttons you should record the state of each one in the variable we created earlier. I'm using a bit-mask to make things simpler as we can just read the integer value afterwards to get the 'direction' of the hat.

} else {
// We're now looking at the remaining four buttons as hat switches in this section

// Using a bit-mask, set the bit that corresponds to each button so we can // determine the state of all four buttons in one go later on if (buttonState == LOW) { // Set the bit if the button is pressed bitSet(hatButtons, i - 8); } else { // Clear the bit if the button is NOT pressed bitClear(hatButtons, i - 8); } }

Now we know which of the hat buttons are pressed we can read the value of the hatButtons variable and pass the correct value to the joySt object:

// Determine value for hatSw1 according to the buttons that are pressed
// This uses the last four bits in the byte so we just need to check the value of // the byte and set the joySt.hatSw1 value if we have a single button pressed or // a valid pair of buttons pressed. Below i've shown what's pressed using UPPER // case and what's not pressed using lower case. i.e. ULdr = UP and LEFT pressed // Key: UP = bit 1, LEFT = bit 2, DOWN = bit 3, RIGHT = bit 4 switch (hatButtons) { case 0: joySt.hatSw1 = 8; break; // uldr : center : hatButtons = B00000000 case 1: joySt.hatSw1 = 0; break; // Uldr : 0 : hatButtons = B00000001 case 3: joySt.hatSw1 = 1; break; // ULdr : 45 : hatButtons = B00000011 case 2: joySt.hatSw1 = 2; break; // uLdr : 90 : hatButtons = B00000010 case 6: joySt.hatSw1 = 3; break; // uLDr : 135 : hatButtons = B00000110 case 4: joySt.hatSw1 = 4; break; // ulDr : 180 : hatButtons = B00000100 case 12: joySt.hatSw1 = 5; break; // ulDR : 225 : hatButtons = B00001100 case 8: joySt.hatSw1 = 6; break; // uldR : 270 : hatButtons = B00001000 case 9: joySt.hatSw1 = 7; break; // UldR : 315 : hatButtons = B00001001 }

Now all that's left to be done is to output the current state to update the computer

// Call Joystick.setState and send the data to the computer

I've attached the code for this example to this step to save your fingers.

Step 8: Test the Prototype

Now you can test with your PC. I would strongly recommend using a PC rather than a Mac here as Windows has a test program built-in.

Upload the final sketch to your Arduino and fire up the 'Set up USB game controllers' Control Panel applet. If you've got the HID descriptor and the Joystick code right then you'll see the Arduino appear as a game controller. Click Properties, press a few buttons and revel in the satisfaction of a job well done!

Apart from fitting the hardware in the stick of course!

Step 9: Open Up Your X52-Pro

If you've got a different joystick, you're on your own, but if you have an X52-Pro then here's what I did.

Undo the 8 screws visible from underneath, including the two under the solid rubber feet. Gently pull the bottom apart, allowing for the two clips at the bottom edge of the base. Once you've freed the catches the base should pull off freely. Watch the copious amounts of extremely sticky grease!

Remove the two screws from the remaining white plastic retainer. Undo the two screws from the white collar on the centre of the pivot. Remove the free parts, noting where they came from as they're not quite the same.

Disconnect the wiring plugs from the main circuit board, pulling on the plugs, NOT the wires! Once you have released the wires, carefully unsticking any hot glue if necessary, you can ease up the throttle pivot and slide the cables through to give yourself some wiggle room. You can then see and remove the four screws holding the throttle handle onto the pivot.

Slide the wires through and you will be able to remove the screws from the bottom of the throttle handle and ease the handle apart. You're now ready to finalise the position of your buttons.

Step 10: Fit the Buttons to the Throttle

Now you can take your buttons and dry-fit them in your pre-marked locations. You'll probably need to tweak things a bit here as existing components and structures might be in the way - I certainly did. I used 12mm tactile switches and a toggle switch in my build. Once you're sure that they will fit then you can drill your holes. Make sure the hole is slightly larger than the button cap so that the buttons won't bind in the holes. I also had to trim the corner of a circuit board using my Dremel on the end of the throttle as the circuit board was just a fraction too large. If you do this then look carefully at the board to ensure that you are't going to make a mess of it. Fortunately there's not a lot to these boards so you can check quite easily - I wouldn't consider chopping the corner off one of the multi-layered boards in the base!

Once you've drilled you hole and checked the clearance for the button caps then you can fix the buttons in place with some epoxy resin. I had a little trouble with the glue seeping and sticking the button up, so either use a 5-minute epoxy and hold the button firmly in place until it sets, or use the resin in a thin layer at first and allow the it to thicken slightly before finally fitting the button. Once it's in place you can build up in layers if you feel it needs it.

I fitted the four-way tactile switch next to the mouse wheel style button so I had to add more clearance to allow the button to tilt. I wrapped very thin card around the button to forcefully centre the button until the resin hardened.

The long-handled toggle switch was fitted to the base under the main circuit board as well. If you do this be very sure that it will fit without fouling the board. I actually split apart one switch and 'test fitted' it with a thin sliver of blu-tac underneath and a thicker slug on top to double check. Once the circuit board over it was re-fitted and removed I could see what clearance I had by how squashed the blu-tac was.

Step 11: Solder the Buttons and Run the Signal Wires to the Arduino

Once all the buttons have been glued in and the resin has hardened you can start to wire them up. Each button will share a ground wire and this runs from button to button. I used a black wire for ground and as you can see each button, apart from the end one, has a black wire running to and from the same pin. Try to use thin, flexible wires with as many different colours as possible and make sure you note which one goes to which button and/or pin.

Once the buttons have had their wires soldered on test them with a multimeter. Use a hot-melt glue gun to tack the wires down as close as possible to their solder points so that any stress on the wires doesn't stress the solder joints. I went to town a little so those wires aren't going to shift! Now that they're secure gather the loose ends together and run them alongside the existing wire bundle keeping things as neat as possible. Make sure you keep them tight enough so that they don't get in the way of anything, but leave enough slack so that they don't tighten up with the movement of the throttle.

The status LED can be mounted by drilling a hole sideways into one of the fixing holes that run through the body of the base. Use a drill 0.5 mm bigger than the body of the LED but not bigger than the little flare at the bottom. Then you can secure it in place with hot-melt glue. The glow of the LED will show from the mounting hole but not so brightly that it feels intrusive.

Now, referring to your wiring notes and schematic diagram, solder the wires directly to the Arduino board. I found that plugging the board into the USB cable allowed me to use it as a 'third hand' that you always seem to need when soldering. When all the soldering is done, connect the board up to the PC and check that the buttons respond as you expect, especially the 4-way hat if you're using one. If you've got anything wrong then, or course, you can fix it in software, but I think it's better to correct the wiring at this stage.

The last thing to do is to route the USB cable. The existing cable has a number of strain-relief pegs that it runs around before exiting the base. I used these for the USB cable but pushing it further down the pegs and exiting one peg earlier. Mark the place where it will come out and re-assemble the base without the USB cable attached. You can then drill a hole, about 1mm larger than the diameter of the actual cable, across the join of the two parts of the base for a neat finish. Re-open the base, route the cables and secure with liberal amounts of hot-melt glue.

Now all that is left to be done is to screw the base together plug it in and go flying!

Step 12: Parts List

If you want to copy what I did, then these are the parts I used:

Parts from

  • 1 x A-Star 32U4 Micro (part# A-STAR-32U4 @ £8.00)
  • 3 x 6 Core Signal Cable (part# CABLE6 @ £0.30 each)
  • 1 x 3mm Green LED/resistor combo (part# LED3MMGR @ £1.30 for 10-pack)

Parts from

  • 4 x Key Cap Black Round (part# 653-B32-1610 @ £0.27 each)
  • 2 x Key Cap Black Square (part# 688-SK2AA00040 @ £0.17 each)
  • 1 x 4-direction 10mm (part# 688-SKQUBA @ £1.61)
  • 1 x (on)OFF(on) Toggle switch (part# 633-M2018Q4S1W01 @ £3.09)
  • 3 x 12mm Tactile Switch (part# 653-B3F-5150 @ £0.35 each)
  • 1 x 1.5M USB A to micro-b cable (part# 552-IPUSB1MS-R @ £3.88)

Plus you'll need a hot-melt glue gun, soldering iron, high-quality electronics solder, drill, drill bits, screwdrivers, craft knife etc.

Be the First to Share


    • Microcontroller Contest

      Microcontroller Contest
    • Automation Contest

      Automation Contest
    • Make it Glow Contest

      Make it Glow Contest



    4 years ago

    Hi, this is a great tutorial, thank you so much. I have utilised the code that relates only to the buttons (not the hat switch) but I get the error 'Bounce' is not a direct base of 'Bounce'. Does anyone know what this means? Thanks.


    4 years ago


    Go out and get a USB joystick with a stick full of buttons, think logitech's 20-30 button is $15-25, and take it apart paying close attention to the driver board (I would leave everything internal-wise connected).

    I grabbed a project box from radio shack and just mounted the usb board and wires inside. I used the x y and z axis for pitch trim, roll trim and yaw trim. I used the multitude of buttons to make a complete cesena lighting and radio control panel with flaps and gear.. also If you really know your stuff (basic electronics because up until now anyone can do what I've already mentioned), you can use LED and simple logic circuits to detect signals being sent from your control interface that could let you know if the landing gear is up for example.

    I also made one for war thunder to control my tank rangefinder and barrel angle adjustment


    5 years ago

    Glad you like it BielMB! I think there are three possibilities here. Either the code is wrong, there's a problem with the soldering or a problem with the switch. If you've used my code then it should be ok - it's worked for me on a couple of these mods. Either way you could add a few lines to output the current state to the console to see what's happening.

    There could be a soldering problem - use a magnifying glass to check for whiskers of solder or wire that might be shorting the terminals.

    The other possibility is the switch itself is faulty. If it were me I would check it with a multimeter (if you have one). You could also re-connect the wires backwards to see if the problem follows the wiring or the code.

    Good luck, and please let me know how you got in.



    5 years ago on Introduction

    Amazing work! Congratulation.

    I have one question. When you use the Toggle Switch ( ON-OFF-ON) as two button, let's say Button 2 and Button 3, when you turn this Toggle Switch in one of ON position, if you go to "WINDOWS > Control Panel > Devices and Printers > Game Controller > Arduino Micro > Properties", will it show Button 2 (or Button 3) as always pressed?


    Hi, cool Project!

    Im trying to make some rudder pedals using an arduino nano and while im waiting for parts to arrive im going over this and its really, really useful!

    I have 1 question about this, even after editing the HID and USBAPI files i get the error: " 'JoyState_t' does not name a type" when verifying my sketches.

    I also get this same error when i run the template sketch in the referenced blog (using the edited HID and USBAPI files provided):

    so im pretty confused about whats causing it.

    Im flying by the seat of my pants a little bit here as my C++ skills are pretty basic but any help would be much appreciated.


    Reply 5 years ago on Introduction

    Thanks for the positive comment. Tell you what, why don't you private message me with copies of the HID.cpp and USBAPI.h files as well as your current arduino sketcha and I'll take a look at it for you and see if I can help to figure it out.


    6 years ago on Introduction

    Doing things like this is awesome! I love using microcontrollers for things like this!


    Reply 6 years ago on Introduction

    I'll say! I only discovered Arduino at Christmas when I bought my kids a MiRobot ( and now thanks to sites like this I'm already building useful stuff :-)


    Reply 6 years ago on Introduction

    I love to hear things like that! Instructables is the best!