Skip to the content
PCBKitNew board
2 layers65 × 56 mmRp2350-rgb pcb, the printed circuit board in 3D

Rp2350-rgb pcb

will-davis/rayglow1

Audio-reactive GLSL visuals on an RGB LED matrix

Size
65 × 56 mm
Copper layers
2
Thickness
1.6 mm
Parts
22
Nets
56
Pads
182
Tracks, vias
516, 58
Pours
1
Routed
Yes
KiCad
KiCad 10.0 (file 20260206)
Created
7 Jun 2026
Updated
8 Sep 2026

Overview

Rp2350-rgb pcb is an open-source printed circuit board by will-davis, from the will-davis/rayglow repository on GitHub, published under the MIT licence. It is a 2-layer board of 65 × 56 mm with 22 parts on 56 nets, routed with 516 tracks and 58 vias.

Its parts include 5 capacitors, 5 connectors, 4 ICs and 2 resistors. It belongs with the lEDs and displays and audio and music boards and the RP2350 boards of this library.

README

README.md

RayGLow

Audio-reactive GLSL visuals on a custom RGB LED wall. Music plays on a desktop; a small daemon there reduces it to per-frame audio features and unicasts them over UDP to a Raspberry Pi 5, which renders Shadertoy-dialect GLSL on its GPU, packs each frame, and ships it over a 4-lane parallel bus to an RP2350b microcontroller whose PIO+DMA engine drives the HUB75 panels with zero-CPU, jitter-free timing. Write a shader, paste it in, watch it move to the music.

https://github.com/user-attachments/assets/fb1bfaf4-e451-43c7-8cc3-830d7c186d91

text
desktop (optional)                 Raspberry Pi 5                         RP2350b PiZero + HAT
┌────────────────────────┐         ┌────────────────────────────┐         ┌───────────────────────┐
│ music ▶ sink monitor   │  UDP    │ feed.receiver (latest-win) │  4-lane │ phase6: PIO+DMA RX    │
│ sender.py: FFT ▶ bands │ ──────▶ │ render: GLSL ▶ pack        │   bus   │ zero-CPU scan-out     │
│ + flywheels/beat/key   │  :5005  │ (to_chains ▶ hub75.pack)   │ ──────▶ │ ▶ HUB75 ▶ 384×128 wall│
│ ▶ 2996-B v3 @ ~60 Hz   │ ~180KB/s│ headless EGL + GLES3       │ 192 KB  │ (2 chains × 12 panels)│
└────────────────────────┘         └────────────────────────────┘         └───────────────────────┘
        point $RAYGLOW_HOST ──────────────▶ your Pi

The three stages live in one repo. The desktop and Pi halves are Python: the desktop runs sender/, the Pi runs the rayglow package installed editable (pip install -e) from a copy of this repo — get it there however you like (git pull, or a continuous file sync such as mutagen/sshfs for live shader editing). The RP2350b half is Rust firmware (firmware/) flashed onto the board, plus a custom level-shifting HAT (hardware/). First-time setup is in Deploy; full credits and licensing are in ATTRIBUTION.md (RayGLow is MIT — see LICENSE).

The desktop sender is optional: rayglow.fake_sender synthesizes the same packet struct, so the Pi can drive the wall with no music source at all.

Why features, not streamed audio

Streaming PCM means reconstructing a continuous signal on the far end: ring buffers, clock-drift resampling, jitter buffers. Features are stateless per frame — a lost or late packet just means the Pi renders with the previous values, and at 60 Hz one held frame is invisible. The Pi does zero audio work, the wire carries ~0.25 MB/s, and UDP gets used the way UDP wants to be used.

Why offload scan-out to the RP2350b

Bit-banging HUB75 from a Linux SoC fights the scheduler — it needs a dedicated RT core and still jitters (the Pi 5's RP1 southbridge made it worse). The RP2350b runs the refresh loop entirely in 3 PIO state machines + 4 self-chaining DMA channels, with the CPU never touching the pixel path. The Pi renders at whatever rate it likes and ships 192 KB frames over the link — already gamma-corrected on the Pi (the GPU resolve pass bakes the firmware lut.rs CIE curve into each frame; --readback legacy instead applies the packer's bit-exact LUT replica to a LINEAR readback); the RP2350b holds a rock-steady, flicker-free refresh.

Why a parallel bus, not SPI

The link was originally 1-lane SPI, and SPI still works as the proven fallback (--transport spi). But a frame takes ~13 ms at 40 MHz SPI even at the v1 wall's 64 KB — ~39 ms at the v2 wall's 192 KB — long enough to sit squarely on the critical path. A 4-lane source-synchronous parallel bus, clocked out by the Pi 5's RP1 PIO block, drops that to ~2 ms (clkdiv 3; ~1.3 ms at clkdiv 2). Only the wire changes: the byte stream is identical to the SPI path (same hub75.py packer output, same READY handshake), so the firmware and packer don't change. (4 lanes, not 8: the RP2350b scan-out engine owns GP0–18, leaving GP19–27 for the link.) See rayglow/render/piobridge/README.md.

Hardware

RayGLow custom HAT

A Waveshare RP2350-PiZero on a custom HAT that level-shifts (3.3 V → 5 V via SN74AHCT245) and breaks out two HUB75 chains + the Pi↔RP2350b link. Design files, the KiCad project, and fab Gerbers are in hardware/.

Repo layout

pathwhat
sender/desktop half — the feature daemon (sender.py), its own uv project (numpy + sounddevice). sender/README.md has the protocol + feature detail.
rayglow/feed/the audio-feature feed: packet receiver, FeatureState, and the rig config (geometry/network/gamma). Shared, dependency-free.
rayglow/render/the live renderer — headless EGL + GLES3, multipass pipeline, iChannel textures (audio, milk, noise, images), hot reload, a TCP control plane (control.py: push/switch shaders + media controls), the GPU resolve pass + zero-copy dma-heap readback (output.py, dmabuf.py), the frame packer (hub75.py) + link backends (spi_out.py, pio_out.py / piobridge/), and presets/*.glsl.
rayglow/fake_sender.pymusic-free test harness; emits the same packet struct with synthesized features.
rayglow/link.pyremote render wire contract — frame fragments out, flip credits back (UDP, latest-wins, credit-paced; see REMOTE-RENDER-PLAN.md). Shared by render/net_out.py (--output net) and framesink.
rayglow/framesink.pythe Pi when rendering happens elsewhere: reassemble frames → drm_out page flip → credit per vblank. A socket and a memcpy — no GL stack.
rayglow.example.tomlper-machine CLI defaults (rayglow/userconf.py): pin each machine's flags (output, net-host, window, …) in ~/.config/rayglow/config.toml so production launches are just python -m rayglow.render <shader> / python -m rayglow.framesink. CLI flags always override.
rayglow/spi_test.pystatic test pattern (no GL) — isolates the link/firmware/cabling from the renderer. Per-panel ID markers confirm the serpentine fold; runs over either transport.
firmware/RP2350b Rust firmware — zero-CPU PIO+DMA HUB75 scan-out, brought up in verifiable phases. firmware/README.md.
hardware/custom HAT — KiCad project, Gerbers, and the locked net/pinout spec. hardware/README.md.
tools/verify.py — proves the Python packer (render/hub75.py) is byte-identical to the firmware via a Rust golden frame. fold_check.py — proves the serpentine fold + the geometry/SRAM contract from the desk. link_check.py — locks the remote-render frame link (fragments/credits/pacing) over loopback. dmabuf_probe.py — the readback benchmark.
ROADMAP.mdqueued workstreams — the living handoff doc between working sessions.
docs/design-history/the original project record (MilkDrop reverse-engineering, the RP2350 plan, the build-history brain-dump). Superseded by the docs above where they disagree.

Quickstart

Desktop (sender, optional):

fish
cd sender
set -x RAYGLOW_HOST 192.168.0.50   # your Pi's IP (or pass --host); see LOCAL-SETUP.example.md
uv run sender.py                   # capture default sink's monitor -> the Pi
uv run sender.py --list-sources    # enumerate pulse sources
uv run sender.py --debug           # + raw pre-normalization band energies

Pi (renderer) — see Deploy for first-time setup:

fish
# headless, no root, no hardware: render -> animated GIF (works on a desktop GPU too)
python -m rayglow.render rayglow/render/presets/milk-verbose.glsl --dry-run 120 --no-listen

# on the Pi, driving the panels (root for GPIO):
sudo ~/venv/bin/python -m rayglow.render rayglow/render/presets/milk-verbose.glsl

milk-verbose.glsl is the live-feed reference card — every audio feature drawn as a labeled bar. Run the sender on the desktop at the same time to see it react.

Exact temporal captures for agents and tools:

fish
uv run python -m rayglow.render rayglow/render/presets/milk-verbose.glsl \
  --no-listen --sample-mode direct --sample-reference-fps 120 \
  --width 384 --height 128 \
  --sample-dir /tmp/rayglow-samples --sample-manifest /tmp/rayglow-samples.json \
  --sample-time 0 --sample-time 0.5 --sample-time 1.5 \
  --sample-time 3 --sample-time 5 --sample-time 8

Sampling writes lossless RGB PNGs and an atomic version 1 JSON manifest with deterministic uniforms and panel-preview-rgb8 color. It runs headlessly, without live sockets or hardware output. Direct mode evaluates only the requested times and rejects Buffer A–D siblings; sequential mode is reserved. See the temporal-sample contract for pixels, source determinism and failure semantics. Verify it with uv run tools/sample_check.py.

Deploy

Renderer (Raspberry Pi 5). The Pi runs the same git checkout, installed editable into a uv venv:

fish
# on the Pi, first time:
git clone <your-fork-url> ~/rayglow
cd ~/rayglow
uv venv ~/venv
uv pip install --python ~/venv/bin/python -e '.[pi]'   # core + Pi link deps
# thereafter: update the checkout (git pull, or let your file sync — e.g. a
# mutagen session pushing from the dev machine — keep it current)

.[pi] pulls the Pi-only deps (spidev, gpiozero, lgpio); EGL/GLES come from system libraries, not pip. Editable install (not PYTHONPATH) because sudo scrubs the environment but respects the installed package; hardware mode keeps root for GPIO and to re-read shader files on hot reload, so the clone must live somewhere root can read (e.g. under ~).

  • Parallel link (default, --transport pio): build piobridge/libpioshim.so against the RP1 piolib first — see rayglow/render/piobridge/README.md.
  • SPI fallback (--transport spi): enable SPI and make sure /sys/module/spidev/parameters/bufsiz >= 196608 so the v2 wall's 192 KB frame goes in one transfer (the default 4096 is far too small; size it to hub75.FRAME_BYTES).

Per-machine addresses/paths: see LOCAL-SETUP.example.md.

Firmware (RP2350b). Flash the board once: phase6-parallel --features two-chain for the 4-lane bus (production) or phase5-spi for the SPI fallback. Toolchain bootstrap and cargo run/probe-rs instructions are in firmware/README.md.

Status

Working end-to-end: the desktop sender feeds the renderer, which packs frames the firmware accepts byte-for-byte (tools/verify.py is green). The renderer hot-reloads .glsl files live (edit, save, watch the panel recompile); for the fast dev loop a TCP control plane (tools/rayglow_ctl.py push, or the tools/nvim-rayglow.lua save-hook) ships edits straight to the running renderer in <100ms — skipping the file-sync lag — and adds media controls (next/prev/play/pause/loop) plus live per-shader supersample scale (// rayglow: scale=N directive or rayglow-ctl scale). The Phase 6 4-lane parallel bus is the production transport (hardware-verified on the custom HAT driving the 256×64 v1 wall; the 384×128 v2 wall is in bring-up — host + firmware ship, cabling/SI pending, see ROADMAP §5); the Phase 5 SPI link remains the proven fallback. A microphone input path exists (sender/esp32-mic/ + sender/espnow-dongle/). Next: pulling config into a user-editable yaml so the project runs on someone else's wall/network by editing one file.

Parts

22
QtyValueFootprintReferences
6MountingHole_3.2mm_M3MountingHole:MountingHole_3.2mm_M3, , , , ,
4100nFCapacitor_SMD:C_0805_2012MetricC1, C2, C3, C4
4SN74AHCT245DWRPackage_SO:SOIC-20W_7.5x12.8mm_P1.27mmU1, U2, U3, U4
222Resistor_SMD:R_0805_2012MetricR1, R2
1+5V_INConnector_JST:JST_XH_B2B-XH-A_1x02_P2.50mm_VerticalJ5
1100uFCapacitor_SMD:CP_Elec_6.3x7.7C5
1HUB75_AConnector_IDC:IDC-Header_2x08_P2.54mm_VerticalJ2
1HUB75_BConnector_IDC:IDC-Header_2x08_P2.54mm_VerticalJ3
1RP2350-PiZeroConnector_PinSocket_2.54mm:PinSocket_2x20_P2.54mm_VerticalJ1
1SPI_SYNCConnector_PinHeader_2.54mm:PinHeader_1x08_P2.54mm_VerticalJ4

Questions

How big is the Rp2350-rgb pcb PCB?

It measures 65 × 56 mm and has 2 copper layers, 1.6 mm thick.

What parts does the Rp2350-rgb pcb use?

22 parts. The full list of its parts is on this page, with links to each part's symbol and footprint where PCBKit's library has them.

Where can I download the Rp2350-rgb pcb KiCad files?

From the will-davis/rayglow repository on GitHub, at the commit this page was made from: the board is hardware/rp2350-rgb-pcb/rp2350-rgb-pcb.kicad_pcb and its schematic hardware/rp2350-rgb-pcb/rp2350-rgb-pcb.kicad_sch. PCBKit opens the board in the browser, with nothing to install.

Can I use the Rp2350-rgb pcb design in my own project?

Its repository publishes it under the MIT licence: you may reuse it on that licence's terms (keep the author's notice, and for a copyleft licence share your changes alike).

Similar boards

  • 2 layers158 × 11 mmDaylight Matchstick, the printed circuit board in 3D

    Daylight Matchstick

    VoronDesign/Voron-Hardware

    Cool Hardware designed by the Voron Design group

    22 parts1.1kGPL-3.0
  • 2 layers57 × 65 mmRgb to hdmi, the printed circuit board in 3D

    Rgb to hdmi

    hoglet67/RGBtoHDMI

    Bare-metal Raspberry Pi project that provides pixel-perfect sampling of Retro Computer RGB/YUV video and conversion to HDMI

    22 parts1kGPL-3.0
  • 2 layers81 × 57.2 mmRP2040-Macropad, the printed circuit board in 3D

    RP2040-Macropad

    hackclub/OnBoard

    💡 Join 1,000 teenagers and make your first PCB with a $100 grant!

    22 parts915
  • 2 layers54.6 × 70.9 mmBeagleLogic Cape, the printed circuit board in 3D

    BeagleLogic Cape

    abhishek-kakkar/BeagleLogic

    A Software Suite that implements a logic analyzer with the PRU on the BeagleBone / BeagleBone Black.

    22 parts479GPL-3.0
  • 2 layers66.4 × 24.5 mmConnecting piece, the printed circuit board in 3D

    Connecting piece

    Chrismettal/flipper-zero-backpacks

    Backpack-style addon boards for the Flipper Zero!

    22 parts456
  • 2 layers51 × 70 mmDMG A04-01, the printed circuit board in 3D

    DMG A04-01

    HDR/NintendoPCBs

    Replacement pcb's for Nintendo games.

    22 parts321
  • 2 layers27.8 × 16 mmSpi 1v8 level shifter, the printed circuit board in 3D

    Spi 1v8 level shifter

    CRImier/MyKiCad

    My KiCad boards. Can't be bothered uploading individually anymore, so here you go.

    22 parts253GPL-3.0