CP/M 2.2 running on a real Zilog Z80 processor via the RetroShield Z80, controlled by an Arduino Giga R1 WiFi. Disk I/O is served over WiFi by a Rust sector server.
The Z80's 64KB address space lives in the Giga's internal SRAM. A custom level converter PCB bridges the Giga's 3.3V logic to the RetroShield's 5V. The system boots CP/M, runs DIR, and plays Zork I interactively.
| Component | Description |
|---|---|
| Arduino Giga R1 WiFi | STM32H747, 480MHz Cortex-M7, 1MB SRAM, WiFi |
| RetroShield Z80 | Real Zilog Z80 CPU on shield board, 5V logic |
| Level converter PCB | Custom shield with TXB0108 bidirectional level shifters (design files) |
| Sector server | Rust TCP server on local network (source) |
The TXB0108 level converters use auto-direction sensing, which fails for several Z80 bus signals:
- IORQ_N — stuck HIGH (never toggles)
- RD_N — stuck HIGH (never toggles)
- WR_N — works for memory cycles only, not I/O
- Data bus — Z80-to-Arduino direction invisible during I/O
These failures required a fundamentally different approach: instead of passively reading bus signals, the Arduino actively decodes the Z80's instruction stream and maintains software copies of the CPU's internal registers.
Each Z80 instruction takes a known number of T-states. A lookup table (tStates[256]) provides the count for every opcode. After detecting one M1 (opcode fetch), a guard timer prevents false M1 detection until the instruction completes. The next MREQ-active memory read after the guard expires is the next M1.
A software copy of the Z80's program counter, updated at each M1 by analyzing the opcode and computing the next address. Immune to the TXB0108's address bus propagation delay.
Software copies of A, B, C, D, E, H, L, F, and SP, updated at M1 time by decoding each opcode from z80RAM[softPC]. Used for:
- I/O data —
OUTinstructions need the A register value, which can't be read from the bus - Branch prediction — conditional branches need flag values to determine the taken/not-taken path
- Pre-writes — memory store instructions use shadow register values to write correct data to
z80RAM
Since bus writes go to wrong addresses (TXB0108 propagation delay), all memory stores are handled in software at M1 detection time. Read-modify-write instructions (INC/DEC (HL), CB-prefix on (HL)) use deferred writes to avoid double-applying operations.
All hot-path I/O uses direct STM32H747 GPIO register access (BSRR, IDR, MODER) instead of Arduino HAL digitalRead()/digitalWrite(). This, combined with removing unnecessary pinMode() calls and I-cache optimization, achieves ~690,000 Z80 cycles/sec (~0.69 MHz effective clock).
| File | Description |
|---|---|
kz80_cpm_giga.ino |
Main Arduino sketch (~2,600 lines) |
boot.bin |
Z80 boot loader binary (331 bytes) — loads CPM.SYS via sector server |
CPM.SYS |
CP/M 2.2 system image: CCP + BDOS + BIOS (6,607 bytes) |
A.DSK |
CP/M disk image for drive A (256 KB) — contains ZORK1, ZORK2 |
The RetroShield uses the same physical pin positions on the Giga as on the Mega 2560. The direct GPIO register access requires knowing the STM32H747 port/pin for each Arduino pin:
| Function | Arduino Pin | STM32 Port/Pin |
|---|---|---|
| CLK | D52 | PK2 |
| MREQ_N | D41 | PK7 |
| WR_N | D40 | PE6 |
| IORQ_N | D39 | PI14 |
| INT_N | D50 | PI11 |
| RESET_N | D38 | PJ7 |
| Data D0–D7 | D49–D42 | PE4, PK0, PB2, PH15, PI13, PG10, PI10, PI15 |
| Addr A0–A7 | D22–D29 | PJ12, PG13, PG12, PJ0, PJ14, PJ1, PJ15, PJ2 |
| Addr A8–A15 | D37–D30 | PJ6, PK6, PJ5, PK5, PJ4, PK4, PJ3, PK3 |
0000-00FF Page Zero (jump vectors, FCBs, command buffer)
0100-DFFF TPA (Transient Program Area) — 56KB
E000-E7FF CCP (Console Command Processor)
E800-F5FF BDOS (Basic Disk Operating System)
F600-FFFF BIOS (Basic I/O System)
| Port | Function |
|---|---|
| 0x80 | MC6850 ACIA control/status (console serial) |
| 0x81 | MC6850 ACIA data (console TX/RX) |
| 0x10 | Disk command register |
| 0x11 | Disk status register |
| 0x12 | Disk data byte |
| 0x13 | Disk filename (write char by char, null-terminated) |
| 0x14–0x15 | Disk seek position (low, mid bytes) |
| 0x16–0x17 | DMA address (low, high bytes) |
| 0x18 | Block command (0=read, 1=write) |
| 0x19 | Seek position high byte |
- Arduino IDE or
arduino-cliwith thearduino:mbed_gigaboard package installed - A RetroShield Z80 with a level converter board
- A machine on the same WiFi network running the sector server
Edit the WiFi credentials and server IP at the top of kz80_cpm_giga.ino:
const char* WIFI_SSID = "YourNetwork";
const char* WIFI_PASS = "YourPassword";
IPAddress SERVER_IP(192, 168, 0, 248);
const int SERVER_PORT = 9000;# Using arduino-cli
arduino-cli compile --fqbn arduino:mbed_giga:giga kz80_cpm_giga.ino
arduino-cli upload -p /dev/cu.usbmodem2101 --fqbn arduino:mbed_giga:giga kz80_cpm_giga.ino
# Serial monitor
screen /dev/cu.usbmodem2101 115200Or open kz80_cpm_giga.ino in the Arduino IDE, select Arduino Giga R1 WiFi as the board, and click Upload.
-
Start the sector server on your host machine, pointing it at a directory containing
boot.bin,CPM.SYS, and disk images:sector_server ./cpm_files 9000
-
Upload the sketch to the Giga and open a serial terminal at 115200 baud.
-
The Giga connects to WiFi, connects to the sector server, loads the boot loader, and starts the Z80. You should see:
RetroShield Z80 Boot Loader Copyright (c) 2025 Alex Jokela, tinycomputers.io Loading CPM.SYS..................................................... Boot complete. RetroShield CP/M 2.2 56K TPA a> -
Type
dirto see disk contents, orzork1to play Zork I.
This project is documented in a three-part series on tinycomputers.io:
- My Experience Using Fiverr for Custom PCB Design: A $468 Arduino Giga Shield
- Porting CP/M to the Arduino Giga R1: When Level Converters Fight Back
- Playing Zork on a Real Z80: From CP/M Boot to the Great Underground Empire
The Arduino sketch (kz80_cpm_giga.ino) is licensed under the BSD 3-Clause License. See LICENSE.
The CP/M 2.2 system files (CPM.SYS, boot.bin) and disk image (A.DSK) contain software originally developed by Digital Research, Inc. CP/M was released under its own license by Lifeboat Associates and later placed into free distribution by Tim Olmstead and the Unofficial CP/M Web Site. These files are included for convenience and are not covered by the BSD license.
Alex Jokela — tinycomputers.io