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
61 lines
1.6 KiB
C++
61 lines
1.6 KiB
C++
//
|
|
// pianoleds.h
|
|
//
|
|
// Maps incoming MIDI note events onto a WS2812B strip mounted above an
|
|
// 88-key keybed.
|
|
//
|
|
#ifndef _pianoleds_h
|
|
#define _pianoleds_h
|
|
|
|
#include <circle/usb/usbmidi.h>
|
|
#include <circle/types.h>
|
|
#include <WS28XX/ws28xxstripe.h>
|
|
#include "config.h"
|
|
|
|
class CPianoLEDs
|
|
{
|
|
public:
|
|
CPianoLEDs (void);
|
|
~CPianoLEDs (void);
|
|
|
|
boolean Initialize (void);
|
|
|
|
// Attach to a USB MIDI device. Safe to call again after the gadget has
|
|
// been re-enumerated, which destroys and recreates the device object.
|
|
void AttachMIDIDevice (CUSBMIDIDevice *pMIDIDevice);
|
|
|
|
// Push pending state to the strip. Call from the main loop only; this
|
|
// blocks for ~5.3ms of SPI traffic and must never run in IRQ context.
|
|
// Does nothing when no state has changed since the last call.
|
|
void Update (void);
|
|
|
|
// Extinguish every pixel and forget all held notes.
|
|
void AllOff (void);
|
|
|
|
private:
|
|
// Called in IRQ context by the USB MIDI driver.
|
|
static void MIDIPacketHandler (unsigned nCable, u8 *pPacket, unsigned nLength,
|
|
unsigned nDevice, void *pParam);
|
|
|
|
void OnMIDIPacket (const u8 *pPacket, unsigned nLength);
|
|
|
|
void SetKey (u8 ucNote, u8 ucVelocity, boolean bHint);
|
|
|
|
// Scale a colour channel by velocity and the global brightness ceiling.
|
|
static u8 Scale (u8 ucChannel, u8 ucVelocity);
|
|
|
|
static boolean ChannelMatches (u8 ucChannel, u8 ucWanted);
|
|
|
|
private:
|
|
CWS28XXStripe m_Stripe;
|
|
|
|
// Written in IRQ context, read by Update(). Index is
|
|
// note - MIDI_NOTE_MIN. Zero means the key is not lit.
|
|
volatile u8 m_KeyVelocity[KEY_COUNT];
|
|
volatile u8 m_HintVelocity[KEY_COUNT];
|
|
|
|
volatile boolean m_bDirty;
|
|
};
|
|
|
|
#endif
|