実行タイミングの指定

はじめに

Arduino Unoを使って、タイミングを指定して処理を行う方法を考えていきます。タイミングにもいろいろありますが、ここでは、一定周期で処理を繰り返す方法を考えていきます。

他にも方法はあるかもしれませんが、一般的に利用されている以下の方法について考えます。

delay()を使う

実行したい処理や処理の対象が一つのときは、delay()を使って簡単に実現することができます。例えば、1秒周期で内蔵LEDを点滅させる場合は、以下のように書くことができます。これは、Blinkの処理と同じです。例えば、以下では、digitalWrite()を1秒(=1000ミリ秒)毎に実行します。

1
2
3
4
5
6
void loop() {
  digitalWrite(LED_BUILTIN, HIGH);   // turn the LED on (HIGH is the voltage level)
  delay(1000);                       // wait for a second
  digitalWrite(LED_BUILTIN, LOW);    // turn the LED off by making the voltage LOW
  delay(1000);                       // wait for a second
}

ただし、delay()を実行している間は、他の処理を行うことができません。このため、複数の処理を同時に制御したい場合は、そもそも処理ができなかったり、あるいは、複雑化してしまったりします。

例えば、LEDを点滅させながら、モーターの制御を行おうとしても、delay()の実行中はモーターの動作を変更させることはできません。あるいは、LED1を1秒周期、LED2を1.5秒周期で点滅させたい場合は、0.5秒ごとに何をするかを判断する必要があります。

millis()を使う

複数の処理を同時に制御する場合は、delay()の代わりに、millis()を使う方法があります。この方法の基本的な考え方は、以下の通りです。なお、ここでいう「時刻」は、通常利用する10時10分とかいう時刻ではなく、millis()で取得できる、Arduinoが起動してからの経過時間のことを示します。

基本的な考え方は、BlinkWithoutDelayで示されている通り、以下のようになります。

このとき、前回実行時刻の更新方法により、周期処理の実行精度が少し異なってきます。

  1. 前回実行時刻を、処理の実行前に取得した時刻で更新する方法

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    
    unsigned long prev, next, interval;
    
    void setup() {
      prev = 0;         // 前回実行時刻を初期化
      interval = 100;   // 実行周期を設定
    }
    
    void loop() {
      unsigned long curr = millis();    // 現在時刻を取得
      if ((curr - prev) >= interval) {  // 前回実行時刻から実行周期以上経過していたら
        // do periodic tasks            // 周期処理を実行
        prev = curr;                    // 前回実行時刻を現在時刻で更新
      }
      // do other tasks                 // その他の処理を実行
    }  
    

  2. 前回の前回実行時刻に、処理の実行周期を足す方法

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    
    unsigned long prev, next, interval;
    
    void setup() {
      prev = 0;         // 前回実行時刻を初期化
      interval = 100;   // 実行周期を設定
    }
    
    void loop() {
      unsigned long curr = millis();    // 現在時刻を取得
      if ((curr - prev) >= interval) {  // 前回実行時刻から実行周期以上経過していたら
        // do periodic tasks            // 周期処理を実行
        prev += interval;               // 前回実行時刻に実行周期を加算
      }
      // do other tasks                 // その他の処理を実行
    }  
    

i
ここではprevをグローバル変数としていますが、loop()内で使うだけであれば、loop()内でstatic変数として定義しても問題ありません。
i
「前回の前回実行時刻に、処理の実行周期を足す方法」は、millis()のオーバーフローの影響を受けます。

両者の比較

今回実行予定時刻とmillis()の実行時刻との差分(赤い線)だけ、次に設定する前回実行時刻が少し異なります。少し誇張して描いています。

millis()のオーバーフロー

millis()は、約50日後にオーバーフローします。ただし、millis()は、unsigned longを返す関数なので、(curr - prev) を実行する限りは、オーバーフローの影響は受けません。これは、C++/C言語の仕様で保証されています。詳細は、millis()のオーバーフローを参照してください。

タイマ割り込みを使う

Arduino Unoでは、タイマ割り込みが利用できるので、そちらを使い、一定周期で処理を繰り返すこともできます。

MsTimer2ライブラリを使う

タイマ割り込みを簡単に利用するためのライブラリの一つに、MsTimer2があります。MsTimer2を使うことで、指定した関数をミリ秒単位で繰り返し実行することができます。

参考URL

https://forum.arduino.cc/index.php?topic=223286.0

バージョン

Hardware:Arduino UNO R3
Software:Arduino AVR Boards 1.8.6

最終更新日

June 2, 2024

inserted by FC2 system