Browse by type
A full port of the BasiliskII Macintosh 68k emulator to the ESP32-P4, bringing classic Mac OS (System 7.x through Mac OS 8.1) to portable embedded devices with touchscreen and USB peripheral support.
Two boards are currently supported from the same source tree:
| Board | Display | Mac screen | PlatformIO env |
|---|---|---|---|
| M5Stack Tab5 | 5" 1280x720 | 640x360 @ 2x | esp32p4_pioarduino |
| Waveshare ESP32-P4-WIFI6-Touch-LCD-10.1 | 10.1" 800x1280 (rotated to 1280x800 landscape) | 640x400 @ 2x | waveshare_p4_101 |
Both variants share the BasiliskII core, video pipeline, USB HID handling, and boot GUI. Per-board drivers live behind a thin HAL in src/board/; see docs/waveshare/README.md for the Waveshare-specific pin map and notes.

Flying Toasters running smoothly with write-time dirty tracking and tile-based rendering — the three-finger on-screen keyboard lives one gesture away



Browsing the web on System 7 via the built-in WiFi networking
(even, even) sub-pixel of each block, giving exact
25% coverage so the Mac content shows through the other three
sub-pixels for free. Held keys overlay a 50% checker of black for a
pressed look; latched modifiers use the opposite checker so you can
tell at a glance which Shift state is active.55.03.35 (IDF 5.5.1) to avoid a
MIPI-DSI backlight flicker that hit M5GFX in IDF 5.5.2; Waveshare
moves to 55.03.38-1 (IDF 5.5.4) to pick up the newer esp_hosted
that no longer panics in sdio_rx_get_buffer under sustained SD I/O.


This project runs a Motorola 68040 emulator that can boot real Macintosh ROMs and run genuine classic Mac OS software. Performance is comparable to a Macintosh Quadra 610 (25 MHz 68040), achieving 24 FPS video and 2-3 MIPS CPU speed. The emulation includes:
The Tab5 features a unique dual-chip architecture that makes it ideal for this project:
| Chip | Role | Key Features |
|---|---|---|
| ESP32-P4 | Main Application Processor | 400MHz dual-core RISC-V, 32MB PSRAM, MIPI-DSI display |
| ESP32-C6 | Wireless Co-processor | WiFi 6, Bluetooth LE 5.0 — provides internet access to classic Mac OS |
| Component | Details |
|---|---|
| Display | 5" IPS TFT, 1280×720 (720p), MIPI-DSI interface |
| Touch | Capacitive multi-touch (ST7123 controller) |
| Memory | 32MB PSRAM for emulated Mac RAM + frame buffers |
| Storage | microSD card slot for ROM, disk images, and settings |
| USB | Type-A host port for keyboard/mouse, Type-C for programming |
| Audio | ES8388 DAC/ADC codec — classic Mac sound output |
| Battery | NP-F550 Li-ion (2000mAh) for portable operation |
See boardConfig.md for detailed pin mappings and hardware documentation.
The emulator leverages the ESP32-P4's dual-core RISC-V architecture for optimal performance:
┌─────────────────────────────────────────────────────────────────┐
│ ESP32-P4 (400MHz) │
├────────────────────────────┬────────────────────────────────────┤
│ CORE 0 │ CORE 1 │
│ (Video & I/O Core) │ (CPU Emulation Core) │
├────────────────────────────┼────────────────────────────────────┤
│ • Video rendering task │ • 68040 CPU interpreter │
│ • Double-buffered DMA │ • Fast-path memory access │
│ • 2×2 pixel scaling │ • Write-time dirty marking │
│ • Input task (60Hz) │ • Batch instruction execution │
│ • USB HID processing │ • ROM patching │
│ • Audio output (ES8388) │ • Disk I/O │
│ • Network RX polling │ │
│ • Event-driven @ 24 FPS │ │
└────────────────────────────┴────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────┐
│ 32MB PSRAM Allocation │
├──────────────────────────────────────────────────────────────┤
│ Mac RAM (4-16MB) │ Configurable via Boot GUI │
├────────────────────────────┼─────────────────────────────────┤
│ Mac ROM (~1MB) │ Q650.ROM or compatible │
├────────────────────────────┼─────────────────────────────────┤
│ Mac Frame Buffer (230KB) │ 640×360 @ 8-bit indexed color │
├────────────────────────────┼─────────────────────────────────┤
│ Display Buffer (1.8MB) │ 1280×720 @ RGB565 │
├────────────────────────────┼─────────────────────────────────┤
│ Free PSRAM │ Varies based on RAM selection │
└──────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────┐
│ Internal SRAM (Priority) │
├──────────────────────────────────────────────────────────────┤
│ CPU Function Table │ cpufunctbl - hot path lookup │
├────────────────────────────┼─────────────────────────────────┤
│ Memory Bank Pointers │ 256KB - memory banking │
├────────────────────────────┼─────────────────────────────────┤
│ Palette (512 bytes) │ 256 RGB565 entries │
├────────────────────────────┼─────────────────────────────────┤
│ Dirty Tile Bitmap │ 144 bits (write-time tracking) │
├────────────────────────────┼─────────────────────────────────┤
│ Tile Render Lock Bitmap │ 144 bits (race prevention) │
├────────────────────────────┼─────────────────────────────────┤
│ Double-Buffered Tile Bufs │ ~28KB (DMA pipelining) │
└──────────────────────────────────────────────────────────────┘
The video system uses a highly optimized pipeline with write-time dirty tracking to minimize CPU overhead:
┌─────────────────────────────────────────────────────────────────┐
│ Video Pipeline Architecture │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────┐ marks dirty ┌─────────────────────────┐ │
│ │ 68040 CPU │ ─────────────────▶│ Dirty Tile Bitmap │ │
│ │ (Core 1) │ │ (16×9 = 144 tiles) │ │
│ └──────────────┘ └─────────────────────────┘ │
│ │ │ │
│ │ writes │ read & clear │
│ ▼ ▼ │
│ ┌──────────────┐ ┌─────────────────────────┐ │
│ │ Mac Frame │ │ Video Task (Core 0) │ │
│ │ Buffer │ ─────────────────▶│ • Tile snapshot │ │
│ │ (640×360) │ read tiles │ • Palette lookup │ │
│ └──────────────┘ │ • 2×2 scaling │ │
│ └─────────────────────────┘ │
│ │ │
│ │ push tiles │
│ ▼ │
│ ┌─────────────────────────┐ │
│ │ MIPI-DSI Display │ │
│ │ (1280×720) │ │
│ └─────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
Write-Time Dirty Tracking: When the 68040 CPU writes to the framebuffer, the memory system immediately marks the affected tile(s) as dirty. This eliminates expensive per-frame comparisons.
Tile-Based Rendering: The screen is divided into a 16×9 grid of 40×40 pixel tiles (144 total). Only dirty tiles are re-rendered each frame, typically reducing video CPU time by 60-90%.
Double-Buffered DMA: Render to one buffer while DMA pushes another to the display. Both tile rendering and full-frame streaming use this pipelining for maximum throughput.
Per-Tile Render Locks: Atomic locks prevent race conditions during tile snapshot. If the CPU writes to a tile being rendered, it's automatically re-queued for the next frame—ensuring glitch-free display.
Multi-Depth Support: Supports 1/2/4/8-bit indexed color modes with packed pixel decoding. Mac OS can switch between depths via the Monitors control panel.
Event-Driven Refresh at 24 FPS: Cinema-standard frame rate with task notifications—the video task sleeps until signaled, reducing idle polling overhead.
This port includes the following BasiliskII subsystems, adapted for ESP32:
| Component | File(s) | Description |
|---|---|---|
| UAE CPU | uae_cpu/*.cpp |
Motorola 68040 interpreter |
| Memory | uae_cpu/memory.cpp |
Memory banking with write-time dirty tracking |
| ADB | adb.cpp |
Apple Desktop Bus for keyboard/mouse |
| Video | video_esp32.cpp |
Tile-based display driver with 2× scaling |
| Disk | disk.cpp, sys_esp32.cpp |
HDD image support via SD card |
| CD-ROM | cdrom.cpp |
ISO image mounting |
| XPRAM | xpram_esp32.cpp |
Non-volatile parameter RAM |
| **Timer |
$ claude mcp add M5Tab-Macintosh \
-- python -m otcore.mcp_server <graph>