picoZ80 Technischer Leitfaden

picoZ80 Technischer Leitfaden

Dieser Leitfaden dokumentiert die picoZ80 Hardware-Architektur, die RP2350 PIO-Busschnittstelle, das Speichermodell, die JSON-Konfigurationsreferenz, das virtuelle Geräteframework und die Debugging-Verfahren. Er richtet sich an Entwickler, die die internen Abläufe verstehen, neue Treiber schreiben, die Firmware auf einen neuen Hostrechner portieren oder Probleme auf Firmware-Ebene debuggen möchten.
Für die Einrichtung durch Endbenutzer und die Nutzung der Weboberfläche siehe das picoZ80 Benutzerhandbuch. Für die Projektübersicht und Bauanleitungen siehe die picoZ80-Projektseite.

Hardware-Architektur

Der picoZ80 integriert fünf Subsysteme auf einer einzelnen kompakten Platine, die so konzipiert ist, dass sie in die Grundfläche eines DIP-40-Gehäuses passt. Die gesamte Logik arbeitet mit 3,3V; die Z80-Busschnittstelle übernimmt die Pegelanpassung und Stromtreibung für den 5V-Hostbus.

Systemblockdiagramm

┌─────────────────────────────────────────────────────────────────────────┐
│                         picoZ80 PCB                                     │
│                                                                         │
│  ┌────────────────────────────┐      ┌──────────────────────────────┐  │
│  │         RP2350B            │      │           ESP32-S3           │  │
│  │  (Cortex-M33, dual core)   │      │                              │  │
│  │                            │      │  ┌──────┐  ┌──────────────┐ │  │
│  │  Core 0: USB, file I/O,    │◄────►│  │  SD  │  │  Web Server  │ │  │
│  │          ESP32 relay       │ FSPI │  │ Card │  │  (Bootstrap) │ │  │
│  │  Core 1: Z80 bus hot loop  │ UART │  └──────┘  └──────────────┘ │  │
│  │                            │      │                              │  │
│  │  PIO 0,1,2: bus interface  │      │  WiFi ─── 802.11 b/g/n AP   │  │
│  │                            │      │           or Client mode     │  │
│  │  16MB SPI Flash            │      └──────────────────────────────┘  │
│  │  8MB PSRAM (SPI)           │                                         │
│  └────────────────────────────┘                                         │
│                │                                                         │
│       ┌────────┴────────┐                                               │
│       │ Z80 Bus Interface│                                               │
│       │ (40-pin DIP out) │                                               │
│       └────────┬────────┘                                               │
│                │ 5V bus (A0–A15, D0–D7, MREQ, IORQ, RD, WR...)         │
└────────────────┼────────────────────────────────────────────────────────┘
                 │
         ┌───────┴───────┐
         │  Host Z80     │
         │  DIP-40 socket│
         │  (legacy      │
         │   computer)   │
         └───────────────┘

Hauptkomponenten

Komponente Bauteil Funktion
Primärer MCURP2350B (QFN-80)Dual Cortex-M33, 150MHz (bis zu 300MHz OC), 512KB SRAM, 12 PIO State Machines, 48 GPIO-Pins
FlashW25Q128 (16MB SPI)Bootloader, duale Firmware-Slots, Konfigurationspartitionen
PSRAM8MB SPI PSRAM64 × 64KB RAM/ROM-Bänke für den Z80-Adressraum
KoprozessorESP32-S3-PICO-1WiFi, SD-Karte, Webserver, OTA
USB-HubCH334FUSB-Hub, Firmware-Update-Überbrückung
StromversorgungTLV62590BV5V → 3,3V synchroner Abwärtswandler

RP2350B GPIO-Zuordnung

Das RP2350B QFN-80-Gehäuse stellt 48 GPIO-Pins bereit. Der picoZ80 nutzt praktisch jeden Pin. Die Zuordnung ist im Platinendesign festgelegt und wird in den PIO-Programmen abgebildet:
GPIO-Bereich Signale Richtung
GPIO 0–15 A0–A15 (Z80 Address Bus) Ausgang (von PIO angesteuert)
GPIO 16–23 D0–D7 (Z80 Data Bus) Bidirektional (PIO Tri-State)
GPIO 24 MREQ Ausgang
GPIO 25 IORQ Ausgang
GPIO 26 RD Ausgang
GPIO 27 WR Ausgang
GPIO 28 M1 Ausgang
GPIO 29 RFSH Ausgang
GPIO 30 BUSREQ Eingang
GPIO 31 BUSACK Ausgang
GPIO 32 HALT Ausgang
GPIO 33 INT Eingang
GPIO 34 NMI Eingang
GPIO 35 WAIT Ausgang
GPIO 36 CLK Eingang (Host-Takt)
GPIO 37 RESET Eingang
GPIO 38–41 ESP32 FSPI (CS, CLK, MOSI, MISO) SPI
GPIO 42–43 ESP32 UART (TX, RX) UART
GPIO 44–45 PSRAM SPI SPI
GPIO 46–47 USB (D+, D–) USB

Firmware-Architektur

Die RP2350-Firmware wird mit dem Raspberry Pi Pico SDK 2.x für die RP2350-arm-s-Plattform erstellt. Die Firmware ist in zwei unabhängige Programme unterteilt: den Bootloader und die Applikation.

Flash-Speicher-Layout

Partition Adressbereich Größe Inhalt
Bootloader 0x10000000 – 0x1001FFFF 128KB USB-Bridge, Firmware-Update, Partitionsauswahl
App Slot 1 0x10020000 – 0x1051FFFF 5MB Z80-Firmware — aktive Applikation (Slot 1)
App Slot 2 0x10520000 – 0x10A1FFFF 5MB Z80-Firmware — aktive Applikation (Slot 2)
App Config 1 0x10A20000 – 0x10C9FFFF 2,5MB ROM-Images + minifizierte Config-JSON (Slot 1)
App Config 2 0x10CA0000 – 0x10F1FFFF 2,5MB ROM-Images + minifizierte Config-JSON (Slot 2)
General Config 0x10F20000 – 0x10FFEFFF 892KB Kerneinstellungen, Arbeitsspeicher
Partition Table 0x10FFF000 – 0x11000000 4KB Aktive Slot-Nummer, Prüfsummen, Metadaten

Dual-Core-Aufgaben

Die beiden Cortex-M33-Kerne haben vollständig getrennte Aufgaben und kommunizieren über eine Inter-Core-Nachrichtenwarteschlange (queue_t). Diese Trennung stellt sicher, dass nicht-echtzeitkritische Arbeit auf Core 0 niemals Jitter in die Z80-Bustransaktionen auf Core 1 einbringt.
Core Aufgaben
Core 0 USB CDC-Serial-Bridge; Firmware-Update-Koordination; Datei-I/O (über UART an ESP32 weitergeleitet); ESP32-Befehlsverteilung (Disk-Image-Wechsel, Konfigurationsneuladen, Versionsabfragen); Partitionsverwaltung; Inter-Core-Nachrichtenverteilung.
Core 1 Z80-Busemulations-Hauptschleife — läuft exklusiv. Bedient PIO-FIFOs, löst jede Bustransaktion gegen die Speicherzuordnung auf und leitet weiter an: physische Host-Hardware (PHYSICAL), PSRAM (RAM/ROM) oder virtuellen Geräte-Handler (FUNC). Die innere Schleife ist im SRAM platziert.

PIO-Busschnittstelle

Die Z80-Busschnittstelle ist vollständig in RP2350 PIO-Assembler (z80.pio) implementiert. Der RP2350 stellt drei PIO-Blöcke bereit (PIO 0, PIO 1, PIO 2), jeder mit vier State Machines — insgesamt zwölf State Machines, von denen die Z80-Firmware alle zwölf nutzt.
PIO-Programme werden unabhängig von den Cortex-M33-Kernen ausgeführt. Die Busschnittstelle reagiert weiterhin deterministisch, auch wenn Core 1 mit PSRAM-Zugriffen oder virtuellen Geräte-Funktionsaufrufen beschäftigt ist. State Machines kommunizieren über PIO-IRQ-Flags anstatt durch Polling, wodurch die Latenz zwischen den Maschinen eliminiert wird.

PIO-Programmtabelle

PIO State Machine Programm Funktion
0 SM 0 z80_addr Gibt die 16-Bit-Adresse (A0–A15) auf den Bus aus und signalisiert den Zyklusstart an SM 2.
0 SM 1 z80_data Treibt oder tastet D0–D7 mit Tri-State-Steuerung ab; wird während BUSRQ freigegeben.
0 SM 2 z80_cycle Übergeordneter Buszyklus-Sequenzer — orchestriert Fetch-, Lese-, Schreib-, I/O- und DRAM-Refresh-Zyklen.
0 SM 3 z80_fetch Opcode-Abrufzyklus (M1 + MREQ + RD).
1 SM 0 z80_mem_read Speicher-Lesezyklus (MREQ + RD).
1 SM 1 z80_mem_write Speicher-Schreibzyklus (MREQ + WR).
1 SM 2 z80_io_read I/O-Lesezyklus (IORQ + RD).
1 SM 3 z80_io_write I/O-Schreibzyklus (IORQ + WR).
2 SM 0 z80_busrq Verwaltet BUSREQ/BUSACK; gibt /IORQ, /MREQ, /RFSH, /M1, /HALT, /WR, /RD frei.
2 SM 1 z80_nmi Erkennt NMI-Aktivierung und signalisiert Core 1.
2 SM 2 z80_clk_sync Synchronisiert PIO-State-Machines mit dem Host-Z80-CLK-Signal.
2 SM 3 z80_int_ack Behandelt Interrupt-Bestätigungszyklen (M1 + IORQ).

PIO-IRQ-Signalkonventionen

Die Kommunikation zwischen State Machines nutzt PIO-IRQ-Flags. Core 1 überwacht diese Flags in der Hauptschleife, um bei jedem Busereignis zu reagieren:
IRQ Ereignis
IRQ 0 Adresse gültig / Zyklusstart — ein neuer Buszyklus hat begonnen und A0–A15 sind stabil.
IRQ 1 Datenphase — die Datenbusrichtung wurde aufgelöst; D0–D7 sollten getrieben oder abgetastet werden.
IRQ 2 T1 erkannt — die steigende Flanke von T1 im aktuellen Zyklus. Wird zur Synchronisation interner Operationen mit dem Host-Takt verwendet.
IRQ 3 RESET-Ereignis — die Host-RESET-Leitung wurde aktiviert. Core 1 sollte den Emulationszustand reinitialisieren.
IRQ 4 NMI erkannt — Host-NMI-Leitung aktiviert.
IRQ 6 BUSRQ aktiv — Host hat BUSREQ aktiviert; PIO gibt den Bus frei.

Wait-State-Erzeugung

Das z80_wait-PIO-Programm in PIO 2 SM 0 fügt konfigurierbare T-Zyklus-Wait-States auf dem Hostbus ein, indem es /WAIT aktiviert. Die Anzahl der zusätzlichen Wait-States wird pro Speicher- oder I/O-Block durch den tcycwait-Parameter in config.json gesteuert.
Wait-States sind notwendig, wenn der RP2350 zusätzliche Zeit benötigt, um einen PSRAM-Zugriff oder einen virtuellen Geräte-Funktionsaufruf abzuschließen, bevor Daten auf dem Hostbus bereitgestellt werden. Der tcycsync-Parameter aktiviert die T1-Synchronisation (z80_sync in PIO 2 SM 1), die das PSRAM-Zugriffsfenster an die steigende T1-Flanke jedes Buszyklus koppelt und so Timing-Drift bei Anwendungen verhindert, die für präzises Timing auf den Host-Takt angewiesen sind (Kassette, serielles Bit-Banging).

PIO-Architektur — Wie Buszyklen nachgebildet werden

Das Programmable I/O (PIO)-Subsystem des RP2350 ist die Schlüsseltechnologie, die es dem picoZ80 ermöglicht, zyklusgenaues Z80-Bustiming nachzubilden. Das Verständnis der Zusammenarbeit der PIO-State-Machines ist für jeden unerlässlich, der die Busschnittstelle modifizieren oder Timing-Probleme debuggen möchte.

RP2350 PIO-Grundlagen
Jeder PIO-Block enthält vier unabhängige State Machines (SMs), die kleine Programme aus einem gemeinsamen 32-Befehls-Speicher ausführen. State Machines laufen unabhängig von den Cortex-M33-Kernen mit der Systemtaktfrequenz (bis zu 300 MHz). Wichtige PIO-Ressourcen, die vom picoZ80 genutzt werden:
  • TX FIFO — eine 4-Eintrags-Warteschlange von der CPU zur State Machine. Der C-Code auf Core 1 schiebt Daten (Adressen, Steuerwörter, injizierte Befehle) in den FIFO; das PIO-Programm liest sie mittels out oder pull.
  • RX FIFO — eine 4-Eintrags-Warteschlange von der State Machine zur CPU. Die PIO schiebt Datenbus-Abtastwerte mittels in in den FIFO; Core 1 liest sie nach Abschluss jedes Buszyklus.
  • IRQ-Flags — 8 Flags (IRQ 0–7), die von allen State Machines innerhalb eines PIO-Blocks gemeinsam genutzt werden. Flags können auch über PIO-Blöcke hinweg gesehen werden (IRQ 0–3 in einem Block entsprechen IRQ 4–7 im benachbarten Block). State Machines verwenden irq set / irq wait / irq clear zur gegenseitigen Synchronisation und zur Synchronisation mit dem C-Code.
  • Scratch-Register X und Y — zwei 32-Bit-Register pro SM, die für Schleifenzähler und temporäre Werte verwendet werden.
  • out exec — ein spezieller Befehl, der einen Wert aus dem TX FIFO liest und ihn als PIO-Befehl ausführt. Dies ist der Mechanismus, mit dem der C-Code die Buszyklussequenzen dynamisch steuert (siehe unten).
  • set pins / out pins — steuern GPIO-Pins direkt. set verwendet einen unmittelbaren 5-Bit-Wert; out verschiebt Daten vom Ausgangsschieberegister (OSR) zu den Pins.
  • in pins — tastet GPIO-Pins in das Eingangsschieberegister (ISR) ab und überträgt dann automatisch in den RX FIFO.
  • wait gpio — blockiert die SM, bis ein bestimmter GPIO-Pin einen festgelegten Pegel erreicht. Wird intensiv zur Synchronisation mit dem Host-Z80-Taktsignal verwendet.
  • Side-set — ermöglicht die Ansteuerung von ein oder zwei GPIO-Pins als Nebeneffekt eines beliebigen Befehls, ohne einen Befehlszyklus zu verbrauchen. Der picoZ80 verwendet 2-Bit-Side-set zur gleichzeitigen Steuerung von /RD und /WR zusammen mit anderen Operationen.
  • JMP PIN — bedingter Sprung basierend auf dem Pegel eines festgelegten GPIO-Pins. Wird zum Testen von /WAIT, BUSREQ, /NMI und /RESET verwendet.

Der out exec-Mechanismus — Dynamische Befehlsinjektion
Das markanteste Merkmal des picoZ80-PIO-Designs ist die Verwendung von out exec, 16 in der z80_cycle-Orchestrator-State-Machine. Dieser Befehl liest einen 16-Bit-Wert aus dem TX FIFO und führt ihn sofort als PIO-Befehl aus — der Wert sind keine Daten, er ist der nächste Befehl, den die State Machine ausführen wird.
Dieser Mechanismus ermöglicht es dem C-Code auf Core 1, die Buszyklussequenz in Echtzeit zu steuern. Anstatt ein festes PIO-Programm für jeden Zyklustyp zu laden, schiebt Core 1 eine Sequenz vorab kodierter PIO-Befehle in den TX FIFO, und die Zyklus-SM führt sie nacheinander aus:
// z80_cycle SM (PIO 0 SM 2) — the orchestrator
//
// .program z80_cycle
// .side_set 2 opt
// public start_cycle:
//     wait 0 irq 6             ; Pause if BUSACK is active (bus relinquished).
//     irq set 0                ; Signal "ready for new cycle".
//     wait 0 irq 0             ; Wait until C code clears IRQ 0 (address loaded).
//     wait 1 gpio Z80_PIN_CLK  ; Sync to T1 rising edge of host clock.
// cycle_exec:
//     out exec, 16             ; ← Pull next instruction from TX FIFO and execute it.
//     jmp cycle_exec           ; Loop: keep executing injected instructions.
//
// The C code pushes a sequence of encoded PIO instructions into the FIFO.
// Each instruction controls one step of the bus cycle (assert /MREQ, wait for
// clock edge, read data bus, etc.). The sequence ends with a JMP back to
// start_cycle, which restarts the orchestrator for the next bus transaction.
Der C-Code berechnet diese Befehlssequenzen beim Start für jeden Zyklustyp vor (Fetch, Speicherlesen, Speicherschreiben, I/O-Lesen, I/O-Schreiben, Refresh, Interrupt-Bestätigung). Während der Ausführung wählt Core 1 die passende vorgefertigte Sequenz aus und schiebt sie in den FIFO. Dieser Ansatz hat zwei entscheidende Vorteile:
  • Programmspeicher-Effizienz — jeder PIO-Block hat nur 32 Befehlsslots. Durch dynamisches Injizieren von Befehlen benötigt die Zyklus-SM nur 7 Befehle Programmspeicher, um alle Zyklustypen zu orchestrieren. Die eigentlichen Zyklustyp-Programme (Fetch, Lesen, Schreiben usw.) existieren als C-Arrays kodierter Befehle, nicht als residente PIO-Programme.
  • Flexibilität — der C-Code kann die injizierte Befehlssequenz zur Laufzeit ändern, um Sonderfälle zu behandeln (z.B. zusätzliche Wait-States einfügen, die Refresh-Phase überspringen oder einen nicht standardkonformen Zyklus zum Debuggen erzeugen).

State-Machine-Koordination
Die 12 State Machines arbeiten als koordinierte Pipeline. Das folgende Diagramm zeigt den Ablauf eines typischen Speicher-Lesezyklus:
                  Core 1 (C code)               PIO State Machines
                  ─────────────                 ──────────────────
    1. Resolve address                          z80_cycle: IRQ 0 set
       from memory map                           (waiting for work)
                  │
    2. Push addr → TX FIFO ──────────────────→ z80_addr: receives addr
       Clear IRQ 0                               outputs A0–A15 on pins
                  │
    3. Push cycle instructions ──────────────→ z80_cycle: out exec, 16
       (e.g. mem_read sequence)                   executes: set /MREQ low
       into cycle SM TX FIFO                      executes: set /RD low
                  │                               executes: wait CLK edges
    4. Wait for RX FIFO ←────────────────────  z80_data: samples D0–D7
       (data byte from bus)                       pushes to RX FIFO
                  │
    5. Read data from                          z80_cycle: JMP start_cycle
       RX FIFO                                    (ready for next cycle)
                  │
    6. Dispatch to PSRAM
       or driver handler
Der IRQ-basierte Handshake stellt sicher, dass der Adressbus stabil ist, bevor Steuersignale aktiviert werden, und dass Daten zum richtigen Zeitpunkt im Buszyklus abgetastet werden. Die State Machines pollen niemals — sie verwenden wait 0 irq N, um zu schlafen, bis das relevante Ereignis eintritt, und verbrauchen dabei null CPU-Zyklen während des Wartens.

Z80-Abrufzyklus (M1-Zyklus) — Schritt für Schritt
Der Opcode-Abruf ist der komplexeste Z80-Buszyklus — er kombiniert einen Speicherlesevorgang mit einem Refresh-Zyklus. Das z80_fetch-Programm wird über 4 T-Zyklen des Host-Takts ausgeführt:
Host CLK:  ──┐  ┌──┐  ┌──┐  ┌──┐  ┌──
             │  │  │  │  │  │  │  │
             └──┘  └──┘  └──┘  └──┘
              T1    T2    T3    T4

A0–A15:    ══╤═══ PC address ══════╤═══ Refresh addr ══╗
             │                     │                    ║
/M1:       ──┘                     └────────────────────╜── (low during T1–T2, high T3–T4)
/MREQ:     ────┘              ┌────┘              ┌──── (low T1↓–T3↑, then T3↓–T4↓ for refresh)
/RD:       ────┘              ┌─────────────────────── (low T1↓–T3↑)
/RFSH:     ────────────────────┘                   ┌── (low T3↑–T4↓)
D0–D7:     ═══════════════╤═══╗                        (sampled at T3↑)
                          │   ║
                       opcode read
Das PIO-Programm implementiert dies wie folgt:
  1. T1 steigende Flankez80_addr-SM gibt den PC-Wert auf A0–A15 aus. z80_fetch aktiviert /M1 auf Low über set pins.
  2. T1 fallende Flanke/MREQ und /RD werden auf Low gesetzt (über set pins und Side-set). Die Adresse ist nun gültig und das Speichersystem kann beginnen zu antworten.
  3. T2 — die SM tritt in eine Wait-State-Schleife ein: sie wartet auf die steigende, dann fallende CLK-Flanke und prüft dann den /WAIT-Pin über jmp pin. Wenn /WAIT Low ist, wiederholt die SM die Schleife (und fügt Tw-Zyklen hinzu). Wenn High, fährt sie mit T3 fort.
  4. T3 steigende Flankein pins, 8 tastet D0–D7 (das Opcode-Byte) ab und schiebt es in den RX FIFO. IRQ 1 wird gesetzt, um z80_data/z80_addr zu signalisieren, dass nun die Refresh-Adresse ausgegeben werden soll. /M1, /MREQ und /RD werden deaktiviert; /RFSH wird auf Low gesetzt.
  5. T3 fallende Flanke/MREQ wird erneut aktiviert (für den Refresh-Row-Strobe).
  6. T4 — Refresh wird fortgesetzt. Am Ende von T4 werden /MREQ und /RFSH deaktiviert. Die Zyklus-SM kehrt zu start_cycle zurück, bereit für die nächste Bustransaktion.
Core 1 liest den Opcode aus dem RX FIFO und verwendet ihn, um den Befehl zu dekodieren, die Anzahl der nachfolgenden Speicher- oder I/O-Zyklen zu bestimmen und die entsprechenden Befehlssequenzen zu schieben.

Speicher-Lese- und -Schreibzyklen
Speicher-Lese- und -Schreibzyklen sind einfacher als der Fetch — sie erstrecken sich über 3 T-Zyklen ohne Refresh-Phase.
Memory Read:
Host CLK:  ──┐  ┌──┐  ┌──┐  ┌──
             │  │  │  │  │  │
             └──┘  └──┘  └──┘
              T1    T2    T3

A0–A15:    ══╤═══ address ═══════╗
/MREQ:     ────┘              ┌──── (low T1↓–T3↓)
/RD:       ────┘              ┌──── (low T1↓–T3↓)
D0–D7:     ═══════════════╤═══╗     (sampled at T3↓)


Memory Write:
Host CLK:  ──┐  ┌──┐  ┌──┐  ┌──
             │  │  │  │  │  │
             └──┘  └──┘  └──┘
              T1    T2    T3

A0–A15:    ══╤═══ address ═══════╗
D0–D7:     ══════╤═══ data ═════╗   (driven from T2 onwards)
/MREQ:     ────┘              ┌──── (low T1↓–T3↓)
/WR:       ──────────┘        ┌──── (low T2↓–T3↓)
Beim Lesen aktiviert die z80_mem_read-SM /MREQ und /RD an der fallenden T1-Flanke, wartet durch T2 (und prüft /WAIT auf Wait-States), dann tastet sie den Datenbus an der fallenden T3-Flanke mittels in pins, 8 ab. Beim Schreiben aktiviert z80_mem_write /MREQ an der fallenden T1-Flanke, dann /WR an der fallenden T2-Flanke, nachdem der Datenbus von z80_data angesteuert wird. Beide deaktivieren alle Steuersignale am Ende von T3.

I/O-Lese- und -Schreibzyklen
Z80-I/O-Zyklen verwenden /IORQ anstelle von /MREQ und enthalten immer einen automatischen Wait-State (Tw) zwischen T2 und T3. Dies ist ein architektonisches Merkmal des Z80 — der zusätzliche Zyklus gibt langsameren I/O-Geräten Zeit zum Antworten:
I/O Read:
Host CLK:  ──┐  ┌──┐  ┌──┐  ┌──┐  ┌──
             │  │  │  │  │  │  │  │
             └──┘  └──┘  └──┘  └──┘
              T1    T2    Tw    T3

A0–A15:    ══╤═══ port address ══════════╗
/IORQ:     ──────┘                    ┌──── (low T2↑–T3↓)
/RD:       ──────┘                    ┌──── (low T2↑–T3↓)
D0–D7:     ═══════════════════════╤═══╗     (sampled at T3↓)
Die z80_io_read-SM aktiviert /IORQ und /RD an der steigenden T2-Flanke (nicht an T1 wie bei Speicherzyklen — dies ist die Z80-Spezifikation). Der automatische Tw-Wait-State wird durch dasselbe jmp pin / wait-Schleifenmuster implementiert wie bei Speicherzyklen. I/O-Schreibvorgänge folgen dem gleichen Muster, wobei /WR /RD ersetzt.

BUSREQ / BUSACK-Behandlung
Die z80_busrq-SM (PIO 2 SM 0) überwacht den Host-/BUSREQ-Eingangspin. Wenn /BUSREQ aktiv wird (Low), führt die SM folgendes aus:
  1. Setzt IRQ 6, um der Zyklus-SM zu signalisieren, dass eine Busanforderung ansteht.
  2. Wartet auf den Abschluss des aktuellen Buszyklus (wait 1 irq 0).
  3. Liest ein 32-Bit-Steuerwort aus dem TX FIFO, das die Pinrichtungen und -werte für den Bus-Freigabezustand festlegt — dies schaltet die Adress- und Datenbusse auf Tri-State und aktiviert /BUSACK auf Low.
  4. Wartet in einer Schleife über jmp pin, bis /BUSREQ inaktiv wird (High).
  5. Liest ein zweites 32-Bit-Wort, um die normalen Pinrichtungen wiederherzustellen und /BUSACK zu deaktivieren.
  6. Löscht IRQ 6, wodurch die Zyklus-SM fortfahren kann.
Die Zyklus-SM prüft IRQ 6 zu Beginn jedes Zyklus über wait 0 irq 6 — wenn das Flag gesetzt ist, blockiert die SM, bis die Busanforderung abgeschlossen ist. Dies stellt sicher, dass die Busfreigabe sauber zwischen Zyklen erfolgt, niemals mitten in einem Zyklus.

Taktsynchronisation
Alle Zyklustyp-SMs synchronisieren sich mit dem Host-Z80-Takt über wait 1 gpio Z80_PIN_CLK (auf steigende Flanke warten) und wait 0 gpio Z80_PIN_CLK (auf fallende Flanke warten). Das bedeutet:
  • Die PIO-Programme sind taktfrequenzunabhängig — sie funktionieren bei jeder Host-Taktgeschwindigkeit von DC bis zur maximalen Rate, die der RP2350 erfassen kann (begrenzt durch den PIO-Systemtakt und die GPIO-Abtastrate).
  • Der 300-MHz-PIO-Takt des RP2350 liefert bei 3,5 MHz ungefähr 85 PIO-Zyklen pro Z80-T-State, was mehr als genug Zeit bietet, um PIO-Befehle auszuführen, FIFOs zu bedienen und IRQ-Flags zwischen Taktflanken zu prüfen.
  • Die z80_sync-SM (PIO 2 SM 1) stellt ein T1-Synchronisations-IRQ bereit, das der C-Code verwendet, um PSRAM-Zugriffe mit dem Host-Takt abzugleichen und Timing-Drift bei taktsensitiver Host-Software zu verhindern.
  • Die z80_clk_sync-SM (PIO 2 SM 2) regeneriert den Host-Takt an einem separaten GPIO und liefert einen sauberen Taktausgang für externe Überwachung oder Logikanalysator-Triggerung.

Interrupt-Bestätigungszyklus
Die z80_int_ack-SM (PIO 2 SM 3) implementiert die Z80-Interrupt-Bestätigungssequenz. Wenn der C-Code eine Interrupt-Bedingung erkennt, lädt er das int_ack-Programm. Dieser Zyklus ist ähnlich wie ein Fetch, jedoch mit wichtigen Unterschieden:
  • /M1 wird bei T1 aktiviert (wie beim Fetch), aber /IORQ wird anstelle von /MREQ beim Wait-State (Tw1) aktiviert.
  • Zwei automatische Wait-States (Tw1, Tw2) werden eingefügt, um dem unterbrechenden Gerät Zeit zu geben, einen Vektor auf den Datenbus zu legen.
  • Das Vektor-Byte wird von D0–D7 gelesen und in den RX FIFO geschoben.
  • Ein Refresh-Zyklus folgt, identisch mit der Fetch-Refresh-Phase.

Speichermodell

Speicherzugriffe werden über drei Stufen mit zunehmender Latenz aufgelöst. Das Drei-Stufen-Design stellt sicher, dass der häufigste Fall (PSRAM-gestütztes RAM/ROM) schnell ist, während maximale Flexibilität für virtuelle Geräte und physischen Host-Durchgriff ermöglicht wird.

Stufe 1 — RP2350 SRAM-Dispatch-Tabelle

Ein 128-Eintrags-Array von 32-Bit-membankPtr-Werten, resident im 512KB On-Chip-SRAM des RP2350, bietet eine O(1)-Blocktyp-Suche für jede Bustransaktion. Ein Eintrag deckt jeden 512-Byte-Block des 64KB-Z80-Adressraums ab (128 × 512 = 65.536 Bytes). Jeder Eintrag kodiert:
  • Den Blocktyp (PHYSICAL, RAM, ROM, FUNC usw.).
  • Für PSRAM-gestützte Blöcke: die PSRAM-Banknummer und den Offset.
  • Für FUNC-Blöcke: einen Index in die Funktionszeiger-Tabelle des virtuellen Geräts.
Dies ist der schnellste Pfad — Core 1 liest den Dispatch-Tabelleneintrag für die aktuelle Adresse in einem einzigen SRAM-Zugriff (null Wait-States bei 300MHz), bevor entschieden wird, was als nächstes zu tun ist.

Stufe 2 — Externes PSRAM

Das 8MB PSRAM ist organisiert als:
  • 64 Bänke × 64KB — RAM- oder ROM-Image-Daten für den Z80-Adressraum.
  • 64KB memPtr — Byte-Umleitungszeiger-Array für PTR-Typ-Blöcke.
  • 64KB memioPtr — Funktionszeiger-Array für speicherabgebildete FUNC-Geräte.
  • 64KB ioPtr — Funktionszeiger-Array für I/O-Port FUNC-Geräte.
PSRAM wird über die dedizierte SPI-Peripherie des RP2350 mit DMA zugegriffen. Die Zugriffslatenz ist deterministisch und wird vom Wait-State-Generator verwaltet, um Busverletzungen zu vermeiden.

Stufe 3 — 16MB SPI Flash

ROM-Images werden beim Booten aus dem Flash (oder von der SD-Karte über den ESP32) ins PSRAM geladen. Zur Laufzeit wird der Flash nicht für Bustransaktionen verwendet — alle ROM-Daten werden aus dem PSRAM bereitgestellt. Der Flash wird verwendet für:
  • Bootloader und Applikations-Firmware.
  • Minifizierte config.json (bei jedem Boot von der SD-Karte zwischengespeichert).
  • ROM-Images in App-Config-Partitionen (verwendet, wenn keine SD-Karte vorhanden ist).

Speicherblocktypen

Typ Beschreibung
PHYSICAL Durchreichung — der RP2350 gibt den Bus frei und der physische Host-Speicher antwortet. Wird für das native ROM und RAM des Hosts verwendet.
PHYSICAL_VRAM Wie PHYSICAL, aber mit zusätzlichen Wait-States für das Host-Video-RAM-Timing. Geeignet für MZ-700/MZ-80A VRAM-Bereiche.
PHYSICAL_HW Durchreichung für Host-Hardware-Register (I/O-gemappte Geräte im Speicherbereich).
RAM Lesen/Schreiben — durch eine PSRAM-Bank gestützt. Der RP2350 bedient Lese- und Schreibzugriffe aus dem/in das PSRAM.
ROM Nur lesen — durch eine PSRAM-Bank gestützt. Schreibzyklen werden stillschweigend ignoriert (der Host sieht normales Bustiming, aber es werden keine Daten gespeichert).
VRAM PSRAM-gestütztes Video-RAM. Schreibzyklen werden gleichzeitig sowohl ins PSRAM als auch ins physische Host-VRAM gespiegelt.
FUNC Virtuelles Gerät — jeder Zugriff löst einen C-Funktionsaufruf über die memioPtr- oder ioPtr-Funktionszeiger-Tabelle aus, was eine beliebige I/O-Emulation ermöglicht.
PTR Byte-Umleitung — jedes Byte des 512-Byte-Blocks kann unabhängig auf einen beliebigen anderen Blocktyp oder PSRAM-Speicherort verweisen.

Konfigurationsreferenz

Das gesamte picoZ80-Verhalten wird durch config.json auf der SD-Karte gesteuert. Der RP2350 liest und minifiziert diese Datei beim Booten und speichert das Ergebnis im Flash. Nachfolgende Bootvorgänge verwenden die Flash-Kopie, wenn keine SD-Karte vorhanden ist.
Die JSON-Struktur auf oberster Ebene ist:
{
  "esp32": {
    "core":  { ... },
    "wifi":  { ... }
  },
  "rp2350": {
    "core":  { ... },
    "z80":   [ { "memory": [...], "io": [...], "drivers": [...] } ]
  }
}

esp32.core

Schlüssel Typ Beschreibung
device string CPU-Persönlichkeit — "Z80" für picoZ80, "6502" für pico6502, "6512" für pico6512.
mode integer Standard-WiFi-Bootmodus: 0 = Client (Station), 1 = Access Point.

esp32.wifi

Schlüssel Typ Beschreibung
override 0/1 Hauptschalter: 1 = alle folgenden Einstellungen anwenden; 0 = gespeicherte NVS-Einstellungen verwenden.
wifimode string "ap" = Access-Point-Modus; "client" = Station/Client-Modus.
ssid string WiFi-Netzwerkname zum Erstellen (AP) oder Beitreten (Client).
password string WiFi-Passphrase.
ip string Feste IP-Adresse (z.B. "192.168.1.192").
netmask string Subnetzmaske (z.B. "255.255.255.0").
gateway string Standard-Gateway (z.B. "192.168.1.1").
dhcp 0/1 Client-Modus: 1 = DHCP; 0 = feste IP-Einstellungen verwenden.
webfs string Web-Dateisystem-Stammverzeichnis auf der SD-Karte (Standard "webfs").
persist 0/1 1 = aufgelöste Einstellungen für Persistenz über Neustarts hinweg in NVS schreiben.

rp2350.core

Schlüssel Typ Beschreibung
cpufreq integer RP2350-Systemtakt in Hz (z.B. 300000000). Die maximale stabile Frequenz hängt von der PSRAM-Frequenz und der Kernspannung ab.
psramfreq integer PSRAM-SPI-Takt in Hz (z.B. 133000000).
voltage float RP2350-Kernspannung in Volt (z.B. 1.10). Höhere Taktgeschwindigkeiten erfordern eine höhere Spannung.
addrDrive integer (0–3) Adressbus-GPIO-Treiberleistung: 0=2mA, 1=4mA, 2=8mA, 3=12mA. Standard 0.
addrSlew integer (0–1) Adressbus-GPIO-Flankensteilheit: 0=langsam (Standard), 1=schnell.
dataDrive integer (0–3) Datenbus-GPIO-Treiberleistung. Gleiche Kodierung wie addrDrive. Standard 0.
dataSlew integer (0–1) Datenbus-GPIO-Flankensteilheit. Gleiche Kodierung wie addrSlew. Standard 0.
ctrlDrive integer (0–3) Steuersignal-GPIO-Treiberleistung. Gleiche Kodierung wie addrDrive. Standard 0.
ctrlSlew integer (0–1) Steuersignal-GPIO-Flankensteilheit. Gleiche Kodierung wie addrSlew. Standard 0.
addrSchmitt / dataSchmitt / ctrlSchmitt 0/1 Aktiviert den Eingangs-Schmitt-Trigger für die Adress- / Daten- / Steuerbus-Gruppe. 1 = aktiviert (Standard), 0 = deaktiviert. Gilt für als Eingänge konfigurierte Pins.
addrPull / dataPull / ctrlPull integer (0–2) Pull-Widerstand für die Adress- / Daten- / Steuerbus-Gruppe: 0 = keiner, 1 = Pull-Down, 2 = Pull-Up.
refresh integer Z80-DRAM-Refresh-Erzeugung: 0 = aus (keine Refresh-Zyklen), 1 = ein Refresh-Zyklus bei jedem Opcode-Fetch, N = ein Refresh-Zyklus pro N Fetches. Reduziert den Bus-Overhead auf Hosts, deren DRAM keinen Refresh pro Fetch benötigt.
pinOverrides array GPIO-Überschreibungen pro Pin. Jedes Element: { "pin": N, "drive": D, "slew": S, "schmitt": 0/1, "pull": P }. Überschreibt die Bus-Level-Standards für einzelne GPIO-Pins, wenn spezifische Hardware andere elektrische Eigenschaften erfordert.

z80[].memory — Speicherzuordnungseinträge

Das memory-Array definiert die Z80-Speicherzuordnung. Einträge müssen nach Adresse geordnet sein. Bereiche müssen auf Vielfache von 512 Bytes ausgerichtet und dimensioniert sein. Lücken zwischen Einträgen werden als PHYSICAL-Durchreichung behandelt.
Schlüssel Typ Beschreibung
enable 0/1 Ob dieser Eintrag aktiv ist. Deaktivierte Einträge werden beim Booten ignoriert.
addr hex string Startadresse im Z80-Adressraum (z.B. "0x0000"). Muss 512-Byte-ausgerichtet sein.
size hex string Bereichsgröße in Bytes (z.B. "0x2000" für 8KB). Muss ein Vielfaches von 512 sein.
type string Blocktyp — siehe Speicherblocktypen.
bank integer PSRAM-Banknummer (0–63) für RAM/ROM/VRAM/FUNC-Typen.
tcycwait integer Zusätzliche T-Zyklus-Wait-States, die bei jedem Zugriff auf diesen Bereich eingefügt werden.
tcycsync 0/1 T1-Synchronisation für diesen Bereich aktivieren. Erforderlich für zeitkritische Bereiche.
task string Optionaler Task-Bezeichner für FUNC-Typ-Blöcke (Treiber-Bindungsstring).
file string SD-Karten-Pfad zu einem ROM-Image, das beim Booten in die PSRAM-Bank vorgeladen wird (z.B. "/ROM/mz700.rom").
fileofs integer Byte-Offset in der ROM-Image-Datei, ab dem gelesen werden soll.

z80[].io — I/O-Portzuordnungseinträge

Das io-Array ordnet Z80-I/O-Portbereiche Blocktypen zu. Nur PHYSICAL- und FUNC-Typen sind für I/O-Einträge sinnvoll.
Schlüssel Typ Beschreibung
enable 0/1 Ob dieser I/O-Eintrag aktiv ist.
addr hex string Start-I/O-Portadresse (z.B. "0xE0").
size hex string Anzahl aufeinanderfolgender Ports (z.B. "0x04" für Ports E0–E3).
type string PHYSICAL = an Host weiterleiten; FUNC = C-Handler-Funktion aufrufen.
task string Treiber-Bindungsstring für FUNC-Typ-Einträge.

z80[].drivers — Treiberinstanzen

Das drivers-Array instanziiert virtuelle Gerätetreiber und bindet sie an Speicher- oder I/O-Bereiche. Jeder Treiber hat einen Typ (das C-Treibermodul), einen Namen (Instanzbezeichner) und ein oder mehrere Interface-Objekte, die die ROM-Images, Adresszuordnungen, I/O-Zuordnungen und Parameter für diese Treiberinstanz definieren.
"drivers": [
  {
    "enable": 1,
    "name":   "MZ700",
    "type":   "PHYSICAL",
    "if": [
      {
        "enable": 1,
        "name":   "main",
        "type":   "PHYSICAL",
        "rom": [
          {
            "enable":   1,
            "file":     "/MZ700/mz700.rom",
            "loadaddr": [
              {
                "enable":   1,
                "position": 0,
                "addr":     "0x0000",
                "bank":     0,
                "size":     "0x1000",
                "tcycwait": 0,
                "tcycsync": 0
              }
            ]
          }
        ],
        "addrmap": [
          { "enable":1, "srcaddr":"0x0000", "size":"0x1000",
            "dstaddr":"0x0000" }
        ],
        "iomap": [
          { "enable":1, "srcaddr":"0xE0", "size":"0x08",
            "dstaddr":"0xE0", "16bit":0 }
        ],
        "param": [
          { "enable":1, "file":"/config/mz700.cfg" }
        ]
      }
    ]
  },
  {
    "enable": 1,
    "name":   "MZ-1E05",
    "type":   "PHYSICAL",
    "if": [
      {
        "enable": 1,
        "name":   "fdc0",
        "type":   "PHYSICAL",
        "rom":    [],
        "addrmap": [],
        "iomap": [
          { "enable":1, "srcaddr":"0xD8", "size":"0x04",
            "dstaddr":"0xD8", "16bit":0 }
        ],
        "param": [
          { "enable":1, "file":"/DSK/MZ700/disk0.dsk" }
        ]
      }
    ]
  }
]
Jeder Treibereintrag hat einen übergeordneten "name" (das zu instanziierende C-Treibermodul), "type" (PHYSICAL oder VIRTUAL) und ein "if"-(Interface)-Array mit einem oder mehreren Interface-Objekten. Jedes Interface-Objekt beschreibt einen Untertreiber oder eine Peripheriekarte und enthält diese Felder:
Schlüssel Typ Beschreibung
enable 0/1 Ob dieses Interface aktiv ist (0 = wird bei der Initialisierung übersprungen).
name string Instanzbezeichner — muss einem Namen in der interfaceFuncMap[] der Persona entsprechen.
type string PHYSICAL oder VIRTUAL.
rom array ROM-Images, die in PSRAM geladen werden (siehe unten).
addrmap array Speicheradressen-Umzuordnungseinträge (srcaddr/dstaddr/size).
iomap array I/O-Port-Umzuordnungseinträge (srcaddr/dstaddr/size/16bit).
param array Treiberspezifische Parameter — typischerweise { "enable":1, "file":"/path/to/image" } für Disk-Images, { "name":"key", "value":"val" } für benannte Parameter oder { "ip":"a.b.c.d:port", "enable":1 } für die Celestite-Netzwerk-Dateiserver-Adresse.

Verschiebbare Interface-Basisports. Damit die maschinenunabhängigen Karten auf eigenen / Experimentierboards verwendet werden können (siehe OpenZ80), lesen mehrere Interface-Treiber ihren Basis-I/O-Port aus dem dstaddr ihres iomap-Eintrags (wobei srcaddr auf den authentischen Port der Karte gesetzt ist) und greifen auf ihren ursprünglichen Port zurück, wenn kein iomap-Eintrag vorhanden ist. Die verschiebbaren Karten und Standardbasen sind MZ-1R12 (0xF8/3), MZ-1R18 (0xEA/2), MZ-1R23 (0xB8/2), MZ-1R37 (0xAC/2), PIO-3034 (0x00/4), MZ-8BIO3 / MZ-1E24 (0xB0/4), MZ-1E05 (0xD8/7) und Celestite (0x60/16). Die JSON-Schlüssel sind kleingeschrieben (srcaddr / dstaddr) und ihre Werte werden als Zahlen geparst. Das Feld Base I/O Port auf der GUI-Konfigurationsseite schreibt den korrekten iomap-Eintrag automatisch.

rom[].loadaddr[] — ROM-Ladeadressen

Jeder ROM-Dateieintrag enthält ein loadaddr-Array, das angibt, wohin die ROM-Daten in PSRAM geladen werden. Mehrere loadaddr-Einträge ermöglichen es, eine einzelne ROM-Datei über nicht zusammenhängende Adressbereiche oder PSRAM-Bänke aufzuteilen.
Schlüssel Typ Beschreibung
enable 0/1 Ob diese Ladeadresse aktiv ist.
position integer Byte-Offset innerhalb der ROM-Datei, ab dem gelesen werden soll.
addr hex string Ziel-Z80-Adresse in der Speicherzuordnung (z.B. "0x0000").
bank integer PSRAM-Banknummer, in die geladen wird.
size hex string Anzahl der zu ladenden Bytes (z.B. "0x1000" für 4KB).
tcycwait integer Zusätzliche T-Zyklus-Wait-States für diesen Bereich.
tcycsync integer T-Zyklus-Synchronisationswert für diesen Bereich.

Persona-Interface-Kompatibilität

Nicht jeder Interface-Treiber ist für jede Persona verfügbar. Die folgende Tabelle zeigt die aktuelle Kompatibilität der Sharp MZ / X1-Serien-Interfaces. Die Amstrad PCW-9512-Persona ist ein eigenständiger Treiber mit integriertem uPD765 FDC (keine separaten Interface-Treiber). Die Tatung Einstein TC-01-Persona ist ein eigenständiger Treiber mit integriertem WD1770 FDC und EinsteinFDC-Unter-Interface. Die Spalte Open zeigt die maschinenunabhängigen Karten, die von der OpenZ80-Experimentier-Persona akzeptiert werden.
Die Tabelle zeigt, welche Interfaces mit jeder übergeordneten Maschinen-Persona über das "if"-Array verwendet werden können. Der Interface-"name"-Wert im JSON muss einem der unterstützten Einträge für die gewählte Persona entsprechen.
Interface MZ-700 MZ-1500 MZ-80K MZ-800 MZ-80A MZ-2000 MZ-2200 MZ-80B MZ-2500 Open
RFS Yes Yes Yes Yes Yes Yes
TZFS Yes
MZ-1E05 Yes Yes Yes Yes
MZ80FIO Yes Yes
MZ80AFI Yes Yes Yes
MZ-8BFI / E0054PA Yes Yes Yes Yes Yes
MZ-1E14 Yes Yes Yes Yes Yes Yes
MZ-1E19 Yes Yes Yes Yes Yes Yes Yes Yes
MZ-1R12 Yes Yes Yes Yes Yes Yes Yes Yes Yes Yes
MZ-1R18 Yes Yes Yes Yes Yes Yes Yes Yes Yes Yes
MZ-1R23 Yes Yes Yes Yes Yes
MZ-1R37 Yes Yes Yes Yes Yes Yes
PIO-3034 Yes Yes Yes Yes Yes Yes
Celestite Yes Yes Yes Yes Yes
MZ-8BIO3 Yes Yes Yes Yes Yes
MZ-1E24 Yes Yes Yes Yes Yes
MZ-1E30 Yes Yes

Integrierte Treiber

Die Firmware unterstützt ein zielgerichtetes Build-System mit fünf Modellzielen: BaseZ80 (alle Treiber), SharpZ80 (nur Sharp), AmstradZ80 (nur Amstrad), TatungZ80 (nur Tatung) und OpenZ80 (die maschinenunabhängige Experimentier-Persona). Die Kompilierzeit-Defines INCLUDE_SHARP_DRIVERS, INCLUDE_AMSTRAD_DRIVERS, INCLUDE_TATUNG_DRIVERS und INCLUDE_OPEN_DRIVERS steuern, welche Treibermodule einkompiliert werden. Die folgenden Tabellen listen alle verfügbaren Treiber auf.
Sharp MZ-Serie (INCLUDE_SHARP_DRIVERS)
Wenn die Firmware mit INCLUDE_SHARP_DRIVERS gebaut wird, werden die folgenden Treibermodule einkompiliert und können über das drivers-Array instanziiert werden:
Treiber Typ-String Beschreibung
MZ700.c MZ700 Sharp MZ-700 Bank-Switching, Video, Tastatur-I/O
MZ80A.c MZ80A Sharp MZ-80A — Monitor-ROM (SA-1510), VRAM, Intel 8253 PIT-Emulation, 8255 PPI, MEMSW/MEMSWR-Speichertausch (einschließlich CP/M-Bank-Switching), gemischter physischer+virtueller Modus-Unterstützung und Unterstützung für RFS, MZ80AFI, MZ-1E14, MZ-1E19, MZ-1R12, MZ-1R18 Unter-Interfaces
MZ2000.c MZ2000 Sharp MZ-2000 — BST/NST-Speichermodus-Umschaltung, Zeichen- + Grafik-VRAM-Overlay, 8253 PIT, 8255 PPI, Z80 PIO, MB8866 FDC. Unterstützt sowohl physischen Modus (Z80-Ersatz mit automatischer Boot/Normal-Modus-Erkennung) als auch virtuellen Modus (vollständige PSRAM-basierte Emulation mit IPL-ROM-Spiegelung)
MZ2200.c MZ2200 Sharp MZ-2200 — BST/NST-Speichermodus-Umschaltung, VRAM-Overlay, 8253 PIT, 8255 PPI, Z80 PIO, MB8866 FDC, Farb-CRT
MZ80B.c MZ80B Sharp MZ-80B — 2K IPL-ROM, BST/NST, Monochrom-Display, duale GRPH-Seiten, 8253 PIT, 8255 PPI, Z80 PIO
MZ2500.c MZ2500 Sharp MZ-2500 (SuperMZ) — 8-Seiten-MMU (64 Blöcke), MZ-2000/MZ-80B-Kompatibilitätsmodi, YM2203, G-CRTC, MB8876 FDC, Palette, Interrupt-Controller. Virtueller Modus unterstützt interrupt-gesteuerte Software über benutzerdefinierte fetchByte/RETI-Handler (physische M1-Buszyklen für Gate-Array-Taktung), D88-natives Diskformat mit automatischer Erkennung von Sparse/Contiguous, einmalige Interrupt-Unterdrückung während der Initialisierung
MZ1500.c MZ1500 Sharp MZ-1500 — MZ-700-Obermenge mit eingebautem Quick-Disk-Laufwerk, PCG, Stereo-PSG-Sound (SN76489AN), Z80 PIO-Druckerschnittstelle, 8253 PIT, DIP-Schalter MZ-700/MZ-1500-Moduswahl. Unter-Interfaces: RFS, MZ-1E05, MZ-1E14, MZ-1E19, MZ-1R12, MZ-1R18, MZ-1R23, MZ-1R37, PIO-3034, Celestite
MZ80K.c MZ80K Sharp MZ-80K — SP-1002 Monitor-ROM (0x0000–0x0FFF), 2KB VRAM (0xD000–0xD7FF), 8255 PPI / 8253 PIT / LS367-Tastatur (0xE000–0xE7FF), natives MZ-80FD Boot-ROM (0xF000–0xF3FF), MEMSW/MEMSWR-Speichertausch für CP/M. Physischer + virtueller Modus (CP/M im physischen Modus nutzt den treiberinternen Z80-Zugriffs-Remap; der virtuelle Modus ist für unverändertes CP/M erforderlich). Zwei Floppy-Pfade: nativer MZ80FIO (T3444M) — ursprüngliche MZ-80FD-Schnittstelle, bootet/liest alle MZ-80K-Disks; und MZ80AFI (MZ-80A-FDC) — verwendet für CP/M, bootet MZ-80K-CP/M und liest MZ-80K-CP/M-Disks aus CP/M heraus (C:/D:). Es ist immer nur eine Floppy-Schnittstelle gleichzeitig aktiv; MZ80FIO hat Vorrang, wenn beide vorhanden sind. Unter-Interfaces: RFS, MZ80FIO, MZ80AFI, MZ-1E14, MZ-1E19, MZ-1R12, MZ-1R18, MZ-1R37, PIO-3034
MZ800.c MZ800 Sharp MZ-800 — Dual-Modus-Treiber (MZ-700-Kompatibilität + natives MZ-800). Schnüffelt am GDG-Display-Modus-Register (Port 0xCE), um Modi zur Laufzeit umzuschalten: Der native Modus ergänzt 320×200- / 640×200-Grafik (VRAM-Ebenen 0x8000–0xBFFF), 4/16-Farben-Palette, portgemapptes GDG-I/O (0xCC–0xCF, Palette 0xF0), IM2-vektorisierte Interrupts über die Z80-PIO-Daisy-Chain; der MZ-700-Modus nutzt speichergemapptes 8255/8253 (0xE000–0xE7FF) und Text-VRAM (0xD000). SN76489 PSG (0xF2), WD1773 FDC (0xD8–0xDF), QuickDisk (0xF4–0xF7), Memory-Banking-Ports 0xE0–0xE6. Der virtuelle Modus spielt ein physisches RETI auf dem realen Bus ab, um den PIO-In-Service-Latch zu bedienen.
MZ80AFI.c MZ80AFI Sharp MZ-80A Floppy-Interface — emuliert den MZ-80A AFI Floppy-Disk-Controller
MZ80FIO.c MZ80FIO Sharp MZ-80FD/MZ-80FIO Floppy-Interface (MZ-80K) — FDIF Boot-ROM bei 0xF000–0xF3FF, T3444M-Ports 0xF8–0xFB, bis zu 4 Laufwerke, Disk-Wechsel zur Laufzeit
T3444M.c (verwendet von MZ80FIO) Toshiba T3444M/T3444A FDC — natives MZ-80K-Floppy. CPC-Extended-DSK, 35 Spuren, 2 Köpfe, 16 Sektoren/Spur, 128-Byte-FM-Sektoren; robuster Track-Info-DSK-Parser; 4 gleichzeitige Laufwerks-Images
WD1773.c WD1773 WD1773 FDC — 80 Spuren, 2 Köpfe, 8 Sektoren, DSK/RAW/D88-Images
QDDrive.c QDDRIVE Sharp QuickDisk-Laufwerk — vollständige Z80 SIO/2-Emulation mit Spiralspurdaten, Motorsteuerung und asynchronem SD-Karten-I/O
RFS.c RFS ROM Filing System — MZF-Laden, CP/M, BASIC von SD-Karte
TZFS.c TZFS TranZPUter Filing System — funktionsfähiger Multi-Bank-Monitor + CP/M-Dateisystem, auswählbar auf der MZ-700-Persona. tranZPUter-Speichermodi über Port 0x60, virtueller K64F-Service-Prozessor über OUT (0x68), CP/M-Sektor-I/O über den ESP32. ROM roms/tzfs.bin aus TZFS/asm/tzfs.asm
MZ-1E05.c MZ1E05 Sharp MZ-1E05 Floppy-Disk-Interface-Einheit (WD1773-basiert)
MZ8BFI.c MZ8BFI / E0054PA MZ-2000 Floppy-Disk-Interface — MB8866 FDC ohne Treiber-ROM (Code im IPL). D88-Format-Unterstützung.
MZ-1E14.c MZ1E14 MZ-1E14 QuickDisk-Controller mit BIOS-ROM (MZ-700/MZ-800)
MZ-1E19.c MZ1E19 MZ-1E19 QuickDisk-Controller ohne BIOS-ROM
MZ-1R12.c MZ1R12 32KB batteriegepuffertes RAM-Board (auf SD-Karte persistiert)
MZ-1R18.c MZ1R18 64KB RAM-Erweiterungsboard
MZ-1R23.c MZ1R23 MZ-1R23 128KB Kanji-ROM (16×16 JIS-Muster) und MZ-1R24 256KB Dictionary-ROM. ROM-Dateien werden von der SD-Karte geladen. I/O-Ports B8h–B9h mit Auto-Increment-Lesevorgang
MZ-1R37.c MZ1R37 MZ-1R37 640KB EMM (Expanded Memory Manager) — 20-Bit-Adressraum mit I/O-Port-Adresslatch
PIO-3034.c PIO3034 IO DATA PIO-3034 320KB EMM — 19-Bit-Adresszähler mit Auto-Increment-Datenport
Celestite.c Celestite Celestite-Komposit-Board — Wiznet W5100 Ethernet-Controller (Registeremulation), Interrupt-Controller, UFM, integriertes MZ-1R12 32KB CMOS-RAM (erweiterbar auf 64KB), optionaler MZ-1R37 640KB EMM. I/O-Ports 60h–6Fh. Der ip-Parameter im JSON-param-Array konfiguriert die netfs.py-Dateiserver-Adresse (z.B. "192.168.1.210:6800"). Phase 2: echtes TCP/IP-Networking über ESP32-Bridge — W5100-Socket-Befehle werden an ESP32 für tatsächliche BSD-Socket-Operationen weitergeleitet. Inter-Core-IPC für NET_CFG, NET_SOCK, NET_SEND, NET_RECV, NET_PING
MZ8BIO3.c MZ-8BIO3 RS-232C-Serienkarte (BI-Steckverbinder), emulierter Z80 SIO an Ports 0xB0–0xB3 (konfigurierbare Basis). Kanäle A/B auf USB-CDC-Serienports 2 und 3 gebrückt. Kein ROM.
MZ1E24.c MZ-1E24 RS-232C-Serienkarte (Sharp-ST-Steckverbinder); identisch mit MZ-8BIO3, jedoch mit anderer Steckverbinderverdrahtung.
Z80SIO.c (verwendet von MZ-8BIO3 / MZ-1E24) Registergenauer Zilog Z80 SIO/2: WR0–WR7, RR0–RR2, Z80-Mode-2-vektorisierte Interrupts, 4-stufige In-Service-Daisy-Chain, Status-affects-Vector. Lock-freie SPSC-Ringe brücken den Z80-Kern (Core 1) und den USB-CDC-Dienst (Core 0).
SASI.c + MZ1E30.c MZ-1E30 MZ-1E30 SASI-Festplattencontroller — emuliert den Sharp MZ-1E30 SASI (Shugart Associates System Interface) Festplattencontroller für MZ-2500/MZ-80B. Bis zu 4 Disk-Targets (~21,4 MB je, 256-Byte-Blöcke), 32KB IPL-ROM (I/O-Zugriff über Ports 0xA8–0xA9), bedarfsgesteuerter Sektor-I/O von SD-Karten-Disk-Images. SASI-Befehle: TEST_UNIT_READY, REQUEST_SENSE, READ(6), WRITE(6), SEEK(6), INQUIRY. I/O-Ports 0xA4–0xA5 (SASI-Daten/Steuerung)
PIT8253.c PIT8253 Eigenständige Intel 8253 PIT-Emulation — alle sechs Zählermodi, BCD/Binär, Latch, LSB/MSB-Lese-/Ladevorgang
PPI8255.c PPI8255 Eigenständige Intel 8255 PPI-Emulation — Modus 0 I/O, Bit-Set/Reset, Ausgangs-Callbacks, Eingangsinjektion
Amstrad PCW-Serie (INCLUDE_AMSTRAD_DRIVERS)
Wenn die Firmware mit INCLUDE_AMSTRAD_DRIVERS gebaut wird, werden die folgenden Treibermodule einkompiliert:
Treiber Typ-String Beschreibung
PCW9512.c PCW9512 Amstrad PCW-9512 — Z80A @ 4MHz, 512KB RAM mit 4-Bank-16KB-Seitenumschaltung (Ports F0–F3), Gate Array (ASIC) für Video/Systemtakt/FDC-Routing/Motorsteuerung (Port F8), 8041 Typenrad-Druckercontroller (Ports FC–FD), Bootstrap-Sequenz-Emulation. Virtueller und physischer Modus unterstützt
uPD765.c NEC uPD765 FDC-Emulation — CPC DSK-Format-Unterstützung (Ports 00–01). Gate-Array-Befehle: Bootstrap beenden, Neustart, FDC-INT-Routing (NMI/INT/ignorieren), Terminal Count, Motor ein/aus. Physisches Disk-Imaging über injizierten Z80-Code. Wiederverwendbares FDC-Modul (analog zu WD1773.c für Sharp)
Tatung Einstein-Serie (INCLUDE_TATUNG_DRIVERS)
Wenn die Firmware mit INCLUDE_TATUNG_DRIVERS gebaut wird, werden die folgenden Treibermodule einkompiliert:
Treiber Typ-String Beschreibung
EinsteinTC01.c EinsteinTC01 Tatung Einstein TC-01 — Z80A @ 4MHz, 64KB RAM + 8KB umschaltbares ROM (X-TAL MOS), ROM/RAM-Umschaltung über Port 0x24, TMS9129 VDP mit erzwungenem Zugriffstiming (~2us Abstand), AY-3-8910 PSG (Ports 0x02-0x03), Z80 CTC (Ports 0x28-0x2B), Z80 PIO (Ports 0x30-0x33), Tastaturschnittstelle (Port 0x20). Virtueller und physischer Modus unterstützt
EinsteinFDC.c Einstein FDC-Unter-Interface — 2 Laufwerke, DSK/D88-Format-Unterstützung. Physisches Disk-Imaging: physische Floppy zu DSK lesen, DSK auf physische Floppy schreiben
WD1770.c WD1770 FDC-Emulation — Extended CPC DSK-, D88- und Standard-DSK-Format-Unterstützung. 40 Spuren, 1 Kopf, 10 Sektoren, 512 Bytes (200KB-Disketten). Wiederverwendbares FDC-Modul für WD1770-basierte Maschinen (getrennt von WD1773.c, das für Sharp verwendet wird)

TZFS — Monitor- + CP/M-Modell (MZ-700-Persona)
TZFS.c implementiert einen funktionsfähigen Multi-Bank-Low-Level-Monitor und ein Dateisystem — eine Reihe von Erweiterungen gegenüber dem ursprünglichen MONITOR 1Z-013A (SD-Zugriff, ROM-Banking, ein Assembler/Disassembler und Werkzeuge) — modelliert nach dem TZFS der tranZPUter SW und ihrem virtuellen K64F-I/O-Prozessor. Es wird als auswählbare Schnittstelle ausschließlich auf der MZ-700-Persona angeboten (in MZ700.c neben RFS registriert und in der Praxis mit ihm gegenseitig ausschließend), nicht als übergeordnete Persona. CP/M läuft unter dem Monitor.
Speichermodus-Emulation (Port 0x60)
Das Schreiben eines tranZPUter-Speichermodus an den I/O-Port 0x60 richtet die Bank-Zeiger neu aus. Die Modi sind TZMM_ORIG, TZMM_BOOT, TZMM_TZFS, TZMM_TZFS2, TZMM_TZFS3, TZMM_TZFS4, TZMM_CPM, TZMM_CPM2 und TZMM_COMPAT. Das CP/M-CPM2-Layout verwendet byte-granulares Block-0-Paging — 0x00000x003F werden auf den Vektoren-Block und 0x00400x01FF auf den Anfang der TPA abgebildet.
Virtueller K64F-Service-Prozessor (Port 0x68)
Der Z80 fordert Dienste durch Ausführen von OUT (0x68) an, was eine MSG_TZFS_SVCREQ-Nachricht an Core 0 einreiht; Core 0 führt TZFS_processServiceRequest aus. Die Dateisystem-Dienste sind READDIR / NEXTDIR (gecachte 16-Einträge-Verzeichnisblöcke), READFILE / NEXTREADFILE, LOADFILE (MZF-Header- und Bank-Objekt-bewusst), CHANGEDIR und CLOSE. Die CP/M-Dienste sind LOADBDOS (Warm-Boot-CCP + BDOS-Neuladung), ADDSDDRIVE, READSDDRIVE und WRITESDDRIVE sowie CPU-Frequenz-Dienste. Da der picoZ80 keinen direkten SD-Zugriff hat, werden CP/M-512-Byte-Sektoren über den ESP32 (ESP_readSector / ESP_writeSector) gegen ganze Image-Dateien gelesen und geschrieben; die Image-Pfade pro Laufwerk stammen aus den JSON-Einträgen param[].file der Schnittstelle, mit Fallback-Vorlage CPM/SDC16M/RAW/CPMDSK<nn>.RAW. Das TZFS-ROM ist roms/tzfs.bin auf der SD-Karte, assembliert aus TZFS/asm/tzfs.asm (sein CP/M-BIOS aus TZFS/asm/cbios.asm / cpm22.asm). Es wird in config_MZ-700_MZ-700.json mit "enable": 0 ausgeliefert und wird in der JSON oder über die Konfigurationsseite der Web-GUI aktiviert.

OpenZ80 — Experimentier-Persona (INCLUDE_OPEN_DRIVERS)
Das OpenZ80-Target (TARGET_MODEL_OPENINCLUDE_OPEN_DRIVERS) kompiliert eine bewusst schlichte „Vanilla"-Z80-Persona für Experimentierende, die den picoZ80 in ein selbst entworfenes Board oder eine Maschine ohne dedizierten Treiber einbauen. Im PHYSICAL-Modus wird der gesamte 64K-Speicher- und I/O-Raum an das reale Board durchgereicht (Interface-Karten überlagern ihre I/O-Ports); im VIRTUAL-Modus stellt es ein flaches 64K-RAM bereit, in das treiberseitige ROM-Images sequentiell ab 0x0000 geladen werden. Es enthält keine Maschinen-Hardware und verwendet die Sharp-Interface-Karten-Module unverändert wieder (sie halten keinen personaspezifischen Zustand), wobei nur die maschinenunabhängigen Karten unten bereitgestellt werden — von denen jede die Verschiebung des Basis-I/O-Ports unterstützt.
Treiber Typ-String Beschreibung
Open.c Open Vanilla / Experimentier-Z80-Persona — keine Maschinen-Hardware. Physische Durchreichung oder flaches 64K-Virtual-RAM mit treiberseitigem ROM-Laden ab 0x0000
MZ-1R12.c MZ1R12 MZ-1R12 32K RAM-Datei-Karte (Standardbasis 0xF8, verschiebbar)
MZ-1R18.c MZ1R18 MZ-1R18 64K RAM-Board (Standardbasis 0xEA, verschiebbar)
MZ-1R23.c MZ1R23 MZ-1R23 Kanji- / MZ-1R24 Dictionary-ROM-Board (Standardbasis 0xB8, verschiebbar)
MZ-1R37.c MZ1R37 MZ-1R37 640K EMM (Standardbasis 0xAC, verschiebbar)
PIO-3034.c PIO3034 PIO-3034 paralleles EMM / parallele I/O (Standardbasis 0x00, verschiebbar)
MZ8BIO3.c / MZ1E24.c MZ8BIO3 / MZ1E24 Duale RS-232C-Serienkarten (Z80 SIO, Standardbasis 0xB0, verschiebbar; Kanäle A/B auf USB CDC 2/3)
MZ-1E05.c MZ1E05 WD1773 Floppy-Interface (Standardbasis 0xD8, verschiebbar; benötigt ein FDC-Boot-ROM)
Celestite.c Celestite Celestite ESP32 LAN-Board (Standardbasis 0x60, verschiebbar)
Bauen Sie mit build_tzpuPico.sh open. Um den picoZ80 an eine neue Maschine anzupassen, nehmen Sie einen der Persona-Treiber unter src/drivers/{Sharp,Amstrad,Tatung,Other}/ als Basis und kombinieren ihn mit der passenden Monitor- / IPL- / CP/M-BIOS- / Floppy-Boot-ROM-Quelle in den asm/-Verzeichnissen der RFS- und TZFS-Projekte (z.B. RFS/asm/sa1510.asm, RFS/asm/cbios.asm, TZFS/asm/mz2000_ipl.asm, RFS/asm/mz80afi.asm). Siehe das Entwicklerhandbuch für die schrittweise Anleitung.

Virtuelles Geräteframework

Der FUNC-Blocktyp ermöglicht beliebige I/O-Emulation, indem bei jedem Buszugriff C-Handler-Funktionen aufgerufen werden. Jeder 512-Byte-Block des Speichers oder jeder I/O-Portbereich kann durch eine Funktion hinterlegt werden.

Handler-Funktionssignaturen

Speicher-FUNC-Handler sind in der memioPtr-Tabelle im PSRAM gespeichert. I/O-FUNC-Handler sind in der ioPtr-Tabelle gespeichert. Die Funktionssignaturen sind:
/* Memory read handler */
uint8_t mem_read_handler(uint16_t addr, void *ctx);

/* Memory write handler */
void mem_write_handler(uint16_t addr, uint8_t data, void *ctx);

/* I/O read handler */
uint8_t io_read_handler(uint8_t port, void *ctx);

/* I/O write handler */
void io_write_handler(uint8_t port, uint8_t data, void *ctx);
Handler-Funktionen werden direkt aus der Hauptschleife von Core 1 aufgerufen. Sie müssen abgeschlossen werden, bevor die Wait-States des aktuellen Buszyklus ablaufen — Handler müssen kurz gehalten werden und dürfen keine blockierenden Operationen ausführen (Datei-I/O, UART usw.). Wenn ein Handler eine längere Operation auslösen muss (z.B. einen Disksektor laden), sollte er eine Nachricht über die Inter-Core-Warteschlange an Core 0 senden und sofort mit einem Statusbyte zurückkehren, wobei die eigentliche I/O-Operation an Core 0 delegiert wird.

Einen neuen Treiber schreiben

Um Unterstützung für ein neues Peripheriegerät oder einen neuen Hostrechner hinzuzufügen:
  1. Erstellen Sie eine neue .c / .h-Datei im Verzeichnis src/drivers/.
  2. Implementieren Sie Lese- und Schreib-Handler-Funktionen entsprechend der oben genannten Signaturen.
  3. Registrieren Sie die Handler-Funktionszeiger in den memioPtr- oder ioPtr-Tabellen während der Treiberinitialisierung.
  4. Fügen Sie den Treiber zum CMakeLists.txt-Build-Target hinzu.
  5. Fügen Sie einen Typ-String-Eintrag hinzu, damit der JSON-Konfigurationsparser den Treiber namentlich instanziieren kann.
  6. Dokumentieren Sie die param-Schlüssel des Treibers in der Header-Datei Ihres Treibers.
Die Treiberinitialisierungsfunktion wird einmal beim Booten aufgerufen, nachdem config.json geparst wurde. Der Treiber erhält einen Zeiger auf seinen Interface-Konfigurationsblock und sollte zu diesem Zeitpunkt seinen internen Zustand einrichten und seine Handler registrieren.

ICE (Debug-Shell)

Der picoZ80 enthält eine eingebaute ICE (In-Circuit Emulator) Debug-Shell auf USB CDC Channel 1 (dem zweiten seriellen Port, der beim Anschluss des Boards über USB aufgelistet wird). Die Shell läuft auf Core 0 und kommuniziert mit der Core-1-Emulationsschleife über gemeinsame Flags in der Z80CPU-Kontextstruktur. Die Debug-Shell ist nur in der DBGSH-Firmware-Variante verfügbar, die mit dem INCLUDE_DBGSH-Define kompiliert wird.

USB-CDC-Serielle-Kanäle

Der picoZ80 listet beim Anschluss an einen Host mehrere USB-CDC-Serienports auf. CDC 0 und CDC 1 bedienen die physischen UARTs / ESP32-Bridge und die ICE-Debug-Shell (CDC 1, DBGSH-Varianten). CDC 2 und CDC 3 sind die beiden Kanäle (A und B) einer virtuellen RS-232C-Serienkarte — des MZ-8BIO3- oder MZ-1E24-Treibers — sofern eine in config.json konfiguriert ist. Diese beiden Ports haben keine physische UART dahinter: Sie sind Ringpuffer-Brücken zum emulierten Z80 SIO, sodass am Host auf CDC 2/3 geschriebene Daten auf Kanal A/B des vom Gast gesehenen Z80 SIO erscheinen und umgekehrt. Ist keine Serienkarte konfiguriert, werden CDC 2 und CDC 3 nicht aufgelistet.

Architektur

  • Ein-/Ausgabe: USB CDC Channel 1 bei 115200 Baud. Der Shell-Prompt ist dbg> . Befehlshistorie (16 Einträge) und Zeichenecho werden unterstützt.
  • Breakpoints: Bis zu 8 gleichzeitige Breakpoints, gespeichert in cpu->dbgBpAddr[]. Core 1 prüft das Breakpoint-Array vor jedem Opcode-Fetch; bei einem Treffer setzt er cpu->hold = true und signalisiert Core 0 über dbgBpHit.
  • Einzelschritt: Der step-Befehl setzt cpu->dbgStepCount. Core 1 dekrementiert diesen Zähler nach jedem Befehl und hält automatisch an, wenn er null erreicht. Vor/nach-Registerzustand und der disassemblierte Befehl werden für jeden Schritt angezeigt.
  • Ausführungstrace: Ein 512-Eintrags-Ringpuffer (cpu->dbgTrace[]) zeichnet PC, Opcode und Flags-Register für jeden ausgeführten Befehl auf, wenn Tracing aktiviert ist. Jeder 32-Bit-Eintrag packt [31:16]=PC, [15:8]=Opcode, [7:0]=F-Register.
  • Speicherzugriff: Physischer Speicherzugriff (dm p, wm p) führt echte Z80-Buszyklen über die PIO-State-Machines aus. Virtueller Zugriff (dm v, wm v) liest/schreibt PSRAM direkt. Automatischer Modus (wm ohne Qualifier) folgt der Speicherzuordnung. RP2350-Zugriff (dm r) liest den Adressraum des Host-Mikrocontrollers mit Bereichsvalidierung.
  • Hold/Release: Der hold-Befehl setzt cpu->hold = true. Core 1 bestätigt über cpu->holdAck, wodurch sichergestellt wird, dass die CPU im Ruhezustand ist, bevor die Shell auf den gemeinsamen Zustand zugreift.
  • Break: Der break-Befehl hält Core 1 an und meldet dann den aktuellen PC, den als Nächstes auszuführenden Befehl (disassembliert) und den vollständigen Registersatz — der schnellste Weg, um vor dem Schrittausführen oder Inspizieren zu sehen, wo sich ein laufendes Programm befindet. go/cont setzt fort.
  • Kurztasten & Abkürzungen: Um die Tippmenge beim Debuggen zu reduzieren, besitzen die häufigsten Befehle einzelne Buchstaben-Kurztasten — c=cont, s=step, g=go, b=break, r=regs, h=hold — die vor dem Präfix-Abgleich aufgelöst werden, sodass sie nie als mehrdeutig behandelt werden. Jeder andere Befehl kann als sein kürzestes eindeutiges Präfix eingegeben werden (z.B. ste=step, sta=status, dis=disassemble); ein mehrdeutiges Präfix listet die Kandidaten auf, und ein vollständig ausgeschriebener Name gewinnt immer.

Befehlsreferenz

Befehl Syntax Beschreibung
help help Alle Befehle auflisten
regs regs Alle Z80-Register, Flags und Zykluszähler ausgeben
dm dm <p|f|v|r> <addr> [len] Speicher ausgeben (physisch / Fetch / virtuell / RP2350)
search search [p|v] <start> <end> <hex..>|"text" Speicher nach einem Bytemuster oder ASCII-Textstring durchsuchen. p = physischer Bus, v = virtuelles PSRAM, ohne Angabe = gemappt. Treffer werden mit 8 Bytes Kontext angezeigt. CPU wird für physischen/gemappten Zugriff automatisch angehalten. Muster bis zu 32 Bytes
cmp cmp [f] <phys> <virt> <len> Physischen Busspeicher mit virtuellem PSRAM vergleichen
dis dis [p|v] [addr] [count] Z80-Code disassemblieren
asm asm [addr] Interaktiver Z80-Assembler
memmap memmap [block] Speicherbank-Zeigertabelle anzeigen
memptr memptr [addr] PSRAM memPtr-Tabelle anzeigen
iomap iomap [port] I/O-Port-Handler-Tabelle anzeigen
status status Systemstatus (CPU-Frequenz, PSRAM, Betriebszeit)
ver ver Firmware-Version und Partitionsinformationen
drivers drivers Aktive Treiber und Interfaces auflisten
hold hold CPU-Emulation anhalten
release release CPU-Emulation fortsetzen
break break Den laufenden Z80 anhalten und melden, wo er gestoppt hat — PC, den als Nächstes auszuführenden Befehl (Bytes + Disassemblierung) und einen vollständigen Register-Dump. Anders als hold (das stillschweigend pausiert) zeigt break den Zustand an, sodass Sie genau sehen können, wo die Ausführung steht (z.B. wenn sie in einer Schleife festhängt). Erneutes Ausführen von break zeigt den Zustand erneut an. Fortsetzen mit go/cont oder Einzelschritt mit step. Läuft nach ~3 s in ein Timeout, wenn der Z80 im Reset gehalten wird oder sein Takt gesperrt ist
go go Fortfahren (Hold aufheben, Breakpoints aktiv)
cont cont Alias für go. Ausführung fortsetzen (Hold aufheben, Breakpoints aktiv)
step step [n] n Befehle im Einzelschritt ausführen
bp bp <addr> Breakpoint setzen (max. 8)
bc bc <n|*> Breakpoint n oder alle löschen
bl bl Breakpoints auflisten
wm wm [p|v] <addr> <byte>... In Speicher schreiben (physisch/virtuell/automatisch)
fill fill [p|v] <addr> <len> [w|d] <val> Speicher mit konstantem Wert füllen
copy copy <pv|fp|vp> <src> <len> <dst> Speicher zwischen physisch und virtuell kopieren
memtest memtest <addr> <len> [pattern] Physischen Speicher testen (Schreiben+Lesen, Schreiben+Fetch, verschachtelt)
in in <port> Z80-I/O-Port lesen
out out <port> <byte> Z80-I/O-Port schreiben
trace trace <on|off|dump [n]|clear|rt|byte ...> Ausführungstrace-Steuerung; rt aktiviert Echtzeit-Trace-Ausgabe; byte aktiviert Byte-Level-Tracing
verify verify <on|off> Vollständige Opcode-Fetch-Verifizierung umschalten
fwait fwait <0-4> Zusätzliche M1 (Opcode-Fetch) Wait-States erzwingen; 0 = aus (Standard)
iowait iowait <0-8> Zusätzliche I/O-Zyklus-Wait-States erzwingen; 0 = aus (Standard)
corrupt corrupt [clear] Erkannte Fetch-Verfälschungen anzeigen oder löschen
fdctrace fdctrace <on|off|dump> FDC-I/O-Trace aktivieren/deaktivieren; dump zeigt die letzten 64 Operationen
qdtrace qdtrace <on|off|dump> Quick-Disk-I/O-Trace aktivieren/deaktivieren; dump zeigt die letzten 64 Operationen
piodbg piodbg [clear] RP2350-PIO-Hardware-Diagnose anzeigen (FDEBUG, FSTAT, FIFO, PCs, GPIO); clear setzt persistente Flags zurück
load load <p|v> <file> <addr> [len] [ofs] Eine Datei von der ESP32-SD-Karte in den Z80-Speicher laden. p = physischer Bus, v = virtuelles PSRAM Bank 0. file relativ zu /sdcard/. Wenn len weggelassen wird, wird die gesamte Datei geladen (bis zu 64KB); wenn angegeben, max. 1MB. Optionales ofs für Datei-Offset. Hält CPU automatisch für physische Schreibvorgänge an. Verwendet PSRAM Bank 63 als Zwischenspeicher
save save <p|pf|v> <file> <addr> <len> Z80-Speicher in eine Datei auf der ESP32-SD-Karte speichern. p = physischer Bus, pf = physischer Fetch (M1), v = virtuelles PSRAM Bank 0. file relativ zu /sdcard/. Max. 64KB. Hält CPU automatisch für physische Lesevorgänge an. Periodischer DRAM-Refresh während physischer Lesevorgänge
dir dir [path] Dateien auf der ESP32-SD-Karte auflisten. Optionaler Pfad ist relativ zu /sdcard/. Zeigt Dateinamen und Größen
echo echo [on|off] Terminal-Echo umschalten
reset reset Z80-Reset erzwingen
set set <reg|flags|memmap|memptr|iomap> <idx> <val> Z80-Register, Flags, Speicherzuordnung, memPtr oder I/O-Zuordnungseintrag zur Laufzeit ändern
hist hist [n] Befehlshistorie anzeigen (über ESP32 NVS sitzungsübergreifend gespeichert)
savehst savehst Befehlshistorie zwangsweise in ESP32 NVS speichern
ipl ipl Einen IPL-Reset (BST-Modus) durchführen, indem 8255 PPI Port C Bit 3 umgeschaltet wird. Setzt in den Boot-Modus zurück, ohne einen vollständigen Z80-Reset
mmutrace mmutrace Maschinenspezifische Trace-Informationen ausgeben (MMU-Zustand, I/O-Register-Snapshots). Ausgabe variiert je nach Persona über registrierten Trace-Handler
intcount intcount Interrupt-Bestätigungszähler und aktuellen Interrupt-Zustand anzeigen
psync psync [start end] Physischen Speicher mit PSRAM synchronisieren. Optionaler Adressbereich; Standard ist der gesamte Adressraum
dskimage dskimage read <filename> [cylinders] [heads] / dskimage write <filename> Eine physische Floppy-Disk in eine DSK-Datei auf der SD-Karte abbilden (read) oder eine DSK-Datei von der SD-Karte auf eine physische Floppy schreiben (write). dskimage <filename> ist standardmäßig Lesen (abwärtskompatibel). Geometrie wird automatisch erkannt, wenn nicht angegeben
busdiag busdiag Busdiagnose anzeigen (PIO-Zustand, Signalpegel, Buskonflikt)
fdcimage fdcimage FDC-Imaging-Status und Fortschritt anzeigen
fdcdiag fdcdiag FDC-Diagnoseinformationen anzeigen (Controller-Zustand, Registerdump)
gadiag gadiag Gate-Array-Diagnoseinformationen anzeigen (Befehlszustand, Interrupt-Routing)
Kurztasten und Abkürzungen. Befehle müssen nicht vollständig eingegeben werden. Die sechs am häufigsten verwendeten Befehle besitzen einzelne Buchstaben-Kurztastenc (cont), s (step), g (go), b (break), r (regs) und h (hold) — und jeder andere Befehl kann als sein kürzestes eindeutiges Präfix eingegeben werden (zum Beispiel ste für step, sta für status, dis für disassemble). Ein vollständig ausgeschriebener Befehlsname hat immer Vorrang, und ein mehrdeutiges Präfix gibt die Liste der passenden Befehle aus.

ESP32-Koprozessor

Das ESP32-S3-PICO-1-Modul fungiert als Koprozessor und übernimmt alle Netzwerk- und Speicherfunktionen. Es kommuniziert mit dem RP2350 über zwei Schnittstellen:
  • FSPI (50MHz, 4-Draht-SPI) — Binäres IPC-Protokoll v1.1 — Hochgeschwindigkeits-Massendatentransfer (ROM-Images, Disksektor-Lese-/Schreibvorgänge, Config-Datei-Download). Das Protokoll verwendet einen festen 64-Byte-Binärframe-Header mit CRC32-Integritätsprüfung (ersetzt die frühere XOR-Prüfsumme). DMA-Kanäle werden bei der Initialisierung vorab zugewiesen und nie freigegeben, wodurch der Claim/Unclaim-Overhead pro Transfer und Race Conditions eliminiert werden. Burst-Sektortransfers ermöglichen bis zu 16 × 512-Byte-Sektoren (8KB) in einer einzigen SPI-Transaktion, was die Ladezeiten für Floppy- und QuickDisk-Images erheblich verbessert. Der RX-DMA-Kanal wird auf HIGH PRIORITY erhöht, um FIFO-Überläufe zu verhindern, die durch Core-1-PSRAM-QMI-Buskonflikte verursacht werden.
  • UART (460,8 kBaud) — Befehls-/Antwortprotokoll für Steuernachrichten, Statusabfragen und kurze Datenaustausche.

Netzwerkmodi

Die ESP32-Firmware unterstützt drei Netzwerkmodi, die zur Build-Zeit über vorgefertigte sdkconfig-Dateien ausgewählt werden:
Modus Config-Datei WiFi USB NCM Konsole FCC/RED erforderlich
Nur WiFi sdkconfig.mode_wifi_only Ja Nein USB Serial/JTAG Ja
WiFi + NCM sdkconfig.mode_wifi_and_ncm Ja Ja TinyUSB CDC-ACM Ja
Nur NCM sdkconfig.mode_ncm_only Nein Ja TinyUSB CDC-ACM Nein
USB NCM (Network Control Model) stellt einen CDC-NCM-Ethernet-Adapter am ESP32-S3 USB OTG-Port (GPIO 19/20) bereit. Ein zusammengesetztes USB-Gerät stellt sowohl einen CDC-ACM-seriellen Port (für Debug-Logging) als auch die NCM-Netzwerkschnittstelle zur Verfügung. Der integrierte DHCP-Server weist dem Host eine IP-Adresse aus dem 192.168.7.0/24-Subnetz zu, wobei der picoZ80 unter 192.168.7.1 erreichbar ist. Die Lease-Zeit beträgt 120 Minuten.
Im WiFi+NCM-Modus bindet der HTTP-Server an INADDR_ANY:80 und bedient beide Schnittstellen gleichzeitig. WiFi verbindet sich asynchron, sodass die USB-NCM-Schnittstelle sofort beim Einschalten verfügbar ist.
Wenn nur NCM aktiviert ist, wird das WiFi-Funkmodul vollständig deaktiviert, die WiFi-Manager-Seite wird aus der Weboberfläche entfernt, und der Dashboard-Statusbereich-Titel ändert sich von "WiFi Configuration" zu "Network Configuration" (zeigt USB-NCM-Status anstelle von SSID/WiFi-Details). Das Antennenanpassungsnetzwerk des ESP32-S3 muss auf der Platine nicht bestückt werden.

SD-Karten-Schnittstelle

Der ESP32 verwaltet die SD-Karte über seine SPI-Schnittstelle. Die SD-Karte wird als FAT32 eingehängt und der gesamte Dateizugriff vom RP2350 wird durch den ESP32 vermittelt — der RP2350 sendet Datei-I/O-Befehle über die FSPI/UART-Verbindung und der ESP32 führt die eigentlichen FAT32-Lese-/Schreiboperationen aus.
Die SD-Karte ist auch direkt für den ESP32-Webserver zugänglich, der Dateien aus dem webfs/-Verzeichnis bereitstellt und es dem Dateimanager ermöglicht, den Karteninhalt über HTTP zu durchsuchen und zu ändern.

Webserver

Der ESP32 betreibt einen HTTP-Server auf Port 80 (kein TLS — nur für lokale Netzwerknutzung). Alle Web-Assets (HTML, CSS, JavaScript) werden aus dem webfs/-Verzeichnis auf der SD-Karte bereitgestellt, sodass die Weboberfläche aktualisiert werden kann, ohne die ESP32-Firmware neu zu flashen. Der Webserver behandelt:
  • Bereitstellung statischer Web-Assets aus dem SD-Karten-webfs/-Verzeichnis.
  • REST-API-Endpunkte für JSON-Daten (Systemstatus, Config-Lesen/-Schreiben, Dateioperationen).
  • OTA-Firmware-Upload-Endpunkte für sowohl den RP2350 als auch den ESP32.
  • WebSocket-Verbindung für Dashboard-Statusaktualisierungen in Echtzeit.

RP2350 ↔ ESP32 Kommandoprotokoll

Der RP2350 (Core 0) kommuniziert mit dem ESP32 über ein einfaches Befehls-/Antwortprotokoll über die UART-Verbindung. Befehle sind Einzelbyte-Opcodes mit optionalen Payload-Bytes. Der ESP32 bestätigt jeden Befehl mit einem Statusbyte, gefolgt von etwaigen Antwortdaten.
Häufige Befehlskategorien:
  • Datei-I/O — Öffnen, Lesen, Schreiben, Schließen, Verzeichnisauflistung, Dateistatistik.
  • Config — config.json-Inhalt anfordern, aktualisierte Config schreiben, Neuladen anfordern.
  • Disk — Disk-Image einhängen/aushängen, Sektor lesen/schreiben (weitergeleitet von WD1773- und QDDrive-Emulation). Die aktuell eingehängten Floppy- und QuickDisk-Image-Dateinamen werden vom ESP32 verfolgt und im Aktionsmenü der Weboberfläche angezeigt.
  • System — Versionsabfrage, Neustartanforderung, NVS-Lesen/-Schreiben.
Die FSPI-Schnittstelle wird für Massendatentransfers verwendet, bei denen die Payload zu groß für die UART ist (ROM-Image-Uploads, Disksektordaten), während die UART alle Steuerbefehle behandelt.

Netzwerk-IPC-Befehle

Die Celestite Phase 2-Netzwerkimplementierung fügt fünf Inter-Core-IPC-Befehle hinzu, die der RP2350 verwendet, um Netzwerkoperationen vom ESP32 anzufordern. Diese Befehle werden über die FSPI/UART-Verbindung weitergeleitet und der ESP32 führt die entsprechenden BSD-Socket-Operationen aus. Nicht-blockierende Verbindungsaufbauten verwenden select() mit einem Timeout, und pro Socket verfolgen Pending-Flags laufende Operationen.
Befehl Opcode Beschreibung
IPCF_CMD_NET_CFG 0x10 ESP32-Netzwerkkonfiguration abrufen — gibt IP-Adresse, Gateway, Subnetzmaske und MAC-Adresse zurück
IPCF_CMD_NET_SOCK 0x11 Socket-Lebenszyklusoperation — Socket öffnen, verbinden, lauschen, schließen oder trennen
IPCF_CMD_NET_SEND 0x12 Daten an einen offenen Socket senden
IPCF_CMD_NET_RECV 0x13 Daten von einem offenen Socket empfangen
IPCF_CMD_NET_PING 0x14 ICMP Echo Request (Ping)
Die über diese IPC-Schicht unterstützten W5100-Socket-Befehle sind: OPEN, CONNECT, LISTEN, SEND, RECV, CLOSE und DISCON. Der ESP32 übersetzt diese in Standard-BSD-Socket-API-Aufrufe (socket(), connect(), listen(), send(), recv(), close(), shutdown()), wodurch das Celestite-Board mit Netzwerkdiensten wie dem netfs.py-Dateiserver kommunizieren kann.

Watchdog und Boot-Diagnose

Die RP2350-Firmware verwendet einen Hardware-Watchdog-Timer, um Boot-Hänger und Hauptschleifen-Stillstände zu erkennen und zu beheben. Der Watchdog wird früh in der Boot-Sequenz mit einem 30-Sekunden-Timeout aktiviert und bei jedem wichtigen Meilenstein zurückgesetzt (watchdog_update()). Wenn ein Boot-Schritt oder eine Hauptschleifen-Iteration länger als das Timeout dauert, setzt der Watchdog den RP2350 automatisch zurück.

Boot-Fortschrittsverfolgung

Der Boot-Fortschritt wird über die Watchdog-Scratch-Register des RP2350 verfolgt, die Watchdog-Resets überleben (aber keine Power-On-Resets). Dies ermöglicht der Firmware, nach einem Watchdog-Reset genau festzustellen, welcher Boot-Schritt vor dem Hänger erreicht wurde.
Scratch-Register Name Inhalt
scratch[0–3] Boot-Historie Letzte vier Reset-Versuche — jeder Eintrag kodiert (attempt_count << 24) | (stage << 16) | (resetCause & 0xFFFF). Einträge verschieben sich bei jedem Watchdog-Reset: [0]←[1]←[2]←[3]←aktuell.
scratch[4] SPI-Diagnose Gepackte Diagnosezähler für die FSPI-Verbindung: Breadcrumbs, Nachrichtentyp, T1/T3-Timeout-Zähler, Fehlframe-Anzahl und OK-Anzahl.
scratch[5] Magic-Marker Auf 0xB00710BE gesetzt, um anzuzeigen, dass die Scratch-Register gültige Boot-Fortschrittsdaten enthalten.
scratch[6] Aktueller Schritt Der jüngste Boot-Schritt-Code (siehe Tabelle unten).
scratch[7] Reset-Ursache Der Reset-Ursachen-Code vom Hardware-Reset-Controller.
Boot-Schritt-Codes schreiten von 0x01 (Start) bis 0x10 (Hauptschleife betreten) voran. Unterschritte innerhalb der Hauptschleife (0x11–0x17) und der Inter-Core-Befehlsverarbeitung (0x20–0x27) bieten feinkörnige Verfolgung:
Code Schritt Beschreibung
0x01 BOOTP_START Einsprungpunkt erreicht
0x02 BOOTP_CLK_SET Systemtakt konfiguriert
0x03 BOOTP_PSRAM_INIT PSRAM-Initialisierung gestartet
0x04 BOOTP_PSRAM_OK PSRAM erfolgreich initialisiert
0x05 BOOTP_STDIO_INIT USB-Stdio initialisiert
0x06 BOOTP_PIO_INIT PIO-State-Machines geladen
0x07 BOOTP_Z80_INIT Z80-CPU-Kontext initialisiert
0x08 BOOTP_USB_INIT USB-Bridge initialisiert
0x0A BOOTP_ESP_HS_SYNC ESP32-SPI-Handshake-Synchronisation
0x0B BOOTP_CORE1_LAUNCH Core 1 gestartet
0x0D BOOTP_FSPI_INIT FSPI-binäres IPC initialisiert
0x0E BOOTP_ESP_INIT ESP32-Kommunikation bereit
0x10 BOOTP_MAIN_LOOP Hauptschleife betreten
0x11–0x17 Hauptschleifen-Unterschritte USB-Poll, Inter-Core, SPI NOP/CMD, Tasks
0x20–0x27 Inter-Core-Befehle Floppy-Laden, QD-Laden, RAMFILE-Laden, Datei-I/O

PSRAM Persistentes Log (plogf)

Die letzten 4KB des 8MB PSRAM (Adresse 0x117FF000) sind für ein persistentes Debug-Log reserviert, das Watchdog-Resets überlebt. Das plogf()-Makro schreibt printf-artige Nachrichten in diesen Puffer während des Boots, bevor USB für normale debugf()-Ausgabe verfügbar ist. Beim nächsten erfolgreichen Boot gibt die dump_plog()-Funktion alle erfassten Nachrichten auf der Debug-Konsole aus und löscht dann den Puffer. Das Log verwendet eine einfache Struktur: einen 4-Byte-Magic-Marker (0x504C4F47 = "PLOG"), einen 4-Byte-Längenzähler und einen 3840-Byte-zirkulären Textpuffer.

Fehlerdiagnose

Die Firmware installiert Cortex-M33-Fehlerbehandlungsroutinen für Hard Faults, Memory-Management-Faults, Bus-Faults und Usage-Faults. Wenn ein Fehler auftritt, speichert der Handler einen vollständigen Diagnose-Snapshot in den letzten 256 Bytes des PSRAM (Adresse 0x117FFF00) mit einem Magic-Marker (0xFA017000), dem Fehlertyp, allen relevanten Registern (PC, LR, SP, R0–R3, R12, PSR), dem Configurable Fault Status Register (CFSR), Hard Fault Status Register (HFSR), Bus Fault Address Register (BFAR), Memory Management Fault Address Register (MMFAR) und der Core-ID. Der Handler geht dann in eine Endlosschleife, sodass der Watchdog einen Reset auslösen kann. Beim nächsten Boot prüft die Firmware auf einen gültigen Fehlerdiagnose-Eintrag und gibt die erfassten Informationen über debugf() aus, wodurch eine Post-Mortem-Analyse ohne Live-Debugger-Sitzung ermöglicht wird.

Flash-Konfiguration löschen

Der RP2350-OTA-Update-Mechanismus unterstützt zwei zusätzliche Operationen über den Firmware-Upload hinaus:
  • App Config löschen (FW_CFGCLEAR_ID = 0xB1D7E5FA) — löscht die App-Config-Partition (ROM-Images und minifiziertes JSON), die dem Ziel-Firmware-Slot zugeordnet ist. Dies zwingt die Firmware, config.json beim nächsten Boot erneut von der SD-Karte zu lesen, was notwendig ist, wenn sich das Konfigurationsschema zwischen Firmware-Versionen geändert hat.
  • Flash-Header löschen (FW_HDRCLEAR_ID = 0xC2E8F6AB) — setzt den Flash-Partitions-Header auf Werkseinstellungen zurück. Die Bootloader-Konfiguration (Partition 0) bleibt erhalten, aber alle Applikationspartitions-Metadaten werden von Grund auf neu aufgebaut. Verwenden Sie dies, wenn die Partitionstabelle beschädigt ist oder beim Downgrade auf eine frühere Firmware-Version, die ein anderes Partitions-Layout erwartet.
Beide Operationen werden über die Kontrollkästchen auf der RP2350-OTA-Webseite ausgelöst und vom Bootloader während des Firmware-Update-Prozesses ausgeführt.

SWD-Debugging — RP2350

Der RP2350 unterstützt vollständiges Debugging auf Quelltextebene über ARM Serial Wire Debug (SWD). Verbinden Sie eine CMSIS-DAP-kompatible Sonde (Raspberry Pi Debug Probe, Black Magic Probe oder ähnlich) mit Pin 1 (SWCLK), Pin 2 (SWDIO) und Pin 5 (GND) des Debug-Headers.

OpenOCD-Einrichtung

Der picoZ80 erfordert eine kleine Änderung am Standard-OpenOCD-RP2350-Target-Skript, um SMP-Debugging mit separaten GDB-Ports pro Core zu ermöglichen:
sudo cp /usr/local/share/openocd/scripts/target/rp2350.cfg \
        /usr/local/share/openocd/scripts/target/rp2350_tzpu.cfg
Bearbeiten Sie rp2350_tzpu.cfg — suchen Sie die target smp-Zeile innerhalb des if {[string compare $_USE_CORE SMP] == 0}-Blocks und entfernen Sie das führende #:
# Before:
    #target smp $_TARGETNAME_0 $_TARGETNAME_1

# After:
    target smp $_TARGETNAME_0 $_TARGETNAME_1
Diese einzige Änderung bewirkt, dass OpenOCD Core 0 auf GDB-Port 3333 und Core 1 auf GDB-Port 3334 registriert, was unabhängige GDB-Sitzungen pro Core ermöglicht. Starten Sie OpenOCD vor dem Start von GDB:
openocd -f interface/cmsis-dap.cfg -f target/rp2350_tzpu.cfg -c "adapter speed 5000"

GDB-Konfiguration

Fügen Sie Folgendes zu ~/.gdbinit hinzu (mit absoluten Pfaden, die Ihrem Projektstandort entsprechen), um das automatische Laden verzeichnisspezifischer .gdbinit-Dateien zu ermöglichen:
set history save on
set history filename ~/.gdb_history
set history size 65536
add-auto-load-safe-path /path/to/project/build/bin/model/BaseZ80/.gdbinit
add-auto-load-safe-path /path/to/project/build/bin/model/Bootloader/.gdbinit
Bootloader debuggen
# Terminal 1 — Core 0 (port 3333)
cd build/bin/model/Bootloader
cp ../../../../.gdbinit.bootloader.3333 .gdbinit
gdb-multiarch Bootloader.elf

# Terminal 2 — Core 1 (port 3334)
cd build/bin/model/Bootloader
cp ../../../../.gdbinit.bootloader.3334 .gdbinit
gdb-multiarch Bootloader.elf
Haupt-Firmware debuggen
# Terminal 1 — Core 0 (port 3333)
cd build/bin/model/BaseZ80
cp ../../../../.gdbinit.3333 .gdbinit
gdb-multiarch BaseZ80_0x10020000.elf

# Terminal 2 — Core 1 (port 3334)
cd build/bin/model/BaseZ80
cp ../../../../.gdbinit.3334 .gdbinit
gdb-multiarch BaseZ80_0x10020000.elf

# Memory dump (from GDB prompt) — hex + ASCII:
(gdb) xac 0x20000000 64
Der xac <address> <count>-GDB-Befehl ist in den .gdbinit.3333 / .gdbinit.3334-Dateien definiert. Er gibt Speicher als kombinierte Hex- und ASCII-Ausgabe aus und ist nützlich zur Untersuchung von PSRAM-Bankinhalten und speicherabgebildeten Gerätezuständen.

ESP32 USB-Debugging

Der ESP32-S3-Koprozessor verfügt über eine eingebaute USB-JTAG-Schnittstelle — keine externe Debug-Sonde erforderlich. Verbinden Sie ein USB-Kabel vom Host-PC direkt mit dem ESP32-USB-Port auf dem picoZ80-Board.
# Start OpenOCD for ESP32-S3
openocd -f board/esp32s3-builtin.cfg

# In a second terminal — launch Xtensa GDB
xtensa-esp32s3-elf-gdb esp32/build/main.elf
(gdb) target extended-remote :3333
Stellen Sie sicher, dass das ELF aus derselben Quellrevision gebaut wurde wie die auf dem Gerät laufende Firmware, damit Symbole und Adressen korrekt übereinstimmen.

Build-System

Die picoZ80-Firmware verwendet CMake mit dem Raspberry Pi Pico SDK 2.x. Das Build-System erzeugt die Bootloader- und die Applikations-Firmware. Die Applikation wird in vier Varianten gebaut — zwei Partitionen (Partition 1 bei 0x10020000, Partition 2 bei 0x10520000), jeweils in Standard- und DBGSH-Konfiguration. Die DBGSH-Varianten fügen INCLUDE_DBGSH zu den Kompilier-Flags hinzu und aktivieren damit die vollständige Debug-Shell auf USB CDC Channel 1. Die ESP32-Firmware wird separat mit ESP-IDF v5.4 gebaut, verwaltet über Docker.
Der schnellste Weg zu einer funktionierenden Umgebung ist das automatisierte setup_picoZ80-Skript (macOS/Linux und Windows), das das SDK und alle Abhängigkeiten installiert und einsatzbereite Build-Skripte erstellt — siehe die README und das Entwicklerhandbuch. Die nachfolgenden CMake-Targets, -Flags und -Befehle dokumentieren den zugrunde liegenden Build, den das Skript automatisiert, für alle, die ihn lieber manuell konfigurieren möchten.

CMake Build-Targets

Target Ausgabe Flash-Adresse Anmerkungen
Bootloader Bootloader.elf, Bootloader.uf2 0x10000000  
BaseZ80_0x10020000 BaseZ80_0x10020000.elf, .bin 0x10020000 (Slot 1) Standard (keine Debug-Shell)
BaseZ80_0x10520000 BaseZ80_0x10520000.elf, .bin 0x10520000 (Slot 2) Standard (keine Debug-Shell)
BaseZ80_DBGSH_0x10020000 BaseZ80_DBGSH_0x10020000.elf, .bin 0x10020000 (Slot 1) DBGSH — enthält ICE-Debug-Shell
BaseZ80_DBGSH_0x10520000 BaseZ80_DBGSH_0x10520000.elf, .bin 0x10520000 (Slot 2) DBGSH — enthält ICE-Debug-Shell
Die Modelle SharpZ80, AmstradZ80, TatungZ80 und OpenZ80 folgen demselben Slot- / DBGSH-Muster, z.B. OpenZ80_0x10020000, OpenZ80_0x10020000_DBGSH, OpenZ80_0x10520000 und OpenZ80_0x10520000_DBGSH (jedes erzeugt .elf, .bin, .hex und .map; die Applikations-Slots verwenden reines .bin, nicht UF2). Bauen Sie ein einzelnes Modell mit dem build_tzpuPico.sh-Filter, z.B. build_tzpuPico.sh open.

Wichtige CMake Build-Flags

Flag Wirkung
INCLUDE_SHARP_DRIVERS Kompiliert alle Sharp MZ-Peripherietreiber ein (MZ700, MZ80K, MZ800, MZ80A, MZ80B, MZ2000, MZ2200, MZ2500, MZ1500, WD1773, T3444M, QDDrive, RFS, TZFS, MZ-1E05, MZ80AFI, MZ80FIO, MZ8BFI, MZ8BIO3, MZ1E24, Z80SIO, MZ-1E14, MZ-1E19, MZ-1R12, MZ-1R18, MZ-1R23, MZ-1R37, PIO-3034, Celestite, MZ-1E30).
INCLUDE_AMSTRAD_DRIVERS Kompiliert Amstrad PCW-Peripherietreiber ein (PCW9512, uPD765).
INCLUDE_TATUNG_DRIVERS Kompiliert Tatung Einstein-Peripherietreiber ein (EinsteinTC01, EinsteinFDC, WD1770).
INCLUDE_OPEN_DRIVERS Kompiliert die OpenZ80-Experimentier-Persona (Open.c) und die maschinenunabhängigen Interface-Karten ein (MZ-1R12/1R18/1R23/1R37, PIO-3034, MZ8BIO3, MZ1E24, Z80SIO, MZ-1E05, WD1773, Celestite).
TARGET_MODEL_TATUNG Setzt Tatung Einstein als exklusives Zielmodell (wird im TatungZ80-Build verwendet).
TARGET_MODEL_OPEN Setzt die OpenZ80-Experimentier-Persona als exklusives Zielmodell (wird im OpenZ80-Build verwendet; aktiviert INCLUDE_OPEN_DRIVERS).
INCLUDE_DBGSH Kompiliert die ICE-Debug-Shell auf USB CDC Channel 1 ein. Nur in DBGSH-Build-Varianten vorhanden.
CMAKE_BUILD_TYPE=Debug Aktiviert Debug-Symbole und deaktiviert Optimierung. Erforderlich für Quelltext-Debugging mit GDB.
CMAKE_BUILD_TYPE=Release Volle Optimierung (-O3). Wird für Produktions-Firmware verwendet.

Build-Befehle

# First time: clone and build the SDK
./get_and_build_sdk.sh

# Standard release build (RP2350 only)
./build_tzpuPico.sh

# Debug build
./build_tzpuPico.sh DEBUG

# Full build: RP2350 + ESP32 (ESP32 built via Docker)
./build_tzpuPico.sh ALL

# ESP32 only, using the Docker idf54 alias
cd projects/tzpuPico/esp32
idf54 build
Das build_tzpuPico.sh-Skript erhöht automatisch die Versionsnummer bei einem erfolgreichen Build und kopiert versionierte Ausgabedateien nach fw/uf2/ (Bootloader-UF2) und fw/bin/ (Applikations-Binary für OTA). Das Bootloader-UF2 wird nur für das initiale USB-Mass-Storage-Flashing verwendet. Applikations-Slot-Binaries verwenden das reine Binärformat (nicht UF2), da sie sich an nicht standardmäßigen Flash-Adressen befinden.

ESP32-Netzwerkmodus-Auswahl

Um den Netzwerkmodus zu wechseln, kopieren Sie die entsprechende vorgefertigte Konfigurationsdatei nach sdkconfig, bevor Sie bauen:
cd esp32/
cp sdkconfig.mode_ncm_only sdkconfig    # NCM only (FCC/RED safe)
# or: cp sdkconfig.mode_wifi_only sdkconfig
# or: cp sdkconfig.mode_wifi_and_ncm sdkconfig
idf.py build
idf.py flash

Referenzseiten

Ressource Link
picoZ80-Projektseite /de/picoz80/
picoZ80 Benutzerhandbuch /de/picoz80-usermanual/
pico6502-Projektseite /de/pico6502/
RP2350 Datenblatt datasheets.raspberrypi.com
RP2350 PIO-Referenz datasheets.raspberrypi.com — Appendix B
Pico SDK-Dokumentation raspberrypi.github.io/pico-sdk-doxygen
ESP32-S3 Technische Referenz docs.espressif.com
ESP-IDF Programmierhandbuch docs.espressif.com/esp-idf
Zilog Z80 CPU Benutzerhandbuch zilog.com
OpenOCD-Dokumentation openocd.org
X (Twitter) Projektvorschau engineerswork1

Hinweis zur Funkregulierung

Dieses Gerät enthält ein ESP32-S3-PICO-1 Funkmodul, das im 2,4 GHz ISM-Band senden kann, wodurch es bei installierter WiFi-Firmware weltweit als absichtlicher Strahler gemäß den Hochfrequenzvorschriften gilt (einschließlich FCC Part 15 Subpart C in den Vereinigten Staaten und der Funkanlagenrichtlinie 2014/53/EU in der Europäischen Union).
Konfiguration im Auslieferungszustand
Im Auslieferungszustand ist das picoZ80-Board mit der NCM-only-Firmware (sdkconfig.mode_ncm_only) geflasht. In dieser Konfiguration ist das WiFi-Funkmodul vollständig deaktiviert und die ESP32-S3-Antennenanpassungsnetzwerk-Komponenten sind auf der Platine nicht bestückt. Da keine HF-Übertragung stattfindet, ist das Gerät kein absichtlicher Strahler und erfordert keine FCC-, CE/RED- oder gleichwertige Zertifizierung. Es darf ohne behördliche Genehmigung verkauft, vertrieben oder verschenkt werden.
WiFi hinzufügen
Endbenutzer können das Antennenanpassungsnetzwerk bestücken und WiFi-fähige Firmware (sdkconfig.mode_wifi_only oder sdkconfig.mode_wifi_and_ncm) für persönliche, experimentelle oder Bildungszwecke flashen. Sobald WiFi aktiviert ist, wird das Gerät ein absichtlicher Strahler und die folgenden Regeln gelten:
Obwohl das ESP32-S3-PICO-1-Modul selbst über bereits bestehende Regulierungszertifizierungen verfügt (FCC, CE und andere), erstrecken sich diese Modul-Zertifizierungen nicht automatisch auf ein Endprodukt, das das Modul enthält. Die vorzertifizierte Modul-Ausnahme erlaubt es einzelnen Hobbyisten, eine begrenzte Anzahl von Geräten für persönliche, experimentelle oder Bildungszwecke zu bauen, ohne eine separate Gerätegenehmigung einzuholen.
Wichtige Einschränkungen
  • Geräte mit WiFi-Firmware dürfen nicht verkauft, zum Verkauf angeboten, verschenkt oder anderweitig an Dritte verteilt werden, es sei denn, das Endprodukt wurde unabhängig getestet und hat seine eigene Gerätegenehmigung (z.B. FCC ID, CE-Kennzeichnung mit einer Bewertung durch eine benannte Stelle) in der jeweiligen Rechtsordnung erhalten.
  • Der Bau dieses Projekts mit aktiviertem WiFi für den persönlichen Gebrauch in begrenzten Stückzahlen ist in der Regel gemäß den Bestimmungen für Hobbyisten und experimentelle Nutzung erlaubt (z.B. FCC § 15.23), vorausgesetzt, das Gerät verursacht keine schädlichen Störungen.
  • Der kommerzielle Verkauf mit aktiviertem WiFi erfordert eine vollständige FCC/RED (oder gleichwertige) Produktzertifizierung.
  • Die regulatorischen Anforderungen variieren je nach Land. Erbauer außerhalb der Vereinigten Staaten sollten ihre nationale Hochfrequenzbehörde bezüglich der geltenden Regeln konsultieren.
Verantwortung des Erbauers
Es liegt in der alleinigen Verantwortung des Erbauers sicherzustellen, dass jedes aus diesen Entwürfen gebaute Gerät allen geltenden Hochfrequenzvorschriften in seiner Rechtsordnung entspricht. Der Autor stellt diese Entwürfe für persönliche, Bildungs- und Hobbynutzung zur Verfügung und gibt keine Zusicherung, dass ein aus ihnen gebautes Gerät die regulatorischen Anforderungen für den kommerziellen Vertrieb erfüllt.