thkanaのアイコン画像
thkana 2022年04月21日作成 (2022年04月30日更新) © CC BY 4+
セットアップや使用方法 セットアップや使用方法 Lチカ Lチカ 閲覧数 5221
thkana 2022年04月21日作成 (2022年04月30日更新) © CC BY 4+ セットアップや使用方法 セットアップや使用方法 Lチカ Lチカ 閲覧数 5221

[Arduino] マルチタスクのまねごと(1:まずはLED2個の点滅から)

まずは普通にLチカ

最近のマイコンを使った電子工作の最初の一歩、サイタサイタサクラガサイタ(いつの時代?)、HelloWorldといえばとりあえずLチカが定番です。Arduino UNOでボード上のLEDを使うとこんな感じでしょうか。

// Program 1
void setup() {
    pinMode(13, OUTPUT);
}

void loop() {
    digitalWrite(13, HIGH); //LEDを点灯する
    delay(1000); //1秒間なにもしない

    digitalWrite(13, LOW); //LEDを消灯する
    delay(1000); //1秒間なにもしない
}

LEDを一定周期で点滅させました、最初はこれだけでも「おぉ~」となりますよね。で、次は? 例えばスイッチでその点滅を制御してみたくなる、そうするといきなり躓いてしまうような例を時々見かけるのでその話を書いてみました。

Lチカ x 2

スイッチを例に出しましたけれど、その前に、まずはLED2個を自在に点滅させてみましょう。なぜLED2個? それぞれの状態が見えるので説明しやすいからです。

同時点滅

LED2個を同時に点滅させてみましょうか。特に問題ないと思います。2個のLEDのピン番号を変数(定数)にしておきましょうか。とりあえずLED2は12ピンに繋ぐとします。

// Program 2.1
const int LED1=13;
const int LED2=12;

void setup() {
    pinMode(LED1, OUTPUT);
    pinMode(LED2, OUTPUT);
}

void loop() {
    digitalWrite(LED1, HIGH); //LED1を点灯する
    digitalWrite(LED2, HIGH); //LED2を点灯する
    delay(1000); //1秒間なにもしない

    digitalWrite(LED1, LOW); //LED1を消灯する
    digitalWrite(LED2, LOW); //LED2を消灯する
    delay(1000); //1秒間なにもしない
}

2秒周期と4秒周期

今度は、LED1を2秒周期、LED2を4秒周期で点滅してみます。まずはベタに...

// Program 2.2
const int LED1=13;
const int LED2=12;

void setup() {
    pinMode(LED1, OUTPUT);
    pinMode(LED2, OUTPUT);
}

void loop() {
    digitalWrite(LED1, HIGH); //LED1を点灯する
    digitalWrite(LED2, HIGH); //LED2を点灯する
    delay(1000); //1秒間なにもしない

    digitalWrite(LED1, LOW); //LED1を消灯する
    delay(1000); //1秒間なにもしない

    digitalWrite(LED1, HIGH); //LED1を点灯する
    digitalWrite(LED2, LOW); //LED2を消灯する
    delay(1000); //1秒間なにもしない

    digitalWrite(LED1, LOW); //LED1を消灯する
    delay(1000); //1秒間なにもしない
}

あざとくも区切って書きましたが...loop()関数の方は4つの部分に分けられるのが明白だと思います。

  • パート0では
    LED1を点灯する
    LED2を点灯する
  • パート1では
    LED1を消灯する
    (LED2は操作しないのでそのまま)
  • パート2では
    LED1を消灯する
    LED2を消灯する
  • パート3では
    LED1を消灯する
    (LED2は操作しないのでそのまま)

これをLOOP()関数で繰り返して実行しているわけです。

さてここで、別のプログラムにより道。

// Program 2.3
void setup(){
   Serial.begin(9600);
}

int part=0;
void loop(){
    Serial.println(part);
    part++;
    if(part == 4){
        part = 0;
    }
    delay(1000);
}

このプログラムの動きはどうなるでしょうか。loop()関数を実行する毎にシリアルモニタに出るpartの値が増えていくのですが、partが4になると0に書き換えてしまうので、結果としてloop()が呼ばれる毎に0, 1, 2, 3, 0, 1, 2, 3, 0, ...という繰り返しになります。こう言うと、より道した理由が見えてきますでしょうか。Program 2.2の動作をProgram 2.1に取り込むと、こんなプログラムが書けます。

// Program 2.4
const int LED1 = 13;
const int LED2 = 12;

void setup() {
    pinMode(LED1, OUTPUT);
    pinMode(LED2, OUTPUT);
}

int part = 0;
void loop() {
    if (part == 0) {
        digitalWrite(LED1, HIGH);  // LED1を点灯する
        digitalWrite(LED2, HIGH);  // LED2を点灯する
    }
    if (part == 1) {
        digitalWrite(LED1, LOW);  // LED1を消灯する
    }
    if (part == 2) {
        digitalWrite(LED1, HIGH);  // LED1を点灯する
        digitalWrite(LED2, LOW);   // LED2を消灯する
    }
    if (part == 3) {
        digitalWrite(LED1, LOW);  // LED1を消灯する
    }
    part++;
    if (part == 4) {
        part = 0;
    }
    delay(1000);
}

(switch文で書きたいとか、if(part == 1 || part == 3)でいいんじゃないとかもあるかと思います。その通りですので、そう書けるかたはそう書いてください。ここでは、partの値で動作を切り替えられる、ということを知って頂きたいのです)

さて、もう1段階飛躍します。今回はpartのそれぞれの値に動作が割り当てられましたが、別にpartが取りうる全ての値に動作が割り当てられないといけない、なんてことはありません。例えば、Program 2.3からLED1の動作を全部取り去ってしまえば、loop()関数は

void loop() {
    if (part == 0) {
        digitalWrite(LED2, HIGH);  // LED2を点灯する
    }
    if (part == 2) {
        digitalWrite(LED2, LOW);   // LED2を消灯する
    }
    part++;
    if (part == 4) {
        part = 0;
    }
    delay(1000);
}

となりますが、LED2は期待どおりに4秒周期で点滅しますよね。
だとすれば...LEDを2個に戻して、最後のdelay(1000)をdelay(100)にして、

// program 2.5
// setup()まではprogram 2.3から変更ないので省略
void loop() {
    if (part == 0) {
        digitalWrite(LED1, HIGH);  // LED1を点灯する
        digitalWrite(LED2, HIGH);  // LED2を点灯する
    }
    if (part == 10) {
        digitalWrite(LED1, LOW);  // LED1を消灯する
    }
    if (part == 20) {
        digitalWrite(LED1, HIGH);  // LED1を点灯する
        digitalWrite(LED2, LOW);   // LED2を消灯する
    }
    if (part == 30) {
        digitalWrite(LED1, LOW);  // LED1を消灯する
    }
    part++;
    if (part == 40) {
        part = 0;
    }
    delay(100);
}

これでもLED1が2秒周期、LED2が4秒周期で点滅します。
こうなってしまうと、変数の名前としてはpartというのはちょっと不適当かな...今後は単にloop()の実行回数を数えるということで、countという変数名に置き換えましょう。
(プログラムを作るときには、変数の名前というのは実は結構大事です。名前が適切でないと、プログラムの動作が読み取れなくなって変な間違いをしちゃいます。必要なときは、勇気を持って変数名は変えていきましょう。最近のプログラミング用のエディタだと、変数名をまとめて変えてくれる機能があったりもします)

いざ、自由自在に!

もう一つだけ予備知識。
日常生活では意識してはあまり使わないけれど、プログラミングでは時々使う演算に「剰余」ってのがあります。割り算したときの「余り」のことです。C/C++(そしてC++をベースとしたArduino言語)では、演算子%で表しますが、例えば10 割る3は3余り1で、これに対し10 % 3は余りである1を返す演算になります。これを知っていると何が嬉しいか...count変数が増えていくとき、N回に1回というような処理を書くときに、count % N == 0 はcountが増えていくと0, N, 2xN, 3xN,...とN回に1回真になります。また、count % N == Mは、0+M, N+M, 2xN+M, 3xN+M,... のときに真になるわけです。

それを知った上で...では、LED1を1.3秒点灯/0.7秒消灯、LED2を0.5秒点灯、1.2秒点灯するようにしてみましょうか。
LED1の点滅周期は1.3+0.7=2秒、LED2は0.5+1.2は1.7秒。公倍数をとると34秒です。この中で、1.7秒目毎にLED2を点灯、2秒目毎にLED1点灯、1.7秒毎+0.5秒でLED2消灯、2秒毎+1.3秒でLED1消灯してみます。

// program 2.6
// setup()まではprogram 2.3から変更ないので省略

int count = 0;
void loop() {
    // LED1の制御
    if (count % 20 == 0) {         // 2秒毎
        digitalWrite(LED1, HIGH);  // LED1を点灯する
    }
    if (count % 20 == 13) {  // 2秒毎+1.3秒
        digitalWrite(LED1, LOW);   // LED1を消灯する
    }
    // LED2の制御
    if (count % 17 == 0) {         // 1.7秒毎
        digitalWrite(LED2, HIGH);  // LED2を点灯する
    }
    if (count % 17 == 5) {      // 1.7秒毎+0.5秒
        digitalWrite(LED2, LOW);  // LED2を消灯する
    }
    // 全体周期の管理
    count++;
    if (count == 340) {  // 全体のワクは34秒
        count = 0;
    }
    delay(100);
}

この考え方を身につければ、LEDが何個でも、どんな周期でも自在にLEDを点滅できますね。
次回は、この応用として冒頭に書いた「スイッチで点滅を制御する」ことをやってみたいと思います。

1
thkanaのアイコン画像
一応組み込みエンジニアのつもり... でも最近製品になるようなコード書いてないなぁ。
  • thkana さんが 2022/04/21 に 編集 をしました。 (メッセージ: 初版)
  • thkana さんが 2022/04/21 に 編集 をしました。 (メッセージ: 誤記修正、体裁改善)
  • thkana さんが 2022/04/21 に 編集 をしました。 (メッセージ: 誤記修正)
  • thkana さんが 2022/04/21 に 編集 をしました。 (メッセージ: 次回へのリンク追加)
  • thkana さんが 2022/04/30 に 編集 をしました。 (メッセージ: プログラム連番の付け間違い修正)
ログインしてコメントを投稿する