|
|
||
|---|---|---|
| components | ||
| main | ||
| resources | ||
| .gitignore | ||
| .gitmodules | ||
| CMakeLists.txt | ||
| COPYING | ||
| MAIN_C_ERKLAERUNG.md | ||
| Makefile | ||
| QUICKSTART.md | ||
| README.md | ||
| icon.png | ||
| icon.svg | ||
| partitions.csv | ||
| sdkconfig | ||
| sdkconfig.defaults | ||
README.md
🎮 ESP32-S3 GameBoy Emulator
Hochoptimierter GameBoy/GameBoy Color Emulator für Waveshare ESP32-S3-Touch-LCD-2
📋 Projekt-Übersicht
Dieses Projekt ist ein vollständig funktionsfähiger GameBoy Emulator, der auf dem Waveshare ESP32-S3-Touch-LCD-2 Board läuft. Es nutzt den Peanut-GB Emulator-Core und erreicht 90-111 FPS bei vielen Spielen - schneller als der Original GameBoy (59.73 FPS)!
✨ Fertige Features
- ✅ GameBoy Emulation (Peanut-GB Core)
- ✅ ST7789 Display (2.0", 320x240, 80 MHz SPI)
- ✅ Perfekter 4-Kanal Audio (I2S MAX98357A, 32768 Hz)
- ✅ SD Card ROM Loading (FAT32, .gb Dateien)
- ✅ PSRAM Optimization (8MB Octal Mode, Double-Buffering)
- ✅ Dynamisches Display-Scaling (1.0x bis 1.67x konfigurierbar)
- ✅ 90-111 FPS Performance (besser als Original!)
🚧 Geplante Features
- ⏳ 8 GameBoy Buttons (GPIO 8-14, 21 - Hardware fertig, Software TODO)
- ⏳ NFC ROM-Auswahl (PN532 I2C - Hardware fertig, Software TODO)
- ⏳ Potentiometer Controls (Volume & Brightness - Hardware fertig, Software TODO)
- ⏳ Link Cable 2-Player (GPIO 2, 15, 17 - Hardware fertig, Software TODO)
🏗️ Programmablauf & Architektur
Systemarchitektur
┌─────────────────────────────────────────────────────────────┐
│ ESP32-S3 Dual Core │
├──────────────────────────────┬──────────────────────────────┤
│ CORE 1 │ CORE 0 │
│ (Emulation Task) │ (Display Task) │
│ │ │
│ ┌────────────────────┐ │ ┌────────────────────┐ │
│ │ GameBoy Emulator │ │ │ Display Rendering │ │
│ │ (Peanut-GB) │ │ │ (ST7789 SPI) │ │
│ │ │ │ │ │ │
│ │ - CPU Emulation │ │ │ - Byte Swapping │ │
│ │ - PPU Rendering │ │ │ - SPI Transfer │ │
│ │ - APU Audio │◄─────┼──┤ - Compact Buffer │ │
│ │ - Memory Map │ │ │ │ │
│ └────────────────────┘ │ └────────────────────┘ │
│ │ │ ▲ │
│ ▼ │ │ │
│ ┌────────────────────┐ │ ┌────────────────────┐ │
│ │ Render Buffer │──────┼─►│ Display Buffer │ │
│ │ (PSRAM 150KB) │ Swap │ │ (PSRAM 150KB) │ │
│ └────────────────────┘ │ └────────────────────┘ │
│ │ │
│ ┌────────────────────┐ │ │
│ │ Audio Task │ │ │
│ │ (I2S Output) │ │ │
│ └────────────────────┘ │ │
└──────────────────────────────┴──────────────────────────────┘
│ │
▼ ▼
┌─────────────────┐ ┌─────────────────┐
│ I2S Audio │ │ SPI Display │
│ (MAX98357A) │ │ (ST7789) │
└─────────────────┘ └─────────────────┘
Programmablauf beim Start
1. app_main() startet
│
├─► ST7789 Display initialisieren (80 MHz SPI)
│ └─► Backlight auf 80% setzen
│
├─► SD-Karte mounten (FAT32)
│ └─► ROM laden: /tetris.gb
│
├─► PSRAM prüfen (8MB Octal Mode)
│ ├─► Render Buffer allokieren (150 KB)
│ └─► Display Buffer allokieren (150 KB)
│
├─► I2S Audio initialisieren (32768 Hz, 16-bit)
│ └─► Audio Task starten (Core 1, Priority 5)
│
├─► Peanut-GB Emulator initialisieren
│ ├─► ROM laden
│ ├─► Callbacks registrieren:
│ │ ├─► gb_lcd_draw_line() - Zeile rendern
│ │ └─► audio_callback() - Audio-Sample
│ └─► GameBoy-Palette setzen (DMG Grün)
│
├─► Display Task erstellen (Core 0, Priority 10)
│ └─► Wartet auf frame_ready_sem
│
└─► Emulation Loop starten (Main Loop)
│
└─► Für immer:
├─► gb_run_frame() - Emuliert 1 Frame
│ ├─► CPU läuft (70224 Takte)
│ ├─► PPU rendert Zeilen → gb_lcd_draw_line()
│ └─► APU erzeugt Audio → audio_callback()
│
├─► Buffer Swap (Render ↔ Display)
│ └─► frame_ready_sem freigeben
│
├─► Auf frame_done_sem warten
│ └─► Display Task hat gerendert
│
└─► FPS berechnen & ausgeben (alle 60 Frames)
Double-Buffering Synchronisation
EMULATION TASK (Core 1) DISPLAY TASK (Core 0)
───────────────────── ────────────────────
┌─ Frame N rendern ┌─ Wartet auf Semaphore
│ in Render Buffer │ (frame_ready_sem)
│ │
│ gb_run_frame() │
│ └─► Zeilen in │
│ render_buffer │
│ schreiben │
│ │
└─ Frame fertig │
│
Buffer Swap: │
render_buffer ↔ │
display_buffer │
│
frame_ready_sem │
freigeben ──────────────────► └─ Semaphore empfangen!
Warte auf ┌─ Display buffer kopieren
frame_done_sem │ zu compact_buffer
◄──────────────────────────── │ (nur GameBoy-Region)
│
├─ SPI Transfer
│ st7789_draw_buffer_
│ preswapped()
│
└─ Fertig!
frame_done_sem
freigeben
frame_done_sem empfangen ◄────
┌─ Nächstes Frame ┌─ Wartet wieder...
│ rendern │
🧩 Component-Beschreibung
1. Peanut-GB Component
Pfad: components/peanut-gb/
Funktion: Kern des GameBoy-Emulators. Header-only Bibliothek für GameBoy CPU/PPU/APU Emulation.
Wichtige Funktionen:
gb_init()- Initialisiert den Emulatorgb_run_frame()- Emuliert 1 GameBoy Frame (70224 CPU-Takte = 16.7ms)gb_lcd_draw_line()- Callback für jede gerenderte Bildschirmzeile
Optimierung: Kompiliert mit -O3 Flag für maximale Performance
2. ST7789 Display Driver Component
Pfad: components/st7789/
Funktion: Treiber für ST7789 TFT-Display (320×240 Pixel, RGB565 Farbformat)
Wichtige Funktionen:
st7789_init()- Display initialisieren (80 MHz SPI)st7789_draw_buffer_preswapped()- Optimierter Transfer (vorher RGB→BGR gewandelt)st7789_set_backlight()- Hintergrundbeleuchtung steuern (0-100%)
Besonderheit:
- Chunked SPI Transfer (max 32KB pro Transfer wegen DMA-Limit)
- Byte-Swapping wird im Emulator gemacht (RGB→BGR), nicht im Treiber
3. Link Cable Component
Pfad: components/link_cable/
Status: Hardware fertig, Software TODO
Funktion: 2-Player Multiplayer über GPIO (serieller Transfer)
Geplante Nutzung:
- Pokemon-Tausch
- Tetris 2-Player
- Andere Multiplayer-Games
4. NFC Manager Component
Pfad: components/nfc_manager/
Status: Hardware fertig (PN532 auf I2C), Software TODO
Funktion: ROM-Auswahl per NFC-Tag scannen
Geplante Funktionsweise:
- NFC-Tag scannen
- UID auslesen
- In
nfc_roms.jsonnachschlagen - Entsprechende ROM laden
5. Potentiometer Manager Component
Pfad: components/potentiometer_manager/
Status: Hardware fertig (ADC GPIO 3, 4), Software TODO
Funktion: Analoge Steuerung für Volume und Brightness
Geplante Nutzung:
- Poti 1 (GPIO 3): Lautstärke (0-100%)
- Poti 2 (GPIO 4): Helligkeit (10-100%)
6. Minizip Component
Pfad: components/minizip/
Funktion: ZIP-Dekompression für .zip ROM-Dateien
Status: Eingebunden, aber aktuell nicht genutzt (ROMs sind .gb, nicht gezippt)
7. Zlib Component
Pfad: components/zlib/
Funktion: Compression-Bibliothek (für Minizip und Save-States)
Status: Eingebunden als Dependency für Minizip
🔧 Wichtige Code-Bereiche in main.c
1. GameBoy Palette
// Zeilen 417-422: DMG Grün-Palette
static const uint16_t gb_palette[4] = {
0xFFFF, // Weiß (Hintergrund)
0xAD55, // Hellgrün
0x52AA, // Mittelgrün
0x0000 // Schwarz (Vordergrund)
};
Erklärung: GameBoy hat 4 Graustufen (2-bit), hier als RGB565-Werte definiert.
2. Audio Callback
// Zeilen 424-461: Audio-Sample Callback
void audio_callback(struct gb_s *gb, uint16_t left, uint16_t right)
Funktion: Wird von Peanut-GB für jedes Audio-Sample aufgerufen (32768 Hz).
Ablauf:
- Samples in Ring-Buffer schreiben
- Bei Buffer voll: I2S schreiben
- APU-Register auslesen für Kanal-Status
3. Display Zeilen-Rendering
// Zeilen 463-530: LCD Zeilen-Callback
static void gb_lcd_draw_line(...)
Funktion: Wird 144× pro Frame aufgerufen (eine Zeile pro Aufruf)
Scaling-Algorithmus:
- Vertikales Scaling:
y_base = (line * GB_RENDER_HEIGHT) / 144 - Horizontales Scaling: Jedes Pixel wird dynamisch auf 1-2 Output-Pixel gemapped
- Byte-Swapping: RGB565 → BGR565 (für ST7789)
- Pixel-Width-Berechnung verhindert Lücken im Bild
Beispiel bei Scale 1.6:
- GameBoy Zeile 0 → Display Y = 0
- GameBoy Zeile 10 → Display Y = 16 (10 * 1.6 = 16)
- Einige Zeilen werden dupliziert für gleichmäßige Skalierung
4. Display Task
// Zeilen 610-653: Display Rendering Task
static void display_task(void *arg)
Funktion: Läuft auf Core 0, rendert fertigen Frame zum Display
Optimierung - Compact Buffer:
- Nur GameBoy-Region kopieren (nicht schwarze Ränder)
- Von 320×240 Buffer → 256×230 kompakter Buffer
- 33% weniger SPI-Transfer!
- Geschwindigkeit steigt von 20ms auf 16ms pro Frame
5. Main Loop
// Zeilen 702-786: Hauptschleife
void app_main(void)
Ablauf:
- Display init
- SD-Card mount
- PSRAM check & Buffer alloc
- Audio init & Task start
- Emulator init
- Display Task start
- Unendliche Emulations-Loop
🎯 Performance-Optimierungen
1. PSRAM Double-Buffering
Problem: SPI-Transfer (10ms) blockiert Emulation Lösung: 2 Buffer in PSRAM, parallel arbeiten
Ergebnis:
- Core 0: Display rendering (10ms)
- Core 1: Emulation (10ms)
- Parallel = 50% schneller!
2. Display Scaling
Problem: Fullscreen (320×240) zu langsam (20ms) Lösung: Kleinere Auflösung mit schwarzen Rändern
Ergebnis:
- Scale 1.0: 160×144 = 83 FPS (sehr klein)
- Scale 1.5: 240×216 = 90-100 FPS (gut)
- Scale 1.6: 256×230 = 60-90 FPS (beste Balance)
- Scale 1.67: 267×240 = 55-70 FPS (volle Höhe)
3. Compact Buffer
Problem: 320×240 Buffer mit vielen schwarzen Pixeln Lösung: Nur GameBoy-Region transferieren
Ergebnis:
- Vorher: 153.600 Bytes SPI-Transfer
- Nachher: 117.760 Bytes (23% weniger!)
- Speedup: 3-4ms pro Frame
4. Compiler Optimierung
Problem: Emulator zu langsam (45ms pro Frame)
Lösung: -O3 Compiler-Flag für Peanut-GB
Ergebnis:
- -O2: 22-25ms
- -O3: 16-19ms
- 40% schneller!
5. SPI Clock Speed
Problem: Display-Transfer Bottleneck Lösung: 80 MHz SPI (Maximum für ST7789)
Ergebnis:
- 40 MHz: 28ms pro Frame
- 80 MHz: 16ms pro Frame
- Fast doppelt so schnell!
📊 Performance-Tabelle
| Game | Scale | FPS | Frame Time | Audio |
|---|---|---|---|---|
| Tetris | 1.5 | 60-70 | 14-16ms | ✅ |
| DuckTales | 1.5 | 90-111 | 9-11ms | ✅ |
| Pokemon | 1.6 | 55-65 | 15-18ms | ✅ |
| Super Mario | 1.6 | 60-75 | 13-16ms | ✅ |
| Original GB | - | 59.73 | 16.7ms | ✅ |
Fazit: Bei vielen Spielen schneller als Original GameBoy!
🚀 Schnellstart - Build & Flash
Voraussetzungen
- ESP-IDF v4.4 installiert
- Python 3.10 (pyenv empfohlen)
- Git
Build
# ESP-IDF Environment laden
source ~/esp-idf/export.sh
# Projekt bauen
cd /home/duffy/Arduino/gameboy/gnuboy
idf.py build
Flash
# Flashen
idf.py -p /dev/ttyUSB0 flash
# Mit Monitor
idf.py -p /dev/ttyUSB0 flash monitor
🛠️ Hardware
Hauptkomponenten
| Component | Model | Notes |
|---|---|---|
| MCU Board | Waveshare ESP32-S3-Touch-LCD-2 | 16MB Flash, 8MB PSRAM |
| Display | ST7789 2.0" | 320×240, integriert |
| Audio | MAX98357A | I2S Amplifier |
| Storage | MicroSD Card | FAT32, via SPI |
Pin-Belegung
Siehe
main/include/hardware_config.hfür alle Pin-Definitionen!
Display (ST7789 SPI):
- MOSI: GPIO 38, SCLK: GPIO 39, CS: GPIO 45
- DC: GPIO 42, BCKL: GPIO 1
Audio (I2S):
- BCLK: GPIO 48, LRC: GPIO 47, DIN: GPIO 16
Buttons (TODO):
- UP: 8, DOWN: 9, LEFT: 10, RIGHT: 11
- A: 12, B: 13, START: 14, SELECT: 21
NFC (I2C, TODO):
- SCL: GPIO 6, SDA: GPIO 5 (shared mit Touch)
Link Cable (TODO):
- SCLK: GPIO 15, SOUT: GPIO 2, SIN: GPIO 17
Potentiometer (ADC, TODO):
- Volume: GPIO 3, Brightness: GPIO 4
📦 Projekt-Struktur
gnuboy/
├── CMakeLists.txt # Root CMake
├── sdkconfig # ESP-IDF Konfiguration
├── README.md # Diese Datei
│
├── main/
│ ├── CMakeLists.txt
│ ├── main.c # Hauptprogramm (ausführlich kommentiert)
│ └── include/
│ └── hardware_config.h # Pin-Definitionen & Scaling
│
└── components/
├── peanut-gb/ # GameBoy Emulator Core (-O3)
├── st7789/ # Display Driver (80 MHz SPI)
├── minizip/ # ZIP Support
├── zlib/ # Compression
├── link_cable/ # 2-Player (TODO)
├── nfc_manager/ # NFC ROM Selection (TODO)
└── potentiometer_manager/ # Volume/Brightness (TODO)
🔧 Konfiguration
Display Scaling ändern
In main/include/hardware_config.h:
// Zeile 54: Scaling-Faktor anpassen
#define GB_SCALE_FACTOR 1.6 // Ändern auf 1.4, 1.5, 1.67, etc.
Empfohlene Werte:
1.4- Klein, sehr schnell (>100 FPS)1.5- Gut, schnell (90-100 FPS)1.6- Beste Balance (60-90 FPS) ⭐1.67- Volle Höhe (55-70 FPS)
Fullscreen aktivieren
// Zeile 41: Scaling deaktivieren
#define GB_PIXEL_PERFECT_SCALING 0 // Fullscreen 320×240
Warnung: Fullscreen ist langsamer (~50 FPS bei vielen Spielen)
🐛 Troubleshooting
Build Fehler?
# Clean & Rebuild
idf.py fullclean
idf.py build
PSRAM nicht erkannt?
# Prüfen im Monitor
idf.py monitor
# Sollte zeigen: "PSRAM: 8191 KB total"
Falls nicht:
idf.py menuconfig
# → Component config → ESP32S3-Specific
# → [*] Support for external, SPI-connected RAM
# → Mode: Octal Mode PSRAM
# → Speed: 80MHz
Zu langsam?
- Display Scaling reduzieren (
GB_SCALE_FACTOR 1.4) - Compiler-Optimierung prüfen (sollte
-O3sein) - SPI-Speed prüfen (sollte 80 MHz sein)
Audio knackt?
- Normal bei sehr langsamen Spielen (<40 FPS)
- Bei >50 FPS sollte Audio perfekt sein
📝 Lizenz
- Peanut-GB: MIT License
- Projekt-spezifischer Code: MIT
- Components: Siehe jeweilige LICENSE-Dateien
🙏 Credits
- Peanut-GB: Delta (Header-only GameBoy Emulator)
- Waveshare: Hardware Board ESP32-S3-Touch-LCD-2
- Espressif: ESP-IDF Framework
- Duffy: Dieses LEGO GameBoy Projekt! 🎮
🎯 Roadmap
Nächste Schritte:
- ✅ Emulator läuft perfekt (90-111 FPS!)
- ✅ Audio funktioniert (4 Kanäle)
- ✅ Display-Scaling optimiert
- ⏳ Button-Input implementieren
- ⏳ ROM-Menü auf SD-Card
- ⏳ NFC ROM-Auswahl
- ⏳ Potentiometer Volume/Brightness
- ⏳ Link Cable 2-Player
- ⏳ Save-States
Viel Spaß beim Zocken! 🎮🔊
Erstellt mit Liebe zum Detail ❤️