Thrustmaster T.Flight Hotas X Teensy MCU mod (USB Flight Joystick/Throttle)

DogP

Well-known member
I finished my Teensy project I've been working on for the last week, so I figured I'd post it here to show it off, and hopefully someone will find at least part of it useful. It's a replacement controller for a nice USB joystick with a flawed MCU. The source code and necessary files are all in the zip file attached to this post.

Intro:
The Thrustmaster T.Flight Hotas X is a budget HOTAS (Hands On Throttle-And-Stick) controller. While a good value for the money, there are some shortcomings. For me, the biggest one was the large dead zone in the middle of the axes. This made control choppy and inconsistent crossing the axes, and made it very difficult to aim near the center (where you're typically aiming). While you can install software to allow gain adjustment to give more movement near the center, the underlying problem can't be fixed in software, since the joystick controller simply tells the computer that it's _at_ 0 anytime it's _near_ 0 (approximately the center 17%!).

hotasx.jpg

Thrustmaster has recently released a HOTAS called the Hotas 4, which looks nearly identical, but has a new controller board that greatly improved the deadzone... so I'd recommend purchasing that, even though it's about $30 more (this mod should work similarly on the Hotas 4, though not really necessary). In my case, I already had the Hotas X, so rather than let it sit unused, I started working on this mod.

Moving forward:
I decided that I should replace the stock controller with my own microcontroller... and from a quick search, found that the Teensy would be a very good match, with lots of analog and digital pins. I already had a Teensy 3.1 that I bought as an add-on to an OSH Park PCB order, and within a couple hours, I had my first prototype up and running. EASY!

I needed to make changes from the available joystick examples to make it exactly how I wanted, so after a few frustrating hours of figuring out the details of USB descriptors, I got it to show up as an 8-axis, 32-button + hat joystick. I tweaked the back-end code to actually support those, and then made the main Arduino sketch, which actually set up and read the hardware, and reported the proper values.

Hardware:
Making this work requires removing the wires from the original controller board and connecting them to the corresponding pins on the Teensy. I used a Teensy 3.1, though it should work on a 3.0, 3.1, or 3.2. You can see the original wire connections for the Hotas X in hotasx_wires.jpg , and described with wire colors in hotasx_wires.txt (you should verify that your wire colors are the same). Then connect the wires to the corresponding Teensy pins referenced in teensy_hotasx.pdf .

hotasx_wires.jpg

hotasx_board.jpg

You also need to connect your GND wires to the AGND (analog ground) pin on the Teensy, as they're the GND of the analog pots, and connect the 3.3V wires to the Teensy's 3.3V pins. As there are a lot of wires, and not a lot of holes on the Teensy, and connected all of my GND wires together and 3.3V wires together, then connected them to the Teensy with short jumper wires (so I only needed to put a single wire into the Teensy's AGND and 3.3V holes).

If you want to use the buttons, switch, and LED soldered to the original PCB, you need to cut the traces going to them (so the original unpowered controller doesn't interfere), and solder a wire from them to the Teensy (shown in orig_pcb_wires.jpg ). You also need to connect GND between the Teensy and the original board. The LED on the original board is wired with the series resistor connected to 3.3V, so I rotated the LED 180 degrees, cut the connection to 3.3V, jumpered it to GND, and connected the other side to the Teensy I/O (so I could reuse the original series resistor and not have to connect 3.3V to the board).

orig_pcb_wires.jpg

You also need to connect the USB cable. I wanted to reuse the original cable, but needed a little bit more length inside the case, so I slid the rubber boot up the cable a bit, and soldered the USB cables to the pads on the bottom of the Teensy. Sliding the rubber boot isn't easy, as it's glued to the cable... so it took a lot of effort to break it free. The normal way would be to just use your own Micro USB cable plugged into the Teensy, and fed through the USB cable hole in the case.

Finally, you need to find a place for the board inside the joystick There's plenty of room, so I picked a spot along the curved side with the tail ( teensy_inside.jpg ), as the cables seemed to naturally reach easily there. For now, it's just sitting in there. With all the wires, it's not going anywhere and is well insulated from any metal... but once I'm done for good I'll probably just zip tie it to a screw post.

teensy_bottom.jpg

teensy_inside.jpg

Unrelated to the Teensy mod, but I also didn't like the large detent in the throttle at 50%, so I opened it up, used a Dremel to grind off the bump on the throttle, used very course sandpaper to rough up both sides (to make sure it had some friction to stay in place), then used Nyogel to re-lubricate it ( throttle_center_mod.jpg ).

throttle_inside.jpg

throttle_center_mod.jpg

Software:
Once everything is wired up, you need to load the code to the Teensy. There were several files changed from the default Teensy code. Under the Arduino hardware\teensy\avr\cores\teensy3 directory, you need to put the .c, .cpp, and .h files. Under the Arduino hardware\teensy\avr directory, you need the boards.txt file. You should back up your original files as my files may affect compatibility with other existing code.

Once those files are in place, load HotasX.ino (thanks to Paul Stoffregen for the code I based it off of!), and under Tools, select the Teensy board you're using and USB type of HotasX. Then click upload and it'll send the program to the joystick, and it should show up on your computer as an 8-axis, 32-button + hat joystick.

Future:
While this is fully working, there is room for improvement/expansion in the future, which I expect to do once I put some time into using this and determining my needs. I have code to drive the LEDs, but I don't currently use them for anything. I expect that I'll use them with future button mappings to show the user what the current mode is (i.e. Button 5 is mapped to 5 when the home LED is off, but 18 when in Green mode and 24 when in Red mode)... or as feedback to special modes, such as blinking the Preset LED during recalibration, setting a dead zone, etc.

There are also extra inputs that I've both assigned and not yet assigned. I have 3 more axes for Rudders and Toe Brakes. These are enabled when the pedal enable pin is grounded (so they can be added/removed without changing code), but without them being connected, they're reported as fixed values. There are also misc. I/O pins that can be used for more switches, buttons, talking to external hardware, etc.

Lastly, at some point I'd like to add the ability to have rumble motors in the joystick. I tentatively assigned the PWM pins to rumble functionality, though I haven't figured out exactly how to make the PC see the joystick as a rumble capable joystick.

Using:
Once everything is done, you're ready to simply use the joystick. When you plug it in, make sure all axes are centered, except the throttle which should be at 100% (since I no longer have the 50% detent to find the middle). Other than that, enjoy playing games with a joystick that is extremely responsive, and fully customizable and open source, and best of all... made by you!

DogP
 

Attachments

  • HotasX.zip
    770.5 KB · Views: 738
First post here and a beginner in Arduino, so pls forgive me for maybe the very basic question: What is exactly meant by "...select the ... USB type of HotasX" ?? Would this have anything to do with the error messages "HotasX: In function 'void loop()': HotasX:nnn: error: 'HotasX' was not declared in this scope HotasX.X(INV_X, afiltval(x)-startx);" that I receive on compiling the code, in setup() and in loop() ?? Thanks a lot for your help.
 
What is exactly meant by "...select the ... USB type of HotasX"

After you've installed all the changed files as intended, restart Arduino, then click the Tools menu in Arduino. First make sure Teensy is selected in the Boards sub-menu.

Then click Tools again, and look for the USB Type sub-menu. If you've put all the files in the right locations, it should have "Hotas X" in that sub-menu.
 
After you've installed all the changed files as intended, restart Arduino, then click the Tools menu in Arduino. First make sure Teensy is selected in the Boards sub-menu.

Then click Tools again, and look for the USB Type sub-menu. If you've put all the files in the right locations, it should have "Hotas X" in that sub-menu.

Yes ! Sorry , I was missing the modified ''boards.txt'' in the right place ;). It all works fine now. Thanks a lot !
 
Thanks for this post, just what i was looking for. I have just got a Teensy 3.2 and i have a Hotas X, with the 'very large' dead band that makes no sense at all. Question: did your modification solve that issue of such a large dead band in the centre?
 
Not a hardware guy myself, never used Arduino before, followed your instructions with Teensy 3.2 and everything worked like a charm.

Thank you!!!
 
Thanks for this post, just what i was looking for. I have just got a Teensy 3.2 and i have a Hotas X, with the 'very large' dead band that makes no sense at all. Question: did your modification solve that issue of such a large dead band in the centre?
Yeah, the dead zone is completely gone... the dead zone was a "feature" in the Hotas X joystick MCU. If you look at the raw analog values from the pots, the values are continuous... the MCU added a dead zone, presumably to make up for imperfections in hardware, assembly, pots, etc. That way when you take your hands off the stick, the joystick reports perfectly centered (even though it's probably off by a little bit). A dead zone in the center (with a smooth transition) is sometimes desired, but the way they programmed it in the Hotas X made it unusable IMO. Particularly bad was that the dead zone extended all the way up and down each axis, rather than just the near dead center. In this Teensy version, I simply read the analog value and report the raw value all the time. If you want a dead zone, you can set it in the game, or using a utility like Joystick Curves.

Not a hardware guy myself, never used Arduino before, followed your instructions with Teensy 3.2 and everything worked like a charm.

Thank you!!!
Great to hear!

Pat
 
I took the liberty to add some small changes to HotasX.ino file (attached). There was some 'micro-jerking' in the axis outputs which I tried to smooth by filtering random value peaks around the last joystick readings (the peak heights can be tuned for each axis and is subject of personal preference). Also, I decided to make use of the LEDs. The preset LED now lights when the PC/PS3 switch is in PS3 position. The LED on Home button is now red when throttle is to pushed to max and green when set to idle (min).
Hope, you will find these changes useful.

P.S. Revised smoothing. Now the jitter is almost gone.
 

Attachments

  • HotasX.ino
    6.2 KB · Views: 349
Last edited:
Hey guys, I am new to literally all of this (Arduino, programming, hardware hacks), but followed DogP's instructions and everything is working (almost) perfectly! Thanks a lot for your detailed guide - really came in handy. :D

I do, however, have a few remaining problems:

1. I am using my HotasX mostly for Star Citizen, and since putting in the Teensy (3.2) the throttle (Z) axis is not being detected in any way while setting controls in-game but all other buttons and axes seem to be detected properly. It is functioning visibly in Windows 10 and JoyToKey as the Z axis. Do you guys have any ideas on what might be going on there that has caused it to no longer be detected? It was detected prior to the Teensy swap.

2. Also, thank you to homofortis for revising the code a bit to reduce/eliminate jitters! It definitely seems to improve things to almost perfect, but there also seems to now be a noticeable input lag that isn't there in DogP's original build. If you move the throttle quickly to either extreme to the point where the LEDs light up, it takes around half a second for the light to activate. Not sure how easily that can be fixed but I figure you should know.

3. Finally, some strange new problems that might be more related to my particular hardware than anything, but maybe you guys have an idea of how to help or to point me in the right direction:

-My Z axis does not reach completely to the 'bottom', as in I can throttle up all the way, but there's still a bit left over when I throttle down.
-Same with the X axis- I can turn to the far right all the way, but there's still a bit to go when I try for far left.
and lastly...
-My RZ Axis flips back to extreme right when I rotate hard left. It works fine in the other direction, but basically makes the RZ axis unusable for the time being.
Note that I did not have these three problems before the Teensy swap, and I assume it's related to the HotasX hardware now sending completely raw data?

I always plug in my HotasX's USB with full throttle and a centered joystick.

Regarding these hardware problems: Is there a way to set the 'start' positions of those axes slightly to one side or the other via code? Or is that something that doesn't have such an easy fix? It seems like these axes are starting slightly to one side (instead of right in the middle) and that is causing those issues.

Any and all help is greatly appreciated and thanks again guys for your excellent work! :)
 
1. I am using my HotasX mostly for Star Citizen, and since putting in the Teensy (3.2) the throttle (Z) axis is not being detected in any way while setting controls in-game but all other buttons and axes seem to be detected properly. It is functioning visibly in Windows 10 and JoyToKey as the Z axis. Do you guys have any ideas on what might be going on there that has caused it to no longer be detected? It was detected prior to the Teensy swap.
Hmm... that's a strange one. If it's detected in Windows and other applications, it seems that it'd be something weird with how Star Citizen is reading the inputs. I'd think that the Z axis would be a pretty standard axis, but you could try changing the code to make it the Slider 1 axis instead (which would be unused, assuming you're not using rudder pedals).

2. Also, thank you to homofortis for revising the code a bit to reduce/eliminate jitters! It definitely seems to improve things to almost perfect, but there also seems to now be a noticeable input lag that isn't there in DogP's original build. If you move the throttle quickly to either extreme to the point where the LEDs light up, it takes around half a second for the light to activate. Not sure how easily that can be fixed but I figure you should know.
I haven't tried that build yet, but from a quick look at the code, I see it's filtering the last 32 values (up from 8 values that I used). I purposefully kept that value low to avoid input lag problems. It's probably okay to do filtering with 32 values, but you'd want to decrease the delay at the bottom of the code (which will make it read more values per second) If you simply reduce that delay, it'll also send a lot more joystick readings to the PC, which probably isn't desired... you'd probably want to change it to only send the joystick value every 4th time through the loop... or something similar. A smarter filtering algorithm could be implemented as well (one that smooths jittery movements, but quickly responds to large movements).

3. Finally, some strange new problems that might be more related to my particular hardware than anything, but maybe you guys have an idea of how to help or to point me in the right direction:

-My Z axis does not reach completely to the 'bottom', as in I can throttle up all the way, but there's still a bit left over when I throttle down.
-Same with the X axis- I can turn to the far right all the way, but there's still a bit to go when I try for far left.
and lastly...
-My RZ Axis flips back to extreme right when I rotate hard left. It works fine in the other direction, but basically makes the RZ axis unusable for the time being.
It sounds like your hardware is just slightly different than mine (which isn't unexpected)... I didn't do any sort of scaling, which could look at the minimum and maximum values, and scale between them. I simply assumed that the analog input will go all the way from full minimum to full maximum.

You might want to check out this thread: https://forum.pjrc.com/threads/33763-analogRead()-minimum-value-of-1 . I was noticing an offset, and not being able to reach full range, which I fixed by adjusting the ADC0_OFS value in analog.c .

DogP
 
With the odd results from range may be worth getting hold of a multimeter and with power off testing the resistance values of the axis. Measuring across the resistor should be unchanged during motion, and from the wiper to a single end should get a smooth range from 0 ohms to the value for the whole unit. Looks like at least one axis has a spot on it that shorts and makes the axis swap over.

Using the analog read examples may also be informative in chasing down if this is electrical or software in nature.

The basic fix for the range offsets is to add a map https://www.arduino.cc/en/reference/map command at the point where the averaging has finished but before the value is sent, where the fromlow,fromhigh numbers are your observed max and min values and the tomin,tomax values are your desired full range (0-1024?). Will be better if you can fix things mechanically though if just dodgy wiring.
 
Hey guys,

Thanks a lot for your advice! I'm slowly getting it to where I want it, though based on my testing with a multimeter I think I'll need to replace a couple malfunctioning potentiometers in the joystick (there's inconsistencies/axis swapping in two of them).

I'm also considering getting a used T16000m stick somewhere locally and combining it with my HotasX throttle for a couple of hall effect sensors (instead of replacing/upgrading the potentiometers). I know this thread is focused on the HotasX only, but do you guys suppose it would be considerably more complicated to incorporate the T16000m stick with the HotasX throttle on the Teensy 3.2, or not too different than with just the HotasX?

Thanks again for all your great advice and assistance :)
 
The Teensy won't care what sort of hardware is plugged in, as long as the voltage from the wiper is 0->3.3V (and easier if mid point is 1.65) Getting the mechanical parts together may be a bit tedious, but that is harder to judge from here.
 
Trying to upload to teensy 3.2 but getting compiling error:

Arduino: 1.8.2 (Windows 10), TD: 1.36, Board: "Teensy 3.2 / 3.1, Hotas X, 96 MHz (overclock), US English"

C:\Program Files (x86)\Arduino\hardware\teensy\avr\cores\teensy3/WProgram.h:52:21: error: ambiguating new declaration of 'uint32_t random()'

uint32_t random(void);

^

In file included from c:\program files (x86)\arduino\hardware\tools\arm\arm-none-eabi\include\stdlib.h:11:0,

from C:\Program Files (x86)\Arduino\hardware\teensy\avr\cores\teensy3/WProgram.h:4,

from C:\Program Files (x86)\Arduino\hardware\teensy\avr\cores\teensy3/Arduino.h:1,

from C:\Users\Simon\AppData\Local\Temp\arduino_build_485843\sketch\HotasX.ino.cpp:1:

c:\program files (x86)\arduino\hardware\tools\arm\arm-none-eabi\include\stdlib.h:253:6: note: old declaration 'long int random()'

long _EXFUN(random,(_VOID));

^

Error compiling for board Teensy 3.2 / 3.1.



Could anyone shed some light as to what I'm doing wrong, please.
 
Last edited:
Ok, managed to solve that issue... now have a new error...

exec: "C:\\Program Files (x86)\\Arduino\\hardware\\teensy/../tools/arm/bin/arm-none-eabi-ar": file does not exist
Error compiling for board Teensy 3.2 / 3.1.

anyone any ideas, please?

.
 
Looks like the HotasX boards.txt file was copied from an older version of Teensyduino. Since then the gcc toolchain has been updated. The required ar command has changed slightly, due to the new link time optimization feature.

Open the file and find this line:

teensy31.build.command.ar=arm-none-eabi-ar

edit the file to this:

teensy31.build.command.ar=arm-none-eabi-gcc-ar
 
Last edited:
I have the same error siecho posted in #15, could you please post the solution here. Thanks, it is a great project. I cant wait getting it running.
 
In WProgram.h I changed "uint32_t random(void);" to "long random(void);"

Not sure if this is the correct way to "fix" the issue but it worked for me.
 
Last edited:
I'm now having the issue that D4v3thund3r had in post 9...

Tried the adding ADC0_OFS value in analog.c but unable to get it to work properly... anyone have a properly written analog.c with the ADC0_OFS value set properly?

Thanks
 
Windows 10 does not recognize device after uploading code?

Hi all!

I have a HotasX stick and recently got a Teensy 3.2 board to try this hack.
After some fiddling and changing the "uint32_t random(void);" to "long random(void);" in WProgram.h, I was able to compile and upload.

However, windows 10 does not recognize the device when I plug it in. I haven't connected anything to the Teensy board yet, but it should register as a joystick device, right?
Now all that I get is a USB composite device that's not recognized. See this folder for screenshots:
https://drive.google.com/open?id=1M8kZq_Icwr2iv8t_TTsQfGHI7h6Nt0vl

Does anybody have an idea what's going wrong here?
I don't want to hack into my flight stick before I get this up and running, but I need to get rid of that damned deadzone! :)
 
Under Tools I have it set to:
Board: "Teensy 3.2 / 3.1"
USB Type: "Hotas X" (as instructed above)
CPU Speed: "72 MHz optimized" (I thought the board was 72MHz so changed it from 96 to 72)
and Programmer: "AVRISP mkII". (default)
I've added a screenshot of the Tools menu (settings.png) to the google drive folder that I linked in my previous post.

Any ideas?
 
I tried the original files with the "joystick complete" example, which works fine.
So I decided to check for differences in the files in this thread to the original ones.
In the boards.txt file I changed this:
teensy31.build.flags.defs=-D__MK20DX256__ -DTEENSYDUINO=127
to this:
teensy31.build.flags.defs=-D__MK20DX256__ -DTEENSYDUINO=141
and this:
teensy31.build.flags.ld=-Wl,--gc-sections,--relax,--defsym=__rtc_localtime={extra.time.local} "-T{build.core.path}/mk20dx256.ld"
to this:
teensy31.build.flags.ld=-Wl,--gc-sections,--relax,--defsym=__rtc_localtime={extra.time.local} "-T{build.core.path}/mk20dx256.ld" -lstdc++

And now it works! I don't know which of the 2 did the trick but it shows up fine under joy.cpl now.
I hope this will help someone in the future :)
 
Back
Top