prosolis 138efc28ca BOM: confirm the plain Zero and Zero W as verified fallbacks
The BOM claimed "Zero W also works" as an aside. Checked it: Circle's
CHANGELOG gives gadget support for "Zero (2) (W)", the parentheses including
the plain Zero, and the README lists Raspberry Pi Zero as Tested with no
caveat. build.sh already emits kernel.img for RASPPI=1, and the MIDI gadget's
string descriptors are present in that image, so no build change is needed
for either board.

Keeping the Zero 2 W as the specified part on availability grounds. Records
that a plain Zero would permanently close the WiFi option in section 3a,
since it has no radio silicon at all.

Claude-Session: https://claude.ai/code/session_01TVCB25LBsmeteWvaSMz4Ne
2026-08-27 22:28:15 -07:00
2026-08-27 22:09:47 -07:00
2026-08-27 22:09:47 -07:00
2026-08-27 22:09:47 -07:00
2026-08-27 22:09:47 -07:00
2026-08-27 22:09:47 -07:00
2026-08-27 22:09:47 -07:00
2026-08-27 22:09:47 -07:00

Piano LED Visualizer — Circle firmware

Bare-metal firmware for a Raspberry Pi Zero that lights a WS2812B strip above an 88-key keybed in response to MIDI.

The design rationale, bill of materials, electrical notes and project phases live in PIANO-LED-CIRCLE-PLAN.md. This file covers only how to build and run the firmware (plan Phase 1).

What this is

The Pi is a USB MIDI gadget. The PC is the host and owns everything else — the piano connection, the learning software, the song library. From the PC's side this firmware is just another ALSA MIDI output port:

Casio PX-S7000 --USB-B--> PC --USB--> Pi Zero (this firmware) --> WS2812B strip

There is no network stack, no filesystem, no shell. The Pi boots into this firmware in about a second and does one job.

Circle has no OTG support, so the USB controller is gadget-only here. The piano cannot be plugged into the Pi directly; all MIDI arrives from the PC.

Layout

Path
firmware/config.h Every tunable. Start here.
firmware/pianoleds.cpp Note-to-LED mapping, colour, brightness clamps.
firmware/kernel.cpp USB gadget lifecycle and the main loop.
tests/ Host-side tests for the mapping and clamps.
circle/ Circle as a submodule, pinned to Step51.
build.sh Builds both kernel images.

Building

Circle is a submodule pinned to Step51, so clone recursively:

git clone --recursive <this repo>
# or, in an existing clone:
git submodule update --init

Then:

sudo apt-get install gcc-arm-none-eabi     # Debian/Ubuntu
./build.sh

The pin is deliberate. A pinned Circle tree still builds in five years; nothing here tracks a moving upstream.

This produces two images in boot/, which coexist on one card:

Image RASPPI Model
kernel.img 1 Pi Zero / Zero W (ARM1176)
kernel7.img 2 Pi Zero 2 / Zero 2 W (Cortex-A7)

The Pi picks the right one at boot, so the same SD card runs on either model.

SD card

FAT32, single partition. Copy in:

  • boot/kernel.img and boot/kernel7.img
  • From the Raspberry Pi firmware repo boot/ directory: bootcode.bin, start.elf, fixup.dat
  • cmdline.txt (optional). Circle reads kernel options from it; useful for raising the log level while bringing the board up.

Nothing is ever written to the card at runtime, so pulling the power mid-note cannot corrupt it.

Wiring

Verified against circle/addon/WS28XX: CWS28XXStripe clocks the WS2812B waveform out over SPI at a fixed 6.4 MHz, encoding one LED bit per SPI byte. On SPI master device 0 that puts the data line on:

MOSI = GPIO10 (BCM) = physical pin 19.

Three things from plan section 7 that are not optional:

  • Level shifter. WS2812B wants logic high at 0.7 × VDD = 3.5V on a 5V rail; the Pi's GPIO is 3.3V. Put a 74AHCT125 on the data line. Skipping this is the single most common cause of "the strip flickers intermittently".
  • Common ground. The Pi's ground and the LED supply's ground must be tied.
  • Power injection. Feed 5V at both ends of the strip.

Connect the PC to the Zero's USB port, not PWR, with a data cable.

Configuring

Everything is in firmware/config.h, and every value there is a Phase 0 question. Two are load-bearing:

  • STRIP_REVERSED — whether pixel 0 sits at the bass or treble end. Decide this after the strip is physically mounted; it is one flag to flip.
  • GLOBAL_BRIGHTNESS and MAX_LIT_KEYS — the power clamps. 176 LEDs at full white would draw ~10.5A against a 6A supply. Real playing never comes close, but these two make a whited-out strip unreachable rather than merely unlikely. Do not raise them without redoing the arithmetic in plan section 7.

Any of them can also be overridden at build time without editing the file:

make -C firmware EXTRADEFINE=-DSTRIP_REVERSED=1

MIDI behaviour

  • Notes 21108 (A0C8) map to the strip; anything outside is dropped.
  • Note On with velocity 0 is treated as Note Off.
  • CC 120 (All Sound Off) and CC 123 (All Notes Off) clear the strip.
  • Notes on HINT_MIDI_CHANNEL (default channel 16) light in a separate colour, for the plan's Phase 3 "light the next key to play". A key actually being played takes precedence over a hint on the same key.
  • Notes held when the USB host suspends are cleared, so nothing stays lit.

Tests

./tests/run.sh

Compiles the real firmware/pianoleds.cpp against stubbed Circle headers and exercises the mapping, the note-off paths, the range clamping and both power clamps across nine configuration variants. This does not need the ARM toolchain and does not replace bench-testing on real hardware — it checks the arithmetic, not the wiring.

S
Description
No description provided
Readme
139 KiB
Languages
C++ 58.6%
C 18.9%
Shell 11.6%
CMake 9.9%
Makefile 1%