Arduino NES

20,792

249

45

Introduction: Arduino NES

About: Make it yourself if you cannot buy one!

This instructables show how to build a portable NES console with Arduino IDE.

Note:

currently most NES emulator required manual command line build with sort of C compiler, e.g. esp-idf, it is a big barrier for the beginner. I believe make it Arduino IDE compatible can break this barrier, enjoy!

Supplies

Step 1: What Is Nofrendo?

Nofrendo is a good NES emulator that can run at full speed on a slow Pentium system. It has decent sound support, and good compatibility.

Nofrendo is developed by Matthew Conte at previous century. It can run at Windows, Unix and BeOS. Since the implementation is generalized to support different platform, many developers start develop other platforms NES emulator base on it.

Ref.:

http://www.baisoku.org

https://www.zophar.net/nes/nofrendo.html

https://github.com/adafruit/nofrendo_arcada

https://github.com/espressif/esp32-nesemu

Step 2: Arduino-nofrendo

Github user rickyzhang82 pushed Nofrendo original source code to Github at 2018. I have forked the repository and transformed it to an Arduino library compatible layout. Simply implement all functions declared in osd.h to make it works.

Theoretically, this library can use in any Arduino platform if it have enough RAM and processing power. Since Espressif already proofed ESP32 can run Nofrendo, I base on their sample code developed an esp32-nofrendo example under examples folder.

Ref.:

https://github.com/rickyzhang82/nofrendo

https://github.com/moononournation/arduino-nofrend...

Step 3: Display Options

In order to support as much display variations as possible, I changed the display implementation to use Arduino_GFX. Even large 4 inches 320x480 display like ST7796 also supported now!

Here are the Arduino_GFX supported displays variations that can fit for Nofrendo resolution:

  • HX8347C 240 x 320
  • HX8347D 240 x 320
  • HX8352C 240 x 400 (16:9)
  • HX8357B 320 x 480 (9-bit SPI and scale up make it a little bit slow)
  • ILI9341 240 x 320
  • ILI9481 320 x 480 18-bit color (max 20 MHz and 18-bit color make it a little bit slow)
  • ILI9486 320 x 480 18-bit color
  • ILI9488 320 x480 18-bit color
  • M5Stack 240 x 320
  • R61529 320 x 480
  • ST7789 240 x 320
  • ST7789 240 x 240 (square size)
  • ST7796 320 x 480

Note:

  • 320 x 480 display requires crop original 256 x 240 resolution to 240 x 214 and then scale up to 480 x 320.
  • Width is scaled double and height is scaled 1.5 times, every 2 original lines draw 3 lines. The third line can be repeat the second line or simply leave third line remain in background color. Details implementation can be found in display.cpp.

Step 4: Audio Options

Currently arduino-nofrendo support (actually arduino-esp32 support) 2 audio output methods:

  • internal DAC - ESP32 has two 8-bit DAC (digital to analog converter) channels, connected to GPIO25 (Channel 1) and GPIO26 (Channel 2), since the output is very weak it still require an extra audio amplifier for speaker output.
  • external I2S DAC module - simply connect 3 signal lines and power to I2S amplifier module, it can do the jobs of DAC and audio amplifier in the same module.

Note:

The internal DAC only have 8-bit resolution and have too much silent noise, use and external I2S amplifier module can have much better result.

Step 5: Controller Options

Currently arduino-nofrendo support 3 types of controller:

Tobe support:

  • More I2C device e.g. I2C Gamepad
  • Bluetooth device e.g. BT Gamepad and BT Keyboard

Note:

The default arduino-esp32 I2C interface connected to GPIO 21 (SDA) and 22 (SCL).

Step 6: File System Options

There are 3 types of nofrendo files stored in the file system:

  • ROM files (*.nes) - the game
  • Game save files (*.sav) - when you save in the game, the data backed up to this file
  • State files (*.ss[0-9]) - when you use Nofrendo save state function, the data stored in these files

Currently arduino-nofrendo support (actually arduino-esp32 support) 3 types of file system:

  • SPIFFS - normally it is the last partition of ESP32 flash
  • SD - Arduino standard SPI mode SD card interface
  • SD_MMC - native 1-bit or 4-bit SD mode SD card interface

Note:

SPIFFS is most easy way for initial testing. But it is hard to backup the save and state files in SPIFFS, SD or SD_MMC are recommended for long term use. Also as mentioned at the above video demo, you can treat a SD card as a game cartridge, simply swap SD cards to change the game.

Step 7: Software Preparation

Arduino IDE

Download and install Arduino IDE if you are not yet do it:

https://www.arduino.cc/en/main/software

ESP32 Support

Follow the Installation Instructions to add ESP32 support if you are not yet do it:

https://github.com/espressif/arduino-esp32

Arduino ESP32 filesystem uploader

Follow the installation steps to install Arduino ESP32 filesystem uploader if you are not yet do it:

https://github.com/me-no-dev/arduino-esp32fs-plugi...

Arduino_GFX Library

Download latest Arduino_GFX libraries: (press "Clone or Download" -> "Download ZIP")

https://github.com/moononournation/Arduino_GFX

Import libraries in Arduino IDE. (Arduino IDE "Sketch" Menu -> "Include Library" -> "Add .ZIP Library" -> select downloaded ZIP file)

Arduino Nofrendo Library

Download latest Arduino Nofrendo libraries: (press "Clone or Download" -> "Download ZIP")

https://github.com/moononournation/arduino-nofrend...

Import libraries in Arduino IDE. (Arduino IDE "Sketch" Menu -> "Include Library" -> "Add .ZIP Library" -> select downloaded ZIP file)

Step 8: Breadboard Prototype

Before building your own final version of portable Arduino NES console, let's start a PoC with a breadboard prototype.

Step 9: Breadboard Patch

4 inches LCD is a big one, combine four 400 holes breadboard is large enough to handle it. However, 8 power bus strips are over-killed, cut out 6 of them and only keep 2 is good enough.

Step 10: TTGO T8 V1.7 Patch

Combine four 400 holes breadboard can fit the 4 inches LCD, but it still does not have enough room remained to place an ESP32 dev board. so it is required hide part of the ESP32 dev board under the LCD. Also hide the clumesy wiring under the LCD can make it more tidy. But I found the TTGO T8 V1.7 3D antenna cannot fit under the LCD. Since the NES console does not require Internet access, I simply tear it off :P

Note:

  • it can still redirect WiFi signal to a external antenna via IPEX connector in the future.
  • Bluetooth device connection also require antenna.

Step 11: LCD Patch

  • Soldering SD pin header if not yet
  • Optional: Re-soldering all LCD pins to shift the pins a little bit longer to the breadboard
  • Insert a extra plastic separator to reserve more room between the breadboard and LCD

Step 12: Fix Analog Joystick

The side of analog joystick with breakout pins can fix on the breadboard very good, but the other side require some extra pins to fix it on the breadboard.

Step 13: I2S Amplifier Module Patch

The I2S Amplifier module speaker connectors are not 2.54 mm pitch (the pitch size of breadboard holes), it is required bend the pin header a little bit for soldering.

Step 14: Layout Design

Before actual breadboard wiring, first design all components layout. And double check all components positions not conflict with the LCD.

Step 15: Pin Mapping Design

This step is optional, just make the wire neatly. Since all wires hidden behind the LCD, it is not essential.

Most ESP32 interface can remap to any GPIO pins, except SD_MMC. Since TTGO T8 v1.7 already built-in SD card slot connected to SD_MMC interface, I would like to reuse that pins to LCD SD card slot.

I have used some breadboard wires under the dev broad to redirect some GPIOs to other breadboard strips for more tidy layout:

  • Analog joystick pins GPIO 34 & 35 to left bottom
  • SD_MMC pins GPIO 13, 15, 2 & 14 to right top
  • Start & Select button pins GPIO 26 & 27 to right bottom
  • 3v3 to 2 power strips
  • GND to power strips

Note:

SPI display connect 7 GPIO pins normally, it is CS, RESET, D/C, SCK, MOSI, MISO and LED. In order to reduce the number of GPIO usage to 3, some arrangement made:

  • CS pull down to GND, means always enable
  • Reset connect to ESP32 Reset, LCD reset while ESP32 reset
  • MISO leave not connected, no need to read data from LCD
  • LED connect to VCC, LCD backlight alway 100% on

Step 16: Breadboard Wiring

Here are the connection summary:

ESP32
VCC      ->  LCD VCC & LED, I2S Amplifier Module VCC, Joystick VCC
GND      ->  LCD GND & CS, I2S Amplifier Module GND, Joystick GND, all Button GND
RST      ->  LCD RST
GPIO 34  ->  Joystick Up Down Analog
GPIO 35  ->  Joystick Left Right Analog
GPIO 32  ->  LCD D/C
GPIO 33  ->  LCD MOSI
GPIO 25  ->  LCD SCK
GPIO 26  ->  Button Start
GPIO 27  ->  Button Select
GPIO 23  ->  Button X
GPIO 18  ->  Button Y
GPIO  5  ->  Button A
GPIO  4  ->  Button B
GPIO 14  ->  SD SCK
GPIO 13  ->  SD CS
GPIO 15  ->  SD MOSI
GPIO  2  ->  SD MISO
GPIO 22  ->  I2S Amplifier Module BCLK
GPIO 21  ->  I2S Amplifier Module WCLK (or called LRC)
GPIO 19  ->  I2S Amplifier Module DOUT (or called DIN)
             I2S Amplifier Module GAIN -> 100 ohm resistor -> GND (optional 15 dB gain)
             I2S Amplifier Module +ve -> Speaker +ve
             I2S Amplifier Module -ve -> Speaker -ve

Step 17: Optional Battery

TTGO T8 v1.7 dev board built-in Lipo charge and regulate circuits. Connect a Lipo Battery can make the console portable but it is not essential for prototyping.

Behind the breadboard is a big flat surface that can place a very large Lipo Battery.

Step 18: Program

  1. Connect the device with USB cable
  2. Open Arduino IDE
  3. Open esp32-nofrendo sample code ("File" -> "Example" -> "arduino-nofrendo" -> "esp32-nofrendo")
  4. Check the configuration parameters in hw_config.h and display.cpp, the default parameters is already set for this breadboard prototype
  5. Press Arduino IDE "Upload" button

Note:

Sometimes you need remove the SD to make the upload success.

Step 19: ROM

If you does not have any NES rom files in hand, there are many homebrewn NES rom on the web, e.g.:

http://www.nesworld.com/article.php?system=nes&dat...

I have selected a simple game called "Chase" in the "BEST HOMEBREWN NINTENDO ENTERTAINMENT SYSTEM GAMES" for the esp32-nofrendo example.

Note:

A homebrewn shooting game called "BLADE BUSTER" also recommended, but the rom size cannot fit for the dev board that does not have PSRAM. (nofrendo require load the rom file to the memory)

Step 20: Upload ROM File

The default hw_config.h parameter is set to use SD filesystem, simply copy your ROM file (*.nes) to a SD card and plug it in to make it work.

If you changed to use SPIFFS filesystem, the esp32-nofrendo example already included a "Chase.nes" ROM file in data folder.

Simply select "Tools" menu in Arduino IDE -> "ESP32 Sketch Data Upload" will upload the ROM file to ESP32 SPIFFS.

You can also copy your ROM files to data file and upload.

Note:

At this moment, esp32-nofrendo example will found the first "*.nes" file and load it.

Step 21: Status Save & Load

Status Save & Load, also called infinity recovery, is a simple cheating technique that can help you play game easier.

NES does not have button X & Y, I have used it as a status save & load buttons.

Note:

Nofrendo designed 10 slot for status save & load, currently arduino-nofrendo only can use first slot (slot 0).

Step 22: Pre-built ESP32 Console

The are few ESP32 dev device pre-built as a game console, e.g.:

  • TTGO T-Watch + Game module
  • ODROID-GO
  • M5Stack + M5Stack CardKB

Step 23: TTGO T-Watch + Game Module

  1. Open esp32-nofrendo sample code ("File" -> "Example" -> "arduino-nofrendo" -> "esp32-nofrendo")
  2. Select "TTGO T-Watch" ("Tools" -> "Board" -> "TTGO T-Watch")
  3. Press Upload button
  4. Upload Data ("Tools" -> "ESP32 Sketch Data Upload")

Ref.:

https://t-watch-document-en.readthedocs.io/en/late...

Step 24: ODROID-GO

  1. Open esp32-nofrendo sample code ("File" -> "Example" -> "arduino-nofrendo" -> "esp32-nofrendo")
  2. Select "ODROID ESP32" ("Tools" -> "Board" -> "ODROID ESP32")
  3. Press Upload button

Ref.:

https://www.hardkernel.com/shop/odroid-go/

Step 25: M5Stack + M5Stack CardKB

  1. Open esp32-nofrendo sample code ("File" -> "Example" -> "arduino-nofrendo" -> "esp32-nofrendo")
  2. Select "M5Stack-Core-ESP32" ("Tools" -> "Board" -> "M5Stack-Core-ESP32")
  3. Press Upload button

Note:

CardKB cannot report more than 1 key pressed at the same time, so it is suitable for playing certain games.

Ref.:

https://m5stack.com/collections/m5-core

https://m5stack.com/products/cardkb-mini-keyboard

Step 26: Enjoy!

It's time to build your own Arduino NES console!

Arduino IDE is a very easy to use platform. The esp32-nofrendo example only a starting point, you can add much more features on it, e.g.:

  • Touch screen UI
  • Game selection UI with art image like RetroPie
  • Snapshot while playing
  • Status slot selection
  • Status save with snapshot
  • Status browse and load UI with snapshot preview
  • Volume setting
  • LCD backlight setting
1000th Contest

Second Prize in the
1000th Contest

4 People Made This Project!

Recommendations

  • Tinkercad Student Design Contest

    Tinkercad Student Design Contest
  • Lamps and Lighting Contest

    Lamps and Lighting Contest
  • Micro:bit Contest

    Micro:bit Contest

45 Comments

0
chazix3
chazix3

Question 4 months ago on Step 18

Very Great Project! But when I run the esp32-nofrendo example it seems there're something wrong in the libraries. Have anyone met this before? It almost drive me crazy. Could anybody help me with that?

0
陳亮
陳亮

Answer 4 months ago

What is your problems?

0
chazix3
chazix3

Reply 4 months ago

Like this.

QQ图片20210617003116.jpg
0
chazix3
chazix3

Reply 4 months ago

Sorry,I've solved this problem. After I plug in the SD card with Chase in it, the screen is white and show nothing. I also tried the SPIFFS and it didn’t work.

0
Snake675
Snake675

5 months ago

Cool project. I made one following your discription. Everything went fine until I updated the Arduino_GFX_Library to the latest version. That lead to 2 problems. First I had to adapt the declaration and delete the "_DMA" part. Second there appeared a blueish stripe to the right of the frame. I tried different ILI9341 displays, always the same. This only happens with rotation=0, when rotation=1 there is no blue stripe at all (picture).

I dont have any idea how to fix this. Its very annoying!

nofrendo_bluestripe.jpg
0
陳亮
陳亮

Reply 5 months ago

The Arduino_ESP32SPI_DMA class is already obsoleted at Apr 4 commit. Please get latest code and try again.

0
Snake675
Snake675

Reply 5 months ago

Hi
Thanks for the replay. I already use version 1.1.3 of your fantastic Arduino_GFX library. That was the reason why I had to omit the "_DMA" in the declaration :)
The issue with the blue stripe appeared after updating to 1.1.3. Could there be a bug somewhere in the code (rotation code), since it only appears if rotation is 0 (zero).

0
陳亮
陳亮

Reply 5 months ago

not only update Arduino_GFX, also update arduino_nofrendo

0
Snake675
Snake675

Reply 5 months ago

Hi
Thanks. I am using version 1.2 of arduino-nofrendo. Seems to be the latest version too. Did someone else already had this issue?

0
Snake675
Snake675

Reply 4 months ago

Ok, thanks. I will download the latest version of the nofrendo library and recompile my ino-file.

0
Snake675
Snake675

Reply 4 months ago

Just did it. But the blue stripe is still there.

0
陳亮
陳亮

Reply 4 months ago

The latest display.cpp should look like this, can you check it:
...
extern "C" void display_init()
{
w = gfx->width();
h = gfx->height();
if (w < 480) // assume only 240x240 or 320x240
{
if (w > NES_SCREEN_WIDTH)
{
frame_x = (w - NES_SCREEN_WIDTH) / 2;
frame_x_offset = 0;
frame_width = NES_SCREEN_WIDTH;
frame_height = NES_SCREEN_HEIGHT;
frame_line_pixels = frame_width;
}
else
{
frame_x = 0;
frame_x_offset = (NES_SCREEN_WIDTH - w) / 2;
frame_width = w;
frame_height = NES_SCREEN_HEIGHT;
frame_line_pixels = frame_width;
}
frame_y = (gfx->height() - NES_SCREEN_HEIGHT) / 2;
}
else // assume 480x320
{
frame_x = 0;
frame_y = 0;
frame_x_offset = 8;
frame_width = w;
frame_height = h;
frame_line_pixels = frame_width / 2;
}
}
...
0
Snake675
Snake675

Reply 4 months ago

Thanks. But its the same thing. Nevertheless I tried again with a new download of the libraries and a fresh script from the example. Still no success. Can someone confirm this?

0
Acmedina
Acmedina

Question 5 months ago

Hi! Nice proyect, which PSP Analog joystick module did you use? 1000, 2000 or 3000 version? thank you so much

PspJoy.png
0
emdida
emdida

6 months ago

this is the result but not full screen this is the code
extern "C"
{
#include <nes/nes.h>
}

#include "hw_config.h"

#include <Arduino_GFX_Library.h>
#include "display/Arduino_ILI9341.h"

#define TFT_BRIGHTNESS 128 /* 0 - 255 */

/* ST7796 on breadboard */
// #define TFT_BL 32
Arduino_DataBus *bus = new Arduino_ESP32SPI_DMA(32 /* DC */, -1 /* CS */, 25 /* SCK */, 33 /* MOSI */, -1 /* MISO */);
Arduino_TFT *gfx = new Arduino_ILI9341(bus, -1 /* RST */, 1 /* rotation */);

static int16_t w, h, frame_x, frame_y, frame_x_offset, frame_width, frame_height, frame_line_pixels;
extern int16_t bg_color;
extern uint16_t myPalette[];

extern void display_begin()
{
gfx->begin();
bg_color = gfx->color565(24, 28, 24); // DARK DARK GREY
gfx->fillScreen(bg_color);

#ifdef TFT_BL
// turn display backlight on
ledcAttachPin(TFT_BL, 1); // assign TFT_BL pin to channel 1
ledcSetup(1, 12000, 8); // 12 kHz PWM, 8-bit resolution
ledcWrite(1, TFT_BRIGHTNESS); // brightness 0 - 255
#endif
}

extern "C" void display_init()
{
w = gfx->width();
h = gfx->height();
if (w < 480) // assume only 240x240 or 320x240
{
if (w > NES_SCREEN_WIDTH)
{
frame_x = (w - NES_SCREEN_WIDTH) / 2;
frame_x_offset = 0;
frame_width = NES_SCREEN_WIDTH;
frame_height = NES_SCREEN_HEIGHT;
frame_line_pixels = frame_width;
}
else
{
frame_x = 0;
frame_x_offset = (NES_SCREEN_WIDTH - w) / 2;
frame_width = w;
frame_height = NES_SCREEN_HEIGHT;
frame_line_pixels = frame_width;
}
frame_y = (gfx->height() - NES_SCREEN_HEIGHT) / 2;
}
else // assume 480x320
{
frame_x = 0;
frame_y = 0;
frame_x_offset = 8;
frame_width = w;
frame_height = h;
frame_line_pixels = frame_width / 2;
}
}

extern "C" void display_write_frame(const uint8_t *data[])
{
gfx->startWrite();
if (w < 480)
{
gfx->writeAddrWindow(frame_x, frame_y, frame_width, frame_height);
for (int32_t i = 0; i < NES_SCREEN_HEIGHT; i++)
{
gfx->writeIndexedPixels((uint8_t *)(data[i] + frame_x_offset), myPalette, frame_line_pixels);
}
}
else
{
/* ST7796 480 x 320 resolution */

/* Option 1:
* crop 256 x 240 to 240 x 214
* then scale up width x 2 and scale up height x 1.5
* repeat a line for every 2 lines
*/
// gfx->writeAddrWindow(frame_x, frame_y, frame_width, frame_height);
// for (int16_t i = 10; i < (10 + 214); i++)
// {
// gfx->writeIndexedPixelsDouble((uint8_t *)(data[i] + 8), myPalette, frame_line_pixels);
// if ((i % 2) == 1)
// {
// gfx->writeIndexedPixelsDouble((uint8_t *)(data[i] + 8), myPalette, frame_line_pixels);
// }
// }

/* Option 2:
* crop 256 x 240 to 240 x 214
* then scale up width x 2 and scale up height x 1.5
* simply blank a line for every 2 lines
*/
int16_t y = 0;
for (int16_t i = 10; i < (10 + 214); i++)
{
gfx->writeAddrWindow(frame_x, y++, frame_width, 1);
gfx->writeIndexedPixelsDouble((uint8_t *)(data[i] + 8), myPalette, frame_line_pixels);
if ((i % 2) == 1)
{
y++; // blank line
}
}

/* Option 3:
* crop 256 x 240 to 240 x 240
* then scale up width x 2 and scale up height x 1.33
* repeat a line for every 3 lines
*/
// gfx->writeAddrWindow(frame_x, frame_y, frame_width, frame_height);
// for (int16_t i = 0; i < 240; i++)
// {
// gfx->writeIndexedPixelsDouble((uint8_t *)(data[i] + 8), myPalette, frame_line_pixels);
// if ((i % 3) == 1)
// {
// gfx->writeIndexedPixelsDouble((uint8_t *)(data[i] + 8), myPalette, frame_line_pixels);
// }
// }

/* Option 4:
* crop 256 x 240 to 240 x 240
* then scale up width x 2 and scale up height x 1.33
* simply blank a line for every 3 lines
*/
// int16_t y = 0;
// for (int16_t i = 0; i < 240; i++)
// {
// gfx->writeAddrWindow(frame_x, y++, frame_width, 1);
// gfx->writeIndexedPixelsDouble((uint8_t *)(data[i] + 8), myPalette, frame_line_pixels);
// if ((i % 3) == 1)
// {
// y++; // blank line
// }
// }
}
gfx->endWrite();
}

extern "C" void display_clear()
{
gfx->fillScreen(bg_color);
}

index.jpg
0
陳亮
陳亮

Reply 6 months ago

My sample only show how to sketch NES 256x240 to ST7796 480x320. If you would like to use another display resolution, you are required to pay some effort in display_write_frame() with your own algorithm to sketch NES 256x240 to ILI9341 320x240.

0
emdida
emdida

6 months ago

i have esp32 with ILI9341 / 2.4 inch and i want run NES gams on full screen and the buzzer not work sound in the pin 4
when i modifit the library for the ILI9341 the game run verry good bat it's not full screen dimension
thank you so much have a good day

0
陳亮
陳亮

Reply 6 months ago

The example code have some tailor-made code for ST7796 resolution(480x320). If you would like to contribute on ILI9341 resolution(320x240), please take a look on display_write_frame() in display.cpp file.

0
emdida
emdida

Reply 6 months ago

I was unsuccessful -_-