プログラミング素人のはてなブログ

プログラミングも電気回路も専門外の技術屋の末端が勉強したことや作品をアウトプットするブログ。コードに間違いなど見つけられたら、気軽にコメントください。 C#、Python3、ラズパイなど。

Arduinoのtimerがオーバーフローしたとき計算される時間差はどうなるか?

timerがオーバーフローしたらどうなるか?

Arduinoで時間を測定するにはmills()micros()という関数があります。
これはArduinoの電源ONからの時間をミリ秒、またはマイクロ秒単位で返すものです。
長い時間を測定するにはmills()unsigned longで取得すると最大約49日(もう少し正確には、232ミリ秒=4,294,967,296ミリ秒)まで測定することが出来ます。
基本的にはこの約49日というのが最大計測できる時間と考えられます。
この時間までにArduinoが再起動されれば、特に問題は起きないと考えられますが、これを超えたときどうなるか?ということを考えてみます。

こちらの記事の前半でシミュレーションがされていて、ここではオーバーフローが起きても問題にはならない、と書かれています。
garretlab.web.fc2.com

シミュレーションではなく実際にやってみて確認してみます。
ただし、検証を短時間で終えるためunsigned intmicros()を使います。
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相当の時間を加算する、といったことが考えられると思います。