第42回 列挙体

今回もさらに関数を変更します。かなり無理矢理感がありますが、ぜひ最後までお付き合いください!

目次

時間表示関数の引数について

現在のスケッチでは、時間表示用LEDの制御用に次の関数を作成しています。

void controlTimeLed(uint8_t nokori_jikan) {

  switch(nokori_jikan) {  // 残り時間に応じた処理
    case 3:  // 残り時間十分
      digitalWrite(LED_MIDORI, HIGH);
      digitalWrite(LED_KIIRO,  LOW);
      digitalWrite(LED_AKA,    LOW);
      break;

    case 2:  // 残り時間半分以下
      digitalWrite(LED_MIDORI, LOW);
      digitalWrite(LED_KIIRO,  HIGH);
      digitalWrite(LED_AKA,    LOW);
      break;

    case 1:  // もうすぐ設定時刻
      digitalWrite(LED_MIDORI, LOW);
      digitalWrite(LED_KIIRO,  LOW);
      digitalWrite(LED_AKA,    HIGH);
      break;
  }

}

この関数はこれで問題なさそうですが、よくよく考えるとちょっと違和感があるんです。

あまり意識していなかったと思いますが、引数の数字そのものにちょっと違和感があります。

この関数ではswitchを使用して、残り時間に応じて場合分けをして処理をしています。その場合分けは次のようにしています。

caseで使用する数値残り時間の意味
3十分にある
2半分以下
1もうすぐ

違和感というのはcase場合分けに使用している数字です。

残り時間が十分なときは「3」、半分以下は「2」、もうすぐは「1」としていますが、そもそもこの3、2、1というのは意味がある数字なのでしょうか。

例えば、残り時間が十分な場合は「2」、半分以下は「1」、もうすぐは「0」としてもいいのではないでしょうか。

さらには、「3、2、1」や「2、1、0」ではなく「11、12、33」という3つの数字で判断することもできるので、特に「3、2、1」でなければならないということはありません

ちょっと極端な例ですが、次のような数値を使用してcaseで場合分け処理しても、処理上の問題はありません。(適当な数字を使用してみました)

void controlTimeLed(uint8_t nokori_jikan) {

  switch(nokori_jikan) {  // 残り時間に応じた処理
    case 78:  // 残り時間十分
      digitalWrite(LED_MIDORI, HIGH);
      digitalWrite(LED_KIIRO,  LOW);
      digitalWrite(LED_AKA,    LOW);
      break;

    case 17:  // 残り時間半分以下
      digitalWrite(LED_MIDORI, LOW);
      digitalWrite(LED_KIIRO,  HIGH);
      digitalWrite(LED_AKA,    LOW);
      break;

    case 199:  // もうすぐ設定時刻
      digitalWrite(LED_MIDORI, LOW);
      digitalWrite(LED_KIIRO,  LOW);
      digitalWrite(LED_AKA,    HIGH);
      break;
  }

}

この関数を呼ぶときに、「残り時間十分」のときは次のように書けば問題なく動作します。

controlTimeLed(78);

このように考えると、最初のスケッチで書いたcaseの値に「3、2、1」という数値を使う必然性はないですよね。

でもこのように数字で書いてしまうと「何かこの数字には意味があるのかな」と思ってしまいそうです

例えば初めて見たスケッチに、先ほどの極端な例のようにcaseの値が「78」「17」「199」と書いてあったら、なぜこの数字なんだろう、と考え込んでしまうのではないでしょうか。

このように考えると、残り時間表示LED制御用の関数のcaseの値は単に残り時間を区別するための記号のようなものだった、ということになります。

そこで、もう少し見やすいスケッチを検討してみましょう。

#defineを使ってみる

switchの場合分けの部分にcase 3:などと書いてあるとわかりづらいわけですから、この数字の部分を#defineで定義するというアイデアはどうでしょうか?

例えば、次のように#define定義する、というアイデアです。

#define JYUUBUN 3  // 残り時間十分
#define HANBUN  2  // 残り時間半分以下
#define MOUSUGU 1  // もうすぐ

関数はこの定義を使用して以下のように書きます。

void controlTimeLed(uint8_t nokori_jikan) {

  switch(nokori_jikan) {  // 残り時間に応じた処理
    case JYUUBUN:  // 残り時間十分
      digitalWrite(LED_MIDORI, HIGH);
      digitalWrite(LED_KIIRO,  LOW);
      digitalWrite(LED_AKA,    LOW);
      break;

    case HANBUN:  // 残り時間半分以下
      digitalWrite(LED_MIDORI, LOW);
      digitalWrite(LED_KIIRO,  HIGH);
      digitalWrite(LED_AKA,    LOW);
      break;

    case MOUSUGU:  // もうすぐ設定時刻
      digitalWrite(LED_MIDORI, LOW);
      digitalWrite(LED_KIIRO,  LOW);
      digitalWrite(LED_AKA,    HIGH);
      break;
  }

}

この関数を呼び出す場合、次のように書けば謎の数字は出てきません。

controlTimeLed(JYUUBUN);

とは言っても、スケッチのヘッダ部分では#define定義に相変わらず数字がありますよね…!

この数字は「3、2、1」を使用していますが、例えば次のように定義してもスケッチは全く問題なく動作します。

#define JYUUBUN 12  // 残り時間十分
#define HANBUN  34  // 残り時間半分以下
#define MOUSUGU 56  // もうすぐ

ということで、#defineを使用するとスケッチは読みやすくなるものの、結局スケッチでは謎の数字を使用しています。

初めてスケッチを見た人は「この数字に何か意味があるのかな」と思ってしまう可能性があります。


プログラミングでは、このように「単に何かを区別するための記号」を使うケースがよくあります。

C++ではキッチンタイマーで使用しているcaseの値のように「単に何かを区別するための記号」の仕組みが用意されていますので、確認していきましょう!

列挙体

「何かを区別するための記号」の仕組みとして、C++言語では「列挙体」というものか用意されています。

列挙体の宣言

このあと使い方など詳しく説明しますが、列挙体を使うには最初に「列挙体」を宣言する必要があります。

なんだかさっぱりわかりませんので、ゆっくり読み解いていきましょう!

「enum」は「enumeration」の略で、日本語では「列挙」という意味です。「列挙」とは平たくいうと「並べる」というようなイメージで捉えていただければと思います。

この列挙体の説明は、具体例から理解した方がわかりやすいので、実際の具体例で説明していきます。


キッチンタイマーのcontrolTimeLed関数では、switchの処理で3種類のケースを場合分けして処理をしています。具体的には、残り時間が「十分」「半分以下」「もうすぐ」の3種類です。

つまり、3種類の区別するための記号(単語)が欲しい、という状況です。

このような場合、スケッチに次のように書いて「列挙体」を宣言、つまり用意してもらいます。

enum Nokori {JYUUBUN, HANBUN, MOUSUGU};

このように書くと、Arduino IDEは以下のように理解してくれます。

このように列挙体を宣言すると、Arduino IDEは裏で、列挙子JYUUBUN HANBUN MOUSUGUを、それぞれ次のように数値に置き換えてくれます。

#define定義と似てますよね。違うところは、スケッチ上で数値が見えない点です。

また、このenum宣言は、基本的には次の#define定義と同じ、と理解いただいて問題ありません。

#define JYUUBUN 0
#define HANBUN  1
#define MOUSUGU 2

これで列挙体が宣言できました。

次に、宣言した列挙体の使い方を確認します。

列挙体の使い方

次に使い方ですが、基本的には#defineで定義した場合と同じです。(#defineとの違いはこのあと説明します)

次のように列挙体を宣言した場合、スケッチの中でJYUUBUN HANBUN MOUSUGUを使用することができます。

enum Nokori {JYUUBUN, HANBUN, MOUSUGU};

キッチンタイマーのスケッチで定義しているcontrolTimeLed関数のcaseの値の部分を、JYUUBUN HANBUN MOUSUGUを使って次のように書き換えることができます。

void controlTimeLed(uint8_t nokori_jikan) {

  switch(nokori_jikan) {  // 残り時間に応じた処理
    case JYUUBUN:  // 残り時間十分
      digitalWrite(LED_MIDORI, HIGH);
      digitalWrite(LED_KIIRO,  LOW);
      digitalWrite(LED_AKA,    LOW);
      break;

    case HANBUN:  // 残り時間半分以下
      digitalWrite(LED_MIDORI, LOW);
      digitalWrite(LED_KIIRO,  HIGH);
      digitalWrite(LED_AKA,    LOW);
      break;

    case MOUSUGU:  // もうすぐ設定時刻
      digitalWrite(LED_MIDORI, LOW);
      digitalWrite(LED_KIIRO,  LOW);
      digitalWrite(LED_AKA,    HIGH);
      break;
  }

}

この関数を呼び出すときは、次のように書くことができます。

controlTimeLed(JYUUBUN);  // LED点灯状態を「残り時間が十分」にする

ここまでの説明では、列挙体は#defineと同じように利用できることがわかりました。

でも列挙体を宣言するときに「列挙体名」をつけることができますが、上の例では列挙体名はどこにも出てきませんでしたよね。そこで列挙体名はどのような時に使うのか、列挙体についてもう少し知識をつけておきましょう!

列挙体の変数

列挙体は#defineと同じような働きをしてくれますが、#defineとは異なり列挙体の変数を宣言することができます

ちょっと意味不明ですので、具体例で確認していきます。

例えば、先ほどのようにNokoriという名前の列挙体を次のように宣言したとします。

enum Nokori {JYUUBUN, HANBUN, MOUSUGU};

このとき、この列挙子(JYUUBUN, HANBUN, MOUSUGU)が使える変数を宣言することができるんです。例えば次のような感じです。

Nokori nokori_jikan;

このように宣言したnokori_jikanは、Nokori型の変数として扱うことができて、JYUUBUN HANBUN MOUSUGUのいずれかを代入することができます。

例えば次のように値を代入できます。

nokori_jinkan = JYUUBUN;

注意点としては、変数nokori_jikanに代入できる値はJYUUBUN HANBUN MOUSUGUいずれかのみになる、という点です。

そのため、次のような代入はエラーになります。

nokori_jikan = 1;

ただ、デフォルト状態のArduino IDEではこのルールを緩くしているようで、上のように直接数値を代入してもエラーにならないようです。(Arduino IDE Version 2.3.3時点の確認)

列挙体の基本的な使い方は以上になります。

それでは列挙体を使ってスケッチを変更してみましょう!

列挙体による関数の変更

それでは、現在の関数の引数を列挙体に変更します。

まずスケッチのヘッダ部分に、残り時間を区別するために次のように列挙体を宣言します。

// 残り時間の列挙体宣言
//            十分    半分以下  もうすぐ
enum Nokori {JYUUBUN, HANBUN, MOUSUGU};

次にcontrolTimeLed関数を、この列挙体を使うように変更します。

関数の仮引数をNokori型のnokori_jikanにます。また、switchの場合分け条件をnokori_jikanにして、caseの値をJYUUBUN HANBUN MOUSUGUに変更します。

void controlTimeLed(Nokori nokori_jikan) {

  switch(nokori_jikan) {  // 残り時間に応じた処理
    case JYUUBUN:  // 残り時間十分
      digitalWrite(LED_MIDORI, HIGH);
      digitalWrite(LED_KIIRO,  LOW);
      digitalWrite(LED_AKA,    LOW);
      break;

    case HANBUN:  // 残り時間半分以下
      digitalWrite(LED_MIDORI, LOW);
      digitalWrite(LED_KIIRO,  HIGH);
      digitalWrite(LED_AKA,    LOW);
      break;

    case MOUSUGU:  // もうすぐ設定時刻
      digitalWrite(LED_MIDORI, LOW);
      digitalWrite(LED_KIIRO,  LOW);
      digitalWrite(LED_AKA,    HIGH);
      break;
  }

}

あとは、関数呼び出し部分をJYUUBUNなどを使用するように変更します。

// タイマー開始時に残り時間十分の表示にする
controlTimeLed(JYUUBUN);

以前のスケッチでは残り時間が「3, 2, 1」という謎の数値でしたが、列挙体を使用することによってスケッチから謎の数値をなくすことができました!

スケッチ

それでは最後にスケッチとしてまとめます。

/*
  キッチンタイマー
  
  内容: スイッチ、LED、スピーカーを使ったキッチンタイマー
  変更履歴:
    2024.11.25: 新規作成
    2024.12.01: スイッチが押されたらLED点滅開始
    2024.12.02: スイッチ関連の#define追加
    2024.12.05: 点滅回数カウント追加
    2024.12.06: 繰り返し処理をforに変更
    2024.12.13: アラーム音追加・動作開始時とタイマー時間の時に青色LEDを点灯
    2024.12.15: 残り時間のLED制御を追加
    2024.12.17: 残り時間が少なくなったら音も鳴らす制御を追加
    2024.12.17: アラーム音をメロディーに変更
    2024.12.22: 周波数定義部分をファイル化
    2024.12.23: メロディーデータを配列化
    2024.12.26: 残り時間LED制御の関数化
    2024.12.27: 残り時間LED制御をswitchに変更
    2025.1.6:   残り時間の引数を列挙体に変更
*/

// 各音程の周波数定義読み込み
#include "onkai.h"

// 秒を表現するLED関連の定義
#define BYOU_LED 12  // 秒を表現するLEDの接続端子
#define BYOU_ON  50 // 秒を表現するLEDを点灯している時間(単位:ミリ秒)
#define BYOU_OFF (1000 - BYOU_ON) // 秒を表現するLEDを消している時間(単位:ミリ秒)

// 残り時間を表現するLED関連(緑色、黄色、赤色LED)
#define LED_MIDORI 8 // 緑色LEDの端子番号
#define LED_KIIRO  6 // 黄色LEDの端子番号
#define LED_AKA    4 // 赤色LEDの端子番号

// タイマースタートスイッチ関連の定義
#define SWITCH A5 // スイッチを接続している端子名
#define SWITCH_OFF 1 // スイッチOFFの時のdigitalReadの値
#define SWITCH_ON  0 // スイッチONの時のdigitalReadの値

// タイマー時間設定(LEDの点滅回数)
#define TIMER_JIKAN 30

// 残り時間を表現するLEDの制御時間
#define KIIRO_JIKAN 10  // 残り時間3分の2
#define AKA_JIKAN   20  // 残り時間3分の1

// アラーム音関連
#define SPEAKER A0  // スピーカーの端子番号
#define ALARM   880 // アラーム音の音程

#define MELODY_LENGTH 13 // アラームメロディーの音の数
uint16_t melody[] = {DO_4, MI_4, SO_4, MI_4, DO_4, MI_4, SO_4, MI_4, DO_4, MI_4, SO_4, SI_4, DO_5};

// 残り時間の列挙体宣言
//            十分    半分以下  もうすぐ
enum Nokori {JYUUBUN, HANBUN, MOUSUGU};

// 残り時間表示用LEDの制御関数
//   引数: HIGHまたはLOW
//   返り値: なし
void controlTimeLed(Nokori nokori_jikan) {

  switch(nokori_jikan) {  // 残り時間に応じた処理
    case JYUUBUN:  // 残り時間十分
      digitalWrite(LED_MIDORI, HIGH);
      digitalWrite(LED_KIIRO,  LOW);
      digitalWrite(LED_AKA,    LOW);
      break;

    case HANBUN:  // 残り時間半分以下
      digitalWrite(LED_MIDORI, LOW);
      digitalWrite(LED_KIIRO,  HIGH);
      digitalWrite(LED_AKA,    LOW);
      break;

    case MOUSUGU:  // もうすぐ設定時刻
      digitalWrite(LED_MIDORI, LOW);
      digitalWrite(LED_KIIRO,  LOW);
      digitalWrite(LED_AKA,    HIGH);
      break;
  }

}


void setup() {
  // 端子の設定
  // スピーカー接続端子はtoneの指示で自動的に出力設定になるので設定していません
  pinMode(BYOU_LED,   OUTPUT); // 秒表現のLED接続端子の出力設定
  pinMode(SWITCH,     INPUT_PULLUP); // スイッチ接続端子をプルアップ設定
  pinMode(LED_MIDORI, OUTPUT); // 緑色LED接続端子設定
  pinMode(LED_KIIRO,  OUTPUT); // 緑色LED接続端子設定
  pinMode(LED_AKA,    OUTPUT); // 緑色LED接続端子設定

  // 秒のLED(青色LED)を点灯する
  digitalWrite(BYOU_LED, HIGH);

  // スイッチが押されるまで何もしないで待つ
  while( digitalRead(SWITCH) == SWITCH_OFF ) {
  }



  // タイマー開始時に残り時間十分の表示にする
  controlTimeLed(JYUUBUN);
}

void loop() {

  // TIMER_JIKAN分の回数を数える
  for(uint8_t count=0;  count<TIMER_JIKAN; count++) {

    // 残り時間が3分の2のとき、緑色をOFF、黄色をONにする
    if( count == KIIRO_JIKAN ) {
      controlTimeLed(HANBUN);
    }

    // 残り時間が3分の1のとき、黄色をOFF、赤色をONにする
    if( count == AKA_JIKAN ){
      controlTimeLed(MOUSUGU);
    }

    // 残り時間が5秒以下かそうでないかで処理を変える
    if( (TIMER_JIKAN-count) <= 5 ) {
      // 残り時間が5秒以下のときは、1秒に1回青色LEDを点滅すると同時に音を鳴らす
      digitalWrite(BYOU_LED, HIGH);
      tone(SPEAKER, ALARM);
      delay(BYOU_ON);
      digitalWrite(BYOU_LED, LOW);
      noTone(SPEAKER);
      delay(BYOU_OFF);
    } else {
      // そうでなければ、1秒に1回青色LEDを点滅する(音は鳴らさない)
      digitalWrite(BYOU_LED, HIGH);
      delay(BYOU_ON);
      digitalWrite(BYOU_LED, LOW);
      delay(BYOU_OFF);
    }
  
  }

  // 時間になったので秒のLED(青色LED)を点灯する
  digitalWrite(BYOU_LED, HIGH);

  // メロディーを3回演奏する
  for(uint8_t count=0; count<3; count++) {
  
    // 1回分のメロディー演奏
    for(uint8_t i=0; i<MELODY_LENGTH; i++ ) {
      tone(SPEAKER, melody[i]);
      delay(200);
    }   
    // 音を消す
    noTone(SPEAKER);

    // メロディーを区切るために少し時間待ちをする
    delay(500);
  }

  // 何もしないで待つ
  while( true ) {
  }

}

更新履歴

日付内容
2019.12.1新規投稿
2021.8.27新サイトデザイン対応
2025.1.6「列挙体」に変更
説明図変更
通知の設定
通知タイミング
guest
0 コメント
新しい準
古い順 一番投票が多い
本文中にフィードバック
全てのコメントを見る
目次