Building a Handheld Retro Gaming Console With Local Wireless Connectivity

March 03, 2024 by Kristijan Nelkovski

In this project article, we design a handheld gaming console with wireless multiplayer functionality using ESP8266 microcontrollers. We then program a game for it with the Arduino IDE.

In this project, we’ll create a retro-style handheld gaming console that features local wireless connectivity. At the heart of this project is the ESP8266 microcontroller. This microcontroller is famous for its integrated TCP/IP networking and Wi-Fi functionality, making it perfect for IoT applications such as ours.

Once we’ve assembled our hardware, we’ll use the Arduino IDE to program a simple multiplayer version of a popular 1980s arcade game called PONG for the console. If you’re not familiar, this game was originally developed as a digital version of table tennis. Two players each operate their own paddle on opposite sides of the screen, trying to hit a ball back and forth without letting it fall through their side of the court.

Figure 1 shows the completed project. Though the version in the figure uses a custom PCB I created, everything covered in this article is entirely breadboard and perfboard friendly.


A retro-style handheld gaming console built using a custom PCB.

Figure 1. The handheld console we’ll be designing.


If you’d like to make your own custom PCB, the Eagle Cad files to do so are available here:


Bill of Materials

This bill of materials (BoM) is for only one device. To utilize our multiplayer code, you’ll need to build two of these—one node to act as a “host” server and one node to act as a “client.”


Part Quantity      Notes
LOLIN D1 Mini 1 Convenient ESP8266 development board.
ATtiny85/45 1 Microcontroller for playing music.
Nokia 5110 LCD 1 To display the game graphics.
PCF8574 1 I/O expander for buttons.
Buzzer 2 One for music, one for other sounds.
Button 8 Minimum 2. The remaining 6 are for other games in the future.
10 kΩ resistor 10 Pull down (buttons) and pull up (I2C bus).
330 Ω resistor 2 Current limiting (buzzers).
Dip switch (2 positions) 1 For muting buzzers.
On/Off switch 1 For the whole device.
Male and female headers 1 For connecting display, dev board, and power.
AAA or AA batteries 4 Either AAA or AA batteries can be used.
Battery holder for 4 AAA or AA batteries 1 Whichever is appropriate for the chosen battery type.
Custom PCB, breadboard, or perfboard 1 Pick one of the three options.


Circuit Design

The schematic in Figure 2 tells us how to wire all of our components together, whether it’s on a breadboard, a perforated PCB, or a custom PCB built from the files provided above. Don’t forget to add batteries to your project if you want to make it portable. Otherwise, you can power it directly via the D1 Mini USB port from either a computer or a 5 V charger.


Project schematic.

Figure 2. Schematic for the project hardware.


Now it’s time for the coding portion of this project. Some of the code we’ll use is based on this How to Write a Game tutorial. I’ve made significant modifications to it, however, from adding wireless multiplayer connectivity to changing the control input and graphics library. All of the code files needed for this project are available in the article’s final section.


Drawing Graphics

Our game graphics consist of a start menu and a game scene. Both of these will appear on the 84-by-48 pixel Nokia 5110 display. To generate the graphics, we’ll directly draw letters and basic shapes using the Adafruit GFX library in combination with the Adafruit PCD8544.h library. The PCD8544 chip is a display driver embedded into the Nokia 5110 module’s LCD component.

The start menu will be a simple “press this button to start” screen. You can also add other selectable options—or even nesting menus—for audio selection, game mode selection, or whatever else you like. If you want to make it look prettier, you can use an online tool like image2cpp to create more complex graphics and superimpose them over the selection text. That’s what I did to produce the on-screen image in Figure 3.


The game's start screen.

Figure 3. The game’s start screen.


For the game scene itself, the first thing we’re going to do is define the dynamic elements. These are:

  • The two rectangular PONG paddles (one at the top of the court, one at the bottom).
  • The ball that bounces between the paddles and the court walls.

The paddles will have fixed y coordinates, only being able to move left and right, while the ball will be free to move around the entirety of the LCD (Figure 4).


Figure 4. First, we add the game’s dynamic elements.


Next, we’re going to define the static (fixed x and y coordinates) elements of the scene. These elements, which are visible in Figure 5, include:

  • The box defining the borders of the court.
  • A dotted demarcation line splitting the court into two halves.
  • The letters ‘A’ and ‘B’ marking each player's half on the left side.
  • Two numbers marking the players’ scores on the right side.


Figure 5. The game now includes both dynamic and static elements.



Each player will be able to control their own paddle using one button for moving it left and another for moving it right. The game will also have a dedicated “start” button separate from the On/Off switch. However, only the host will be able to start a new game.

Because the ESP8266 has limited GPIO pins, our push buttons will be controlled using a PCF8574 8-bit input/output expander chip connected to the I2C bus of the microcontroller and programmed with Adafruit’s PCF8575.h library. For a rudimentary version of PONG, we could technically skip using this chip and its library by implementing the I2C pins as standard GPIO for reading out the states of only two buttons.

If you want to use this project’s setup as a starting point to program your own games in the future, however, more buttons will come in handy—you can use them for a variety of functionalities and actions. For that reason, our schematic includes a total of 8 buttons: left, right, up, down, A, B, C, and D. This represents the maximum amount of inputs/outputs that a single PCF8574 chip can handle (8 bits).


Collision Detection and Scorekeeping

A round of PONG ends when a paddle misses the ball and, as a result, the ball goes through that player’s side of the court. We’ll implement collision detection by checking whether the ball’s x coordinates overlap with those of the paddle when the ball and the paddle both share the same y coordinate. If this condition is met, the ball will bounce off the paddle by changing its direction at an angle and the game will play a “hit” tone.

If the ball’s x coordinate falls outside of these bounds, on the other hand, the game will do the following:

  1. Pause for half a second.
  2. Play a “miss” tone.
  3. Update a score variable for the winner of the round.
  4. Reset the ball to a random x and central y coordinate on the display.

A new round then begins.

The rounds continue until the score variable of either player reaches a certain threshold. I set the score threshold to five, but it can be changed. The first player to reach the threshold is declared the winner of the game, and the host is prompted with the option to start a new game.


Wireless Communication

We’ll use ESP-NOW, which comprises the ESP8266WiFi.h and espnow.h Arduino libraries, to facilitate wireless data transfer between the nodes of our system. ESP-NOW is a Wi-Fi based connectionless communications protocol developed by Espressif, the manufacturer of the ESP8266. It enables the transfer of small packets of data between ESP devices without the need for a router or internet access, identifying each node only by its MAC address.

In this way, we can establish a link between the two nodes of our system so that they can exchange game information, including:

  • The location of dynamic elements.
  • Each player’s score.
  • Control and status flags.

The host of the game will receive a direction flag whenever the client moves their paddle. The client will receive scores and relevant coordinates of all dynamic elements calculated by the host, including the client’s own paddle.

To circumvent latency between pressing buttons on one device and receiving packets on the other, the host will “stream” the game to the client. This is necessary because being even one pixel off on an 84-by-48 resolution display could hinder the proper functioning of the entire game.


Playing Audio

The audio for this project can be divided into two categories: game sounds and game music. For game sounds, we’re going to create two distinct notes using the Arduino tone() function—one for when the ball hits a paddle or a wall, and one for when the ball falls through the court. These notes will be played on the first piezo buzzer controlled directly by the ESP8266.

For the game music, we’re going to use segments of MIDI songs that I’ve transformed into Arduino code as tone() functions and delays with the help of Extramaster’s MIDI to Arduino online tool. The ATtiny will play loops of these song snippets through the second piezo buzzer, which is selected by an input from the ESP8266 on two of its GPIO pins (2-bit selector input).

The ATtiny won’t be able to decode any more information on its selection pins once it goes into a track loop. This is why a third I/O pin from the ESP8266 is connected directly to the dedicated reset pin of the ATtiny, allowing it to reset the whole microcontroller when necessary. Doing so causes the ATtiny to exit the music loop, and starts the track selection code check all over again.

Because we’re only building a single game for this project, we’ll limit ourselves to using one track. You can expand your MIDI snippet library if you want to create more games—just keep in mind that the ATtiny microcontrollers have very limited memory.


Uploading the Code

Next, we need to set up the Arduino IDE with all relevant boards and libraries. Installing libraries is straightforward—you can accomplish it by typing the libraries out in the Arduino Library Manager. Installing the boards can be a little trickier. I recommend using these two GitHub pages as guides:


Finally, it’s time to upload the game itself. You can find the game code here:


The .zip file contains the following sketches:

  • Host.
  • Client.
  • Audio.
  • MAC address finder.

The host sketch is for the first node, the client sketch is for the second, and the audio sketch is universal to both ATtinys. Before uploading, use the MAC address sketch to check your development boards’ MAC addresses. You’ll need to hard-code them into your sketches. The address of the client goes into the host program, while the address of the host goes into the client program.

When uploading a sketch, make sure to select the correct ESP8266 board that you’re using. In my case, that’s the LOLIN D1 Mini. You’ll also need to use another development board, such as an Arduino UNO, as an in-system-programmer (ISP) to upload the code to your ATtiny85.

The video in Figure 6 shows gameplay for the completed project. Once everything is wired up, you can boot your consoles and start playing—or, if you prefer, use the project as a starting point to begin working on your own game.


Figure 6. Playing PONG on the handheld gaming console. Includes audio.


All images and videos used courtesy of Kristijan Nelkovski

1 Comment
  • R
    radellaf March 08, 2024

    Use the more advanced, but still cheap, ESP32 and could probably skip the GPIO expander…. and get much more CPU, RAM, and flash.

    Like. Reply