Skip to content

darrrgghh/NEESP

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

5 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

NEESP

Two NEESP handhelds running NES side by side — wireless 2-player multiplayer

NES + ESP = NEESP — a handheld NES emulator for ESP32, with wireless two-player multiplayer over ESP-NOW. No router, no accounts: two consoles, one couch (or workbench).

Built on the excellent DSN-Nes-Emulator-Universal / Nofrendo stack, heavily adapted for real hardware: ST7789 display, I2S audio, LittleFS game storage, and a custom network session for 1P/2P play.

Repo github.com/darrrgghh/NEESP
Flash guide docs/FLASHING.md
Pinouts docs/HARDWARE.md
Controls Controls (this file)
Multiplayer docs/MULTIPLAYER.md

Why “50 in 1”?

On the main handheld (ESP32-S3 N16R8), the flash partition gives you about 13.4 MB of LittleFS for ROMs — enough for on the order of ~60 small .nes files if you optimize the set.

The menu title “NEESP 50 in 1” is simply the curated pack I liked: I only kept 50 games that ran well on this hardware. You can change that name and the game list anytime — drop your own ROMs into games/ and, if you want a different banner, edit one string in firmware/src/osd.cpp (MAX_GAMES is 50 in the same file for the menu list).

This repository does not ship copyrighted ROMs. See games/README.md.


What makes NEESP special

  • Two-player multiplayer (ESP-NOW) — host = player 1, client = player 2; inputs synced per frame; pause sync; host can approve or force game launch.
  • ROM pull — if player 2 is missing a game, the host can send it over the air into LittleFS (handy on the small-flash V3 build).
  • All-in-one handheld — ESP32-S3, 2.0" ST7789 320×240, MAX98357 I2S speaker, GPIO gamepad, many games in internal flash (no SD card in firmware).
  • Three hardware targets — two orientations of the same S3 build (V1 / V2) plus a breadboard ESP32 DevKit variant (V3).
  • Save states — SNSS slots on LittleFS under /littlefs/saves/ - easily save your states in all games, load and delete them (max 5 saves per game).

Not in scope (yet): 3+ players, online play over the internet.


Gallery

V1 — profile 1 (default landscape)

Assembled handheld, then the same unit opened up.

NEESP V1 assembled NEESP V1 disassembled

V2 — profile 2 (same wiring, flipped screen)

V2 is the same PCB and wires as V1. Only the display rotation (180°) and D-pad GPIO mapping change so the shell mounting direction still feels right.

NEESP V2 assembled NEESP V2 disassembled

V3 — ESP32 DevKit breadboard

Smaller flash, analog joystick, five ROMs baked in by default — great for experiments and multiplayer with ROM pull from a full S3 host.

NEESP V3 assembled NEESP V3 disassembled


Hardware at a glance

ESP32-S3 handheld — shared by V1 and V2

MCU ESP32-S3, 16 MB flash, 8 MB OPI PSRAM
Display 2.0" SPI ST7789 (GMT020 class), landscape 320×240
Audio MAX98357A (I2S)
Input 8× tactile buttons (pull-up, active low to GND)
Storage LittleFS (~13.4 MB for ROMs) — no microSD in firmware
PlatformIO esp32-s3-n16r8

V1 vs V2 — only these differ:

V1 V2
HARDWARE_PROFILE 1 2
Screen rotation DISPLAY_TFT_ROTATION = 3 = 1 (180°)
D-pad GPIO profile 1 table remapped (vertical ↔ horizontal, up ↔ down)
A / B / START / SELECT same pins same pins
TFT / I2S wiring same same

Full pin tables: docs/HARDWARE.md.

V3 — ESP32 DevKit (delta only)

MCU Classic ESP32 DevKit (~4 MB flash)
Display ST7789, VSPI pinout (see V3 section in hardware doc)
Input A/B/Start/Select on GPIO + ADC joystick for D-pad
ROMs in image First 5 .nes files (alphabetical); more via multiplayer ROM pull
PlatformIO esp32d-v3 (HARDWARE_PROFILE=3)

Which firmware should I flash?

flowchart TD
  start[What board do you have?]
  start -->|ESP32-S3 N16R8 handheld| s3[Environment esp32-s3-n16r8]
  start -->|ESP32 DevKit breadboard| v3[Environment esp32d-v3]
  s3 --> orient[How is the display mounted?]
  orient -->|Default| p1[HARDWARE_PROFILE 1 — V1]
  orient -->|Upside down| p2[HARDWARE_PROFILE 2 — V2]
Loading
Target Set profile Commands (from firmware/)
V1 HARDWARE_PROFILE 1 in board_profile.h pio run -e esp32-s3-n16r8 -t upload then uploadfs
V2 HARDWARE_PROFILE 2 (same env) same
V3 automatic in esp32d-v3 pio run -e esp32d-v3 -t upload then uploadfs

First time on a chip: always flash both upload (app) and uploadfs (games partition).
V1 ↔ V2 only: changing profile needs upload only, not uploadfs, if games are already on the device.

Details: docs/FLASHING.md.


Quick start

  1. Install PlatformIO.
  2. Clone this repo and add your legal .nes files to games/.
  3. cd firmware
  4. Pick your environment (see table above) and run upload + uploadfs.
  5. Open serial monitor @ 115200 — look for TFT OK and LittleFS OK.

If the PC does not see the port: hold BOOT, press RESET, release BOOT.


Controls

NEESP uses a familiar NES-style layout on every build. On the V3 breadboard, the D-pad is an analog joystick (X/Y); A, B, START, and SELECT are separate GPIO buttons. On V1/V2 handhelds, all eight directions are tactile buttons.

Button NES meaning NEESP menus
D-pad Up / Down / Left / Right Move cursor, adjust values
A NES A button Confirm / select
B NES B button Cancel / back (in menus)
START NES Start Start game from list; in pause menu — resume
SELECT NES Select Open Settings from the game list; in-game — Select as on a real NES

Buttons are active low (press = connect to GND). There is a short debounce (~150 ms) on menu repeats.

Boot → game list (main menu)

After power-on you land on the game list (“NEESP 50 in 1” title, scrollable ROM names, 1/N counter top-right).

Input Action
Up / Down Move selection (list scrolls when there are more than 5 games)
Left / Right Jump by page (5 titles per page)
A or START (without SELECT) Launch highlighted game
SELECT Open Settings (see below)

When multiplayer is connected, launching may ask the host to approve (2P) or start immediately (1P). See Multiplayer.

Settings (from the game list)

Press SELECT on the game list, then:

Menu item Keys inside
Volume Up / Down — change level (0–200%); A — keep; B — revert and back
Connection Up / Down — move; A — activate row; B — back to game list. Rows: Initialize connection (1P host), Establish connection (2P client), Disconnect, Back
Back A on “Back”, or B anywhere — return to game list

Connection screen legend is shown at the bottom (host vs client hints). Screenshot: menu.jpg.

Inside a running game

Input Action
D-pad, A, B, START, SELECT Passed to the NES as on original hardware (Start/Select work in-game for games that use them)
START + SELECT together Open the pause menu (game freezes; pause can sync to the other console in multiplayer)

Hold both START and SELECT until the pause window appears, then release both (the firmware waits for no buttons pressed so Start does not instantly resume).

Pause menu (in game)

Opened with START + SELECT. Main items:

Item Action
Resume A on Resume, or START / B from the main pause list — continue playing
Volume Up / Down adjust; A confirm; B cancel changes
Load game Pick slot 1–5 (Up/Down), A load (confirm dialog), B delete save in slot, START back to pause main
Save game Same slot UI; A save (overwrite confirm if slot used)
Main menu Quit to game list (emulator stops)

Save slots: 5 per game (Slot 1Slot 5), stored on LittleFS under /littlefs/saves/.

Multiplayer: on player 2 (client) console, Load game and Save game are disabled (only 1P / host may read/write saves).

Confirm dialogs (load / save / delete): A yes, B no.

Quick map: where am I?

Power on
   └── Game list ──SELECT──► Settings ──► Volume / Connection
        │ A or START
        └── Playing ──START+SELECT──► Pause ──► Resume / Volume / Load / Save / Main menu

Adding games

  1. Put .nes files in games/ (repo root).
  2. Build from firmware/copy_games_to_data.py copies them into the LittleFS image with safe filenames.
  3. pio run -e <your-env> -t uploadfs

Legal note: you are responsible for ROMs you use. This project does not distribute Nintendo or third-party commercial ROMs.


Two-player multiplayer

Short version:

  1. Settings → Connection on both consoles (see screenshot below).
  2. Console A: Initialize connection (host / 1P).
  3. Console B: Establish connection (client / 2P).
  4. Pick a game; host = player 1, client = player 2.

NEESP connection settings menu

Full walkthrough, ROM pull, and troubleshooting: docs/MULTIPLAYER.md.


How NEESP differs from stock Nofrendo / DSN

Area Upstream-style Nofrendo / DSN NEESP
Platform Desktop / generic ports Arduino on ESP32-S3 and ESP32
Video SDL or similar TFT_eSPI, banded pushImage, ESP32-S3 FSPI fix
Storage Files / SD paths LittleFS in flash, ROM preload
Display Often larger panels ST7789 2.0" 320×240, rotation profiles, CGRAM patch script
Audio Platform default I2S ring buffer + dedicated task; autoframeskip underrun fix (notes)
UI Minimal Game menu, volume, connection settings, pause + save slots
Multiplayer ESP-NOW net_session (2 players)
Hardware One reference design Profiles 1 / 2 / 3 (rotation, DevKit + joystick)

Core CPU/PPU/APU emulation code still traces back to Nofrendo and the DSN port; the “product” layer around it is new.


Repository layout

NEESP/
├── firmware/          PlatformIO project (build & flash here)
│   ├── src/           Emulator + NEESP UI + net_session
│   ├── include/       Pins, board profiles, TFT setups
│   └── data/          ROM staging for LittleFS (generated from games/)
├── games/             Put your .nes files here (not in git)
├── docs/
│   ├── HARDWARE.md
│   ├── FLASHING.md
│   ├── MULTIPLAYER.md
│   └── images/        Photos (v1a/b, v2a/b, v3a/b, menu)
└── LICENSE            GPL v3 (see upstream DSN)

Troubleshooting (short)

Problem Hint
Black screen, backlight on Check SPI pins, TFT OK in serial — docs/HARDWARE.md
Red/blue screen ~3 s No ROMs — run uploadfs after adding games to games/
Guru Meditation 0x10 on S3 USE_FSPI_PORT in User_Setup.h; clean rebuild
Left garbage strip Rebuild so patch_st7789_rotation.py runs
V3 won’t link (dram0_0_seg) Use current tree (large buffers on heap)
Multiplayer won’t pair Host initializes first; see docs/MULTIPLAYER.md

3D printed case

No STL files live in this repo. Start from DSN-Nes-Emulator-Universal enclosure models, then adapt for a 2.0" panel instead of the original 2.8" design.


Credits & license

Copyright (c) Alexey Voronin for NEESP-specific code. Licensed under the GNU General Public License v3.0 — see LICENSE and NOTICE. You may use, modify, and share this project under GPL v3; comply with GPL when you distribute binaries or forks.


Contributing

Issues and PRs welcome on GitHub. If you build a variant (different screen, 4-player dream, etc.), we’d love to see photos in discussions.

About

handheld NES on ESP32-S3 with ESP-NOW and 2-player multiplayer

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors