Skip to main content

Overview

The GamecubeBackend class implements the Joybus protocol for communicating with GameCube consoles. It handles bidirectional communication over a single data line, responding to console polls and sending controller state.
The GameCube backend uses hardware-specific features: PIO state machines on Pico and bit-banging on Arduino.

Platform Support

  • Raspberry Pi Pico: ✅ Supported (PIO-based)
  • Arduino (AVR): ✅ Supported (Nintendo library)

Constructor

Raspberry Pi Pico

GamecubeBackend(
    InputState &inputs,
    InputSource **input_sources,
    size_t input_source_count,
    uint data_pin,
    PIO pio = pio0,
    int sm = -1,
    int offset = -1
)
inputs
InputState&
required
Reference to the global input state structure.
input_sources
InputSource**
required
Array of pointers to input source objects.
input_source_count
size_t
required
Number of elements in the input_sources array.
data_pin
uint
required
GPIO pin number for the Joybus data line (bidirectional communication).
pio
PIO
default:"pio0"
PIO block to use for Joybus communication (pio0 or pio1).
sm
int
default:"-1"
State machine index to use. -1 for automatic allocation.
offset
int
default:"-1"
Program offset in PIO instruction memory. -1 for automatic allocation.

Arduino (AVR)

GamecubeBackend(
    InputState &inputs,
    InputSource **input_sources,
    size_t input_source_count,
    int polling_rate,
    int data_pin
)
polling_rate
int
required
Expected polling rate from the console in Hz (typically 125 or 200). Set to 0 to disable delay compensation.
data_pin
int
required
Arduino pin number for the Joybus data line.
The AVR version uses the polling rate to calculate optimal timing delays, ensuring reports are ready just before the next poll.

Configuration Example

// Pico configuration
const Pinout pinout = {
    .joybus_data = 28,  // GPIO 28 for data line
    // ... other pins
};

GamecubeBackend gc_backend(
    inputs,
    input_sources,
    input_source_count,
    pinout.joybus_data  // data_pin
);
// Arduino configuration
GamecubeBackend gc_backend(
    inputs,
    input_sources,
    input_source_count,
    125,  // polling_rate (125 Hz)
    2     // data_pin (Arduino pin 2)
);

Backend ID

CommunicationBackendId BackendId() {
    return COMMS_BACKEND_GAMECUBE;
}

SendReport Method

void SendReport();
The SendReport() method implements the GameCube polling response protocol:

Pico Implementation

  1. Scans slow and medium-speed inputs
  2. Waits for console poll start signal
  3. Delays 40μs to optimize timing
  4. Scans fast inputs (maximum 40μs old when sending)
  5. Updates output state based on game mode
  6. Maps outputs to GameCube report format
  7. Sends report if poll command is valid
ScanInputs(InputScanSpeed::SLOW);
ScanInputs(InputScanSpeed::MEDIUM);

_gamecube.WaitForPollStart();

busy_wait_us(40);
ScanInputs(InputScanSpeed::FAST);

UpdateOutputs();

if (_gamecube.WaitForPollEnd() != PollStatus::ERROR) {
    _gamecube.SendReport(&_report);
}

Arduino Implementation

  1. Scans all inputs at once
  2. Updates output state based on game mode
  3. Maps outputs to GameCube report format
  4. Sends report to console
  5. Delays until just before next expected poll
ScanInputs();
UpdateOutputs();

_gamecube.write(_data);

delayMicroseconds(_delay);

Report Structure

GameCube controllers report the following:

Digital Buttons

  • A, B, X, Y
  • Z (mapped from buttonR)
  • L, R (trigger digital)
  • Start
  • D-pad: Up, Down, Left, Right

Analog Inputs

  • Main stick: X/Y (0-255, 128 = center)
  • C-stick: X/Y (0-255, 128 = center)
  • L trigger analog (0-255)
  • R trigger analog (0-255)

Special Mappings

// Select and Home mapped to D-pad
_report.dpad_left = _outputs.dpadLeft | _outputs.select;
_report.dpad_right = _outputs.dpadRight | _outputs.home;
The Select button is mapped to D-pad Left and Home is mapped to D-pad Right since GameCube controllers don’t have these buttons natively.

Polling Rate and Timing

Pico (PIO-based)

  • Uses hardware state machines for precise timing
  • Automatically synchronizes with console polls
  • 40μs optimization window for minimal input lag

Arduino (Software-based)

  • Requires manual polling rate configuration
  • Calculates delay: (1000000 / polling_rate) - 850
  • Leaves 850μs for processing before next poll

GetOffset Method (Pico Only)

int GetOffset();
Returns the PIO program offset, useful for sharing PIO resources between multiple backends.

Hardware Requirements

Signal Line

  • Voltage: 3.3V logic (use level shifter if needed)
  • Pull-up: 1kΩ resistor to 3.3V
  • Cable: Connect to controller port data line

Pin Configuration

  • Bidirectional data line (input/output)
  • Must support fast GPIO operations
  • Pico: Any GPIO pin
  • Arduino: Pin 2 recommended (interrupt capable)

Usage Example

#include "comms/GamecubeBackend.hpp"

const Pinout pinout = {
    .joybus_data = 28,
};

static InputState inputs;
static InputSource *input_sources[] = { &gpio_input };
size_t input_source_count = 1;

// Pico
GamecubeBackend gc_backend(
    inputs,
    input_sources,
    input_source_count,
    pinout.joybus_data
);

// Arduino
GamecubeBackend gc_backend(
    inputs,
    input_sources,
    input_source_count,
    125,              // 125 Hz polling
    2                 // Pin 2
);

void loop() {
    gc_backend.SendReport();
}

Console Detection

The GameCube backend can be automatically selected based on console detection:
CommunicationBackendConfig backend_config = {
    .backend_id = COMMS_BACKEND_GAMECUBE,
};

Performance Notes

  • Pico: Uses hardware PIO for precise timing, minimal CPU overhead
  • Arduino: Software-based, requires careful timing calibration
  • Polling delay: Optimized to minimize latency while ensuring stability
  • Input lag: Typically under 1ms with proper configuration

Troubleshooting

If the console doesn’t detect the controller:
  • Check data pin connection and pull-up resistor
  • Verify voltage levels (3.3V for Pico)
  • Ensure polling rate matches console (125-200 Hz)
  • Check for proper grounding between controller and console

See Also

Build docs developers (and LLMs) love