Repository navigation
Architecture
openrb-pico is an RP2040 firmware that reads a rhythm-game instrument — a drum kit over
USB MIDI (the PIO-USB host) or serial MIDI (a UART) — and presents to a console as
an Xbox One pro-drums controller. Guitar (HID) support is in progress. It runs a
FreeRTOS-SMP build across both cores: core1 runs the single bit-banged PIO-USB
host task; core0 runs the USB-device stack and all feature logic. The chip runs at
120 MHz (modules/app/main.cpp set_sys_clock_khz(120000)).
This doc is the authoritative description of the layered C++23 design and the runtime
model. The enforceable rules contributors must follow live in
AGENTS.md; this is the why and the shape.
The code is organized bottom-up under modules/. Each layer is a CMake
INTERFACE/object library that links only lower layers — the link graph in
CMakeLists.txt is the architecture, and the boundary is enforced
in CI (see below).
| Layer | Depends on | What it is |
|---|---|---|
core |
— | Portable, zero-dependency utilities: Result, SpscRing, static_vector, function_ref, lifetime (start_lifetime_as). Header-only, no SDK, no RTOS. |
osal |
core | RTOS abstraction over FreeRTOS: Task<N>, Queue<T,N>, Mutex, Timer, critical, tick chrono. Owns FreeRTOSConfig.h. |
hal |
platform | Compile-time hardware interface (concepts + value types): GpioOut, Uart, Clock, IrqGuard. Zero-cost — no vtables on hot paths. |
platform/pico |
(sdk) | The concrete pico-sdk implementation of the HAL. The only layer, besides the driver seam, meant to touch pico-sdk. |
log |
core, hal | Deferred logging: dlog rings, orb_log front end, usb_log USB-stick sink, the orb_debug.h gate. |
protocol |
core, log | Wire logic: the Xbox One protocol, WLA identifiers. No hardware. |
service |
core, protocol, osal, log, board | Hardware-free feature logic: DrumEngine (fed by both USB-MIDI and serial-MIDI), SerialMidi, InstrumentManager, and Guitar (HID, in progress). No SDK/RTOS/TinyUSB includes (CI-enforced). |
driver |
core, protocol, service, log, hal | The vendor/TinyUSB seam — Xbox host/device drivers, MIDI seam, descriptors, PIO-USB glue. The sanctioned place for extern "C" and TinyUSB includes. |
board |
(pins) | Board pin maps + GPIO actuation (Actuators: LED, hub RESET#, 5 V enable). |
app |
everything | Orchestration / composition root: main, system (wires the object graph), app_tasks, device_session, host_controller, housekeeping, recovery. |
-
Static dependency injection, one composition root. The whole object graph is
constructed once in
modules/app/system.cppand passed by reference; there are no singletons or free-function forwarders. A CI guard (scripts/check-boundary.sh) fails the build if the deleted forwarders reappear anywhere inmodules/. -
No heap. No
new/mallocin firmware paths; tasks, queues, and buffers are staticorb::osalwrappers andconstinitstate. -
The portability boundary is enforced, not aspirational.
check-boundary.shgrepsmodules/service/andmodules/protocol/for forbidden SDK/RTOS/TinyUSB includes and bare FreeRTOS types (TaskHandle_t, …). Onlydriver/andplatform/may touch the vendor stacks. -
void*is confined to a small CI allowlist (theosaltask/timer trampolines,function_ref,lifetime, and the vendor seams); anywhere else it fails CI. -
The HAL is zero-cost. Hardware access goes through compile-time concepts and value
types, so the abstraction costs nothing at
-O2— no vtable indirection on the USB or drum hot paths.
configNUMBER_OF_CORES=2, core affinity on, configMAX_PRIORITIES=8, 1 kHz tick,
configCPU_CLOCK_HZ=120000000 (modules/osal/FreeRTOSConfig.h).
The scheduler itself launches core1 (vTaskStartScheduler() in main.cpp) — the firmware
never calls multicore_launch_core1. All tasks are created in one place,
modules/app/app_tasks.cpp:
| Task | Core | Prio | Role |
|---|---|---|---|
usb_host |
1 | 6 | The only core1 task: PIO-USB host loop (tuh_task, host-tx drain, MIDI read, USB-stick writes, runtime recovery). host_controller.cpp
|
usb_dev |
0 | 6 | Xbox One device stack + device-tx drain. device_session.cpp
|
drum_in |
0 | 5 | Drain MIDI notes + serial MIDI, age hits, emit drum packets. drums.cpp
|
instr |
0 | 5 | Drain the instrument hot-plug event queue. instrument_manager.cpp
|
housekeep |
0 | 4 | Announce cadence, reboot-recovery service, log drain. housekeeping.cpp
|
Inter-core communication is all lock-free rings or osal queues — never a cross-core
spinlock:
-
midi_notes— core1 → core0 (Queue<midi_note_t, 32>): parsed host-MIDI notes. -
host_tx— core0 → core1 (osal::Queue): device-side packets to relay to the host. -
device_tx— both cores →usb_dev(tu_fifo+ a write mutex): outbound Xbox packets. -
adaptercontext — both cores: aligned lock-free volatiles (adapter state, controller address, alive/seen flags). -
dlog/usb_logrings — SPSC, drained on the owning core (see Logging on the wiki).
core1 owns timing-critical PIO-USB. It runs only usb_host, and the following are
forbidden on it (they take spinlocks or yield and will wedge the bus): sleep_ms /
sleep_us, board_millis, blocking or unbounded mutexes, and any non-relaxed
std::atomic (this is an M0+; only relaxed ordering is free). Pre-scheduler code must use
busy_wait_ms, not sleep_ms — a sleep before the scheduler is up deadlocks the boot.
These rules are restated as hard requirements in AGENTS.md.
Four options select the feature/logging profile at compile time; they propagate through the
orb_buildconfig interface lib to every board target. See
BUILDING.md and CMakePresets.json.
| Option | Default | Effect |
|---|---|---|
ORB_DEBUG |
ON | OFF drops all LOG_* + magic_enum reflection (release image). |
ORB_LOG_LEVEL |
DEBUG | Compile-time log floor (NONE…TRACE). |
ORB_LOG_COLOR |
OFF | Bake ANSI color into the UART stream. |
ORB_HIHAT_MODE |
0 | CC-keyed hi-hat mode (see Hi-Hat Mode on the wiki). |
The release preset is ORB_DEBUG=OFF + ORB_LOG_LEVEL=WARN. An orthogonal
ORB_DEBUG_BUILD=ON compiles the project's own sources at -Og with full unwind info for
deep gdb backtraces (see docs/DEBUGGING.md).
How the host stack, the 240 → 120 MHz clock decision, and the FreeRTOS-SMP move came to be is recorded on the wiki: Design History.
Getting started
Features
Background
Repo