Skip to content
All projects

Embedded

Bare-Metal SNES Controller Interface

C firmware that reads an SNES controller directly through Raspberry Pi GPIO and sends button states to a desktop game over serial.

  • Embedded Systems
  • Bare-Metal Programming
  • C
  • GPIO
  • UART
  • Python

Connecting physical input to a desktop game

I connected an SNES controller to a desktop puzzle through custom Raspberry Pi firmware and a Python serial decoder.

The Pi reads the controller’s electrical signals without an operating system. A separate desktop process turns the transmitted button mask into actions in an existing Pygame game.

From electrical signals to button state

Raspberry Pi firmware

  1. LatchCapture button state
  2. Clock × 16Sample the data line
Sampled line… 1 0 1 1 …
Pressed mask… 0 1 0 0 …
Active-low input: 0 on the wire becomes 1 in the pressed mask.
Changed state only ↓Hexadecimal / UART / 115200 baud

Desktop Python host

  1. Decode maskCheck individual button bits
  2. DispatchMove or control the Pygame puzzle
The Pi handles controller timing. The desktop receives a state message and maps its bits to actions. The bit pattern above is illustrative.

Reading the controller directly

I wrote C firmware that reads an SNES controller through Raspberry Pi 4 GPIO registers, without an operating system. The firmware drives the latch and clock lines, then samples the data line sixteen times to reconstruct the controller state.

A pressed SNES button produces a low signal. I convert those samples into a mask where a set bit means pressed, then compare it with the previous state. Only changes are sent over serial, avoiding repeated messages while a button remains held.

The signaling uses a 12-microsecond latch and 6-microsecond clock half-periods. A 33,333-microsecond polling delay targets roughly 30 reads per second; it is a configured interval, not a latency measurement.

Turning serial messages into actions

The firmware sends changed button masks as hexadecimal messages over Mini UART at 115200 baud. On the desktop, a Python decoder checks the message prefix, parses the mask, and tests button bits, including combinations of pressed buttons.

I connected that decoder to an existing Pygame slide puzzle alongside its keyboard and mouse controls. Eight actions map controller input to movement, reset, solving, starting a new game, and quitting. The diagnostic decoder recognizes all twelve button bits.

Firmware and host responsibilities

The Pi handles electrical signaling and state encoding; the desktop handles game input and rendering. That boundary keeps the firmware small and lets the host interpret a button state without depending on GPIO timing.

The firmware builds with supplied AArch64 startup, linker, UART, and timer support. My contribution is the controller polling, state protocol, and Python integration. The puzzle’s board logic and animation come from Al Sweigart’s Pygame slide puzzle.

Tools and technologies

  • C
  • Raspberry Pi 4 / BCM2711
  • AArch64 assembly environment
  • GPIO
  • Mini UART
  • Python
  • pyserial
  • Pygame
  • Make

C accesses BCM2711 GPIO registers, and the GNU AArch64 toolchain builds the freestanding firmware. Python, pyserial, and Pygame receive the UART messages and dispatch desktop actions.