Web Banner

Project - Electronic Dice

I wanted to learn more about the capabilities of the OLED (SSD1306) and decided this would be an interesting project. This is where I first encountered problems with the Adafruit OLED display driver changing the I2C bus speed. The Adafruit SSD1306 library had two hidden parameters in the class constructor.

In INO file below (Line 11) you can see that there are two parameters with the value of 100000. The first is the I2C bus speed to use when sending any display commands. The second is the I2C bus speed to set after the display command has been sent. These are not shown in the example file, but are very important.

By default the display library set's the I2C bus speed to 400000 for it's communication overriding the Wire speed. It then lowers the clock speed back to 100000. The unexpected increase in I2C bus speed caused a problem with the pull-up resistor that I was using.

I2C Rule of Thumb

- A faster bus speed requires a lower resistor.

Arduino .INO File

In order to use the class, it's very simple. Create a reference to the Dice class, then call begin.

begin (&Dispay Object , Solid Dice)

- Pass in the SSD1306 display object and then set true or false for a solid dice image

setNumber(Number Of Dice)

- Adjusts the number of dice to roll from 1 to 5.

setDiceType(Max Number On Dice) (Optional)

- Set's the maximum number on the dice (6 for D6 1-6, 4 for D4 1-4).

roll()

- Starts dice rolling.

Note: This class is blocking. The code will not proceed until the dice roll completes.

Arduino .INO File

#include <Arduino.h> #include <Adafruit_GFX.h> #include <Adafruit_SSD1306.h> #include "dice.h" #define SCREEN_WIDTH 128 // OLED display width, in pixels #define SCREEN_HEIGHT 64 // OLED display height, in pixels #define OLED_RESET -1 // Reset pin # (or -1 if sharing Arduino reset pin) #define SCREEN_ADDRESS 0x3C // I2C address of the SSD1306 Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET, 1000000, 1000000); Dice myDice = Dice(); // Initialize my dice object const int PIN_SWITCH = 8; // The number of the pushbutton pin void setup() { pinMode(PIN_SWITCH, INPUT); // Initialize the switch pin as an input: if(!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS, OLED_RESET)) { // Initialize the SSD1306 display Serial.println(F("SSD1306 allocation failed")); } display.clearDisplay(); // Clear the display memory display.display(); // Display the memory values on the screen myDice.begin(&display, true); // 1 : Pass in the reference to the display // 2 : Display dice as solid color or as an outline myDice.setNumber(4); // Sets the number of dice to roll myDice.setDiceType(6); // Sets the dice type D4 or D6 } void loop() { if (digitalRead(PIN_SWITCH) == true){ myDice.roll(); } }

Arduino .H File

//---------------------------------------------------- // Developed By: MakingSense_otw //---------------------------------------------------- // Description: // Rolls one or more dice and displays the animation // using the Adafruit_SSD1306 library. // // Date: Feb 18th 2024 // Version: 1.0 //---------------------------------------------------- #ifndef _DICE_H #define _DICE_H #include #define X_LARGE_DICE 44 //Size in pixels for the X-Large Dice #define LARGE_DICE 40 //Size in pixels for the Large Dice #define MEDIUM_DICE 29 //Size in pixels for the Medium Dice #define SMALL_DICE 24 //Size in pixels for the Small Dice class Dice { public: Dice(void); bool begin (Adafruit_SSD1306 *display, bool solid); void setNumber (int numberOfDice); void setDiceType (int diceType); void roll(); private: int _numberOfDice = 1; // The number of dice to roll int _minDiceValue = 1; // Sets the smallest random number. int _maxDiceValue = 6; // Sets the highest random number. bool _solid = true; // If true the dice has a solid white color with black dots int _numberOfRollsBeforeStopping = 10;// The number of random dice rolls before stopping int _yPositionDice = 20; // Sets the top location in pixels for the dice images Adafruit_SSD1306 *_display; // Reference to the Adafruit display object bool randomized = false; // A flag to indicate if the random seed value has been set int getValue(); void drawTextNumber (int x, int y, int size, int value, bool solid); void drawDiceImage (int x, int y, int size, int value, bool solid); void drawDiceShape (int x, int y, int size, int radius, bool solid); void drawDiceDots (int x, int y, int radius, bool solid ); void clearGraphics (void); }; #endif

Arduino .CPP File

//---------------------------------------------------- // Developed By: MakingSense_otw //---------------------------------------------------- // Description: // Rolls one or more dice and displays the animation // using the Adafruit_SSD1306 library. // // Date: Feb 18th 2024 // Version: 1.0 //---------------------------------------------------- #include "dice.h" #include <Adafruit_SSD1306.h> #ifdef ALLOW_DICE_TEXT #include <Fonts/FreeSansBold9pt7b.h> #include <Fonts/FreeSansBold12pt7b.h> #include <Fonts/FreeSansBold18pt7b.h> #endif Dice::Dice(){ } bool Dice::begin(Adafruit_SSD1306 *display, bool solid){ // Store the references & values for the parameters _display = display _solid = solid; clearGraphics(); _display->setTextSize(1); _display->display(); return true; } void Dice::setNumber(int numberOfDice){ _numberOfDice = numberOfDice } void Dice::setDiceType(int diceType){ #ifdef ALLOW_DICE_TEXT if (diceType > 0 && diceType <= 99){ _minDiceValue = 1; _maxDiceValue = diceType; }else if (diceType == 100){ _minDiceValue = 0; _maxDiceValue = 99; } else { _minDiceValue = 1; _maxDiceValue = 6; } #else if (diceType > 0 && diceType <= 6){ _minDiceValue = 1; _maxDiceValue = diceType; } #endif } int Dice::getValue(){ return random(_minDiceValue,_maxDiceValue+1); } void Dice::roll(){ int offSet = 0; int remainingSpace = 0; if (randomized==false){ randomSeed(millis()); randomized = true; } int rollDelay = 220; for (int index = 0; index < _numberOfRollsBeforeStopping; index++) { clearGraphics(); switch(_numberOfDice){ case 1: drawDiceImage(40, _yPositionDice, X_LARGE_DICE, getValue(), _solid); break case 2: drawDiceImage(14, _yPositionDice, X_LARGE_DICE, getValue(), _solid); drawDiceImage(74, _yPositionDice, X_LARGE_DICE, getValue(), _solid); break case 3: remainingSpace = 128 - (LARGE_DICE*3); offSet = remainingSpace/2; drawDiceImage(0, _yPositionDice, LARGE_DICE, getValue(), _solid); drawDiceImage(LARGE_DICE+offSet, _yPositionDice, LARGE_DICE, getValue(), _solid); drawDiceImage((LARGE_DICE+offSet)*2, _yPositionDice, LARGE_DICE, getValue(), _solid); break case 4: remainingSpace = 128 - (MEDIUM_DICE*4); offSet = remainingSpace/3; drawDiceImage(0, _yPositionDice, MEDIUM_DICE, getValue(), _solid); drawDiceImage(MEDIUM_DICE+offSet, _yPositionDice+15, MEDIUM_DICE, getValue(), _solid); drawDiceImage((MEDIUM_DICE+offSet)*2, _yPositionDice, MEDIUM_DICE, getValue(), _solid); drawDiceImage((MEDIUM_DICE+offSet)*3, _yPositionDice+15, MEDIUM_DICE, getValue(), _solid); break case 5: remainingSpace = 128 - (SMALL_DICE*5); offSet = remainingSpace/4; drawDiceImage(0, _yPositionDice, SMALL_DICE, getValue(), _solid); drawDiceImage(SMALL_DICE+offSet, _yPositionDice+20, SMALL_DICE, getValue(), _solid); drawDiceImage((SMALL_DICE+offSet)*2, _yPositionDice, SMALL_DICE, getValue(), _solid); drawDiceImage((SMALL_DICE+offSet)*3, _yPositionDice+20, SMALL_DICE, getValue(), _solid); drawDiceImage((SMALL_DICE+offSet)*4, _yPositionDice, SMALL_DICE, getValue(), _solid); break } _display->display(); delay(rollDelay); rollDelay = rollDelay - 20; } } void Dice::clearGraphics (void){ _display->fillRect(0,16,127,48, SSD1306_BLACK); } // Draws the image of the dice void Dice::drawDiceImage(int x, int y, int size, int value, bool solid){ //There is a margin around the edge //Then the middle is split in to three points int margin = size/4; int diceRadius = size /6; int dotRadius = size/10; int diceSegment = (size - (margin * 2))/2; // Set spacing of the dots on the dice int offset0 = margin; int offset1 = margin + diceSegment; int offset2 = margin + (diceSegment *2); // Set position of each on the dots on the dice int leftTopX = x+offset0; int leftTopY = y+offset0; int leftMiddleX = x+offset0; int leftMiddleY = y+offset1; int leftBottomX = x+offset0; int leftBottomY = y+offset2; int middleMiddleX = x+offset1; int middleMiddleY = y+offset1; int rightTopX = x+offset2; int rightTopY = y+offset0; int rightMiddleX = x+offset2; int rightMiddleY = y+offset1; int rightBottomX = x+offset2;; int rightBottomY = y+offset2;; drawDiceShape(x, y, size, diceRadius, solid); if (_maxDiceValue <= 6){ if(value == 2 ||value == 3 ||value == 4 ||value == 5 ||value == 6){ drawDiceDots(leftTopX, leftTopY, dotRadius, solid); // Left Top drawDiceDots(rightBottomX, rightBottomY, dotRadius, solid); // Right Bottom } if(value == 6){ drawDiceDots(leftMiddleX, leftMiddleY, dotRadius, solid); // Left Middle drawDiceDots(rightMiddleX, rightMiddleY, dotRadius, solid); // Right Middle } if(value == 4 ||value == 5 ||value == 6){ drawDiceDots(leftBottomX, leftBottomY, dotRadius, solid); // Left Bottom drawDiceDots(rightTopX, rightTopY, dotRadius, solid); // Right Top } if(value == 1 ||value == 3 ||value == 5){ drawDiceDots(middleMiddleX, middleMiddleY, dotRadius, solid); // Middle } } #ifdef ALLOW_DICE_TEXT else { drawTextNumber(x, y, size, value, solid); } #endif } #ifdef ALLOW_DICE_TEXT void Dice::drawTextNumber (int x, int y, int size, int value, bool solid){ char charArray[6]; itoa(value,charArray,10); if (solid){ _display->setTextColor(SSD1306_BLACK); }else{ _display->setTextColor(SSD1306_WHITE); } switch (size){ case SMALL_DICE: _display->setFont(&FreeSansBold9pt7b); if (value < 10){ _display->setCursor(x+7,y+18); _display->write(charArray); }else if (value>=10 && value <=19){ _display->setCursor(x+1,y+18); _display->write(charArray[0]); _display->setCursor(x+11,y+18); _display->write(charArray[1]); }else{ _display->setCursor(x+2,y+18); _display->write(charArray[0]); _display->setCursor(x+12,y+18); _display->write(charArray[1]); } break case MEDIUM_DICE: _display->setFont(&FreeSansBold12pt7b); if (value < 10){ _display->setCursor(x+8,y+21); _display->write(charArray); }else if (value >=10 && value <=19){ _display->setCursor(x+1,y+21); _display->write(charArray[0]); _display->setCursor(x+12,y+21); _display->write(charArray[1]); } else{ _display->setCursor(x+1,y+21); _display->write(charArray); } break case LARGE_DICE: _display->setFont(&FreeSansBold18pt7b); if (value < 10){ _display->setCursor(x+11,y+31); _display->write(charArray); }else if (value>= 10 && value<=19){ _display->setCursor(x+2,y+31); _display->write(charArray[0]); _display->setCursor(x+18,y+31); _display->write(charArray[1]); }else{ _display->setCursor(x+2,y+31); _display->write(charArray[0]); _display->setCursor(x+19,y+31); _display->write(charArray[1]); } break case X_LARGE_DICE: _display->setFont(&FreeSansBold18pt7b); if (value < 10){ _display->setCursor(x+12,y+33); _display->write(charArray); }else if (value>= 10 && value<=19){ _display->setCursor(x+3,y+33); _display->write(charArray[0]); _display->setCursor(x+18,y+33); _display->write(charArray[1]); }else{ _display->setCursor(x+3,y+33); _display->write(charArray[0]); _display->setCursor(x+21,y+33); _display->write(charArray[1]); } break } } #endif void Dice::drawDiceShape (int x, int y, int size, int radius, bool solid){ if (solid == false){ _display->drawRoundRect(x, y, size, size, radius, SSD1306_WHITE); }else { _display->fillRoundRect(x, y, size, size, radius, SSD1306_WHITE); } } void Dice::drawDiceDots (int x, int y, int radius, bool solid ){ if (solid == false){ _display->drawCircle(x,y,radius,SSD1306_WHITE); } else { _display->fillCircle(x,y,radius,SSD1306_BLACK); } }

Download

Source Code

A zip file containing the complete Arduino dice project

Zip file containing source code

Arduino Libraries

Library Name Author Version
Adafruit GFX Library Adafruit 1.11.9
Adafruit SSD1306 Adafruit 2.5.9