振るだけで動く!Arduinoサイコロ – LEDマトリックス&傾斜センサー
必要な部品と材料
- Arduino Nano R3 × 1
- 3mm 8×8 ドットマトリックス MAX7219 × 1
- 傾斜スイッチモジュール (デジタルインターフェース) × 1
- スライドスイッチ × 1
- MT3608 DC-DC ブーストコンバーター (2V-24V → 5V/9V/12V/28V) × 1
必要な工具
- はんだごて (汎用)
- ホットグルーガン (汎用)
使用するアプリとオンラインサービス
- Arduino IDE
- Autodesk Fusion 360
プロジェクト概要
約1年前、息子とシンプルなLEDサイコロを作りました。今年は学んだことを活かし、バージョン2.0として大幅アップデート。ボタンなしで「振る」だけでサイコロが振れる、より直感的で楽しいガジェットに仕上げました。
傾斜センサーの採用
ガラス管内の金属球が接点に触れると導通する傾斜センサーを2個、90度ずらして配置。どの方向に振っても確実に検出できます。金属球の動きは音でも確認可能です。
割り込み処理で確実に検出
傾斜センサーの信号はハードウェア割り込み (ピンD2, D3) で処理。メインループのタイミングに依存せず、振動を取りこぼしません。
8×8 LEDマトリックスで演出
MAX7219ドライバー内蔵の8×8マトリックスを採用。コンパクトで安価、コードもシンプル。サイコロの目は物理演算で動き、壁で跳ね返り減速するアニメーションを実現。結果表示時もランダムな位置から正しい位置へ転がるエフェクト付きです。
電源設計
最初はCR2032×2で試しましたが、数分で電圧低下。単4電池×2 + ブーストコンバーターで5V昇圧し、VINピンから給電する構成に変更しました。
3Dプリント筐体
Fusion 360で設計。上カバー、電池ボックス付き下カバー、乳白アクリルカバーの3パーツ構成。アクリルカバーで消灯LEDが目立たず、表示がクリアになります。0.1mmのクリアランスでネジ不要の嵌め合わせ、電池交換も簡単です。
スケッチのポイント
LedControl ライブラリを使用。サイコロの目パターンを配列で定義し、各目の座標(4つのLEDで1つの目)を管理。振動検出時に全目の速度ベクトルを加算し、物理シミュレーションで移動・バウンド・減速を計算。2秒間振動がなければ乱数で目を決定し、現在位置から目標位置へ転がるアニメーションで結果表示します。
組み立て手順
- Arduino Nanoをソケットピンに実装、シールド風に配線
- LEDマトリックスのCS, CLK, DINをピン5,6,7に接続
- 傾斜センサー2個をピン2,3と5V/GNDに接続
- アクリル板をホットグルーで固定、マトリックスも同様
- スライドスイッチを取り付け配線
- ブーストコンバーターを事前に5Vに調整して組み込み
- 上下カバーを嵌め合わせて完成
必要なもの まとめ
- Arduino Nano (またはESP8266)
- 傾斜センサー × 2 (内蔵抵抗付き)
- 8×8 LEDマトリックス MAX7219
- ソケットストリップ
- オンオフスイッチ
- ユニバーサル基板
- 単4電池 × 2
コード
wuerfel8x8.ino
/* 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 */
#include "LedControl.h"
int PinTiltX = 2;
int PinTiltY = 3;
int PinCLK = 7;
int PinCS = 6;
int PinDIN = 5;
LedControl lc = LedControl(PinDIN, PinCLK, PinCS, 1);
// サイコロの目パターン (8パターン × 6目 × XY座標)
int DicePic[8][6][2] = {
{ // 0: 空/初期位置
{9,9}, {9,8}, {9,7}, {9,6}, {9,5}, {9,4}
},
{ // 1
{4,4}, {-1,-1}, {-1,-1}, {-1,-1}, {-1,-1}, {-1,-1}
},
{ // 2
{2,2}, {6,6}, {-1,-1}, {-1,-1}, {-1,-1}, {-1,-1}
},
{ // 3
{2,6}, {6,2}, {4,4}, {-1,-1}, {-1,-1}, {-1,-1}
},
{ // 4
{2,2}, {2,6}, {6,2}, {6,6}, {-1,-1}, {-1,-1}
},
{ // 5
{2,2}, {2,6}, {6,2}, {6,6}, {4,4}, {-1,-1}
},
{ // 6
{2,1}, {2,4}, {2,7}, {6,1}, {6,4}, {6,7}
},
{ // 7: スタート位置 (画面外)
{-1,-1}, {-1,-1}, {-1,-1}, {-1,-1}, {-1,-1}, {-1,-1}
}
};
float DiceXpos[6], DiceYpos[6];
float DiceXdir[6], DiceYdir[6];
volatile byte DiceXspeed[6], DiceYspeed[6];
int DiceValue;
unsigned long timestamp;
byte Mode;
volatile int shakes;
int ShakesPerSecond;
int step;
void InterruptChecks() {
shakes++;
timestamp = millis();
}
void SetSpeedX() {
if (Mode == 0) {
for (int i = 0; i < 6; i++) {
if (DiceXspeed[i] < 255) DiceXspeed[i] += 5;
}
}
InterruptChecks();
}
void SetSpeedY() {
if (Mode == 0) {
for (int i = 0; i < 6; i++) {
if (DiceYspeed[i] < 255) DiceYspeed[i] += 5;
}
}
InterruptChecks();
}
void ShowLed(int x, int y, bool onoff) {
if (x < 8 && y < 8 && x >= 0 && y >= 0) {
lc.setLed(0, x, y, onoff);
}
}
void ShowDot(int x, int y, bool onoff) {
ShowLed(x-1, y-1, onoff);
ShowLed(x, y-1, onoff);
ShowLed(x-1, y, onoff);
ShowLed(x, y, onoff);
}
void ShowDicePic(int value) {
boolean done;
for (int i = 0; i < 6; i++) {
DiceXspeed[i] = 100;
DiceYspeed[i] = 100;
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;
}
do {
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);
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);
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 += float(sp) / 255 * dir;
if (check) {
if (pos > 7) { pos = 7; dir *= -1; }
if (pos < 1) { pos = 1; dir *= -1; }
}
if (sp > 0) sp--;
}
void MoveDots() {
for (int i = 0; i < 6; i++) {
DoStep(DiceXpos[i], DiceXdir[i], DiceXspeed[i], true);
DoStep(DiceYpos[i], DiceYdir[i], DiceYspeed[i], true);
}
lc.clearDisplay(0);
for (int i = 0; i < 6; i++) ShowDot(int(DiceXpos[i]), int(DiceYpos[i]), true);
}
void setup() {
lc.shutdown(0, false);
lc.setIntensity(0, 8);
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;
}
pinMode(PinTiltX, INPUT_PULLUP);
pinMode(PinTiltY, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(PinTiltX), SetSpeedX, CHANGE);
attachInterrupt(digitalPinToInterrupt(PinTiltY), SetSpeedY, CHANGE);
lc.clearDisplay(0);
timestamp = millis();
Mode = 1;
ShowDicePic(6);
delay(1000);
lc.clearDisplay(0);
Mode = 0;
Serial.begin(9600);
step = 0;
shakes = 0;
}
void loop() {
delay(50);
step++;
if (step > 20) {
step = 0;
ShakesPerSecond = shakes;
shakes = 0;
}
if (Mode == 0) {
MoveDots();
if (millis() - timestamp > 2000) {
Mode = 1;
DiceValue = random(6) + 1;
ShowDicePic(DiceValue);
}
}
if (ShakesPerSecond > 5) Mode = 0;
}
回路図

製造プロセス
- サイコロの歴史・設計・製造:古代ツールから現代ゲームまで
- Microsoft Flight Simulator 2020用Arduinoスイッチ&LEDパネル
- Arduinoデジタルダイス: LCD表示の乱数ジェネレーター自作
- Bluetooth操作のArduinoスパイボットを自作する
- FlickMote:ジェスチャー操作のIRリモコンでスマートホームを制御
- 自作Arduino TV-B-Gone:全テレビ対応の万能リモコン
- 12メートル先から見える大型LEDマスタークロック(アラーム機能付き)を自作しよう
- 自作16×8 LEDマトリックスでテトリスゲーム:Arduino Nano実装
- ArduinoとBluetoothで作るスマート紛失防止タグ「Find Me」
- Arduinoプロジェクトのための最適化電源ソリューション
- スマートワイヤレス照明スイッチコントローラー:Arduinoで物理スイッチを遠隔操作