If your dice seem to be everywhere when you play the game, this skull might be what you’ll need to create the next.

Share on facebook
Share on twitter
Share on linkedin
Share on pinterest
Share on whatsapp

If you buy something from a this link, myelectricsparks Media may earn a commission. See our Read More.

COMPONENTS AND SUPPLIES

  • Arduino Nano R3
  • RGB Diffused Common Cathode
  • Resistor 220 ohm
  • Resistor 10k ohm
  • Photo resistor
  • 9V battery (generic)
  • Slide Switch

When we play games that involve dice, one or more of the dice inevitably end up on the floor after an over enthusiastic player tries to roll double 6’s. 

This dice skull solves the problem and, with an added Arduino Nano, lights up red whenever it detects a dice throw. I would have liked for it to make a “roar!” sound but the Nano isn’t really up to playing audio.

Step 1: Building the Skull and Battery Holder

The skull was printed 3D from a design provided through Windham Graves. Check out ThingIVerse.com for the files needed to 3D-print the head. It will take an average of 26 hours for printing using the Creality 3 S1 3D printer with standard quality settings using PLA filament.

The skull’s back is hollow, making it easy to place electronics there.

Optional: After printing, cover the skull with Epoxy with a foam squeegee which you can throw away afterwards. Once the Epoxy is dry and the skull is painted, spray paint it matte black with Rust-oleum Ultra Matte paint and primer.

Find or buy a switch that can be turned off or on. Utilize a Dremel tool to create the hole for the switch on/off close to the right ear’s skull. Make sure you don’t put the button in the spot until you are ready because wires must be connected first (more on this in the next section).

On/off Switchy

Through the switch opening, drill it into the “throat” of the skull to create a space for the RGB LED that illuminates the eyes. Utilize a drill bit similar to the size of the LED. The switch is located in the ear of the skull’s right to make the largely inaccessible left side of the mouth available to drill. The incline in the throat makes it simpler to make a hole from the left, so it’s not necessary to drill holes in the exterior of the skull to that side.

Another hole should be made to the other end of your throat to create the photoresistor. They will get identified when they disrupt the light from the photoresistor through the LED. The detection algorithm is sensitive to changes in light levels, so a simple flick of your hands around the eye holes could be enough to trigger the dice off. The closer the photoresistor is to being directly in front of that LED, the more influential the circuit is in finding dice.

This 9V battery case can be as well 3D-printed. Visit ThingIVerse.com for the print file. It’s the “compact” version of the print file that was utilized. However, there’s plenty of space to accommodate both interpretations within the skull.

Step 2 : Building the Circuit

A small PCB is utilized to store the Nano and a two-terminal connector to connect the switch and battery leads.


Step 2.1

Connect Nano LED, Photo-resistor, and any associated resistors
Photo-resistor regular resistors and LEDs are soldered together before the switch and battery are inserted into the skull. Use a small two-conductor screw-down terminal on the PCB to connect to GND and Vin GND Terminals on the Nano. The terminal posts will be helpful in the final assembly.


Step 2.2:

Solder the 9V battery connector and the switch connector
The battery holder and the switch can be joined using a 2 inch (5 centimetre) wire. Ensure you don’t connect the battery/switch leads onto the PCB.

Step 3 : Programming the Nano

				
					// Light up skull code for Arduino Nano
// (C) Copyright 2021 John Dillenburg
// john _at_ dillenburg.org
//
double avgWhenOn;
double avgWhenOff; 
long last = 0;
int lightOnLevel = 4;
long lastTrigger = 0;
long triggerCooldown = 500; // milliseconds
int detectThreshold = 10;
long recalibrateInterval = 300000; // milliseconds
long lastRecalibrate = 0;
void setup() {
 Serial.begin(115200);
 pinMode(9, OUTPUT);
 pinMode(10, OUTPUT);
 pinMode(11, OUTPUT);
 pinMode(A0, INPUT);
 calibrate();
 if (Serial) {
   Serial.print("lightOnLevel = ");
   Serial.print(lightOnLevel);
   Serial.print("   avgWhenOn = ");
   Serial.print(avgWhenOn);
   Serial.print("   avgWhenOff = ");
   Serial.println(avgWhenOff);
 }
 detectMode();
}
void calibrate() {
 lightOnLevel = 20;
 avgWhenOn = average(500, lightOnLevel);
 avgWhenOff = average(500, 0);
 while (avgWhenOff - avgWhenOn < detectThreshold && lightOnLevel < 255) {
   lightOnLevel += 16;
   avgWhenOn = average(500, lightOnLevel);
 }
 if (lightOnLevel > 255) lightOnLevel = 255;
 lastRecalibrate = millis();
}
void rgb(int r, int g, int b) {
 analogWrite(9, 255 - r);
 analogWrite(10, 255 - g);
 analogWrite(11, 255 - b);
}
void movementDetected() {
 rgb(255, 0, 0);
 delay(3000);  
}
void detectMode() {
 rgb(lightOnLevel, lightOnLevel, lightOnLevel);
}
double average(int duration, int level) {
 rgb(level, level, level);
 long start = millis();
 long count = 0;
 double sum = 0.0;
 while (millis() < start + duration) {
   sum += analogRead(A0);
   count++;
 }
 return sum / count;
}
void loop() {
 int detector = analogRead(A0);
 avgWhenOn = avgWhenOn * 0.999 + detector * 0.001;
 if (Serial && millis() > last + 1000) {
   Serial.print("detector = ");
   Serial.print(detector);
   Serial.print("  avg = ");
   Serial.println(avgWhenOn);
   last = millis();
 }
 if (detector > avgWhenOn + detectThreshold && millis() > lastTrigger + triggerCooldown) {
   if (Serial) {
     Serial.print("triggered ");
     Serial.println(detector);
   }
   movementDetected();
   lastTrigger = millis();
   detectMode();
 }
 if (millis() > lastRecalibrate + recalibrateInterval) {
   calibrate();
 }
}
				
			

The code monitors the LED intensity with the avgWhenOn variable. If a shadow is detected in front of the photo-resistor connected to pin A0, the value read from A0 will rise, activating the movement caught () method. This () movement will turn the LED on for 3 minutes.

The avgWhenOn variable utilizes the infinite impulse response method to combine the most recent photo-resistor readings with the previous lessons. I chose 0.999 or 0.001 to be the weights. It might appear to weigh the most recent reading slightly, but this is not the case. Nano loop() function is used so frequently that the variable settles to a minimum value within a fraction of a second.

These Serial print commands aren’t required and were programmed to ensure that, once installed, they can be removed.

Step 4: Putting it Together

In the bottom or back part of the skull. The photoresistor is put in the hole in the left-hand side of the throat and is then secured by a large glob of glue that is hot melting.

The same applies to the LED located on the throat’s right-hand side.

Connect the wires to the switch and the 9V connector for the battery into the controller. Connect the two leads onto the smaller Nano PCB.

Put the 9V battery in the battery holder. Hot melt glue underneath.

The PCB itself can be stuffed into the bottom and be able to remain in the wires.

Bottom of skull

When you turn the skull on it will show a white light at times and then off, when it determines the initial level of light. After that, even the slightest shadow that is visible to the eyes will trigger the skull to turn the LED on to bright red.

Enjoy!

Code:

				
					// Light up skull code for Arduino Nano
// (C) Copyright 2021 John Dillenburg
// john _at_ dillenburg.org
//
double avgWhenOn;
double avgWhenOff; 
long last = 0;
int lightOnLevel = 4;
long lastTrigger = 0;
long triggerCooldown = 500; // milliseconds
int detectThreshold = 10;
long recalibrateInterval = 300000; // milliseconds
long lastRecalibrate = 0;

void setup() {
  Serial.begin(115200);
  pinMode(9, OUTPUT);
  pinMode(10, OUTPUT);
  pinMode(11, OUTPUT);
  pinMode(A0, INPUT);
  calibrate();
  if (Serial) {
    Serial.print("lightOnLevel = ");
    Serial.print(lightOnLevel);
    Serial.print("   avgWhenOn = ");
    Serial.print(avgWhenOn);
    Serial.print("   avgWhenOff = ");
    Serial.println(avgWhenOff);
  }
  detectMode();
}

void calibrate() {
  lightOnLevel = 20;
  avgWhenOn = average(500, lightOnLevel);
  avgWhenOff = average(500, 0);
  while (avgWhenOff - avgWhenOn < detectThreshold && lightOnLevel < 255) {
    lightOnLevel += 16;
    avgWhenOn = average(500, lightOnLevel);
  }
  if (lightOnLevel > 255) lightOnLevel = 255;
  lastRecalibrate = millis();
}

void rgb(int r, int g, int b) {
  analogWrite(9, 255 - r);
  analogWrite(10, 255 - g);
  analogWrite(11, 255 - b);
}

void movementDetected() {
  rgb(255, 0, 0);
  delay(3000);  
}

void detectMode() {
  rgb(lightOnLevel, lightOnLevel, lightOnLevel);
}

double average(int duration, int level) {
  rgb(level, level, level);
  long start = millis();
  long count = 0;
  double sum = 0.0;
  while (millis() < start + duration) {
    sum += analogRead(A0);
    count++;
  }
  return sum / count;
}

void loop() {
  int detector = analogRead(A0);
  avgWhenOn = avgWhenOn * 0.999 + detector * 0.001;
  if (Serial && millis() > last + 1000) {
    Serial.print("detector = ");
    Serial.print(detector);
    Serial.print("  avg = ");
    Serial.println(avgWhenOn);
    last = millis();
  }
  if (detector > avgWhenOn + detectThreshold && millis() > lastTrigger + triggerCooldown) {
    if (Serial) {
      Serial.print("triggered ");
      Serial.println(detector);
    }
    movementDetected();
    lastTrigger = millis();
    detectMode();
  }
  if (millis() > lastRecalibrate + recalibrateInterval) {
    calibrate();
  }
}
				
			

Skull print Download