Development Guide¶
This section is for contributors who want to extend Joypad OS -- adding new input interfaces, output interfaces, apps, or device drivers.
Prerequisites¶
Before diving in, make sure you can build and flash existing firmware:
# One-time setup (macOS)
brew install --cask gcc-arm-embedded cmake git
# Clone and initialize
git clone https://github.com/joypad-ai/joypad-os.git
cd joypad-os
make init
# Build any app to verify your setup
make usb2gc_kb2040
See the build guide for full setup instructions including Linux and Windows.
Repository Layout¶
src/
apps/ App configurations (one directory per app)
core/ Shared firmware: router, buttons, input_event, output_interface
router/ Input-to-output routing
services/ Players, profiles, storage, LEDs, hotkeys, codes, display, button, speaker
usb/
usbh/ USB host (input): HID parsing, vendor drivers, XInput
usbd/ USB device (output): HID gamepad, XInput, PS3/4, Switch modes
bt/ Bluetooth: transport, BTstack host, BT HID device drivers
wifi/ WiFi: JOCP protocol (Pico W)
native/
host/ Native controller reading (SNES, N64, GC, NES, LodgeNet, etc.)
device/ Console output protocols (GameCube, PCEngine, Dreamcast, etc.)
platform/ Platform HAL (RP2040, ESP32, nRF52840)
lib/ External libraries (TinyUSB, BTstack, pico-sdk, joybus-pio)
esp/ ESP32-S3 build directory (ESP-IDF)
nrf/ nRF52840 build directory (Zephyr/nRF Connect SDK)
Adding a New App¶
-
Create
src/apps/<appname>/with three files: -
app.h-- Compile-time config: version, routing mode, max players, transform flags. app.c-- Runtime wiring: return input/output interface arrays, callrouter_init()with your config, register profiles.-
profiles.h-- (Optional) Button remapping tables. -
Add build targets to
CMakeLists.txtandMakefile. -
Build:
make <appname>_<board>
Use an existing app as a template. usb2gc is a good example of a console adapter; bt2usb is a good example of a USB output app.
Key decisions for your app:
- Routing mode: SIMPLE (1:1), MERGE (all-to-one), or BROADCAST (one-to-all)
- Player management: SHIFT (slots shift on disconnect) or FIXED (slots stay assigned)
- Profiles: Define in profiles.h or omit for passthrough
Adding a New USB Device Driver¶
When a new USB controller needs special handling beyond generic HID:
-
Create
src/usb/usbh/hid/devices/vendors/<vendor>/<device>.cand.h -
Implement four functions:
-
Register in
hid_registry.c.
The _is_device function matches VID/PID. The _process function parses the raw HID report and calls router_submit_input() with a normalized input_event_t.
Adding a New Bluetooth Device Driver¶
Same pattern as USB, but in src/bt/bthid/devices/vendors/<vendor>/:
- Create the driver
.cand.hfiles. - Implement the same four-function interface.
- Register in the BT device registry.
BT drivers receive HID reports from BTstack instead of TinyUSB, but the normalization and router submission are identical.
Adding a New Input Interface¶
For a new input source (new protocol, new bus type):
-
Create
src/native/host/<protocol>/with<protocol>_host.cand.h. -
Implement
InputInterface: -
Add
INPUT_SOURCE_NATIVE_<PROTOCOL>torouter.h. -
In
task(), poll the controller and callrouter_submit_input()with a normalizedinput_event_t. -
Use device addresses in the 0xD0+ range for native controllers.
-
If the protocol uses non-HID Y-axis convention (like Nintendo controllers), invert Y during normalization.
Adding a New Output Interface¶
For a new console or output device:
-
Create
src/native/device/<console>/with the device driver and any PIO programs. -
Implement
OutputInterface: -
In
core1_task(), read from the router withrouter_get_output(target, slot)and send via PIO. -
PIO programs have a 32-instruction limit. GameCube requires 130MHz overclock via
set_sys_clock_khz(130000, true). -
Use
__not_in_flash_funcfor timing-critical code to keep it in SRAM.
Adding Platform Support¶
To port Joypad OS to a new microcontroller:
-
Implement the platform HAL functions in
src/platform/<platform>/: -
Create the platform-specific build directory (like
esp/ornrf/). -
Implement flash storage backend for the storage service.
-
Implement LED driver if the board has NeoPixel or similar.
See esp/ and nrf/ for complete examples of platform ports.
Common Pitfalls¶
- GameCube requires 130MHz --
set_sys_clock_khz(130000, true)must be called before PIO init. - PIO has 32 instruction limit -- Optimize or split programs across state machines.
- Use
__not_in_flash_func-- For all timing-critical code called from Core 1. - Y-axis convention -- HID standard: 0=up, 128=center, 255=down. Nintendo is inverted.
- ESP32
tud_task()blocks forever -- Always usetud_task_ext(1, false)on FreeRTOS. - BTstack threading -- All BTstack API calls must happen in the BTstack task/thread, not the main task.
CI/CD¶
GitHub Actions (.github/workflows/build.yml) builds all apps on push to main. Docker-based for consistency. Artifacts go to releases/.
Next Steps¶
- Architecture -- Understand the layer model
- Data Flow -- How data moves through the system
- Glossary -- Key terms defined
- Apps -- See how existing apps are structured