Looking for something a little unique for your next tabletop RPG? How about an electronic D20 with custom graphics for critical hits and misses? Today I will show you how to build your own Arduino and a few simple details.

Don’t worry if you’ve never used an Arduino before, we’ve got a guide for you.

Construction plan

This is a simple project. The Arduino will control the OLED display and the button will scroll the matrix. Custom graphics will be displayed for critical hits or throws. You can easily change the code to D8, D10 or D12.

What you need


  • 1 x Arduino
  • 1 x 0.96″ I2C OLED display
  • 1 x button
  • 1 x 10k? resistor
  • 1 x breadboard
  • Assorted connect wires
  • The full code is here if you don’t want to follow all the written instructions.

These are the basic parts you need to build your own D20. You can mount it in a case (see below) and solder the circuit in a more permanent state. Here are the additional details you will need for this:

  • 4 bolts M2 x 10 mm (0.4 in.)
  • 4 x M2 nuts
  • 4 x 7 mm (0.28 in.) washers
  • 9V battery (or suitable alternative)
  • Assorted heat shrink tubing

These OLED displays are very cool. They can usually be purchased in white, blue, yellow, or a mix of the three. I bought one in blue to match my occasion. Make sure you get the model I2C instead of SPI .

Almost any Arduino will do. I chose the Nano as they are small enough to fit in a case. Check out our buying guide buy for more information about Arduino models.


Here is the schematic you need:

arduino d20 schematic

Connect VCC and GND on OLED display to Arduino +5V and grounding . Connect analogue 4 on Arduino to pin with marking SDA . Connect analogue 5 to the conclusion SCL . These pins contain the circuitry needed to drive the display over the I2C bus. The exact pins will vary by model, but A4 and A5 are used on the Nano and Uno. Check the Wire library documentation for your model if you are not using Uno or Nano.

Connect the battery to ground and contact VIN . This stands for input voltage and accepts various DC voltages, but check your specific model first and it can sometimes vary slightly.

Connect the button to digital pin 2 . Notice how 10k? resistor connected to ground. It is very important! This is known as a pull-down resistor, and it prevents the Arduino from detecting spurious data or noise when the button is pressed. It also serves to protect the board. If this resistor were not used, +5V would go straight to ground. This is known as dead short and this is an easy way to kill Arduino.

If you are soldering this circuit, protect your connections with heat shrink tubing:

Arduino D20 Shrink

Make sure you don’t heat it up too much, and only do this after you’re sure the circuit is working. You may also want to twist your cables into pairs. This keeps them neat and helps protect them from excessive stress:

Arduino D20 twisted cable

Button test

Now that you have built the circuit, download this test code (make sure you select the correct board and port from menu « Tools»> « Fee and Tools» > «Ports» ):

const int buttonPin = 2; // the number of the button pin void setup() { pinMode(buttonPin, INPUT); // setup button Serial.begin(9600); // setup serial } void loop(){ if(digitalRead(buttonPin) == HIGH) { Serial.print("It Works"); delay(250); } } 

After downloading, leave the Arduino connected via USB and open the serial monitor ( top right > Serial Monitor ). You must see that the words It Works appear every time you press the button.

If nothing happens, go and recheck your circuit.

OLED installation

Arduino Oled test

You need to install two libraries to control the display. Download the Adafruit_SSD1306 and Adafruit-GFX libraries [больше не доступны] from Github and save them in your library folder. If you’re not sure where your library folders are, read my retro games tutorial where I configure this same display in more detail.

Restart your Arduino IDE and upload the test sketch from the » File»>»Examples» . Select Adafruit SSD1306, and then ssd1306_128x64_i2c . Download this code (it will take some time) and you should see a lot of shapes and patterns on the display:

Arduino Oled test

If nothing happens, double check your connections. If after checking it still doesn’t work, you will need to change the sample code.

Change this line (at the beginning of the function settings ):

 display.begin(SSD1306_SWITCHCAPVCC, 0x3D); 

To that:

 display.begin(SSD1306_SWITCHCAPVCC, 0x3C); 

This tells the library specific information about the display you are using. You should now be ready to proceed with the build.

A business

If you’re building this on a breadboard, or don’t want to package it, you can skip this step.

case Arduino D20

I designed and 3D printed this box. Get files on Thingiverse. Don’t worry if you don’t have a 3D printer — 3D Hubs and Shapeways online services provide online printing services.

You can easily make this box out of wood, or by purchasing a plastic project box.

The cover is a simple design with a tight fit and contains several cutouts for fasteners:

case Arduino D20

The code

Now that everything is ready, it’s time for the code. Here’s how it would work in pseudocode:

 if button is pressed generate random number if random number is 20 show graphic else if random number is 1 show graphic else show number 

For this to work properly, a random number needs to be generated — that’s a die roll. Arduino has a random number generator called random but should not use it. While it is good enough for basic random problems, it is not random enough for an electron crystal. The reasons are somewhat complicated, but you can read more if you’re interested in boallen.com.

Download the TrueRandom library from sirleech on Github. Add this to your library and restart your IDE.

Now create a new file and customize your source code (or just get the code ready from GitHub):

 #include  #include  #include  #include  #include  Adafruit_SSD1306 display(4); void setup() { display.begin(SSD1306_SWITCHCAPVCC, 0x3C); // setup the OLED pinMode(buttonPin, INPUT); // setup button } void loop() { } 

This code sets up the OLED and includes all the libraries you need to communicate with it, as well as the new random number library. Now add this to the main loop:

 if(digitalRead(buttonPin) == HIGH) { delay(15); if(digitalRead(buttonPin) == HIGH) { display.fillScreen(BLACK); // erase the whole display display.setTextColor(WHITE); display.setTextSize(2); display.setCursor(0, 0); display.println(TrueRandom.random(1, 21)); // print random number display.display(); // write to display delay(100); } } 

It’s pretty simple for a minute, but it’s a working D20. Each time the button is pressed, a random number from one to 20 is displayed on the screen:

Arduino D20 first code

It works well but is a bit boring. Let’s make it better. Create two new methods, drawDie and eraseDie :

 void drawDie() { display.drawRect(32, 0, 64, 64, WHITE); } 

They will draw a cube in the middle of the screen. You might want to make this more complex, perhaps by drawing D20 or D12 and so on, but it’s easier to draw a basic six-sided die. Here is the main usage:


Then change your main loop to draw a random number, just bigger and in the middle. Change the text size and cursor to this:

 display.setTextColor(WHITE); display.setCursor(57, 21); 

Now it looks much better:

Arduino D20 single symbol

The only problem with numbers greater than nine:

Arduino D20 dual character

It’s easy to fix. For any numbers less than 10, the cursor will be set to a position other than those numbers 10 or greater. Replace this line:

 display.setCursor(57, 21); 

With this:

 int roll = TrueRandom.random(1, 21); // store the random number if (roll < 10) { // single character number display.setCursor(57, 21); } else { // dual character number display.setCursor(47, 21); } 

Here's what it looks like now:

Arduino D20 fixed double symbol

Now there are only images left when you throw a critical hit or miss. There are a few steps, but it's a fairly simple process.

Find the right image you want to use (the simpler the better since the display is only one color). Here are the images I used:

Arduino D20 work
Image credit: publicdomainvectors.org

Any image you want to use must be converted to a HEX array. This is the code representation of the image. There are many tools available for this, and some of them are written specifically for OLED displays. The easiest way is to use the PicturetoC_Hex online tool. Here are the required settings:

Arduino images to hex

Upload your image and set the code format HEX: 0x . Install Is used for on the Black/White for all image drawing functions . Leave all other options as default. You can resize the image here if you need to. Click Get C String, and you should see the image data:

Arduino D20 image data

You'll need this generated data in a minute. Create two functions with drawExplosion names and drawSkull (or a suitable name for your version). Here is the code:

 void drawExplosion() { // store image in EEPROM static const unsigned char PROGMEM imExp[] = { 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x30,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xfc,0x00,0x00,0x00,0x00,0x00,0x78,0x7f,0xff,0xc0,0x00,0x00,0x00,0x00,0xfe,0xff,0xff,0xf0,0x00,0x00,0x00,0x3f,0xff,0xff,0xff,0xfb,0x00,0x00,0x00,0x7f,0xff,0xff,0xff,0xff,0xc0,0x00,0x00,0x7f,0xff,0xff,0xff,0xff,0xff,0x00,0x01,0xff,0xff,0xff,0xff,0xff,0xff,0x80,0x03,0xff,0xff,0xff,0xff,0xff,0xff,0x80,0x03,0xff,0xff,0xff,0xff,0xff,0xff,0x80,0x03,0xff,0xff,0xff,0xff,0xff,0xff,0xc0,0x03,0xff,0xff,0xff,0xff,0xff,0xff,0xf0,0x07,0xff,0xff,0xff,0xff,0xff,0xff,0xf0,0x07,0xff,0xff,0xff,0xff,0xff,0xff,0xf0,0x07,0xff,0xff,0xff,0xff,0xff,0xff,0xe0,0x07,0xff,0xff,0xff,0xff,0xff,0xff,0xc0,0x0f,0xff,0xff,0xff,0xff,0xff,0xff,0xe0,0x1f,0xff,0xff,0xff,0xff,0xff,0xff,0xe0,0x1f,0xff,0xff,0xff,0xff,0xff,0xff,0xe0,0x0f,0xff,0xff,0xff,0xff,0xff,0xff,0xf0,0x03,0xff,0xff,0xff,0xff,0xff,0xff,0xf0,0x03,0xff,0xff,0xff,0xff,0xff,0xff,0xf0,0x03,0xff,0xff,0xff,0xff,0xff,0xff,0xe0,0x01,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x0f,0xff,0xff,0xff,0xff,0xfe,0x00,0x00,0x07,0xff,0xff,0xf9,0xff,0xd8,0x00,0x00,0x00,0x3f,0xff,0xf0,0x0f,0x00,0x00,0x00,0x00,0x1f,0x1f,0xf0,0x00,0x00,0x00,0x00,0x00,0x00,0x0f,0xe0,0x00,0x00,0x00,0x00,0x00,0x00,0x0f,0xe0,0x00,0x00,0x00,0x00,0x00,0x00,0x0f,0xe0,0x00,0x00,0x00,0x00,0x00,0x00,0x0f,0xf0,0x00,0x00,0x00,0x00,0x00,0x00,0x0f,0xf0,0x00,0x00,0x00,0x00,0x00,0x00,0x0f,0xf0,0x00,0x00,0x00,0x00,0x00,0x00,0x3f,0xf8,0x00,0x00,0x00,0x00,0x00,0x00,0x7f,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x7f,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x7f,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x7f,0xfe,0x00,0x00,0x00,0x00,0x00,0x00,0x7f,0xfc,0x00,0x00,0x00,0x00,0x00,0x00,0x0f,0xf0,0x00,0x00,0x00,0x00,0x00,0x00,0x07,0xf0,0x00,0x00,0x00,0x00,0x00,0x00,0x07,0xe0,0x00,0x00,0x00,0x00,0x00,0x00,0x07,0xe0,0x00,0x00,0x00,0x00,0x00,0x00,0x07,0xe0,0x00,0x00,0x00,0x00,0x00,0x00,0x07,0xf0,0x00,0x00,0x00,0x00,0x00,0x00,0x0f,0xf0,0x00,0x00,0x00,0x00,0x00,0x00,0x0f,0xf8,0x00,0x00,0x00,0x00,0x00,0x00,0x0f,0xfc,0x00,0x00,0x00,0x00,0x00,0x00,0x1f,0xff,0x00,0x00,0x00,0x00,0x00,0x0f,0xff,0xff,0xff,0x00,0x00,0x00,0x07,0xff,0xff,0xff,0xff,0xf0,0x00,0x00,0x0f,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x1f,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x1f,0xff,0xff,0xff,0xff,0xfc,0x00,0x00,0x01,0xbf,0xff,0xff,0xff,0x30,0x00,0x00,0x00,0x13,0xf7,0xb8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 }; display.drawBitmap(0, 0, imExp, 64, 62, 1); // draw mushroom cloud } void drawSkull() { // store image in EEPROM static const unsigned char PROGMEM imSku[] = { 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xe0,0x00,0x00,0x00,0x00,0x30,0x00,0x00,0xf0,0x00,0x00,0x00,0x00,0x78,0x00,0x07,0xf0,0x00,0x00,0x00,0x00,0xfc,0x00,0x07,0xf8,0x00,0x00,0x00,0x00,0xfe,0x00,0x07,0xf8,0x00,0x00,0x00,0x01,0xfe,0x00,0x07,0xfc,0x00,0x00,0x00,0x01,0xfe,0x00,0x07,0xfe,0x00,0x3f,0xc0,0x03,0xfe,0x00,0x01,0xff,0x81,0xff,0xfc,0x07,0xec,0x00,0x00,0x3f,0xc7,0xff,0xff,0x1f,0xc0,0x00,0x00,0x0f,0xcf,0xff,0xff,0xdf,0x00,0x00,0x00,0x07,0xbf,0xff,0xff,0xee,0x00,0x00,0x00,0x01,0x7f,0xff,0xff,0xf0,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xf8,0x00,0x00,0x00,0x01,0xff,0xff,0xff,0xf8,0x00,0x00,0x00,0x03,0xff,0xff,0xff,0xfc,0x00,0x00,0x00,0x07,0xff,0xff,0xff,0xfe,0x00,0x00,0x00,0x0f,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x0f,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x1f,0xff,0xff,0xff,0xff,0x80,0x00,0x00,0x1f,0xff,0xff,0xff,0xff,0x80,0x00,0x00,0x1f,0xff,0xff,0xff,0xff,0x80,0x00,0x00,0x1f,0xff,0xff,0xff,0xff,0x80,0x00,0x00,0x1f,0xff,0xff,0xff,0xff,0x80,0x00,0x00,0x1f,0xff,0xff,0xff,0xff,0x80,0x00,0x00,0x1e,0x3f,0xff,0x3f,0xc7,0x80,0x00,0x00,0x1e,0x0c,0x0f,0x00,0x07,0x80,0x00,0x00,0x1e,0x00,0x0f,0x00,0x0f,0x80,0x00,0x00,0x1e,0x00,0x19,0x80,0x0f,0x00,0x00,0x00,0x0f,0x00,0x19,0x80,0x0f,0x00,0x00,0x00,0x0d,0x00,0x30,0xc0,0x1f,0x00,0x00,0x00,0x05,0x80,0x70,0xc0,0x1e,0x00,0x00,0x00,0x05,0xf0,0xe0,0xe0,0x36,0x00,0x00,0x00,0x01,0xff,0xe0,0x7f,0xf0,0x00,0x00,0x00,0x03,0xff,0xc4,0x7f,0xf0,0x00,0x00,0x00,0x03,0xff,0xcc,0x7f,0xf0,0x00,0x00,0x00,0x03,0xff,0xcc,0x7f,0xf0,0x00,0x00,0x00,0x03,0xff,0x9e,0x7f,0xf0,0x00,0x00,0x00,0x00,0xff,0xfe,0x7f,0xc0,0x00,0x00,0x00,0x00,0x01,0xff,0xf8,0x1c,0x00,0x00,0x00,0x03,0xe0,0x3f,0x01,0xbf,0x00,0x00,0x00,0x07,0xa6,0x40,0x09,0x9f,0x80,0x00,0x00,0x1f,0x27,0x5a,0x39,0x9f,0xf8,0x00,0x01,0xff,0x27,0xdb,0x39,0x0f,0xfc,0x00,0x03,0xfe,0x31,0x7f,0x39,0x07,0xfc,0x00,0x03,0xfc,0x10,0x1a,0x02,0x03,0xf8,0x00,0x03,0xf8,0x10,0x00,0x02,0x01,0xf0,0x00,0x01,0xf8,0x10,0x00,0x02,0x01,0xe0,0x00,0x00,0x78,0x10,0x00,0x02,0x00,0xe0,0x00,0x00,0x70,0x30,0x00,0x02,0x00,0x00,0x00,0x00,0x30,0x20,0x00,0x03,0x00,0x00,0x00,0x00,0x00,0x64,0x00,0x1b,0x00,0x00,0x00,0x00,0x00,0x73,0x55,0x63,0x00,0x00,0x00,0x00,0x00,0xf9,0x55,0x4f,0x00,0x00,0x00,0x00,0x00,0x7f,0x14,0x1f,0x00,0x00,0x00,0x00,0x00,0x1f,0xe0,0xfe,0x00,0x00,0x00,0x00,0x00,0x0f,0xff,0xfc,0x00,0x00,0x00,0x00,0x00,0x07,0xff,0xf0,0x00,0x00,0x00,0x00,0x00,0x03,0xff,0xc0,0x00,0x00,0x00,0x00,0x00,0x00,0x38,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 }; display.drawBitmap(0, 0, imSku, 60, 64, 1); // draw skull cloud } 

If you want to use the images I used then copy the code. If you want to use your own images generated before, copy the byte code into arrays imSku and imExp by as needed.

Here is how these images look on the display:

Arduino Oled Images

The most important part of this code is this line:

 static const unsigned char PROGMEM imSku[] 

This tells the Arduino to store your images in EEPROM (What is an EEPROM?) instead of its RAM (A quick guide to RAM). The reason is simple; The Arduino has limited RAM and using all that to store images may leave nothing for your code to execute.

Change your main statement if, to show these new charts when a 1 or 20 is rolled. Pay attention to the lines of code to show the number collapsed along with the images:

 if(roll == 20) { drawExplosion(); display.setCursor(80, 21); display.println("20"); } else if(roll == 1) { display.setCursor(24, 21); display.println("1"); drawSkull(); } else if (roll < 10) { // single character number display.setCursor(57, 21); display.println(roll); // write the roll drawDie(); // draw the outline } else { // dual character number display.setCursor(47, 21); display.println(roll); // write the roll drawDie(); // draw the outline } 

Here's what the new rolls look like:

Arduino D20 critical images

That's all for the code (go grab the code from GitHub if you missed it all). You can easily change this to D12, D8 and so on.

final assembly

Now that everything else is finished, it's time to put everything together. Screw on the display, being careful not to overtighten the screws. This is perhaps the most difficult part. I cracked the display so you can use some plastic washers. I cut out some squares from Plasticard:

Arduino D20 Spacers

Small nuts and bolts can be tricky to connect. Advice: Use a small piece of Blu-Tack on the end of the screwdriver to seat the nuts initially:

Arduino D20 Walnut

Screw the button, connect the battery and close the cover. Be careful not to pinch any wires or squeeze them too hard, which could cause a short circuit. Depending on the length of your lead wires, you may need to protect exposed connections with some insulation (a serial block works well):

Arduino D20 Inside cardboard

Here's what it looks like inside:

Arduino D20 inside

And here is the finished product:

Arduino D20 Skull

Now you should be the proud owner of an electronic D20!

What modifications have you made? Have you changed images? Let us know in the comments, we'd love to see what you've done!

Похожие записи