2. Arduino Joystick Emulation

2. Arduino Joystick Emulation
Photo by Steve DiMatteo / Unsplash

If you're interested in building a custom controller for a Flight Simulator—or any other game—starting with an Arduino Leonardo is a great choice. These microcontrollers natively support acting as an HID device and provide keyboard and mouse emulation, making them incredibly convenient for a project like this.

Design and Implementation

You'll need an additional library for emulating a game controller. I used the Joystick.h which is simple and straightforward to use. While there are plenty of examples available online, the basic idea looks something like this:

#include <Joystick.h>

// Create a default Joystick
// Joystick_ Joystick;

// If you want to customize it, set the number of buttons you want, max is 32, and whether or not you want the axis enabled
Joystick_ Joystick(JOYSTICK_DEFAULT_REPORT_ID, JOYSTICK_TYPE_GAMEPAD,
                   10,                    // Button Count
                   0,                     // Hat Switch Count
                   false, false, false,   // X and Y, but no Z Axis
                   false, false, false,   // No Rx, Ry, or Rz
                   false, false,          // No rudder or throttle
                   false, false, false);  // No accelerator, brake, or steering;


// Constant that maps the phyical pin to the joystick button.
const int pinToButtonMap = 3;

// Last state of the button
int lastButtonState[10] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };

void setup() {
  // Initialize Joystick Library
  Joystick.begin();

  // Initialize Button Pins
  for (int n = 0; n < 10; n++) {
    pinMode(n + pinToButtonMap, INPUT_PULLUP);

    // read the current state and assign it to the Joystick object. this is because if you have switches or rotary encoders, 
    // you want the initial state to be the side of the switch that is selected
    Joystick.setButton(n, !digitalRead(n + pinToButtonMap));
  }

  // send the current state so the game controller in Windows shows the actual state of each button
  Joystick.sendState();
}

void loop() {

  // do your analogReads/digitalReads of the pins you assigned and set the state again like before
  for (int index = 0; index < 10; index++) {
    int currentButtonState = !digitalRead(index + pinToButtonMap);
    if (currentButtonState != lastButtonState[index]) {
      Joystick.setButton(index, currentButtonState);
      lastButtonState[index] = currentButtonState;
    }
  }

  Joystick.sendState();
  delay(50);
}

For handling the analog axes, you'll want to use analogRead() combined with calls like Joystick.setXAxis(int32_t value) to set the axis position. However, keep in mind that the default range of 0-1023 might not align perfectly with the values you are getting from your analogRead() if you're using an Arduino model other than the Leonardo/Micro. See: https://www.arduino.cc/reference/en/language/functions/analog-io/analogread/

Once you've built and uploaded the Sketch to the Arduino, you can open the Game Controllers app in Windows. You'll see a simplified 10-button joystick instead of the default 32-button setup with all the axis controls.

Custom Controller Name

If you want to take it a step further, you can customize the device name as it appears in the Game Controllers app, which I honestly recommend you do, since by default they will show as Arduino Leonardo and if you have multiple connected, you won't know which one is which.

To do this, you'll need to modify the boards.txt file in the Arduino 1.x IDE, redefining the Leonardo/Micro as a new device with a different PID/VID and custom name. Keep in mind that I haven't been able to achieve this with the Arduino 2.x IDE, so these instructions are specific to the 1.x version:

  1. Open C:\Program Files (x86)\Arduino\hardware\arduino\avr\boards.txt in your favorite text editor
  2. Look for where leonardo.* properties are defined and make these changes
  3. Now save this and upload the sketch to your Arduino Leonardo. Note that after that, the name will change in Game Controllers and also in the Arduino IDE, so from now on you need to flash it like that.
  4. If you need to flash another Leonardo as a Leonardo, uncomment the original 3 lines for "Arduino Leonardo" usb_product and comment the ones you added and flash it.
leonardo.name=Arduino Leonardo
leonardo.vid.0=0x2341
leonardo.pid.0=0x0036
leonardo.vid.1=0x2341
leonardo.pid.1=0x8036
leonardo.vid.2=0x2A03
leonardo.pid.2=0x0036
leonardo.vid.3=0x2A03
leonardo.pid.3=0x8036

# add a new one here, these must be unique and cannot exist in the boards.txt
leonardo.vid.4=0x1234
leonardo.pid.4=0xABCD

# temporarily comment these to flash and put your own
#leonardo.build.vid=0x2341
#leonardo.build.pid=0x8036
#leonardo.build.usb_product="Arduino Leonardo"
leonardo.build.vid=0x1234
leonardo.build.pid=0xABCD
leonardo.build.usb_product="My Flight Sim Switch Box"

And that's it! Now, when you open Microsoft Flight Simulator, you'll see your new controller with your custom name. You can easily assign the buttons to the sim's inputs just like you would with any other gamepad or sim controller.