プログラミングの基本が習得できましたので、チャレンジ課題に挑戦してみましょう!
プログラミングまとめ
ずいぶんと長い道のりでしたが、今までの記事でプログラミングの基本的な部分を習得してきました。
このシリーズ記事ではキッチンタイマーを製作しましたが、他にも何か電子工作をしてプログラムで制御したい場合でも同じような考え方、作業をすることになります。
他の制御をするプログラムを作る場合でも、プログラミングの要は次の項目をいかに組み合わせて希望通りの動作にするか、ということになります。
- 変数
- 条件判断制御(
if
やswitch
) - 繰り返し制御(
for
やwhile
)
このシリーズ記事では「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 | 課題内容一部変更 解答例追加 |