Skip to content

Hackagotchi v1.1 πŸ› οΈπŸΎπŸ‘» β€” a face, a ghost, and a steadier probe

Latest

Choose a tag to compare

@prat96 prat96 released this 22 Jun 12:40
· 24 commits to main since this release
Immutable release. Only release title and notes can be modified.

Hackagotchi probe firmware β€” v1.1 πŸ› οΈπŸΎπŸ‘»

The "give it a soul" release. v1.0 shipped the hard part β€” a CMSIS-DAP debug probe, a UART-to-microSD
black-box recorder, and a reactive OLED dashboard, all three on one single-core RP2040 without ever
stalling a flash
. v1.1 gives that machine a face: a cat and Spectre, the ghost, where every
flicker of personality is a literal readout of a real probe/recorder signal β€” not decoration.

And because we hold ourselves to "prove it on hardware," the heavier graphics exposed a subtle, zero-stall
timing regression β€” so we caught it, root-caused it, and fixed it before shipping (the HG_PIN_DAP
story below). Still R1-clean: 0 DAP transfer stalls.


πŸ†• What's new since v1.0

Feature Attested as
πŸ‘» Spectre, the ghost β€” its state is the target's soul: dozing (quiet) / live (talking) / pale (wedged) / glitch (SD fault) / exorcised, driven from real UART liveness g:<state>
🐱 Cat moods β€” sleep / content / hunting / alert from live signals; flying-data particle speed scales with throughput cat:<mood>
🎨 A real graphics engine β€” ssd1306_blit(), a clipped 1-bit sprite blit (OR / ANDNOT / XOR), host-unit-tested, fed by an ASCII-art sprite pipeline. Sprites are flash-resident (~0 RAM) blit_test.c
πŸ“Š Persistent status bar on every screen (REC / SD glyphs + a ghost pip) BAR … line
πŸ’€ Resurrection tally β€” wedgeβ†’recover + fault counts, edge-counted in the 50 Hz SD task (never misses a fast edge) on UPTIME
πŸ•ΉοΈ Companion interaction over CDC1 (no button): pet, summon/banish, exorcise (auto-fired after a clean reflash), ghost (mute β†’ pure instrument), theme (motion density) CDC1 verbs
πŸ›‘οΈ HG_PIN_DAP β€” DAP/USB hot path pinned to SRAM (the XIP-cache fix below). On by default. dap_xfers = live
πŸ“ˆ DAP health telemetry β€” {"q":"status"} now reports dap_xfers (transfers executed) + dap_idle_ms status reply

The whole character layer renders at the lowest priority off snapshot-only reads β€” it never touches
the DAP hot path. (v1.0's full feature set β€” probe, recorder, two CDCs, crash box, watchdog, SD explorer β€”
is all unchanged and carried forward.)


πŸ›‘οΈ Under the hood: the regression we caught (and fixed)

A debugger you can't trust isn't a debugger. So this is worth a paragraph.

The firmware runs from flash XIP through a 16 KB instruction cache (that choice buys +139 KB of SRAM at
identical DAP throughput). The new render loop churns a big enough flash-instruction working set that it
evicts the CMSIS-DAP framing path from that cache β€” and the next probe transaction pays a flash-refill
delay right inside the USB response window. The result was retryable DAP desyncs at ~1.4–3.0%, where
shipped v1.0 sat at ~0.2%. Crucially it was still 0-stall (the RAM-resident USB ISR keeps the bus
acked β€” it's added latency, not a hang), so it slipped past the hard correctness bar while quietly
regressing a softer one. Task priority doesn't help here: priority schedules the CPU, not the shared
cache.
We proved it was the firmware (not bench noise) with an interleaved A/B against the actual
shipped v1.0 image
on one bench, candidate last-in-time.

The fix (HG_PIN_DAP, on by default): a linker variant pins just the 7 DAP/USB transaction objects into
SRAM, out of the contended cache β€” a residency change only (no priority change, no upstream edit,
~18.8 KB of the SRAM headroom). The pinned image soaks 0/500, back at/below the v1.0 floor, 0 stalls
throughout. And to make sure a "clean" soak can never silently pass with a dead probe, the probe now
self-reports a monotonic dap_xfers counter you can read before/after.

β†’ Full write-up: docs/firmware-conventions.md Β§2, docs/mcu-bringup-playbook.md Β§10, docs/release-readiness.md Β§0.


⬆️ Flash it / upgrade from v1.0 (no toolchain needed)

  1. Download hackagotchi_probe.uf2 below.
  2. Enter BOOTSEL β€” on a running v1.0 unit, hands-free: send {"q":"bootsel"} to the control port
    (there's no button β€” GP27 is SWDIO). Otherwise BOOTSEL the XIAO at power-up.
  3. picotool load -x hackagotchi_probe.uf2 (or drag the .uf2 onto the RPI-RP2 drive).
  4. Confirm β€” send {"q":"status"} to the control serial port β†’ {"fw":"Hackagotchi","ver":"1.1.0",…,"dap_xfers":…}.

Settings persist across the upgrade. No re-wiring; same pin map as v1.0.


βœ… Verified on this image

  • Build + static-analysis gate (analyze.sh) β€” PASS; the two pristine TUs stay 0-warning.
  • Artifact provenance β€” strings β†’ ver 1.1.0; nm confirms all 7 DAP/USB objects resident in SRAM
    (0x2000xxxx), i.e. the fix is actually in the binary, not just the source.
  • DAP under sustained flash β€” the pinned image soaks 0/500 (Gate 1, 0 stalls) β€” the clean
    reference. A longer 1000-cycle run on a non-idle host stayed 0-stall but logged 2 retryable
    (recoverable) desyncs on a single cycle
    β€” a strict-bar miss at ~0.2% (the v1.0-class idle floor), with
    a live dap_xfers witness (~2 M transfers serviced) confirming the probe never went dark. The fix is
    proven by the A/B (the ~7–15Γ— regression is gone) + the 0/500; an idle-host 1000-cycle re-run for a
    clean 0/N is the one open item (docs/release-readiness.md Β§0).
  • The v1.0 HIL suite (probe / bridge / recorder / crash box / watchdog / CDC / SD) carries forward β€”
    unaffected by a +0 UI layer + a residency change. See docs/release-readiness.md.

πŸ“¦ Assets

hackagotchi_probe.uf2 Β· hackagotchi_probe.elf (to symbolicate crash dumps) Β· THIRD-PARTY-NOTICES.md Β· LICENSE

πŸ“ Notes

  • The firmware reports its own version live ({"q":"status"} β†’ ver = 1.1.0).
  • License: project GPL-3.0-or-later; the firmware/c/ subtree MIT. All dependencies permissive (MIT / BSD-3 / Apache-2.0).
  • ⚠️ Under an artificial continuous-max-SD soak, retryable (0-stall) DAP errors can still appear β€” ~0 in
    real use (the target is halted during a real flash). Run soaks on an idle host. Some target boards
    re-glitch their QSPI under sustained hammering (still 0 stalls) β€” power-cycle the target between long soaks.

πŸ““ Full changelog: CHANGELOG.md Β· πŸ”§ build from source: docs/c-firmware-build.md Β· πŸ› οΈ build a unit: docs/build-a-hackagotchi.md