An open source, low-cost, 3D-printable set of USB HID controllers for boat/ship simulators. Build your own helm: a steering wheel, a throttle lever, and a control panel / button box, all recognized by your PC as standard USB game controllers.
Project status: Work in progress. The wheel and throttle are complete; the control panel is a newer addition and its instructions are still rough.
Compatibility warning: As built, each controller enumerates as its own USB device. Steam only passes a single controller through to a game, so this build does not currently work with Steam titles, or with anything else expecting one device. Games that read the raw HID devices directly are fine.
We're testing a replacement for the per-controller Arduinos: a single Raspberry Pi Pico flashed with GP2040-CE, which presents the whole helm as one gamepad. See How It Works. If you're building now, everything up to and including the hall effect sensor mounting is unchanged, so build to that point and adapt the rest when the updated design lands.
- How It Works
- Repository Layout
- Safety
- Tools & Consumables
- Core Technique: Embedding Nuts in Printed Parts
- Build Guide: Arduino Holder & Hall Effect Sensor
- Build Guide: The Throttle
- Build Guide: The Wheel
- Build Guide: The Control Panel
- Wiring & Firmware
- License
The wheel and throttle use the same contactless sensing principle. Magnets embedded in a rotating axle are read by an AS5600 magnetic rotary position sensor (a hall effect encoder) mounted to a fixed base. An Arduino Pro Micro reads the sensor over I2C and presents itself to your computer as a standard USB joystick. The control panel is simpler: mechanical key switches wired straight to the digital pins of its own Pro Micro.
Magnets also provide adjustable friction on the throttle and centering on the wheel.
One Arduino per controller means one USB device per controller, which is the current limitation of the build. Steam hands a game a single controller, so the wheel and throttle showing up separately makes them invisible to Steam titles.
The fix we're testing is a single Raspberry Pi Pico running GP2040-CE, an open source gamepad firmware. The Pico reads both sensors and reports one gamepad. It's also a simpler setup: one board and one USB cable for all three controllers, configured through GP2040-CE's web configurator. Only the wiring and firmware from the sensor mounting onward are affected — the sensors, magnets, printed parts and most assembly steps stay the same.
| Path | Contents |
|---|---|
stl/ |
Ready-to-print STL files for all parts |
freecad/ |
Editable FreeCAD source files |
firmware/ |
USB HID firmware for all three controllers (details) |
docs/ |
Build photos and supplementary documentation |
stl/ and freecad/ mirror each other: the same four subfolders (throttle/, wheel/, control_panel/, arduino_holder/) with matching filenames, so stl/wheel/wheel_axle_top.stl is the export of freecad/wheel/wheel_axle_top.FCStd. The File column in each parts list gives the name without its extension.
The throttle and wheel folders each hold an *_assembly.FCStd showing how the parts fit together. These are for reference only.
Read this before starting.
- This project uses very strong neodymium magnets. They can snap together hard enough to pinch skin, can shatter and send sharp fragments flying, and they will erase credit cards and magnetic media. Keep them away from small children, pets, pacemakers and other implanted medical devices, and sensitive electronics. Never let two large magnets slam together.
- Soldering involves high temperatures and fumes. Work in a ventilated area and don't touch the hot end.
- 3D printing carries its own risks (hot surfaces, fumes/particulates). Follow your printer manufacturer's safety guidance.
- Superglue (cyanoacrylate) bonds skin instantly and gives off irritating vapors. Ventilate, and keep some acetone handy.
- If you're under 18, ask a trusted adult for help.
Needed throughout all three builds:
- 3D printer and filament
- Soldering iron and solder
- 2.5 mm Allen key/wrench (all bolts in this project are M3 socket head)
- Print cleanup tools:
- High-grit sandpaper
- X-Acto/hobby knife
- Lighter and tweezers (for heat-setting a nut in the wheel axle)
- Superglue or epoxy (gel-type superglue is easier to work with, since it gives you some working time and doesn't run)
A note on the parts lists: They should be accurate, but keep a few spare M3 nuts and washers on hand. I recommend a kit with several sizes of bolts.
This project uses a lot of M3 nuts embedded in the printed parts. The hexagonal pockets are printed slightly undersized on purpose, so the nuts have to be pulled into place carefully, usually before assembly.
-
Choose a bolt slightly longer than the printed part and nut combined.
-
Thread a washer onto the bolt first. The extra surface area stops the bolt head from embedding itself into the plastic as you tighten.
-
Insert the bolt through the part, then gently thread the nut onto the end and line up the nut's flats with the hexagonal pocket.
-
Hold the nut in place and tighten the bolt with the Allen key. The bolt will pull the nut down into the pocket until it's mostly embedded.
-
Stop before it's fully flush. The nut only needs to be seated deep enough to grip the plastic. Over-tightening will permanently damage the part.
-
You can now unscrew and completely remove the bolt. The nut stays embedded, and the part is ready for assembly.
Never over-tighten anything in this build.
Both the wheel and the throttle use this sub-assembly, so build one for each.
Note: Arduino Pro Micros come in several different sizes. The holder in this repository is designed for a Pro Micro up to 17 mm long.
-
Following the Core Technique section above, embed nuts into both halves of the holder base.
-
Solder the following wires to the AS5600 breakout board. The wires should extend out the back of the board.
- VCC
- GND
- DIR
- SCL
- SDA
I like to pre-attach bolts, washers and nuts to all the mounting holes to make sure the wires and attachment points have enough clearance.
-
Temporarily attach the AS5600 breakout board to the base using bolts, washers and nuts. The face of the board should point the same direction as the hexagonal nut holes on the base.
-
Insert the Arduino into the holder slot on the opposite side of the breakout board. The Arduino's USB port should face away from the four bolt holes on the base, and the bottom of the board should sit flush inside the holder slot (chip side facing out, away from the plastic).
-
Cut the breakout board wires to length (plus about an inch extra) and solder them to the following Arduino pins:
AS5600 pin Pro Micro pin VCC VCC GND GND DIR GND SDA Digital pin 2 SCL Digital pin 3 -
Attach the Arduino holder top using 2× M3×10 bolts.
-
Gently unscrew and detach the AS5600 breakout board. It gets mounted directly to the wheel or throttle in the guides below.
A single-axis throttle lever with magnetically adjustable friction.
| Part | File | Qty | Notes |
|---|---|---|---|
| Throttle handle | throttle_handle |
1 | |
| Axle, top half | throttle_axle_top |
1 | Nut pockets printed with looser tolerance; walls are thin and fragile |
| Axle, bottom half | throttle_axle_bottom |
1 | |
| Axle socket | throttle_axle_socket |
1 | |
| Arduino holder, top | arduino_holder_top |
1 | |
| Arduino holder, bottom | arduino_holder_bottom |
1 |
throttle_base_templateis a flat template for marking out the throttle's mounting holes on your own surface. It isn't part of the standard build.
| Item | Qty | Used for |
|---|---|---|
| Arduino Pro Micro (ATmega32U4, 5V/16MHz) | 1 | Reads the sensor, acts as USB HID device |
| AS5600 magnetic rotary sensor breakout | 1 | Senses axle rotation (I2C) |
| M3×30 bolts | 4 | Securing the handle to the axle |
| M3×20 bolts | 4 | Sensor and Arduino holder mounting (M3×25 if you need extra length for standoffs) |
| M3×10 bolts | 2 | Arduino holder |
| M3 nuts (5.5 mm flat-to-flat) | 12 | Plus a few extras for use as standoffs |
| M3 washers | 16 | |
| 30 mm strong neodymium magnets | 3+ | 2 for sensing, 1+ for friction |
-
Test-fit the handle: line it up with the hole in the bottom axle half before pushing it down (gently).
- The bottom of the handle, near the bolt holes, will likely need light sanding to fit.
- When seated, the bolt holes should line up cleanly.
-
Repeat the test fit with the top axle half, then test everything fitted together.
-
Both axle halves should fit inside the axle socket and rotate freely. Sand and trim until they do.
Embed nuts in both axle halves using the nut-embedding technique above.
Be extra gentle. These pockets are printed with slightly looser tolerance because the surrounding walls are very thin and fragile.
-
Place 2 magnets into the two slots in one axle half, one oriented horizontally and one vertically. Polarity doesn't matter.
-
Close the magnets in with the other axle half.
-
Insert the assembled axle into the axle socket. It should rotate freely within the socket.
- Gently insert the handle into the axle, making sure all the bolt holes line up.
- Secure it with the 4× M3×30 bolts, with washers on the bolt-head side.
Place 1 magnet in the axle socket well opposite the axle. This creates friction between the axle and socket, which keeps the throttle lever from drifting or falling as the parts wear in.
Want a stiffer throttle? Stack additional magnets in the socket well.
See Wiring & Firmware for electronics info.
-
Insert washers and the M3×20 bolts (M3×25 if you need the extra length) from the inside of the socket tunnel: bolt head and washer on the inside, threads pointing out.
-
On the outside of the socket, add a washer, nut, and another washer on each bolt. Finger-tight only.
-
Slide the AS5600 sensor board onto the bolts, sandwiching it between washers and nuts.
- Sensor orientation doesn't matter, as long as the chip side faces toward the printed wall.
- The sensor must be rigidly secured to the base, since any play shows up as input noise. The Arduino holder can have some wiggle; the sensor cannot.
- Extra nuts work well as standoffs to set the sensor spacing.
-
Secure the Arduino holder between nuts and washers on the same bolts, oriented horizontally.
- Insert the combined axle and handle assembly into the socket, axle first.
The throttle is complete!
A steering wheel with magnetic centering detents and a rotation stopper.
| Part | File | Qty | Notes |
|---|---|---|---|
| Wheel, front half | wheel_front |
1 | |
| Wheel, back half | wheel_back |
1 | |
| Axle, top half | wheel_axle_top |
1 | Carries the magnet wings and the sensing magnets |
| Axle, bottom half | wheel_axle_bottom |
1 | |
| Axle socket | wheel_axle_socket |
1 | |
| Base, left half | wheel_base_left |
1 | Not interchangeable with the right half |
| Base, right half | wheel_base_right |
1 | |
| Wheel stopper half | wheel_axle_stopper_half |
2 | Print the same part twice |
| Arduino holder, top | arduino_holder_top |
1 | |
| Arduino holder, bottom | arduino_holder_bottom |
1 |
base_mount_templateis a flat template for adapting the wheel base to your own mounting surface. It isn't part of the standard build.
| Item | Qty | Used for |
|---|---|---|
| Arduino Pro Micro (ATmega32U4, 5V/16MHz) | 1 | Reads the sensor, acts as USB HID device |
| AS5600 magnetic rotary sensor breakout | 1 | Senses axle rotation (I2C) |
| M3×12 bolts | 2 | Joining the axle halves (longer also works, and is easier to handle) |
| M3×14 screws | 6 | Joining the wheel halves |
| M3×18 bolt | 1 | Securing the stopper to the axle |
| M3×20 bolts | 5 | 4 for the axle socket and base, 1 for the wheel-to-axle center bolt (M3×25 if you need extra length for standoffs) |
| M3×25 bolts | 2 | Clamping the base halves down |
| M3×10 bolts | 4 | 2 for the base faces, 2 for the Arduino holder |
| M3 nuts (5.5 mm flat-to-flat) | ~16 | Plus extras for standoffs |
| M3 washers | ~20 | |
| 30 mm strong neodymium magnets | 8 | 2 for the axle magnet slots (polarity matters), 2 for sensing, 4 for the centering detents |
- Clean both axle halves. Make sure the magnet slots are completely free of debris and stray plastic.
- Place a nut in the nut slot, flat side up.
- This nut may need heat-setting: warm it with a lighter until it's just hot enough to barely melt plastic (not glowing hot), place it in the slot with tweezers, and press both pieces together. Repeat until both sides sit flush.
-
Sandwich the nut and magnets between the two axle halves.
-
Screw the halves together with the 2× M3×12 bolts. Longer bolts also work and are easier to handle.
Both magnets must have the same polarity. Double-check before the glue cures. There's no fixing this later.
Glue a magnet into each of the two axle magnet slots.
You can use spare magnets on the plastic side to act as clamps while the glue cures.
-
Insert 2× M3×25 bolts with washers into the axle socket, round end first.
-
Secure each with a washer and nut on the threaded end. Get these reasonably tight, since you won't be able to easily reach them again after assembly.
-
Do the same with the holes on the back end of the base: one bolt in each base half.
Be extra careful with these two. They only need to sit flush with the base, and they're easy to strip.
-
Insert the axle into the axle socket.
-
Sandwich the assembled axle (and socket) from Step 2, with its magnet wings, into the axle base with the hex end facing out, away from the printed base.
- The axle socket should sit at the round end of the axle, flush with the base halves.
- Important: the axle socket must sit flat on the base, with the axle still inside.
-
Check that the axle rotates smoothly before going further.
-
Insert the 2× M3×25 bolts into the holes on the axle socket farthest from the axle wings (closest to the bolts secured in the previous step) and gently tighten them down into the embedded base nuts.
Watch those nuts closely while tightening and make sure they don't strip or spin in their pockets.
-
Secure the base face halves together with 1× M3×10 bolt.
-
Repeat at the other end of the base faces.
-
Sandwich a nut between the two wheel stopper halves.
-
Squeeze the halves together and insert them flat side first into the wheel axle.
-
Insert the M3×18 bolt with a washer into the stopper mounting hole in the axle, and tighten to secure the stopper.
- It's fine if the two stopper halves aren't perfectly flush.
-
Rotate the axle so the stopper is perfectly vertical.
-
Glue magnets into all 4 mounting positions (front and back) on the base. They'll snap into their correct orientation against the axle magnets on their own.
-
Do not rotate the axle until the glue has fully cured.
-
Join the two wheel halves, flat faces together, with the 6× M3×14 screws and washers. The nuts shouldn't need to be embedded, just pushed into place from the back.
-
Mount the wheel on the axle, then insert the M3×20 bolt with a washer through the center of the wheel and gently tighten it into the axle.
- Be extremely careful not to strip the nut inside the axle.
- On the sensor mounting bolts, add a washer and nut on the outside, washer between nut and part. Finger-tight only.
- Slide the AS5600 sensor board on, sandwiching it between washers and nuts.
- Sensor orientation doesn't matter, as long as the chip side faces toward the printed wall.
- The sensor must be rigidly secured to the base. The Arduino holder can have some wiggle; the sensor cannot.
- Use extra nuts as standoffs if necessary.
- Secure the Arduino holder between nuts and washers, oriented vertically.
A configurable button/switch pad for additional inputs, using 3D-printed keycaps.
Warning: this part is a much more recent addition and is still a work in progress. The keycap and switch design holds up well to repeated use, but it's probably faster and cheaper to just buy purpose-built arcade buttons. Some switches may also need slight modification to fit the switch mount holes.
All files live in stl/control_panel/ and freecad/control_panel/.
- Base
- Plate
- Can also be laser cut
- Arduino holder (from
stl/arduino_holder/) - Standoffs
- 6 keycaps (4 small square, 1 circle, 1 large circle)
- 6 switch stem inserts
| Item | Qty | Used for |
|---|---|---|
| Arduino Pro Micro (ATmega32U4, 5V/16MHz) | 1 | Reads the switches, acts as USB HID device |
| Mechanical keyboard switches | 6 | The buttons themselves |
| JST SM 2-pin female plug connectors | 6 | Optional, Arduino side |
| JST SM 2-pin male plug connectors | 6 | Optional, switch side |
| M3×15 bolts | 6 | Securing the plate to the base |
| M3 nuts (5.5 mm flat-to-flat) | ~6 | Plus extras for standoffs |
| M3 washers | ~6 | |
| Medium-grit sandpaper | - | Cleaning up the printed switch stems |
-
Solder the JST SM female plug connectors to the Arduino.
- One leg to each available digital pin (2, 3, 4, 5, 6, 7)
- All other legs to ground
-
Secure the Arduino within the Arduino holder.
-
Solder the JST SM male plug connectors to each key switch.
- These connectors are technically optional — you can solder wires directly between the Arduino and the key switches. They just make it easier to manage the cabling and replace switches.
-
Attach the plate (printed or laser cut) to the base using 6 washers, M3×15 bolts and nuts.
-
Insert the switches wire-first into the plate holes.
-
Attach the Arduino holder, long side up, using standoffs between the holder and the base.
-
Connect all plugs.
-
Print the keycap stems sideways, not flat side down.
- This is essential to ensure the stems don't break under stress.
-
Sand down the support points until the stem can be inserted into a keycap with no resistance.
-
Glue each finished stem into a keycap.
-
Attach all keycaps onto the switches on the control panel.
- Test, sand and adjust the stems until the buttons activate in a clean, satisfying manner.
The firmware lives in firmware/, which has its own README covering the internals. What follows is everything you need to get a finished controller working.
Each of the wheel and throttle uses its own Pro Micro wired to its AS5600 sensor over I2C:
| AS5600 pin | Pro Micro pin |
|---|---|
| VCC | VCC (5V) |
| GND | GND |
| DIR | GND |
| SDA | 2 (SDA) |
| SCL | 3 (SCL) |
The control panel's Pro Micro has no sensor: each switch bridges one digital pin (2-7) to ground.
The firmware is a PlatformIO project with one environment per controller (wheel, throttle, control_panel). There are two ways to get PlatformIO:
Option A - VS Code extension (all platforms, easiest):
- Install Visual Studio Code.
- Open the Extensions panel and install PlatformIO IDE.
- Open the
firmware/folder in VS Code. You can run thepiocommands below from VS Code's built-in terminal, or use the PlatformIO sidebar's Build/Upload buttons per environment.
Option B - command-line (CLI):
-
Windows:
- Install Python 3 and check "Add Python to PATH" in the installer.
- In PowerShell:
pip install --user pipx python -m pipx ensurepath # Reopen PowerShell, then: pipx install platformio
- Pro Micro clones are driverless on Windows 10/11. If yours isn't detected, install the SparkFun Pro Micro drivers.
-
macOS:
brew install platformio # or, if you prefer pipx: brew install pipx && pipx install platformio
-
Linux:
# Install via pipx (from your distro's repos, e.g. apt/dnf/pacman) sudo apt install pipx # Debian/Ubuntu pipx install platformio
Then set up device permissions so uploads and the serial monitor work without root:
# PlatformIO udev rules (allows access to the board's USB/serial interface) curl -fsSL https://raw.githubusercontent.com/platformio/platformio-core/develop/platformio/assets/system/99-platformio-udev.rules | sudo tee /etc/udev/rules.d/99-platformio-udev.rules sudo udevadm control --reload-rules && sudo udevadm trigger # Add yourself to the serial group, then log out and back in sudo usermod -aG dialout $USER # Debian/Ubuntu/Fedora # (on Arch the group is "uucp": sudo usermod -aG uucp $USER)
Verify the install with pio --version in a fresh terminal.
Libraries: no manual installation needed. All dependencies, including the Arduino Joystick library, are declared in platformio.ini and PlatformIO downloads them on the first build.
# From firmware/, build and upload -
# pick the environment for the controller you're flashing
pio run -e wheel -t upload
pio run -e throttle -t upload
pio run -e control_panel -t uploadEach controller flashes with its own USB identity (manufacturer "Open Nautical" plus a unique product name), so your PC can tell them apart.
Upload troubleshooting: Pro Micro clones with the Caterina bootloader can be finicky. If an upload fails, briefly short RST to GND (tap, don't hold) and immediately re-run the upload command. The bootloader only listens for about 8 seconds after reset, and sometimes it just takes a couple of attempts.
Each controller is calibrated once and stores the result in EEPROM. For the wheel and throttle this is mount-orientation independent, so it doesn't matter how the magnets ended up clocked in the housing, or even if the sensor is mounted backwards. For the control panel it means the switch harness can be soldered in any order.
Open a serial monitor (pio device monitor, 9600 baud) and send single keystrokes:
Wheel: center the wheel and send c, turn fully left and send l, turn fully right and send r, then send s to save.
Throttle: move the lever to mid-travel and send c, move to the idle stop and send l, move to the full stop and send h, then send s to save.
Control panel: send c, then press each switch as the prompt names it, and send s to save.
Calibration persists across power cycles. Re-run it anytime by sending c again, or send x to back out without changing anything. There's more on how it works, and on what each controller does before it's been calibrated, in the firmware README.
Plug the controller in and your OS should detect a new USB game controller:
- Windows: run
joy.cpland watch the axis respond (throttle = Rz, wheel = X). - Linux:
sudo apt install joystick, thenjstest /dev/input/js0.
Then calibrate in your sim of choice.
The control panel is wired to report six buttons in a fixed order — Up, Down, Left, Right, A, B — regardless of which pin each switch ended up on, so its in-game bindings stay valid even if you re-solder the harness and re-calibrate.
Two licenses apply:
| Part | License | File |
|---|---|---|
Firmware and any other code (firmware/) |
MIT | LICENSE-CODE |
3D models, CAD sources, and documentation (stl/, freecad/, docs/, this README) |
CC BY 4.0 | LICENSE-MODELS |
- Add a print orientation guide with screenshots
- Add a Templates section covering
throttle_base_template,base_mount_templateandcontrol_panel_template - Add the AS5600 / Pro Micro wiring diagram
AI tools were used in parts of this project:
- This build guide was compiled and copy-edited from my handwritten build notes. The steps, measurements, parts lists and photos are all from my own build.
- The USB HID firmware was written with AI assistance.
The hardware design, CAD models and the build itself are my own work. Everything here has been reviewed by me, but if you spot something wrong or unclear, please open an issue.
























































