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.
- The final result and the components needed.
- The woodwork, a Fusion 360 model and drawings with dimensions.
- 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.
A0 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.
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):
- Visit the Arduino website and follow instructions to install the Arduino IDE software on your PC.
- 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.
- Download the joystick software here and unzip it.
- Copy or Move the ‘Joystick’ folder to your Arduino/libraries folder. (Depends where you installed it … default is C:\Program Files (x86)\Arduino)
- Copy or Move the RBO_Joystick folder to your sketches folder.
- Restart the IDE and open the RBO_Joystick.ino sketch.
- Upload the sketch (the arrow icon at the top left).
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.
—
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”.
LikeLike
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 🙂
LikeLike
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.
LikeLiked by 1 person
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.
LikeLike
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.
LikeLike
Try desktopaviator.com. They have slider pots I use for throttle, prop, mixture
LikeLike
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?
LikeLike
Hi. On the picture of the slider pot the red and brown wores are 5V and GND, with thoise the pot is used as a simpple means of looping through. The black wire on the picture is the signal wire that goes to the Arduino.
LikeLiked by 1 person
then OTA on Arudino, and OTB on GND. Thank you so much!
LikeLike
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
LikeLike
Great idea …
LikeLike
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
LikeLike
Welke Arduino gebruik je en op welke pinnen heb je alles aangesloten … kan je daar een lijsje van sturen?
LikeLike
ik heb de Arduino MEga besteld maar daar werkt de joystick code niet dus nu de Leonardo maar bestellen
Dit is mijn Wire diagram (in PSD) elke laag apart te openen
en mijn Code die voor de leo geen errors geeft maar voor ik de LEO bestel wil ik graag checken of fit ook mogelijk is op een MEGA
De Wire Diagram voor de LEO
https://drive.google.com/file/d/1eE9CmMQumezd8bw-TZDhM0r4Jux7gFRu/view?usp=sharing
De LEO code
https://drive.google.com/file/d/1sjpP-ZT4OnwDDvgFGEqu15-SPWaGvNTn/view?usp=sharing
Maar wilde dit gebruiken op de MEGA
https://drive.google.com/file/d/1lvnhPusRKkGKbO_LREa2b-AgLgcFn3rk/view?usp=sharing
indeling en functie van de knoppen
https://drive.google.com/file/d/1W762-HygamjlzgiDLYDTRNs2Cj3Kd3wX/view?usp=sharing
Groet John
LikeLike
Voor zover ik weet werkt het niet met een Mega. De Leonardo heeft een andere USB interface die zich gedraagt als een HID interface naar de computer. Je zult een paar digitale inputs minder kunnen gebruiken dan op je lijstje staan. Ik moet even naar de code kijken welke inputs gebruikt kunnen worden, dan kan niet eerder dan eind van de week.
LikeLike
Komt goed ik zie het wel tegemoed
LikeLike
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?
LikeLike
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?
LikeLike
Hi, again, anyway I can combine the code below for Force/Feedback/Vibration with your code?
https://github.com/YukMingLaw/ArduinoJoystickWithFFBLibrary
LikeLike
I have no idea … only onme way to find out … just try.
LikeLike
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
LikeLike
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.
LikeLiked by 1 person
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!
LikeLike
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
LikeLike
I’ll send you an email with the pulse code. Ruud.
LikeLike
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
LikeLike
I have no time at the moment to check. Why not just try it?
LikeLike
programming do not understand anything
I don’t want to make mistakes
LikeLike
Just try it, nothing can get broken, worst case it just does not work. Else just try the original code first, that should work, it does here with me.
LikeLike
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.
LikeLike
The pro micro is USB HID compatible, so yes, that should work. Have a look at the documentation here how to configure analog axis:
https://github.com/MHeironimus/ArduinoJoystickLibrary
LikeLike