← Back to Explore

I Built a Game Console Into My Split Keyboard

Distributed Pong across two microcontrollers, development metrics dashboard, and QRY logo animations - all in a daily driver Lily58

The Concept

"The ball literally jumps between computers mid-game"

A split keyboard has two halves, each with its own microcontroller and OLED display. Most people use those displays to show layer information or typing statistics. I built a distributed Pong game where the ball travels across both displays via I2C communication, plus a development metrics dashboard that shows real-time coding stats from uroboro.

Technical Architecture

The Hardware Platform

The Lily58 is a split ergonomic keyboard with 58 keys. Each half runs on an ATmega32U4 microcontroller (Pro Micro compatible) with a 128x32 OLED display. The halves communicate via TRRS cable using I2C protocol. This existing infrastructure becomes the foundation for distributed gaming.

Hardware Specifications:

  • Microcontrollers: Dual ATmega32U4 (Pro Micro compatible)
  • Displays: Two 128x32 monochrome OLEDs
  • Communication: I2C via TRRS cable between halves
  • Input: Rotary encoders for analog paddle control
  • Memory: ~21KB firmware, 7.6KB free for features

Distributed Game State

The game field spans 256 pixels across both displays. The left OLED renders pixels 0-127, the right OLED renders 128-255 (mapped to 0-127 locally). Ball position, velocity, and paddle states synchronize between controllers through a custom message protocol.

Game State Structure:

typedef struct {
    bool pong_mode_active;
    uint16_t ball_x;        // 0-255 across both displays
    uint8_t ball_y;         // 0-31 (OLED height)
    int8_t ball_vx;         // Ball velocity X
    int8_t ball_vy;         // Ball velocity Y
    uint8_t left_paddle_y;  // Left paddle position
    uint8_t right_paddle_y; // Right paddle position
    uint8_t left_score;
    uint8_t right_score;
} pong_state_t;

Distributed Pong Implementation

Cross-Display Ball Physics

The ball physics run at 20 FPS with collision detection for walls and paddles. When the ball crosses the center line (x=128), it seamlessly transfers from one display to the other. The visual effect is a ball that appears to fly off one screen and appear on the other.

Ball Transfer Protocol:

// Ball transfer message when crossing displays
typedef struct {
    int16_t ball_y;
    int16_t velocity_x;
    int16_t velocity_y;
    uint8_t trail_length;
    uint8_t special_effects;
} ball_transfer_payload_t;

Paddle Control System

Each keyboard half controls its respective paddle. The left half uses WASD keys, the right half uses arrow keys. Rotary encoders provide analog control for smoother paddle movement. Paddle hits add spin based on where the ball contacts the paddle surface.

Physics Constants:

  • Field Dimensions: 256x32 pixels (spanning both OLEDs)
  • Paddle Height: 8 pixels
  • Ball Size: 2x2 pixels
  • Update Rate: 20 FPS (50ms physics updates)
  • Spin Factor: Paddle velocity affects ball angle

I2C Communication Protocol

Message Architecture

The communication protocol handles game state synchronization, ball transfers, paddle updates, and score changes. Messages include checksums for error detection and sequence numbers for reliability.

Message Types:

  • System Messages: SYNC, HEARTBEAT, ERROR
  • Game Messages: BALL_TRANSFER, PADDLE_UPDATE, SCORE_UPDATE, STATE_SYNC
  • Control Messages: GAME_START, GAME_PAUSE, GAME_RESET
  • Metrics Messages: METRICS_UPDATE, METRICS_REQUEST

Protocol Reliability

Each message includes a magic byte for validation, protocol version, message type, payload length, timestamp, sequence number, and checksum. This ensures reliable communication even with the limited bandwidth of I2C on split keyboards.

typedef struct {
    uint8_t magic;          // Protocol validation
    uint8_t version;        // Protocol version
    message_type_t type;    // Message type
    uint8_t length;         // Payload length
    uint32_t timestamp;     // Message timestamp
    uint8_t sequence;       // Sequence number
    uint8_t checksum;       // Error detection
    uint8_t payload[24];    // Actual message data
} comm_message_t;

Development Metrics Dashboard

uroboro Integration

Beyond gaming, the OLED displays serve as a development command center. The keyboard receives metrics from the host machine via serial communication, showing real-time development statistics during coding sessions.

Available Metrics:

  • uroboro Stats: Daily captures, current streak, productivity level
  • Project Metrics: Commits today, lines added/removed, build status
  • System Info: CPU/memory usage, battery level, uptime
  • Build Status: Last build duration, test coverage, pass/fail state

Display Modes

The keyboard supports multiple display modes switchable via key combinations:

  • Game Mode: Distributed Pong across both displays
  • Metrics Mode: Development dashboard with live stats
  • Logo Mode: QRY Labs branding for presentations
  • Hybrid Mode: Game with minimal metrics overlay
  • Screensaver Mode: Animated logo after idle timeout

QRY Logo Animation System

Branding Integration

The keyboard displays QRY Labs branding during idle periods and layer transitions. The logo uses an 8x8 pixel matrix design that scales cleanly on the OLED displays, with both normal and inverted variants for visual variety.

Logo Animation States:

  • IDLE_BUILDING: Logo builds systematically during startup
  • ACTIVE_WATERMARK: Subtle watermark during typing
  • BREAK_ANIMATED: Full animation during breaks
  • BUILD_LOADING: Loading animation during builds
  • SUCCESS_CELEBRATION: Celebration effect on successful builds
  • ERROR_ALERT: Alert indicators on errors

Professional Presentation

The animated logo system serves practical purposes: professional branding visible during video calls, conversation starter in technical discussions, and visual feedback for keyboard state. The left and right displays show complementary animations - normal logo on left, inverted on right.

QMK Firmware Integration

Layer Architecture

The firmware maintains full keyboard functionality while adding gaming capabilities. Four layers provide complete workflow support:

Keyboard Layers:

  • Layer 0 (DVORAK): Primary typing layer with custom Dvorak layout
  • Layer 1 (SYMBOLS): Programming symbols and F-keys
  • Layer 2 (NAVIGATION): Arrow keys, mouse wheel, navigation
  • Layer 3 (PONG): Gaming controls with paddle movement

Memory Optimization

The ATmega32U4 has limited resources. The firmware uses fixed-point math instead of floating point, efficient data structures, and optimized rendering with dirty rectangle updates. Current firmware uses 73% of available flash (21KB), leaving room for additional features.

Technical Challenges and Solutions

Communication Synchronization

Challenge: Ensuring consistent game state between controllers with limited I2C bandwidth.

Solution: Master/slave architecture where left half controls game logic and sends state updates. Checksums and sequence numbers ensure message integrity.

Real-Time Performance

Challenge: Maintaining smooth gameplay while handling keyboard input.

Solution: Interrupt-based input processing, timer-controlled physics updates, and optimized OLED rendering that doesn't block keyboard scanning.

OLED Display Stability

Challenge: Horizontal line artifacts and display glitches during rapid updates.

Solution: Rate-limited OLED updates (500ms minimum between refreshes), proper clear/draw sequencing, and disabling pixel-level graphics on the slave side during initial debugging.

Why This Matters

Beyond the Gimmick

This project demonstrates several transferable skills: distributed systems design (two controllers maintaining synchronized state), real-time embedded programming (physics updates without blocking keyboard function), and protocol design (reliable messaging over constrained I2C).

Technical Demonstrations:

  • Distributed Systems: Cross-microcontroller state synchronization
  • Real-Time Programming: Physics updates at consistent frame rate
  • Protocol Design: Custom message format with error handling
  • Embedded Constraints: Working within 32KB flash and limited RAM
  • Hardware Integration: OLED displays, rotary encoders, I2C communication

Daily Driver Enhancement

The development metrics dashboard provides actual utility: seeing commit counts, build status, and uroboro capture streaks while typing. The keyboard becomes a development command center rather than just an input device.

Project Status and Future Development

Current Implementation Status:

  • Complete: Game state structures, physics engine, collision detection
  • Complete: Communication protocol with checksums and reliability
  • Complete: QRY logo animation system with multiple states
  • Complete: Four-layer keyboard layout with gaming controls
  • Testing: Hardware deployment and I2C verification
  • Pending: Full uroboro metrics integration via serial

Potential Extensions

  • Additional Games: Snake, Tetris, or Breakout across displays
  • Advanced Physics: Multiple balls, power-ups, particle effects
  • Sound Integration: Buzzer feedback for game events
  • RGB Underglow: Visual effects synced to game state
  • Tournament Mode: Score tracking across sessions