element14 Community
element14 Community
    Register Log In
  • Site
  • Search
  • Log In Register
  • Community Hub
    Community Hub
    • What's New on element14
    • Feedback and Support
    • Benefits of Membership
    • Personal Blogs
    • Members Area
    • Achievement Levels
  • Learn
    Learn
    • Ask an Expert
    • eBooks
    • element14 presents
    • Learning Center
    • Tech Spotlight
    • STEM Academy
    • Webinars, Training and Events
    • Learning Groups
  • Technologies
    Technologies
    • 3D Printing
    • FPGA
    • Industrial Automation
    • Internet of Things
    • Power & Energy
    • Sensors
    • Technology Groups
  • Challenges & Projects
    Challenges & Projects
    • Design Challenges
    • element14 presents Projects
    • Project14
    • Arduino Projects
    • Raspberry Pi Projects
    • Project Groups
  • Products
    Products
    • Arduino
    • Avnet & Tria Boards Community
    • Dev Tools
    • Manufacturers
    • Multicomp Pro
    • Product Groups
    • Raspberry Pi
    • RoadTests & Reviews
  • About Us
    About the element14 Community
  • Store
    Store
    • Visit Your Store
    • Choose another store...
      • Europe
      •  Austria (German)
      •  Belgium (Dutch, French)
      •  Bulgaria (Bulgarian)
      •  Czech Republic (Czech)
      •  Denmark (Danish)
      •  Estonia (Estonian)
      •  Finland (Finnish)
      •  France (French)
      •  Germany (German)
      •  Hungary (Hungarian)
      •  Ireland
      •  Israel
      •  Italy (Italian)
      •  Latvia (Latvian)
      •  
      •  Lithuania (Lithuanian)
      •  Netherlands (Dutch)
      •  Norway (Norwegian)
      •  Poland (Polish)
      •  Portugal (Portuguese)
      •  Romania (Romanian)
      •  Russia (Russian)
      •  Slovakia (Slovak)
      •  Slovenia (Slovenian)
      •  Spain (Spanish)
      •  Sweden (Swedish)
      •  Switzerland(German, French)
      •  Turkey (Turkish)
      •  United Kingdom
      • Asia Pacific
      •  Australia
      •  China
      •  Hong Kong
      •  India
      •  Japan
      •  Korea (Korean)
      •  Malaysia
      •  New Zealand
      •  Philippines
      •  Singapore
      •  Taiwan
      •  Thailand (Thai)
      •  Vietnam
      • Americas
      •  Brazil (Portuguese)
      •  Canada
      •  Mexico (Spanish)
      •  United States
      Can't find the country/region you're looking for? Visit our export site or find a local distributor.
  • Translate
  • Profile
  • Settings
Spring Clean!
  • Challenges & Projects
  • Project14
  • Spring Clean!
  • More
  • Cancel
Spring Clean!
Spring Clean Projects 2026 Plugged In, At Last: My Custom STM32 HID Joystick
  • News and Projects
  • Forum
  • Members
  • More
  • Cancel
  • New
Join Spring Clean! to participate - click to join for free!
  • Share
  • More
  • Cancel
Group Actions
  • Group RSS
  • More
  • Cancel
Engagement
  • Author Author: Jonathan325
  • Date Created: 31 May 2026 1:15 PM Date Created
  • Views 136 views
  • Likes 5 likes
  • Comments 3 comments
Related
Recommended

Plugged In, At Last: My Custom STM32 HID Joystick

Jonathan325
Jonathan325
31 May 2026
Plugged In, At Last: My Custom STM32 HID Joystick

How it all Started...

Ever since playing some of the older WWII flight simulators and Star Wars Squadrons, I wanted to understand how joysticks actually worked under the hood — the ADCs, push buttons, USB HID protocol and all. So I set about seeing how hard it would be to design and build a working PC flight joystick from scratch.

The electronics came together, the 3D printed grip took shape, and it even worked on the bench. But this project has been sitting there half-finished for longer than I'd like to admit. The grip was designed, printed and wired up — but the enclosure was never fully closed, the wiring was loose and still roughly connected, nothing felt comfortable to use and it never quite made it to "finished." Spring Clean 2026 was the push I needed to finally get it done. More secure wiring. No more exposed PCB. Just plug it in and ready to take to the skies.

Section 1: Inspiration

After searching around online for reference designs, I came across the Gladiator NXT EVO F-14 Combat Edition (https://vkbsimcontrollers.com/products/gladiator-nxt-evo-space-combat-edition-right-hand-us) — a modern aggressive-looking grip that I wanted to use as design inspiration. Around the same time I had a vintage CH Products FlightStick Pro from an earlier project, so I deconstructed the handle to see what was inside and work out how to design something that would fit the existing base and utilize some of the existing axes/trim knobs

Target games: MetalStorm (PC) and general flight sim use for games like Star Wars Squadrons.

Block Diagram

This block diagram aims to illustrate the major connections and interfaces in this prototype setup

image

Section 2: Bill of Materials

Kept the parts list simple and low cost:

  • 4 x Tactile Pushbutton

  • 1 x Rotary Encoder (not programmed in for now)

  • 1 x Limit Switch

  • 1 x mini Joystick (PSP style)

  • 3 x 100kOhm through-hole resistor

  • Nucleo Board STM32G431RB

  • Arduino HAT (neater for soldering)

The CH FlightStick Pro base was repurposed — it already had the physical pitch/roll axes and D-Sub panel mount sorted, which kept the mechanical side much simpler.

Section 3: Step-by-step Build

3.1 Mechanical Design

The major difficulties in this design were to get geometry and ergonomics of this flight stick correct and large enough for assembly of internal components, manufacture design and repairs. As a prototype, this was a fun and the most creative part of the design in my opinion. 

3.1.1 Flight Stick Body Design

Using Fusion360, I found refence side and rear photos for the F-14 joystick to give the angular shape and design I wanted. Then using the Canvas feature in Fusion360, I began to create the profiles to suit this joystick (top 2 are the original veiws and the bottom 2 are the CAD geometry overlayed onto it) to then be split into two halves for printing and then work out how to locate and mount the other hardware like pushbuttons and encoder internally. This took a long time to get just right, guessing the overall length was around 12-14cm for grip height and nominal sizing. I had to leave space and construction points for the paddle (lower button) and the trigger which was a seperate assembly. 

image

3.1.2 Internal Component Integration

This was the tricky part as some items were not necessarily able to be screwed directly to the body and had to be positioned as part of final assembly, such as the hats on the pushbuttons. The tolerance and bend radius of some of the cables were also considered in the CAD. Being two 'halves', each shell had different parts to be designed, one to act as the 'bottom' of the shell and one to be the 'top' part of the shell - where the screws would enter from. Also, the socket head M3 screws were chosen to give a nice flush appearance and better ergonomics. Some CAD of the other portions such as the control HAT, encoder and the trigger sub-assembly part were also put together and/or glued in. the CAD is not perfect but its manufacturable.

image image

3.1.3 Time to 3D Print!

I borrowed my office 3D printer (Bambu Labs) and was lucky to be able to print the two halves and various bits and pieces such as the throttle pull trigger, hats for the momentary pushbutton and paddle were sorted out easily.

image

3.2 Wiring the Assembly

With all the parts printed, it was time to start assembly of the hardware. The use of the Arduino HAT on top of the STM32 Nucleo Board made it easy to check wiring as majority of the wires were female jumper wires. Basically all the cables (ground chained together, switch IO, 3.3V power etc) had to run from the parts in the flight stick internal body down through the flight stick base then into a grommet of sorts to be exposed for STM32 nucleo board connection. 

image image

Here is basic schematic in KiCAD to show the electrical connections on the Nucleo board (for possible later full PCBA design, just schematic capture for now):

 image

Eventually all the wires were soldered up and fed out through the grommet to be connected into the Nucleo board (see image below) with each IO pinout carefully labelled to avoid confusion and make debugging easier. Intention was to mainly perform physical pin checking with Nucleo board pinout vs STM32 CUBE IDE mapping

image

3.3 Software Design

At a very high level, the HID (Human Interface Device) should present the following features to the user. I followed a STM32 USB HID driver setup to give the following functionality (for MetalStorm PC game) similar to example at https://www.hackster.io/voske65/usb-hid-for-nucleo32-cubemx-part-2-edd88f. It is this descriptor which instructs how the device identifies itself to the OS. Then matching STM32 IO to meet this structure, using STM32 CUBE IDE. Initially I tried STM32Duino but this proved difficult as the HID library was not easy to add in so reverted back to the original STM32 IDE for the programming.

  • Axes
    • 4 x ADC (Roll and Pitch + 1 axis (Position) HAT + Throttle Wheel)
  • Buttons
    • Top Control Hatch Button = Missile
    • Trigger = Trigger
    • Side Button = Flare
    • Paddle Button = Paddle
    • Encoder Button = Weapon Swap

Mapping is as follows in the STM32 CUBE IDE:

image

ADC Inputs (ADC1, DMA circular)

Signal IOC Label STM32 Pin ADC Channel Nucleo Connector
Pitch PITCH_ADC PA0 ADC1_IN1 CN8 pin 1 (A0)
Yaw HAT_ADC* PA1 ADC1_IN2 CN8 pin 2 (A1)
Roll ROLL_ADC PC1 ADC1_IN7 CN8 pin 5 (A4)
Throttle THROTTLE_ADC PC3 ADC1_IN9 CN8 pin 6 (A5)

Buttons (GPIO_Input, internal pullup, active low)

Signal STM32 Pin Nucleo Connector
BUTTON_MISSILE PA10 CN9 pin 3 (D3)
BUTTON_FLARE PB10 CN9 pin 7 (D6)
BUTTON_WEAPON PB3 CN9 pin 5 (D4)
BUTTON_TRIGGER PB4 CN9 pin 6 (D5)
BUTTON_PADDLE PB5 CN9 pin 4 (D3)

The main steps completed for the software portion of this project were:

i) ADC Setup and Axis Smoothing

When you are flying an aircraft, you would want the axes to be quite stable with less jitter the better (especially when lining up a TIE fighter in the crosshairs!). I put in a Simple Moving Average filter applied to Pitch, Roll and Yaw — smooths out potentiometer noise without adding complex DSP.

#define SMA_SIZE 16 // 16 samples × 10ms = 160ms smoothing window

typedef struct {
uint16_t buf[SMA_SIZE];
uint8_t idx;
uint32_t sum;
uint8_t full;
} SMA_t;

static inline uint16_t SMA_update(SMA_t *f, uint16_t input)
{
f->sum -= f->buf[f->idx];
f->buf[f->idx] = input;
f->sum += input;
f->idx = (f->idx + 1) % SMA_SIZE;
return (uint16_t)(f->sum / SMA_SIZE);
}

ii) USB HID

1. The HID Report Descriptor — the heart of the project

The HID report descriptor that tell the OS exactly what this device is and how to read it. No driver installation needed.

__ALIGN_BEGIN static uint8_t HID_MOUSE_ReportDesc[HID_MOUSE_REPORT_DESC_SIZE] __ALIGN_END =
{
    0x05, 0x01,        // USAGE_PAGE (Generic Desktop)
    0x09, 0x04,        // USAGE (Joystick)
    0xA1, 0x01,        // COLLECTION (Application)

    // Axes: X (Pitch), Y (Roll), Slider (Throttle), Rz (Yaw)
    0x05, 0x01,        //   USAGE_PAGE (Generic Desktop)
    0x09, 0x30,        //   USAGE (X)      — Pitch
    0x09, 0x31,        //   USAGE (Y)      — Roll
    0x09, 0x36,        //   USAGE (Slider) — Throttle
    0x09, 0x35,        //   USAGE (Rz)     — Yaw
    0x15, 0x00,        //   LOGICAL_MINIMUM (0)
    0x26, 0xFF, 0x03,  //   LOGICAL_MAXIMUM (1023)
    0x75, 0x10,        //   REPORT_SIZE (16 bits)
    0x95, 0x04,        //   REPORT_COUNT (4)
    0x81, 0x02,        //   INPUT (Data, Var, Abs)

    // Buttons (10)
    0x05, 0x09,        //   USAGE_PAGE (Button)
    0x19, 0x01,        //   USAGE_MINIMUM (Button 1)
    0x29, 0x0A,        //   USAGE_MAXIMUM (Button 10)
    0x15, 0x00,        //   LOGICAL_MINIMUM (0)
    0x25, 0x01,        //   LOGICAL_MAXIMUM (1)
    0x75, 0x01,        //   REPORT_SIZE (1 bit)
    0x95, 0x0A,        //   REPORT_COUNT (10)
    0x81, 0x02,        //   INPUT (Data, Var, Abs)

    // Padding (6 bits to fill the byte)
    0x75, 0x06,        //   REPORT_SIZE (6)
    0x95, 0x01,        //   REPORT_COUNT (1)
    0x81, 0x03,        //   INPUT (Cnst, Var, Abs)

    0xC0               // END_COLLECTION
};

iii) Joystick Task

The full joystick task — reads ADC, applies filtering, builds the 10-byte report and sends it over USB at 100Hz

// Rate limited to 100Hz
if (HAL_GetTick() - last_tick < 10)
return;
last_tick = HAL_GetTick();

// Scale 12-bit ADC to 10-bit HID range
uint16_t x = ADC_HID_PITCH >> 2;
uint16_t y = ADC_HID_ROLL >> 2;
uint16_t z = ADC_HID_THROTTLE >> 2;
uint16_t rz = ADC_HID_YAW >> 2;

// Apply smoothing
uint16_t sx = SMA_update(&sma_pitch, x);
uint16_t sy = SMA_update(&sma_roll, y);
uint16_t srz = SMA_update(&sma_yaw, rz);

// Build and send report
HID_JoystickReport_t report;
report.roll = AXIS_MAX - sx; // inverted to match hardware orientation
report.pitch = sy;
report.throttle = z;
report.yaw = srz;
report.buttons = read_hardware_buttons() & 0x03FF;

USBD_HID_SendReport(&hUsbDeviceFS, (uint8_t*)&report, sizeof(report));

Putting it all together, with the inbuilt STM32 ST-Link, it was very convenient to iterate on the prototype joystick firmware until the raw readings and buttons were debugged and correctly mapped. Latest code is at https://github.com/Jono453/STM32-HID-Joystick 

Section 4: Prototype Assembly - It Works!

The rough assembly of the components was done. With the software setup and now flashed onto the Nucleo board, it was time to test it out on the computer. The simplest test was to use the USB Game Controller dialog box and see if it appeared as a HID device - ie check all 4 axes and all buttons worked. Moving the three axes on the vintage joystick panel and the buttons and HAT joystick on the flight stick and all bars and buttons activated as required.

image

Section 5: The Spring Clean: Finishing It Off (Finally)

This is where the project had been stuck for a while....

The core electronics were working — firmware tested, axes responding, buttons registering. But the physical build was far from done. Wiring was basic jumper wires straight into the Nucleo board, the enclosure was open, nothing was strain-relieved and the whole thing felt fragile to use. Not something you'd want to actually game with. The spring clean goal was to turn a working proof-of-concept into a finished, rugged controller you could actually pick up and use.

I had an old chassis box from another project which I wanted to use to house some items and make it more complete as a proper flight joystick base structure. Here is the current state of the setup. Ready for the tinkering and final spring clean up and assembly!

image

The Finishing Touches:

1. Proper extension wiring and making overall wiring more robust. The original wiring was basic — short jumper leads going directly into the Nucleo board headers. Fine for bench testing but not practical for a handheld controller. The grip needs to move freely, which means proper extension cables with enough length to not stress the connections. Replaced the jumper leads with soldered extensions, added heat shrink throughout and made sure nothing was pulling on any connector when the grip moves through its range of motion.

2. Strain relief Every cable exit point through the grip body and base needed proper strain relief so that normal use doesn't gradually work connections loose. An initial grommet was used but since I was intending to use some Dsub 9 pin or Dsub 15 pin connectors, it would be nicer to package these up in a more user-friendly and compact way with some 3D printed parts.

I designed some new parts to better strain relief the cables and tidy up the whole setup, using a spare grommet and a cool guarded arming switch, see CAD below for the assembly, the overall cable was also made more robust with some spare 25pin dsub and spare 15pin dsub connectors used to extend the cable between the joystick itself and the control box for more user friendly lengths. GrabCAD Nucleo board CAD citation - https://grabcad.com/library/stm32-nucleo-64-1. (by Shai Rogel). Gradually finalizing 3D printed components to make the whole setup more rugged and improve connectivity. Add some supports for the 25pin Dsub for the joystick external wires to avoid stress on the cables and also cable-tied the extended harness to make it cleaner. I decided to modify the second front panel to include positions for some tactile rugged switches for future IO. 

image

image

image

The final assembled HID joystick! Custom vintage joystick flight stick with Dsub extension cable into the 'control' box with the USB and 5V power for the whole gaming setup. Due to some last minute power issues, the STM32 board was not powering up through the power switch so I added the wall-wart 5V/3A supply into the board for final testing.

image

Testing on computer and with Star Wars Squadrons (see video) - was a satisfying experience! Phew phew phew (can be heard over the tactile buttons)

You don't have permission to edit metadata of this video.
Edit media
x
image
Upload Preview
image

Section 7: Future Work

This project is now deemed 'completed' but there are still some improvements which can be made. 

  • Designing a proper 'cover' for the control box. See ife a fan is required
  • Change grommet to individual threaded grommets for the USB and power cables

Any other ideas let me know.

  • Sign in to reply
  • arvindsa
    arvindsa 9 days ago

    Ah, A fellow STM32 Enthusuast. :)

    • Cancel
    • Vote Up 0 Vote Down
    • Sign in to reply
    • More
    • Cancel
  • Jonathan325
    Jonathan325 10 days ago in reply to BigG

    Thanks! Great idea, that would be cool to have some vibrations for centre of each axis and for force feedback when flare/rocket/cannons are used in-game. Trying to get this to work with Metalstorm as well.

    • Cancel
    • Vote Up 0 Vote Down
    • Sign in to reply
    • More
    • Cancel
  • BigG
    BigG 11 days ago

    Wow. Impressive work.

    How about haptic feedback...

    • Cancel
    • Vote Up 0 Vote Down
    • Sign in to reply
    • More
    • Cancel
element14 Community

element14 is the first online community specifically for engineers. Connect with your peers and get expert answers to your questions.

  • Members
  • Learn
  • Technologies
  • Challenges & Projects
  • Products
  • Store
  • About Us
  • Feedback & Support
  • FAQs
  • Terms of Use
  • Privacy Policy
  • Legal and Copyright Notices
  • Sitemap
  • Cookies

An Avnet Company © 2026 Premier Farnell Limited. All Rights Reserved.

Premier Farnell Ltd, registered in England and Wales (no 00876412), registered office: Farnell House, Forge Lane, Leeds LS12 2NE.

ICP 备案号 10220084.

Follow element14

  • X
  • Facebook
  • linkedin
  • YouTube