Skip to content

JosePFs/psp-rp2040

Repository files navigation

PSP Controller for RP2040

An embedded firmware implementation for the RP2040 that transforms analog joystick inputs into USB HID joystick or keyboard reports. Built with Rust using Hexagonal Architecture and Domain-Driven Design principles.

Overview

This project implements two USB HID products that read analog inputs from two ADC channels (X and Y axes):

  • Joystick: Reports standard HID joystick data (X/Y axes -32768 to 32767)
  • Keyboard: Maps joystick direction to arrow keys (Up/Down/Left/Right)

The firmware is designed with clean architecture principles, ensuring testability and maintainability through clear separation of concerns.

Architecture

The project follows Hexagonal Architecture (Ports & Adapters) with Domain-Driven Design:

  • Domain Layer (domain/): Core business logic and domain entities

    • Joystick: Domain entity representing the joystick abstraction
    • JoystickData: Value object containing X/Y coordinates
    • Ports: Interfaces defining contracts (AnalogicReader, AnalogicConverter)
  • Application Layer (application/): Orchestrates domain logic

    • JoystickReadUseCase: Encapsulates the business logic for reading and writing joystick report
    • HealthService: Encapsulates health monitor system
  • Infrastructure Layer (infrastructure/): Hardware adapters and framework integration

    • common/: Shared adapters (ADC, USB, channel, signal, health monitor)
    • joystick/: HID joystick report writer and bootstrap
    • keyboard/: HID keyboard report writer and bootstrap
    • Embassy framework integration for async embedded operations

Hardware Requirements

  • Raspberry Pi Pico (RP2040)
  • Analog joystick module connected to:
    • X-axis: GPIO 26 (ADC0)
    • Y-axis: GPIO 27 (ADC1)
  • USB connection for HID communication

Prerequisites

  • Rust toolchain (stable or nightly)
  • thumbv6m-none-eabi target: rustup target add thumbv6m-none-eabi
  • flip-link linker: cargo install flip-link
  • picotool for flashing (or probe-rs for debugging)

Building and Flashing

The project uses custom cargo configuration in .cargo/config.toml:

Build

# Build joystick firmware
cargo build-joystick

# Build keyboard firmware
cargo build-keyboard

# Or build all
cargo build --release

Flash and Run

The default runner is configured to use picotool:

# Joystick
cargo run-joystick

# Keyboard
cargo run-keyboard

This will:

  1. Build the firmware
  2. Flash it to the connected RP2040 (must be in boot mode)
  3. Execute the firmware

Development with Logging

Enable USB CDC-ACM logging for debugging:

cargo run-joystick-dev   # Joystick with USB logging
cargo run-keyboard-dev  # Keyboard with USB logging

This enables:

  • USB CDC-ACM serial logging
  • Debug log level output over USB

Alternative: Using probe-rs

To use probe-rs instead of picotool, uncomment the corresponding line in .cargo/config.toml:

runner = "probe-rs run --chip RP2040 --protocol swd"

Testing

The domain and application layers include unit tests that run on the host:

# Test domain layer
cargo test-domain

# Test application layer
cargo test-application

These aliases are configured in .cargo/config.toml to run tests with the native target.

Docker

# Build image
docker build -t psp-rp2040-ci .

# Run image with volume
docker run -it --rm \
  -v $(pwd):/app \
  -w /app \
  psp-rp2040-ci

# Run tests
cargo test-domain
cargo test-application

# Build firmware
cargo build --release

This docker image and tests are configured in .github/workflows/ci.yml to run tests on push and build native target.

Project Structure

psp-rp2040/
├── domain/                  # Core domain logic and ports
│   └── src/
│       ├── entity/          # Joystick and Health entities
│       ├── port/            # Outbound port interfaces
│       ├── vo/              # Joystick and Health reading intervals
│       ├── error.rs         # Centralized result and error
│       ├── event.rs         # Domain events
│       └── lib.rs           # Modules
├── application/            # Use cases
│   └── src/
│       ├── port/            # Inbound port interfaces
│       ├── use_case/        # Business use cases
│       ├── service/         # Business services
│       └── lib.rs           # Modules
├── infrastructure/          # Hardware adapters and dependencies
│   ├── common/              # Shared components (channel, signal, config, hardware)
│   ├── joystick/            # Joystick HID bootstrap and report writer
│   └── keyboard/            # Keyboard HID bootstrap and report writer
├── psp-rp2040-joystick/     # Binary crate (joystick composition root)
│   ├── src/main.rs          # Entry point
│   └── Embed.toml           # probe-rs configuration
├── psp-rp2040-keyboard/     # Binary crate (keyboard composition root)
│   ├── src/main.rs          # Entry point
│   └── Embed.toml           # probe-rs configuration
└── .cargo/
    └── config.toml          # Configuration and env variables

Features

  • USB HID Joystick: Standard HID joystick reports with X/Y axes (-32768 to 32767)
  • USB HID Keyboard: Maps joystick direction to arrow keys (Up/Down/Left/Right)
  • Analog Input Processing: Reads 12-bit ADC values and scales to 16-bit signed integers
  • Async Architecture: Built on Embassy executor for efficient async/await support
  • Testable Design: Domain and application layers are fully testable without hardware dependencies

Technical Details

  • Target: thumbv6m-none-eabi (ARM Cortex-M0+)
  • Framework: Embassy (async embedded Rust)
  • USB Stack: embassy-usb with usbd-hid for HID reports
  • ADC: RP2040 ADC with interrupt-driven async reads
  • Polling Rate: 10ms (100 Hz) for joystick updates

License

Dual-licensed under MIT OR Apache-2.0.

About

Joystick firmware for RP2040 using Rust and Embassy, ​​DDD and hexagonal architecture

Topics

Resources

License

Apache-2.0, MIT licenses found

Licenses found

Apache-2.0
LICENSE-APACHE-2.0
MIT
LICENSE-MIT

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages