DeMon — Debug + Monitor
AntOS host, system supervisor, hardware glue, and boot-UI display engine — pronounced "Demon".
DeMon is built around the ESP32-P4 (paired with an ESP32-C5 for wireless) and sits at the centre of everything: it boots the system, programs the FPGA, runs AntOS, watches over every other chip, drives the system display via its MIPI engine, and provides hardware debugging across the entire Ant64.
The choice to host AntOS on DeMon — rather than on the FireStorm Execution Engine inside the FPGA — has one important consequence: AntOS keeps running while the FPGA is being reconfigured. Loading a personality cartridge, flashing a development bitstream, or recovering from a fault all happen without disturbing the OS. The main display blanks only for as long as the FPGA takes to reconfigure — a fraction of a second for the native chipset on a normal boot, somewhat longer for a personality load — and AntOS keeps running on DeMon throughout.
Overview
| Feature | Detail |
|---|---|
| Main chip | ESP32-P4 — single 400 MHz HP RISC-V core + 40 MHz LP RISC-V core |
| AI / DSP | PIE extensions (Performance Instruction Extensions — RISC-V SIMD-like for AI and DSP) |
| PSRAM | 32MB (embedded with the ESP32-P4) |
| Wireless chip | ESP32-C5 (Wi-Fi 6, Bluetooth 5.3, Thread, Zigbee) |
| Flash | 16MB |
| RTC | Microchip MCP79410 |
| RTC battery | CR2450 — up to 20 years life |
| Storage | Internal HS USB hub — DBFS master (D:), DBFS shadow (D!:), internal general drive (E:), external memory-stick port (B:) · external SD card slot (A:) — see Storage below and Filesystem & Drive Letters for the full scheme |
| USB (HID / debug) | FS USB hub — internal keyboard · mouse · USB-Serial-JTAG of Pulse + ESP32-C5 |
| OS | AntOS runs here, on the 400 MHz HP core |
| Display feed | 2-lane MIPI DSI TX → FireStorm FPGA — composes UI + sprite and tilemap layer descriptions; FireStorm renders them at scanout |
| Audio | Triple SID engine (L/C/R) — 9 SID voices, PIE-accelerated · SAM speech synthesizer — formant TTS, PIE-accelerated · both streamed to FireStorm via MIPI |
Why ESP32-P4?
The ESP32-P4 is Espressif's high-performance RISC-V SoC: a single 400 MHz HP RISC-V core for application code, a separate 40 MHz LP RISC-V core for low-power background tasks, integrated MIPI display TX, USB OTG, and 32MB embedded PSRAM. It also has PIE (Performance Instruction Extensions) — Espressif's RISC-V SIMD-like instructions for fast AI inference and DSP. See https://developer.espressif.com/blog/2024/12/pie-introduction/ for an overview of PIE.
PIE on DeMon's HP core gives AntOS access to small ML inference and DSP acceleration: speech recognition, ambient music analysis, font rendering acceleration, image scaling, etc. — all without a separate AI chip.
The integrated MIPI engine is the critical feature for DeMon's display role — it lets AntOS render its UI on the HP core and feed it into the FireStorm FPGA as one of two MIPI display sources. Once the FPGA bitstream is loaded, DeMon's MIPI feed becomes the AntOS UI layer in the chipset's display compositor.
The ESP32-C5 provides wireless connectivity (Wi-Fi 6 dual-band, Bluetooth 5.3, Thread, Zigbee, ESP-NOW) and frees the ESP32-P4 from RF responsibilities. They communicate over an internal SPI / UART link.
Pulse uses the same ESP32-P4 SoC, in a parallel but not symmetric role. DeMon hosts AntOS and feeds the system UI to FireStorm's MIPI RX #0; it carries the general-purpose load — the OS, Luau scripting, networking, storage, user-driven work — so its FireStorm feed is one duty among many. Pulse hosts the music sequencer and feeds its UI to MIPI RX #1; it runs a real-time RTOS with a fixed set of dedicated tasks and no user scripting, so it can act as a higher-throughput, lower-jitter data pump to FireStorm — its real-time RTOS and absence of OS scheduling, not a wider bus, are what free it for that role (both supervisors use a QSPI register window). They share the same SoC, toolchain, and recovery model, but differ in role, software model, peripheral wiring, PSRAM use, and delivery profile.
Real-Time Clock — MCP79410
- 64 bytes battery-backed SRAM (a candidate store for the NFC DBFS-switch target — survives power-off on the coin cell)
- 128 bytes EEPROM + 8 bytes protected EEPROM
- Supports years 2001–2399
- Costs less than £1
A standard MCP79410 — the machine ID no longer lives here (it moved to the ESP32-P4 eFuse, below), so the blank-EUI variant is not needed.
Machine ID — ESP32-P4 eFuse
The Ant64 machine ID (aid) lives in the ESP32-P4's on-chip eFuse: a
4096-bit one-time-programmable store (up to 1792 bits reserved for custom use) with
configurable per-block write and read protection. This is genuine silicon OTP — once
a bit is burned 0→1 it can never return to 0 — which is exactly the permanence the
re-writable RTC protected EEPROM could not give. aid is the identifier used in
NFC card registration / licensing.
Stored as a 64-bit aid64 — the 48-bit structured ID plus a 16-bit CRC:
aid64: 64 bits in ESP32-P4 eFuse (custom-use block)
[63..16] aid 48 bits structured machine ID (timestamp ‖ batch ‖ serial)
[15..0] crc 16 bits CRC-16/CCITT-FALSE over the 48-bit aid
aid (the 48-bit value — logical machine ID only, NOT a network MAC):
[47..28] timestamp 20 bits hours since 2026-01-01 00:00 UTC (≈119 yrs), hour of first test
[27..16] batch 12 bits 0–4095
[15..0] serial 16 bits per-batch monotonic, 0–65535
- Uniqueness key = (batch, serial) — a manufacturing-process guarantee (the jig must never reissue a serial within a batch; monotonic counter, resets only on a deliberate new batch). The timestamp is provenance, not part of the key. Decodes to first-test hour + batch + unit-of-batch for support / warranty / recall scoping.
- CRC-16 is a local integrity wrapper (and rounds the burned value to 64 bits).
Recompute it over the 48-bit
aidat end of first-test — to catch a bad burn before the unit ships — and at boot. Because eFuse is 0→1-only, the one available corruption / tamper mode is setting extra bits, and any such change breaks the CRC, so DeMon refuses a corruptedaidrather than licensing against a garbage ID. Detection, not correction (the eFuse's own hardware encoding schemes are the correction layer). Variant pinned: CRC-16/CCITT-FALSE — poly0x1021, init0xFFFF, no reflection, no final XOR — stated explicitly so the jig and firmware always agree. The CRC is part of the stored/burned value only; the URI parameter and licensing key on the 48-bitaid, never the 64-bitaid64. - Write-protected at first-test → hard primitive. After burning
aid64, burn the block's write-protect bit (WR_DIS) so no further writes (even 0→1) are possible — confirmed available on the P4. That turns OTP into fully immutable:aidbecomes a hard identity primitive, not the soft re-writable value the RTC EEPROM would have been.aidis write-protected, not read-protected — it must stay readable, because it is appended to URIs and hashed for transmission. See the burn plan below for the (irreversible!) ordering and granularity cautions. - Identity-grade, not a secret. Readable on-chip; if sent to a server, transmit a per-server salted hash (or the HMAC attestation below), never the raw value — don't broadcast the production calendar or allow cross-service correlation. Keep the raw structured value for local / support decode only.
Hardware attestation — read-protected secret + HMAC (design option)
For genuine machine-side anti-clone — the equivalent of the card's NTAG-424-DNA path
— burn a separate secret key into a read-protected eFuse block: one DeMon's
crypto hardware can use internally but can never read back out. Attestation is then a
hardware-keyed HMAC — the server sends a nonce, DeMon returns HMAC(secret, nonce ‖ aid), and only a board holding that exact un-extractable key can produce it. Unlike a
salted hash of aid (which only proves "someone who once saw this aid"), this
cannot be cloned even with full bus access, because the key is unreadable by
construction — the same mechanism ESP32 secure-boot / flash-encryption keys use. Not
yet committed; documented as the path to a hard machine-side identity if commercial
licensing needs it.
eFuse burn plan — handle with care (eFuse is irreversible)
Write-protection is confirmed available on the ESP32-P4 (the WR_DIS register —
burning the relevant bit permanently disables further programming of its field). But
every eFuse burn is one-way and unrecoverable — a wrong value, or a wrong order,
means a permanently mis-IDed unit (scrap, not re-flash). Unlike the re-writable RTC
EEPROM, there is no second chance. Three rules for the first-test jig:
-
Granularity check (before burning anything).
WR_DISis not per-bit — one write-disable bit can govern a whole field or group. Confirm exactly whichWR_DISbit covers the custom block holdingaid64, and that burning it does not collaterally write-protect anything we may need to write later. Pick a block whose write-protect bit is ours alone. -
Burn order: protection LAST, verify before the point of no return. Stage → burn
aid64→ read back and re-check the CRC-16 → only then burn theWR_DISbit. Never write-protect before the read-back verify (afterWR_DIS, even a 0→1 fix is impossible). This mirrors IDF's own discipline — its tooling burnsWR_DISbits separately, after all other data, precisely so the burn's retry mechanism can recover from coding errors first. -
Plan the WHOLE eFuse layout up front.
aid64, the optional HMAC secret (with itsRD_DISread-protection), and any future Secure Boot v2 / Flash Encryption keys all share the sameWR_DIS/RD_DISmachinery — and protection bits have ordering conflicts (e.g. write-protectingRD_DISfor Secure Boot can foreclose read-protecting a Flash-Encryption key later). Decide the complete allocation and burn sequence for the unit's lifetime before burning the first one, because an ordering mistake is as unrecoverable as a wrong value.
Responsibilities
DeMon (ESP32-P4 — 400 MHz HP core + 40 MHz LP core)
|
├── Runs AntOS on the HP core (persists across FPGA reload)
├── Programs FireStorm FPGA at boot via JTAG — also acts as a full-time JTAG
│ host for FPGA reflash, fabric debug, and SPI flash updates (see JTAG Host below)
├── UPDI orchestrator for Sticky and Crank — DeMon supplies parsed firmware
│ images and fuse settings; Pulse does the actual bit-bang (see UPDI Orchestration)
├── Drives AntOS UI as MIPI display layer → FireStorm MIPI RX #0
├── Composes sprite + tilemap layer descriptions (display list + tile-index map) — FireStorm renders them at scanout (see Composer)
├── Triple SID audio engine (L/C/R) — driven by AntOS, FireStorm, and Pulse
├── SAM speech synthesizer — text-to-speech for voice prompts / accessibility / dialogue
├── QSPI ↔ FireStorm — register window + FPGA→DeMon return data (data-ready IRQ on GPIO36)
├── Bidirectional SPI ↔ Pulse — peer command/data link (master + slave channels)
├── 5 Mbit/s asynchronous, GDMA-driven UART ↔ Pulse — additional peer link, complements the SPI channels
├── UPDI → Clicky (AVR128DB64) keyboard controller — program (io45)
├── HS USB host → hub → D: (DBFS), D!: (DBFS shadow), E: (general), B: (external stick)
├── PIE-accelerated AI/DSP for AntOS services + SID engine + layer composition
├── Streams debug output from each component (including FireStorm internal hard RISC-V coprocessor)
├── Remote reset of any component
├── FS USB host → hub → keyboard, mouse, ESP32-C5 JTAG, Pulse JTAG — see Storage
├── External SD card slot (general removable storage)
├── 32MB PSRAM available for AntOS, application data, OTA staging,
│ layer descriptions / sprite display lists, SID engine audio buffers
└── LP core (40 MHz) — RTC monitoring, low-power housekeeping, wake handling
DeMon (ESP32-C5)
|
├── Wi-Fi 6 (2.4 + 5 GHz)
├── Bluetooth 5.3 LE + Mesh
├── Thread 1.3 mesh
├── Zigbee 3.0
├── ESP-NOW (no router needed)
└── Optional LoRa via external SX1262
MIPI Display Feed to FireStorm
The ESP32-P4's integrated MIPI DSI transmitter is wired to MIPI RX #0 on the FireStorm FPGA (one of two 2-lane + clock D-PHY receivers — the other, MIPI RX #1, is fed by Pulse).
Specifications:
- 2 data lanes + differential clock (D-PHY)
- Up to 1.5 Gbps per lane = 3 Gbps total (~375 MB/s peak)
- Enough bandwidth for 1280×720 at 60 Hz RGB UI overlay plus headroom for bulk data
FireStorm uses DeMon's MIPI feed as the AntOS UI display layer — the chipset composites it alongside whatever application is running on the FireStorm EE. Recovery menus, file browsers, system status, network state, and the AntOS shell all live here. The text and videotext content within this layer is composed by DeMon's text subsystem as a cell-grid description — grid, font selection, VT100/ANSI parsing, style attributes — and FireStorm renders the glyphs from its own font ROM at scanout; DeMon never produces finished text pixels in the production path. See AntOS Text Architecture for the subsystem's design and Composer for the compositing model.
The display description uses only a fraction of the link, so the spare bandwidth carries lower-priority traffic into FireStorm's DDR3 — network and storage data for the running application (packets, file/DBFS reads), asset uploads, OTA payloads, debug captures. The frame description always takes priority against the vblank deadline; this bulk fills the gaps beneath it and never delays a frame.
Return Path — FireStorm → DeMon
The reverse direction is QSPI, reusing DeMon's existing QSPI register window into the chipset (SPI2, CS FireStorm) rather than any MIPI or USB path. FireStorm raises a data-ready interrupt on DeMon's GPIO36; DeMon then clocks the payload out over QSPI as master — interrupt-driven and on-demand, separate from DeMon's outbound MIPI feed above. It carries chipset telemetry, audio buffers from the Tempest mixer (e.g. for OTA stream encoding via the ESP32-C5), compressed / reduced-resolution video readback (capture / streaming / recording), and bulk transfers from FireStorm DDR3 into AntOS memory. Throughput is QSPI-clock-bound (order of tens of MB/s), so raw full-resolution frame readback still compresses or downscales to fit. Routing the return over QSPI leaves DeMon's HS USB OTG free for the internal storage hub (see Storage). Pulse's return path differs — Pulse keeps a USB-HS device link to FireStorm (its HS USB isn't needed for storage); see Pulse and Memory.
MIPI is Dormant During FPGA Reconfig
DeMon's MIPI TX port has one destination — FireStorm (the return path above is a separate QSPI link). While the FPGA is being reprogrammed (personality cartridge load, bitstream update, recovery), FireStorm's MIPI RX isn't running, so DeMon's MIPI link is dormant for the duration; there is no system vsync either. During that window AntOS UI moves to the SPI-driven vertical touchscreen (see Keyboard Touchscreen), which is on its own bus and unaffected by FPGA state. Pulse's circular TFT (also SPI) does the same job on the music side. When the FPGA comes back, MIPI resumes and the next vsync triggers normal frame-locked operation.
Frame-Sync Cadence
DeMon's MIPI traffic is frame-locked, not continuous. FireStorm's system vsync output is wired to a DeMon GPIO, and DeMon waits for that pulse before pushing each frame. On vsync, DeMon bursts the next frame's layer descriptions (text cell grid, tile-index map, sprite display list) plus that frame's SID / SAM audio chunk over MIPI; the link is idle between pulses. The descriptions are tiny — a full 64×30 cell grid is ~7.5 KB, dirty-cell streaming a few hundred bytes most frames — a small fraction of the per-frame MIPI budget. FireStorm renders and scales the layers to the active output resolution at scanout via per-layer pixel replication, so DeMon never produces finished pixels and doesn't have to render at 4K or 1080p (see Composer).
Sprite and Tilemap Layer Capability
DeMon composes display layers as descriptions, not pixels: a text cell grid, a tilemap tile-index map, and a sprite display list, each streamed over MIPI to FireStorm, which renders and composites them at scanout. The P4's job is to maintain layer state — what sprite, where, which tile, which glyph — and FireStorm turns state into pixels. This is the production model and it deletes the per-frame palette-expand that used to dominate the bring-up path; see Composer for the full architecture and the benchmark that drove it. (The ESP32-P4's 2D Pixel Processing Accelerator is still on the die, but the supervisor display feed no longer goes through a P4-side pixel blit.)
Content kinds DeMon composes:
- AntOS sprites — system icons, file-browser thumbnails, notification badges, animated cursors, status indicators (id + position + scale/flip/rotation + palette bank, emitted into the display list)
- Sprite-based overlays for applications — AntOS-side widgets composited over the running game/demo without involving the FireStorm EE
- Effects layer — particle bursts, screen transitions, accessibility overlays
- Recovery / boot sprites — animated splash, progress indicators before the user-facing application even starts
- AntOS tile-grid backgrounds — file-browser grids, icon-grid views, settings panels and themed surfaces, composed as a tile-index map over a tileset
- Recovery / boot grids — a status grid populating tile-by-tile as services come up
These supervisor-composed layers are rendered by the same FireStorm tile and sprite hardware as its native layers (tilemap, sprites) — the only difference is the authoring path: DeMon streams a description, the running application writes chipset registers. The supervisor path is for UI-scale grids and overlays at modest update rates; full-screen game-style backgrounds at high update rates stay on FireStorm's native engines, driven by the application.
Pulse composes its own description streams the same way (MIPI RX #1). Between them the two supervisors deliver two independent description feeds into FireStorm — for example, AntOS overlay sprites and a file-browser tile grid on RX #0; music-performance sprites and a step-sequencer tile matrix on RX #1.
Display Behaviour: Native Boot vs Personality Load
On a normal power-on or reset, the FPGA auto-loads its native Ant64 chipset from its own attached flash chip. This takes only a fraction of a second — well under what the user perceives as a wait. Once loaded:
- The native chipset is up
- MIPI RX #0 (from DeMon) and MIPI RX #1 (from Pulse) start accepting feeds
- The AntOS UI from DeMon and the sequencer UI from Pulse appear on the main display almost immediately
- Resetting the system always returns the FPGA to this native state — the flash is the canonical source of truth
When the user (or AntOS) loads a personality cartridge — Amiga, Atari ST, C64, etc. — the FPGA temporarily takes on that personality's bitstream instead of the native chipset. This is a more substantial reconfiguration:
- The personality bitstream is loaded into the FPGA's configuration logic (via JTAG from DeMon, or directly from the cartridge depending on the personality)
- Main display is dark for the duration of this load — typically up to ~1 second
- Once the personality is up, its own display pipeline drives the main display
A reset at any time returns the FPGA to its native chipset by re-reading the FPGA flash — discarding the temporary personality and bringing the AntOS UI back. The personality is, by design, an overlay onto the persistent state of the flash; it's never written to flash.
In all cases:
- AntOS keeps running on DeMon, undisturbed
- The keyboard touchscreen stays active, driven directly by DeMon, showing AntOS status, reload progress, recovery prompts, or any other useful information throughout the blackout
- The circular display above the keyboard (driven by Pulse via SPI) also stays active independently
- The blackout window is bounded by FPGA configuration time alone — fast for native, slightly longer for personality
This is intentionally different from the previous design where AntOS ran on the main processor: there's no OS to bring back up, no kernel re-init, no filesystem remount. And even within the blackout window the user has visible feedback via the touchscreen and the circular display.
Triple SID Audio Engine
DeMon runs a three-SID emulator in software on the ESP32-P4 — three instances of the MOS Technology 6581/8580 SID chip, panned Left / Centre / Right for a wide stereo image. Audio rendered by the SID engine is streamed to FireStorm over the MIPI bulk-data path (sharing the link with the AntOS UI feed) and joins the Tempest mixer as another voice source alongside Tempest, Pulse AMY, and FireStorm EE application audio.
The SID engine is a shared audio resource: although it lives in DeMon firmware, it can be driven by any of the three CPUs in the system — AntOS on DeMon, FireStorm in the FPGA, and Pulse over its direct SPI link to DeMon (see below). It's an audio engine that happens to be hosted on DeMon, not a private DeMon facility.
What It's For
- System sounds — alerts, notifications, navigation feedback, error tones, low-battery warnings, the kind of UI audio that should be available the moment AntOS is up, before the user has loaded an application or fired up the sequencer
- Sound effects for AntOS-side applications — file-browser audio cues, network event sounds, debug audio output, accessibility tones
- Game and demo sound effects — applications running on FireStorm can trigger SID voices for retro-flavoured sound effects (explosions, alerts, jumps, lasers) without burning Tempest voice slots; particularly useful when those voices are committed to music or sample playback
- Sequencer-driven SID voices — Pulse can route MIDI tracks to the SID engine for compositions that want authentic SID timbre in the mix
- SID file playback — the engine plays classic
.sidfiles directly; AntOS doubles as a C64 music player out of the box - AntOS Luau scripts — algorithmic / generative composition driving the SIDs as a programmable instrument
Why on DeMon?
DeMon is the right host for this because:
- PIE-accelerated — the SID engine's filter math, oscillator mixing, and waveform generation benefit from the ESP32-P4's PIE SIMD extensions, fitting comfortably within available CPU budget
- Independent of FPGA state — the SID engine keeps running even during FPGA reload; system audio is uninterrupted
- MIPI bulk path is already there — no new bus is needed; the SID audio shares the MIPI link with the AntOS UI feed
- Spare CPU on the supervisor — AntOS does not saturate the HP core; the SID engine fits in the headroom
How It's Triggered
The SID engine accepts events from three sources:
| Trigger source | Path | Latency | Use |
|---|---|---|---|
| AntOS on DeMon | direct function call | sub-µs | OS sounds, scripts, SID file player, accessibility |
| Pulse | SPI master → DeMon SPI slave (LPSPI) | low µs | Sequencer-driven SID voices, MIDI track routing |
| FireStorm EE | chipset event mailbox + IRQ to DeMon | low µs | Game / demo / app sound effects |
The FireStorm path uses a chipset register block as a mailbox: FireStorm writes SID note-on / note-off / parameter events into a small queue, then raises a dedicated IRQ to DeMon over a GPIO line. DeMon reads the queue via its QSPI window into the chipset and dispatches the events to the SID engine. This is the same general mechanism used elsewhere for FPGA-to-supervisor signalling — a few microseconds of latency, dominated by the IRQ-to-handler time on the ESP32-P4.
All three sources can drive the engine simultaneously; SID voices are dynamically allocated to whichever source is asking. Conflict policy (which source gets which SID chip when contended) is configurable in AntOS — typical defaults reserve one of the three SIDs for OS sounds and let games / sequencer share the other two.
Capability
- 3× SID chips (MOS 6581 / 8580 selectable per instance — 6581 for the classic "muddy" sound, 8580 for the cleaner late-era variant)
- 3 voices per SID — 9 total
- Per-voice triangle / sawtooth / pulse / noise waveforms with ring modulation and oscillator sync
- Per-SID multi-mode filter (low-pass, high-pass, band-pass, notch, with resonance)
- L / C / R panning assigning each SID to a stereo position
- CPU cost — roughly 5–10% of the HP core for all three SIDs at full polyphony, well within DeMon's available budget alongside AntOS itself
- Compatibility — the engine can play SID files (the iconic
.sidformat), letting AntOS act as a C64 music player out of the box
Audio Path
AntOS scripts Pulse (SPI master) FireStorm EE
SID file player sequencer events game/demo FX
│ │ │
│ (SPI slave on DeMon) (chipset mailbox + IRQ
│ │ read via QSPI)
│ │ │
└───────────────────────┴──────────────────────────┘
│
▼
3× SID emulators (DeMon ESP32-P4, PIE-accelerated)
│
▼
L / C / R mix to stereo buffer in PSRAM
│
▼
stream over MIPI link → FireStorm
│
▼
Tempest mixer (alongside Tempest voices, AMY, application audio)
│
▼
WM8958 / WM8960 codec
Speech Synthesizer (SAM)
Alongside the Triple SID engine, DeMon runs a retro formant speech synthesizer in the tradition of S.A.M. (Software Automatic Mouth, 1982) — the iconic robotic voice of the Commodore 64, Atari 8-bit, and Apple II era. Like the SID engine it's hosted on DeMon but shared across all three CPUs, and like SID it streams its audio to FireStorm via the MIPI bulk-data path for mixing alongside everything else.
What It's For
- AntOS system voice prompts — boot messages, error alerts, low-battery warnings, network-event announcements; voice feedback that AntOS can produce the moment it's running, with no application involvement
- Accessibility — screen-reader mode for menu navigation, spoken descriptions of UI state, audio-first navigation when the main display is busy or unavailable
- System narration — patch names spoken aloud, file-browser audio cues, status-line readouts
- Musical use via Pulse — Pulse can send text or phoneme events to DeMon over the SPI link; the rendered speech enters the mixer as another voice source. SAM-style robotic vocals as a musical instrument — sequencer-driven phoneme streams, vocoder leads, "talking" basslines
- Game / demo dialogue from FireStorm — applications running on FireStorm can request SAM speech via the chipset mailbox; iconic robotic voice-acting at the cost of a few register writes
- AntOS Luau scripts — algorithmic narration, text-to-speech for arbitrary AntOS text
Why on DeMon?
- AntOS owns voice prompts and accessibility — the OS is where text-to-speech requests originate; hosting the engine on the same chip makes the path direct
- PIE-accelerated — formant synthesis (additive partials at three formants per phoneme) benefits from PIE SIMD
- Available before applications — boot-time voice prompts work without any application running
- Same dispatch model as SID — three trigger sources, MIPI audio out, mixer-side handling identical to SID
How It's Triggered
The same three-source pattern as the Triple SID engine:
| Trigger source | Path | Latency | Use |
|---|---|---|---|
| AntOS on DeMon | direct function call | sub-µs | Voice prompts, accessibility, scripts |
| Pulse | SPI master → DeMon SPI slave (LPSPI) | low µs | Musical use, vocoder leads, dialogue tracks |
| FireStorm EE | chipset event mailbox + IRQ to DeMon | low µs | Game / demo dialogue, app speech |
Pulse can send either complete text strings (DeMon's Reciter does the text-to-phoneme conversion) or pre-converted phoneme streams (for tighter musical timing).
How It Works
SAM converts text to speech in two stages:
Text input
│
▼
Reciter (text-to-phoneme)
Text → phoneme string using English pronunciation rules
e.g. "HELLO" → /HH EH L OW/
│
▼
Phoneme-to-speech (formant synthesis, PIE-accelerated)
Each phoneme → three formant frequencies (F1, F2, F3)
→ three formant amplitudes (A1, A2, A3)
→ pitch contour
→ voiced/unvoiced classification
Combined via additive synthesis → audio samples
│
▼
PCM audio buffer → streamed to FireStorm via MIPI link
→ joins the Tempest mixer
The formant synthesis produces the characteristic 1980s robotic voice — not because the algorithm is simple, but because it deliberately computes speech with limited resolution (originally 8-bit samples, low sample rate). On the Ant64, running at full ESP32-P4 speed with the output going to the WM8958 codec via FireStorm's mixer, the algorithm is authentically SAM-like but with modern fidelity. The output sample rate is configurable; the engine can run at the original ~7 kHz for full retro flavour, or at 48 kHz for cleaner integration with the rest of the audio path.
Implementation
The open-source C port of SAM (s-macke/SAM, ~39 KB) runs as a bare-metal function on the ESP32-P4. No OS involvement is needed for the inner rendering loop — AntOS / Pulse / FireStorm submit requests, the SAM engine renders into a buffer in DeMon's PSRAM, and the buffer is streamed out over MIPI as part of DeMon's audio output to FireStorm.
Parameters (matching original SAM)
| Parameter | Range | Effect |
|---|---|---|
| Speed | 0–255 | Talking rate (72 = normal SAM) |
| Pitch | 0–255 | Voice fundamental frequency (64 = normal) |
| Throat | 0–255 | Formant F1 shaping — voice quality (128 = normal) |
| Mouth | 0–255 | Formant F2 shaping — vowel colour (128 = normal) |
Preset Voices (matching original SAM manual)
| Voice | Speed | Pitch | Throat | Mouth |
|---|---|---|---|---|
| SAM (default) | 72 | 64 | 128 | 128 |
| Elf | 72 | 64 | 110 | 160 |
| Little Robot | 92 | 60 | 190 | 190 |
| Stuffy Guy | 82 | 72 | 110 | 105 |
| Little Old Lady | 82 | 32 | 145 | 145 |
| Extra-Terrestrial | 100 | 64 | 150 | 200 |
Extensions Beyond SAM
The ESP32-P4 has far more processing power than the original 6502 host. The engine can be extended beyond the historical SAM algorithm:
- Higher sample rate output (original SAM: ~7 kHz; DeMon can render at 48 kHz)
- Smoother formant interpolation between phonemes
- Additional phoneme sets (non-English languages)
- Pitch envelope per phoneme (more natural intonation)
- The Pi Zero 2W accelerator (when fitted) can assist with neural TTS for more natural speech, while DeMon retains the retro formant mode
Audio Path
AntOS scripts Pulse (SPI master) FireStorm EE
voice prompts text / phoneme events app dialogue events
accessibility for musical use via chipset mailbox + IRQ
│ │ │
└───────────────────────┴──────────────────────────┘
│
▼
SAM Reciter (text → phonemes, when needed)
│
▼
SAM formant synthesis (DeMon ESP32-P4, PIE-accelerated)
│
▼
PCM buffer in DeMon PSRAM
│
▼
stream over MIPI link → FireStorm
│
▼
Tempest mixer (alongside SID, Tempest voices, AMY, application audio)
│
▼
WM8958 / WM8960 codec
DeMon ↔ Pulse — Bidirectional SPI Link
DeMon and Pulse are connected by two SPI channels that form a full bidirectional command/data link between the two supervisors. Each chip has both a master and a slave SPI port wired to the other.
| Direction | DeMon side | Pulse side | Used for |
|---|---|---|---|
| DeMon → Pulse | SPI master (SPI2) | SPI slave (LPSPI) | Boot config, firmware updates, supervisor commands, AntOS-side events to play through Pulse (UI sounds via AMY, sequencer transport control), file / sample / patch data |
| Pulse → DeMon | SPI master (SPI2) | SPI slave (LPSPI) | MIDI events for AntOS to record or forward, SID and SAM trigger events, jog-dial state for AntOS UI, file-system / sample-library requests, status updates |
This is not a hierarchical supervisor link any more — DeMon and Pulse are peers. Either side can initiate a transaction at any time; the slave side accepts whatever the master sends. AntOS arbitrates its end; Pulse firmware arbitrates the other. The same two ports also carry the firmware-update flow (for emergency recovery DeMon can reflash Pulse over its USB-Serial-JTAG via the FS HID hub, but day-to-day operation uses the SPI link).
JTAG Host — FireStorm Programming and Debug
DeMon is the system's JTAG host for the FireStorm FPGA. The JTAG link from DeMon to the GoWin GW5AST goes well beyond initial boot-time bitstream loading — it covers the full programming and debug lifecycle of the FPGA over the life of the machine.
What it does
- Boot-time bitstream load — on power-on, DeMon programs the FPGA from the canonical bitstream in storage (native chipset by default, personality cartridge image when one is loaded)
- FPGA reflash — bitstream updates pushed from AntOS, OTA payloads, or development workflows are written into the FPGA via JTAG with no separate programmer required
- FPGA SPI flash programming — JTAG-to-SPI bridging through the FPGA lets DeMon write the on-board configuration flash that holds the native chipset bitstream. The flash is the canonical source on reset; updating it from DeMon updates the persistent state
- Fabric debug — single-stepping, breakpointing, and register access for FireStorm soft cores (and the GW5AST-138's hard RISC-V core, which has its own debug path that JTAG can reach orthogonally)
- Recovery and bring-up — when development bitstreams misbehave, DeMon's JTAG access is the recovery path that doesn't require a separate dongle
Toolchain compatibility
Two implementation routes are on the table, picking which is a roadmap decision:
- Custom protocol over USB-Serial-JTAG / TCP. A small DeMon-side service, a small host-side tool. Minimum work, full control over performance and feature scope. Means new tooling rather than reusing the ecosystem.
- openFPGALoader / urJTAG / OpenOCD compatibility via a well-known transport (FTDI FT2232 emulation, dirtyJtag, or XVC). Lets existing GoWin/RISC-V toolchains drive the FPGA without a separate adapter. More work upfront, big payoff in tooling reuse.
Either way, the JTAG signals themselves are bit-banged or driven via the ESP32-P4's GP-SPI peripheral configured as JTAG (TCK on SCLK, TDI on MOSI, TDO on MISO, TMS on a separate GPIO). The GP-SPI path gets useful clock rates for multi-megabyte bitstream loads; pure bit-bang would be painfully slow.
UPDI Orchestration — Sticky and Crank Firmware Updates
DeMon is the orchestrator for firmware updates to the two AVR128DB-family microcontrollers in the system — Sticky (joystick / paddle / 5V controller I/O) and Crank (jog dials). Neither AVR is directly wired to DeMon; both are reached through Pulse, which owns the physical UPDI lines and performs the bit-bang protocol on instruction from DeMon.
Split of responsibilities
| Component | Role |
|---|---|
| DeMon | Holds the firmware images on storage, parses them (Intel HEX / ELF), extracts the program bytes and fuse settings, ships a clean payload over the bidirectional SPI link to Pulse, watches Pulse's progress / status reporting, surfaces results to AntOS |
| Pulse | Receives the parsed payload, parks any active sequencer work, enters UPDI session mode against the requested target (Sticky or Crank), writes flash + fuses, reads back to verify, exits UPDI, reports pass/fail to DeMon |
This split keeps the parser on the side that already has the firmware files and the toolchain to handle them, and keeps the timing-sensitive bit-bang on the side that is physically next to the target AVRs. Pulse doesn't need an Intel HEX implementation; DeMon doesn't need UPDI line drivers.
Scope
UPDI is used for firmware update and possible debug — not for runtime control. Both AVRs run autonomously between updates; routine joystick / jog-dial data flows over the SPI peer links from Sticky and Crank to Pulse and onward to AntOS. UPDI sessions are infrequent, explicitly invoked from AntOS (e.g. update sticky, update crank), and require the sequencer to be parked for the duration.
OCD-level debug (single-step, breakpoint, register inspection on the AVR cores via pyupdi-style tooling) is the natural extension if it's ever needed; the bit-bang transport supports it. Initial implementation is programming only.
Wireless — ESP32-C5 (Phreak)
The C5 is the core of Phreak, the Ant64's wireless subsystem — Wi-Fi/BLE here, plus the optional LoRa and GPS radios (see Communications).
| Protocol | Standard | Notes |
|---|---|---|
| Wi-Fi 6 | 802.11 b/g/n/ax | 2.4 GHz + 5 GHz |
| Thread | 1.3 | Mesh networking |
| Zigbee | 3.0 | IoT mesh |
| Bluetooth | 5.3 LE + Mesh | |
| ESP-NOW | 2.4 GHz / 5 GHz | No router needed — 1Mbps |
The ESP32-P4 controls the C5 over the internal UART1 / SPI link (§ pinout). One option for that link is Espressif's ESP-AT firmware, which exposes Wi-Fi, TCP/IP, TLS, MQTT, HTTP, BLE, and BluFi as text AT commands — see the Phreak AT Command Reference for the full command set, transport, and flashing details.
Debug Connectivity
| Channel | Speed | Notes |
|---|---|---|
| Wi-Fi | Up to 54Mbps | Router required |
| Bluetooth | 2Mbps | |
| ESP-NOW | 1Mbps | No router needed |
| SPI (to ESP32-P4) | Up to 62.5Mbps | Practical ~10Mbps |
Optional: Waveshare Core1262 HF LoRa Module (SX1262) for LoRaWAN and/or Meshtastic use.
Anti-Brick
DeMon can be unbricked via an ICP cartridge even when the ESP32-P4's firmware is corrupted: its USB-Serial-JTAG D+/D- lines are brought out to the cartridge port, so a cartridge in the standard slot can reflash DeMon directly. The ESP32-C5 cannot unbrick DeMon — recovery goes through the cartridge port, not over the air.
Keyboard Touchscreen — Always-Available Display
In addition to the MIPI feed into the FPGA, DeMon owns a vertical touchscreen mounted next to the keyboard (a TFT panel with capacitive touch, SPI-connected). The screen is wired directly to the ESP32-P4 over its own SPI bus, independent of the MIPI link — it has its own controller, its own framebuffer in PSRAM, and its own touch input path. This independence is what makes it the fallback display whenever MIPI has no destination.
This matters because the touchscreen is visible at all times:
- At power-on, the FPGA loads its native Ant64 chipset from its own flash in a fraction of a second — but during that brief moment, before MIPI RX is active, the touchscreen is already showing AntOS boot status, the current boot phase, or a splash logo. By the time the user is consciously waiting for the screen to come up, it is already up.
- During FPGA reload (personality cartridge swap, bitstream update, recovery), the main display blanks while the FPGA reconfigures — typically up to ~1 second for a full personality bitstream, a fraction of a second for a native-chipset reset. The touchscreen continues to show AntOS status, progress, and any interactive prompts throughout.
- During normal operation, the touchscreen is a secondary display surface — AntOS can render system status, the file browser, settings, debug info, soft keys, or jog-dial-style controls here.
Combined with Pulse's circular display above the keyboard (see Pulse), the Ant64 has two always-on auxiliary displays that survive any FPGA state — useful for diagnostics, recovery, and informational UI that needs to be visible even when the main display compositor isn't running.
Hidden Debug Display — SSD1363 OLED (optional)
DeMon optionally drives a small 256×128 16-greyscale OLED (SSD1363 controller, I2C at 0x3C) as a hidden diagnostic surface inside the case. The OLED is not fitted by default — it's a builder/developer option for units that need a permanently-on debug surface independent of the FPGA, MIPI, network, and keyboard touchscreen. Production Ant64s typically ship without it; debugging and development units, kits, and field-service builds may include it.
When fitted, the OLED is for the system to talk to itself — boot progress, subsystem state, scrolling event log, recovery information. It is read by whoever opens the case or knows where to look, not by users in normal operation. Unlike the keyboard touchscreen (which is user-facing and shows AntOS UI), the OLED is purely for introspection.
The OLED's value is its independence. It runs on plain I2C from DeMon and comes alive within milliseconds of power-on — before the FPGA loads, before MIPI is configured, before the network is up, before the user touchscreen has initialised. If any of those subsystems is itself the thing that's broken, the OLED still tells the story.
When not fitted, sys_oled detects no device on the bus and silently disables itself — all the diagnostic content that would have gone to the OLED still goes to the UART console and (once available) the network debug stream, so nothing is lost functionally. The OLED is purely additive.
Full layout, screen list, and refresh strategy are documented separately in Debug Display.
SPI1 — Device Select
DeMon's ESP32-P4 uses SPI1 with different CSn lines to control multiple devices:
| Value | Selected Device |
|---|---|
| 0 | Pulse Sequencer |
| 1 | ESP32-C5 |
| 2 | Cartridge |
| 3 | Touch TFT |
Smart Cart — Cartridge Port
A 30-pin edge connector connected to DeMon's ESP32-P4. The full pinout, the physical notes, and the catalogue of cartridge designs (ICP, chain cart, …) live in Cartridges.
- If a cart is present at boot, it is used in preference to the SD card
- Cart insertion/swap whilst powered on triggers an automatic reboot (can be disabled)
- Cart can be safely removed whilst powered on — no reboot
- Reinserting the same cart also does not reboot
Pins 13/M and 14/N lay the groundwork for a future redesign of the ICP cartridge: they expose DeMon's USB-Serial-JTAG — the same debug/programming interface that also reaches the USB-C port — so an ICP cartridge in the standard slot can debug and reflash DeMon directly, without needing a separate USB-C connection. The cartridge itself draws power from the cart 5V rail (pin 02/B); the board is powered from its main USB-C input and gated by the power button, so no dedicated board-power feed is needed on the cart port. (See Anti-Brick and What DeMon Can Reprogram.)
Built-In NFC Card Reader
The Ant64 has an on-board PN532 NFC reader — no cartridge required. Its antenna sits just behind the cartridge port, so a card placed at the cartridge slot is read directly: the same physical location accepts either a cartridge or a tapped NFC card.
The reader is on DeMon's I3C peripheral bus (I²C-legacy mode), with reset on io20 and IRQ on io21 (see the GPIO table below). The full card model — the "a card is a URL" scheme-dispatch mechanism, user/environment (DBFS) switching, and the game-manifest system — is documented in NFC — Cards, DBFS Switching & the Game Manifest System. A card carries only a short URI (a manifest URL or a dbfs:// target); game content is cached in the user's DBFS rather than on the card.
Neopixels — 8 Status LEDs
These eight WS2812 status LEDs are still controlled by DeMon, but physically driven by Clicky. DeMon sends a per-LED colour/state intent to Clicky over UART2 and Clicky drives the string; the buzzer works the same way — DeMon tells Clicky which tone to play and for how long, and Clicky handles the timing. DeMon never bit-bangs LED or buzzer timing itself.
| No. | Name | Behaviour |
|---|---|---|
| 0 | Power | Off → white (self test) → amber (booting) → green (ready) |
| 1 | External SD card | Off / blue (detected) / green (R/W) / red (error) |
| 2 | Cartridge SD | Off / blue (detected) / green (R/W) / red (error) |
| 3 | Main (fast) SD | Off / blue (detected) / green (R/W) / red (error) |
| 4 | Network | Off / blue (connected) / green (R/W) / red (error) |
| 5 | Wi-Fi | Off / blue (connected) / green (R/W) / red (error) |
| 6 | Bluetooth | Off / green (R/W) / red (error) |
| 7 | MIDI | Off / green (R/W) / red (error) |
During self-test all LEDs go white, then change individually: green = passed, red = failed. LED functions are documented and can be repurposed if needed.
Power and the power button
Power is supplied by USB-C. DeMon JTAG D+ and D- also go to the USB-C (and to the cart port, for ICP programming). DeMon will have power button feedback and control. Power in should be via a diode (1N5822 ?) so it's one way. The power button has an RGB LED driven via DeMon's MCP23017 I2C expander (PA2/PA3/PA4) — one digital line per channel, so it shows 8 fixed colours including off (no PWM dimming). The expander also carries the power-off output and the power-pushed input (PA0/PA1, with a pushed-input interrupt on PA INT), linked to a power controller. Pressing the button for approx 10 seconds will force a power down.
| Pin | USB-C | Main board/DeMon connection |
|---|---|---|
| VOUT | +V | |
| OFF | GPIO 23 OUT (needs pull up?) | |
| PUSH | GPIO 22 IN (needs pull up set) | |
| BTN | Physical button to GND | |
| VIN | +V | |
| GND | GND | GND |
Sparkfun open source soft power switch mk2
What DeMon Can Reprogram
- FireStorm FPGA bitstream (via JTAG) — without interrupting AntOS
- FireStorm FPGA flash (via JTAG chain)
- Pulse (via USB-Serial-JTAG, through DeMon's FS hub)
- Sticky (via UPDI through Pulse)
- Crank (via UPDI through Pulse)
- Clicky keyboard controller (via UPDI direct from DeMon — io45)
- ESP32-C5 (via USB-Serial-JTAG, through DeMon's FS hub)
The ESP32-P4 itself can be reprogrammed over USB or via the cartridge ICP port. Because AntOS lives on DeMon, reprogramming DeMon is a deliberate, deeper-recovery operation — it reboots the OS. Recovery is layered: if DeMon is bricked, the ICP cart can reflash it; if everything is bricked, JTAG access from a PC can rebuild the system from scratch.
If anything goes wrong — Ant64 can heal itself.
Storage
The Ant64 has a layered storage topology, hosted off DeMon's USB controllers. Throughout this section, internal drives sit inside the case (not user-removable in normal use) and external ports are sockets mounted on the case, accessible from outside without opening the box.
Drive-letter assignment is port-based — each physical USB port maps to a fixed letter, independent of attach order or enumeration timing. The complete drive-letter scheme, DBFS shadowing rules, multi-user DBFS support, and boot/hot-plug behaviour are documented in Filesystem & Drive Letters.
USB topology — HS storage hub, FS HID hub
DeMon's two USB 2.0 OTG controllers are split by role:
- HS USB OTG → storage hub. The High-Speed port drives a hub carrying up to four USB mass-storage devices, run at High-Speed for bulk throughput:
- D: — DBFS master — primary storage for AntOS, applications, user data, the DBFS database
- D!: — DBFS shadow — a continuous mirror of D: for redundancy; hidden from user-facing APIs; if the primary drive fails, no user data is lost — the shadow takes over
- E: — Internal general drive — additional internal storage for things that don't belong in DBFS (large media libraries, downloaded asset caches, OTA staging)
- B: — External memory-stick port — user-supplied USB memory stick for transient data, transfers, and backups
- FS USB OTG → HID hub. A Full-Speed hub carries the human-interface and debug devices:
- Keyboard and mouse
- ESP32-C5 USB-Serial-JTAG — program / debug of the radio companion
- Pulse USB-Serial-JTAG — so DeMon programs / debugs Pulse (Pulse's own OTG ports are committed: HS to FireStorm, FS to its MIDI port)
Drive-letter assignment is port-based on the storage hub (see Filesystem). The FPGA→DeMon return path is not USB — it's the QSPI link with a data-ready interrupt on GPIO36 (see Return Path) — which is exactly why both OTG controllers are free for storage and HID.
External SD card slot
An external SD card slot (mapped as A:) for user-supplied removable storage — used by the MOD player, sample import, Doom WAD libraries, save transfers, and as a generic file source for AntOS. It is not the primary system storage (DBFS holds that on D:); the slot is for removable media and bulk content.
Dual-host and single-speed hubs
Dual-host operation — hosting the HS storage hub and the FS HID hub at once — is enabled via peripheral_map = BIT0 | BIT1 in the USB host config and is tested working on the ESP32-P4. Keeping HS storage and FS HID/debug on separate hubs avoids the IDF host stack's TT-not-supported limitation — no mixed-speed traffic behind either hub.
Switched Hub Power — Software Power-Cycle
Each hub takes its +5 V supply through a software-controlled high-side switch, so AntOS can power-cycle a hub without anyone touching the hardware. The control lines exist on the MCP23017 expander: PB5 = HS storage-hub power and PB4 = FS HID-hub power. The drives are the recovery-critical case — the DBFS keepalive pulses PB5 to re-enumerate a wedged drive without disturbing HID on the other hub.
Why it's needed: ESP-IDF's external-hub driver can, on a bad hot-unplug, park a downstream port in a "device not working — wait for removal" state that a fresh insertion won't clear. The only reliable recovery is to remove the hub's VBUS and re-apply it, forcing a clean re-enumeration of the hub and everything behind it. With PB4/PB5 driving the hub power switches, AntOS does that itself — the DBFS keepalive detects the wedged drive and pulses PB5 (the storage hub), rather than asking the user to pull the hub's power lead.
High-side, not low-side. USB devices share their ground reference with the host (it's the return for D+/D−). Switch the +5 V rail and keep GND common; never switch the hub's ground, or the data lines lose their reference while the port is "off."
Why two transistors. A 3.3 V expander pin can't turn off a P-channel pass FET whose source sits at +5 V — it can't pull the gate all the way up to 5 V. So a small N-channel FET level-shifts: the expander pin drives the N-FET, and the N-FET pulls the P-FET gate down to pass +5 V through to the hub.
Recommended discrete switch (one per hub)
| Ref | Part | Role |
|---|---|---|
| Q1 | AO3401A — P-ch, −30 V, −4 A, ~50 mΩ, SOT-23 | High-side pass FET: +5 V in → +5 V out to hub |
| Q2 | 2N7002 — N-ch, SOT-23 | Gate driver / level shifter from the 3.3 V expander pin |
| R1 | 10 kΩ | Q1 gate pull-up to +5 V — holds the hub off when Q2 is off |
| R2 | 1 kΩ | Series gate resistor, expander pin → Q2 gate |
| R3 | 100 kΩ | Q2 gate pull resistor — sets the boot default while the expander pin is high-Z. Tie to +3.3 V → default power-on (hub up before firmware runs, drives enumerate at boot); tie to GND → default power-off. The MCP23017 boots with all pins as high-Z inputs, so this one resistor decides the hub's state until AntOS drives the pin. +3.3 V is recommended for the storage hub. |
| C1 | 100 nF | Q1 gate-to-source — soft-start; slows turn-on to tame inrush (recommended for the 4-port hub) |
| R4 | 1 kΩ | In series Q2-drain → Q1-gate; sets the soft-start ramp with C1 (optional) |
| C2 | 10–47 µF ∥ 100 nF | Bulk + decoupling on the switched +5 V at the hub |
Wiring (HS hub on PB5 shown; the FS hub is identical on PB4):
+5V ──┬───────────────[ R1 10k ]───────────┬── Q1.gate
│ │
Q1.source [ C1 100n ] (soft-start to +5V)
│
Q1.drain ───────────────────────────────────► +5V to hub ──[ C2 10–47µF ∥ 100n ]── GND
Q1.gate ──[ R4 1k ]── Q2.drain
Q2.source ── GND
MCP PB5 ──[ R2 1k ]──┬─ Q2.gate
└─[ R3 100k ]── +3.3V
Polarity / default state. PB5 high (or high-Z at boot, via R3) → Q2 on → Q1 on → hub powered. PB5 low → Q1 off → hub unpowered. A power-cycle is therefore: drive PB5 low for ≥ 500 ms, then high.
Current. A fully-loaded four-port hub can draw ~2 A (4 × 500 mA plus the hub). The AO3401A handles the prototype's D: + D!: pair (~1 A) comfortably; for a fully-populated four-port hub use a higher-current P-FET (e.g. AON7407, −8 A) or the load-switch IC below.
Production recommendation — dedicated load switch. For a shipping board, prefer a high-side USB load-switch IC (e.g. SY6280AAC or AP22653, ~2–2.6 A, SOT-23-6) in place of Q1/Q2: active-high
ENdriven straight from PB4/PB5, built-in soft-start (no inrush network needed), current limit (a shorted stick can't drag the rail down), and a fault flag you can route back to a spare MCP23017 input. Same single control line, fewer failure modes than the discrete pair.
Firmware. AntOS drives PB5 high during bring-up and leaves it. On the hub-wedge condition — today this shows up as repeated D: keepalive FAILED (errno=2) with no re-attach — it issues one low → delay → high pulse on PB5 to power-cycle the HS hub: the in-software equivalent of pulling the hub's power lead, and the recovery path for a USB port that ESP-IDF's hub driver has left stuck.
Prototype
The prototype uses an Olimex ESP32-P4-DevKit board. This provides a network RJ45 connector and a MIPI-DSI-to-HDMI output for the display; io36 is pulled to 3v and io35 (boot) is pulled to 3v. (In production io36 = the FireStorm data-ready interrupt that drives the QSPI return path; on the devkit there's no FPGA, so it's simply tied high.)
DeMon's own USB-Serial-JTAG goes to the USB-C power port — providing power, console, and JTAG debug over a single cable.
The HS USB port drives the storage hub — D:/D!:/E:/B: (see Storage).
The FS USB port drives the HID hub described in Storage above — keyboard, mouse, and the ESP32-C5 and Pulse USB-Serial-JTAGs.
SDCard pinout on prototype (IO39-IO45)
| Pin | GPIO | Original MMS | Usage SPI/QSPI |
|---|---|---|---|
| 1 | IO41 | DAT2 | SPI3 HOLD/IO3 |
| 2 | IO42 | *CD/DAT3/CS | SPI3 CS (QSPI) |
| 3 | IO44 | *CMD/DI | SPI3 MOSI/IO0 (Pulse to DeMon) |
| 4 | IO45 out power | UPDI to Clicky | |
| 5 | GND | ||
| 6 | IO43 | *SCK | SPI3 SCK |
| 7 | IO39 | *DAT0/DO | SPI3 MISO/IO1 (DeMon to Pulse) |
| 8 | IO40 | DAT1 | SPI3 WP/IO2 |
| CD | IO3 | NC |
SPI2 has multiple select lines and can be SPI or QSPI
SPI3 is QSPI slave to pulse
MCP23017 I2C IO expansion
| Pin | Behaviour | Pin | Behaviour |
|---|---|---|---|
| PA0 | Power off output | PB0 | Firestorm Config |
| PA1 | Power pushed input | PB1 | Firestorm Ready |
| PA2 | Power R LED | PB2 | Firestorm Done |
| PA3 | Power G LED | PB3 | Pulse EN |
| PA4 | Power B LED | PB4 | FS HID-hub power |
| PA5 | NFC Reset | PB5 | HS storage-hub power |
| PA6 | ESP32-C5 BOOT (IO28) | PB6 | Touch reset |
| PA7 | ESP32-C5 EN | PB7 | HyperRAM reset |
| PA INT | Power pushed input interrupt | PB INT | Firestorm done/Firestorm Ready |
(ESP32-C5 pin27 should be wired to 1)
**** = spare for now
PB4 / PB5 (FS HID-hub / HS storage-hub power) drive the high-side hub power switches described in Switched Hub Power under Storage — software can power-cycle either hub (drive the line low → wait → high) to recover a wedged USB port without physical intervention; the DBFS keepalive uses PB5 for a wedged drive.
PB6 resets the vertical TFT touch screen's I²C touch controller (the touch keypad set into the keyboard). The OLED debug display's former cycle button and reset line are gone — diagnostics now move to the ICP cartridge, which reads the byte-form data straight off the I²C ports and renders it however it likes; PB INT accordingly carries only FireStorm done/ready.
PB7 drives the FPGA HyperRAM reset line. Because DeMon controls it independently of the FPGA, AntOS can load ROM / cartridge / disk images into the HyperRAM, then hold it out of reset across an FPGA reprogram — so the HyperRAM keeps its contents while FireStorm (or a guest personality image) reloads, and comes back with memory already populated for the incoming FPGA image. (There is no longer a software screen on/off line — the screen stays powered.)
| UEXT | Name | Behaviour |
|---|---|---|
| 1 | 3.3v | |
| 2 | GND | |
| 3 | io37 | UART0_TX to Pulse |
| 4 | io38 | UART0_RX from Pulse |
| 5 | io23 | NC |
| 6 | io22 | NC |
| 7 | io54 | NC (gpio54 carries Clicky UART2 RX — not connected here) |
| 8 | io53 | NC (gpio53 carries Clicky UART2 TX — not connected here) |
| 9 | io4 | nc |
| 10 | io5 | nc |
| EXT1 | Name | Behaviour |
|---|---|---|
| 1 | 3.3v | |
| 2 | GND | |
| 3 | io2 | UART1_TX to ESP32-C5 |
| 4 | io3 | UART1_RX from ESP32-C5 |
| 5 | io4 | ESP32-C5 HANDSHAKE |
| 6 | io5 | SPI2 CS SDCARD (SPI) |
| 7 | io6 | SPI2 CLK |
| 8 | io7 | SPI2 MOSI/IO0 (controller to SD) |
| 9 | io8 | SPI2 MISO/IO1 (SD to controller) |
| 10 | io9 | SPI2 CS SCREEN (SPI) |
| 11 | io10 | SPI2 CS CARTRIDGE (SPI) |
| 12 | io11 | SPI2 CS ESP C5 WIFI (QSPI) |
| 15 | io14 | SPI2 CS Pulse (QSPI) |
| 16 | io15 | SPI2 CS FireStorm (QSPI) |
| 17 | io16 | JTAG TDI |
| 18 | io17 | JTAG TMS |
| 19 | io18 | JTAG TCK |
| 20 | io19 | JTAG TDO |
| EXT2 | Name | Behaviour |
|---|---|---|
| 1 | 5v | |
| 2 | GND | |
| 3 | io54 | UART2 RX (from Clicky) |
| 4 | io53 | UART2 TX (to Clicky) |
| 5 | io48 | SPI2 HOLD/IO3 |
| 6 | io47 | SPI2 WP/IO2 |
| 7 | io46 | MCP23017 INT-A |
| 8 | io33 | I3C master SDA |
| 9 | io32 | I3C master SCL |
| 10 | io23 | pulled to 3v VBLANK from FireStorm |
| 11 | io22 | pulled to 3v SCREEN CMD/DATA |
| 12 | io21 | NFC Interrupt |
| 13 | io20 | MCP23017 INT-B |
| 14 | ESP_EN | |
| 15 | GND | |
| 16 | io27 USB1P1_1P | Full USB |
| 17 | io26 USB1P1_1N | 12Mbps |
| 18 | GND | |
| 19 | DP USB_DP | Fast USB |
| 20 | DN USB_DN | 480Mbps |
Prototype 2 GPIO assignments
| GPIO | Role | GPIO | Role |
|---|---|---|---|
| 0 | UART1 CTS (to ESP32-C5) | 28 | RMII CRS DV |
| 1 | UART1 RTS (to ESP32-C5) | 29 | RMII RXD0 |
| 2 | UART1 TX to ESP32-C5 | 30 | RMII RXD1 |
| 3 | UART1 RX from ESP32-C5 | 31 | MDC (SMI CLK) |
| 4 | ESP32-C5 HANDSHAKE | 32 | I3C SCL |
| 5 | SPI2 CS SDCARD (SPI) | 33 | I3C SDA |
| 6 | SPI2 CLK | 34 | RMII TXD0 |
| 7 | SPI2 MOSI/IO0 (controller to SD) | 35 | RMII TXD1 |
| 8 | SPI2 MISO/IO1 (SD to controller) | 36 | **** |
| 9 | SPI2 CS SCREEN (SPI) | 37 | UART0 TX to Pulse |
| 10 | SPI2 CS CARTRIDGE (SPI) | 38 | UART0 RX from Pulse |
| 11 | SPI2 CS ESP C5 WIFI (QSPI) | 39 | SPI3 MISO/IO1 (DeMon to Pulse) |
| 12 | UART0 CTS (to Pulse) | 40 | SPI3 WP/IO2* |
| 13 | UART0 RTS (to Pulse) | 41 | SPI3 HOLD/IO3 |
| 14 | SPI2 CS Pulse (QSPI) | 42 | SPI3 CS (QSPI) |
| 15 | SPI2 CS FireStorm (QSPI) | 43 | SPI3 SCK |
| 16 | JTAG TDI | 44 | SPI3 MOSI/IO0 (Pulse to DeMon) |
| 17 | JTAG TMS | 45 | UPDI to Clicky |
| 18 | JTAG TCK | 46 | MCP23017 INT-A |
| 19 | JTAG TDO | 47 | SPI2 WP/IO2 |
| 20 | MCP23017 INT-B | 48 | SPI2 HOLD/IO3 |
| 21 | NFC interrupt | 49 | RMII TX EN |
| 22 | SCREEN CMD/DATA | 50 | RMII REF CLK |
| 23 | VBLANK from FireStorm | 51 | PHY reset |
| 24 | JTAG USB P1 0P | 52 | MDIO (SMI DATA) |
| 25 | JTAG USB P1 0N | 53 | UART2 TX (to Clicky) |
| 26 | USB1P1_1P | 54 | UART2 RX (from Clicky) |
| 27 | USB1P1_N |
**** = spare for now
A Note on the Previous Architecture
In earlier prototypes, DeMon acted only as a low-level supervisor, with AntOS hosted on a separate application SoC. Consolidating AntOS onto DeMon's ESP32-P4 (paired with an ESP32-C5 for connectivity) brings several benefits:
- AntOS persistence across FPGA reload — bitstream changes no longer require an OS reboot, only a brief screen blackout (a fraction of a second for the native chipset; up to ~1 second for a personality)
- One coherent OS processor — AntOS runs entirely on DeMon's ESP32-P4 rather than being split across a separate application SoC
- 32MB PSRAM — enough for AntOS, application data, and OTA staging
- Native MIPI display engine — DeMon drives its UI directly into the FPGA as a composited layer
- PIE extensions — small AI / DSP acceleration available in the OS without a separate accelerator chip
- Native Wi-Fi 6 + BT 5.3 + Thread + Zigbee via the ESP32-C5
- Simpler power tree — both ESPs are 3.3 V parts with well-characterised power management
The trade-off is that FireStorm in the FPGA is no longer an OS host — it runs user applications, games, demos, and real-time chipset interaction code, while AntOS-level services (filesystem, network, scheduler, UI, debug server) live on DeMon. The two communicate via the QSPI register window and the MIPI link.