Multi-game touchscreen arcade machine for the ESP32-2432S028 (Cheap Yellow Display).
Built with LVGL 8, TFT_eSPI, PlatformIO, and ElegantOTA.
- Board: ESP32-2432S028 (CYD) — ESP32-WROOM-32, 4MB flash
- Display: 2.8" ILI9341 TFT, 320x240, resistive touchscreen (XPT2046)
- Extras: RGB LED, passive piezo buzzer (GPIO 22), light sensor, SD card slot
For an ASCII rear-view diagram and how to power the board via the S1 / S3 solder pads (alternative to USB), see Hardware: Power & Connections.
| Game / App | Players | Network | Description |
|---|---|---|---|
| Battleship | 1-2P | Yes | 8x8 grid, drag/rotate ships, fire to sink fleet, vs CPU (hunt/target AI)/local/network |
| Clock | - | - | Clock (with Islamic date), Timer, Stopwatch, Alarm, Seattle Weather (7-day forecast), Calendar (Gregorian + Hijri month grid) |
| Pong | 1-2P | Yes | Touch paddle, vs CPU or network, first to 10 |
| Connect 4 | 1-2P | Yes | vs CPU (minimax w/ alpha-beta pruning), local, or network, 4-direction win check |
| Memory Match | 1-2P | Yes | Card matching with 6 pairs, solo/local/network |
| Checkers | 1-2P | Yes | vs CPU, full rules with kings and forced jumps |
| Chess | 1-2P | Yes | vs CPU, Unicode piece symbols, full rules, board auto-flips so your pieces are always closest in network games |
| Anagram | 1P | - | Unscramble words, 20s timer, 10 rounds, 80+ words |
| Dots & Boxes | 2P | Yes | Claim boxes by completing lines, 5x5 dot grid (4x4 boxes) |
| Whack-a-Mole | 1P | - | Whack brown moles, spare baby faces, POW effects, 30s |
| Cup Pong | 1P | - | Bounce ball off table into 10 red cups, 10 throws |
| Sudoku | 1P | - | 9x9 puzzle, number pad, check box, Done validates with error highlights, elapsed timer |
| Pictionary | 2P | Yes | Draw & guess, 6 colors, 30s timer, local or network with live drawing sync |
- LVGL UI — Dark-themed interface with animated screen transitions
- OTA Updates — Custom web UI at
http://<IP>/updateshowing device info, firmware version, and partition status - Remote Diagnostics —
GET http://<IP>/debugreturns reset reason (panic/watchdog/brownout/normal), free heap, and a crash-trace breadcrumb (last build checkpoint + LVGL memory stats), readable without a USB connection - Dual OTA Partitions — app0/app1 alternating, with automatic rollback protection
- Network Multiplayer — Works over WiFi (UDP) or ESP-NOW (no WiFi needed), invite/accept lobby system with automatic retry + acknowledgement (dropped invite/accept packets no longer strand one side), heartbeat + move-counter resync auto-heals dropped moves in turn-based games
- Play Again — Every game's game-over screen offers a one-tap rematch alongside the return-to-menu option
- ESP-NOW — Peer-to-peer multiplayer without WiFi infrastructure, automatic fallback when WiFi is unavailable
- NTP Clock — Current date/time (Pacific) displayed on the main menu when WiFi is connected
- Sound Effects — Piezo buzzer feedback for moves, opponent moves, wins, losses, and startup
- Persistent Settings — Brightness, dark mode, sound, device name, and Hijri date offset saved to NVS across power cycles
- Settings Screen — Device name editor, brightness slider, Dark Mode toggle, Sound on/off, WiFi on/off switch, WiFi Setup (on-screen SSID/password entry), Hijri date adjustment (±2 days for moonsighting), IP, MAC, RSSI, firmware version, partition, heap, uptime, OTA URL
# Build
pio run
# Flash via USB
pio run -t upload
# Serial monitor
pio device monitoresptool --chip esp32 --port /dev/cu.usbserial-110 write_flash \
0x1000 .pio/build/esp32-2432S028/bootloader.bin \
0x8000 .pio/build/esp32-2432S028/partitions.bin \
0xe000 /Users/arizvi/.platformio/packages/framework-arduinoespressif32/tools/partitions/boot_app0.bin \
0x10000 .pio/build/esp32-2432S028/firmware.bin# CLI upload
./ota-upload.sh <DEVICE_IP>
# Or open in browser
http://<DEVICE_IP>/updatecurl http://<DEVICE_IP>/debugReturns firmware version, last reset reason (useful for spotting crashes/watchdog resets/brownouts), free heap, and a crash-trace breadcrumb — no USB connection needed.
Credentials are stored in NVS and can be entered directly on the device:
- Open Settings → WiFi Setup to set the SSID and password using the on-screen keyboard. Use Test to attempt a connection and Save to persist (auto-reconnects).
- On first boot with no credentials, the WiFi Setup screen launches automatically.
Optionally, you can hard-code defaults at build time by creating include/secrets.h (gitignored):
#pragma once
#define WIFI_SSID "YourSSID"
#define WIFI_PASSWORD "YourPassword"secrets.h is optional — if absent, the device falls back to NVS-stored credentials.
If WiFi is unavailable or disabled in Settings, multiplayer automatically uses ESP-NOW instead.
├── platformio.ini # Build config, TFT_eSPI pins, library deps
├── partitions.csv # Dual OTA partition layout
├── lv_conf.h # LVGL configuration
├── ota-upload.sh # CLI OTA upload script
├── include/
│ ├── config.h # Pin definitions and constants
│ └── secrets.h # WiFi credentials (gitignored)
├── src/
│ ├── main.cpp # Setup/loop orchestration
│ ├── hal/ # Display, backlight, LED, audio, sound effects, preferences
│ ├── net/ # WiFi, OTA, UDP/ESP-NOW discovery
│ ├── ui/ # Screen manager, menu, settings, shared styles
│ ├── apps/ # Clock (Timer, Stopwatch, Alarm, Calendar)
│ ├── utils/ # Global alert state/overlay, crash-trace breadcrumbs
│ └── games/ # Battleship, Memory, Pong, Connect 4,
│ # Checkers, Chess, Anagram, Dots & Boxes,
│ # Whack-a-Mole, Cup Pong, Sudoku, Pictionary
Two-player games support two transport modes:
- WiFi (UDP) — Broadcast discovery on the local network (port 4328)
- ESP-NOW — Direct peer-to-peer, no WiFi network needed, ~200m range
The transport is selected automatically at boot based on WiFi connectivity, or manually via the WiFi toggle in Settings.
- Both devices select "Network" from the game menu
- Peers appear in the lobby list within seconds
- Tap a peer to send an invite
- Other device sees an Accept/Decline popup
- Game starts once invite is accepted
Invite and accept messages are retried automatically (every ~350ms, up to ~5s) until acknowledged, so a single dropped WiFi/ESP-NOW packet no longer leaves one device stuck in the lobby while the other has already started.
Each game syncs state independently — turn-based games send moves, Pong syncs ball/paddle positions at 20fps, Memory Match syncs the card layout and flips.
For technical details, see the design documents:
- ESP-NOW Transport Design — Transport layer, discovery protocol, peer management, packet formats
- Game Network Sync Design — Per-game sync patterns, JSON message formats, reliability strategies
- LVGL + ESP32 Lessons Learned — Custom fonts, text rendering, touch handling, layout, scrolling, drawing, ArduinoJson gotchas
Set a custom device name in Settings (persisted across reboots). This name appears in lobby lists and invite dialogs instead of the default MAC-based CYD-XXYY.