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
+86 -2
View File
@@ -15,6 +15,60 @@
static CPicoLEDStrip s_Strip (LED_COUNT, WS2812_PIN);
static CPianoLEDs s_PianoLEDs (s_Strip);
// --------------------------------------------------------------------------
// Status indicator
// --------------------------------------------------------------------------
//
// Most Waveshare RP2350 boards have an onboard WS2812 rather than a plain LED,
// and the board header names the pin. Where it exists, it is the only feedback
// this headless board has: it answers "did the firmware boot" and "did the host
// enumerate us" before the strip is even wired.
#if STATUS_LED_ENABLED && defined(PICO_DEFAULT_WS2812_PIN)
#define HAVE_STATUS_LED 1
static CPicoLEDStrip s_Status (1, PICO_DEFAULT_WS2812_PIN);
enum TStatus
{
StatusFault, // strip did not initialise
StatusWaiting, // no USB host yet
StatusReady, // enumerated, running normally
StatusCalibrating // a calibration pattern is active
};
static TStatus s_LastStatus = StatusFault;
static bool s_bStatusValid = false;
static void ShowStatus (TStatus Status)
{
if ( s_bStatusValid
&& Status == s_LastStatus)
{
return; // only touch the LED when it changes
}
const uint8_t B = STATUS_LED_BRIGHTNESS;
switch (Status)
{
case StatusFault: s_Status.SetLED (0, B, 0, 0); break;
case StatusWaiting: s_Status.SetLED (0, B, B / 2, 0); break;
case StatusReady: s_Status.SetLED (0, 0, B, 0); break;
case StatusCalibrating: s_Status.SetLED (0, 0, 0, B); break;
}
s_Status.Update ();
s_LastStatus = Status;
s_bStatusValid = true;
}
#else
#define HAVE_STATUS_LED 0
static void ShowStatus (int) {}
enum { StatusFault, StatusWaiting, StatusReady, StatusCalibrating };
#endif
// Number of MIDI bytes carried by a USB MIDI event packet, indexed by its
// Code Index Number (USB MIDI 1.0, table 4-1). 0 means "not a message we
// forward".
@@ -53,6 +107,13 @@ extern "C" void tud_resume_cb (void)
// --------------------------------------------------------------------------
// sleep_ms takes uint32_t, which is unsigned long on this target; the portable
// header deliberately knows nothing about platform types.
static void DelayMs (unsigned nMilliSeconds)
{
sleep_ms (nMilliSeconds);
}
static void PollMIDI (void)
{
uint8_t Packet[4];
@@ -80,18 +141,26 @@ int main (void)
{
stdio_init_all ();
#if HAVE_STATUS_LED
s_Status.Initialize ();
#endif
if (!s_Strip.Initialize ())
{
// Nothing sensible left to do; make the failure visible rather
// than sitting dark and looking like a power problem.
// Sit here showing red. A dark board would look like a power
// problem; this says the firmware ran and the strip did not.
while (true)
{
ShowStatus (StatusFault);
tight_loop_contents ();
}
}
s_PianoLEDs.Initialize ();
// Before USB, so the sweep runs on power alone and needs no PC.
s_PianoLEDs.RunSelfTest (DelayMs);
tusb_init ();
while (true)
@@ -104,6 +173,21 @@ int main (void)
// rather than in the USB callback. Returns immediately when
// nothing has changed.
s_PianoLEDs.Update ();
s_PianoLEDs.SetHostConnected (tud_mounted ());
if (!tud_mounted ())
{
ShowStatus (StatusWaiting);
}
else if (s_PianoLEDs.GetCalibrationPattern () != CALIB_PATTERN_OFF)
{
ShowStatus (StatusCalibrating);
}
else
{
ShowStatus (StatusReady);
}
}
return 0;