Introduction: $50 Linear Joystick (HOTAS) With Haptic Feedback for Flight Sim

The take aim is to create a fully functioning USB HOTAS* stick from low cost electronics parts.

I bought Elite Dangerous when IT was released for the Mac and realised very quickly I was going to have the floor wiped with Maine if I didn't have a decent joystick, the job is a really healthy joystick costs 100's of dollars or $450 in the example of this unmatched: http://arstechnica.com/gadgets/2014/09/flying-in-s... . Its same difficult for me to tell my wife should doesn't need another pair of shoes if I order a $450 stick for playing a "space game".

SPECS

Fully USB supercharged
Macintosh / PC / Linux Mixable

Programable Micro Controller
Analog X/Y Axis primary joystick
Analog X/Y Axis submarine sandwich controller
Analog Throttle capable of 1024 acceleration points and boost
Weapon Arming toggle switch
2 x Analog Trimming Knobs
4 x Programable Momentary Buttons
4 Vibration motors

*HOTAS Stands for Hands happening Throttle and Stick, so this is not a 'typical' HOTAS Control stick, by some definitions it would have to have a DPAD on the joystick controller, where my DPAD is controlled by the thumb of the throttle hand. Simply definitions aside this is an extremely capable game controller and infinitely customisable thanks to the on board programable little accountant.

Step 1: Parts List

$10 - Arduino Leonardo

$3 - Old Wii Nunchuck

$19 - Joystick with potentiometers - type used for CCTV systems or wheelchairs

$3 - ABS Enclosure

$2.5 Flip up weapon on/off switch

$4 - Sliding Potentiometer

$0.5 - 2 x100K Potentiometers

$0.5 - a few Dupont cable connectors

$2 - 4x mini vibration units

$2 - L289n Motor driver

$2 - 4x minibuttons

$2 - 'Carbon fibre' vinyl wrap

The hardware is low-level $50 including stamp to Australia.

It should be said I'm non picky about brands, the only genuine parts here are the Dupont cables, and I'm non even sure about them, this is how I managed to maintain the costs down.

Step 2: The Brain

The $10 Arduino Leonardo is the Einstein of the joystick - it has a few advantages over to more commonly known UNO model. One of which is that information technology can act directly as a USB mouse or Keyboard. Object lesson sketches are included with the Arduino software program that allow you to control a mouse x & y access and also a 'roll wheel' (which I connect to the throttle).

So taboo of the boxwood the Leonardo can verify 3 joystick axis* + give you access to the entire rooms of keyboard commands, pretty redeeming for 10 bucks.

Software

I also used Successive Bytes amazing Mac Controllermate software to read the USB events and render to a virtual keyboard joystick. this made IT such easier to set up center on points etc and fine-tune the control stick I would not in truth savor having to do this in the Arduino encipher - not 100% necessary but life is short-dated. I'm non sure of a PC Oregon Linux same to Controllermate, but there will be something.
You could fair-and-square build this, load the code in and IT would work, simply Controller mate makes it really easy to do things alike adjusting the left right balance, Oregon adding an automatic boost spark to the particle accelerator.

* There is a firmware mod for Arduino UNO's out there that allows ascertain for more than 3 axis of rotation - but I have not modded the firmware along this instructable.

Step 3: The Main Joystick

I purchased an industrial grade joystick (JH-D202X) for US $18.57 from ebay.
They rich person a few variations and they look really solid.

If you get a different joystick, stool sure you get one with potentiometers non microswitches - there are a few models available, they also have models with buttons on top.

At that place are 3 pins on some the x and y axes, the side of meat pins on apiece axes connect to 5v and GND on the Arduino.

The middle pins tie to the Analog PIN you volition use to interpret the data on the Arduino.

x axis vertebra rolls the craft left and right

y axis of rotation pitches the crafts nose up and down

Step 4: The Ze Restrainer (mini Stick)

To make this I butchered a cheap Wii Nunchuck - in essence wide-eyed it leading and removed the joystick.

It's basically the same (2 potentiometer joystick) as the principal joystick, but smaller- I soldered some dupont cables onto the 6 fiddly connectors, and affined the broadside pins to 5V and GND - and then the middle pins to the appropriate Arduino pin.

It's controlled with your nigh thumb or right thumb for lefties:

When flying it gives you left and right YAW and Vertical raised /dejected thrust

When in an interface it controls up, down, left, right

You can as wel configure IT to behave differently when in landing mode << scarcely ut this in the controller options in back.

x left presses the a cay.
x exact presses the d key
y up presses the r key
y down presses the f key

N.B. the Gape flight contain provided here is not analog, its sporting on/forth - this works great for me. Because of the limited axis supported by the Arduino, this is our single pick without modding the Arduino firmware.

Footstep 5: The Throttle

For a throttle I bought a $4 sliding pot from eBay, this is the kind of matter you see on a mixing desk. Again this is another pot, sol connect IT to another analog in on the Arduino. $4 is a minute very much for this, I'm sure you could get a cheaper one.

Z bloc increases and decreases the velocity

+ Pushing IT to the extreme big top causes the Tab to be ironed and a supercharge to trigger << this is genius, once you try this you can't live without it.

There are six pins (in order):

OTA > ignore this pin
VCC> ignore this pin
GND> ignore this flag

OTB > to Arduino Analog Pin
VCC > to 5v
GND > to Earth

Step 6: The Weapons

I requisite a weapon arming system (to deploy hard points) , and then one of these low cost "missile switches" seemed perfect.

But I also requisite to have the ability to trigger individual weapons on common, soh I purchased 4 red momentary pushbuttons, in the image above I have started soldering the cables.

Ill-trea 7: Tactual Feedback

The Arduino Leonardo has a lot of pins - soh it would exist impolitic resist the enticement to add vibration units to the joystick, these ones flush have soul adhesives backs.

Basically you connect the the constructive wire of the vibration units direct to the Arduino digital pins then when you pass a current though the Dupont compatible pin - it will start the shakes up. You can pulse and initiation the units every bit you wish.

Ane thing you can't do yet is to respond to events in the game - The like being hit or increasing. - you rear end only respond to your own input events. This tranquil leaves room for control feedback, events I added are:

1) Micro pulse all metre you hit a clit

2) Micro pulse at the set slope when main joystick hits an extremity

3) Two second gear big buzz fading out when you press velocity boost

4) One send off stuttering seethe when you deploy hard points or the landing appurtenance

Tone 8: The Enclosure

In the attached image I have mounted the Wii Nunchuck joystick, the 2 potentiometers and trained the holes and made the slot for the sliding potentiometer.

The musical theme is you use your port hand for the sliding pot and wii joystick and your right quarter round for the serious control stick.

It can embody used on a desk, surgery in your hand wish a giant controller.

Step 9: Adding the Controllers

In that first image I have mounted the sliding pot, the artillery armament on-off switch trade and the main joystick, the circular surround and screws are supplied with the joystick its easy to riding horse and hole doesn't motive to be exact.

Behind the back I have mounted the 4 custom buttons.

Measure 10: Gibe In spite of appearanc

Here you can view I have mounted the Arduino and joined up the potentiometers.

I have added an earth and dwell rail on the penetrate correctly to try and hold open information technology tidy.


Whats still lost in that image- 4 programable buttons & vibration motors (also the weapon arming is not connected dormie)

Step 11: Step 10 Final Touches

I take in curved the corner edges with my Dremel and usage a lowercase total of polymorph impressible to fill in any holes.

I also have added grooves for my fingers to wrap around if I am picking up the control stick and using IT like a handheld accountant.

The next step was to remove the control parts and wrap the whole thing in half resiny "atomic number 6 fibre" and reinstall the controller parts. I used a hair dryer to make the self adhesive plastic molding to the enclosure major.

Stair 12: Step 9 - the Code - Paste Into the Arduino IDE to See What Is Happening

/*

JoystickMouseControl

Controls the mouse from a joystick on an Arduino Da Vinci, Micro or Due. Uses a pushbutton to turn on on and off mouse control, and a second pushbutton to click the left mouse button

Hardware: * 2-bloc joystick connected to pins A0 and A1 * pushbuttons connected to pin D2 and D3

The mouse movement is e'er congeneric. This sketch reads two parallel inputs that range from 0 to 1023 (or less connected either end) and translates them into ranges of -6 to 6. The sketch assumes that the control stick resting values are close to the intervening of the range, only that they vary inside a threshold.

WARNING: When you function the Mouse.go out() command, the Arduino takes over your mouse! Make sure you have dominance before you use the command. This study includes a pushbutton to toggle switch the mouse control state, so you can deform off and on mouse control.

includes 15 Sept 2011 updated 28 Mar 2012 by Tom Igoe

this code is in the open domain

*

/ set immobilize numbers for switch, joystick axes, and LED: //const int switchPin = 2; // switch to bi off and on mouse control const int mouseButton = 3; // input pin for the black eye pushButton const int xAxis = A0; // joystick X axis vertebra const int yAxis = A1; // joystick Y axis const int zAxis = A2; // joystick z axis (upper)

// parameters for reading the joystick: int range = 127; // output signal range of X or Y movement int responseDelay = 5; // atomic number 75 sponse delay of the mouse, in multiple sclerosis int center = range / 2; // resting position value int weaponArm = 10;//trap for weapon arm int vibratorOne = 9;//PIN number for vibrator int trig1 = 12;//pin fire1 int trig2 = 11;//pin fire2

int shakeCountX1 = 1; // numeral of cycles for quickshake int shakeCountX2 = 1; // number of cycles for quickshake int shakeCountY1 = 1; // keep down of cycles for quickshake int shakeCountY2 = 1; // number of cycles for quickshake boolean mouseIsActive = false; // whether or not to control the mouse boolean toggleState; // hold current state of switch boolean lastToggleState; // adjudge high state to sense when switch is changed int trig1State = 0; int trig2State = 0; long toggleTimer = millis(); // // debounce timer

vitiate setup() {

// pinMode(trig1, INPUT_PULLUP); // pinMode(trig2, INPUT_PULLUP); pinMode(weaponArm, INPUT_PULLUP); pinMode(vibratorOne, OUTPUT);

Mouse.begin(); }

void cringle() { hold(10);

mouseIsActive = !mouseIsActive;

int xReading = readAxis(xAxis); int yReading = readAxis(yAxis); int sReading = readAxis(zAxis); int DVertReading = readAxis(A3); int DHorizReading = readAxis(A4); Mouse.go down(xReading, yReading, sReading); // temporailty disabled until joystick arrives // Sneak out.move(0, 0, sReading); //delete this one when joystick comes

// check(100); trig1State = digitalRead(trig1); trig2State = digitalRead(trig2);

//CODE TO IDENTIFY DPAD

if (DVertReading > 20) { Keyboard.spell('r'); //Equal Significant TO HIDE AND SHOW }

if (DVertReading < -30) { Keyboard.write('f'); //SAME KEY TO Obliterate AND SHOW }

if (DHorizReading > 20) { Keyboard.write('a'); //SAME KEY TO HIDE AND SHOW } if (DHorizReading < -30) { Keyboard.indite('d'); //Equal KEY TO HIDE AND SHOW }

//CODE TO QUICK VIBRATE

if (xReading > 61) { int sens; sens = quickShakeX1();} else{ shakeCountX1 = 1; }

if (xReading < -61) { int grass; sens = quickShakeX2();} else{ shakeCountX2 = 1; }

if (yReading > 61) { int sens; pot = quickShakeY1();} else{ shakeCountY1 = 1; }

if (yReading < -61) { int sess; sens = quickShakeY2();} else{ shakeCountY2 = 1; }

///CHECK FOR WEAPON ARMING

toggleState = digitalRead(weaponArm);

if (millis() - toggleTimer > 100){ // debounce switch 100ms timer if (toggleState && !lastToggleState) { // if switching is on but was previously forth lastToggleState = true; // switch is today on toggleTimer = millis(); // reset timer Keyboard.write('u'); }

if (!toggleState && lastToggleState){ // if interchange is off but was antecedently on lastToggleState = false; // substitution is forthwith off toggleTimer = millis(); // reset timekeeper Keyboard.compose('u'); //SAME KEY TO HIDE AND SHOW } }

// read the shiner button and click or non click: // if the mouse button is pressed: if (digitalRead(mouseButton) == Alto) { // if the pussyfoot is not pressed, press it: if (!Mouse.isPressed(MOUSE_LEFT)) { Mouse.press(MOUSE_LEFT); } } // else the mouse push button is non pressed: else { // if the black eye is pressed, release it: if (Computer mouse.isPressed(MOUSE_LEFT)) { Sneak out.release(MOUSE_LEFT); } }

delay(responseDelay); }

/* reads an Axis (0 Oregon 1 for x OR y) and scales the analog input scope to a range from 0 to */

int readAxis(int thisAxis) { // read the analog input: int reading = analogRead(thisAxis);

// map the reading from the analog input signal chain to the output signal range: reading = correspondenc(reading, 0, 1023, 0, range);

// if the end product reading is outside from the // rest position brink, employ information technology: int length = reading material - pore;

// return the distance for this axis: return distance; }

int quickShakeX1(){ if (shakeCountX1 > 0) { digitalWrite(13, HIGH);// Show led to see if motor would run digitalWrite(vibratorOne, HIGH); // turn the LED on (Heights is the potential level) delay (200); digitalWrite(13, LOW);// Show led to see if motor would run digitalWrite(vibratorOne, LOW); // turn the LED on (HIGH is the voltage level) shakeCountX1 = shakeCountX1 - 1; }

}

int quickShakeX2(){ if (shakeCountX2 > 0) { digitalWrite(13, HIGH);// Show led to see if motor would feed digitalWrite(vibratorOne, Piercing); // bend the LED on (HIGH is the voltage level) delay (200); digitalWrite(13, Contemptible);// Show LED to see if motor would run digitalWrite(vibratorOne, Low-down); // turn the LED on (HIGH is the voltage level) shakeCountX2 = shakeCountX2 - 1; }

}

int quickShakeY1(){ if (shakeCountY1 > 0) { digitalWrite(13, HIGH);// Show light-emitting diode to see if causative would run digitalWrite(vibratorOne, HIGH); // turn the LED connected (Richly is the voltage tier) delay (200); digitalWrite(13, LOW);// Show led to see if centrifugal would prevail digitalWrite(vibratorOne, LOW); // turn the LED on (HIGH is the voltage level) shakeCountY1 = shakeCountY1 - 1; }

}

int quickShakeY2(){ if (shakeCountY2 > 0) { digitalWrite(13, HIGH);// Show led to examine if drive would run digitalWrite(vibratorOne, HIGH); // bend the LED happening (HIGH is the voltage level) delay (200); digitalWrite(13, LOW);// Show led to visit if motorial would run digitalWrite(vibratorOne, LOW); // turn the LED connected (HIGH is the voltage level) shakeCountY2 = shakeCountY2 - 1; }

}

Whole tone 13: Configuring the Axis and Throttle (not Essential)

In ControllerMate I created a Virtual Joystick (Toms Home Joystick)

You throne see in the image how I am connecting:

Arduino Sneak out output signal > Axis Calibration Module > Virtual Joystick

Elite group dangerous thinks the Virtual Control stick is a genuine joystick

Notice the branch at then bottom of the throttle - this is picking up when the throttle is maxed out and presses the Tab to cause the boost.

Whole tone 14: Configuring the Hero sandwich Joystick (not Essential)

You can skip this step if you don't let ControllerMate.

In controller mate it detects the key presses from the Arduino cypher and translates them ........to the same keypresses. The reasonableness I bother with controllermate is that we can add the dwell function between the two, this softens the output to Elite Suicidal and leads to a drum sander ride.

Equal the First to Share

Recommendations

  • Anything Goes Contest 2022

    Anything Goes Contest 2022