LoginSignup
6

More than 1 year has passed since last update.

delay()無しでdelay()な感じにしたい

Posted at

delay()は便利だけど

delay()を使うとその間すべての処理が止まります。そりゃもうCPUガッチガチです。Pythonで言うtime.sleep()とかです。
処理の間隔を調整したり何かとお世話になりますが。

例えば下記のLチカコード。
毎度書いてる気がするのでちょっと変化球なLチカコードにしましたがまぁいつもの「1秒ごとについたり消えたり」するやつです。

blink
void loop()
{
    static int state1 = HIGH;
    state1 = blink(LED1, state1);
    delay(1000);
}

int blink(int pin, int state)
{
    digitalWrite(pin, state);
    return state = HIGH - state;
}

近所のおじちゃんがこう言っていました。「delay()気持ち悪い」
もちろんこれで難なく動きます。しかし今まで全く気にならなかったのですが、何が気持ち悪いかと言うと、delay(1000)の間、完全に処理が停止していることです。Arduinoの公式リファレンスにもこう記載されています。

delay()を使えば簡単にLEDをチカチカさせることができます。また、スイッチのバウンス対策のためにdelay()を使っているスケッチもよく見られます。ただし、こうしたdelay()の使い方には不利な点があります。delay()の実行中は、計算やピン操作といった他の処理が実質的に止まってしまうのです。delay()の代わりにmillis()を使って時間を測り、タイミングをコントロールするほうがいいでしょう。熟練したプログラマーは、よほどスケッチが簡単になる場合を除き、10ms以上のイベントのコントロールにdelay()を使うことは避けるでしょう。

割込みこそ有効なものの、ガチガチに処理が止まるみたいですね。
なになに?

熟練したプログラマーは、よほどスケッチが簡単になる場合を除き、10ms以上のイベントのコントロールにdelay()を使うことは避けるでしょう。

どうやら近所のおじちゃんは熟練のプログラマだったようです。
さぁ、熟練したプログラマになりましょう!(汗)

delay()使わない処理に変えてみた

考え方のヒントは公式リファレンスにあります。
millis()を使って時間を測り、タイミングをコントロールすればいいそうです。
ヒントを元にコーディング。では行こう。

no_delay
void loop()
{
    // staticを付けて変数の値を保持します。
    static unsigned char count = 0;
    static unsigned long millis_buf = 0;
    static int state1 = HIGH;
    static int state2 = HIGH;
    static int state3 = HIGH;

    // 現在の経過時間-この待機処理を通過した時間が1000(ms)になるまで待機
    while ((millis() - millis_buf) < 1000)
    {
        ;
    }
    // 上の待機から抜けた時間を格納
    millis_buf = millis();
    Serial.println(millis_buf);

    // カウントを取る
    count++;

    // カウントを1で割った余り0(ここを通る度に処理)
    if ((count % 1) == 0)
    {
        // LEDのON,OFFを反転させる
        state1 = blink(LED1, state1);
    }
}

int blink(int pin, int state)
{
    digitalWrite(pin, state);
    return state = HIGH - state;
}

こんな感じになりました。
whileを抜けた後にmillis_bufに現在の時間を代入することで1000ms周期を作り出します。
あとは1000ms毎に行いたい処理を下に書けば見事delay()無しでもdelayな感じの処理ができました!やったね。

これ、処理のタイミングずらしたら別々に動くんじゃね?

ここをいじります

    if ((count % 1) == 0)
    {
        state1 = blink(LED1, state1);
    }

以下のように追記します。

if ((count % 1) == 0)
    {
        state1 = blink(LED1, state1);
    }
    // 2の余りを取る(1秒間隔の、2回に一回処理する)
    if ((count % 2) == 0)
    {
        state2 = blink(LED2, state2);
    }
    // 4の余りを取る(4回に一回処理)
    if ((count % 4) == 0)
    {
        state3 = blink(LED3, state3);
    }

count変数は一連の処理一周ごとにインクリメントされます。
で、あればif文の中身を弄ってやれば、一定周期の中で任意のタイミングで別の処理ができますね!

2進数カウンタ爆誕

GIF-210325_205423.gif

はい、簡単に2進数カウンタの出来上がり~!

とりあえずわかりやすく1000msをそのままベタ書きしましたが、100msでループをブン回して10回まわる毎に処理をすれば上記と同じく1秒毎、2秒毎、4秒毎に動作させることができます。
こんな感じに書き換えます。

    // さっき1000msだったところを100msに変更
    while ((millis() - millis_buf) < 100)
    {
        ;
    }
    // こっち側が1秒間に10回回るようになる
    millis_buf = millis();
    Serial.println(millis_buf);

    count++;

    // カウントを10で割った余り0(ここを通る度に処理)
    if ((count % 10) == 0)
    {
        state1 = blink(LED1, state1);
    }
    if ((count % 20) == 0)
    {
        state2 = blink(LED2, state2);
    }
    if ((count % 40) == 0)
    {
        state3 = blink(LED3, state3);
    }

こうするとif文の中の割る数をより細かく調整できるので、使うマイコンの処理能力や処理内容に合わせて応用が利きそうです。
今開発してるプログラムでちょうどこういう処理がしたかったのでいい勉強になりました。熟練プログラマに一歩近づけましたね!

終わりに

つまりポーリングじゃん。

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6