timerがオーバーフローしたらどうなるか?
Arduinoで時間を測定するにはmills()
やmicros()
という関数があります。
これはArduinoの電源ONからの時間をミリ秒、またはマイクロ秒単位で返すものです。
長い時間を測定するにはmills()
をunsigned long
で取得すると最大約49日(もう少し正確には、232ミリ秒=4,294,967,296ミリ秒)まで測定することが出来ます。
基本的にはこの約49日というのが最大計測できる時間と考えられます。
この時間までにArduinoが再起動されれば、特に問題は起きないと考えられますが、これを超えたときどうなるか?ということを考えてみます。
こちらの記事の前半でシミュレーションがされていて、ここではオーバーフローが起きても問題にはならない、と書かれています。
garretlab.web.fc2.com
シミュレーションではなく実際にやってみて確認してみます。
ただし、検証を短時間で終えるためunsigned int
でmicros()
を使います。
66ミリ秒でオーバーフローが起きることになります。
テスト
unsigned int t = 0; unsigned int prev = 0; void setup() { Serial.begin(9600); prev = micros(); } void loop() { t = micros(); Serial.print(t); Serial.print(" "); Serial.println(t - prev); }
152 144 468 460 788 780 1120 1112 1528 1520 1932 1924 2352 2344 11524 11516 25044 25036 38564 38556 52084 52076 68 60 7348 7340 18788 18780 32308 32300 45828 45820 59348 59340 7332 7324 18772 18764 32292 32284 45812 45804 59332 59324 7316 7308 18756 18748 32276 32268 45796 45788 59316 59308
初回(オーバーフローまで)だけ、出力間隔が短いですがこれはもしかするとSerialにデータが貯まってない分処理が速いのかもしれません。
しかし、5万いくつの次でオーバーフローが起きて期待する結果とは違っているように見えます。
これはunsigned int
の16bit、つまり65,536μs
を超えたところと考えられます。
先の記事と同じことをシミュレーション
そこで先の記事と、おなじことを再現してみたいと思います。
unsigned int t = 0; unsigned int prev = 0; void setup() { Serial.begin(9600); } void loop() { prev = 1; t = 65530; for (int i = 0; i < 10; i++) { Serial.print(t + i); Serial.print(" "); Serial.println(t+i-prev); } while (1) {} }
65530 65529 65531 65530 65532 65531 65533 65532 65534 65533 65535 65534 0 65535 1 0 2 1 3 2
異なった結果です。こちらもリセットされてしまっています。
変数に代入したら違う結果になったりするか?
計測された時間diff
をいったんunsigned int
に格納してみます。
unsigned int t = 0; unsigned int prev = 0; unsigned int diff = 0; void setup() { Serial.begin(9600); prev = micros(); } void loop() { prev = 1; t = 65530; for (unsigned int i = 0; i < 10; i++) { Serial.print(t + i); Serial.print(" "); diff = t + i - prev; Serial.println(diff); } while (1) {} }
65530 65529 65531 65530 65532 65531 65533 65532 65534 65533 65535 65534 0 65535 1 0 2 1 3 2
結果は同じ。
時刻tをオーバーフローさせてみる。
unsigned int
の最大値を超えるようにt
を加算してみます。
unsigned int t = 0; unsigned int prev = 0; unsigned int diff = 0; void setup() { Serial.begin(9600); prev = micros(); } void loop() { prev = 1; t = 65530; for (unsigned int i = 0; i < 10; i++, t++) { Serial.print(t); Serial.print(" "); diff = t - prev; Serial.println(diff); } while (1) {} }
65530 65529 65531 65530 65532 65531 65533 65532 65534 65533 65535 65534 0 65535 1 0 2 1 3 2
結果は同じ。
t-prev がマイナスとなる場合
ひとつの大きな違いに気が付きました。
基準時刻がunsigned int
の最大値に近いところでやってみます。
unsigned int t = 0; unsigned int prev = 0; unsigned int diff = 0; void setup() { Serial.begin(9600); prev = micros(); } void loop() { prev = 65525; t = 65530; for (unsigned int i = 0; i < 10; i++, t++) { Serial.print(t); Serial.print(" "); diff = t - prev; Serial.println(diff); } while (1) {} }
65530 5 65531 6 65532 7 65533 8 65534 9 65535 10 0 11 1 12 2 13 3 14
再現しました。
つまり、t-prev < 0
となるときにこの現象が起きるということです。
現実的な状況で確認
unsigned int t = 0; unsigned int prev = 0; unsigned int diff = 0; void setup() { Serial.begin(9600); prev = millis(); } void loop() { t = millis(); Serial.print(t); Serial.print(" "); diff = t - prev; Serial.println(diff); prev = t; delay(5000); }
0 0 4999 4999 10000 5001 15000 5000 20000 5000 25000 5000 30002 5002 35002 5000 40002 5000 45002 5000 50003 5001 55004 5001 60004 5000 65005 5001 4469 5000 9469 5000 14470 5001 19470 5000 24471 5001 29471 5000 34471 5000 39473 5002 44473 5000 49473 5000 54473 5000 59474 5001 64475 5001 3939 5000 8939 5000 13939 5000 18940 5001 23941 5001 28941 5000 33942 5001
オーバーフローをしても問題ないことが確認できました。
まとめ
Arduinoで時間差を計測するときオーバーフローは考慮しなくても問題ないことが確認できました。
ただしこれは先の記事にも書かれていますがunsigned
であることが必要です。
また、時間差が変数のサイズunsigned long
を超えるとき(例えば、100日以上の時間差等)はもう一つ処理を加える必要があります。
例えば、millis()
が最大値付近の一定値を超えた、または0付近の一定値範囲にもどった、等をカウントしてunsigned long
相当の時間を加算する、といったことが考えられると思います。