第39回 チャレンジ課題 3

今までの知識を使ってスケッチを変更してみましょう。

目次

チャレンジ課題

2種類のチャレンジ課題を用意してみました。

チャレンジ課題3-1 動作開始時にもメロディーを鳴らす

現在のスケッチでは、Arduino Microにスケッチを送ると、しばらくしてキッチンタイマーとして動作を開始します。

ただ、キッチンタイマーが動作を始めたのかどうか、タイミングがよくわからないですよね。現在のスケッチでは動作開始時に青色LEDが点灯するので、動作を開始したことはわかりますが、もう少しわかりやすくしたいと思います。

そこで、現在のスケッチに対して、Arduinoの動作開始時に青色LEDを点灯すると同時に、『ドファラ』というそれぞれの音が100msの短いメロディーを鳴らすように変更してみてください。

できれば、1音ずつスケッチに書いて鳴らすスケッチではなく、配列に音のデータを入れて、その配列を使用してメロディーを鳴らすようにしてください。

チャレンジ課題3-2 メロディーの変更をやりやすくする

現在のスケッチでは、アラーム音のメロディーは以下のように実現しています。

最初にメロディーの音の数を#defineで定義して、メロディー自体は配列にしています。

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

このスケッチでメロディーを変更する場合、メロディーの音の数に変更があれはば、変更後のメロディーの音の数を再度数えて、上の#defineの定義値を変更する必要があります。

このようにメロディーを変更した場合、音の数を数え直して#defineの値を変える、というのはなんだか面倒ですし、メロディーを長くした場合、数え間違えの可能性もありますよね。

そこで、音の数を数えて定義する必要がないようにスケッチを変更したいと思います。

具体的には、メロディー配列の最後にデータの終わりを意味する数値の 0 を追加することにしたいと思います。

melody配列は、周波数のデータです。周波数が0Hzというのは音がないことを意味しますので、数値の 0 をメロディーの終わりを意味する値とします。

例えば、現在のキッチンタイマーのスケッチのメロディー配列では、次のように最後に0を追加することになります。(右端が表示されない場合、横スクロールしてください)

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

音の数を数えて#define定義する必要がないように、このメロディー配列を使用してメロディーを演奏するスケッチを作成してみてください。

ヒント

プログラミングに慣れていないと、何から手をつけてよいかわからないという状態になるかもしれません。そこで、スケッチ作成の手かがりを一緒考えていきましょう。

前回まで作成したスケッチでは、次のようにforを使用して決まった回数処理を繰り返していました。

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

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

このチャレンジ課題では、メロディーの音の数はわからないので(あらかじめ数えないので)、一定回数処理を繰り返すように書くことはできません

では、音の数を数えないで次のメロディーデータを演奏するにはどのような仕組みで実現すればよいのでしょうか?

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

このメロディーを演奏するには、当然ながら次のバターンを繰り返すことになります。

tone(SPEAKER, melody[i]);
delay(200);

ポイントは、「最後の音階データのみ 0 という特殊なデータ」になっています。

最後以外のメロディーの音階は周波数のデータですので、必ず0以外になっています

ということは、処理としては次の処理をすれば良いことになります。

melody[i]が 0 以外の間、iを1ずつ増やしながら
上のスケッチのパータンを繰り返す

「条件が成立している間、処理を繰り返す」というのは以前whileのところで出てきましたよね。

というこで、whileの条件をうまく使えば実現できそうです!

チャレンジ課題解答例

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

スタート時演奏用のメロディー配列を次のように用意してみました。このコードはヘッダ部に追記します。

// 動作開始時演奏用メロディー
#define START_MELODY_LENGTH 3
uint16_t start_melody[] = {DO_4, FA_4, RA_4};

このメロディー配列を使って、アラームメロディーと同じ考え方で次のように演奏するようにします。このコードは、setupの青色LED点灯の後に追加してみました。

// 動作開始時メロディー演奏
for(uint8_t i=0; i<START_MELODY_LENGTH; i++) {
  tone(SPEAKER, start_melody[i]);
  delay(100);
}
noTone(SPEAKER);

完成したスケッチは次のようになります。(onkai.hは記載していません)

/*
  チャレンジ課題3-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 // アラーム音の音程

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

// 動作開始時演奏用メロディー
#define START_MELODY_LENGTH 3
uint16_t start_melody[] = {DO_4, FA_4, RA_4};

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

  // 動作開始時メロディー演奏
  for(uint8_t i=0; i<START_MELODY_LENGTH; i++) {
    tone(SPEAKER, start_melody[i]);
    delay(100);
  }
  noTone(SPEAKER);

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

  // タイマー開始時に緑色LEDを点灯
  digitalWrite(LED_MIDORI, HIGH);

}

void loop() {

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

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

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

    // 残り時間が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 ) {
  }

}

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

メロディーの配列は最後が0で終わっています。

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

処理の内容を日本語混じりで表現するとmelody[i]の値が0でない限り、以下のスケッチの処理を繰り返す」という内容になります。

tone(SPEADER, melody[i]);
delay(200);
i++;

配列の添え字iを増やしながら処理していく点に注意してください。

melody[i]の値が0でない限り、処理を繰り返す」というのは次のようにwhileを使うと実現できます。

while( melody[i] != 0 ) {
  処理
}

これらをまとめると、次のように処理を書けば実現できます。変数iについては、使う直前に初期化して宣言すればOKです。

uint8_t i=0;
while( melody[i] != 0 ) {
  tone(SPEADER, melody[i]);
  delay(200);
  i++;
}

次のようにスケッチを完成させてみました。

/*
  チャレンジ課題3-2解答例
    メロディー配列の最後のデータを終わりの意味の0にする
*/

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

// アラームメロディーのデータ(データの終わりは0)
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, 0};

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 ) {
  }

  // タイマー開始時に緑色LEDを点灯
  digitalWrite(LED_MIDORI, HIGH);
}

void loop() {

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

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

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

    // 残り時間が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回分のメロディー演奏
    uint8_t i=0;
    while( melody[i] != 0) {
      tone(SPEAKER, melody[i]);
      delay(200);
      i++;
    }
    // 音を消す
    noTone(SPEAKER);

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

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

}

更新履歴

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