ESP32で電子サイコロを作ろう!OLEDとボタンで1〜6を表示【Arduino】

ESP32

ESP32を使って、ボタンを押すとサイコロが振られる「電子サイコロ」を作ってみましょう。

今回は、OLEDディスプレイにサイコロの目を表示し、ボタンを押すと1〜6の数字がランダムに切り替わるプログラムを作成します。

難しい電子部品は必要ありません。ESP32、OLED、タクトスイッチがあれば製作できます。

また、ESP32-C3を搭載したKUMITATE-C3なら、OLEDとスイッチが基板に搭載されているため、追加の配線なしで製作できます。

この記事では、Arduino IDEでのプログラム作成から、乱数の仕組み、スイッチ入力、OLED表示まで、初心者向けに解説します。

今回作る電子サイコロ

完成する電子サイコロの動作は次のとおりです。

  1. 起動するとOLEDにサイコロの目が表示される
  2. SW1を押すとサイコロの目が高速で切り替わる
  3. 約0.8秒後に表示が止まる
  4. 最後に1〜6のいずれかの目が表示される
  5. もう一度ボタンを押すと、再びサイコロを振る

単に数字を表示するだけでなく、実際のサイコロのように点(ドット)で目を表現します。

例えば、3が出た場合は次のような表示になります。

+--------------------+
|  ELECTRONIC DICE   |
|                    |
|      +-------+     |
|      |  ●    |     |
|      |   ●   |     |
|      |    ●  |     |
|      +-------+     |
|                    |
|  SW1: ROLL         |
+--------------------+

※上記は表示イメージです。実際にはOLED上に四角形と丸を描画します。

必要なもの

部品数量用途
ESP32開発ボード1マイコン
OLEDディスプレイ1サイコロの目を表示
タクトスイッチ1サイコロを振る
USBケーブル1電源供給・書き込み
パソコン1プログラム開発

KUMITATE-C3を使用する場合は、基板本体とUSBケーブルだけで始められます。

KUMITATE-C3の商品ページはこちら

ESP32とOLED・スイッチの接続

今回はKUMITATE-C3のピン配置に合わせてプログラムを作成します。

機能GPIO
OLED SDAGPIO1
OLED SCLGPIO2
SW1GPIO21

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枚の基板に搭載されているため、基本的な電子工作を追加配線なしで学習できます。

今回の電子サイコロのほかにも、反射神経ゲーム、デジタル時計、温湿度モニター、無線通信を使用した作品など、さまざまな電子工作に挑戦できます。

KUMITATE-C3の商品詳細・購入はこちら

まとめ

今回はESP32を使って、OLEDにサイコロの目を表示する電子サイコロを作成しました。

今回学習した主な内容は次のとおりです。

  • random()による1〜6の乱数生成
  • GPIOによるスイッチ入力

KUMITATE

読むだけでなく、
実際に動かして学びませんか?

クミタテは、組み込み開発や電子工作を 実践しながら学べる学習プラットフォームです。 ESP32を使ったプログラミングから、電子回路、センサー、 通信、基板設計まで、自分のペースで学習できます。

  • 無料で学べる実践的な教材を掲載
  • すぐに試せるサンプルプログラム付き
  • Googleアカウントですぐに登録可能
クミタテに無料登録する
ESP32
スポンサーリンク