Arduino Lチカ最短コード導出

Arduino Lチカ最短コード導出

2024-10/26

マイコンのプログラムの入門として、L チカ (LED をチカチカさせる) があります。Arduino を使ったことのある方なら、次のようなコードを見たことがあるのではないでしょうか。

void setup()
{
    pinMode(0, OUTPUT);
}

void loop()
{
    digitalWrite(0, HIGH);
    delay(1000);
    digitalWrite(0, LOW);
    delay(1000);
}

実際には、L チカをしながら他の処理を行う場合がほとんどです。先ほどの例は delay でループを止めてしまうため、他の処理が行えません。ループは常に回っている必要があるのです。

ということで、ループを止めない非同期的な L チカを考えてみました。

const int LED_PIN = 0;

void setup()
{
    pinMode(LED_PIN, OUTPUT);
}

void loop()
{
    digitalWrite(LED_PIN, millis() % 2000 > 1000);
}

解説

millis 関数

millis 関数は、マイコンが起動してからの経過時間をミリ秒単位で返してくれる関数です。

void setup()
{
    Serial.begin(115200);
}

void loop()
{
    Serial.println(millis());
}

millis関数の値

剰余算をしてみる

この値に剰余算をしてみます。

void loop()
{
    Serial.println(millis() % 2000);
}

次のようなのこぎり波になります。2000 ミリ秒で剰余算をしているので、2 秒経過すると 0 に戻ります。

剰余算後のmillis

閾値を設けてみる

このノコギリ波に閾値を設け、閾値を超えているか判定すると L チカの完成です。また閾値を調整することで、点灯時間の調整もできます。

閾値を設けるイメージ

完成

const int LED_PIN = 0;

void setup()
{
    pinMode(LED_PIN, OUTPUT);
}

void loop()
{
    digitalWrite(LED_PIN, millis() % 2000 > 1000);
}

厳密には次のようにした方がいいかもです。HIGH == 1, LOW == 0 であることを Arduino が保証しているか不明なためです。

digitalWrite(LED_PIN, (millis() % 2000 > 1000) ? HIGH : LOW);

マイコンボード上に搭載の LED

マイコンボード上に搭載の LED と接続されているピン番号は LED_BUILTIN というマクロで定義されており、これを使うと次のように書けます。

void setup()
{
    pinMode(LED_BUILTIN, OUTPUT);
}

void loop()
{
    digitalWrite(LED_BUILTIN, millis() % 2000 > 1000);
}

複数点滅

非同期的に処理できる止めない証拠に、複数の LED を別々の周期で点滅させられます。

const int LED_PIN0 = 0;
const int LED_PIN1 = 1;

void setup()
{
    pinMode(LED_PIN0, OUTPUT);
    pinMode(LED_PIN1, OUTPUT);
}

void loop()
{
    digitalWrite(LED_PIN0, millis() % 1000 > 500);
    digitalWrite(LED_PIN1, millis() % 100 > 50);
}

ダメポイント

次のような欠陥がありますが、動作確認用途であれば問題ないかと思います。

  • millis() のオーバーフローに対応できない。オーバーフローした直後一瞬、点滅周期がおかしくなる。

  • ループに入った時に millis が 0 から始まらない場合(setup 内に delay がある場合など)、点滅の始まりの点滅周期がおかしくなる。

↓ これらに対応したスケッチ

厳密な非同期Lチカ
const int LED_PIN = 0;

void setup()
{
    pinMode(LED_PIN, OUTPUT);
}

static uint32_t invertedMs;  ///< 最後に反転した時刻
static bool     isHigh;      ///< 出力ピンの状態

void loop()
{
    if (millis() - invertedMs > 1000) // 差分を取ることでオーバーフローに対応
    {
        isHigh = !isHigh;     // 出力を反転
        digitalWrite(LED_PIN, isHigh);
        invertedMs = millis();
    }
}

剰余算が重い 👊

剰余算の処理時間が気になる場合、キャストや論理積によって(無理矢理)置き換えられます。

digitalWrite(LED_PIN, (uint8_t)millis() > 200);
// 同義: digitalWrite(LED_PIN, millis() % 256 > 200);
digitalWrite(LED_PIN, (millis() & 0b111111111) > 0b011111111);
// 同義: digitalWrite(LED_PIN, (millis() % 512) > 255);

おわり

今回説明した、のこぎり波に閾値を設けて HIGH、LOW を切り替える手法は、PWM 波形の生成原理を参考にしています。L チカを超周期の長い PWM と見ることができるのでは、と思いこのコードに至りました。

最後までご覧頂き有難うございました~ 👋