制造商零件编号 3000
CIRC PLAYGROUND CLASS ATMEGA32U4
Adafruit Industries LLC
License: See Original Project
Courtesy of Adafruit
Guide by Carter Nelson
Overview
Dice are another one of those game related items that have been around for a long time. They come in a wide variety of shapes and sizes, but the most common is the basic 6 sided dice, otherwise known as the D6.
In this guide, we'll show how we can simulate a D6 dice on the Circuit Playground using the NeoPixels to represent the various patterns of the dice face. We'll also use the accelerometer to simulate "rolling the dice" by detecting shaking. And because we can, we'll add a tap detect feature for quick and easy rolling.
Required Parts
This project uses the sensors already included on the Circuit Playground. The only additional items needed are batteries for power and a holder for the batteries.
* Circuit Playground
* 3 x AAA Battery Holder
* 3 x AAA Batteries (NiMH work great!)
Before Starting
If you are new to the Circuit Playground, you may want to first read these overview guides.
This project will use the Arduino IDE. Make sure you have added the board support for the Circuit Playground as well as installed the Circuit Playground library. MUST DO BOTH. This is covered in the guides linked above.
Random, or Not?
The whole point of a dice is to provide a way to randomly come up with a number. In the case of a D6 dice, this would be a number from 1 to 6. The Arduino library provides a random function we can use, but let's take a look at how it behaves.
Try running the simple sketch below.
///////////////////////////////////////////////////////////////////////////////
// Circuit Playground Random Demo
//
// Author: Carter Nelson
// MIT License (https://opensource.org/licenses/MIT)
#include <Adafruit_CircuitPlayground.h>
///////////////////////////////////////////////////////////////////////////////
void setup() {
Serial.begin(9600);
CircuitPlayground.begin();
}
///////////////////////////////////////////////////////////////////////////////
void loop() {
// Wait for button press
while (!CircuitPlayground.leftButton() &&
!CircuitPlayground.rightButton()) {
// Do nothing, just waiting for a button press...
}
// Print a random number
Serial.println(random(1,7));
// Debounce delay
delay(500);
}
With this code loaded and running on the Circuit Playground, open the Serial Monitor.
Tools -> Serial Monitor
and then press either button. Each time, a random number from 1 to 6 will be printed out. Let me guess, you got the same sequence I did as shown below.
And if you reset the Circuit Playground and try this again, you will get the same sequence again. So what's going on?
In turns out that the random() function implemented in the Arduino library is only a pseudo-random function. This simply means it isn't fully random (pseudo = false). It just produces a random like sequence of numbers, and the same sequence every time.
To get around this, we need to initialize the random function with a random value. Which, to be honest, sounds a little...unusual! I mean, if we had a random value why use random() at all? But it really does make sense: if it were possible to get one random value, maybe in a round-about way, we could then take advantage of the simple built in function.
This is called seeding the function and the value is called the seed. You plant a seed of randomness, and then we can harvest any number of random numbers we need, forever!
But where can we come up with a random seed value? One way is to use some of the (hopefully) unconnected pads on the Circuit Playground and read in their analog values. Since the pads are not connected, the value returned by a call to analogRead() will contain noise. Noise is random, and that's what we want.
Here's a new version of the code that includes a call to randomSeed() in the setup() . This seed is generated by reading all four of the available analog inputs.
///////////////////////////////////////////////////////////////////////////////
// Circuit Playground Random Demo with Seed
//
// Author: Carter Nelson
// MIT License (https://opensource.org/licenses/MIT)
#include <Adafruit_CircuitPlayground.h>
///////////////////////////////////////////////////////////////////////////////
void setup() {
Serial.begin(9600);
CircuitPlayground.begin();
// Seed the random function with noise
int seed = 0;
seed += analogRead(12);
seed += analogRead(7);
seed += analogRead(9);
seed += analogRead(10);
randomSeed(seed);
}
///////////////////////////////////////////////////////////////////////////////
void loop() {
// Wait for button press
while (!CircuitPlayground.leftButton() &&
!CircuitPlayground.rightButton()) {
// Do nothing, just waiting for a button press...
}
// Print a random number
Serial.println(random(1,7));
// Debounce delay
delay(500);
}
Load this code, open the Serial Monitor, and try again by pressing the buttons. Hopefully you get a different sequence this time, and it's different than the one I got.
While this isn't perfect, it will work for our needs. This is what we will use to generate the random number to simulate a dice roll
Dice Faces
A D6 dice has 6 different patterns of dots on each of its 6 faces to represent the values 1 through 6. We have 10 NeoPixels on our Circuit Playground, so we've got enough lights. We'll turn 1 light on for a roll of 1, 2 lights for 2, etc. We just need to come up with which NeoPixels to use.
The NeoPixel layout could be anything, but the following attempts to match the D6 dice face patterns as much as possible.
ROLL = 1
NeoPixels: 2
ROLL = 2
NeoPixels: 4, 9
ROLL = 3
NeoPixels: 0, 4, 7
ROLL = 4
NeoPixels: 1, 3, 6, 8
ROLL = 5
NeoPixels: 0, 2, 4, 5, 9
ROLL = 6
NeoPixels: 0, 2, 4, 5, 7, 9
Storing the Face Patterns
Here's a simple approach for storing the above patterns of NeoPixels. The idea is to use a two dimensional (2D) array. The first dimension is simply the number of the roll, i.e. 1 through 6, so we'll need 6 entries. The second dimension of the array specifies the NeoPixels to light up for the dice roll. Since we are lighting up 1 NeoPixel per dice value, this will also need to have 6 entries.
So, we can do something like this:
uint8_t dicePixels[6][6] = { // Pixel pattern for dice roll
{ 2, 0, 0, 0, 0, 0 } , // Roll = 1
{ 4, 9, 0, 0, 0, 0 } , // 2
{ 0, 4, 7, 0, 0, 0 } , // 3
{ 1, 3, 6, 8, 0, 0 } , // 4
{ 0, 2, 4, 5, 9, 0 } , // 5
{ 0, 2, 4, 5, 7, 9 } , // 6
};
You'll see this get used later in the final code.
Shake Detect
To roll a dice, you pick it up and shake it. Then you toss it down and see where it lands. We'll want to do this with our Circuit Playground dice as well. Let's see if we can use the accelerometer to sense when the Circuit Playground is being shaken.
For an overview of how the accelerometer works on the Circuit Playground, check out this guide. You will see the following plot shown there.
What we are interested in is the SHAKE IT UP! part. That's where the Circuit Playground is being shaken. The lines are the return values from motionX() , motionY() , and motionZ() . This is a mess of positive and negative values with little hope of using simple logic to determine what is going on.
For detecting shaking, all we really care about is the magnitude of the acceleration. We don't care what direction it is being shaken. Therefore, we can use the total acceleration magnitude.You can use the simple sketch below to try this out.
///////////////////////////////////////////////////////////////////////////////
// Circuit Playground Total Acceleration
//
// Author: Carter Nelson
// MIT License (https://opensource.org/licenses/MIT)
#include <Adafruit_CircuitPlayground.h>
float X, Y, Z, totalAccel;
///////////////////////////////////////////////////////////////////////////////
void setup() {
Serial.begin(9600);
CircuitPlayground.begin();
CircuitPlayground.setAccelRange(LIS3DH_RANGE_8_G);
}
///////////////////////////////////////////////////////////////////////////////
void loop() {
X = 0;
Y = 0;
Z = 0;
for (int i=0; i<10; i++) {
X += CircuitPlayground.motionX();
Y += CircuitPlayground.motionY();
Z += CircuitPlayground.motionZ();
delay(1);
}
X /= 10;
Y /= 10;
Z /= 10;
totalAccel = sqrt(X*X + Y*Y + Z*Z);
Serial.println(totalAccel);
delay(100);
}
To smooth things out, we take 10 readings and average them. Then the total acceleration is computed with:
totalAccel = sqrt(X*X + Y*Y + Z*Z);
With the above sketch loaded and running on the Circuit Playground, open the Serial Plotter
Tools -> Serial Plotter
and give the Circuit Playground a shake. You should see something like what is shown in the figure below.
It still looks noisy, but if we pick an appropriate value for ROLL_THRESHOLD, then we can detect shaking by simply comparing it to the total acceleration value.
The sketch below uses this idea to play a sound if it detects shaking.
///////////////////////////////////////////////////////////////////////////////
// Circuit Playground Shake Detect
//
// Author: Carter Nelson
// MIT License (https://opensource.org/licenses/MIT)
#include <Adafruit_CircuitPlayground.h>
#define ROLL_THRESHOLD 30 // Total acceleration threshold for roll detect
float X, Y, Z, totalAccel;
///////////////////////////////////////////////////////////////////////////////
void setup() {
Serial.begin(9600);
CircuitPlayground.begin();
CircuitPlayground.setAccelRange(LIS3DH_RANGE_8_G);
}
///////////////////////////////////////////////////////////////////////////////
void loop() {
// Compute total acceleration
X = 0;
Y = 0;
Z = 0;
for (int i=0; i<10; i++) {
X += CircuitPlayground.motionX();
Y += CircuitPlayground.motionY();
Z += CircuitPlayground.motionZ();
delay(1);
}
X /= 10;
Y /= 10;
Z /= 10;
totalAccel = sqrt(X*X + Y*Y + Z*Z);
// Play sound if rolling
if (totalAccel > ROLL_THRESHOLD) {
CircuitPlayground.playTone(800, 100);
}
}
Play around with different values of ROLL_THRESHOLD. If it's too low, the detect is too sensitive. If it's too high, you'll break your arm trying to get it to beep. The value of 30 in the above sketch seemed to work OK for me.
Tap Detect
After a lot of shaking, you might wear your arm out. So let's add another way to roll the dice. The accelerometer on the Circuit Playground has a built in tap detect function. Let's use that to allow the dice to be rolled by taping the Circuit Playground.
There are two library functions associated with this feature:
setAccelTap();
getAccelTap();
There are also a couple of example sketches that you can look at to explore the tap detect feature. You can access them via:
File -> Examples -> Adafruit Circuit Playground -> accelTap
and
File -> Examples -> Adafruit Circuit Playground -> comm_badge
In each of these you will see something like this cryptic line:
attachInterrupt(digitalPinToInterrupt(7), myFunction, RISING);
This is setting up a function callback for an interrupt. This is somewhat of an advanced topic, but you can think of it as off loading the tap detection work to the accelerometer hardware. What the above line of code does is set things up so that when the accelerometer detects tap(s), the function myFunction will be called. It's up to you to create myFunction to do what you want, and the name can be any valid name.
If you look at the code in the accelTap example, you will see that there is no code in the loop() function. That's because the function tapTime() is being called via the interrupt.
You can still put code in the loop() function if you want. In fact, we'll be doing that for our D6 Dice. Here's another example that will be more like how we will use the tap detect for our dice. The callback function simply sets a flag. We then look for the state of that flag in loop() .
///////////////////////////////////////////////////////////////////////////////
// Circuit Playground Tap Detect
//
// Author: Carter Nelson
// MIT License (https://opensource.org/licenses/MIT)
#include <Adafruit_CircuitPlayground.h>
#define TAP_THRESHOLD 10 // Tap detect threshold
bool tapDetected;
///////////////////////////////////////////////////////////////////////////////
void tapCallback() {
tapDetected = true;
}
///////////////////////////////////////////////////////////////////////////////
void setup(void) {
Serial.begin(9600);
CircuitPlayground.begin();
CircuitPlayground.setAccelRange(LIS3DH_RANGE_8_G);
CircuitPlayground.setAccelTap(2, TAP_THRESHOLD);
attachInterrupt(digitalPinToInterrupt(7), tapCallback, FALLING);
tapDetected = false;
}
///////////////////////////////////////////////////////////////////////////////
void loop() {
if (tapDetected) {
Serial.println("TAP!");
tapDetected = false;
}
}
Note that we set up the detect for double tap by passing in a 2 in the setup() function:
CircuitPlayground.setAccelTap(2, TAP_THRESHOLD);
The way this works it that in the loop() function we look for a boolean value which is set to true in the interrupt callback function tapCallback() . This boolean is then set to false so that the print statement only happens once, until another double tap causes it to be true. Etc. Etc.
D6 Dice Code
OK, let's put all the pieces together. Here's the final D6 Dice code. With this code loaded and running on the Circuit Playground you can pick it up and shake it to make a roll. You can also just double tap it.
///////////////////////////////////////////////////////////////////////////////
// Circuit Playground D6 Dice
//
// Roll them bones.
//
// Author: Carter Nelson
// MIT License (https://opensource.org/licenses/MIT)
#include <Adafruit_CircuitPlayground.h>
#define ROLL_THRESHOLD 30 // Total acceleration threshold for roll detect
#define TAP_THRESHOLD 10 // Tap detect threshold
#define DICE_COLOR 0xEA6292 // Dice digits color
unsigned long rollStartTime;
bool rolling;
bool newRoll;
bool tapDetected;
uint8_t rollNumber;
float X, Y, Z, totalAccel;
uint8_t dicePixels[6][6] = { // Pixel pattern for dice roll
{ 2, 0, 0, 0, 0, 0 } , // Roll = 1
{ 4, 9, 0, 0, 0, 0 } , // 2
{ 0, 4, 7, 0, 0, 0 } , // 3
{ 1, 3, 6, 8, 0, 0 } , // 4
{ 0, 2, 4, 5, 9, 0 } , // 5
{ 0, 2, 4, 5, 7, 9 } , // 6
};
///////////////////////////////////////////////////////////////////////////////
void tapCallback() {
tapDetected = true;
}
///////////////////////////////////////////////////////////////////////////////
void setup() {
Serial.begin(9600);
CircuitPlayground.begin();
CircuitPlayground.setBrightness(100);
CircuitPlayground.setAccelRange(LIS3DH_RANGE_8_G);
CircuitPlayground.setAccelTap(2, TAP_THRESHOLD);
// Setup tap detection and callback function
attachInterrupt(digitalPinToInterrupt(7), tapCallback, RISING);
// Seed the random function with noise
int seed = 0;
seed += analogRead(12);
seed += analogRead(7);
seed += analogRead(9);
seed += analogRead(10);
randomSeed(seed);
// Initialize the global states
newRoll = false;
rolling = false;
tapDetected = false;
}
///////////////////////////////////////////////////////////////////////////////
void loop() {
// Compute total acceleration
X = 0;
Y = 0;
Z = 0;
for (int i=0; i<10; i++) {
X += CircuitPlayground.motionX();
Y += CircuitPlayground.motionY();
Z += CircuitPlayground.motionZ();
delay(1);
}
X /= 10;
Y /= 10;
Z /= 10;
totalAccel = sqrt(X*X + Y*Y + Z*Z);
// Check for rolling
if ((totalAccel > ROLL_THRESHOLD) || tapDetected) {
rollStartTime = millis();
newRoll = true;
rolling = true;
tapDetected = false;
}
// Rolling momentum
// Keep rolling for a period of time even after shaking has stopped.
if (newRoll) {
if (millis() - rollStartTime > 1000) rolling = false;
}
// Compute a random number from 1 to 6
rollNumber = random(1,7);
// Display status on NeoPixels
if (rolling) {
// Make some noise and show the dice roll number
CircuitPlayground.playTone(random(400,2000), 20, false);
CircuitPlayground.clearPixels();
for (int p=0; p<rollNumber; p++) {
CircuitPlayground.setPixelColor(dicePixels[rollNumber-1][p], DICE_COLOR);
}
delay(20);
} else if (newRoll) {
// Show the dice roll number
newRoll = false;
CircuitPlayground.clearPixels();
for (int p=0; p<rollNumber; p++) {
CircuitPlayground.setPixelColor(dicePixels[rollNumber-1][p], DICE_COLOR);
}
}
}
Questions and Code Challenges
The following are some questions related to this project along with some suggested code challenges. The idea is to provoke thought, test your understanding, and get you coding!
While the sketches provided in this guide work, there is room for improvement and additional features. Have fun playing with the provided code to see what you can do with it.
Questions
Code Challenges