Build a Custom USB Sim Racing Button Box with RP2040 or ESP32-S3: TinyUSB HID Gamepad Descriptors and Debouncing
- Raspberry Pi Pico or Pico 2 (RP2040/RP2350)
- Momentary arcade pushbuttons, 12mm or 16mm, assorted colors
- Rotary encoders with detent, EC11 style
- Toggle switches, SPDT, panel mount
- 1N4148 diodes for the button matrix
- Ribbon cable and IDC connector kit
- ABS project enclosure box
- USB-C panel mount extension cable
- Heat shrink tubing assortment
- Perfboard/protoboard sheets
Commercial sim racing button boxes run $150–$400 for what's fundamentally a handful of switches and encoders wired to a microcontroller that shows up as a USB HID gamepad. Build your own on an RP2040 or ESP32-S3 and you get exactly the button count and layout your car and rig actually need, at a fraction of the cost, with full control over every button's HID mapping. This project covers wiring a real button matrix, writing the USB HID gamepad descriptor with TinyUSB, and handling debouncing properly so your pit-limiter button doesn't fire twice.
Why RP2040 or ESP32-S3, Specifically
USB HID gamepad emulation needs native USB device support, not a USB-to-serial bridge chip translating for you — which rules out boards like an Arduino Uno that rely on an external USB-serial converter with no native USB stack. Both the RP2040 (Raspberry Pi Pico) and the ESP32-S3 have native USB peripherals with TinyUSB support baked into their respective Arduino cores, which is what lets either one present itself to Windows or a game console as a real, unmodified-driver-required USB gamepad. Pick the RP2040 if you want the simplest possible wiring and lowest cost; pick the ESP32-S3 if you want to add wireless telemetry display, a small screen showing lap times, or Wi-Fi configuration on top of the button box later.
Wiring the Button Matrix
Wiring every button to its own GPIO pin works for a small box but eats pins fast — a 20-button box would need 20 GPIOs plus grounds. A diode-isolated matrix instead arranges buttons in rows and columns: an 4×5 matrix covers 20 buttons using only 9 GPIO pins (4 row pins + 5 column pins). Each button gets a 1N4148 diode in series, cathode toward the row line, which prevents "ghost" presses when multiple buttons are held simultaneously — a real problem in sim racing where you might hold a paddle-equivalent button while also hitting a pit-limiter switch.
Rotary encoders and toggle switches don't fit the matrix pattern the same way; wire encoders directly to two GPIO pins each (plus a shared ground) and read them with an interrupt-driven quadrature decode rather than polling, since polling in the main loop can miss fast rotation. Toggle switches can go directly to spare GPIOs with internal pull-ups enabled and no diode needed, since they're not part of the scanned matrix.
The USB HID Gamepad Descriptor
On the RP2040 with the Arduino-Pico core (or on ESP32-S3 with its native USB support), TinyUSB's HID gamepad class handles the low-level USB descriptor negotiation, but you still define how many buttons your device reports and how they map. A typical descriptor for this project reports 32 buttons as a single 32-bit field, plus your rotary encoders mapped either as additional buttons (clockwise/counterclockwise as two separate momentary presses) or as a HID axis if your sim racing software supports rotary axis input for things like a brake bias adjuster.
// Simplified concept - actual TinyUSB descriptor setup varies by core version Joystick.button(BUTTON_PIT_LIMITER, pressed); Joystick.button(BUTTON_HEADLIGHT_FLASH, pressed); // Encoders reported as button pulses on rotation direction if (encoderDelta > 0) Joystick.button(ENCODER_CW, true);Test the descriptor early and often using your operating system's built-in game controller test panel (Windows: joy.cpl) before wiring the full matrix — confirming that button 1 through button 8 map correctly with a breadboard prototype saves you from discovering a wiring mistake after everything is soldered into the enclosure.
Debouncing: The Part People Skip and Regret
A mechanical switch doesn't transition cleanly from open to closed; it bounces for a few milliseconds, and a naive polling loop will report that single physical press as five or six rapid presses. For a sim racing button box, this matters more than it sounds — a double-fired pit-limiter toggle or a headlight flash that fires three times from one press is exactly the kind of bug that only shows up mid-race. Debounce in software with a simple time-based check (ignore state changes within roughly 5–10 ms of the last accepted change per button) rather than relying on hardware RC debounce circuits, which add parts and board space for no real benefit at these button counts. Apply debouncing per-button in your matrix scan function, tracking a last-change timestamp for each of the 20 matrix positions independently.
Assembly and Final Wiring
- Prototype the full button matrix and encoder wiring on a breadboard first and confirm every input reports correctly through your OS's game controller panel.
- Transfer the matrix to perfboard, soldering diodes and matrix traces before mounting buttons in the enclosure — it's far easier to solder a flat matrix than to solder inside an assembled box.
- Drill and mount the enclosure panel for your button and encoder layout, dry-fitting the panel before final wiring to confirm nothing conflicts with the box's internal depth.
- Route the ribbon cable from the matrix board to the microcontroller, double-checking row/column pin assignments against your firmware before final assembly.
- Mount the microcontroller and panel-mount USB-C extension, close the enclosure, and re-run the full button test through your sim racing software's control binding screen.
Finishing Up
Once every input is confirmed and mapped in your sim (iRacing, Assetto Corsa, ACC, and most others read standard HID gamepad input natively with no special driver), label the panel with a laser-engraved or printed overlay so button functions are readable at a glance during a race. The same matrix-and-TinyUSB approach scales directly to other custom controllers — a flight sim panel, an arcade cabinet control deck, or a dedicated streaming deck — so the wiring and debouncing skills here carry over well beyond this one build.