Skip to content

GhostRoboticsLab/Hackagotchi

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

111 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Hackagotchi πŸ› οΈπŸΎ

The debug probe with a soul β€” and tools up its sleeves.

Firmware CI Firmware: MIT Project: GPL-3.0-or-later Release

Hackagotchi β€” a CMSIS-DAP debug probe, UART black-box recorder, and reactive OLED companion on one Seeed XIAO RP2040

A fork of Raspberry Pi's official debugprobe firmware β€” drop-in with OpenOCD, probe-rs, and the Pico SDK β€” that doesn't just flash your target, it watches it. One Seeed XIAO RP2040, one firmware image, three simultaneous roles:

  • πŸ”Œ A real SWD debug probe β€” halt, erase, and reflash a wedged target with OpenOCD / probe-rs, whatever state its firmware is in. Standards-compliant CMSIS-DAP; enumerates as "Hackagotchi Probe (CMSIS-DAP)".
  • πŸ“Ό A UART black-box recorder β€” continuously logs the target's serial to microSD (session headers, heartbeats, a freeze-frame on a wedge), so a board that goes dark still leaves its last words.
  • 🐾 A reactive OLED that's alive, not a dongle β€” a cat and a ghost whose every flicker is a literal readout of your target. The cat is the bench at a glance; Spectre, the ghost, IS the target board's soul β€” it wakes when the board talks, goes hollow when it wedges, dissolves when you reflash. The probe and recorder feed a face that shows the board's pulse. (This layer is evolving fast.)

🎯 All three on one single-core RP2040, with 0 DAP transfer stalls under load β€” the dashboard and SD writer never steal a cycle from a flash in progress. That invariant (R1) is proven on hardware, not asserted: see docs/release-readiness.md.

⚑ Flash it in 30 seconds

No toolchain needed β€” just picotool (brew install picotool). Grab hackagotchi_probe.uf2 from the latest release, drop into BOOTSEL, and load:

# BOOTSEL the XIAO: hold B, tap R, release B  ->  an RPI-RP2 drive mounts
picotool load -x hackagotchi_probe.uf2          # -x = run after loading

# alive check: send this to the control serial port (the one that answers as "Hackagotchi")
printf '{"q":"status"}\n' > /dev/cu.usbmodemXXXX  # -> {"fw":"Hackagotchi","ver":"1.0.0",...}

That's it β€” the cat wakes up and the probe is live.

Already running an older build? You don't even need the chord β€” send {"q":"bootsel"} to the control port and it drops to BOOTSEL on its own. Building from source instead? β†’ docs/c-firmware-build.md.

Two firmwares live here. The shipping product is the C firmware in firmware/c/ (everything below). firmware/micropython/ is the original MicroPython v1 prototype, kept for reference.


πŸ”§ Wiring & pin map β€” it's a XIAO on a Seeed expansion board; you only wire SWD + GND to your target
Function Pins
SWD β†’ target (the probe) SWCLK GP26, SWDIO GP27, + GND
Target UART tap β†’ CDC0 bridge TX GP0, RX GP1 (uart0)
microSD β€” SPI0 (black box) SCK GP2, MOSI GP3, MISO GP4, CS GP28
OLED β€” I2C1 @ 1 MHz (addr 0x3C) SDA GP6, SCL GP7
Buzzer (PWM) GP29
WS2812 NeoPixel (status LED) data GP12, power GP11

No physical button β€” GP27 became SWDIO, so navigation and tools are driven over USB (CDC1, below). A deliberate trade: when the device is a debugger, an extra debug pin beats a menu button.

πŸŽ›οΈ Using it β€” two USB serial ports: a transparent UART bridge + a JSON control channel
Port Role
CDC0 Transparent UART bridge β€” your target's serial console, passed straight through (115200 default; runtime-changeable).
CDC1 JSON control channel β€” newline-delimited {"q":"..."} requests, JSON replies.

The numeric /dev/cu.usbmodemXXXX suffix is not stable across replug β€” identify the control port by behavior: it's the one that answers {"q":"status"} with "fw":"Hackagotchi". The other is the bridge.

Debugging a target (SWD) β€” it's a standard CMSIS-DAP probe, so point your usual tools at it:

probe-rs list                                   # -> "Hackagotchi Probe (CMSIS-DAP)"
probe-rs download --verify firmware.elf --chip RP2040
openocd -f interface/cmsis-dap.cfg -f target/rp2040.cfg
πŸ“‘ Control channel (CDC1) β€” full command reference

Send {"q":"<cmd>", ...}\n; you get one JSON line back.

Group Command Does
Status {"q":"status"} live telemetry: fw, ver, heap, up, n, crashes, wd_armed, page, urx_drop, frag, …
{"q":"dump"} status + last crash report
{"q":"lastfault"} the post-mortem crash box (HardFault / malloc-fail, survives reboot)
Recorder / SD {"q":"rec"} / {"q":"tail"} recorder state / the on-card log tail
{"q":"ls"} / {"q":"cat","i":N,"off":M} list logs / read log #N (by index β€” no path traversal)
{"q":"sd"} SD mount + bring-up status
Dashboard {"q":"next"} / {"q":"prev"} / {"q":"screen","n":N} navigate screens
{"q":"hex"} toggle the SNIFFER between ASCII and hex
Tools {"q":"macros"} / {"q":"macro","i":N} list / send a predefined string out the target UART
{"q":"setmacro","i":N,"s":"..."} set macro N (persisted to SD)
{"q":"baud","v":N} change the target-UART baud (validated set; persisted)
Feedback {"q":"beep"} / {"q":"led",...} / {"q":"pixel",...} buzzer / status LEDs / NeoPixel
{"q":"fb"} feedback-layer readback β€” beep count + live NeoPixel colour (diagnostic)
Companion {"q":"pet"} a happy cat beat (heart + chirp)
{"q":"summon"} / {"q":"banish"} force the ghost present / absent (also lets tests drive it)
{"q":"exorcise"} the exorcism dissolve β€” a host flasher fires it after a clean reflash
{"q":"ghost","on":0/1} / {"q":"theme","n":0/1} mute the character layer (pure-instrument) / motion density
{"q":"ghost"} (no on) clear a summon/banish override β€” return the ghost to AUTO
Maintenance {"q":"bootsel"} reset to BOOTSEL for a hands-free reflash
{"q":"wd_arm"} / {"q":"wd_reset"} software watchdog control

There are also test/diagnostic hooks used by the HIL suite (uloop_on/off, recgen_on/off, crash, oom_test, wd_test) β€” see firmware/c/src/cdc1_control.c.

🐾 The dashboard β€” a cat + a ghost, 6 auto-cycling monitor screens, 3 summonable tool screens
HOME         SNIFFER        RECORDER     THROUGHPUT    WATCHDOG       UPTIME
 /\_/\  .-.  UART RX LOG    BLACK BOX     β–β–‚β–„β–†β–ˆβ–†β–„β–‚      last freeze    + heap
( o.o )(o o) live tail      log NNN       bytes/s       frame          rev/flt

Every flicker of personality is a literal readout of a real signal β€” two characters, no whimsy that isn't load-bearing:

  • The cat is your bench familiar, its mood the bench at a glance: sleeping (idle), content (target chatting), hunting (data really flowing β€” particles + tail speed scale with throughput), alert (target wedged or SD fault). Pet it with {"q":"pet"}.
  • Spectre, the ghost, is the target board's soul. It dozes when the target is quiet and wakes solid when it talks over the UART tap (GP0/GP1), goes hollow/pale when it wedges, torn on a recorder fault, and is exorcised (dissolves) when the host reflashes. A status pip rides the top-right corner of every screen. (Ghost liveness follows the target's serial β€” an SWD-only attach with no UART traffic shows the dozing ghost.)

The six monitor screens auto-cycle; three tool screens (MACRO / BAUD / SD-EXPLORER) are summoned over CDC1 and stay put (excluded from auto-cycle). The buzzer + NeoPixel add event feedback (summon chirp, wedge alarm + red, recovery gasp + green) β€” all driven off the DAP hot path. Want a pure instrument cluster? {"q":"ghost","on":0}.

This character layer is under heavy active development and expanding fast β€” the cat and Spectre are just the first faces of the bench.

🧠 How it works β€” the R1 invariant (single-core coexistence bought with priority, not a 2nd core)

The RP2040 in debugprobe-v2.2.3 is single-core (no SMP), so coexistence is bought with task priority:

priority  high ─────────────────────────────────────────────► low
          UART bridge / watchdog  >  USB (TUD)  >  DAP  >  dashboard + SD writer

The DAP path is never blocked by anything below it, and nothing above it takes a DAP-needed lock. Cross-task data moves through single-writer published snapshots (a seqlock) and lock-free SPSC rings β€” never shared mutable structs. Each resource has exactly one owning task (FatFs ↔ SD task, OLED/I2C1 ↔ dashboard task, uart0 TX ↔ bridge task). The firmware runs from flash XIP (not copy_to_ram), buying +139 KB of SRAM at identical DAP throughput.

The measured payoff: 0 DAP transfer stalls in every soak β€” while the SD writer hammers the card and the dashboard renders. Rationale: docs/engineering-plan.md Β· evidence: docs/release-readiness.md.

πŸ“ Repository layout
firmware/c/          the shipping C firmware (this README)
  src/               our overlay: probe + bridge + control + recorder + dashboard + reliability
  boards/            the locked XIAO pin map (board config shim over upstream)
  tests/             gates/ (Gate 0/1/2) + m1..m4 (HIL + host unit tests)
  build_fork.sh setup.sh analyze.sh   build / fetch-upstream / static-analysis gate
firmware/micropython/  legacy v1 prototype (MicroPython)
docs/                engineering-plan Β· release-readiness Β· c-firmware-build Β· recovery-model Β· firmware-conventions
.github/workflows/   firmware-c.yml β€” manual-dispatch build + static-analysis gate + Release
case/                3D-printable enclosure
πŸ›‘οΈ Reliability & evidence β€” this project is gates-first

The risky architectural claims β€” does the dashboard starve the probe? does the SD writer corrupt a flash? does the 2nd CDC keep DAP enumerating? β€” were proven on real hardware as falsifiable gates before any feature work. See docs/release-readiness.md for the full CI-automated-vs-HIL-attested evidence table, and firmware/c/tests/gates/GATE_RESULTS.md.

⚠️ A green CI badge means "builds + passes static analysis," not "the gates ran" β€” the HIL gates need the physical bench and are attested by hand on each release image.

πŸ“œ License & open-core model
  • Project: GPL-3.0-or-later (LICENSE) β€” Β© 2026 GhostRoboticsLab and the Hackagotchi authors. Same copyleft lineage as Pwnagotchi / Flipper Zero / Meshtastic: use, study, modify, redistribute; derivatives stay GPL.
  • firmware/c/ subtree: MIT (firmware/c/LICENSE) β€” kept MIT so fixes stay rebasable onto upstream debugprobe.
  • Third-party components are all permissive (MIT / BSD-3 / Apache-2.0), attributed in THIRD-PARTY-NOTICES.md (+ firmware/c/THIRD-PARTY-NOTICES.md).

Open-core: the firmware is free and forkable; the project is sustained by selling assembled, pre-flashed units and the enclosure β€” not by closing the code.

πŸ™ Acknowledgements

Built on the shoulders of raspberrypi/debugprobe, the Raspberry Pi Pico SDK, TinyUSB, FreeRTOS, carlk3's no-OS-FatFS, ChaN's FatFs, daschr/pico-ssd1306, and zserge/jsmn. 🐾


Contributing? Start with CONTRIBUTING.md β€” the fork-overlay model (don't edit upstream), the R1 concurrency rules, the gates-first discipline, and the build/test loop. 🐾