John Bradnam
Published © GPL3+

Animated Matrix Dice

Spice up your next board game with this fun animated dice that uses a 8x8 LED matrix display

IntermediateFull instructions provided6 hours1,370
Animated Matrix Dice

Things used in this project

Hardware components

ATtiny85
Microchip ATtiny85
×1
MAXREFDES99# MAX7219 Display Driver Shield
Maxim Integrated MAXREFDES99# MAX7219 Display Driver Shield
Get a kit from eBay - Search for "MAX7219 dot matrix module" - Get the one with the blue board
×1
Battery, 3.7 V
Battery, 3.7 V
3.7V 120mA/Hr
×1
2 Pin JST 2mm right angle socket
×1
Through Hole Resistor, 1.5 kohm
Through Hole Resistor, 1.5 kohm
0805 SMD variant
×1
Slide Switch, DPDT
Slide Switch, DPDT
Get one from eBay - Search for "Mini Slide Switch On-OFF 2Position Micro Slide Toggle Switch SMD"
×1
Vibration switch (cylinder and ball bearing type)
Get from eBay - Search for "SW-460 Tilt Sensor Electronic Vibration Sensor Switch"
×2

Software apps and online services

Arduino IDE
Arduino IDE

Hand tools and fabrication machines

AVR ISP Programmer
This is my AVR ISP Programmer that I created to program Amtel AVR microcontrollers
Digispark Programmer
This is my Digispark Development Kit that I created program ATTiny85 microcontrollers using the Digispark ennvironment

Story

Read more

Custom parts and enclosures

Case

Schematics

Schematic

PCB

Eagle Files

Schematic and PCB in Eagle format

Code

MatrixDiceV2.ino

C/C++
Updated software to run on an ATtiny85 micro-controller
/*
Blog-Artikel: Schttel-Wrfel mit LED Matrix und Bewegungssimulation

https://techpluscode.de/schuettel-wuerfel-mit-led-matrix-und-bewegungssimulation/

techpluscode.de
Copyright 2019 von Thomas Angielsky

2019-09-18 jbrad2089@gmail.com
  Reduced RAM by making DicePic a int8_t array rather than a int array
  Changed pin values from variables to compiler constants
  Removed Serial.begin reference to eliminate inclusion of Serial library
  Reduced RAM requirement to 319 bytes, Flash to 5768 bytes to fit into a Digispark
  Changed PIN numbers for a Digispark board
  Removed unsupported digitalPinToInterrupt references
  Changed ISR as ATTiny85 has only one interrupt vector for all pin changes
*/

//Bibliothek fr die Ansteuerung der 8x8 LED-Matrix einbinden
//Include lib for the 8x8 LED matrix
#include "LedControl.h"


#define PinTiltX 0 //Pin fr Tiltsensor X
#define PinTiltY 2 //Pin fr Tiltsensor Y

//Pins der LED-Matrix
//Pins of the LED matrix
#define PinCLK 4  
#define PinCS 3
#define PinDIN 1

LedControl lc = LedControl(PinDIN, PinCLK, PinCS, 1);

//Koordinaten der Wrfelaugen in der LED-Matrix
//Coordinates of the Dice points in the LED matrix
const int8_t DicePic[8][6][2] =
{
  { //leere Matrix und Startposition:
    {9,9}, //1. Punkt
    {9,8}, //2. Punkt
    {9,7}, //3. Punkt
    {9,6}, //4. Punkt
    {9,5}, //5. Punkt
    {9,4} //6. Punkt
  },
  { //1:
    {4,4}, //1. Punkt
    {-1,-1}, //2. Punkt
    {-1,-1}, //3. Punkt
    {-1,-1}, //4. Punkt
    {-1,-1}, //5. Punkt
    {-1,-1}  //6. Punkt
  },
  { //2:
    {2,2}, //1. Punkt
    {6,6}, //2. Punkt
    {-1,-1}, //3. Punkt
    {-1,-1}, //4. Punkt
    {-1,-1}, //5. Punkt
    {-1,-1}  //6. Punkt
  },
  { //3:
    {2,6}, //1. Punkt
    {6,2}, //2. Punkt
    {4,4}, //3. Punkt
    {-1,-1}, //4. Punkt
    {-1,-1}, //5. Punkt
    {-1,-1}  //6. Punkt
  },
  { //4:
    {2,2}, //1. Punkt
    {2,6}, //2. Punkt
    {6,2}, //3. Punkt
    {6,6}, //4. Punkt
    {-1,-1}, //5. Punkt
    {-1,-1}  //6. Punkt
  },
  { //5:
    {2,2}, //1. Punkt
    {2,6}, //2. Punkt
    {6,2}, //3. Punkt
    {6,6}, //4. Punkt
    {4,4}, //5. Punkt
    {-1,-1}  //6. Punkt
  },
  { //6:
    {2,1}, //1. Punkt
    {2,4}, //2. Punkt
    {2,7}, //3. Punkt
    {6,1}, //4. Punkt
    {6,4}, //5. Punkt
    {6,7}  //6. Punkt
  },
  { //Start:
    {-1,-1}, //1. Punkt
    {-1,-1}, //2. Punkt
    {-1,-1}, //3. Punkt
    {-1,-1}, //4. Punkt
    {-1,-1}, //5. Punkt
    {-1,-1}  //6. Punkt
  }
  };


//Variablen der Wrfelaugen: Position, Richtung, Geschwindigkeit fr X und Y
//Variables of the dice: position, direction, speed for X and Y
float DiceXpos[6];
float DiceXdir[6];
volatile byte DiceXspeed[6];
float DiceYpos[6];
float DiceYdir[6];
volatile byte DiceYspeed[6];


int DiceValue;
unsigned long timestamp;
byte Mode;
int volatile shakes;
int ShakesPerSecond;
int currentStep;

//Pin Change interrupt on ATTiny85
//Test which pin changed and update accordingly
ISR(PCINT0_vect)
{
  if (Mode == 0) 
  {
    byte pins = PINB;   //get the current state of the pins
    for (int i = 0; i < 6; i++) 
    {
      if ((pins & 0b00000001) == 0 && DiceXspeed[i] < 255) 
      {
          //Speed-up dice in X
          DiceXspeed[i] = DiceXspeed[i] + 5;
      }
      if ((pins & 0b00000100) == 0 && DiceYspeed[i] < 255) 
      {
          //Speed-up dice in Y
          DiceYspeed[i] = DiceYspeed[i] + 5;
      }
    }
  }  
  //Count Shakes
  shakes = shakes + 1;
  timestamp = millis();
}

void ShowLed(int x, int y, bool onoff) {
//LED nur anzeigen, wenn im sichtbaren Bereich
//show only, when x/y in matrix
  if ((x<8) and (y<8) and (x>=0) and (y>=0)) {
    lc.setLed(0, x, y, onoff);
  }
}

void ShowDot(int x, int y, bool onoff) {
//Wrfel-Auge anzeigen oder ausblenden
//Show or hide dice point
  ShowLed(x-1, y-1, onoff);
  ShowLed(x, y-1, onoff);
  ShowLed(x-1, y, onoff);
  ShowLed(x, y, onoff);
}

void ShowDicePic(int value) {
//Wurf anzeigen
//Show dice

boolean done;

  //alle Punkte von der aktuellen Position aus zur Zielposition von DiceValue bewegen
  //move all points from current position to destination of DiceValue
  for (int i = 0; i < 6; i++) {
    DiceXspeed[i]=100;
    DiceYspeed[i]=100;

    //Werte fr X berechnen
    //Calc x values
    DiceXdir[i]=0;
    if (int(DiceXpos[i]) > DicePic[value][i][0]) {DiceXdir[i]=-1;} 
    else if (int(DiceXpos[i]) < DicePic[value][i][0]) {DiceXdir[i]=1;} 
    
    DiceYdir[i]=0;
    if (int(DiceYpos[i]) > DicePic[value][i][1]) {DiceYdir[i]=-1;} 
    else if (int(DiceYpos[i]) < DicePic[value][i][1]) {DiceYdir[i]=1;} 
  }

  //Serial.println(value);
  //Serial.println("Bewegung Start // Start moving");
  //Punkte bewegen
  do {
    //Serial.println("Bewegung // Moving");
    for (int i = 0; i < 6; i++) {
      if (int(DiceXpos[i]) != DicePic[value][i][0]) {
        DoStep(DiceXpos[i],DiceXdir[i],DiceXspeed[i],false);
      }
      if (int(DiceYpos[i]) != DicePic[value][i][1]) {
        DoStep(DiceYpos[i],DiceYdir[i],DiceYspeed[i],false);
      }
    }

    lc.clearDisplay(0);
    for (int i = 0; i < 6; i++) {
      ShowDot(int(DiceXpos[i]), int(DiceYpos[i]), true);
    }
    
    delay(50);

    //Sind alle Augen an ihrer Zielposition
    //Dice points are on destition position
    done=true;
    for (int i = 0; i < 6; i++) {
      if (int(DiceXpos[i]) != DicePic[value][i][0]) {done=false;}
      if (int(DiceYpos[i]) != DicePic[value][i][1]) {done=false;}
    }

  } while (done==false);
  //Serial.println("Bewegung Ende // End moving");

  lc.clearDisplay(0);
  for (int i = 0; i < 6; i++) {
    ShowDot(DicePic[value][i][0],DicePic[value][i][1], true);
  }
}


void DoStep(float &pos, float &dir, volatile byte &sp, bool check) {
  pos=pos+float(sp)/255*dir;

  if (check==true) {
    if (pos>7) {
        pos=7;
        dir=dir*(-1);
        }
    if (pos<1) {
        pos=1;
        dir=dir*(-1);
      }

  }
  // Geschwindigkeit wird pro Schritt langsamer
  // Velocity decreases every step
    if (sp>0) {sp=sp-1;}
}

void MoveDots() {
  //alle Wrfel einen Schritt weiter bewegen
  //move dice points one step further
  for (int i = 0; i < 6; i++) {
    //neue Koordinaten berechnen
    //calc new coordinates
    DoStep(DiceXpos[i],DiceXdir[i],DiceXspeed[i],true);
    DoStep(DiceYpos[i],DiceYdir[i],DiceYspeed[i],true);
  }

  //Wrfel-Augen anzeigen
  //show dice points
  lc.clearDisplay(0);
  for (int i = 0; i < 6; i++) {
    ShowDot(int(DiceXpos[i]), int(DiceYpos[i]), true);
  }
  
}



void setup() 
{
  //Der MAX7219 ist beim Starten im Power-Saving Modus,
  //er muss aufgeweckt werden.
  //The MAX7219 is in power-saving mode on startup,
  //we have to do a wakeup call
  lc.shutdown(0, false);
  //Helligkeit auf einen Mittelwert
  //Set the brightness to a medium values 
  lc.setIntensity(0, 4);
  //und Display lschen
  //and clear the display 
  lc.clearDisplay(0);

  randomSeed(analogRead(0));
  DiceValue=0;
  
  for (int i = 0; i < 6; i++) {
    DiceXpos[i] = DicePic[7][i][0];
    DiceYpos[i] = DicePic[7][i][1];
    
    DiceXdir[i]=random(3)-1;
    DiceYdir[i]=random(3)-1;
    DiceXspeed[i]=random(126)+120;
    DiceYspeed[i]=random(126)+120;
  }

  //Pins einstellen
  //Setup the pins
  pinMode(PinTiltX, INPUT_PULLUP);
  pinMode(PinTiltY, INPUT_PULLUP);

  //ATTiny85 only has one ISR for pin changes
  //Couldn't get attachInterrupt to work so registers are directly manipulated
  GIMSK = 0b00100000;    // turns on pin change interrupts
  PCMSK = 0b00000101;    // turn on interrupts on pins PB0 and PB2
  sei();                 // enables interrupts
 
  lc.clearDisplay(0);

  timestamp=millis();
  Mode=1;

  ShowDicePic(6);
  delay(1000);
  
  lc.clearDisplay(0);
  Mode=0;
  
  //Serial.begin(9600);
  currentStep=0;
  shakes=0;
  
}

void loop() 
{
  delay(50);
  currentStep = currentStep + 1;
  if (currentStep > 20) 
  {
    currentStep = 0;
    ShakesPerSecond = shakes;
    shakes = 0;
  }

  if (Mode==0) {
    MoveDots();
    if (millis()-timestamp>2000) {
      //seit 2 sek kein Schtteln mehr
      //there is no shaking since 2 sec
      Mode=1;
      DiceValue=random(6)+1;
      ShowDicePic(DiceValue);
    }
  }

  if (ShakesPerSecond>5) {
    //Es wird wieder geschttelt
    //shaking again
    Mode=0;
  }
}

Credits

John Bradnam

John Bradnam

141 projects • 167 followers
Thanks to Thomas Angielsky.

Comments