Greenfield bare-metal firmware for a Pi Zero that lights a WS2812B strip above an 88-key keybed. The Pi is a USB MIDI gadget; the PC is the host and owns the piano connection and everything else. No network stack, no filesystem, no shell. Circle is a submodule pinned to Step51. Builds kernel.img (RASPPI=1, Zero / Zero W) and kernel7.img (RASPPI=2, Zero 2 / Zero 2 W); both coexist on one card, so either model runs from the same SD. Resolves two open assumptions from the plan against the Circle sources rather than by guessing: - CWS28XXStripe clocks the waveform out over SPI at a fixed 6.4MHz, one SPI byte per LED bit. On device 0 that puts data on MOSI = GPIO10 = physical pin 19. The implied 5.28ms frame time matches the plan's arithmetic. - The USB gadget lifecycle follows sample/29-miniorgan, which already carries a USB_GADGET_MODE path. Three details the hardware forces: - The gadget destroys and recreates its CUSBMIDIDevice across a USB suspend, so the kernel re-fetches it and clears notes held at that moment. Otherwise a chord would stay lit forever when the PC sleeps. - Rendering blocks for ~5.3ms of SPI traffic, so the MIDI packet handler only records state and the main loop draws. - MAX_LIT_KEYS complements the global brightness ceiling. 176 LEDs at full white would draw ~10.5A against a 6A supply; together the two clamps make that unreachable rather than merely unlikely. Every Phase 0 product decision has a named slot in firmware/config.h, all overridable at build time via EXTRADEFINE. tests/run.sh compiles the real pianoleds.cpp against stubbed Circle headers and checks the mapping, note-off paths, range clamping and both power clamps across nine configuration variants. It verifies arithmetic, not wiring, and does not replace bench-testing on real hardware. Claude-Session: https://claude.ai/code/session_01TVCB25LBsmeteWvaSMz4Ne
147 lines
5.1 KiB
C
147 lines
5.1 KiB
C
//
|
|
// config.h
|
|
//
|
|
// Piano LED Visualizer on Circle - all tunable parameters.
|
|
//
|
|
// Every value in this file is a product decision that Phase 0 of
|
|
// PIANO-LED-CIRCLE-PLAN.md exists to answer. Bench-test on Raspberry Pi OS
|
|
// first, then transcribe the answers here and build once.
|
|
//
|
|
#ifndef _config_h
|
|
#define _config_h
|
|
|
|
// --------------------------------------------------------------------------
|
|
// Keybed and strip geometry (plan section 6)
|
|
// --------------------------------------------------------------------------
|
|
|
|
// An 88-key keybed spans MIDI notes 21 (A0) through 108 (C8).
|
|
#define MIDI_NOTE_MIN 21
|
|
#define MIDI_NOTE_MAX 108
|
|
|
|
#define KEY_COUNT (MIDI_NOTE_MAX - MIDI_NOTE_MIN + 1) // 88
|
|
|
|
// LEDs per key. At 144 LEDs/m, 2 per key spans 1.222m, which lines up with a
|
|
// standard 88-key keybed almost exactly.
|
|
#ifndef LEDS_PER_KEY
|
|
#define LEDS_PER_KEY 2
|
|
#endif
|
|
|
|
#define LED_COUNT (KEY_COUNT * LEDS_PER_KEY) // 176
|
|
|
|
// Strip orientation. Pixel 0 of a WS2812B strip is at the end the data line
|
|
// enters. Decide this AFTER the strip is physically mounted, then flip this
|
|
// one flag.
|
|
//
|
|
// 0 = pixel 0 is at the bass end -> led = (note - 21) * 2
|
|
// 1 = pixel 0 is at the treble end -> led = (108 - note) * 2
|
|
#ifndef STRIP_REVERSED
|
|
#define STRIP_REVERSED 0
|
|
#endif
|
|
|
|
// --------------------------------------------------------------------------
|
|
// Power safety (plan section 7) - NOT optional
|
|
// --------------------------------------------------------------------------
|
|
//
|
|
// 176 LEDs at full white draw ~60mA each = 10.56A theoretical maximum, against
|
|
// a 6A supply. Real playing never approaches that (a ten-finger chord lights 20
|
|
// LEDs, ~1.2A), but a firmware bug that whites out the strip would brown out
|
|
// the rail. These two clamps make that unreachable rather than unlikely.
|
|
|
|
// Global brightness ceiling, applied to every channel of every pixel.
|
|
// 0-255. At 96 a full-strip white would draw roughly 4A, still inside 6A.
|
|
#ifndef GLOBAL_BRIGHTNESS
|
|
#define GLOBAL_BRIGHTNESS 96
|
|
#endif
|
|
|
|
// Hard cap on simultaneously lit keys. Beyond this, further held notes are
|
|
// tracked but not lit, so current draw stays bounded no matter what arrives
|
|
// on the wire. 20 keys is a ten-finger chord; 30 leaves room for pedal-held
|
|
// passages without ever approaching the supply limit.
|
|
#ifndef MAX_LIT_KEYS
|
|
#define MAX_LIT_KEYS 30
|
|
#endif
|
|
|
|
// --------------------------------------------------------------------------
|
|
// Colour (Phase 0 decides these against the actual diffuser)
|
|
// --------------------------------------------------------------------------
|
|
//
|
|
// Colours look substantially different through a diffuser than on bare strip.
|
|
// Do not finalise these from a photo.
|
|
|
|
// Colour for a played key, before brightness scaling.
|
|
#ifndef NOTE_COLOR_R
|
|
#define NOTE_COLOR_R 0
|
|
#endif
|
|
#ifndef NOTE_COLOR_G
|
|
#define NOTE_COLOR_G 140
|
|
#endif
|
|
#ifndef NOTE_COLOR_B
|
|
#define NOTE_COLOR_B 255
|
|
#endif
|
|
|
|
// Distinct colour for a "next note to play" hint driven by learning software
|
|
// on the PC (plan Phase 3). Reached over MIDI channel HINT_MIDI_CHANNEL.
|
|
#ifndef HINT_COLOR_R
|
|
#define HINT_COLOR_R 255
|
|
#endif
|
|
#ifndef HINT_COLOR_G
|
|
#define HINT_COLOR_G 80
|
|
#endif
|
|
#ifndef HINT_COLOR_B
|
|
#define HINT_COLOR_B 0
|
|
#endif
|
|
|
|
// --------------------------------------------------------------------------
|
|
// Velocity response
|
|
// --------------------------------------------------------------------------
|
|
|
|
// 1 = velocity scales pixel brightness, 0 = every key lights at full
|
|
// GLOBAL_BRIGHTNESS regardless of how hard it was struck.
|
|
#ifndef VELOCITY_SENSITIVE
|
|
#define VELOCITY_SENSITIVE 1
|
|
#endif
|
|
|
|
// Floor for velocity scaling, as a percentage. A pianissimo note should still
|
|
// be clearly visible, so velocity maps onto [VELOCITY_FLOOR_PCT, 100] rather
|
|
// than onto [0, 100].
|
|
#ifndef VELOCITY_FLOOR_PCT
|
|
#define VELOCITY_FLOOR_PCT 35
|
|
#endif
|
|
|
|
// --------------------------------------------------------------------------
|
|
// MIDI routing
|
|
// --------------------------------------------------------------------------
|
|
|
|
// Channel carrying notes actually played on the piano. 0-15 on the wire
|
|
// (channel 1 in a DAW), or MIDI_CHANNEL_ANY to accept every channel.
|
|
#define MIDI_CHANNEL_ANY 0xFF
|
|
#ifndef NOTE_MIDI_CHANNEL
|
|
#define NOTE_MIDI_CHANNEL MIDI_CHANNEL_ANY
|
|
#endif
|
|
|
|
// Channel reserved for Phase 3 "light the next key" hints from the PC. Kept
|
|
// separate from played notes so the two never overwrite each other. Set to
|
|
// MIDI_CHANNEL_NONE to ignore hints entirely.
|
|
#define MIDI_CHANNEL_NONE 0xFE
|
|
#ifndef HINT_MIDI_CHANNEL
|
|
#define HINT_MIDI_CHANNEL 15 // channel 16 in a DAW
|
|
#endif
|
|
|
|
// --------------------------------------------------------------------------
|
|
// Hardware wiring (VERIFIED against circle/addon/WS28XX, do not guess)
|
|
// --------------------------------------------------------------------------
|
|
//
|
|
// CWS28XXStripe clocks the WS2812B waveform out over SPI at a fixed 6.4MHz,
|
|
// encoding each LED bit as one SPI byte. On SPI master device 0 that puts the
|
|
// data line on:
|
|
//
|
|
// MOSI = GPIO10 (BCM) = physical pin 19
|
|
//
|
|
// Feed that through a 74AHCT125 to get a 5V logic level at the strip, and tie
|
|
// the Pi's ground to the LED supply ground. See plan section 7.
|
|
#ifndef SPI_MASTER_DEVICE
|
|
#define SPI_MASTER_DEVICE 0
|
|
#endif
|
|
|
|
#endif
|