第五項 Arduino開発基礎(if構文・ループ構文)
電子工作創作表現(2019/5/24)
スライドPDF
今日の内容
- if / for構文
- 配列変数
プログラムの基本は「上から下」
- 前回までやったルール「上から下」と「ジャンプ」
- 決まり切った動作しかできない
- 複雑な動作を実現するための「制御構造」(control structure)
前回まで、Arduinoのプログラムは基本的に上から下に動き、setup()関数は1度、loop()関数は繰り返し行われるという話をしました。
ですが、これだけだと決まりきった動作をさせることしかできません。もう少し複雑な動作を実現するための「制御構造」というのがいくつか用意されており、それを紹介していきたいと思います。
代表的な制御構造
-
if構文(If statement)
- for構文
- while構文
代表的な制御構造を解説していきます。「XX構文」という言い方をします。Arduinoのリファレンスページにもありますが、これらはArduino開発のベースになっているC/C++で定義されている構文になります。
if構文
- 特定の条件を満たした時だけ動作する
- もしも...だったら~をする
まず一つ目はif構文です。これは特定の条件を満たしたときにだけ実行したい処理を定義するための構文です。
書き方は
if (条件式) {
/* 条件を満たしたときの処理 */
}
という書き方をします。かっこで囲まれた条件式を満たす時だけ、この中に書いた処理が実行されるので、条件が満たされない場合は処理がスキップされます。
条件式 (conditional expression)
- 等号・不等号(== < >)で値を検証する
- 等しい(==) 以下・以上(>= <=) 等しくない(!=) など、少し書き方が特殊
- 結果はtrue(真)かfalse(偽)
条件式は、特定の変数や値が等しいか等しくないか、大きいか小さいかで定義します。イコールは等号が二つ、以上・以下は>=, <=など、ここでも数学とは少し違う書き方をします。
条件の書き方
if (digitalRead(13) == HIGH) //13番ピンがHIGH
if (analogRead(0) < 300) //アナログ0番が300「未満」
if (analogRead(0) <= 300) //アナログ0番が300「以下」
if (Serial.read() != 'a') //シリアル通信で来た文字がa以外
具体的には、このような書き方をします。Arduinoの場合はピンから入力した値を比較したり、シリアル通信で文字のやりとりを検証したりすることに使うことが多いくあります。
条件を満たさない時は「else」も使う
- 条件を満たさなかった時に実行する処理
そしてこのif文には後ろにelseという構文を追加することができます。これは逆に「条件を満たさなかった場合」の処理を記述するブロックで「もし~ならこう、そうでなければこう」という場合分けを書く際便利な書き方です。
if (条件式){
条件を満たした時の処理
}else{
条件を満たさなかった時の処理
}
if文を使った例
- if文を使うと複雑な動作も条件付けで実現できる
- 0.5秒周期と、0.3秒周期で点滅するLEDを同時に制御するには?
if文を使うとどういったことができるようになるか。単純に考えると「インタラクティブな要素が作れる」というのが素直な結論ですが、それだけではなく色々な表現の幅が生まれます。
例えば0.5秒ごとに点滅するLEDをBlinkのサンプルに沿って作るとdelay関数を使いますが、ここへ0.3秒ごとに点滅するLEDも付けたいとなるとどうなるでしょうか。もう1個Arduinoを用意すればできますが、それは無駄なのでIf文を組み合わせてこんな感じに実現することができます。
void loop()
{
if (millis() % 1000 < 500) digitalWrite(10, HIGH);
else digitalWrite(10, LOW);
if (millis() % 600 < 300) digitalWrite(9, HIGH);
else digitalWrite(9, LOW);
}
for構文
- 命令を少しずつ変えながら繰り返したい時に便利な構文
- 繰り返す回数や条件を指定することができる
二つ目はfor構文です。これは繰り返しをするための構文です。loop()関数は電源が入っている限り繰り返されるようになっていますが、このfor構文は有限回繰り返したい時に便利な構文です。
for (int i = 0;i < 10;i++)
{
/* 繰り返す処理 */
}
for文は初期化・繰り返し条件・ループ処理の3つをセミコロンで区切ってカッコの中に定義します。上の例ではiという変数を用意して、iが10未満の間繰り返すという条件になっています。ループの終わりまで到達すると「i++」が呼ばれてiの値が1増えます。
for文の書き方
for (初期化; 繰り返す条件式; カウント式)
for (int i = 0; i < 回数; i++) で覚えておくのが便利
カッコの中を日本語で書くとこのようになります。初期値を0以外にしたりカウントを++ではなく--にしたり、5ずつ追加するなど色々な応用が効きますが、はじめのうちはシンプルに特定の回数繰り返すものとして、int iから始まる書き方を覚えておくのが良いでしょう。i, j, K などを使うのが通例になっていますが、変数の名前は基本的に自由です。
初期値を工夫した用法
初期値を工夫して、変数iを有効活用する
for (int i = 9;i < 12;i++)
{
analogWrite(i, millis() % 255);
}
変数はループのカウンタとして使いますが、初期値や条件を工夫することで中の処理に活用することができます。
ただの繰り返しではなく、連続して変化する処理を複数行いたい時にはこのような書き方が便利です。
配列変数(array variables)
- for文と組み合わせてよく使う「配列変数」
- 複数の値を格納することが可能
そしてこのfor構文とセットで覚えたい「配列変数」を紹介します。
配列変数は同じ種類の変数をいくつも扱う時に便利なもので、変数名とインデックスの2つの要素があります。
変数の定義
- float value; //通常の変数は一つの値を保持する
- float arrValue[5]; //10個の数値を持てる配列変数
- float arrValue[] = {10, 12, 15, 31, 35}; //こういう書き方も
通常の変数は宣言(変数を用意)するときに変数の種類と名前を定義しますが、配列変数の場合名前の後ろに[10](角括弧、bracket)という形で作りたい変数の数を記述します。最初に入れておきたい数が決まっている時は、3つ目のような書き方も可能です。
便利な「添え字」
- 0から始まって、連続する
- 角括弧の中の添え字は数字なので、変数などを使って呼び出せる
この角括弧の中の数字は「添え字」や「インデックス」という呼び方をして、変数XXの何番目の数かということを表しています。
少し注意したいのは、添え字は0から始まるということです。なので、arrValue[5]と定義した場合添え字に指定できる数は0~4までということになります。
使い方
- 複数の数値をひとまとまりとして扱う
- 配列の添え字とForループを組み合わせて使う
どういう使い方が便利なのかというのを実践を交えて解説していきます。
複数の数値をひとまとまりとして扱うときには便利です。例えばフルカラーLEDは赤・青・緑の三色を組み合わせて色を表現しますが、こういうものをコントロールするときは赤用の変数、青用の変数…と別々に用意するよりも、色用の配列変数として定義するのが効率的です。
int led_pin[] = {9, 10, 11};
int led_color[3];
void setup()
{
for (int i = 0;i < 3;i++) pinMode(led_pin[i], OUTPUT);
}
void loop()
{
delay(12);
for (int i = 0;i < 3;i++)
{
led_color[i] = (sin(millis() / ((i+2) * 100.0)) + 1) * 128.0;
analogWrite(led_pin[i], led_color[i]);
}
}
演習
- 回路:https://www.tinkercad.com/things/5l5dPsFDRzz
- 梅:ダイヤルを途中まで上げると赤が光り、さらに上げるとオレンジが光る
- 竹:ダイヤルを上げていくと順番に光っていく(数が多いのでFor文を使おう)
- 松:ボタンを押したときに、LEDが順番にフラッシュする
梅の回答
ヒント:赤のLEDと橙のLEDをそれぞれIF文でコントロールしましょう。
analogRead(0)で値が取れるので、数値を使って光り方を変えていきます。
void setup()
{
for (int i = 2;i <= 9;i++)
{
pinMode(i, OUTPUT);
}
}
void loop()
{
delay(12);
//赤色が点灯する
if (analogRead(0) > 300) {
digitalWrite(2, HIGH);
}
else{
digitalWrite(2, LOW);
}
//橙色が点灯する
if (analogRead(0) > 1000) {
digitalWrite(3, HIGH);
}
else{
digitalWrite(3, LOW);
}
}
竹の回答
ヒント:基本的な方向性は梅と一緒ですが、8個別々に記述していくと大変です。
できるっちゃできますが、For構文を使うとよりきれいに書くことができます。
void setup()
{
for (int i = 2;i <= 9;i++)
{
pinMode(i, OUTPUT);
}
}
void loop()
{
delay(12);
for (int i = 2;i <= 9;i++)
{
//光る境目の値(スレッショルド)をiを使って定義する
if (analogRead(0) > i * 100) {
digitalWrite(i, HIGH);
}
else{
digitalWrite(i, LOW);
}
}
}
松の回答
ヒント:光るタイミングがスイッチに依存していて、かつそれぞれ独立しているので、LEDの状態を持っておくための変数を配列で持っておくと作りやすくなります。
int led_stat[8];
void setup()
{
for (int i = 2;i <= 9;i++)
{
pinMode(i, OUTPUT);
}
pinMode(10, INPUT_PULLUP);
}
void loop()
{
delay(12);
//ボタンが押された時
if (digitalRead(10) == LOW)
{
for (int i = 0;i < 8;i++)
led_stat[i] = 0;
}
for (int i = 0;i < 8;i++)
{
//led_statは常にカウントし続ける
led_stat[i]++;
//led_statの値が特定の範囲になった時、LEDを光らせる
if ((led_stat[i] > (i + 1) * 4) && (led_stat[i] < (i + 1) * 10))
{
digitalWrite(i + 2, HIGH);
}
else
{
digitalWrite(i + 2, LOW);
}
}
}