lego-esp32s3-gameboy/README.md

596 lines
18 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 🎮 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 Emulator
- `gb_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:**
1. NFC-Tag scannen
2. UID auslesen
3. In `nfc_roms.json` nachschlagen
4. 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
```c
// 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
```c
// 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:**
1. Samples in Ring-Buffer schreiben
2. Bei Buffer voll: I2S schreiben
3. APU-Register auslesen für Kanal-Status
### 3. Display Zeilen-Rendering
```c
// Zeilen 463-530: LCD Zeilen-Callback
static void gb_lcd_draw_line(...)
```
**Funktion:** Wird 144× pro Frame aufgerufen (eine Zeile pro Aufruf)
**Scaling-Algorithmus:**
1. Vertikales Scaling: `y_base = (line * GB_RENDER_HEIGHT) / 144`
2. Horizontales Scaling: Jedes Pixel wird dynamisch auf 1-2 Output-Pixel gemapped
3. Byte-Swapping: RGB565 → BGR565 (für ST7789)
4. 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
```c
// 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:**
1. Nur GameBoy-Region kopieren (nicht schwarze Ränder)
2. Von 320×240 Buffer → 256×230 kompakter Buffer
3. 33% weniger SPI-Transfer!
4. Geschwindigkeit steigt von 20ms auf 16ms pro Frame
### 5. Main Loop
```c
// Zeilen 702-786: Hauptschleife
void app_main(void)
```
**Ablauf:**
1. Display init
2. SD-Card mount
3. PSRAM check & Buffer alloc
4. Audio init & Task start
5. Emulator init
6. Display Task start
7. 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
```bash
# ESP-IDF Environment laden
source ~/esp-idf/export.sh
# Projekt bauen
cd /home/duffy/Arduino/gameboy/gnuboy
idf.py build
```
### Flash
```bash
# 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.h` fü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`:
```c
// 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
```c
// 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?
```bash
# Clean & Rebuild
idf.py fullclean
idf.py build
```
### PSRAM nicht erkannt?
```bash
# Prüfen im Monitor
idf.py monitor
# Sollte zeigen: "PSRAM: 8191 KB total"
```
Falls nicht:
```bash
idf.py menuconfig
# → Component config → ESP32S3-Specific
# → [*] Support for external, SPI-connected RAM
# → Mode: Octal Mode PSRAM
# → Speed: 80MHz
```
### Zu langsam?
1. Display Scaling reduzieren (`GB_SCALE_FACTOR 1.4`)
2. Compiler-Optimierung prüfen (sollte `-O3` sein)
3. 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:**
1. ✅ Emulator läuft perfekt (90-111 FPS!)
2. ✅ Audio funktioniert (4 Kanäle)
3. ✅ Display-Scaling optimiert
4. ⏳ Button-Input implementieren
5. ⏳ ROM-Menü auf SD-Card
6. ⏳ NFC ROM-Auswahl
7. ⏳ Potentiometer Volume/Brightness
8. ⏳ Link Cable 2-Player
9. ⏳ Save-States
---
**Viel Spaß beim Zocken! 🎮🔊**
*Erstellt mit Liebe zum Detail ❤️*