第41回 switch文

今回は関数を変更してみます。

やりたいこと

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

controlTimeLed(HIGH, LOW, LOW);

というように、1つの命令で3個のLEDを制御できる関数を作りました。この関数についてもう少し深く考えてみます。

この関数は、1つの命令で、3個のLEDを使って残り時間を表現することが目的です。確かに1つの命令で3個のLEDを制御できれば便利ですが、パラメータが3つあり、緑LED、黄LED、赤LEDを制御する関数になっています。

残り時間を表現するのであれば、パラメータはこのようなものではなくて、残り時間を指定できればもう少し便利になりそうです。例えば、パラメータに残り時間の目安を表す数字として以下のように関数を呼び出すことができればもっと便利になるのではないか、という考え方です。

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

 

つまり、controlTimeLed関数のパラメータを残り時間に応じて、3、2、1というように指定して、その残り時間に合わせて3個のLEDを制御できればいいのではないか、という考え方です。

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

 

関数の変更方針

これから関数を変更しますが、変更後の関数のパラメータは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文を使えば実現できます。実際にif文で以下のように関数を定義すればOKそうです。

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のとき
  if( nokori_jikan == 1) {
    digitalWrite(LED_MIDORI, LOW);
    digitalWrite(LED_KIIRO,  LOW);
    digitalWrite(LED_AKA,    HIGH);
  }

}

実際にこのように書けばきちんと動作するのですが、C/C++言語では別の書き方ができるようになっています。今回の記事ではその書き方を習得します。

 

switch文

先ほどif文で書いた関数でやりたいことは、

「nokori_jikanという変数の値が3のとき、2のとき、1のときでそれぞれ別の処理をする」

ということですよね。このように変数の値に応じて場合分けをして処理をしたい場合、C/C++言語では「switch文」という構文が用意されています。最初にswitch文の書き方を確認しましょう。

switch文

最初に言葉の意味を確認しておきます。「switch」は日本語でも「スイッチ」と言っていますが、「切り替える」という意味です。また「case」は「〜の場合」という意味です。「default」は日本語でも「デフォルト」と言っていることが多いですが、「通常」というような意味ですが、switch文では「その他」というような意味になります。また「break」は「止める」という意味です。ということで、以下のように日本語で書くとわかりやすいと思います。

(式)で切り替える {
 値1の場合:
  処理1;
  処理終了;
 値2の場合:
  処理2;
  処理終了;
 その他の場合:
  処理;
}

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

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

ちょっと気になった方もいるかもしれませんが、それぞれの処理で「break;」(処理終了)という文がありますよね。また最後の「default:」では「break;」がありません。この「break;」は、switch文の処理を終わらせる、という意味があります。以下に具体的な例で確認しましょう。

例えばswitch文を以下のように書いた場合の動作を見てみます。

switch( 式 ) {
 case 値1:
  処理1;
  break;
 case 値2:
  処理2;
  break;
 default:
  処理;
}

例えば「式」が「値1」に一致した場合、「処理1」が実行されて、次の行に「break;」がありますので、このswitch文の処理は終わります。それでは以下のように処理1の後にbreakがないときはどうなるのでしょうか。

switch( 式 ) {
 case 値1:
  処理1;
 case 値2:
  処理2;
  break;
 default:
  処理;
}

「式」が「値1」に一致した場合、「処理1」が実行されますが、次に「break;」がないので、「処理2;」も実行されてしまうんです。処理1の後に「case 値2:」がありますが、これは無視されてしまいます。

switchは場合分けの処理な訳だから、「break;」なんていらないのではないか、と思いませんか?

実は「break;」がないと次の処理がされてしまう、という性質を利用して以下のような書き方ができるんです。

switch( 式 ) {
 case 値1:
 case 値2:
  処理2;
  break;
 default:
  処理;
}

これは「式」が「値1」か「値2」に一致するときに「処理2;」を実行します。

とは言っても、「break;」を書くのを忘れそうですよね。実際にswitch文を書くときこの「break;」を書き忘れて動作がおかしくなることが多々あります。

それでは、実際に前回の記事で作成した関数を書き直してみます。

 

switch文による関数

変更後の関数はパラメータは「nokori_jikan」(uint8_t型)という変数で、この値が3、2、1で場合分けをして処理をするようにします。switch文では3、2、1で場合分けをして3個の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;
  }

}

この例では、最後のcaseでもbreakを入れていますが、「case 1:」の処理は最後ですので、breakを入れても入れなくても動作は変わりません。ただ、このような場合でもbreakを入れることをお勧めします。

「case 1:」の処理のところに「break;」がない場合、後から「case 1:」の後に別の処理、例えば「case 0:」を追加した場合、「case 1:」の処理にはbreakが必要になります。このように後からcaseを追加する場合、前に書いたcaseにbreakがない場合にbreakを追加する、ということよく忘れます。これが原因で動作がおかしく気づかずに苦労することがあります。

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

 

スケッチまとめ

それでは前回の関数をswitchに変更したスケッチを以下に示します。関数を変更後、関数を呼ぶところも修正することに注意してください。

/*
 * キッチンタイマー
 * 
 * 内容: スイッチ、LED、スピーカーを使ったキッチンタイマー
 * 変更履歴:
 *   2019. 8.11: 新規作成
 *   2019. 8.15: スタートスイッチ処理を追加
 *   2019. 8.17: スイッチ関連の#define追加
 *   2019. 9. 8: 点滅回数カウント追加
 *               最終的に繰り返し処理をfor文で作成
 *   2019. 9.16: スケッチ動作開始時にLEDを点滅
 *   2019.10. 5: アラーム音追加
 *               動作開始時とタイマー時間の時に青色LEDを点灯するように変更
 *   2019.10.13: 残り時間LED制御追加
 *   2019.10.14: 残り時間が5秒以下になったらスピーカーを鳴らす動作を追加
 *   2019.11. 4: アラーム音をメロディーに変更
 *               音階定義をファイル化
 *   2019.11.11: メロディーを配列化
 *   2019.11.23: 残り時間表示LEDの関数化
 *   2019.11.24: 残り時間表示LED関数をswitch文に変更
 */

// 音階定義ファイルのインクルード
#include "onkai.h"

// 秒を表現するLED関連(青色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 23    // スイッチを接続している端子番号
#define SWITCH_OFF 1 // スイッチOFFの時のdigitalReadの値
#define SWITCH_ON  0 // スイッチONの時のdigitalReadの値

// タイマー時間設定(単位:秒)
#define TIMER_JIKAN 10

// 残り時間を表現するLEDの制御時間
#define KIIRO_JIKAN TIMER_JIKAN / 3
#define AKA_JIKAN   TIMER_JIKAN / 3 * 2

// アラーム音関連
#define SPEAKER 18  // スピーカーの端子番号
#define ALARM   880 // 通常のアラーム音周波数

#define ALARM_ONKAISUU 13 // アラームメロディーの音の数
uint16_t alarm_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の制御関数
//   パラメータ: 残り時間目安
//       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;
  }

}


void setup() {
  // 端子の設定
  pinMode(BYOU_LED,   OUTPUT);   // 青色LED接続端子設定
  pinMode(LED_MIDORI, OUTPUT);   // 緑色LED接続端子設定
  pinMode(LED_KIIRO,  OUTPUT);   // 緑色LED接続端子設定
  pinMode(LED_AKA,    OUTPUT);   // 緑色LED接続端子設定
  pinMode(SWITCH, INPUT_PULLUP); // スイッチ接続端子の設定

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

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

  // タイマー開始時に緑色LEDを点灯
  controlTimeLed(3); // 「残り時間十分」の表示

}


void loop() {
  
  uint8_t count;       // for文で回数を数えるために使用する変数
  uint8_t oto_bangou;  // for文でメロディーを鳴らすために使用する変数
  

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

    // 残り時間表現用LEDの制御
    if( count == KIIRO_JIKAN ) {
      controlTimeLed(2); // 「残り時間少ない」の表示
    }

    if( count == AKA_JIKAN ){
      controlTimeLed(1); // 「もうすぐ設定時間」の表示
    }

    // 残り時間に応じて処理を変える
    if( count >= (TIMER_JIKAN - 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( count=0; count<3; count++) {

    // メロディーを演奏する
    for( oto_bangou=0; oto_bangou<ALARM_ONKAISUU; oto_bangou++ ) {
      tone(SPEAKER, alarm_melody[oto_bangou]);
      delay(200);
    }
    
    // 音を消す
    noTone(SPEAKER);

    // 1.5秒あける
    delay(1500);

  }

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

}

 

更新履歴

日付 内容
2019.11.24 新規投稿