第22回 while構文と比較演算子

キッチンタイマーのスケッチを変更して、スイッチが押されたらLEDの点滅を開始するようにします。

習得した項目の確認

前回までの記事で、青色LEDを1秒に1回、ピカッ、ピカッ、と光らせることができました。また、スイッチの状態を読み取ることもできました。

これから考え方がだんだん複雑になってきますので、今まで習得した以下の命令を十分理解しておくようにしてください。

 

実現したいこと

一般的なキッチンタイマーにはスタートボタンがありますよね。前回までに作成したキッチンタイマーのスケッチでは、スケッチをArduinoボードに送るとすぐに青色LEDの点滅が始まってしまいます。そこで、スイッチを押したら点滅を開始するようにスケッチを変更したいと思います。

今まで習得したことで以下のことはできています。

実現したいことは「スイッチが押されたら青色LEDの点滅を開始する」という動作です。

「〇〇したら□□する」という動作はいろいろな場面で出てきそうですよね。これから新しい仕組みを習得してスイッチが押されたらLEDの点滅を開始するようにスケッチを変更していきましょう。

 

while構文

日常生活を振り返ってみると「〇〇したら□□する」という動作はたくさんありますよね。「アラームが鳴ったら起きる」とか「おこづかいをもらったら欲しかったものを買いに行く」とか、他にもいろいろあります。

ちょっと意外に思うかもしれませんが、プログラミングの世界では「〇〇したら□□する」という動作をこのまま書ける命令はないんです。「スイッチが押されたらLEDの点滅を開始する」というスケッチが書ければいいのですが、そのような命令は用意されていません。

とはいっても、これから「スイッチが押されたらLEDの点滅を開始する」という動作をするスケッチを書くわけですが、「〇〇したら□□する」という命令がないのにどうやって実現するのでしょうか。

これから、日常生活でよく出てくる「〇〇したら□□する」という考え方を変えますので、頭を柔らかくしておきましょう。

C/C++言語に限らず、どのプログラム言語でも「●●という条件が成立している間、■■をする」という構文が用意されています。この構文を使って「〇〇したら□□する」という動作を実現します。

今まで出てきたdigitalWriteなどは、スイッチを制御するという実際の動作を指示するものでした。このように動作を指示するため「命令」と呼んでいたわけです。これから習得するものはそのような実際の動作を「制御」する目的のため、「命令」ではなく「制御構文」または単に「構文」と呼ばれています(「構文」という言葉を忘れてしまっていたら第12回の記事を読み返してみてください)。

ところで、「●●という条件が成立している間、■■をする」ということができるのであれば、スイッチが押されたらLEDの点滅を開始するというのを、●●は「スイッチが押されている」にして、■■は「LEDの点滅をする」にすれば実現できるのでは?と思いませんでしたか?

でも良く考えてみると、「『スイッチが押されている』という条件が成立している間、LEDの点滅をする」と書いてしまうと、LEDを点滅させるにはスイッチをずっと押したままにしなくてはいけません。「スイッチが押されたら、LEDの点滅を開始する」という動作にはならないんです。日常では出てこないことを考えているので、だんだんややこしくなってきましたね。もうちょっと頑張りましょう。

C/C++言語では「●●という条件が成立している間、■■をする」という動作を実現するために以下の構文が用意されています。

while構文

Arduinoボードは、while構文を以下のように制御します。

  1. Arduinoボードは「条件」を判定します
  2. 「条件」が成立していれば、「命令」を実行します。命令の実行後はすぐに(1)に戻ります
  3. 「条件」が成立していなければ、「命令」は実行せずに次に進みます

ちょっとわかりづらいですよね。もう少しプログラムに近い形で動作を確認してみます。例えば以下のようなスケッチがあるとします。

while構文サンプル

Arduinoボードはこのスケッチを以下のように実行します。

  1. 電源投入後、setupの処理を開始します
  2. whlie構文の「条件」を判定します
  3. 条件が成立していれば「命令1」を実行します。「命令1」を実行後はすぐに(2)に戻ります
  4. 条件が成立していなければwhille構文の制御を終えてsetupを終了します(条件が成立していない場合は「命令1」は実行せずに(5)に進みます)
  5. loopの「命令2」をずっと繰り返します

while構文は、条件が成立している間、ずっと命令を繰り返す、という制御になる、ということを押さえてください。

 

「スイッチが押されたら点滅を開始する」の実現方法

このwhile構文を使って「スイッチが押されたらLEDの点滅を開始する」という動作を実現します。このような動作をするには、以下のようなスケッチを書きます。スケッチはわかりやすいように日本語まじりで書いてします。

while構文のスイッチ制御方法

日常生活では「スイッチが押されたらLEDの点滅を開始する」と考えますが、プログラムの世界では以下の順番で処理をするように書くわけです。

  1. 「スイッチが押されていない」という条件が成立している間、何もしない
  2. 青色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) {
}

というスケッチです。これはこれで根深い問題を起こしますので十分注意してください。このように書くとどうなるかは「=」を説明した後に詳しく説明します。

それでは、スイッチを押したら青色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 新規投稿
2019.8.17 while構文処理の考え方補足