第45回 チャレンジ課題 4

プログラミングの基本が習得できましたので、チャレンジ課題に挑戦してみましょう!

目次

プログラミングまとめ

ずいぶんと長い道のりでしたが、今までの記事でプログラミングの基本的な部分を習得してきました。

このシリーズ記事ではキッチンタイマーを製作しましたが、他にも何か電子工作をしてプログラムで制御したい場合でも同じような考え方、作業をすることになります。

他の制御をするプログラムを作る場合でも、プログラミングのかなめ次の項目をいかに組み合わせて希望通りの動作にするか、ということになります。

  • 変数
  • 条件判断制御(ifswitch
  • 繰り返し制御(forwhile

このシリーズ記事では「C++言語」を習得してきましたが、他のプログラミング言語、例えば「JavaScript」や「Python」などでも、変数、条件判断制御、繰り返し制御があります。その書き方はC++言語とは異なりますが、プログラミングの基本的な考え方自体は同じなんです。

プログラミングに興味があれば、ぜひ他のプログラミング言語も覗いてみていただければと思います。(同じ制御を得る場合でも書き方が違いますので、その点も面白いところです)


今回の記事では、キッチンタイマーにさらに余計な機能?を追加するチャレンジ課題を用意しました。

ぜひチャレンジしてみてください!

チャレンジ課題

チャレンジ課題4-1

現在のキッチンタイマーのスケッチでは、スイッチを押すと動作を開始して、設定時間になったらメロディーを3回演奏して終わります

この動作でも良いのですが、メロディーを演奏したあとキッチンタイマーの動作完全に停止してしまい、再度キッチンタイマーを動作させる場合は電源を入れ直す必要があります

そこで、現在のキッチンタイマーの動作を次のように変更してみてください。

メロディー演奏後、スイッチを押したら1秒間「ピーッ」と音を鳴らして、タイマーを最初の状態に戻す(「最初の状態」とは「スイッチが押されるまで待ち、押されたらキッチンタイマーの動作を開始する」状態です)

チャレンジ課題4-2

現在のメロディーデータは、構造体の配列に用意しています。

このメロディーデータの状態では、毎回必ずなんらかの音程を出すことになります。つまり音を出さない休符が実現できていません

そこで、メロディーデータに休符を指定できるようにスケッチを変更してみてください。

チャレンジ課題解答例

チャレンジ課題4-1解答例

考え方

現在のスケッチはメロディー演奏後、次の部分で動作を停止しています。

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

この部分を「スイッチが押されたら、1秒間アラーム音を鳴らして最初の状態に戻る」という処理に変更が必要です。それぞれの処理をどうすればよいか考えましょう。

「スイッチが押されたら」という部分はすでに次のようにプログラミングしています。

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

また、「1秒間アラーム音を鳴らして」という部分は、次のようにスケッチを書けばOKです。

  // アラーム音を1秒間鳴らす
  tone(SPEAKER, ALARM);
  delay(1000);
  noTone(SPEAKER);

「最初の状態に戻る」という部分ですが、loop関数は最後の}まで行くと、loopの最初から処理を行います。この部分は特に何もしなくてもloop関数が自動的に実現してくれます。

ただし、メロディー演奏後は残り時間LEDは赤色LEDを点灯したままですので、LEDをOFFにしておきたいと思います。

この処理digitalWrite関数で行ってもいいのですが、せっかくcontrolTimeLed関数を作成してありますので、その関数に「LEDを全てOFFにする」という処理を追加することにしてみました。

まず、controlTimeLedの引数は現在次のように列挙体で用意しています。

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

全てOFFにする値も追加しますので、次のようにALL_OFFという値を追加してみました。

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

また、controlTimeLed関数で、引数にALL_OFFが指定された場合の処理を追加します。

    case ALL_OFF:  // 全てOFFにする
      digitalWrite(LED_MIDORI, LOW);
      digitalWrite(LED_KIIRO,  LOW);
      digitalWrite(LED_AKA,    LOW);
      break;

また、現在のスケッチは1回だけの処理を想定していますので、setup関数に書いてあるタイマーをスタートするための「スイッチが押されるまで待つ」処理と、「タイマー開始時に残り時間十分の表示にする」という処理はloop関数の先頭に移します。

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

  // タイマー開始時に残り時間十分の表示にする処理は繰り返し行うためloop関数に移動
  controlTimeLed(JYUUBUN);

完成したスケッチ

これまでの内容をまとめて、次のようにスケッチを作成してみました。

/*
  チャレンジ課題4-1解答例
    キッチンタイマーを繰り返し動作できるようにする
*/

// 各音程の周波数定義読み込み
#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 // アラーム音の音程

// 音データの構造体
struct Note {
  uint16_t ontei;
  uint16_t length;
};

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

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

// 残り時間表示用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;
    
    case ALL_OFF:  // 全てOFFにする
      digitalWrite(LED_MIDORI, LOW);
      digitalWrite(LED_KIIRO,  LOW);
      digitalWrite(LED_AKA,    LOW);
      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);

  // スイッチが押されるまで何もしないで待つ処理は繰り返し行うためloop関数に移動
  //while( digitalRead(SWITCH) == SWITCH_OFF ) {
  //}

  // タイマー開始時に残り時間十分の表示にする処理は繰り返し行うためloop関数に移動
  //controlTimeLed(JYUUBUN);
}

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

  // タイマー開始時に残り時間十分の表示にする処理は繰り返し行うためloop関数に移動
  controlTimeLed(JYUUBUN);

  // 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].ontei);
      delay(melody[i].length);
    }
    // 音を消す
    noTone(SPEAKER);

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

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

  // アラーム音を1秒間鳴らす
  tone(SPEAKER, ALARM);
  delay(1000);
  noTone(SPEAKER);

  // 残り時間LEDの表示を全てOFFにする
  controlTimeLed(ALL_OFF);

}

チャレンジ課題4-2解答例

考え方

現在のスケッチではメロディーデータの音程は周波数の数値が指定されています。

Note melody[] = {
  {DO_4, 200},
  {MI_4, 200},
  {SO_4, 400},
  {MI_4, 400},
  {DO_4, 200},
  {MI_4, 200},
  {SO_4, 400},
  {MI_4, 400},
  {DO_4, 200},
  {MI_4, 200},
  {SO_4, 400},
  {SI_4, 400},
  {DO_5, 800}};

休符のデータをどのようにするかですが、アイデアとして次のように考えてみました。

休符は音がない状態、つまり周波数はないので、休符の周波数は「0」と考えます。

例えば上のメロディーデータで休符にしたいところの周波数を0にします。(5行目と9行目)

Note melody[] = {
  {DO_4, 200},
  {MI_4, 200},
  {SO_4, 400},
  {0   , 400},
  {DO_4, 200},
  {MI_4, 200},
  {SO_4, 400},
  {0   , 400},
  {DO_4, 200},
  {MI_4, 200},
  {SO_4, 400},
  {SI_4, 400},
  {DO_5, 800}};

このようにすると、メロディーデータの音程の数値が0以外であればその音を出す、0であれば音を出さない、という演奏制御をすればよい、ということになります。

現在の演奏している部分は次のようになっています。

for(uint8_t i=0; i<MELODY_LENGTH; i++ ) {
    tone(SPEAKER, melody[i].ontei);
    delay(melody[i].length);
}

このforの処理の中で、melody[i].onteiが0かそれ以外かで処理を分ければよいので、ifで次のように処理を分けるようにしてみました。

for(uint8_t i=0; i<MELODY_LENGTH; i++ ) {
  if( melody[i].ontei ) {
    // 音がある場合の処理
  } else {
    // 休符の場合の処理
  }
}

if( melody[i].ontei )という部分は、ifの中が0以外の場合は成立、0の場合は不成立となりますので、上のように処理を切り分ければよいことになります。

あとは音がある場合の処理と休符の処理をすればよいので、演奏部分のスケッチは次のようにしてみました。

for(uint8_t i=0; i<MELODY_LENGTH; i++ ) {
  if( melody[i].ontei ) {
    tone(SPEAKER, melody[i].ontei);
    delay(melody[i].length);
  } else {
    noTone(SPEAKER);
    delay(melody[i].length);
  }
}

なお、メロディーデータで休符を0と書くとわかりづらくなりますので、ヘッダファイルに休符を指定するためにRESTを0に定義しておくことにします。

// 休符
#define REST   0

これでうまく動作するはずです。

完成したスケッチ

この課題ではヘッダファイルも変更しましたので、次のヘッダファイルと本体スケッチのようにしてみました。

ヘッダファイル
// メロディー演奏用の音の周波数

// 休符
#define REST   0

// 通常音程
#define DO_3   131
#define DOS_3  139
#define RE_3   147
#define RES_3  156
#define MI_3   165
#define FA_3   175
#define FAS_3  185
#define SO_3   196
#define SOS_3  208
#define RA_3   220
#define RAS_3  233
#define SI_3   247

#define DO_4   262
#define DOS_4  277
#define RE_4   294
#define RES_4  311
#define MI_4   330
#define FA_4   349
#define FAS_4  370
#define SO_4   392
#define SOS_4  415
#define RA_4   440
#define RAS_4  466
#define SI_4   494

#define DO_5   523
#define DOS_5  554
#define RE_5   587
#define RES_5  622
#define MI_5   659
#define FA_5   698
#define FAS_5  740
#define SO_5   784
#define SOS_5  831
#define RA_5   880
#define RAS_5  932
#define SI_5   988
スケッチ本体
/*
  チャレンジ課題4-2解答例
    休符を指定できるようにする
*/

// 各音程の周波数定義読み込み
#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 // アラーム音の音程

// 音データの構造体
struct Note {
  uint16_t ontei;
  uint16_t length;
};

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

// 残り時間の列挙体宣言
//            十分    半分以下  もうすぐ
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++ ) {
      if( melody[i].ontei ) {
        tone(SPEAKER, melody[i].ontei);
        delay(melody[i].length);
      } else {
        noTone(SPEAKER);
        delay(melody[i].length);
      }
    }

    // 音を消す
    noTone(SPEAKER);

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

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

}

更新履歴

日付内容
2019.12.22新規投稿
2019.12.23一部表現変更
2021.8.27新サイトデザイン対応
2025.1.10課題内容一部変更
解答例追加
通知の設定
通知タイミング
guest
0 コメント
新しい準
古い順 一番投票が多い
本文中にフィードバック
全てのコメントを見る
目次