No description
Find a file
2025-11-25 16:24:42 -07:00
.vscode Initial commit 2025-11-20 11:20:19 -07:00
controller_db Make other controllers work 2025-11-21 08:11:24 -07:00
.gitignore WORKING 2025-11-20 22:40:36 -07:00
build.sh Build with colors :) 2025-11-24 22:40:51 -07:00
CMakeLists.txt Remove debug logging if flag is present 2025-11-20 17:39:53 -07:00
controller_color_config.h Add color config 2025-11-21 13:23:43 -07:00
controller_uart_bridge.py Refactor into lib 2025-11-25 06:23:01 -07:00
host_uart_logger.py WORKING 2025-11-20 22:40:36 -07:00
pico_sdk_import.cmake Initial commit 2025-11-20 11:20:19 -07:00
README.md Remove rust 2025-11-25 16:24:42 -07:00
requirements.txt add requirements.txt 2025-11-25 11:37:09 -07:00
switch-pico.cpp Add hints for pro controller 2025-11-22 09:24:13 -07:00
switch_pico_uart.py Refactor into lib 2025-11-25 06:23:01 -07:00
switch_pro_descriptors.h WORKING 2025-11-20 22:40:36 -07:00
switch_pro_driver.cpp Add color config 2025-11-21 13:23:43 -07:00
switch_pro_driver.h WORKING 2025-11-20 22:40:36 -07:00
tusb_config.h WIP No debug 2 2025-11-20 18:03:06 -07:00

Switch Pico Controller Bridge

Raspberry Pi Pico firmware that emulates a Switch Pro controller over USB and a host bridge that forwards real gamepad input over UART (with rumble round-trip).

What you get

  • Firmware (switch-pico.cpp + switch_pro_driver.*): acts as a wired Switch Pro. Takes controller reports over UART1 and passes rumble from the Switch back over UART.
  • Python bridge (controller_uart_bridge.py): reads SDL2 controllers on the host, sends reports over UART, and applies rumble locally. Hotplug friendly and crossplatform (macOS/Windows/Linux).
  • Optional Rust bridge (fast_uart_bridge/): higherperformance alternative if the Python bridge has rumble latency.
  • Colour override (controller_color_config.h): compiletime RGB overrides for body/buttons/grips as seen by the Switch.

Hardware wiring (Pico)

  • UART1 pins (fixed in firmware):
    • TX: GPIO4 (Pico pin 6) → RX of your USBserial adapter.
    • RX: GPIO5 (Pico pin 7) → TX of your USBserial adapter.
    • GND: common ground between Pico and adapter.
  • Baud rate: 921600 (default). Some adapters only handle 500,000; both bridges accept a --baud flag.
  • Keep logic at 3.3V; do not feed 5V UART into the Pico.

Building and flashing firmware

Prereqs: Pico SDK + CMake toolchain set up.

cmake -S . -B build -DSWITCH_PICO_AUTOTEST=OFF -DSWITCH_PICO_LOG=OFF
cmake --build build -j

Flash the UF2 to the Pico (e.g., bootsel + drag-drop or picotool load). Flags:

  • SWITCH_PICO_AUTOTEST (default ON in some configs): disable autopilot/test replay with OFF.
  • SWITCH_PICO_LOG: enable/disable UART logging on the Pico.

Changing controller colours

./build.sh now writes a random colour into controller_color_config.h before building so the Switch shows a fresh colour when you flash.

  • Use ./build.sh --color FF00AA (or --color 12,34,56) to pick a specific colour applied to the body/buttons/grips.
  • Use ./build.sh --keep-color if you want to build without touching controller_color_config.h.

Works on macOS, Windows, Linux. Uses SDL2 + pyserial.

Install dependencies with uv (or pip)

# from repo root
uv venv .venv
uv pip install pyserial pysdl2
  • SDL2 runtime: install via your OS package manager (macOS: brew install sdl2; Windows: place SDL2.dll on PATH or next to the script; Linux: sudo apt install libsdl2-2.0-0 or equivalent).

Run

source .venv/bin/activate  # or .venv\Scripts\activate on Windows
python controller_uart_bridge.py --interactive

Options:

  • --map index:PORT (repeatable) to pin controller index to serial (e.g., --map 0:/dev/cu.usbserial-0001 or --map 0:COM5).
  • --ports PORTS... or --interactive for auto/interactive pairing.
  • --all-ports to include non-USB serial devices in discovery.
  • --ignore-port-desc SUBSTR / --include-port-desc SUBSTR to filter serial ports by description (repeatable).
  • --include-controller-name SUBSTR to only open controllers whose name matches (repeatable).
  • --list-controllers to print detected controllers and their GUIDs, then exit (useful for GUID-based options).
  • --baud 921600 (default 921600; use 500000 if your adapter cant do 900K).
  • --frequency 1000 to send at 1 kHz.
  • --deadzone 0.08 to change stick deadzone (0.0-1.0).
  • --trigger-threshold 0.35 to change analog trigger press threshold (0.0-1.0).
  • --swap-abxy to flip AB/XY globally.
  • --swap-abxy-index N (repeatable) to flip AB/XY for controllers first seen at index N (auto-converts to a stable GUID).
  • --swap-abxy-guid GUID (repeatable) to flip AB/XY for a specific physical controller (GUID is stable across runs).
  • --sdl-mapping path/to/gamecontrollerdb.txt to load extra SDL mappings (defaults to controller_db/gamecontrollerdb.txt).

Hotplugging: controllers and UARTs can be plugged/unplugged while running; the bridge will auto reconnect when possible.

Using the lightweight UART helper (no SDL needed)

For simple scripts or tests you can skip SDL and drive the Pico directly with switch_pico_uart.py:

from switch_pico_uart import SwitchUARTClient, SwitchButton, SwitchHat

with SwitchUARTClient("/dev/cu.usbserial-0001") as client:
    client.press(SwitchButton.A)
    client.release(SwitchButton.A)
    client.move_left_stick(0.0, -1.0)  # push up
    client.set_hat(SwitchHat.TOP_RIGHT)
    print(client.poll_rumble())  # returns (left, right) amplitudes 0.0-1.0 or None
  • SwitchButton is an IntFlag (bitwise friendly) and SwitchHat is an IntEnum for the DPAD/hat values.
  • The helper only depends on pyserial; SDL is not required.

macOS tips

  • Ensure the USBserial adapter shows up (use /dev/cu.usb* for TX).
  • Some controllers Guide/Home buttons are intercepted by macOS; using XInput/DInput mode or disabling Steams controller handling helps.

Windows tips

  • Use COMx for ports (e.g., COM5). Autodetect lists COM ports.
  • Ensure SDL2.dll is on PATH or alongside the script.

Linux tips

  • You may need udev permissions for /dev/ttyUSB*//dev/ttyACM* (add user to dialout/uucp or use udev rules).

Troubleshooting

  • No input on Switch: verify UART wiring (Pico GPIO4/5), baud matches both sides, Pico flashed with current firmware, Switch sees a “Pro Controller”.
  • Constant buzzing rumble: the bridge filters small rumble payloads; ensure baud isnt dropping bytes. Try lowering rumble scale in controller_uart_bridge.py if needed.
  • Guide/Home triggers system menu (macOS): try different controller mode (XInput/DInput), disable Steam overlay/controller support, or connect wired.
  • SDL cant see controller: load controller_db/gamecontrollerdb.txt (default), add your own mapping, or try a different mode on the pad (e.g., XInput).