Checked against the pinned Circle tree after the question of supporting Bluetooth and WiFi on the Pi alongside USB. Bluetooth is not possible on Circle, for two independent reasons: the stack was removed for legal reasons and was classic Bluetooth in any case, with no BLE or GATT anywhere in the tree, while MIDI over BLE is GATT; and the remaining HCI transport would need a USB dongle in host mode, which the gadget-only constraint already rules out. That costs nothing. The WU-BT10 does GATT MIDI to the PC, and the PC is the hub, so Bluetooth MIDI works today with no firmware change - it just terminates one hop earlier. Section 3 now describes it that way rather than as a spare part. WiFi is possible via addon/wlan but is a project rather than a flag: Circle lists WLAN as unknown on the Zero 2 W, ships no RTP-MIDI (only mDNS advertisement of _apple-midi._udp), needs firmware blobs off the card at boot, and its coexistence with gadget mode is unproven. Deferred, not rejected; that last point is what to settle first. Also marks Phase 1 done-but-unverified, and records that Phase 0 should bench rpi_ws281x in SPI mode on GPIO10, since the stock examples use GPIO18 over PWM and would prove the wrong wiring. Claude-Session: https://claude.ai/code/session_01TVCB25LBsmeteWvaSMz4Ne
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.imgandboot/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_BRIGHTNESSandMAX_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 21–108 (A0–C8) 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.