You're reading ...
Arduino, Hardware, Software

A super smooth Joystick and Throttle part 3: wiring and software (with download)

This is part 3 of 3 on building your own ‘super smooth’ joystick and throttle that can be used with flight simulators or racing games.

  1. The final result and the components needed.
  2. The woodwork, a Fusion 360 model and drawings with dimensions.
  3. The wiring and the software, with a download link of course.

An Arduino Leonardo is the heart of the system. It reads out the push buttons, switches and potentiometers and it sends the data via USB to the PC. Beware … a standard Arduino UNO won’t work … a Leonardo has a different kind of Atmel chip on board, one that connects to USB in a way that makes it possible to function as a HID (Human Interface Device), which is needed for this application. A Leonardo can be acquired here.

The wiring.

The list below shows all connections to be made.

All pushbuttons are connected to GND at one side. The three way toggle switch has GND in the middle. The potentiometers are connected to 5V and GND on the outsides and the slider / rotator go to pins A0-A3. The rotary encoder also needs 5V and GND, its noted on the connector which pin is witch.

Leonardo_ICSPA0 joystick X
A1 joystick Y
A2 joystick Z
A3 throttle slider pot
A4 rotary encoder CLK
A5 rotary encoder DATA
0 do not use, it is needed for USB
1 do not use, it is needed for USB
2 pushbutton 1
3 pushbutton 2
4 pushbutton 3
5 pushbutton 4
6 pushbutton 5
7 pushbutton 6 (on top of the joystick)
8 pushbutton 7
9 pushbutton 8
10 pushbutton 9
11 pushbutton 10
12 pushbutton 11 (rotary encoder)
13 LED
MISO three way toggle switch up
SCK three way toggle switch down

The LEDs at the bottom of the Joyctick and Throttle are connected to 5V and GND, with the resistor in between of course. Take care the long leg goes to the 5V. They show there is power. The third LED long leg = pin 13 … it lights when a button is pressed.

blog_throt_img_bottom   blog_joy_img_bottom

My joystick and throttle do not look very tidy … but all wires are firmly connected. I always do a ‘pull check’ and a resistance measurement immediately after soldering every wire to assure all is OK before I go on.

The software (download link here).

First of all many thanks go to M. Heironimus for making his Arduino USB HID library publicly available. It is thanks to this library that the software effort to make things work was minimal.

The joystick X,Y,Z axis are configured as 10 bits, running from -511 to +511, with an S-curve for very fine control around the middle.

The throttle linear pot is configured as 10 bits, running from 0 to 1023.

With the .ino file with ‘analog’ in the name the rotary encoder is configured as an analog axis with an 8 bits range of -127 to +127. Pressing its switch will set it to zero. To avoid having to turn it many times to get to the limits, during calibration in FSX or X-plane you can press the encoder switch and button 9 or 10 simultaneously to quickly go to limit values. I use it for pitch trim up / down, it gives a nicer feel than using push buttons for pitch trim.

With the .ino file with ‘pulses’ in the name the encoder is used to simulate two push buttons, which can for instance be used as rudder- or elevator trim. The joystick button numbers used are 13 and 14.

Follow these steps to upload the software to your Arduino Leonardo (also see the video below):

  1. Visit the Arduino website and follow instructions to install the Arduino IDE software on your PC.
  2. Start up the IDE and go to File > Preferences to tell where you want to store your ‘sketches’ (‘sketches’ is the Arduino name for programs). Close the IDE again.
  3. Download the joystick software here and unzip it.
  4. Copy or Move the ‘Joystick’ folder to your Arduino/libraries folder. (Depends where you installed it … default is C:\Program Files (x86)\Arduino)
  5. Copy or Move the RBO_Joystick folder to your sketches folder.
  6. Restart the IDE and open the RBO_Joystick.ino sketch.
  7. Upload the sketch (the arrow icon at the top left).

Control_PanelConfiguration

If needed, disconnect and reconnect your USB cable to the PC … the PC should now recognize the Leonardo as a HID. It can be tested via the Windows Control Panel > Printers and Devices.

If your joystick is recognized it is now time to start FSX or X-plane and go to the ‘settings’ to configure your joystick and throttle axis and buttons.

The video shows the steps to take to upload the software to your Arduino Leonardo.

About RudyB

Hobbyist

Discussion

37 thoughts on “A super smooth Joystick and Throttle part 3: wiring and software (with download)

  1. Hi!
    I come out with an idea how to solve the “not going back OFF” problem with xplane and switches.
    I just add another virtual switch which is not connected at all to board and then I add code:
    if (switch_state[i]) {
    Joystick.pressButton(i);
    Joystick.releaseButton(i+14);
    }

    else {
    Joystick.pressButton(i+14);
    Joystick.releaseButton(i);
    }
    In my case it is 14, you may have it different.
    Remebmer to change also “MAX_SWITCHES” and “switch_pin”.

    Like

    Posted by Filip | April 28, 2022, 20:42
  2. Hi there, I’m using this code with xplane11 and I have some trouble with my switchs (MTS-101 ON-OFF in real, lights in xplane). I wanted to reduce pins occupancy in my Arduino so I have only ON and GND connected to my switches. The problem is, when I toggle the switch on, something also toggles on in xplane, but if I toggle it off, it stays in place and I have to move my switch up one more times. I tried to code with some delays, else, if etc. even tried to change wirings but I’m beginner so its really hard for me. Maybe something to toggle for 0.1s when the button is turning off… Any advices? I will be very grateful 🙂

    Like

    Posted by Filip | April 28, 2022, 19:28
  3. I followed the link to Alliexpress for the 10K slider pot and the page had 5 shown which all seemed the same. Not sure which to go for. I did try this project about a year or so ago after ordering what looked like an ideantical slider pot on eBay (UK) which turned out to be a 10K log pot which was no good for use as a throttle. I couldn’t find a linear pot at the time. Looking at all of the Alliexpress ones and can’t see mentioned if they are a log or lionear pot.

    Liked by 1 person

    Posted by Rob | April 16, 2021, 12:28
    • The pots on Ali are all logarithmic, or have an S curve. You’ll have to get used to that with the throttle, which is doable … it’s actually quite nice to have fine control in the lower area for finer tuning with landings. 🙂 It’ll be hard to find a linear pot on Ali, or they mention they are, but then they are not, I’ve had a money back situation like that, I now have the pots and the money. 🙂 If you really want linear you’ll have to go to a more specialized shop, and they’ll probably be around $10.

      Like

      Posted by RudyB | April 17, 2021, 07:47
      • Thanks Rudy. I must admiy I never thought of the advantages of using a log slider pot. Most of the ones available are probably designed around sound use with things like mixing desks. I will certainly give one a try.

        Like

        Posted by Rob | April 17, 2021, 10:52
    • Try desktopaviator.com. They have slider pots I use for throttle, prop, mixture

      Like

      Posted by TB2 | May 28, 2021, 03:50
  4. This is awesome, thanks for sharing it, with the rest of your stuff !!
    i am trying to try my hand but i am a beginner with arduino. escapes me how you connected the slider pot on A3, because a 2 pin output. I see in the photos that you worked on both outputs, so I don’t understand.
    would you be kind enough to give me a tip?

    Like

    Posted by Nano GGFamily | April 15, 2021, 16:19
  5. Hello.
    I am just on my way to build my own steering tiller for an A320 with your example.
    One idea for improvement:
    You used for your S-curve three linear zones within 0 to 1023.
    With the following formula you could make it a real smooth s-curve:
    OUTPUT=((0,4*(2*INPUT/1023-1)+(0,6)*(2*INPUT/1023-1)^3)/(2/1023))+511,5
    Ole

    Like

    Posted by Ole | March 19, 2021, 12:58
  6. Rudy
    can you help me (in Dutch)
    im stuck with my project need diagram of wiring checked en i need the sketchcode for it only buttons ,toggles and 4 sliders to work i just cant program myself i have no idea how

    https://drive.google.com/file/d/1JVds3JE-t4uZ1wcBstRQxXiE2UzsjBz0/view?usp=sharing

    can you hel me with it

    there are 6 (on/Off) toggles
    3 (On/Off/On) momentary toggles
    5 (On/Off/On) 2 way turn knobs momentary
    5 (On/Off) push buttons Momentary
    4 Potentiometers Sliders

    I realy could need some help
    being at it for 2 monts now (;

    regards John

    PS. you can answer in dutch

    Like

    Posted by John Post | December 28, 2020, 05:19
  7. I’m using this to create a custom yoke for X-Plane. I’ve loaded the code to an Arduino Leonardo and have three potentiometers which I’m hooking up to A0 thru A3.

    X-Plane recognizes the Leonardo and accepts input for the calibration process, however I’m seeing what looks like crosstalk.

    If I connect a pot to A0 and rotate it I see output changes to the outputs to A0, A1, A2 and A5 but not A4.

    I’ve added decoupling caps to the Vcc and AREF, thinking it might be electrical, but that didn’t help.

    Any idea what bug I may be chasing?

    Like

    Posted by Dexter Francis | October 21, 2020, 21:58
  8. Great project! Built it in a couple of plastic boxes instead of the wooden craftmanship needed and now using it daily with MSFS. Much better feeling that any flight joystick I have used before, including X52 and various other Saiteks.

    One thing: the button simulation of the rotary encoder in RBO_Joystick_Encoder_Pulses did not work for me when using Arduino IDE 1.8.13 on Windows. The buttons registered as pressed but never released.
    The issue was that the variable rotary in read_rotary function had to be initialized so adding “rotary = 0;” before the enc_clk = … line fixes this! Might be something in newer versions of the compiler that needs this?

    Next enhancement is to use a PCF8574 to be able to add more buttons 🙂 anybody done any mod of this to add more buttons via a 8574 or similar?

    Like

    Posted by Copson | October 17, 2020, 15:34
  9. Hi, again, anyway I can combine the code below for Force/Feedback/Vibration with your code?
    https://github.com/YukMingLaw/ArduinoJoystickWithFFBLibrary

    Like

    Posted by Eric | September 8, 2020, 16:17
  10. Hello,
    Thank you for sharing this.
    The Axis Y and X responding only at the very end, any way to fix this?
    By the way, is this is the latest version?
    last question, any way to add HAT and maybe force back?
    Regards

    Like

    Posted by Eric | August 16, 2020, 15:57
    • Is your potentiometer maybe of the logarithmic type in stead of linear? You could check by loading a sketch that does nothing more than serial print the analog value rad from the input pin.
      Yes, the download is the latest version, although I created another version where the digital encoder is not translated to an analog value but rather sends out pulses to be used for instance as pitch trim.
      HAT switches can of course be added as push buttons, provided you have enough input pins. you may have to start using a digital input matrix, such that with 4+4 inputs you have 16 switches.

      Liked by 1 person

      Posted by RudyB | August 24, 2020, 09:02
      • Thank you so much for the quick reply.
        I am about to finish a crazy project where I used very old Joystick (Loigitec Wingman Force) from the 90s and gave a new life with you help of cours.
        You can see the picture and great responses at Reddit:

        Still looking for a solution to use the Force Back with the motors ;-(

        Cheers!

        Like

        Posted by Eric | September 5, 2020, 15:35
      • Hello, I am using a pro micro, so without A4 and A5 I could not use your code directly. I made some alterations to fit my board and setup, everything works great, except the encoder. I cannot get it to change the value of the assigned axis, it just stays in the middle. I will post my code below. Do you happen to have the other version using pulses for the pitch trim available?

        // CONFIG
        #define MAX_SWITCHES 7 // the number of switches
        byte switch_pin[MAX_SWITCHES] = {2,3,5,7,14,15,16}; // digital input pins
        #define DEBOUNCE_TIME 5 // ms delay before the push button changes state
        #define MAX_ANALOG 4 // the number of analog inputs
        byte analog_pin[MAX_ANALOG] = {A0,A1,A2,A3}; // analog input pins Rx,Ry,RUDDER,THROTTLE
        #define ENC_CLK_PIN A8 // rotary encoder CLK input
        #define ENC_DAT_PIN A9 // rotary encoder DATA input
        #define ENC_MAX 127 // max value of the rotary encoder
        #define ENC_MIN -127 // min value of the rotary encoder
        #define ENC_ZERO 0 // value to jump to after pressing encoder switch
        // END CONFIG

        // DECLARATIONS
        #include “Joystick.h”
        Joystick_ Joystick(JOYSTICK_DEFAULT_REPORT_ID,JOYSTICK_TYPE_JOYSTICK, MAX_SWITCHES, 0, false, false, true, true, true, false, true, true, false, false, false);
        byte reading, clk, clk_old;
        byte switch_state[MAX_SWITCHES];
        byte switch_state_old[MAX_SWITCHES];
        int analog_value[MAX_ANALOG+1]; // +1 for the rotary encoder value
        unsigned long debounce_time[MAX_SWITCHES+1]; // +1 for CLK of rotary encoder
        // END DECLARATIONS

        // FUNCTIONS
        int read_rotary_encoder() {
        clk = digitalRead(ENC_CLK_PIN);
        if (clk == clk_old) debounce_time[MAX_SWITCHES] = millis() + (unsigned long)DEBOUNCE_TIME;
        else if (millis() > debounce_time[MAX_SWITCHES]) { // clk has changed and is stable long enough
        clk_old = clk;
        if (clk) {if (digitalRead(ENC_DAT_PIN)) return 1; else return -1;}
        }
        return 0;
        }
        // END FUNCTIONS

        // SETUP
        void setup() {
        for (byte i=0; i<MAX_SWITCHES; i++) pinMode(switch_pin[i],INPUT_PULLUP);
        pinMode(ENC_CLK_PIN,INPUT_PULLUP);
        pinMode(ENC_DAT_PIN,INPUT_PULLUP);
        pinMode(13,OUTPUT); // on board LED
        digitalWrite(13,0);
        Joystick.begin(false);
        Joystick.setRxAxisRange(0, 1023);
        Joystick.setRyAxisRange(0, 1023);
        Joystick.setRudderRange(0, 1023);
        Joystick.setZAxisRange(ENC_MIN, ENC_MAX);
        Joystick.setThrottleRange(0, 1023);
        } // END SETUP

        // LOOP
        void loop() {
        for (byte i=0; i debounce_time[i]) switch_state[i] = reading;
        if (switch_state[i] != switch_state_old[i]) { // debounced button has changed state
        // this code is executed once after change of state
        digitalWrite(13,switch_state[i]);
        if (switch_state[i]) Joystick.pressButton(i); else Joystick.releaseButton(i);
        if (i==2) analog_value[A8] = ENC_ZERO;
        switch_state_old[i] = switch_state[i]; // store new state such that the above gets done only once
        }
        } //END read the switches

        for (byte i=0; i<MAX_ANALOG; i++) { // read analog inputs
        analog_value[i] = analogRead(analog_pin[i]);
        if (analog_value[i] < 256) analog_value[i] = analog_value[i] * 1.5;
        else if (analog_value[i] < 768) analog_value[i] = 256 + analog_value[i] / 2;
        else analog_value[i] = 640 + (analog_value[i] – 768) * 1.5;
        switch(i) {
        case 0:
        Joystick.setRxAxis(analog_value[0]);
        break;
        case 1:
        Joystick.setRyAxis(analog_value[1]);
        break;
        case 2:
        Joystick.setRudder(analog_value[2]);
        break;
        case 3:
        Joystick.setThrottle(analog_value[3]);
        break;
        }
        }
        analog_value[8] = analog_value[8] + read_rotary_encoder();
        Joystick.setZAxis(analog_value[8]);
        // use 2 switches simultaneously to quickly calibrate the rotary encoder
        if (!digitalRead(switch_pin[3]) && !digitalRead(switch_pin[2])) analog_value[8] = ENC_MAX;
        if (!digitalRead(switch_pin[5]) && !digitalRead(switch_pin[2])) analog_value[8] = ENC_MIN;
        Joystick.sendState();
        delay(10);
        } // END LOOP

        Like

        Posted by Clinton E Melson | September 8, 2020, 08:08
      • I’ll send you an email with the pulse code. Ruud.

        Like

        Posted by RudyB | September 8, 2020, 08:14
  11. HI
    is the code correct without rotary encoder with 12 botton?

    // Ruud Boer, June 2018
    // Joystick with Arduino Leonardo

    // CONFIG
    #define MAX_SWITCHES 13 // the number of switches
    byte switch_pin[MAX_SWITCHES] = {2,3,4,5,6,7,8,9,10,16,14,15}; // digital input pins
    #define DEBOUNCE_TIME 5 // ms delay before the push button changes state
    #define MAX_ANALOG 4 // the number of analog inputs
    byte analog_pin[MAX_ANALOG] = {A0,A1,A2,A3}; // analog input pins X,Y,Z,THROT
    #define ENC_CLK_PIN A4 // rotary encoder CLK input
    #define ENC_DAT_PIN A5 // rotary encoder DATA input
    #define ENC_MAX 127 // max value of the rotary encoder
    #define ENC_MIN -127 // min value of the rotary encoder
    #define ENC_ZERO 0 // value to jump to after pressing encoder switch
    // END CONFIG

    // DECLARATIONS
    #include “Joystick.h”
    Joystick_ Joystick(JOYSTICK_DEFAULT_REPORT_ID,JOYSTICK_TYPE_JOYSTICK, MAX_SWITCHES, 0, true, true, true, false, false, false, true, true, false, false, false);
    byte reading, clk, clk_old;
    byte switch_state[MAX_SWITCHES];
    byte switch_state_old[MAX_SWITCHES];
    int analog_value[MAX_ANALOG+1]; // +1 for the rotary encoder value
    unsigned long debounce_time[MAX_SWITCHES+1]; // +1 for CLK of rotary encoder
    // END DECLARATIONS

    // SETUP
    void setup() {
    Joystick.begin(false);
    Joystick.setXAxisRange(-511, 511);
    Joystick.setYAxisRange(-511, 511);
    Joystick.setZAxisRange(-511, 511);
    Joystick.setRudderRange(ENC_MIN, ENC_MAX);
    Joystick.setThrottleRange(0, 1023);
    } // END SETUP

    // LOOP
    void loop() {
    for (byte i=0; i debounce_time[i]) switch_state[i] = reading;
    if (switch_state[i] != switch_state_old[i]) { // debounced button has changed state
    // this code is executed once after change of state
    digitalWrite(13,switch_state[i]);
    if (switch_state[i]) Joystick.pressButton(i); else Joystick.releaseButton(i);
    if (i==10) analog_value[4] = ENC_ZERO;
    switch_state_old[i] = switch_state[i]; // store new state such that the above gets done only once
    }
    } //END read the switches

    for (byte i=0; i<MAX_ANALOG; i++) { // read analog inputs
    analog_value[i] = analogRead(analog_pin[i]);
    if (analog_value[i] < 256) analog_value[i] = analog_value[i] * 1.5;
    else if (analog_value[i] < 768) analog_value[i] = 256 + analog_value[i] / 2;
    else analog_value[i] = 640 + (analog_value[i] – 768) * 1.5;
    switch(i) {
    case 0:
    Joystick.setXAxis(511 – analog_value[0]);
    break;
    case 1:
    Joystick.setYAxis(511 – analog_value[1]);
    break;
    case 2:
    Joystick.setZAxis(511 – analog_value[2]);
    break;
    case 3:
    Joystick.setThrottle(analog_value[3]);
    break;
    }
    }
    Joystick.setRudder(analog_value[4]);
    // use 2 switches simultaneously to quickly calibrate the rotary encoder
    Joystick.sendState();
    delay(10);
    } // END LOOP

    Like

    Posted by Claudio | May 10, 2020, 18:15
  12. A very interesting and useful project. I am wanting to make a simple throttle only for a space sim (Elite Dangerous) using a 10 slider pot. I have your sketch working on a Leonardo but a couple of questions. As I am using only 1 analogue input and possibly 1 digital input can I still use the sketch as is and just ignore the random noise on the other inputs or do I need to edit the sketch for only the inputs I am using.

    I am hoping to use a Pro Micro Arduino in the finished project as the Leonardo is too big for this purpose, it’s on a breadboard at the moment. I believe the Pro Micro is Leonardo based so can you envisage any probelms.

    Like

    Posted by Rob | October 29, 2019, 12:42

Trackbacks/Pingbacks

  1. Pingback: PC Joystick, USB Game Controller with Vibration Function and Throt – Coolest ele Devices - October 9, 2021

  2. Pingback: Throttle issues – broken board? – Arduino Apprentices - December 26, 2020

Leave a reply to Rob Cancel reply

Archive of all posts