Lego Gameboy mit Poti für Helligkeit und Volume Power Button (Sleep mode) Steuerkreuz A B und Select Start Tasten. Per NFC simulierter Catridge Tausch und Link Cable für zwei Gameboys Da ganze eingebaut im Offiziellen Lego Gameboy
Go to file
duffyduck 1675d59f54 buttons, power button pin 18, deep sleep implementation, dokumentaion, and decription. optimzed sd card and display init, remove bloink screens at start, added color screens for error notifications 2025-12-01 22:44:18 +01:00
components buttons, power button pin 18, deep sleep implementation, dokumentaion, and decription. optimzed sd card and display init, remove bloink screens at start, added color screens for error notifications 2025-12-01 22:44:18 +01:00
main buttons, power button pin 18, deep sleep implementation, dokumentaion, and decription. optimzed sd card and display init, remove bloink screens at start, added color screens for error notifications 2025-12-01 22:44:18 +01:00
resources display and gem lgic seperate cpu cores, audio fixed, display scaling implemented, psram aktivatet for threads in core 2025-12-01 01:09:49 +01:00
.gitignore display and gem lgic seperate cpu cores, audio fixed, display scaling implemented, psram aktivatet for threads in core 2025-12-01 01:09:49 +01:00
.gitmodules display and gem lgic seperate cpu cores, audio fixed, display scaling implemented, psram aktivatet for threads in core 2025-12-01 01:09:49 +01:00
CMakeLists.txt first commit 2025-11-23 11:46:56 +01:00
COPYING display and gem lgic seperate cpu cores, audio fixed, display scaling implemented, psram aktivatet for threads in core 2025-12-01 01:09:49 +01:00
MAIN_C_ERKLAERUNG.md buttons, power button pin 18, deep sleep implementation, dokumentaion, and decription. optimzed sd card and display init, remove bloink screens at start, added color screens for error notifications 2025-12-01 22:44:18 +01:00
Makefile display and gem lgic seperate cpu cores, audio fixed, display scaling implemented, psram aktivatet for threads in core 2025-12-01 01:09:49 +01:00
QUICKSTART.md first commit 2025-11-23 11:46:56 +01:00
README.md buttons, power button pin 18, deep sleep implementation, dokumentaion, and decription. optimzed sd card and display init, remove bloink screens at start, added color screens for error notifications 2025-12-01 22:44:18 +01:00
icon.png display and gem lgic seperate cpu cores, audio fixed, display scaling implemented, psram aktivatet for threads in core 2025-12-01 01:09:49 +01:00
icon.svg display and gem lgic seperate cpu cores, audio fixed, display scaling implemented, psram aktivatet for threads in core 2025-12-01 01:09:49 +01:00
partitions.csv first commit 2025-11-23 11:46:56 +01:00
sdkconfig display and gem lgic seperate cpu cores, audio fixed, display scaling implemented, psram aktivatet for threads in core 2025-12-01 01:09:49 +01:00
sdkconfig.defaults first commit 2025-11-23 11:46:56 +01:00

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 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

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

// 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:

  1. Samples in Ring-Buffer schreiben
  2. Bei Buffer voll: I2S schreiben
  3. 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:

  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

// 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

// 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

# 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.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:

// 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?

  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 ❤️