delay(time); vs if(millis()-previous> time); そしてドリフト


8

古いプロジェクトを通過すると、次のようなコードが2つのArduino Dueにありました

void loop()
{
  foo();
  delay(time);
}

心に取って大多数文献を使用してdelay();、私としてこれを再コード

void loop()
{
  static unsigned long PrevTime;
  if(millis()-PrevTime>time)
  {
    foo();
    PrevTime=millis();
  }
}

ただし、これにより、2つのデバイスが以前はドリフトしなかった期間にドリフトする状況が発生したようです

私の質問は2つあります。

  1. なぜif(millis()-PrevTime>time)よりドリフトを引き起こすの でしょうdelay(time)か?
  2. 戻ることなくこのドリフトを防ぐ方法はありdelay(time)ますか?

1
ドリフトに気づく「期間」の大きさはどの程度ですか?2つのデバイスは同じ場所にありますか?つまり同じ温度ですか?それらは水晶発振器またはセラミック共振器で動作しますか?
joseは2017

個人的に私はマジェンコのソリューションを好み、常にそれを使用します(インクリメントを他の指示の前に置きますが、これは単なる好みです)。ただし、このタイミングは正確に100msですが、他のコード(foo; delay;)の周期は100msよりも長いことに注意してください(100ms +の時間ですfoo)。したがって、ドリフトが発生します(ただし、ドリフトしているのは、delay実装されたSWです)。いずれの場合も、クロックが等しくないため、完全に等しい実装でも「ドリフト」することに注意してください。完全な同期が必要な場合は、信号を使用して2つのプログラムを同期します。
frarugi87 2017

2つのデバイスは隣り合っており、金曜日の17:00から月曜日の9:00に実行した後、4分のドリフトがありました。私はあなたの提案に従って入力を同期するためにデジタルピンを使用するつもりです
ATE-ENGE

「2つのデバイスは互いに隣接しています...」ということは、タイミングメカニズムが正確ではないという意味ではありません。これは、2つのクリスタルオシレータでは高いが、1つのセラミック共振器でも妥当な約800ppmのエラーレートについて話していることを意味します。それをかなり正確なタイミング基準と比較する必要があります。水晶は通常20ppm以内であり、tcxoは1ppm未満で実行できます。それが私のやり方です。
dannyf 2017

回答:


10

Arudinoで時間を使って作業する際に覚えておかなければならない重要なことが1つあります。

  • すべての操作には時間がかかります。

foo()関数は、ある程度の時間がかかります。その時はなんて言えない。

時間を処理する最も信頼できる方法は、トリガーの時間のみに依存することであり、次のトリガーが必要なときのワークアウトではありません。

たとえば、次のようにします。

if (millis() - last > interval) {
    doSomething();
    last = millis();
}

変数lastは、ルーチンがトリガーした時間と、doSomething実行にかかった時間です。したがってinterval、100とするとdoSomething、実行に10ミリ秒かかります。101ミリ秒、212ミリ秒、323ミリ秒などでトリガーが発生します。期待していた100ミリ秒ではありません。

したがって、できることの1つは、特定の時点でそれを覚えておくことで、常に同じ時間を使用することです(Jurajが示唆しています)。

uint32_t time = millis();

if (time - last > interval) {
    doSomething();
    last = time;
}

今、doSomething()かかる時間は何にも影響を与えません。したがって、101ミリ秒、202ミリ秒、303ミリ秒などでトリガーが発生します。100ミリ秒以上経過していることを探しているため、101ミリ秒以上を意味します。代わりに使用する必要があります>=

uint32_t time = millis();

if (time - last >= interval) {
    doSomething();
    last = time;
}

ここで、ループで他に何も起こらないと仮定すると、100ms、200ms、300msなどでトリガーが発生します。しかし、ビットに注意してください:「ループで他に何も起こらない限り」 ...

5ミリ秒かかるオペレーションが99ミリ秒で発生した場合はどうなりますか?次のトリガーは104msまで遅延されます。それはドリフトです。しかし、戦闘は簡単です。「記録された時間は今です」と言う代わりに、「記録された時間はそれよりも100ms遅い」と言います。つまり、コードでどのような遅延が発生しても、トリガーは常に100ミリ秒間隔で行われるか、100ミリ秒のティック内でドリフトします。

if (millis() - last >= interval) {
    doSomething();
    last += interval;
}

これで、100ms、200ms、300msなどでトリガーが発生します。または、コードの他のビットに遅延がある場合、100ms、204ms、300ms、408ms、503ms、600msなどが発生する可能性があります。常に、遅延に関係なく可能な限り間隔。また、間隔よりも大きい遅延がある場合は、現在の時刻に追いつくのに十分なだけルーチンが自動的に実行されます。

ドリフトする前に。今、あなたはジッターを持っています


1

操作後にタイマーをリセットするため。

static unsigned long PrevTime=millis();

unsigned long t = millis();

if (t - PrevTime > time) {
    foo();
    PrevTime = t;
}

番号。PrevTimeは静的であることに注意してください。
dannyf 2017

4
@dannyf、はい、そして?
ジュラジ

「静的」の意味を知っていれば、答えが正しくない理由がわかるでしょう。
dannyf 2017

staticがローカル変数で何をするか知っています。なぜあなたは私の答えがstaticと何か関係があると思うのか分かりません。foo()が呼び出される前に、現在のミリ秒の読み取り値を移動しました。
Juraj

-1

あなたがやろうとしていることに対して、delay()はコードを実装する適切な方法です。if(millis())を使用する理由は、メインループがループを継続できるようにして、コードまたはそのループ外の他のコードが他の処理を実行できるようにする場合です。

例えば:

long next_trigger_time = 0L;
long interval = DESIRED_INTERVAL;

void loop() {
   do_something(); // check a sensor or value set by an interrupt
   long m = millis();
   if (m >= next_trigger_time) {
       next_trigger_time = m + interval;
       foo();
   }
}

これにより、指定された間隔でfoo()が実行され、その間ループを継続して実行できます。ドリフトを最小限に抑えるために、foo()の呼び出しの前にnext_trigger_timeの計算を入れましたが、それは避けられません。ドリフトが重大な問題である場合は、割り込みタイマーまたはある種のクロック/タイマー同期を使用してください。また、millis()は一定期間後にラップアラウンドするので、コード例を単純に保つためにそれを考慮しなかったことも覚えておいてください。


これについて言及するのが嫌い:52日間のロールオーバーの問題。

回答の最後でロールオーバーの問題についてはすでに触れました。
ThatAintWorking 2017

さて、それを解決します。

私があなたのためにコードを書いてほしいなら、私の標準的なコンサルティング料金は$ 100 / hrです。私が書いたことは十分に関連があります。
ThatAintWorking 2017

1
マジェンコがあなたのものより完全でより良い答えを投稿したことを知っていますか?コードがコンパイルされないことを知っていますか?それlong m - millis()はあなたがやろうとしていることをしていませんか?それは家にあります。

-4

あなたのコードは正しいです。

あなたが遭遇する問題はmillis()に関するものです:それはわずかに過小評価されます(最大の過少カウントは、呼び出しごとに1ミリ秒の恥ずかしさです)。

解決策は、micros()のようなより細かい目盛りを使用することですが、これもわずかに過小評価されます。


2
少し少なめに計算されます」についての証拠または参照をいくつか提供してください。
Edgar Bonet
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.