キッチンタイマーのスケッチを変更して、スイッチが押されたらLEDの点滅を開始するようにします。
習得した項目の確認
前回までの記事で、青色LEDを1秒に1回、ピカッ、ピカッ、と光らせることができました。また、スイッチの状態を読み取ることもできました。
これから考え方がだんだん複雑になってきますので、今まで習得した以下の命令を十分理解しておくようにしてください。
- setupとloopの役割
- pinMode命令
- delay命令
- digitalWrite命令
- digitalRead命令
- Serial.begin命令
- Serial.println命令 / Serial.print命令
実現したいこと
一般的なキッチンタイマーにはスタートボタンがありますよね。
前回までに作成したキッチンタイマーのスケッチでは、スケッチをArduinoボードに送るとすぐに青色LEDの点滅が始まってしまいます。そこで、スイッチを押したら点滅を開始するようにスケッチを変更したいと思います。
今まで習得したことで以下のことはできています。
- 青色LEDを1秒に1回点滅する
- スイッチの状態を調べる
実現したいことは「スイッチが押されたら青色LEDの点滅を開始する」という動作です。
「〇〇したら□□する」という動作はいろいろな場面で出てきそうですよね。これから新しい仕組みを習得してスイッチが押されたらLEDの点滅を開始するようにスケッチを変更していきましょう。
while構文
日常生活を振り返ってみると「〇〇したら□□する」という動作はたくさんありますよね。「アラームが鳴ったら起きる」とか「おこづかいをもらったら欲しかったものを買いに行く」とか、他にもいろいろあります。
ちょっと意外に思うかもしれませんが、プログラミングの世界では「〇〇したら□□する」という動作をこのまま直接書ける命令はないんです。「スイッチが押されたらLEDの点滅を開始する」というスケッチが書ければいいのですが、そのような命令は用意されていません。
とはいっても、これから「スイッチが押されたらLEDの点滅を開始する」という動作をするスケッチを書くわけですが、「〇〇したら□□する」という命令がないのにどうやって実現するのでしょうか。
これから、日常生活でよく出てくる「〇〇したら□□する」という考え方を変えますので、頭を柔らかくしておきましょう。
C/C++言語に限らず、どのプログラム言語でも「●●という条件が成立している間、■■をする」という構文が用意されています。この構文を使って「〇〇したら□□する」という動作を実現します。なんだかややこしくなってきました。
今まで出てきたdigitalWriteなどは、スイッチを制御するという実際の動作を指示するものでした。このように動作を指示するため「命令」と呼んでいたわけです。
これから習得するものはそのような実際の動作を「制御」する目的のため、「命令」ではなく「制御構文」または単に「構文」と呼ばれています(「構文」という言葉を忘れてしまっていたら第12回の記事を読み返してみてください)。
ところで、「●●という条件が成立している間、■■をする」ということができるのであれば、スイッチが押されたらLEDの点滅を開始するというのを、●●は「スイッチが押されている」にして、■■は「LEDの点滅をする」にすれば実現できるのでは?と思いませんか?
でも良く考えてみると、「『スイッチが押されている』という条件が成立している間、LEDの点滅をする」と書いてしまうと、LEDを点滅させるにはスイッチをずっと押したままにしなくてはいけません。
「スイッチが押されたら、LEDの点滅を開始する」という動作にはならないんです。日常では出てこないことを考えているので、だんだんややこしくなってきましたね。もうちょっと頑張りましょう。
C/C++言語では「●●という条件が成立している間、■■をする」という動作を実現するために以下の構文が用意されています。
Arduinoボードは、while構文を以下のように制御します。
- Arduinoボードは「条件」を判定します
- 「条件」が成立していれば、「命令」を実行します。命令の実行後はすぐに(1)に戻ります
- 「条件」が成立していなければ、「命令」は実行せずにwhile文の実行をやめて次に進みます
ちょっとわかりづらいですよね。もう少しプログラムに近い形で動作を確認してみます。例えば以下のようなスケッチがあるとします。
Arduinoボードはこのスケッチを以下のように実行します。
- 電源投入後、setupの処理を開始します
- whlie構文の「条件」を判定します
- 条件が成立していれば「命令1」を実行します。「命令1」を実行後はすぐに(2)に戻ります
- 条件が成立していなければwhille構文の制御を終えてsetupを終了します(条件が成立していない場合は「命令1」は実行せずに(5)に進みます)
- loopの「命令2」をずっと繰り返します
while構文は、
条件が成立している間、ずっと命令を繰り返す
、という制御になる、ということを押さえてください。
「スイッチが押されたら点滅を開始する」の実現方法
このwhile構文を使って「スイッチが押されたらLEDの点滅を開始する」という動作を実現します。このような動作をするには、以下のようなスケッチを書きます。スケッチはわかりやすいように日本語まじりで書いてします。
日常生活では「スイッチが押されたらLEDの点滅を開始する」と考えますが、プログラムの世界では以下の順番で処理をするように書くわけです。
- 「スイッチが押されていない」という条件が成立している間、何もしない
- 青色LEDの点滅をする
このような順番で動作をした結果、見た目には「スイッチが押されたらLEDの点滅を開始する」というようになるわけです。う〜ん、ややこしいですね。
このプログラミングではこのような考え方が他にもいろいろ出てきます。最初はとっつきづらいと思いますが、大丈夫です、だんだん慣れてきます。
while構文の詳細
次にwhile構文の書き方を詳しく説明します。
while構文は、条件が成立している間、「while(条件)」の次に書いてある「1つの命令」を繰り返し実行します。
while(条件) // この条件が成立している間
命令; // この1つの命令を実行する
それでは、実行したい命令が2つ以上ある場合はどのように書けばいいのでしょうか。
命令が2つ以上ある場合、今までも出てきた「{」と「}」で命令を一つのかたまりにして以下のように書きます。
while(条件) {
命令1;
命令2;
命令3;
}
なお、命令のかたまりを書く場合、このようにインデントを入れるようにします。while構文の命令のかたまりをわかりやすくするためです。
なお、人によってはwhile構文を以下のように書く方もいます。
while(条件)
{
命令1;
命令2;
命令3;
}
このシリーズでは、最初の方の書き方を採用しています。理由は、Arduino IDEで新規ファイルを作成した場合、setupやloopは以下のようになっているので、これに合わせたためです。
void setup() {
}
それでは、whileの条件が成立している間、何もしない場合はどのように書けばいいのでしょうか。何もしない場合、2通りの書き方があります。
ひとつは以下のように「;」だけを書いて命令がないことを表現する方法です。
while(条件)
;
もうひとつは以下のように命令のかたまりの中に命令がないことを表現する方法です。
while(条件) {
}
なお、命令のかたまりがないことを表現するとき、以下のように「;」と書いても問題ありませんが、この書き方は見たことがありません。
while(条件) {
;
}
このように「『ない』がある」というように、ないことを表現するのは、以前出てきたドーナツの穴の問題のように奥深いものがあります。一概にどれがいいというのは言い切れませんが、一般的によく使われるのは2番目の
while(条件) {
}
この書き方です。このシリーズではwhile構文で何も命令がない場合はこの表現をすることにします。
それでは、ここまでの知識をもとに、スイッチが押されたら青色LEDの点滅を開始する、というスケッチに変更してみます。「kitchen_timer」スケッチを以下のように変更しました。
前回から変更したところは、スイッチの端子番号の#define、スイッチの端子のpinMode設定、while文の追加です。
このスケッチは1ヶ所完成していません。while構文の条件の部分です。この部分はどのように書けばよいのか検討します。
比較演算子
スイッチの状態は「digitaRead命令」で知ることができました。Serial.printlnで確認した結果、以下のことがわかりましたよね。
digitalReadの値 | 意味 |
---|---|
1 | スイッチが押されていない |
0 | スイッチが押されている |
先ほどのwhile構文の条件のところは「スイッチが押されていない」でしたよね。ということは、whileの条件の部分は「digitalReadの値が1」ということです。
while(digitalReadの値が1) {
}
今回は、digitalReadの値が1かどうか確認しますが、このようにある値とある値を比較することが多くあります。このような比較をするために、C/C++言語では「比較演算子」というものが用意されています。
「比較演算子」というと意識高い感じですが、要するに2つの値を比較する記号、ということです。会話するときに「比較するときの記号ってなんだっけ?」と言ってはダメです。「比較演算子は何を使うんだっけ?」と言います。
それではどのような記号が用意されているか確認しましょう。
「<」とか「>」って小学校のときに習いましたよね。これはいいとして、他の記号はちょっとなじみがない感じです。
「以上」「以下」は数学では「≧」「≦」という記号でしたが、キーボードではこの記号は入力できないので、バラして「>=」「<=」が使われています。
等しいか異なるかという記号は数学では、「=」と「≠」でしたよね。プログラミングでは、「=」は他の目的で使用されるので(第23回以降の記事で出てきます)、ちょっとヤケになった感じで「==」が使われています。また「≠」は半角文字にこの記号はないので、これもちょっとヤケになった感じで「!=」が使われています。
異なる記号については、半角文字に縦棒「|」がありますので、「|=」の方が適しているのではないか、という気もします。でも「|=」は別の目的で使用されるので「!=」が使われています。「|=」はかなり高度な内容になりますので説明は省力いたします。
それではキッチンタイマーのスケッチに戻りましょう。
完成していないところは、
while(スイッチが押されていない) {
}
でした。スイッチが押されていない、ということは、digitalRead(SWITCH)の値が1ということですので、この部分は
while(digitalRead(SWITCH) == 1) {
}
と書けばOKです。よくやってしまいがちなのが「等しい」は「=」と頭にこびりついているので、
while(digitalRead(SWITCH) = 1) {
}
というスケッチです。これはこれで根深い問題を起こしますので十分注意してください。
昔、「==」を「=」と書いてしまい、たったこの1文字が間違っていたために大手企業のシステムが止まってしまった、なんてニュースを聞いたことがあります。このように書くとどうなるかは「=」を説明した後に詳しく説明します。
それでは、スイッチを押したら青色LEDの点滅を開始する、というスケッチに仕上げましょう。
/*
* キッチンタイマー
*
* 内容: スイッチ、LED、スピーカーを使ったキッチンタイマー
* 変更履歴:
* 2019.8.11: 新規作成
* 2019.8.15: スタートスイッチ処理を追加
*/
// 秒を表現するLED関連(青色LED)
#define BYOU_LED 12 // 秒を表現する青色LEDの端子番号
#define BYOU_ON 50 // 秒を表現するLEDをつけている時間 (単位:ミリ秒)
#define BYOU_OFF 1000 - BYOU_ON // 秒を表現するLEDを消している時間 (単位:ミリ秒)
// スタートスイッチ関連
#define SWITCH 23 // スイッチを接続している端子番号
void setup() {
// 端子の設定
pinMode(BYOU_LED, OUTPUT); // 青色LED接続端子設定
pinMode(SWITCH, INPUT_PULLUP); // スイッチ接続端子の設定
// スイッチが押されるまで待つ
while(digitalRead(SWITCH) == 1) {
}
}
void loop() {
// 1秒に1回青色LEDを点滅する
digitalWrite(BYOU_LED, HIGH);
delay(BYOU_ON);
digitalWrite(BYOU_LED, LOW);
delay(BYOU_OFF);
}
スケッチを作成したら、Arduinoボードに送ります。送り終わると何も反応しませんが、スイッチを押すと青色LEDが点滅を開始します。
「何もしない」スピード
while構文を使用して「スイッチが押されるまで待つ」というスケッチを作成しました。
while(digitalRead(SWITCH) == 1) {
}
スイッチが押されるまで何もしないとはいえ、Arduinoボードはスケッチのこの部分で、23番ピンの電圧計の値を確認して、1と比較して、等しければまた23番ピンの電圧計の値を確認して、、、ということをずっと繰り返しています。
スイッチが押されるまで、何度も何度も23番ピンの電圧計を確認するのですが、1秒間にどのぐらい確認していると思いますか?
せいぜい1秒間に数十回でしょうか、いやいやすごいスピードで動いてるわけだから1秒間に数百回でしょうか。
実は1秒間にだいたい100万回程度、電圧計を確認しては1と比較して、ということをしています。すごい早いですね。
次回はwhile構文についてさらに詳しく理解してみます。
更新履歴
日付 | 内容 |
---|---|
2019.8.16 | 新規投稿 |
2021.8.22 | 新サイトデザイン対応 |
アルディーノIDEでのプログラムは分かったのですが、アルディーノUNO R3を、使っているのでブレッドボードをどのように配線を繋げればいいのかわからないです。
現在はプログラムをエラーなく実行できるのですが、スイッチを押す押さない関係なく、常にLEDがピカピカ設定した秒数消えたりついたりします。ブレッドボードの画像や、やり方を教えてほしいです。
ご質問どうもありがとうございます。
Arduino UNOの場合、Microとはピン番号が異なります。
例えばスイッチをA5端子に接続している場合、内部の番号は19番になります。
(Microの場合は23番)
そこで、最初にスイッチの番号を#defineで定義しているところを23から19に変更していただければと思います。
また、A5端子に接続している場合、次のように接続すればA5-スイッチ-GND端子に接続されることになります。
不明点がありましたら追加でご質問いただければと思います。
すごく分かったです!
コメントどうもありがとうございます。
プログラミングはわかりやすく説明するのがなかなか難しいこともありますので、わからないことがありましたらコメントいただければと思います。
大変参考になり5分タイマーを作成し、目薬点滴時に便利に使っており、ありがとうございます。またさらにボタン追加し1分タイマーも織り込みたいのですが、いろいろ試しましたがうまくいきません。何かヒントをいただけたらとよろしくお願いいたします。
ご質問どうもありがとうございます。
このシリーズではボタン1個を用意してスタートボタンに使用していますが、空いている端子にもう1つボタンを追加して、「5分タイマー用ボタン」「1分タイマー用ボタン」を用意する、というイメージでしょうか。
プログラミングの方法は色々とありますが、すみません、実装しやすい方法はこのシリーズでは触れていない文法を使った方がいいかもしれません。
まずその文法ですが、while文を抜ける(強制的に終了させる)「break」というものです。具体例を見た方が理解が早いと思います。break文は以下のように使用します。
このプログラムは、while( ture )としていますので、永遠繰り返すことになっています。ただ、while構文の中で、スイッチ1の状態を確認して、スイッチ1が押された場合、「break;」を実行しますが、このbreakはwhile文を抜ける(while文を終了する)命令となっています。このbreak文を使用して、2つのスイッチの状態をチェックしよう、というわけです。
Arduinoにスイッチ1とスイッチ2の2つのスイッチを接続して、それぞれのスイッチを1分タイマーと5分タイマーのスタートボタンに割り当てる場合、以下のようなプログラムがわかりやすいのではないかと思います。
(loop関数内に書きます)
以上ですが、プログラミングに慣れていないと、ちょっとわかりづらいところもあるかもしれません。不明点ございましたらお手数ですがコメントいただけると幸いです。
実は、このシリーズはわかりづらいところもあるので、そろそろ記事を書き直そうとしておりました。基礎編1では必要ないかと思っていましたが、今回ご質問いただいた内容を考慮して、while文でのbreakの使い方は説明しておりませんので、記事書き直しの際に追加説明しようと思います。
ご質問いただいてどうもありがとうございました。