Skip to content

N64 Input Interface

Reads native N64 controllers via the joybus-pio library. The single-wire joybus protocol is implemented entirely in PIO for precise timing. Supports the analog stick, all buttons, and rumble pak.

Protocol

  • Bus: Joybus single-wire bidirectional (open-drain with pull-up)
  • Method: PIO state machine via joybus-pio library (src/lib/joybus-pio)
  • Polling: 60Hz (N64 native rate, configurable via N64_POLLING_RATE)
  • Location: src/native/host/n64/

The N64 joybus protocol uses a single data line for bidirectional communication: 1. Host sends a poll command (0x01) via PIO 2. Controller responds with 32 bits of button/stick data 3. Each bit is encoded as a timed pulse (1us low + 3us high = 0, 3us low + 1us high = 1)

Supported Controllers

Device Type Notes
Standard N64 controller 0x0000 Stick + 14 buttons
Controller with rumble pak 0x0002 Auto-detected, rumble initialized after 10 polls
Controller with controller pak 0x0001 Detected but pak not read

Device type is determined from the N64 status response byte.

Button Mapping

N64 Button JP_BUTTON_* Notes
A B1 Primary face button
C-Down B2 Also maps to ANALOG_RY=255
B B3 Secondary face button
C-Left B4 Also maps to ANALOG_RX=0
Z R1 Shoulder/bumper position
L L2 Left trigger (digital)
R R2 Right trigger (digital)
C-Up L3 Also maps to ANALOG_RY=0
C-Right R3 Also maps to ANALOG_RX=255
Start S2
D-pad Up DU
D-pad Down DD
D-pad Left DL
D-pad Right DR

C-buttons are mapped both as digital buttons (B2, B4, L3, R3) and as right analog stick values for dual-stick mode.

Analog Axes

Stick Scaling

The N64 analog stick typically reaches only +/-80 (not the full +/-128 range). The driver scales the raw value to use the full 0-255 range:

scaled = (raw * 127) / 80
output = scaled + 128

This maps the N64's effective range to the full 0-255 unsigned range with 128 as center.

Y-Axis Inversion

The N64 uses inverted Y convention (positive = up). The driver inverts Y during conversion: stick_y = convert_stick_axis(-report.stick_y).

C-Button Right Stick

C-buttons produce digital right stick values via map_c_buttons_to_analog():

C-Button ANALOG_RX ANALOG_RY
C-Left 0 128
C-Right 255 128
C-Up 128 0
C-Down 128 255

N64 L and R triggers are digital only -- ANALOG_L2 and ANALOG_R2 remain at 0.

Connection Detection

  • Connect: N64Controller_IsInitialized() returns true after successful status command
  • Disconnect debounce: 30 consecutive failed polls (~500ms at 60Hz) before reporting disconnect
  • On disconnect, cleared input is submitted to prevent stuck buttons
  • Brief disconnects during pak commands are ignored by the debounce window

Feedback

  • Rumble pak: Auto-detected on connect. Initialized after 10 stable polls (~170ms). Binary on/off control via N64Controller_SetRumble().
  • Rumble rate limiting: Minimum 50ms between rumble commands to prevent blocking the main loop
  • Deferred rumble: n64_host_set_rumble() marks rumble as pending; actual joybus write happens in n64_host_flush_rumble() after time-critical tasks

Configuration

Setting Default Override
N64_PIN_DATA GPIO 4 #define N64_PIN_DATA <pin>
N64_POLLING_RATE 60 Hz #define N64_POLLING_RATE <hz>
N64_MAX_PORTS 1 (future multitap)

PIO assignment: - Default: PIO0, auto-assigned SM and offset - Dreamcast builds (CONFIG_DC): PIO1 SM3 at offset 10 (leaves room for maple_rx at 0-9)

  • Device address range: 0xE0+ (port 0 = 0xE0)
  • Transport type: INPUT_TRANSPORT_NATIVE
  • Input source: INPUT_SOURCE_NATIVE_N64

Apps Using This Input

  • n642usb -- N64 controller to USB HID
  • n642dc -- N64 controller to Dreamcast
  • n642nuon -- N64 controller to Nuon