ESP32を使って、ボタンを押すとサイコロが振られる「電子サイコロ」を作ってみましょう。
今回は、OLEDディスプレイにサイコロの目を表示し、ボタンを押すと1〜6の数字がランダムに切り替わるプログラムを作成します。
難しい電子部品は必要ありません。ESP32、OLED、タクトスイッチがあれば製作できます。
また、ESP32-C3を搭載したKUMITATE-C3なら、OLEDとスイッチが基板に搭載されているため、追加の配線なしで製作できます。
この記事では、Arduino IDEでのプログラム作成から、乱数の仕組み、スイッチ入力、OLED表示まで、初心者向けに解説します。
今回作る電子サイコロ
完成する電子サイコロの動作は次のとおりです。
- 起動するとOLEDにサイコロの目が表示される
- SW1を押すとサイコロの目が高速で切り替わる
- 約0.8秒後に表示が止まる
- 最後に1〜6のいずれかの目が表示される
- もう一度ボタンを押すと、再びサイコロを振る
単に数字を表示するだけでなく、実際のサイコロのように点(ドット)で目を表現します。
例えば、3が出た場合は次のような表示になります。
+--------------------+
| ELECTRONIC DICE |
| |
| +-------+ |
| | ● | |
| | ● | |
| | ● | |
| +-------+ |
| |
| SW1: ROLL |
+--------------------+
※上記は表示イメージです。実際にはOLED上に四角形と丸を描画します。
必要なもの
| 部品 | 数量 | 用途 |
|---|---|---|
| ESP32開発ボード | 1 | マイコン |
| OLEDディスプレイ | 1 | サイコロの目を表示 |
| タクトスイッチ | 1 | サイコロを振る |
| USBケーブル | 1 | 電源供給・書き込み |
| パソコン | 1 | プログラム開発 |
KUMITATE-C3を使用する場合は、基板本体とUSBケーブルだけで始められます。
ESP32とOLED・スイッチの接続
今回はKUMITATE-C3のピン配置に合わせてプログラムを作成します。
| 機能 | GPIO |
|---|---|
| OLED SDA | GPIO1 |
| OLED SCL | GPIO2 |
| SW1 | GPIO21 |
OLEDはI²C通信で制御します。今回使用するOLEDのI²Cアドレスは0x3Cです。
KUMITATE-C3のSW1は、押すとLOWになる構成です。
一般的なESP32開発ボードを使用する場合は、使用するボードに合わせてGPIOの番号を変更し、OLEDとスイッチを配線してください。
Arduino IDEにライブラリをインストールする
今回のプログラムでは、次の2つのライブラリを使用します。
- Adafruit GFX Library
- Adafruit SSD1306
Arduino IDEの「ツール」ではなく、「スケッチ」→「ライブラリをインクルード」→「ライブラリを管理」から、それぞれ検索してインストールしてください。
Wire.hはESP32のArduino環境に含まれるI²C通信用のライブラリです。
ESP32電子サイコロのサンプルプログラム
それでは、実際にプログラムを作成します。
次のコードをArduino IDEに貼り付けてください。
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
// OLED設定
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_ADDRESS 0x3C
#define SDA_PIN 1
#define SCL_PIN 2
#define SW_PIN 21
Adafruit_SSD1306 display(
SCREEN_WIDTH,
SCREEN_HEIGHT,
&Wire,
-1
);
// サイコロの状態
int diceValue = 1;
bool rolling = false;
// 時間管理
unsigned long rollStartTime = 0;
unsigned long lastFrameTime = 0;
const unsigned long ROLL_TIME = 800;
const unsigned long FRAME_TIME = 80;
const unsigned long DEBOUNCE_TIME = 30;
// スイッチ管理
bool lastRawSw = HIGH;
bool stableSw = HIGH;
unsigned long lastDebounceTime = 0;
// サイコロの点を描画
void drawDot(int x, int y) {
display.fillCircle(
x, y, 3, SSD1306_WHITE
);
}
// サイコロをOLEDに描画
void drawDice(int value) {
display.clearDisplay();
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
display.setCursor(0, 0);
display.print("ELECTRONIC DICE");
// サイコロの外枠
display.drawRoundRect(
41, 10, 46, 46, 5, SSD1306_WHITE
);
// ドットの位置
const int L = 52;
const int C = 64;
const int R = 76;
const int T = 21;
const int M = 33;
const int B = 45;
// 1・3・5の中央
if (value == 1 || value == 3 ||
value == 5) {
drawDot(C, M);
}
// 2以上の斜め2点
if (value >= 2) {
drawDot(L, T);
drawDot(R, B);
}
// 4以上の追加2点
if (value >= 4) {
drawDot(R, T);
drawDot(L, B);
}
// 6の場合の左右中央
if (value == 6) {
drawDot(L, M);
drawDot(R, M);
}
// 画面下部のメッセージ
display.setCursor(0, 57);
if (rolling) {
display.print("ROLLING...");
} else {
display.print("SW1: ROLL");
}
display.display();
}
void setup() {
Serial.begin(115200);
pinMode(SW_PIN, INPUT_PULLUP);
// I2C初期化
Wire.begin(SDA_PIN, SCL_PIN);
// OLED初期化
if (!display.begin(
SSD1306_SWITCHCAPVCC,
OLED_ADDRESS)) {
Serial.println("OLED ERROR");
while (true) {
delay(100);
}
}
// 乱数の初期化
randomSeed(micros());
// 最初の表示
drawDice(diceValue);
}
void loop() {
unsigned long now = millis();
// スイッチの状態を取得
bool rawSw = digitalRead(SW_PIN);
// スイッチ変化時刻を記録
if (rawSw != lastRawSw) {
lastDebounceTime = now;
}
// チャタリング対策
if (now - lastDebounceTime >= DEBOUNCE_TIME) {
if (rawSw != stableSw) {
stableSw = rawSw;
// 押した瞬間にサイコロ開始
if (stableSw == LOW && !rolling) {
rolling = true;
rollStartTime = now;
lastFrameTime = now;
diceValue = random(1, 7);
drawDice(diceValue);
}
}
}
lastRawSw = rawSw;
// サイコロを振っている間の処理
if (rolling) {
// 指定時間が経過したら停止
if (now - rollStartTime >= ROLL_TIME) {
rolling = false;
diceValue = random(1, 7);
drawDice(diceValue);
Serial.print("Dice: ");
Serial.println(diceValue);
}
// 一定間隔で目を切り替える
else if (now - lastFrameTime >= FRAME_TIME) {
lastFrameTime = now;
diceValue = random(1, 7);
drawDice(diceValue);
}
}
}
動作を確認してみよう
プログラムを書き込んだら、KUMITATE-C3のSW1を押してみましょう。
OLEDに表示されているサイコロの目が高速で切り替わり、約0.8秒後に停止します。
また、Arduino IDEのシリアルモニターを115200bpsで開くと、次のように結果が表示されます。
Dice: 4
Dice: 1
Dice: 6
Dice: 2
数字は毎回1〜6の範囲で変化します。
なお、ランダムに選ばれるため、前回と同じ目が連続して出ることもあります。
プログラムの仕組みを解説
① random()で1〜6の乱数を作る
電子サイコロの中心となるのがrandom()関数です。
diceValue = random(1, 7);
Arduinoのrandom(最小値, 最大値)は、最小値以上、最大値未満の整数を返します。
そのため、random(1, 7)では次の6種類の数字が生成されます。
1, 2, 3, 4, 5, 6
7は含まれません。
これによって、実際のサイコロと同じ1〜6の数字をランダムに生成できます。
ただし、random()は疑似乱数を生成する関数です。ゲームなどに使用することはできますが、暗号などの用途には適していません。
② OLEDにサイコロの目を描画する
今回のプログラムでは、単に数字を表示するのではなく、サイコロの目を丸で描画しています。
丸の描画にはfillCircle()を使用します。
display.fillCircle(
x, y, 3, SSD1306_WHITE
);
この関数は、指定した座標を中心として半径3ピクセルの塗りつぶした円を描画します。
サイコロの目は、1〜6の値に応じて描画する位置を変更することで表現できます。
例えば、1の場合は中央の1点だけを描画します。
if (value == 1) {
drawDot(C, M);
}
実際のサンプルプログラムでは、条件をまとめることで記述を短くしています。
また、画面に描画した情報を実際のOLEDへ転送するのが、次の関数です。
display.display();
この命令を実行することで、画面の表示内容が更新されます。
③ millis()でアニメーションを制御する
電子サイコロらしくするため、約0.8秒間だけ目を切り替えるアニメーションを入れています。
const unsigned long ROLL_TIME = 800;
const unsigned long FRAME_TIME = 80;
ROLL_TIMEは、サイコロを振る時間です。
FRAME_TIMEは、目を切り替える間隔です。
今回の設定では、約80ミリ秒ごとに目が変化し、約800ミリ秒後に停止します。
時間の管理にはmillis()を使用しています。
delay()で長時間プログラムを停止させる方法とは異なり、経過時間を確認しながら処理を進めています。
そのため、サイコロを振っている間もスイッチの状態を確認できます。
④ スイッチのチャタリングを防止する
タクトスイッチには、押した瞬間に接点が細かくON/OFFを繰り返す「チャタリング」という現象があります。
これをそのままプログラムで検出すると、1回しかボタンを押していないのに、複数回押したと誤判定する可能性があります。
今回のプログラムでは、スイッチの状態が30ミリ秒間安定した場合に入力を確定させています。
const unsigned long DEBOUNCE_TIME = 30;
このように、不安定なスイッチ入力を除去する処理を「デバウンス処理」と呼びます。
スイッチは電子工作で頻繁に使用する部品なので、覚えておくと役立ちます。
さらに改造してみよう
基本的な電子サイコロが完成したら、次のような改造にも挑戦してみましょう。
① サイコロを2個にする
OLEDにサイコロを2個表示し、それぞれの目をランダムに生成します。
2つのサイコロの合計値も表示すれば、すごろくなどで使いやすくなります。
② ボタンを押している間だけ回転させる
今回のプログラムでは、ボタンを押すと一定時間サイコロが振られます。
これを変更して、ボタンを押している間は目が変化し、離した瞬間に停止する動作にしてみましょう。
スイッチの押下状態と解放状態を使い分ける練習になります。
③ ブザーで効果音を鳴らす
KUMITATE-C3にはブザーも搭載されています。
サイコロが回転している間に短い電子音を鳴らしたり、結果が確定した瞬間に音を鳴らしたりすると、ゲームらしさが増します。
④ 出た目の履歴を表示する
過去に出たサイコロの目を配列へ保存し、OLEDに表示します。
例えば、直近5回の結果を保存すれば、配列や繰り返し処理の学習にもつながります。
OLEDに何も表示されない場合
プログラムを書き込んでもOLEDに何も表示されない場合は、次の点を確認してください。
I²CのGPIO番号
KUMITATE-C3の内蔵OLEDでは、今回のプログラムでGPIO1をSDA、GPIO2をSCLとして使用しています。
別のESP32開発ボードを使用する場合は、使用するGPIO番号を確認してください。
I²Cアドレス
今回のOLEDでは0x3Cを使用しています。
別のOLEDでは異なるアドレスが使われることもあるため、必要に応じてI²Cスキャナで確認しましょう。
関連記事:Arduino・ESP32でI²Cスキャナを使う方法
ライブラリ
Arduino IDEに「Adafruit GFX Library」と「Adafruit SSD1306」の両方がインストールされているか確認してください。
KUMITATE-C3で電子工作を学ぼう
今回の電子サイコロでは、GPIOによるスイッチ入力とI²CによるOLED表示を組み合わせました。
KUMITATE-C3は、ESP32-C3を搭載した組み込みプログラミング学習用ボードです。
LED・スイッチ・OLED・ブザーなどが1枚の基板に搭載されているため、基本的な電子工作を追加配線なしで学習できます。
今回の電子サイコロのほかにも、反射神経ゲーム、デジタル時計、温湿度モニター、無線通信を使用した作品など、さまざまな電子工作に挑戦できます。
まとめ
今回はESP32を使って、OLEDにサイコロの目を表示する電子サイコロを作成しました。
今回学習した主な内容は次のとおりです。
random()による1〜6の乱数生成- GPIOによるスイッチ入力

