picoZ80 User Manual

picoZ80 User Manual

The picoZ80 is a drop-in replacement for a Z80 CPU in any DIP-40 socketed computer. It plugs directly into the Z80 socket of your vintage computer and — without any modifications to the host board — provides expanded memory, virtual peripherals, floppy and QuickDisk emulation, filing systems, and a browser-based management interface over WiFi.
This manual covers the physical board, initial setup, connecting to a host machine, and using the web interface. For architecture details, configuration reference, and development information see the picoZ80 Technical Guide.

Board Overview

The picoZ80 PCB is designed to fit within the footprint of a standard 40-pin DIP package. All 40 DIP pins emerge from the underside of the board, exactly matching the pin-out of a real Z80, so it drops straight into any Z80 socket without bending or adapting.
The top surface of the board carries all active components. The board is narrower than most Z80 computers' internal clearances, and the height above the socket is within the envelope of a standard IC with a DIP-40 socket fitted underneath.

Connectors and Indicators

The picoZ80 board exposes the following connectors and indicators:
Item Description
6-pin Debug Header Located on the board edge. Provides SWD (Serial Wire Debug) access to the RP2350, BOOTSEL and Reset lines for initial flashing, and Reset for the ESP32. See Debug Header Pinout below.
USB Port (Mini-B) USB connectivity for firmware flashing via USB mass-storage (initial flash) and for USB-UART bridging to the ESP32. On board revisions v2.3a and later a second dedicated USB port for the ESP32 may also be present.
SD Card Slot Full-size SD card slot managed by the ESP32. Holds config.json, ROM images, disk images, and filing system trees.

Debug Header Pinout

The 6-pin header is the primary hardware interface for initial programming and source-level debugging. The pins are numbered from Pin 1 at the end closest to the board edge marker.
Pin Signal Function
1 SWCLK Serial Wire Debug clock (ARM SWD)
2 SWDIO Serial Wire Debug data (ARM SWD)
3 Reset RP2350 Active-low reset for the RP2350. Pull low to reset the RP2350.
4 Reset ESP32 Active-low reset for the ESP32. Pull low to hold ESP32 in reset.
5 GND Ground reference
6 BOOTSEL RP2350 boot mode select. Hold low during power-on/reset to enter USB mass-storage mode.

NB. Pins 1, 2, and 5 are used for SWD debugging (SWCLK, SWDIO, GND). Pins 3 and 6 are used only for initial flashing. Pin 4 is used when you need to prevent the ESP32 from starting during RP2350 boot (e.g. during initial ESP32 firmware programming).


Getting Started

Follow these steps in order the first time you set up a picoZ80 board:
  1. Flash the RP2350 bootloader via USB mass-storage mode (BOOTSEL).
  2. Flash the ESP32 firmware via esptool.
  3. Prepare an SD card with config.json and any required ROM/disk images.
  4. Install the picoZ80 in the Z80 socket of your host computer.
  5. Power on, connect to the picoZ80 web interface, and configure as required.

Step 1 — Flash the RP2350 Bootloader

The RP2350 does not have physical BOOTSEL or Reset buttons. Both signals are available on the 6-pin debug header. To enter the USB mass-storage bootloader:
  1. Do not insert the picoZ80 into a host computer yet — connect it to a PC by USB cable only.
  2. Using a jumper or probe, hold Pin 6 (BOOTSEL) low.
  3. Apply power to the USB port — the RP2350 starts booting.
  4. Release BOOTSEL promptly after the board enumerates as a USB mass-storage device. (If you hold BOOTSEL too long after reset the RP2350 cannot access the Flash chip.)
  5. The PC will mount a drive labelled RPI-RP2 (or similar).
  6. Copy Bootloader_<version>.uf2 to the mounted drive. The RP2350 self-flashes and reboots automatically.
After flashing the bootloader, all subsequent RP2350 firmware updates can be performed wirelessly via the web OTA page — you do not need to touch the debug header again for normal firmware updates.

Step 2 — Flash the ESP32 Firmware

The ESP32 firmware is flashed using esptool. On board revisions v2.3a and later, the ESP32 has its own USB port and appears independently on the host PC. On earlier revisions (v2.0 – v2.2), the ESP32 is accessed through the RP2350 acting as a USB-UART bridge.
Set up the Python environment (once only):
python3 -m venv ./venv/
source ./venv/bin/activate
cd $HOME/esptool
Then flash all four ESP32 firmware components. Adjust PORT to the device node assigned by your OS:
PORT=/dev/ttyUSB0       # Linux — adjust as required
# PORT=/dev/tty.usbmodem141403  # macOS

BINPATH=/path/to/esp32/build

python3 ./esptool.py \
  -p ${PORT} -b 115200 \
  --before default_reset --after hard_reset \
  --chip esp32s3 \
  write_flash \
  --flash_mode dio --flash_size 4MB --flash_freq 80m \
  0x0     ${BINPATH}/bootloader.bin \
  0x8000  ${BINPATH}/partition-table.bin \
  0x9000  ${BINPATH}/ota_data_initial.bin \
  0x10000 ${BINPATH}/sd_card.bin

NB. All subsequent ESP32 firmware updates can be performed via the OTA web page (ota-esp32.htm) once the initial flash is complete.

Step 3 — Prepare the SD Card

The SD card is the primary storage medium for the picoZ80. It holds the configuration file, ROM images, disk images, and filing system directories.
Format requirements:
  • FAT32 file system.
  • Any capacity supported by FAT32 (typically up to 32GB is recommended for reliable formatting).
Minimum required files:
  • config.json in the SD card root — the main configuration file. See Basic Configuration for a minimal example.
  • webfs/ directory — the ESP32 web interface assets. Copy the contents of the webfs/ directory from the project repository to the SD card root.
Optional but recommended directories:
Directory Contents
MZF/ MZF format program files for loading via the filing system
ROM/ ROM images referenced by config.json
DSK/ Floppy disk images (DSK format) for WD1773 emulation
QD/ QuickDisk images for QuickDisk emulation
TZFS/ TranZPUter Filing System trees
RFS/ ROM Filing System program trees
The SD card can be managed entirely through the web File Manager once the picoZ80 is running — you can upload ROM images, edit `config.json`, and create directories from the browser without physically removing the card.

Step 4 — Install in the Host Computer

Safety first: Always power off and unplug the host computer before installing or removing the picoZ80. The board operates at 3.3V internally; the DIP-40 pins connect to the 5V host bus through appropriate interface circuitry, but the board must never be inserted or removed while powered.
Installation procedure:
  1. Power off the host computer and discharge any static.
  2. Remove the existing Z80 CPU chip from its DIP-40 socket. A chip extractor tool is recommended to avoid bending pins.
  3. Insert the SD card into the picoZ80 SD card slot.
  4. Align the picoZ80 with the Z80 socket, ensuring that Pin 1 of the picoZ80 (marked with a dot or notch) aligns with Pin 1 of the socket (also marked, and typically adjacent to the notch on the IC outline silkscreen).
  5. Press the picoZ80 gently and evenly into the socket. Ensure all 40 pins are seated. Do not force — if resistance is felt, remove and check pin alignment.
  6. Power on the host computer.
On first power-on after a fresh firmware install, the ESP32 will start in Access Point mode, broadcasting a WiFi network named picoZ80. Connect to this network to access the web interface and complete initial WiFi configuration.

NB. The picoZ80 draws power from the 5V supply on the Z80 socket VCC pin. The on-board buck converter delivers 3.3V to all board components. Ensure your host computer's 5V supply can deliver at least 500mA — the picoZ80 can draw up to 400mA under peak load (RP2350 at 300MHz with WiFi active).


WiFi Setup

WiFi connectivity requires the antenna matching network to be populated on the PCB and the ESP32 firmware to be built with WiFi enabled. See the Regulatory Notice below. If using NCM-only firmware, skip this section and use the USB NCM connection instead.
The picoZ80 ESP32 co-processor provides WiFi connectivity. On first boot (or when no WiFi credentials have been saved) the ESP32 starts in Access Point (AP) mode so that you can connect to it from any device with a browser and configure your home network settings.

Connecting in Access Point Mode

  1. On your phone, tablet, or laptop, open the WiFi settings and look for a network named picoZ80 (or as configured in the esp32.wifi.ssid field of your config.json when in AP mode).
  2. Connect to the picoZ80 network. The default AP password is pZ80pZ80 (check your firmware release notes if this has changed).
  3. Open a browser and navigate to http://192.168.4.1 — this is the default AP mode IP address. The picoZ80 web interface will appear.
  4. Navigate to the WiFi Manager page to enter your home network SSID and password.
  5. Click Save & Connect. The ESP32 will reboot and connect to your home network as a client.

Client (Station) Mode

Once configured for client mode, the ESP32 joins your home WiFi network and is reachable at the IP address shown on the Dashboard page. If you set a fixed IP in `config.json` (recommended for ease of access), navigate directly to:
http://192.168.1.192
Substitute the IP address you configured. If you are using DHCP, find the assigned address from your router's DHCP client list.
The web interface runs entirely in your browser. No additional software is required. The picoZ80 web server runs on port 80.

USB NCM Connection (No WiFi Required)
If the board is built with the NCM firmware (sdkconfig.mode_ncm_only or sdkconfig.mode_wifi_and_ncm), a virtual Ethernet adapter appears on the host computer when the ESP32 USB port is connected. The picoZ80's built-in DHCP server automatically assigns the host an IP address — no manual network configuration is needed.
  1. Connect a USB cable from the host computer to the ESP32 USB port on the picoZ80.
  2. Wait a few seconds for the host operating system to detect the new network adapter.
  3. Open a web browser and navigate to http://192.168.7.1.
  4. The picoZ80 Dashboard page should appear.
This mode provides full access to all web interface features: configuration editing, firmware updates, file management, and persona selection. No WiFi antenna or radio components are required.

Web Interface

The picoZ80 web interface is an eight-page Bootstrap 4 application served by the ESP32. All pages — including the new GUI Configuration editor — are accessible from the navigation bar at the top of each page. The web assets are stored in the webfs/ directory on the SD card and can be updated independently of the firmware.

Dashboard (index.htm)

The Dashboard is the home page of the web interface. It displays real-time system information and provides a quick overview of the picoZ80 state.
Information shown on the Dashboard includes:
  • Firmware Version — the currently running RP2350 firmware version and build date.
  • ESP32 Version — the ESP32 co-processor firmware version.
  • Active Partition — which of the two firmware slots (1 or 2) is currently active.
  • Active Persona — the machine personality currently loaded (e.g. MZ-700, MZ-80A).
  • RP2350 Clock — current CPU frequency in MHz.
  • PSRAM Clock — current PSRAM SPI clock frequency.
  • WiFi Status / Network Status — in WiFi modes: mode (AP or client), SSID, and assigned IP address. In NCM Only mode: this panel is titled Network Configuration and shows the USB NCM network status (IP address, netmask) — there are no WiFi-related fields.
  • WiFi RSSI (WiFi modes only) — live received signal strength indicator (dBm) that auto-updates every three seconds. Colour-coded: green (≥ −50 dBm, excellent), blue (≥ −70 dBm, good), orange (≥ −80 dBm, fair), or red (< −80 dBm, poor). Not shown in NCM Only mode.
  • WiFi TX Power (WiFi modes only) — current transmit power in dBm, also live-updated. Not shown in NCM Only mode.
  • SD Card — whether an SD card is present and the filesystem mount status.
  • Uptime — time since last reboot.
The Dashboard also provides buttons to reboot the RP2350 and to trigger a configuration reload from the SD card without a full reboot.
Two dropdown menus in the top-right navbar are available on every page:
  • Actions menu
    - Change Floppy Disk 1 / 2 — select a new DSK image file from the SD card and mount it in the virtual WD1773 floppy controller slot 1 or slot 2 without rebooting. The currently loaded disk image filename is displayed next to each entry (or "none" if no image is loaded). Where the active floppy controller supports more than two drives, Change Floppy Disk 3 and 4 also appear (they are hidden otherwise).
    - Change QD Disk — swap the active QuickDisk image file on the fly. The currently loaded QD image filename is shown next to the entry.
    - Reload RP2350 Config — send a reload command to the RP2350 over the ESP32–RP2350 UART; the RP2350 re-parses config.json and re-applies the memory map and driver configuration without a full power cycle.
  • Reboot menu
    - ESP32 — soft-reboot the ESP32 co-processor (restarts the web server and WiFi stack, RP2350 is unaffected).
    - RP2350B — reset the RP2350 processor (re-runs the bootloader and reloads the active firmware slot, host CPU is paused during reset).
    - Host — assert the host computer's reset line, rebooting the legacy computer in the Z80 socket without affecting the picoZ80 board itself.
    - IPL Reset — perform an IPL (Initial Program Load) reset by toggling 8255 PPI Port C bit 3, returning the machine to boot mode (BST). Available on MZ-80B, MZ-2000, MZ-2200, and MZ-2500 personas — equivalent to pressing the IPL button on the original hardware.
    - RP2350 UF2 Boot / RP2350 UF2 Boot (App) — restart the RP2350 into its USB bootloader (BOOTSEL) so a firmware .uf2 can be dragged and dropped onto the mounted drive. The plain variant enters the bootloader directly; the (App) variant re-enters it from the running application slot.

Config Editor (config.htm)

The Config Editor page provides an in-browser editor for the config.json file stored on the SD card. The editor features syntax highlighting and basic JSON validation.
To edit the configuration:
  1. Navigate to the Config Editor page.
  2. The current config.json is loaded into the editor automatically.
  3. Make your changes. The editor will highlight syntax errors in red.
  4. Click Save to write the updated file back to the SD card.
  5. Click Apply (or reboot) to apply the new configuration. The RP2350 reads the config at boot, minifies it, and stores it in Flash — subsequent boots use the Flash copy if no SD card is present.
Changes to the memory map, ROM images, and I/O map take effect on the next RP2350 reset. Changes to WiFi settings in the esp32.wifi block take effect on the next ESP32 reboot.

NB. The Config Editor operates directly on the SD card file. It is good practice to keep a backup copy of a working config.json on your PC before making significant changes.

GUI Configuration (configgui.htm)

The GUI Configuration page provides a graphical alternative to the raw JSON Config Editor. All configuration settings are presented as labelled input fields, dropdown menus, and editable tables, organised into four tabs:
  • RP2350 Global — global core voltage, CPU frequency, and PSRAM frequency settings that apply to both partitions.
  • Partition 1 / Partition 2 — per-partition memory map (address, size, type, wait states, bank, ROM file), I/O port map (address, size, type, handler), and driver configuration (e.g. MZ80AFI, RFS, MZ-1E30, Celestite, PCW9512) with enable/disable toggles and an Add Driver button for the full set of available interface drivers. Each section has add/remove buttons for rows.
GPIO bus tuning. The per-partition core settings let you tune the electrical characteristics of the Z80 bus independently for each of the three bus groups — Address, Data and Control. For each group you can set the drive strength (2 / 4 / 8 / 12 mA), slew rate (slow = low EMI, or fast), Schmitt-trigger input (on / off) and pull resistor (float / up / down). An optional per-pin override table lets you override any of these for individual GPIO pins where a particular host machine needs it. The default is 2 mA drive with slow slew (lowest EMI). The core settings also expose the Z80 refresh value: 0 disables DRAM refresh, 1 issues a refresh on every opcode fetch, and N issues one refresh every N fetches — allowing refresh activity to be balanced against peripheral timing.
  • ESP32 — ESP32 device type, network interface controls (WiFi Enable, NCM Enable, NCM Retries, NCM Retry Period), and WiFi configuration (SSID, password, IP address, netmask, mode AP/Client, DHCP, TX power). In addition to the WiFi fields you can set the USB NCM IP address and netmask, and a custom USB product name — useful for distinguishing multiple boards attached to the same host.
Click Save Configuration to write changes to config.json (previous version is backed up) and automatically reload the RP2350 configuration. Click Reload to re-read the current config.json from the SD card, discarding any unsaved changes. For advanced editing beyond what the GUI exposes, use the Config Editor to modify the raw JSON directly.
File browser. ROM and disk-image fields provide a Browse button that opens an SD-card file picker, so you can select a file without typing its path. The chosen path is stored relative to the SD card root.
Serial-card port. The MZ-8BIO3 and MZ-1E24 RS-232C serial interfaces take a port parameter — the base I/O address of the card (default 0xB0) — which is editable in the driver row.
Driver series and Base I/O Port. The Add Driver dropdown offers only the drivers valid for the running firmware — the page detects whether the firmware is a Sharp, Amstrad, Tatung or Open (experimenter) build. For the relocatable interface cards — MZ-1R12, MZ-1R18, MZ-1R23, MZ-1R37, PIO-3034, MZ-8BIO3, MZ-1E24, MZ-1E05 and Celestite — a single Base I/O Port field is shown, pre-filled with the card's authentic base. Change it to move the card's ports when the picoZ80 is used on a board of your own design; the GUI writes the correct mapping automatically. Each driver can also load its own System ROM images, which overwrite the host's ROMs when the driver is enabled.
Demo mode. An optional presentation helper that redacts sensitive fields — WiFi SSID and password, and IP/gateway addresses — from the web pages, so the interface can safely be shown in screenshots or screen recordings without exposing your network details.

File Manager (filemanager.htm)

The File Manager provides a browser-based view of the SD card contents. You can navigate directories, upload new files (ROM images, disk images, MZF program files), download existing files to your PC, create new directories, and delete files and directories.
Common tasks in the File Manager:
  • Upload a ROM image — navigate to the ROM/ directory and use the Upload button to transfer the ROM file from your PC.
  • Upload a disk image — navigate to DSK/ (floppy) or QD/ (QuickDisk) and upload the image file. Once loaded, the current disk image filename is displayed in the Actions menu next to each floppy and QD slot for quick reference.
  • Upload MZF programs — navigate to MZF/ or the relevant filing system directory and upload your MZF files.
  • Backup config.json — click the download icon next to config.json to save a copy to your PC.
  • Backup SD Card — click the Backup SD button to download the entire SD card contents as a single archive to your local computer. This is particularly useful when the picoZ80 is installed inside a host machine and physically removing the SD card is impractical. The backup captures all configuration files, ROM images, disk images, and filing system data in one operation.
File operations are performed directly on the SD card via the ESP32. Large file uploads may take a few seconds depending on file size and WiFi signal quality.

Persona Selection (personality.htm)

The Persona Selection page allows you to choose which machine personality (persona) the picoZ80 presents to the host computer. Different personas load different memory maps, ROM images, and virtual driver bindings from the configuration.
Currently supported personas:
  • MZ-700 — full MZ-700 persona with bank switching, virtual video, keyboard I/O, WD1773 floppy emulation, QuickDisk emulation, and ROM Filing System. Includes CP/M v2.23 (48K), SA-1510 BASIC, and Microsoft BASIC v4.7, all with SD card read/write access.
  • MZ-80K — Sharp MZ-80K persona with SP-1002 monitor, 8255 PPI, 8253 PIT, and LS367 keyboard. Two floppy interfaces are available: the native MZ80FIO (Toshiba T3444M) — the original MZ-80FD disk interface, which boots and reads all MZ-80K disks (games, applications, BASIC and CP/M); and the MZ80AFI card (the MZ-80A FDC) — used for CP/M, which can boot MZ-80K CP/M and read original MZ-80K CP/M disks from within CP/M as drives C:/D:. The persona can additionally bind the other MZ-80A interface cards (RFS, MZ-1E14/E19, MZ-1R12/R18/R37, PIO-3034). CP/M requires virtual persona mode.
  • MZ-80A — MZ-80A persona (in development).
  • MZ-80B — MZ-80B persona (in development).
  • MZ-800 — Sharp MZ-800 persona — dual-mode machine that runs in MZ-700 compatibility mode or native MZ-800 mode (320×200 / 640×200 graphics, 4/16-colour palette via the GDG), SN76489 PSG sound, WD1773 floppy and QuickDisk. Switches modes on the fly. Can bind the full MZ-80A/MZ-700 interface-card set plus the MZ-8BIO3 / MZ-1E24 RS-232C serial cards.
  • PCW-9512 — Amstrad PCW-9512 persona with gate array, uPD765 FDC, 512KB paged RAM, and daisy wheel printer controller.
  • Basic CPU — a bare in-socket Z80 with no machine-specific drivers, for a plain drop-in replacement or generic Z80 development.
  • OpenZ80 — the experimenter persona for a board of your own design or an unsupported machine (see Other Machines). Unlike the personas above it is a dedicated firmware build rather than a selection on this page: flash the OpenZ80 firmware to a partition using the Firmware Updates pages, then add its interface cards and set their base I/O ports on the GUI Configuration page.
Switching persona changes the active memory map and loaded ROM images without rebooting the host. The picoZ80 reinitialises its memory banking to the new persona's configuration and the host sees the new memory layout on the next Z80 bus transaction.

Firmware Updates (OTA)

Firmware for both the RP2350 and the ESP32 can be updated over-the-air (OTA) from the web interface without any physical access to the debug header.

RP2350 OTA Update (ota-rp2350.htm)
  1. Build the RP2350 firmware using the build scripts — the output binary will be in fw/bin/ with a version-stamped filename (e.g. BaseZ80_v3.009.bin).
  2. Navigate to http://<device-ip>/ota-rp2350.htm.
  3. Select which partition slot to update (Slot 1 or Slot 2). The currently active slot is shown on the Dashboard — it is good practice to update the inactive slot so that the existing firmware remains available if the update fails. Two optional checkboxes are available: Clear App Config wipes the ROM images and configuration data for the target slot, and Clear Flash Header resets the partition table to factory defaults — use these when upgrading to a firmware version with an incompatible configuration schema.
  4. Click Choose File, select the .bin file, then click Upload.
  5. Wait for the upload and verification to complete. The page will confirm success and show the new partition as available.
  6. To activate the new firmware, navigate to the partition selection section of the same page, choose the newly updated slot, and click Set Active & Reboot. The RP2350 reboots into the new firmware.
Application partitions use pure binary format (not UF2) because they reside at non-standard flash addresses. The bootloader verifies the image checksum before activating the new partition — if verification fails, the existing active slot remains in use.

ESP32 OTA Update (ota-esp32.htm)
  1. Build the ESP32 firmware — the output binary is esp32/build/tzpuPico_esp32.bin.
  2. Navigate to http://<device-ip>/ota-esp32.htm.
  3. Click Choose File, select the .bin file, then click Upload.
  4. The ESP32 reboots automatically into the new firmware after a successful upload.
The OTA page also accepts a FilePack archive to update the web interface assets stored in the webfs/ directory on the SD card. This allows the web UI to be updated independently of the ESP32 firmware binary.

WiFi Manager (wifimanager.htm)

Note: The WiFi Manager page is only available when the ESP32 firmware is built with WiFi enabled (sdkconfig.mode_wifi_only or sdkconfig.mode_wifi_and_ncm). In NCM Only mode this page is not present — network connectivity is provided automatically via USB NCM with no user configuration required.
The WiFi Manager page configures the ESP32 wireless network settings. Changes made here are persisted in the ESP32 NVS (non-volatile storage) and take effect on the next reboot.
Available settings:
  • WiFi ModeAccess Point (the picoZ80 creates its own network) or Client (the picoZ80 joins an existing network).
  • SSID — the network name to create (AP mode) or join (client mode).
  • Password — the WiFi passphrase.
  • IP Address / Netmask / Gateway — fixed IP settings for client mode (recommended for consistent access).
  • DHCP — enable to obtain an address from your router automatically; disable to use the fixed IP settings above.
  • TX Power — WiFi transmit power in 0.25 dBm units (range 8–84, i.e. 2–21 dBm). Set to 0 to use the region-specific default. The picoZ80 PCB is designed for minimal RF radiation, so when the board is installed inside a metal computer case the signal to the router may be weak. Increasing TX Power can improve connectivity at the expense of slightly higher power consumption; decreasing it can reduce interference in environments with many WiFi devices. The current RSSI and effective TX Power are shown on the Dashboard and on this page, updated live every three seconds.
After saving, click Reboot ESP32 to apply the new settings. The ESP32 will restart and connect using the new configuration. If client mode connection fails (wrong SSID or password), the ESP32 will fall back to Access Point mode after a timeout, allowing you to reconnect and correct the settings.

Basic Configuration

All picoZ80 behaviour is controlled by config.json on the SD card. The following is a minimal working configuration for a Sharp MZ-700. Refer to the Technical Guide — Configuration Reference for a full description of all options.
{
  "esp32": {
    "core": {
      "device": "Z80",
      "mode":   0
    },
    "wifi": {
      "override":  1,
      "wifimode":  "client",
      "ssid":      "YourNetwork",
      "password":  "YourPassword",
      "ip":        "192.168.1.192",
      "netmask":   "255.255.255.0",
      "gateway":   "192.168.1.1",
      "dhcp":      0,
      "webfs":     "webfs",
      "persist":   0
    }
  },
  "rp2350": {
    "core": {
      "cpufreq":   300000000,
      "psramfreq": 133000000,
      "voltage":   1.10
    },
    "z80": [
      {
        "memory": [
          {
            "enable":   1,
            "addr":     "0x0000",
            "size":     "0x1000",
            "type":     "ROM",
            "bank":     0,
            "tcycwait": 0,
            "tcycsync": 0,
            "task":     "",
            "file":     "/ROM/mz700.rom",
            "fileofs":  0
          },
          {
            "enable":   1,
            "addr":     "0x1000",
            "size":     "0xCFFF",
            "type":     "RAM",
            "bank":     0,
            "tcycwait": 0,
            "tcycsync": 0,
            "task":     "",
            "file":     "",
            "fileofs":  0
          },
          {
            "enable":   1,
            "addr":     "0xD000",
            "size":     "0x3000",
            "type":     "PHYSICAL_VRAM",
            "bank":     0,
            "tcycwait": 2,
            "tcycsync": 1,
            "task":     "",
            "file":     "",
            "fileofs":  0
          }
        ],
        "io":      [],
        "drivers": []
      }
    ]
  }
}
This example maps:
  • 0x0000 – 0x0FFF — 4KB ROM loaded from /ROM/mz700.rom on the SD card into PSRAM bank 0.
  • 0x1000 – 0xCFFF — 48KB RAM in PSRAM bank 0 (read/write, zero wait states).
  • 0xD000 – 0xFFFF — 12KB passed through to the physical host hardware (VRAM and I/O registers), with 2 wait states and T1 synchronisation enabled.

Network File Server Setup

The picoZ80 can load, save, and list BASIC programs over the network using the NETx: device prefix in Sharp MZ BASIC. This requires three components: the Celestite virtual interface (configured in the persona's JSON), the picoZ80 connected to the same network as the PC (via WiFi or USB NCM), and the netfs.py Python file server running on the PC.

Prerequisites
  • Python 3.6 or later installed on the PC.
  • The picoZ80 persona must include the Celestite interface in its config.json driver configuration.
  • The Celestite interface must have the ip parameter set to the PC's IP address and netfs.py port in config.json.
  • The picoZ80 and PC must be on the same network (WiFi client mode or USB NCM).
  • A directory on the PC containing MZF program files to serve.

Starting the File Server
The file server is located in tools/NetFileServer/netfs.py in the picoZ80 repository. Start it from a terminal:
# Serve files from the current directory on default port 6800:
python3 netfs.py --dir ./mzf_files

# Serve files on a custom port with multiple unit directories:
python3 netfs.py --port 6800 --dir ./games --dir2 ./utilities --dir3 ./basic_programs

# Each --dirN maps to NETx: in BASIC (--dir = NET1:, --dir2 = NET2:, etc.)
The server supports units 1–7 (NET1: to NET7:), each mapped to a separate directory on the PC. It listens for TCP connections on port 6800 (configurable with --port). The server is multi-threaded and handles MZF header parsing automatically.
Supported file formats: .mzf, .mzt, .m12 (Sharp MZ tape format with 128-byte header), and raw binary files (.bin, .rom, .dat).

Using NETx: in BASIC
Once the file server is running on the PC, use the NETx: device prefix in BASIC commands. The picoZ80's Celestite W5100 driver connects to the PC automatically:
DIR NET1:                  List files on network unit 1 (PC directory)
LOAD "NET1:MYPROG"         Load a program from the PC
SAVE "NET1:MYPROG"         Save a program to the PC
LOAD "NET3:GAME"           Load from unit 3 (mapped to --dir3)
Troubleshooting: If the connection fails, verify that the picoZ80's IP address can reach the PC (check the Dashboard status page for the assigned IP), ensure no firewall is blocking port 6800, and confirm the file server is running. The server prints connection information to the terminal when a client connects.

Virtual Cassette (CMT) Unit

The Virtual Cassette (CMT) unit is a waveform-level cassette deck for the Sharp MZ series. Unlike SD-card loading — which is the fast path — the CMT is the authentic path: it reproduces the cassette waveform rather than short-cutting the loader, so from the host machine's point of view it is indistinguishable from a real tape deck. Monitor LOAD, BASIC, custom and turbo loaders, and SAVE all work exactly as they would with a physical recorder. Take any .mzf file and load it "like a real cassette", or record a running program back out to a fresh .mzf.
The CMT adapts automatically to the host machine. On the simple family (MZ-80K, MZ-80A, MZ-700, MZ-800) the deck is driven by the machine's motor on/off line and behaves as a plain linear tape at 1200 baud. On the computer-controlled family (MZ-80B, MZ-2000, MZ-2200, MZ-2500) the host can command the transport directly (PLAY, STOP, fast-forward, rewind, eject and APSS program search) at 2000 baud. You do not need to choose the family — it is inferred from the active persona.

Enabling the CMT Interface
The CMT is an interface driver and is off by default. Enable it either by setting "enable": 1 on the CMT interface block in config.json, or by adding and enabling it from the web GUI Configuration page. Once a CMT interface is enabled, a new CMT panel appears in the web GUI for building the tape, and a CMT: Virtual / Real item appears in the Actions menu.

Building the Tape Queue
The virtual tape is an ordered queue of up to 16 MZF files — the programs that would sit one after another on a physical cassette. Build and edit the queue live from the CMT panel in the web GUI: add a file from the SD card, remove an entry, or reorder entries to change the play order. Changes take effect immediately, without a reboot.
At the end of each program the tape auto-advances to the next entry, exactly like a real tape running on to the next recording. The next LOAD therefore finds the next program in the queue automatically, with no manual intervention.

Loading Programs
Load from the virtual tape exactly as you would from a real deck — there is nothing CMT-specific to type. Use the monitor LOAD command, load from BASIC, or run any custom or turbo loader; each reads the current program from the head of the queue, and the queue then auto-advances ready for the next load.

Transport Behaviour
How the deck responds depends on the host family:
  • Simple family (MZ-80K / MZ-80A / MZ-700 / MZ-800) — the machine has only a motor on/off line, so the tape simply runs while the motor is energised, exactly like the original deck. There are no host-driven transport controls.
  • Controlled family (MZ-80B / MZ-2000 / MZ-2200 / MZ-2500) — the machine can command the transport directly: PLAY, STOP, fast-forward and rewind (which skip whole programs), EJECT, and APSS program search, which seeks to the next or previous program boundary and stops automatically.

Recording (SAVE)
Saving works too. When the host puts the deck into record mode (motor on, record) and issues a SAVE, the CMT captures the outgoing waveform, decodes it, verifies its checksum, and writes a new MZF file to the CMT/ directory on the SD card. The file is named:
CMT/taperecord_<name>_<YYYYMMDD_HHMMSS>.mzf
where <name> is the program's own Sharp filename (converted to ASCII and sanitised so it is a legal FAT filename — any illegal character becomes _). If the ESP32's clock has not been set, a sequence number is used instead of the timestamp (e.g. taperecord_<name>_0001.mzf) so recordings never collide on the default 1980 date. The original Sharp name is stored inside the MZF header itself, so the program keeps its real name when you reload it.

Real / Virtual Toggle (CMT, Floppy and QD)
The same live Virtual / Real toggle that switches the CMT deck also applies to the Floppy (FDD) and Quick Disk (QD) interfaces. In the web GUI the deck toggle is a menu item — CMT: Virtual / Real in the CMT menu, and Floppy and QD toggle items available on every page. Any of the three can be flipped between the two modes live, without a reboot:
  • Virtual — the firmware emulates the drive or deck: the virtual cassette, or the SD-backed floppy / QuickDisk images, supply what the host reads, and motor/transport commands to any physically attached hardware are suppressed. This is the normal mode for loading and recording SD-card images.
  • Real — pure pass-through to the physically attached drive or deck. The host drives the real hardware directly and the picoZ80 only snoops the bus read-only.
An interface that is physically configured now starts in Real mode, with the menu and toggle present so you can flip it to Virtual at any time. The current mode of each deck is reported over the status (INF) interface — the new fddMode and qdMode fields for the Floppy and QD, while the CMT reports its own mode as before. Behind the scenes a mode switch runs a drive-mode-change task and sends the matching reverse command (FDDM / QDDM) over the ESP32→RP2350 reverse-command queue — the same mechanism as the CMT toggle — installing or uninstalling the per-driver hooks on each switch. In Real QD mode the real SIO pass-through on ports F4–F7 is paced with a 10 µs inter-access recovery floor, because the Z80 SIO needs several clock cycles of recovery between accesses.
The Floppy and QD menus each also gain an EJECT button. Ejecting mounts an empty path, unmounting the drive so the FDC reports that no disk is present — useful for software that probes for a disk or expects a door-open state.
The toggle is handy for transferring programs between media — for example, load a program from a virtual image or tape, then flip to Real and SAVE it to the physical drive or cassette, or vice-versa.

Adding Chips — Virtual Peripheral Devices

The picoZ80 can add support chips to a machine that never had them. A Z80 DMA controller, a counter/timer, a parallel port, a floppy controller — each is a faithful model of the real integrated circuit, and each can be dropped into any machine and wired up from the web interface. Nothing is soldered and no firmware is written; the chips are described in the configuration and built when the machine starts.
This is how you experiment with a design before committing it to hardware, add a peripheral the original machine lacked, or build an entirely new Z80 computer of your own on the picoZ80. The chips available are listed below; the Technical Guide documents every pin of every one of them.
Chip What it is for
Direct Memory Access (DMA) Moves blocks of data between memory and I/O without the processor. Can also search for a byte value.
Counter / Timer (CTC) Four independent timers or event counters. Used for periodic interrupts and to generate baud rates.
Parallel Input / Output (PIO) Two 8-bit ports with handshaking. Used for printers, keyboards and general control lines.
Interval Timer (8253 / 8254) Three counters with six counting modes. Used for timing, tone generation and interrupts.
Parallel Interface (8255) Three 8-bit ports with programmable direction.
Floppy Disk Controller (WD1773) Reads and writes floppy disk images held on the SD card.
Signal Port A test port that puts eight wires onto one address, so a program can drive and read them directly.

Adding a Chip
Open the web interface and choose Config from the left-hand menu, then GUI. The configuration page opens with four tabs; select the partition you want to fit the chips to — either Partition 1 or Partition 2. Scroll past Core Settings, Memory, I/O and Drivers and you will find two panels, Devices and Connections.
Click the heading of the Devices panel to open it, then press Add Device. A list of the available chips appears, each with a short description of what it does. Click the one you want and it is added immediately — there is no dialogue to fill in.
The chip arrives ready to work. The picoZ80 gives it a free address, a name, connects every one of its inputs, joins its interrupt line to the processor, and places it in the interrupt priority chain. You can add a counter/timer, change nothing at all, save, and the machine will start with a working timer fitted. This is deliberate — a half-wired chip is the most common way to get a design that does nothing, so the interface will not let you create one.

Chip Settings
Each chip occupies one row. The settings you will normally touch are:
  • En — tick to fit the chip, clear to leave it out without deleting your work.
  • Device — which chip this is. Hover over it for a reminder of what it does.
  • Name — the name you use when wiring this chip to others. Choose something short and memorable; ctc0 and dma0 are the defaults.
  • Space — whether the chip appears in the I/O map or the memory map. Almost always I/O.
  • Address — the first address the chip answers to. If the box turns pink, the address clashes with another chip; pick a different one.
  • Size — how many addresses it uses. Leave this at the value the picoZ80 chose unless you know otherwise.
The Advanced button reveals two more settings that apply only to the DMA controller — whether it signals that it has taken the bus, and the largest number of bytes it may move before letting the processor back in. The defaults are sensible and are best left alone until you have a reason to change them.
Do not rename a chip after you have wired it. The connections refer to the chip by name and are not updated, so they will show as missing. If you need a different name, remove the chip and add it again.

Wiring Chips Together
The Connections panel is the wiring loom. Each row is one wire: something drives something else. Press Add Connection to make a new one.
Pick what the wire comes from in the Source list and what it goes to in the Destination list. Both lists are written in plain language — you will see entries such as Counter / Timer (CTC) — Channel 0 output (time out) rather than pin numbers. Outputs only appear as sources and inputs only as destinations, so it is not possible to connect two outputs together by mistake. An input that is already driven is shown greyed out and marked (already connected), because a real input can only be driven by one thing.
There are three kinds of connection you can make.
  • A direct wire — choose a source and a destination and you are done. This is the equivalent of a track on a circuit board joining one chip to another.
  • A fixed level — choose Always on (1) or Always off (0) as the source. This is the equivalent of strapping a pin to the supply rail or to ground, and it is what you do with any input the design does not use.
  • A logic condition — click another input to add a second source. A Combine box appears offering When both are on (AND), When either is on (OR), When not both are on (NAND), When neither is on (NOR), When exactly one is on (XOR) and When both or neither (XNOR). This is the equivalent of fitting a small logic gate between the chips.
Beside every source is an invert tick box. Ticking it flips that signal, so a chip that expects an active-low input can be driven from an active-high output without any further thought. The Note column is a label for your own benefit and has no effect on the machine.
If any input is left unconnected, a yellow reminder appears beside Add Connection listing them. It is not an error — an unconnected input simply reads its resting level — but connecting everything explicitly makes the design say what you meant.

A Worked Example — a Timer-Gated DMA
Suppose you want a DMA controller that moves data only while a timer allows it, so that the transfer cannot monopolise the machine. Three steps:
  1. In Devices, press Add Device and choose Counter / Timer (CTC). Then press it again and choose Direct Memory Access (DMA). Both chips are now fitted and fully connected.
  2. In Connections, press Add Connection. Set Source to Counter / Timer (CTC) — Channel 0 output (time out), click another input and set the second source to Channel 1 output (time out). Set Combine to When neither is on (NOR). Set Destination to Direct Memory Access (DMA) — Transfer request from the peripheral.
  3. Press Save Configuration.
The DMA will now transfer only while both timer outputs are low. Everything else about the transfer — where from, where to, how many bytes, and whether it moves or searches — is programmed by the software running on the Z80, exactly as it would be with a real chip.

Saving Your Changes
Press Save Configuration at the foot of the page. The settings are written to config.json on the SD card and the previous file is kept as a backup. Reload discards your edits and reads the file back from the card.
Important: a few seconds after saving, the emulated machine restarts by itself so that the new chips can be built. Anything running on the Z80 at the time is lost, so save your work in the emulated machine first. The page does not warn you about this.
Things to be aware of
  • Each partition has its own chips. Devices and Connections are configured per partition, so a chip fitted to Partition 1 does not appear in Partition 2. Remember to set up the partition you actually intend to run.
  • Saving rewrites the whole configuration file. Comments are removed and addresses are rewritten in decimal. The machine is unaffected, but if you keep a carefully annotated config.json, keep a copy of it elsewhere before using the graphical editor.
  • Clashing addresses do not prevent saving. The address box turns pink but the save is allowed. Check for pink boxes before you save.
  • When adding a connection, always choose the Destination explicitly, even if the list already shows the one you want.
  • If the Add Device list is empty or the Config GUI page is missing altogether, the SD card is carrying an old web interface. Update the file pack on the card.
Chips may also be declared by hand in config.json using the Config Editor, which is the only way to reach a few less common settings such as the floppy controller's disk images and the interval timer's clock frequency. The format is documented in the Technical Guide.

Drivers and Interfaces

The picoZ80 uses a modular driver architecture. Each host machine is implemented as a persona driver that defines the memory map, I/O handlers, and supported peripherals. Expansion hardware is implemented as interface drivers that plug into a persona. All drivers are configured via JSON in config.json on the SD card. Persona drivers are grouped by manufacturer below.

Sharp MZ / X1 Series

The Sharp MZ and X1 series machines are the primary targets of the picoZ80 project. Each machine model has a dedicated persona driver with support for its specific peripheral set.

Machine Persona Drivers
Each persona driver emulates a complete Sharp MZ computer. The persona is selected via the Persona Selection web page or by editing the "name" field in the "drivers" array of config.json.
Persona Driver Description
MZ-700 MZ700 Sharp MZ-700 with bank-switched memory, VRAM, keyboard, 8253 PIT. The default persona.
MZ-1500 MZ1500 MZ-700 superset with inbuilt Quick Disk, PCG, stereo PSG (SN76489AN), Z80 PIO printer.
MZ-80K MZ80K Sharp MZ-80K with SP-1002 monitor, 8253 PIT, 8255 PPI, LS367 keyboard, native MZ-80FD (T3444M) floppy, and MEMSW for CP/M. Can use the full MZ-80A interface-card set. CP/M requires virtual mode.
MZ-800 MZ800 Sharp MZ-800. Dual-mode (MZ-700 compatible + native 320/640-wide GDG graphics, 4/16 colour), SN76489 PSG, WD1773 FDC + QuickDisk. Runs MZ-700 software and CP/M.
MZ-80A MZ80A Sharp MZ-80A with SA-1510 monitor, 8253 PIT, 8255 PPI, MEMSW for CP/M.
MZ-2000 MZ2000 Sharp MZ-2000 with IPL ROM, BST/NST mode switching, VRAM overlay, MB8866 FDC.
MZ-2200 MZ2200 Sharp MZ-2200, MZ-2000 variant with same architecture.
MZ-80B MZ80B Sharp MZ-80B. 2K IPL ROM, BST/NST modes, monochrome with dual GRPH pages, 8253 PIT, 8255 PPI, Z80 PIO.
MZ-2500 MZ2500 Sharp MZ-2500 (SuperMZ). 8-page MMU, MZ-2000/MZ-80B compatibility modes, YM2203 FM+PSG, G-CRTC, MB8876 FDC. Virtual mode supports interrupt-driven software, D88 native disk format, and one-shot interrupt suppression.
X1 / X1 Turbo X1 Sharp X1 and X1 Turbo, run in physical mode — the real machine provides memory, video, keyboard, sub-CPU and sound. Adds virtual SD-card floppy drives on the built-in FDC and a sample-level virtual cassette deck. Initial release; see below.

Sharp X1 — Virtual Floppy Drives
The X1 persona is a first release and is deliberately narrow in scope. The picoZ80 does not emulate the X1 itself: the real machine supplies its own memory, the IPL and BIOS ROM, the video hardware, the keyboard and sub-CPU, the sound generator and the built-in counter, serial and DMA chips. What the picoZ80 adds is the built-in floppy disk controller, so that disk images held on the SD card appear to the X1 as real drives.
The emulated controller is the MB8877A fitted to the X1, and the machine's own IPL boots from it without modification. Up to four drives are supported, although the number actually available depends on free memory and is typically two or three — the boot log reports how many were fitted. Disks are 5.25 inch, double sided, 16 sectors of 256 bytes per track, in either 2D (40 track) or 2DD (80 track) format. Raw sector images, .D88 images and Extended CPC .DSK images are all accepted, and the format is recognised from the file's contents rather than its name. Writes are saved back to the image file on the SD card.
Images are mounted from the Floppy menu of the web interface in the usual way, and may be changed or ejected while the machine is running. The FDD Unit: Virtual / Real toggle works on the X1 as it does on the MZ machines, handing the controller ports back to the X1's own hardware when set to Real and keeping your loaded images ready for when you switch back.
To use it, select Sharp X1 / Turbo on the Personality page. Both partitions must be set to the X1 — no mixed configuration with another machine is provided yet. No ROM files are needed, since the X1 uses its own. Create a dsk/X1/ directory on the SD card and place your disk images there; the persona expects drive0.2d and drive1.2d by default, though you can mount anything you like from the web interface afterwards.

Sharp X1 — Virtual Cassette Deck
The X1 has its own virtual cassette deck, separate from the one used by the MZ machines. It works at the sample level: an X1 tape image is a .tap file, which is not a structured program file but a recording of the cassette read line itself, one bit per sample. The picoZ80 simply clocks that recording onto the machine's read line at the rate the file specifies, and samples the write line back into the same form when recording. Because nothing is decoded or re-encoded, any loader behaves exactly as it would with a real tape.
This is why the X1 deck is a different engine from the MZ one. Sharp MZ tapes are stored as .mzf files — a header record and a body — which the picoZ80 has to encode into a waveform. X1 tapes are already the waveform. The two formats are not interchangeable.
Tape files are commonly sampled at 8 kHz, though 22.05, 24, 32, 44.1 and 48 kHz files are all accepted, and the rate is read from the file. Both the standard .tap layout and the headerless variant are supported. Files often run to several megabytes, so playback streams from the SD card rather than being loaded into memory — a long tape costs nothing in RAM.
The deck responds to the X1's own transport controls, because the X1 commands its cassette through the sub-CPU rather than by toggling a motor line as the MZ machines do. Play, stop, fast forward, rewind, eject and record all work from software, as does APSS — the automatic programme search that steps forward or back to the next gap between recordings. The machine can also read the deck's sensors, so software that checks whether a cassette is present, whether it is write protected, or whether the tape has run to its end will get sensible answers.
Tapes are mounted from the CMT menu of the web interface in the same way as on the MZ machines. Place your .tap files in a CMT/X1/ directory on the SD card; the persona loads tape0.tap by default. Recording writes a new .tap file back to the card.
Not yet supported on the X1
  • Everything other than the floppy controller and the cassette deck. There is no emulation of the X1's memory, video, graphics RAM, keyboard or sound — the real machine provides all of it. A fully virtual X1 is a later project.
  • Turbo-specific hardware. One generic X1 profile is provided. The Turbo's own DMA controller, 2HD media and extended floppy features are not modelled.

Interface Drivers
Interface drivers are added to a persona via the "if" array in the driver's JSON configuration. Each interface can be independently enabled or disabled and has its own ROM, disk image, and I/O configuration.
Interface JSON Name I/O Ports Description
ROM Filing System RFS 0x51–0x53, 0xC1–0xC8, 0xEFF8–0xEFFF Enhanced monitor with SD card storage, CP/M boot, ROM banking.
TranZPUter Filing System TZFS 0x60 (memory modes), 0x68 (service processor) Multi-bank monitor + CP/M filing system for the MZ-700 persona (mutually exclusive with RFS). Boots CP/M; drive images set per-drive via param file fields.
Floppy Controller (MZ-700) MZ-1E05 0xD8–0xDE WD1773-based FDC. Up to 4 drives. DSK/RAW/D88 images.
Floppy Controller (MZ-80K) MZ80FIO 0xF8–0xFB, 0xF000–0xF3FF Native MZ-80FD/MZ-80FIO interface driving a Toshiba T3444M FDC. 128-byte FM sectors, up to 4 drives, CPC extended DSK images.
Floppy Controller (MZ-80A) MZ80AFI 0xD8–0xDE, 0xF3FE–0xF3FF MZ-80A AFI with DRQ-based A10 toggle for 2 MHz operation.
Floppy Controller (MZ-2000) MZ-8BFI or E0054PA 0xD8–0xDE MB8866 FDC without driver ROM (code in IPL). D88 support.
QuickDisk (with ROM) MZ-1E14 0xF4–0xF7 QD controller with BIOS ROM for MZ-700/MZ-800.
QuickDisk (ROM-less) MZ-1E19 0xF4–0xF7 QD controller for MZ-800/MZ-2000/MZ-2200/MZ-2500.
32KB Battery RAM MZ-1R12 0xF8–0xFA Battery-backed CMOS SRAM, persisted to SD card. Up to 2 instances.
64KB RAM Board MZ-1R18 0xEA–0xEB RAM expansion used as RAMFILE disk.
Kanji ROM MZ-1R23 0xB8–0xB9 128KB Kanji ROM (16×16 JIS) + optional 256KB Dictionary ROM.
640KB EMM MZ-1R37 0xAC–0xAD Expanded Memory Manager with 20-bit address space.
320KB EMM PIO-3034 configurable IO DATA EMM with auto-increment data port.
Celestite LAN Celestite 0x60–0x6F W5100 Ethernet, interrupt controller, UFM, integrated MZ-1R12/MZ-1R37. Configurable server IP via JSON ip parameter.
SASI Hard Disk MZ-1E30 0xA4–0xA5, 0xA8–0xA9 MZ-1E30 SASI hard disk controller (MZ-2500/MZ-80B). Up to 4 disks (~21.4 MB each). 32KB IPL ROM. Disk images on SD card.
RS-232C Serial (BI) MZ-8BIO3 0xB0–0xB3 (configurable) Dual-channel RS-232C card (emulated Z80 SIO). Channels A/B bridge to USB CDC serial ports 2 and 3. “BI” connector wiring.
RS-232C Serial (ST) MZ-1E24 0xB0–0xB3 (configurable) As MZ-8BIO3 but Sharp “ST” connector wiring.

TZFS and CP/M (MZ-700 persona)
TZFS is a low-level monitor and filing system offered as a selectable interface on the MZ-700 persona (in practice used instead of RFS — enable one or the other). Beyond the monitor it boots and runs CP/M. TZFS ships in the MZ-700 configuration but is off by default: enable it by setting "enable": 1 on the TZFS interface block in config.json, or by turning it on from the web GUI Configuration page. Its ROM (roms/tzfs.bin) must be present on the SD card.
CP/M disk images are configured per drive through the interface's param array — each param[].file entry gives the path of a whole-image file on the SD card for that drive. When a drive has no explicit file entry, the fallback path template CPM/SDC16M/RAW/CPMDSK<nn>.RAW is used. picoZ80 has no direct SD access, so these sectors are read and written through the ESP32.

Booting a Floppy Disk from TZFS
From TZFS v1.8.3, you can boot a floppy-disk operating system on the MZ-80A, MZ-700 and MZ-800 personas directly from within TZFSincluding MZ-800 CP/M. Insert a bootable disk image into the floppy controller (see Actions → Change Floppy Disk) and boot it from the TZFS monitor; TZFS loads and runs the disk's own IPL exactly as the machine's native boot ROM would, so disk BASIC, CP/M and other disk-based operating systems start normally.
Once a floppy-booted OS is running, pressing the host's hardware RESET switch returns you to TZFS rather than to the bare machine monitor — TZFS restores itself and cold-boots, so you are always brought back to a familiar prompt ready to load the next program.

Persona–Interface Compatibility
Not every interface driver is available for every persona. The table below shows which interfaces can be used with each machine persona via the "if" array.
Interface MZ-700 MZ-1500 MZ-80K MZ-800 MZ-80A MZ-2000 MZ-2200 MZ-80B MZ-2500
RFS Yes Yes Yes Yes Yes Yes
TZFS Yes
MZ-1E05 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
MZ-1R18 Yes Yes Yes Yes Yes Yes Yes Yes Yes
MZ-1R23 Yes Yes Yes Yes
MZ-1R37 Yes Yes Yes Yes Yes
PIO-3034 Yes Yes Yes Yes Yes
Celestite Yes Yes Yes Yes
MZ-1E30 Yes Yes
MZ-8BIO3 Yes Yes Yes Yes
MZ-1E24 Yes Yes Yes Yes

JSON Configuration Example
The following example shows how to configure an MZ-700 persona with RFS, a floppy controller, and a QuickDisk drive. This is placed in the "drivers" array within the partition's z80 block in config.json:
"drivers": [
  {
    "name": "MZ700",
    "enable": 1,
    "type": "VIRTUAL",
    "if": [
      {
        "name": "RFS",
        "type": "VIRTUAL",
        "enable": 1,
        "rom": [
          {
            "file": "roms/MROM_PICOZ80_256.bin",
            "enable": 1,
            "loadaddr": [
              { "enable": 1, "position": "0x0", "addr": "0x0",
                "bank": 32, "size": "0x80000", "tcycwait": 0, "tcycsync": 0 }
            ]
          },
          {
            "file": "roms/USER_ROM_PICOZ80_256.bin",
            "enable": 1,
            "loadaddr": [
              { "enable": 1, "position": "0x0", "addr": "0x0",
                "bank": 40, "size": "0x80000", "tcycwait": 0, "tcycsync": 0 }
            ]
          }
        ]
      },
      {
        "name": "MZ-1E05",
        "type": "VIRTUAL",
        "enable": 1,
        "param": [
          { "file": "dsk/MZ-2Z009-RFS.dsk", "enable": 1 }
        ]
      },
      {
        "name": "MZ-1E19",
        "type": "VIRTUAL",
        "enable": 1,
        "param": [
          { "file": "qd/BASIC_5Z008A_RFS.qd", "enable": 1 },
          { "file": "qd/BASIC_5Z008B.qd", "enable": 1 }
        ]
      },
      {
        "name": "Celestite",
        "type": "VIRTUAL",
        "enable": 1,
        "param": [
          { "file": "ram/celestite_r12.ram", "enable": 1 },
          { "file": "ram/celestite_r37.ram", "enable": 1 },
          { "ip": "192.168.1.210:6800", "enable": 1 }
        ]
      }
    ]
  }
]
Configuring the file server address: Add an ip entry to the Celestite interface's param array in config.json, specifying the PC's IP address and netfs.py port:
{ "ip": "192.168.1.210:6800", "enable": 1 }
MZ-1E30 SASI hard disk configuration: Add an MZ-1E30 interface to the persona's "if" array. The rom entry provides the 32KB IPL ROM, and param entries specify hard disk images (up to 4):
{
  "name": "MZ-1E30",
  "type": "VIRTUAL",
  "enable": 1,
  "rom": [{ "file": "roms/MZ-1E30.ROM", "enable": 1 }],
  "param": [
    { "file": "hdd/boot.hdd", "enable": 1 },
    { "file": "hdd/data.hdd", "enable": 1 }
  ]
}
Key JSON fields:
  • name — persona or interface name (must match a compiled driver).
  • enable — set to 1 to activate, 0 to skip.
  • type"VIRTUAL" (emulated in PSRAM) or "PHYSICAL" (pass-through to real hardware).
  • rom — array of ROM images to load. Each entry has a file path (relative to SD card root) and loadaddr array specifying where to place the data (Z80 address, PSRAM bank, size).
  • param — array of disk/RAM image files. param[0].file is the first disk, param[1].file the second, etc.
  • addrmap — optional address remapping (source → destination).
  • iomap — optional I/O port remapping.
All file paths are relative to the SD card root directory (e.g. "roms/MROM_PICOZ80_256.bin").

Amstrad PCW Series

The picoZ80 supports the Amstrad PCW-9512 and the Tatung Einstein TC-01 in addition to the Sharp MZ series.

Machine Persona Drivers
Each Amstrad persona driver emulates a complete Amstrad machine. The persona is selected via the Persona Selection web page or by editing the "name" field in the "drivers" array of config.json.
Persona Driver Description
PCW-9512 PCW9512 Amstrad PCW-9512. Z80A @ 4MHz, 512KB RAM with 4-bank 16KB page switching (ports F0-F3), gate array (ASIC) for video/system clock/FDC routing/motor control, NEC uPD765 FDC with CPC DSK format support, 8041 daisy wheel printer controller. Virtual and physical modes supported.
Einstein TC-01 EinsteinTC01 Tatung Einstein TC-01. Z80A @ 4MHz, 64KB RAM + 8KB switchable ROM, WD1770 FDC, TMS9129 VDP, AY-3-8910 PSG, Z80 CTC, Z80 PIO. Virtual and physical modes supported.
The PCW-9512 persona includes a complete bootstrap sequence emulation: the gate array byte stream loads the BIOS, which then loads CP/M from disk. Gate array commands include end bootstrap, reboot, FDC interrupt routing (NMI/INT/ignore), terminal count, and motor on/off.
Disk imaging: The picoZ80 can image physical floppy disks when running in the PCW-9512 persona. The dskimage ICE command injects Z80 code that reads all tracks via the host FDC and saves the result as a CPC DSK file on the SD card. See the ICE Command Reference for usage details.

Tatung Einstein Series

The picoZ80 supports the Tatung Einstein TC-01 — a Z80A-based home computer with integrated floppy disk drive.
Machine Persona Drivers
Each Tatung persona driver emulates a complete Tatung machine. The persona is selected via the Persona Selection web page or by editing the "name" field in the "drivers" array of config.json.
Persona Driver Description
Einstein TC-01 EinsteinTC01 Tatung Einstein TC-01. Z80A @ 4MHz, 64KB RAM + 8KB switchable ROM (X-TAL MOS), ROM/RAM toggle via port 0x24 (any access toggles; writes always go to RAM), WD1770 FDC with virtual disk support (40 tracks, 1 head, 10 sectors, 512 bytes — 200KB disks), TMS9129 VDP (video display processor) with inter-access timing enforcement (~2us gap), AY-3-8910 PSG (3-channel sound) at ports 0x02-0x03, Z80 CTC (4 channels) at ports 0x28-0x2B, Z80 PIO at ports 0x30-0x33, keyboard interface at port 0x20. Physical disk imaging: read physical floppy to DSK, write DSK to physical floppy. Virtual and physical modes supported.
Disk imaging: The Einstein TC-01 persona supports physical disk imaging via the dskimage ICE command with read and write subcommands. dskimage read images a physical floppy to a DSK file on the SD card, while dskimage write writes a DSK file from the SD card to a physical floppy.

Other Machines

Support for additional Z80-based machines is planned. Each new machine will follow the same persona driver architecture — a dedicated persona driver implementing the machine's memory map, I/O, and peripherals, with interface drivers for expansion hardware. Check the project repository for the latest list of supported machines.
OpenZ80 — for your own board or an unsupported machine. If you are building your own Z80 board, or you have a Z80 computer that does not yet have a dedicated picoZ80 persona, use the OpenZ80 firmware build. Its main attraction is that it lets you add virtual interface cards borrowed from other Sharp, Amstrad and Tatung machines onto your bespoke or unknown Z80 computer, so you can write new software for those cards — or adapt existing software to run on your machine. OpenZ80 behaves as a plain Z80: in physical mode it passes the whole 64K memory and I/O space through to your board, and in virtual mode it presents a flat 64K RAM into which you can load your own ROM images. It exposes only the machine-agnostic interface cards (the MZ-1R12/1R18/1R23/1R37 memory cards, the PIO-3034 parallel card, the MZ-8BIO3 / MZ-1E24 RS-232C serial cards, the MZ-1E05 floppy interface and the Celestite LAN board), and each of these can have its Base I/O Port relocated to match your board on the GUI Configuration page. To adapt one of the existing machines, developers can take any of the supplied drivers and its monitor / IPL / BIOS ROM source (supplied as Z80 assembler in the RFS and TZFS projects' asm/ directories) as a starting point — see the Developer's Guide.

ICE (Debug Shell)

The picoZ80 includes a built-in interactive debug shell (ICE — In-Circuit Emulator) for real-time inspection and control of the Z80 CPU emulation.
The ICE debug shell is included in the DBGSH firmware variant. If using the standard firmware, the debug shell is not available. Flash the DBGSH variant to enable ICE functionality.
Connecting: The debug shell is available on the second USB serial port. When the picoZ80 is connected via USB, two serial ports appear on the host computer — the first is the standard debug log output, the second is the debug shell. Open it with any terminal emulator at 115200 baud, 8N1. The prompt dbg> appears when connected.
Key capabilities:
  • Register inspectionregs displays all Z80 registers, flags, and cycle count.
  • Memory dumpdm p 0000 100 dumps 256 bytes from address 0x0000 via the physical bus; dm v 0000 100 reads virtual PSRAM directly.
  • Breakpointsbp E800 sets a breakpoint at 0xE800. Up to 8 breakpoints. bl lists them, bc * clears all.
  • Single-stephold pauses the CPU, then step executes one instruction showing before/after register state.
  • Execution tracetrace on records every executed instruction. trace dump 20 shows the last 20 with disassembly.
  • Disassemblydis v E800 20 disassembles 32 instructions from 0xE800.
  • I/O accessin D8 reads port 0xD8; out D8 03 writes 0x03 to port 0xD8.
  • System infostatus shows CPU frequency, PSRAM clock, emulation speed, and uptime. drivers lists active drivers.
Type help at the prompt for a quick summary.

Command Reference
The complete ICE command set is listed below. All address and data values are entered in hexadecimal without a 0x prefix.
Command Parameters Description
help Display a summary of all available commands.
regs Dump the complete Z80 register set: main registers (AF, BC, DE, HL), alternate registers (AF’, BC’, DE’, HL’), index registers (IX, IY), stack and program counter (SP, PC), interrupt state (I, R, IM, IFF1, IFF2), decoded flags (S, Z, H, P/V, N, C), HALT line state, MEMPTR, and the cumulative T-cycle count.
dm <p|f|v|r> <addr> [len] Dump memory in hex and ASCII. Four modes: p (physical) reads via the Z80 bus triggering real hardware; f (fetch) reads using M1 fetch cycles; v (virtual) reads PSRAM directly with no bus activity; r (RP2350) reads the microcontroller’s own address space (Flash, SRAM, peripherals). addr is the start address, len is the number of bytes (default 256).
search [p|v] <start> <end> <hex..>|"text" Search memory for a byte pattern or ASCII text string. p = physical bus, v = virtual PSRAM, omit for mapped (follows memory map). Specify the pattern as hex bytes (e.g. ED 4D 00) or a quoted ASCII string (e.g. "HELLO"). Matches are displayed with 8 bytes of context before and after. Auto-holds CPU for physical/mapped access. Pattern length up to 32 bytes.
cmp [f] <phys> <virt> <len> Compare physical bus memory with virtual PSRAM. Optional f uses fetch (M1) cycles instead of read cycles. Reports mismatches with addresses and values.
dis [p|v] [addr] [count] Disassemble Z80 machine code. p reads opcodes from the physical bus, v from virtual PSRAM (default). addr defaults to the current PC. count is the number of instructions (default 16). The current PC is marked with >.
asm [addr] Enter the interactive Z80 assembler at addr (default: current PC). Type Z80 mnemonics one per line; assembled bytes are written to virtual PSRAM and the address advances. Supports db and dw/defw directives. Enter an empty line or . to exit.
memmap [block] Display the SRAM memory-block pointer table. Each entry shows the block number, Z80 address range, memory type (PHYSICAL, RAM, ROM, FUNC), bank number, and PSRAM offset. If block is given, show only that 512-byte block.
memptr [addr] Display the PSRAM memPtr/memioPtr tables for the given address, showing the handler function pointer and attributes.
iomap [port] Display the I/O port handler table. Each entry shows the port address, handler function, and direction (read/write/both). If port is given, show only that port.
status System status overview: RP2350 CPU frequency, PSRAM SPI clock, host Z80 clock, emulation speed ratio, free SRAM, and uptime.
ver Display firmware version, build date, flash partition layout (address, size, checksum, active flag), author, and licence.
drivers List all active machine persona drivers and their sub-interfaces, showing initialisation status.
hold Pause Z80 CPU emulation. Core 1 finishes the current instruction and stops fetching. The shell waits for the hold to be acknowledged before returning. Required before using out or other commands that modify running state.
release Resume Z80 CPU emulation from the point it was held. Breakpoints remain active.
break Halt the running Z80 CPU and return control to the debug shell prompt. Alias: b.
go Continue execution (same as release). If a breakpoint is subsequently hit, the CPU will hold again automatically.
cont Alias for go. Continue execution from the current hold point. Breakpoints remain active.
step [n] Single-step one instruction (or n instructions). For each step the shell displays the register state before execution, the disassembled instruction with hex bytes, and the register state after execution. The CPU must be held first. If a breakpoint is hit during multi-step, execution stops early.
next [n] Step over one instruction (or n instructions). Like step, but CALL and other subroutine invocations run to completion rather than being stepped into. The CPU must be held first. Alias: n.
bp <addr> Set a breakpoint at the given Z80 address. Up to 8 breakpoints can be active simultaneously. When the PC matches a breakpoint address during opcode fetch, the CPU is automatically held and the hit is reported. Duplicate addresses are rejected.
bc <n|*> Clear breakpoint slot n (0–7) or all breakpoints (*).
bl List all active breakpoints showing slot number and address.
wm [p|v] <addr> <byte> [byte]... Write one or more bytes to memory. p = physical Z80 bus, v = virtual PSRAM, omit for auto (follows memory map). Multiple bytes can be specified separated by spaces. Auto-holds CPU for physical/mapped writes.
fill [p|v] <addr> <len> [w|d] <val> Fill a memory region with a constant value. p = physical, v = virtual, omit for auto. Default 8-bit; w = 16-bit, d = 32-bit (little-endian). Auto-holds CPU when needed.
copy <pv|fp|vp> <src> <len> <dst> Copy memory between physical and virtual. pv = physical read to virtual, fp = physical fetch to virtual, vp = virtual to physical write. Auto-holds CPU.
memtest <addr> <len> [pattern] Test physical memory with three passes: write+read, write+fetch, interleaved. Default pattern: ascending bytes. Reports errors per pass.
in <port> Read a Z80 I/O port. Drives a real IORQ+RD bus cycle and displays the byte read.
out <port> <byte> Write a byte to a Z80 I/O port. The CPU must be held first. Drives a real IORQ+WR bus cycle.
trace <on|off|dump [n]|clear|rt|byte ...> Control the execution trace. on enables recording of every executed instruction into a 512-entry ring buffer (PC, opcode, flags). off disables recording. dump displays the last n entries (default: all) with disassembly and decoded flags. clear resets the buffer. rt enables real-time trace output to the debug console as instructions execute. byte enables byte-level tracing of memory/IO transactions.
verify <on|off> Toggle full opcode fetch verification. When on, every fetch is verified against readPhysicalMem (50% speed reduction). When off (default), only checks 0x00/0xFF opcodes.
fwait <0-4> Force extra wait states on M1 (opcode fetch) cycles. <n> is 0–4 additional T-states (0 = off, default). Useful for debugging code that behaves differently with wait states — some peripherals or memory devices require specific timing margins. Without a parameter, displays the current setting.
iowait <0-8> Force extra wait states on I/O read and write cycles. <n> is 0–8 additional T-states (0 = off, default). Stretches I/O cycles for diagnosing peripherals with slow response times. Without a parameter, displays the current setting.
corrupt [clear] Display detected fetch corruptions (PC, fetched opcode, verified opcode). clear resets the log.
fdctrace <on|off|dump> Enable, disable, or dump FDC I/O trace. on/off controls recording of WD1773 FDC register accesses into a 64-entry ring buffer. dump displays the last 64 operations, showing the register name (Status/Command, Track, Sector, Data, DriveSel, SideSel, DDEN), read or write direction, byte value, and FDC status.
qdtrace <on|off|dump> Enable, disable, or dump Quick Disk I/O trace. on/off controls recording of QD SIO register accesses into a 64-entry ring buffer. dump displays the last 64 operations, showing port, direction, byte value, and tape position (byte offset).
qdprobe [seconds] / eng [pace_us] Hand-drive the real Quick Disk SIO (ports F4/F6/F7) and map its signals — motor on, hunts, and sync events (A = ChA RR0: b3=HDST, b4=hunt, b0=Rx; B = ChB RR0: b3=HOME). Runs for seconds (default 20, max 60), holding the Z80 CPU while it probes; go resumes. qdprobe eng [pace_us] replays the TZFS QD engine’s exact sequence (init, hunt, read) at a settable inter-access pace, for validating real-hardware timing.
piodbg [clear] Display RP2350 PIO hardware diagnostics for all three PIO blocks (PIO 0, PIO 1, PIO 2). Shows FDEBUG sticky error flags (stall, RX underflow, TX overflow), FSTAT status, FIFO fill levels, per-state-machine program counters, GPIO output-enable mask, and GPIO pin levels. clear resets the FDEBUG sticky flags. Useful for diagnosing PIO state machine hangs or bus contention.
load <p|v> <file> <addr> [len] [ofs] Load a file from the ESP32 SD card into Z80 memory. p = write to physical bus, v = write to virtual PSRAM bank 0. file is relative to /sdcard/ on the ESP32. addr is the Z80 start address. If len is omitted the entire file is loaded (up to 64KB); if specified, loads that many bytes (max 1MB). Optional ofs is the file offset to start reading from. Auto-holds CPU for physical writes. Uses PSRAM bank 63 as scratch buffer.
save <p|pf|v> <file> <addr> <len> Save Z80 memory to a file on the ESP32 SD card. p = read via physical bus, pf = read via physical fetch (M1 cycles), v = read from virtual PSRAM bank 0. file is relative to /sdcard/. addr is the Z80 start address, len is bytes to save (max 64KB). Auto-holds CPU for physical reads. Performs periodic DRAM refresh during physical reads.
dir [path] List files on the ESP32 SD card. Optional path is relative to /sdcard/. Shows filenames and sizes.
echo [on|off] Toggle or set character echo. When on (default), typed characters are echoed back to the terminal.
reset Force an immediate Z80 CPU reset. The emulation restarts from the reset vector (typically 0x0000).
set <reg|flags|memmap|memptr|iomap> <idx> <val> Modify a Z80 register, flags, memory map, PSRAM memPtr, or I/O port map entry at runtime. reg sets a Z80 register, flags sets the flags register, memmap/memptr/iomap modify the memory or I/O configuration. idx is the register name or table index (decimal or hex), val is the new value. This allows hot-patching registers and memory/IO configuration without reloading config.json.
hist [n] Display command history buffer. Shows the last n commands (default: all entries, up to 16). History is preserved across power cycles via ESP32 NVS storage.
savehst Force-save the current command history to ESP32 NVS storage immediately. History is normally saved automatically on graceful shutdown; this command forces an immediate save.
ipl Perform an IPL reset (BST mode) by toggling 8255 PPI Port C bit 3. Resets the machine to boot mode without a full Z80 reset. Useful for MZ-80B, MZ-2000, and MZ-2500 machines that distinguish between BST (boot) and NST (normal) modes.
mmutrace Dump machine-specific trace information such as MMU state and I/O register snapshots. The output varies by persona — each driver registers its own trace handler, so the information shown depends on the active machine persona.
intcount Show the interrupt acknowledge count and current interrupt state. Useful for debugging interrupt-driven software — displays how many interrupt acknowledge cycles have occurred and whether interrupts are currently pending or enabled.
psync [start end] Synchronise physical memory to PSRAM by reading the physical bus and writing to virtual memory. Optional start and end parameters specify an address range; if omitted, the full address space is synchronised. Useful for diagnostics after DMA operations or when physical and virtual memory may have diverged.
dskimage read <filename> [cylinders] [heads] / write <filename> Image a physical floppy disk to a DSK file on the SD card (read), or write a DSK file from the SD card to a physical floppy (write). dskimage <filename> defaults to read (backward compatible). filename is the DSK file path relative to /sdcard/. If cylinders and heads are omitted for read, geometry is auto-detected. The CPU must be held first. Progress is reported during imaging.
busdiag Display bus diagnostics including PIO state, signal levels, and bus contention status. Useful for diagnosing electrical issues with the host bus connection.
fdcimage Display FDC imaging status and progress.
fdcdiag Display FDC diagnostic information (controller state, register dump).
gadiag Display gate array diagnostic information (command state, interrupt routing).
Typical debugging workflow:
  1. Connect a terminal to the second USB serial port (115200, 8N1).
  2. hold — pause the CPU.
  3. regs — inspect current register state.
  4. dis v — disassemble code at the current PC.
  5. bp E800 — set a breakpoint.
  6. go — resume. The CPU will stop when it reaches 0xE800.
  7. step 5 — single-step 5 instructions from the breakpoint.
  8. dm v D000 80 — inspect VRAM contents.
  9. trace on / go / trace dump 50 — trace and review execution history.

Troubleshooting

Host computer does not boot

  • Check orientation — ensure Pin 1 of the picoZ80 aligns with Pin 1 of the socket. Inserting the board backwards will damage both the picoZ80 and the host computer.
  • Check seating — remove the picoZ80 and reinsert, ensuring all 40 pins are fully seated and none are bent under the board.
  • Check the SD card — if config.json is missing or malformed the firmware will attempt to use the previously cached Flash configuration. If no valid config exists the RP2350 will not initialise the memory map and the host will see an open bus.
  • Confirm firmware is flashed — try connecting the picoZ80 via USB (without inserting in the host) and verify the board enumerates on the PC.
  • Check host power supply — the picoZ80 can draw up to 400mA from the 5V rail. Some vintage computers have marginal power supplies; check the 5V rail voltage under load with a multimeter.

Cannot connect to the web interface

  • Check WiFi mode — on first boot or after a factory reset the ESP32 starts in AP mode. Connect to the picoZ80 WiFi network first, then navigate to http://192.168.4.1.
  • Check configured IP address — if client mode is configured with a fixed IP, verify the IP matches your network subnet.
  • Check the SD card is present — the web assets in webfs/ on the SD card are required. Without the SD card the web server cannot serve any pages.
  • Check the webfs directory — confirm the webfs/ directory exists on the SD card and contains the HTML/CSS/JS files from the project repository.
  • Factory reset WiFi — if credentials are lost, re-flash the ESP32 firmware to clear NVS settings and start fresh.

SD card not recognised

  • Ensure the SD card is formatted as FAT32. NTFS, exFAT, and FAT16 are not supported.
  • Try a different SD card — some high-capacity or high-speed cards can have compatibility issues with the ESP32 SPI SD interface.
  • Cards up to 32GB are well supported. Very large cards (>32GB) may require special formatting.
  • Re-seat the SD card — remove and reinsert to ensure good contact.

ROM images not loading

  • Verify the file path in config.json exactly matches the file location on the SD card (paths are case-sensitive).
  • Confirm the ROM image file is present on the SD card — use the File Manager to browse to the expected location.
  • Check the fileofs value — an incorrect offset will load garbage data into the ROM bank.
  • Verify the size field in the memory map entry is not larger than the ROM image file. Oversized mappings will pad with undefined data.

Board resets or reboots unexpectedly

  • The picoZ80 has a hardware watchdog timer that automatically resets the RP2350 if the firmware hangs during boot or normal operation. If the board reboots repeatedly, the watchdog is triggering because a boot stage is not completing in time.
  • Check the SD card — a missing or corrupt config.json can cause the boot sequence to stall during configuration parsing.
  • Check WiFi settings — if the ESP32 cannot establish a connection, the SPI handshake between the RP2350 and ESP32 may time out, triggering the watchdog.
  • Use SWD debugging — connect a debug probe and inspect the watchdog scratch registers (watchdog_hw->scratch[5] through scratch[7]). The boot progress code in scratch[6] indicates which stage the firmware reached before the watchdog fired, and scratch[0–3] hold a history of the last four reset attempts.
  • Check the persistent log — the firmware writes boot-critical messages to the last 4KB of PSRAM using plogf(). These messages survive the watchdog reset and are dumped to the debug console on the next successful boot, revealing what happened before the reset.

OTA update fails

  • Ensure you are uploading a .bin file (not a .uf2) for RP2350 OTA updates.
  • Ensure you are uploading to the inactive slot to preserve the working firmware.
  • If the OTA update fails or results in a non-booting partition, the bootloader will keep the previous active slot running — repeat the upload process.
  • For ESP32 OTA failures, re-flash via esptool using the debug header USB connection.

Reference Sites

Resource Link
picoZ80 project page /picoz80/
picoZ80 Technical Guide /picoz80-technicalguide/
pico6502 project page /pico6502/
RP2350 Datasheet datasheets.raspberrypi.com
Pico SDK Documentation raspberrypi.github.io/pico-sdk-doxygen
ESP32-S3 Technical Reference docs.espressif.com
X (Twitter) project preview engineerswork1

FCC / RED Regulatory Notice

The picoZ80 board has not been submitted for FCC (USA) or RED (EU) intentional radiator certification. As shipped, the board must have the WiFi antenna matching components unpopulated and the ESP32 firmware built with the NCM Only configuration. In this state no radio frequency energy is emitted and the board does not fall under intentional radiator regulations.
End users who wish to use WiFi may populate the antenna matching network, build the WiFi firmware variant, and operate under the hobbyist/experimental exemption applicable in their jurisdiction. Any party wishing to sell the board commercially with WiFi enabled must first obtain FCC and/or RED certification for the complete assembly.