第41回 switch

今回は、条件に応じて処理を切り替える、という方法を習得します。

目次

やりたいこと

前回の記事で、残り時間を表現する3個のLEDを制御する関数を作りました。

具体的には、次のように1回の指示で3個のLEDをまとめて制御できる関数を作りました。

controlTimeLed(HIGH, LOW, LOW);

今回はこの関数について、もう少し深く考えてみます。


この関数は、3個のLEDの点灯状態を3つの引数で指定するようになっています。

でもこの関数の目的は、「3個のLEDを使って残り時間を表現するということです。

残り時間を表現するのであれば、引数はこのように一個一個のLEDの点灯状態を指定するのではなく、残り時間を指定できればもう少し便利になりそうです。

ちょっと理解しづらかいかもしれませんので、具体例で説明します。

例えば、controlTimeLed関数の引数は1つで、この引数に残り時間の目安を表す数字を指定してはどうか?というアイデアです。

例えば次のような呼び出し方です。

残り時間関数の呼び出し方
十分あるcontrolTimeLed(3);
少なくなってきたcontrolTimeLed(2);
もうすぐcontrolTimeLed(1);

つまりcontrolTimeLed関数の引数に、残り時間に応じて、3、2、1というように指定して、その値に合わせて3個のLEDを制御できればいいのではないか、というアイデアです。

ということで、今回はこのような制御ができるように、controlTimeLed関数を変更したいと思います。

関数の変更方針

これから関数を変更しますが、関数の仮引数はuint8_t型の「nokori_jikan」という変数にします。

関数の定義は、日本語混じりで書くと次のように変更すればよいことになります。

void controlTimeLed(uint8_t nokori_jikan) {
  // この関数内では次の制御をする
  //    ・もしnokori_jikanが3だったら、緑ON、 黄OFF、赤OFFにする
  //    ・もしnokori_jikanが2だったら、緑OFF、黄ON、 赤OFFにする
  //    ・もしnokori_jikanが1だったら、緑OFF、黄OFF、赤ONにする
}

この関数の中身の処理は今まで習得した知識で書くことができそうです。

具体的にはifを使ってnokori_jikanの値に応じて処理を切り替えれば実現できます。

実際に次のように関数を定義すれば問題なさそうです。

void controlTimeLed(uint8_t nokori_jikan) {

  // 残り時間が3のとき
  if( nokori_jikan == 3) {
    digitalWrite(LED_MIDORI, HIGH);
    digitalWrite(LED_KIIRO,  LOW);
    digitalWrite(LED_AKA,    LOW);
  }

  // 残り時間が2のとき
  if( nokori_jikan == 2) {
    digitalWrite(LED_MIDORI, LOW);
    digitalWrite(LED_KIIRO,  HIGH);
    digitalWrite(LED_AKA,    LOW);
  }

  // 残り時間が1のとき -> 赤色LEDを点灯する
  if( nokori_jikan == 1) {
    digitalWrite(LED_MIDORI, LOW);
    digitalWrite(LED_KIIRO,  LOW);
    digitalWrite(LED_AKA,    HIGH);
  }

}

実際にこのようスケッチで問題なく動作するのですが、このような処理をしたい場合、C++言語では別の書き方ができるようになっています。

今回の記事ではその書き方を習得します。

switch

先ほどifで書いた関数の処理は、次のように「変数の値に応じた場合分けの処理」をしている感じがしませんか?

この先の説明に繋げるために誘導してしまいますが、別の言い方をすると「変数の値に応じて処理をスイッチする」というイメージがしませんか?(ここは「そうだな」と思ってください)

実はC++言語では、このように変数などの値に応じて処理をスイッチするswitchという仕組みが用意されているんです。

上のように変数の値に応じて場合分け処理をしたい場合にぴったりな仕組みです。

最初に言葉の意味を確認しておきます。

「switch」は日本語でも「スイッチ」と言っていますが、「切り替える」というような意味です。switchのあとに場合分けしたい変数や式などを括弧囲んで書きます。

「case」は「〜の場合」という意味です。

「default」は日本語でも「デフォルト」と言われることが多いですが、日本語では「通常」というような意味になります。

「break」は日本語でも「ブレーキ」などと使われていますが、「止める」という意味です。

上のswitchの書き方は、次のように日本語で書くと見通しがよくなるかな、と思いますので参考にしてみてください。

(式や変数)の値に応じて処理切り替える {
 値1の場合:
  処理1;
  処理終了;
 値2の場合:
  処理2;
  処理終了;
 どれにも一致しない場合:
  処理;
}

なお、「default」に対応する処理がない場合はdefaultの部分は省略することができます。


switchは変数の値に応じて処理を切り替える場合に便利ですが、いくつか注意点があります。

気になった方もいるかもしれませんが、それぞれの処理でbreak;というここで処理を終了する、ということが書かれています。

switchは場合分け処理をするので、それぞれの処理の最後にbreak;と書かなくても、そこで処理をやめて欲しいですが、そのような動作になっていませんので注意が必要です。

例えば、次スケッチのように「処理1」の後にbreak;がないときはどうなるのでしょうか?

switch(式) {
 case 値1:
  処理1; // このあとにbreak;がない!
 case 値2:
  処理2;
  break;
 default:
  処理;
}

「式」が「値1」に一致した場合、「処理1」が実行されますが、次にbreak;がないので、引き続き処理2;も実行されてしまうんです。

処理1の後にcase 値2:がありますが、これは無視されてしまいます

なんだか不思議な感じがしますが、Arduinoはswitchを次のように処理しているため、このような処理になっているんです。

  • Arduinoはswitchの「式」の値を確認する
  • の値と一致するcaseを探す。
  • 一致するcaseがあれば、それ以降書かれている処理を続ける(このあとcaseの値は確認しないことに注意!
  • 一致するcaseがなければdefaultに書かれている処理を行う

重要なポイントは「caseは処理の開始位置を決めるだけに使用される」ということになります。

switchに慣れてきてもbreak;を忘れることがあるので(私だけかもしれませんが)、このポイントを忘れないようにしていただければと思います。

switchによるLED制御

それでは、switchを使って残り時間を表現するLED制御を行うスケッチを作成してみます。

controlTimeLed関数の定義は、引数はuint8_t型のnokori_jikanいう変数で、この値が3、2、1で場合分けをして処理をするようにします。

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;
  }
}

この例では、最後のcaseでもbreak;を入れていますが、case 1:の処理は最後ですので、break;を入れても入れなくても動作は変わりません。

ただ、このような場合でもbreakを入れることをお勧めします。

ちょっとややこしいですが、スケッチの例で説明します。

例えば上の例で考えると、次のように最後のcase 1:break;がなくても問題なく動作します。

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;がなくても、最後の処理なのでcase 1は問題なく動作する
  }
}

このスケッチに対して、あとから別の場合分けの処理を追加したい場合を考えてみます。

例えばnokori_jikanが0の時、LEDを全てOFFにする、という例で考えます。

このような処理をする場合、上のスケッチに対してcase 0:を追加してそれに応じた処理を書けば良いのですが、ついうっかり次のようにスケッチを追加してしまうことがあります。

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;がない

    case 0:  // LED全てOFFにする
      digitalWrite(LED_MIDORI, LOW);
      digitalWrite(LED_KIIRO,  LOW);
      digitalWrite(LED_AKA,    LOW);
  }
}

この例では、case 1:の処理にbreak;がないまま、case 0:を追加してしまっています。

上のスケッチでは、nokori_jikanが1の場合、case 1:の処理が終わったあとbreak;がありませんので、case 0:の処理に進みます。つまり、LEDが全てOFFになってしまうのです。


このようにあとからcaseを追加する場合、その前のcaseにbreak;がない場合、break;を追加することを忘れないようにすれば良いのですが、慣れていてもついうっかり忘れてしまい、謎現象になったりします。

ということで、caseでは最後にbreakを入れることをお勧めします。

スケッチまとめ

controlTimeLed関数の処理をswitchに変更したスケッチを以下に示します。

/*
  キッチンタイマー
  
  内容: スイッチ、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に変更
*/

// 各音程の周波数定義読み込み
#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};

// 残り時間表示用LEDの制御関数
//   引数: HIGHまたはLOW
//   返り値: なし
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;
  }

}


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(3);
}

void loop() {

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

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

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

    // 残り時間が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.11.24新規投稿
2021.8.27新サイトデザイン対応
2024.12.27説明内容追加(スイッチのイメージ追加)
通知の設定
通知タイミング
guest
0 コメント
新しい準
古い順 一番投票が多い
本文中にフィードバック
全てのコメントを見る
目次