pi_gpio_joypad.sg (sigil-drivers 9fe4d9b) adds a BCM2835/BCM2711 GPIO digital joypad driver — the native input layer for Pi arcade HATs and DIY joypads wired directly to GPIO header pins. Active-low buttons (pressed = pin low, floating high via pull-up resistors), SNES-style default 12-button pin mapping, sim-safe init path, snap/restore rollback support. 13 checks PASS. State at 0x9F0000. The Pi input driver stack is now complete: GPIO joypad + BT HID host + Pi BT UART transport.
What Pi GPIO joypad covers
Pi arcade HATs (e.g. Waveshare Arcade-C-HAT, Retroflag GPIcase, custom Pi0 arcade sticks) wire joystick microswitches and buttons directly to BCM GPIO pins through the 40-pin header. There is no USB, no Bluetooth, no HID stack — the game controller is the GPIO level. pi_gpio_joypad.sg reads GPLEV0 (the GPIO level register) and maps pin states to the sigilOS joypad ABI.
The active-low protocol
All buttons use a pull-up resistor (the BCM2835/BCM2711 GPIO pull-up can be configured in hardware). The idle state of each pin is logic HIGH (1) — the pull-up holds it there. When a button is pressed, it shorts the pin to GND, pulling it LOW (0). The driver inverts: button = 1 - (lev / pin_div % 2) — where lev is the GPLEV0 word and pin_div is the bit position divisor for the pin (e.g. GPIO 2 → divisor 4, GPIO 17 → divisor 131072). A 0 in GPLEV0 at that bit → button is pressed (output 1). A 1 → button is released (output 0).
Default SNES-style pin mapping
The default mapping matches the most common Pi arcade HAT convention — SNES-style face layout with L/R shoulders:
| Button | GPIO pin | Bit divisor | Notes |
|---|---|---|---|
| Up | 2 | 4 | D-pad |
| Down | 3 | 8 | D-pad |
| Left | 4 | 16 | D-pad |
| Right | 17 | 131072 | D-pad |
| B | 27 | 134217728 | Face (bottom) |
| A | 22 | 4194304 | Face (right) |
| Y | 10 | 1024 | Face (left) |
| X | 9 | 512 | Face (top) |
| L | 11 | 2048 | Left shoulder |
| R | 23 | 8388608 | Right shoulder |
| Select | 8 | 256 | Meta |
| Start | 7 | 128 | Meta |
The pin map is compiled-in at driver init. Remapping is possible by changing the pin_map[] array — all downstream code uses the logical button index, not the GPIO pin number.
Driver API (surface-rows)
hw=0: sim-safe mode — skip MMIO configure_pins() (no GPLEV0 MMIO on x86/QEMU, test with injected GPLEV0 words instead). hw=1 or PI_GPIO_OP_HW_INIT: real Pi mode — configure BCM pull-ups via the GPIO function select and pull-up/pull-down control registers at PBASE+0x200000 (Pi3: 0x3F200000, Pi4: 0xFE200000). Sets state base 0x9F0000.peek32(GPLEV0) to read the GPIO level register. Applies active-low inversion per pin: button = 1 - (lev / pin_div % 2). Returns a 12-bit button word (Up/Down/Left/Right/B/A/Y/X/L/R/Select/Start). In sim mode (hw=0), GPLEV0 can be set by the test harness via poke32(GPLEV0, sim_val).Sim-safe init model
The driver follows the same sim-safe pattern as other sigilOS hardware drivers: init(0) is safe to call in x86 QEMU, where GPLEV0 MMIO does not exist. The hardware configure step (configure_pins()) is gated on hw != 0. This allows the full 13-check test suite to run on x86 without requiring actual Pi hardware:
// x86 QEMU test flow (hw=0):
pi_gpio_joy_init(0) // skip configure_pins; set state base
poke32(GPLEV0, 0xFFFFFFF7) // simulate GPIO 3 low (Down pressed)
st = pi_gpio_joy_read() // active-low: bit3=0 → down=1
assert(st.down == 1) // PASS
// Pi hardware flow (hw=1):
pi_gpio_joy_init(PI_GPIO_OP_HW_INIT) // configure BCM pull-ups
loop: st = pi_gpio_joy_read() // live GPLEV0 read per frame
The Pi input driver stack — now complete
| Driver | Transport | Address | Buttons/axes | PASS |
|---|---|---|---|---|
pi_gpio_joypad.sg |
BCM GPIO GPLEV0 (Pi header) | 0x9F0000 | 12 digital buttons | 13 |
pi_bt_hid.sg + pi_bt_uart.sg |
BT H4/HCI over PL011 UART → ACL→L2CAP→HID | 0x9D8000 | Routes to xbox_bt (16B, 4×16-bit axes) | 14 |
sw_pro_bt.sg |
Bluetooth HID (057E:2009) | 0x9E0000 | 12 buttons + 4×12-bit sticks | 24 |
ds4_bt.sg |
Bluetooth HID (054C:05C4/09CC) | 0x9C0000 | 17 buttons + gyro + accel + touchpad | 21 |
xbox_bt.sg |
Bluetooth HID (045E:02FD) | 0x9A0000 | 16 buttons + 4×16-bit axes + 2 triggers | — |
usb-hid-gamepad |
USB HID | — | 16 buttons + 4×8-bit axes | — |
The GPIO joypad fills the last slot: wired arcade HATs and DIY builds that bypass both USB and Bluetooth entirely. With this driver, every Pi controller scenario is handled in Sigil — from a bare GPIO microswitch wired to header pins to a Switch Pro Controller over Bluetooth.
All drivers share the same rollback ABI: snap() / rest() records and restores controller state for SRDX Mode-2 rollback netplay. The GPIO joypad's state is a single 32-bit GPLEV0 word, making it the cheapest driver to snap.