small medium large xlarge

Make Your Own Video Game System

Rediscover the Early Days of Video Gaming using the Arduino Single-Board Computer

by Maik Schmidt

Generic image illustrating the article
  Return with us now to those innocent days of yesteryear, when you could identify with the fate of a single pixel in a field of asteroids or up against a wall of bricks.  

People use the Arduino microcontroller board for countless different projects. Some use it to create interactive art, some use it automate their home, and some even use it to create games. Often these games are self-contained toys such as remakes of the famous Simon game. For this kind of games you often need only a few LEDs, a piezo buzzer, and sometimes an LC display.

But wouldn’t it be more interesting to create a real video game system—that is, a device that allows you to play different video games like Breakout or Asteroids on your TV? The Arduino makes it surprisingly easy to build such a system and in this article I’ll show you how.

You’ll learn how to generate a monochrome video signal, so you can connect your Arduino directly to your TV set. Also you’ll learn how to hijack a Wii Nunchuk controller, so you can control your games with state-of-the-art gaming hardware. (For the sake of brevity we do not add any sound effects.)

Finally, you’ll learn how to implement a small game. It will be a simulation of the light cycle races from the movie Tron. (We all agree there will always be only one Tron movie and only one Matrix movie, right?)

What You Need

To build this article’s product you only need a few cheap parts. Probably you have most of them at home already:

  • An Arduino board such as the Uno, Duemilanove, or Diecimila

  • A USB cable to connect the Arduino to your computer

  • A Nintendo Nunchuk controller

  • An RCA cable

  • Five wires

  • A 1k-ohm resistor

  • A 470-ohm resistor

  • A pair of wire cutters

Tinkering With the RCA Cable

Before we start to generate video signals, we have to connect the Arduino to a TV set. One of the cheapest and easiest solutions for transmitting audio or video signals is an RCA cable (sometimes called cinch cable). In Figure 1 you can see an RCA cable with three connectors. These cables usually transmit sound (red and white connectors) and video (yellow connector). For our purpose a cable with a single connector is sufficient.


Figure 1: Three RCA Connectors

Unfortunately, the Arduino has no RCA jack, so how do we connect the cable to the Arduino? In principle we could add an RCA jack to the Arduino, but we’ll use a less subtle method and instead connect the cable’s wires directly to the Arduino’s pins. An individual RCA cable contains two wires: signal and ground. The signal wire is usually well protected by insulation to shield it from distortions. The ground wire is not a real wire but a mesh of stranded copper. To get access to the wires, cut the cable in half first. Go ahead, snip it!

Now take one of the halves and use the wire cutters again to carfully remove the cable’s outer insulation. You should now see the mesh of stranded copper.

Bring back the mesh into wire shape by rubbing it between your thumb and forefinger, so you can solder it to a solid-core wire later on. After that, use the wire cutter again to remove the inner insulation. From my experience it’s best to put the cable between the wire cutter’s blades, then turn the cable slowly and carefully, increasing the pressure while turning the cable. Be very careful so you do not accidentally cut the signal wire! After you’ve cut through the whole insulation you can easily remove it. You should now see the cable’s signal wire and your cable should look like Figure 2.


Figure 2: Signal Wire and Ground Wire of Our Cable

Now have a look at Figure 3 to see how to connect the RCA cable to the Arduino. First of all, you have to connect the cable’s ground to one of the Arduino’s ground pins. Then you have to connect the RCA cable’s signal (VIDEO) wire to the Arduino’s digital pins 9 and 7 using two resistors. We need pin 9 for synchronizing the sender (Arduino) and the receiver (TV set) and we need pin 7 to emit the video signal.


Figure 3: Connecting an RCA Cable to the Arduino

So we have to connect the two resistors to the RCA cable’s signal wire and it’s not sufficient to simply knot them together. You have to solder them; and while you’re at, it connect the RCA cable’s ground wire to a piece of solid-core wire, so you can easily attach it to the Arduino. (See Figure 4 for the result.)


Figure 4: Our Final Cable

That’s all the hardware we need for generating a video signal, so connect the cable to the Arduino and let’s see how we can bring it to life.

Generating a Video Signal

Generating a clean video signal is difficult, because the output quality heavily depends on a very exact timing. Usually you need a lot of tricky assembler code dealing with nasty topics such as interrupt handling. Fortunately, Myles Metzler went through all the pain already and created the arduino-tvout library. This library not only offers basic functions for drawing lines, circles, and rectangles, it also comes with a convenient toolset for drawing text in fonts of various sizes. Oh, and it even supports sound output.

Of course, you cannot expect a small device such as the Arduino to generate HD graphics, but at least we are able to generate monochrome graphics with a resolution of 128 by 96 pixels for both PAL and NTSC. That’s enough for many useful applications and games and it’s still better than the resolution of my first mobile phone. (and we all had a lot of fun playing Snake back in these days, right?)

Although the arduino-tvout library comes with a really nice demo (it displays some bitmap graphics and even rotates a cube) we will start with our own, to see how the library works:

 #include <TVout.h>
 #include <fontALL.h>
 #include <stdint.h>
 const uint8_t WIDTH = 128;
 const uint8_t HEIGHT = 96;
 TVout tv;
 void setup() {
  tv.begin(PAL, WIDTH, HEIGHT);
 void loop() {
  tv.print(0, 0, " Welcome to our little demo!");
  tv.print(0, 0, " Let's draw a line:");
  tv.draw_line(0, 10, WIDTH - 1, HEIGHT - 1, WHITE);
  tv.print(0, 0, " Now let's draw a rectangle:");
  tv.draw_rect(0, 10, WIDTH - 11, HEIGHT - 11, WHITE);
  tv.print(0, 0, " And here we have a circle:");
  tv.draw_circle(WIDTH / 2, HEIGHT / 2, 20, WHITE);

This is a simple program but it shows nearly all aspects of the arduino-tvout library. First of all, we include the library’s main header file, and we include fontALL.h, because we want to output some text. We also include the stdint header file, so we have some nice type definitions for common types. Then we define constants for the width and height of our screen.

In the global scope we define a TVout object named tv. We initialize this object in the setup function calling its begin method. This method expects the video signal type, the screen’s width, and the screen’s height. We chose PAL in this case, but you can safely replace it with NTSC. In addition, we select the font we’re going to use to output our messages.

The loop function demonstrates several of the library’s features. We print a few texts calling print and we draw some lines and shapes calling draw_line, draw_rect, and draw_circle (see Figure 5 for the result of draw_circle). Unsurprisingly, the library also has methods named set_pixel and get_pixel, and this is the right time to read the online documentation.


Figure 5: Our First TVout Demo

All in all the arduino-tvout library offers a very intuitive access to a TV screen. But to play games we also need a cool controller.

Tinkering with the Wii Nunchuk

A vital component of every video game system is a controller, and for our system we could simply create our own using a couple of push buttons. But we are lazy, so instead we’ll hijack a Nintendo Nunchuk controller. It’s a perfect candidate for tinkering, for various reasons. It comes with a three-axis accelerometer, an analog joystick, and two buttons, and it is very cheap (less than $10 at the time of this writing). Even better: because of its good design and its easy-to-access connectors, you can integrate it into your own projects surprisingly easily.


Figure 6: A Nintendo Nunchuk Controller

We’ll use an ordinary Nunchuk controller and transfer the data it emits to our computer using an Arduino. You’ll learn how to wire it to the Arduino and how to write software that reads the controller’s current state. You don’t even need a Nintendo Wii to do any of this—you only need a Nunchuk controller.

Wiring a Wii Nunchuk

Wiring a Nunchuk to an Arduino really is a piece of cake. You don’t have to open the Nunchuk or modify it in any way. You only have to put four wires into its connector and then connect the wires to the Arduino.


Figure 7: The Nunchul Connector

In Figure 7, you can see a diagram of a Nunchuk plug. It has six connectors, but only four of them are active: GND, 3.3 V, Data, and Clock. Put a wire into each connector, and then connect the wires to the Arduino. Connect the data wire to analog pin 4 and the clock wire to analog pin 5. The GND wire has to be connected to the Arduino’s ground pin and the 3.3 V wire belongs to the Arduino’s 3.3 V pin.


Figure 8: How to Connect a Nunchuk to an Arduino

That’s really all you have to do to connect a Nunchuk controller to an Arduino. The controller is powered with 3.3 volts now, and in the next section, you’ll see that the two wires connected to analog pins 4 and 5 are all we need to control the controller.

Talking to a Nunchuk

No official documentation shows how a Nunchuk works internally or how you can use it in a non-Wii environment. But some smart hackers and makers on the Internet invested a lot of time to reverse-engineer what’s happening inside the controller.

All in all, it’s really simple, because the Nunchuk uses the Two-Wire Interface (TWI), also known as I2C (Inter-Integrated Circuit) protocol. It enables devices to communicate via a master/slave data bus using only two wires. You transmit data on one wire (DATA), while the other synchronizes the communication (CLOCK).

The Arduino IDE comes with a library named Wire that implements the I2C protocol. It expects the data line to be connected to analog pin 4 and the clock line to analog pin 5. We’ll use it shortly to communicate with the Nunchuk, but before that, we’ll have a look at the commands the controller understands. (At this site you can find a library that allows you to use any pair of pins for I2C communication.)


Figure 9: The Nunchuk always Returns 6 Bytes of Data

To be honest, the Nunchuk understands only a single command, which is “Give me all your data.” Whenever it receives this command, it returns six bytes that have the following meaning (see the data structure in Figure 9):

  • Byte 1 contains the analog stick’s X-axis value, and in byte 2 you’ll find the stick’s Y-axis value. Both are 8-bit numbers and range from about 29 to 225.

  • Acceleration values for the X, Y, and Z axes are three 10-bit numbers. Bytes 3, 4, and 5 contain their eight most significant bits. You can find the missing two bits for each of them in byte 6.

  • Byte 6 has to be interpreted bit-wise. Bit 0 (the least significant bit) contains the status of the Z-button. It’s 0 if the button was pressed; otherwise, it is 1. Bit 1 contains the C-button’s status.

    The remaining six bits contain the missing least significant bits of the acceleration values. Bits 2 and 3 belong to the X axis, bits 4 and 5 belong to Y, and bits 6 and 7 belong to Z.

Now that we know how to interpret the data we get from the Nunchuk, we can start to build a Nunchuk class to control it.

Building a Nunchuk Class

The interface of our Nunchuk class (and the main part of its implementation) looks as follows:

 class Nunchuk {
  void initialize();
  bool update();
 int joystick_x() const { return _buffer[0]; }
 int joystick_y() const { return _buffer[1]; }
 bool left() const { return _buffer[0] < 50; }
 bool right() const { return _buffer[0] > 200; }
 bool up() const { return _buffer[1] > 200; }
 bool down() const { return _buffer[1] < 60; }
 int x_acceleration() const {
  return ((int)(_buffer[2]) << 2) | ((_buffer[5] >> 2) & 0x03);
 int y_acceleration() const {
  return ((int)(_buffer[3]) << 2) | ((_buffer[5] >> 4) & 0x03);
 int z_acceleration() const {
  return ((int)(_buffer[4]) << 2) | ((_buffer[5] >> 6) & 0x03);
 bool z_button() const { return !(_buffer[5] & 0x01); }
 bool c_button() const { return !(_buffer[5] & 0x02); }
  void request_data();
  char decode_byte(const char);
  unsigned char _buffer[NUNCHUK_BUFFER_SIZE];

This small C++ class is all you need to use a Nunchuk controller with your Arduino. To initiate the communication channel between Arduino and Nunchuk, you have to invoke the initialize method once. Then you call update whenever you want the Nunchuk to send new data. You’ll see the implementation of these two methods shortly.

We have public methods for getting all attributes the Nunchuk returns: the x and y positions of the analog stick, the button states, and the acceleration values of the X, Y, and Z axes. We also have methods for checking if the analog stick is pressed to a certain direction. All these methods operate on the raw data you can find in the buffer named _buffer. Their implementation is mostly trivial and requires only a single line of code. Only the assembly of the 10-bit acceleration values needs some tricky bit operations.

At the end of the class declaration you find two private helper methods we need to implement: initialize and update:

 #include <WProgram.h>
 #include <Wire.h>
 #include "nunchuk.h"
 #define NUNCHUK_DEVICE_ID 0x52
 void Nunchuk::initialize() {
 bool Nunchuk::update() {
  int byte_counter = 0;
  while (Wire.available() && byte_counter < NUNCHUK_BUFFER_SIZE)
  _buffer[byte_counter++] = decode_byte(Wire.receive());
  return byte_counter == NUNCHUK_BUFFER_SIZE;
 void Nunchuk::request_data() {
 char Nunchuk::decode_byte(const char b) {
  return (b ^ 0x17) + 0x17;

After including all libraries we need, we define the NUNCHUK_DEVICE_ID constant. I2C is a master/slave protocol; in our case, the Arduino will be the master, and the Nunchuk will be the slave. The Nunchuk registers itself at the data bus using a certain ID (0x52), so we can address it whenever we need something.


Figure 10: Message Flow between Arduino and Nunchuk

In initialize, we establish the connection between the Arduino and the Nunchuk by sending a handshake. Therefore we call Wire’s begin method, so the Arduino joins the I2C bus as a master (if you pass begin an ID, it joins the bus as a slave having this ID). Then we begin a new transmission to the device identified by NUNCHUCK_DEVICE_ID: our Nunchuk.

We send two bytes (0x40 and 0x00) to the Nunchuk, and then we end the transmission. This is the whole handshake procedure, and now we can ask the Nunchuk for its current status by calling update. In Figure 10, we see the message flow between an Arduino and a Nunchuk.

update first pauses for a millisecond to let things settle a bit. Then we request six bytes from the Nunchuk, calling Wire.requestFrom. This does not actually return the bytes, but we have to read them in a loop and fill our buffer. Wire.available returns the number of bytes that are available on the data bus, and Wire.receive returns the current byte. We cannot use the bytes we get from the Nunchuk directly, because the controller obfuscates them a bit. “Decrypting” them is easy, as you can see in decode_byte.

Finally, we tell the Nunchuk to prepare new data by calling request_data. It transmits a single zero byte to the Nunchuk, which means “prepare the next six bytes.”


Figure 11: The Video Game System Is Complete

That’s it! Your complete video game system should look like Figure 11 now. We have video output and access to all of the Nunchuk’s data, so let’s create our first game.

Creating Our First Game

Due to a lack of resources we will not be able to create a first-person shooter, but our system is powerful enough to run some entertaining classics. (At you can find Tetris, Asteroids, Breakout, and Space Invaders, for example.)


Figure 12: Our Game’s Title Screen

We’ll create a Tron-like light cycle racing game—that is, two players will fight each other using light cycles that leave a solid trace. Whenever a player hits a wall or a cycle’s trace, the race is over and the other player wins. In our version of the game a human player always plays against the computer and we will represent the light cycles by a single pixel. Let’s start with the Player class: (You can find the article’s code examples at github.)

 class Player {
  enum Direction {
 enum Behavior {
 uint8_t px, py;
 Direction direction;
 Behavior behavior;
 void move(void) {
  switch (direction) {
  case NORTH: py -= 1; break;
  case WEST: px -= 1; break;
  case EAST: px += 1; break;
  case SOUTH: py += 1; break;
 bool draw(TVout& tv) {
  if (tv.get_pixel(px, py) == 1)
  return true;
  tv.set_pixel(px, py, 1);
  return false;
 void control(TVout& tv, const Nunchuk& controller) {
  switch (behavior) {
  case MANUALLY:
 void control_manually(const Nunchuk& controller) {
  if (controller.left()) {
  direction = WEST;
  } else if (controller.right()) {
  direction = EAST;
  } else if (controller.up()) {
  direction = NORTH;
  } else if (controller.down()) {
  direction = SOUTH;
 void control_preferred_order(TVout& tv) {
  if (is_passable(tv, NORTH))
  direction = NORTH;
  else if (is_passable(tv, WEST))
  direction = WEST;
  else if (is_passable(tv, EAST))
  direction = EAST;
  else if (is_passable(tv, SOUTH))
  direction = SOUTH;
  direction = NORTH;
  bool is_passable(TVout& tv, const uint8_t direction) {
  switch (direction) {
  case NORTH: return (tv.get_pixel(px, py - 1) == 0);
  case WEST: return (tv.get_pixel(px - 1, py) == 0);
  case EAST: return (tv.get_pixel(px + 1, py) == 0);
  case SOUTH: return (tv.get_pixel(px, py + 1) == 0);

First we define two enumerations for the player’s possible moving directions and for the player’s behavior. MANUALLY means the player is controlled by a human being and PREFERRED_ORDER means the player is controlled by the computer using a simple algorithm that tries to move into the first possible direction.

After that we define a few public instance variables. px and py contain the player’s current coordinates. In direction you’ll find the player’s direction and behavior stores the player’s behavior.

Then we define three public methods. move moves the player into its current direction by changing the coordinates accordingly. The draw method draws the player onto the screen using set_pixel, but first it checks if the pixel is already occupied. If yes, it returns true and we know that the player has hit something.

The control method actually controls the player depending on its behavior and it delegates its work to two private methods. Controlling the player manually is trivial, so in control_manually we only have to check the joystick’s current position and adjust the player’s direction accordingly. Our approach to artificial intelligence isn’t very sophisticated either. We simply check what’s the next passable direction and take it.

Let’s implement the game itself now:

 class Game {
  static const uint8_t SCREEN_WIDTH = 128;
  static const uint8_t SCREEN_HEIGHT = 96;
  static const uint8_t FONT_HEIGHT = 6;
 enum GameState {
 void initialize() {
 void reset_game(void) {
  _game_state = INTRO;
  _player1.px = SCREEN_WIDTH / 2 - 4; = SCREEN_HEIGHT / 2;
  _player1.direction = Player::NORTH;
  _player1.behavior = Player::MANUALLY;
  _player2.px = SCREEN_WIDTH / 2 + 4; = SCREEN_HEIGHT / 2;
  _player2.direction = Player::NORTH;
  _player2.behavior = Player::PREFERRED_ORDER;
 void intro(void) {
  _tv.print(0, 20, " Arduino Light Cycle Race");
  _tv.print(0, 46, " by Maik Schmidt");
  _tv.print(0, 72, " Press Button to Start");
  if (_controller.c_button()) {
  _game_state = STARTING;
  0, 0,
 void pause(void) {
  if (_controller.c_button()) {
  _game_state = RUNNING;
  print_message(" ");
 void done(void) {
  if (_controller.c_button()) {
  _game_state = INTRO;
 void start(void) {
  print_message(" ");
  _game_state = RUNNING;
 void play(void) {
  _player1.control(_tv, _controller);
  const bool player1_hit = _player1.draw(_tv);
  _player2.control(_tv, _controller);
  const bool player2_hit = _player2.draw(_tv);
  if (player1_hit && player2_hit) {
  _game_state = DONE;
  print_message("Tie Game");
  } else if (player1_hit) {
  _game_state = DONE;
  print_message("You Lose!");
  } else if (player2_hit) {
  _game_state = DONE;
  print_message("You Win!");
  if (_controller.c_button()) {
  _game_state = PAUSED;
 void run(void) {
  switch (_game_state) {
  case INTRO: intro(); break;
  case PAUSED: pause(); break;
  case DONE: done(); break;
  case STARTING: start(); break;
  default: play(); break;
 void print_message(const char* message) {
  GameState _game_state;
  TVout _tv;
  Nunchuk _controller;
  Player _player1, _player2;

As in most video games, one of the biggest problems is handling the game’s current state, so first of all we define an enumeration containing all possible states: In state INTRO the game will display a title screen and wait for a button press (see Figure 12). If the buttons was pressed, the game enters state STARTING. In this state it draws the playfield and counts down from three to one, so the player has some time to get ready for the race. Then it enters state RUNNING and handles the actual gameplay. If any player hits a wall the game state changes to DONE. If the player presses a button during the game it changes to PAUSED.


Figure 13: Our Game in Action

After that, we have two methods for initializing and resetting the game. These methods initialize our video and controller hardware and they set back the player settings to reasonable defaults. Then we have a single method for every game state and these are all fairly trivial. There are much nicer ways of implementing a state machine, but in this case a few if statements were appropriate. Please note that I did not debounce the Nunchuk button correctly. I’ve only added a delay of 150 milliseconds whenever I’ve waited for a button press, but in this case it’s OK, because video game players usually are not very patient and want to restart a game as fast as possible. Also I did not distinguish between PAL and NTSC, so if you replace PAL by NTSC in initialize the game will run slightly slower.

The only thing left to do is glue it all together:

 Game game;
 void setup() {
 void loop() {;

Isn’t it amazing how clean code for embedded systems can be? So compile and upload the sketch using the Arduino IDE and have some fun!

We could add a lot more stuff easily and evolve our game into something way more professional. For example, we could add a nicer title screen and a high-score list. Also we could add more advanced artificial intelligence and some visual effects such as explosions whenever a player hits a wall. But the point of this article is not the development of professional-looking games. I wanted to show how easy it is to create a minimalistic video game system with the Arduino.

Video Game Shields for the Arduino

If you’re looking for more professional video game solutions or if you do not want to build a video game system yourself, you can choose from several commercial products:

  • The Hackvision project is a complete video game system based on the arduino-tvout library. It comes with sound and a five-button controller, and it supports two paddles and a Nunchuk.

  • If you’re looking for a shield that allows you to output text or graphics to a TV, you can use the video game shield or the TellyMate shield. The former even has support for sound output.

  • And finally, if you’re really thinking about creating great games for the Arduino you should have a look at the Gameduino shield, one of the greatest shields that was ever created for the Arduino. It supports video output of 400x300 pixels in 512 colors via VGA. It has 256 hardware sprites and 12-bit stereo audio output. In addition, it has a co-processor that you can program in Forth and that works in a similar fashion to the Copper in the good old Commodore Amiga.

Maik Schmidt is the author of Arduino: A Quick-Start Guide. He has worked as a software developer for more than fifteen years, creating solutions for large enterprises. He frequently writes book reviews and articles and is also the author of Enterprise Recipes with Ruby and Rails and Enterprise Integration with Ruby.

Send the author your feedback or discuss the article in the magazine forum.