Add boot self-test and idle indicator; target the Waveshare RP2350-Plus

The board arriving is a Waveshare RP2350-Plus (4MB, USB-C), which is now the
default BOARD. Its pico-sdk header defines neither PICO_DEFAULT_LED_PIN nor
PICO_DEFAULT_WS2812_PIN - it has no onboard indicator at all - so a bare board
gives no sign of life. The status-LED support added for boards that do have one
(zero, one, tiny, usb_a on GPIO16; eth on GPIO25) is kept and compiles out here.

That makes feedback on the strip itself the useful path, and it turns out to be
the better one anyway:

- Boot self-test sweeps one pixel from index 0 to the far end once at startup.
  It answers in a single glance whether the firmware runs, PIO drives the line,
  the strip is the length LED_COUNT claims, the far end holds voltage, and -
  because you see which end it starts from - whether STRIP_REVERSED is right.
  It runs before USB, so the first test needs nothing but 5V.
- Idle indicator holds one dim pixel lit while no host is connected, separating
  "powered and waiting" from "no power" and from "crashed".

Both are platform-independent, so the Circle build gets them too.

Verified: tests pass across seventeen configurations, now including the
self-test and idle paths on and off. Both platforms build clean with no
warnings from project sources.

Note on the previous commit's verification: a filtered build log hid a real
compile error in the Pico target (sleep_ms takes uint32_t, which is unsigned
long here, and did not match the portable void(*)(unsigned) delay callback).
The build script now gets an explicit success check rather than a grep.

Claude-Session: https://claude.ai/code/session_01TVCB25LBsmeteWvaSMz4Ne
This commit is contained in:
prosolis
2026-08-27 23:22:56 -07:00
parent 202adacf7a
commit 59bd3b4cea
10 changed files with 307 additions and 14 deletions
+59 -3
View File
@@ -17,6 +17,7 @@
CPianoLEDs::CPianoLEDs (ILEDStrip &Strip)
: m_Strip (Strip),
m_bDirty (true),
m_bHostConnected (false),
m_nCalibPattern (CALIB_PATTERN_OFF),
m_nCalibIndex (0),
m_nCalibIndexHi (0)
@@ -184,6 +185,55 @@ void CPianoLEDs::SetKey (uint8_t ucNote, uint8_t ucVelocity, bool bHint)
m_bDirty = true;
}
void CPianoLEDs::SetHostConnected (bool bConnected)
{
if (m_bHostConnected != bConnected)
{
m_bHostConnected = bConnected;
m_bDirty = true;
}
}
void CPianoLEDs::RunSelfTest (TDelayMs *pDelay)
{
#if BOOT_SELF_TEST
assert (pDelay != nullptr);
// Sweep in strip order, not key order, so what you watch is the strip's
// own geometry: it starts at pixel 0 wherever that physically is.
for (unsigned i = 0; i < LED_COUNT; i++)
{
if (i > 0)
{
m_Strip.SetLED (i - 1, 0, 0, 0);
}
m_Strip.SetLED (i, GLOBAL_BRIGHTNESS, GLOBAL_BRIGHTNESS,
GLOBAL_BRIGHTNESS);
m_Strip.Update ();
pDelay (BOOT_SELF_TEST_MS);
}
m_Strip.Blackout ();
// The sweep left the strip in a state the renderer does not know about.
m_bDirty = true;
#else
(void) pDelay;
#endif
}
void CPianoLEDs::RenderIdle (void)
{
#if IDLE_INDICATOR
// One dim pixel at the strip's start: powered and running, no host yet.
const uint8_t B = GLOBAL_BRIGHTNESS / 8 ? GLOBAL_BRIGHTNESS / 8 : 1;
PaintPixel (0, B, B, B);
#endif
}
void CPianoLEDs::AllOff (void)
{
memset ((void *) m_KeyVelocity, 0, sizeof m_KeyVelocity);
@@ -388,13 +438,19 @@ void CPianoLEDs::Update (void)
m_Strip.SetLED (i, 0, 0, 0);
}
if (m_nCalibPattern == CALIB_PATTERN_OFF)
if (m_nCalibPattern != CALIB_PATTERN_OFF)
{
RenderNotes ();
RenderCalibration ();
}
else if (!m_bHostConnected)
{
// No host means no notes can arrive, so show a heartbeat rather
// than a strip that looks identical to an unpowered one.
RenderIdle ();
}
else
{
RenderCalibration ();
RenderNotes ();
}
m_Strip.Update ();