temporisation); vs if (millis () - précédent> heure); et dérive

8

En passant par un ancien projet, j'avais du code sur deux Arduino Due qui ressemblait à ceci

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

prenant à cœur la majorité de la littérature sur l'utilisation, delay();je recodé cela comme

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

Cependant, cela semble avoir créé une situation où les deux appareils dérivent sur une période de temps alors qu'ils ne l'avaient pas précédemment

Ma question est double:

  1. Pourquoi if(millis()-PrevTime>time)provoquerait plus de dérive que delay(time)?
  2. Existe-t-il un moyen d'empêcher cette dérive sans y revenir delay(time)?
ATE-ENGE
la source
1
Quel est l'ordre de grandeur de la "période" sur laquelle vous remarquez la dérive? Les deux appareils sont-ils au même endroit, donc à la même température? Fonctionnent-ils sur un oscillateur à cristal ou un résonateur en céramique?
jose can uc
Personnellement, je préfère la solution de Majenko, et je l'utilise toujours (je mets l'incrémentation avant les autres instructions, mais ce n'est qu'une préférence). Notez, cependant, que cette temporisation est PRECISEMENT 100 ms, tandis que l'autre code ( foo; delay;) a une période plus longue que 100 ms (c'est 100 ms + temps de foo). Vous allez donc faire l'expérience de la dérive (mais c'est le delaySW implémenté qui dérive). Dans tous les cas, gardez à l'esprit que même des implémentations parfaitement égales "dérivent", car les horloges ne sont pas égales; si vous avez besoin d'une synchronisation complète, utilisez un signal pour synchroniser les deux programmes.
frarugi87
Les deux appareils sont côte à côte, après avoir couru du vendredi à 17h00 au lundi à 9h00, il y a eu une dérive de 4 minutes. Je décide que je vais utiliser une broche numérique pour synchroniser les entrées selon votre suggestion
ATE-ENGE
"Les deux appareils sont côte à côte, ..." cela ne signifie pas que le mécanisme de synchronisation n'est pas précis, vous parlez d'un taux d'erreur de ~ 800ppm, élevé pour deux oscillateurs à cristal mais raisonnable pour même un résonateur en céramique. vous devez le comparer à un standard de synchronisation raisonnablement précis pour être sûr: les cristaux sont généralement à moins de 20 ppm, et les tcxo peuvent faire moins de 1 ppm. ce serait ma façon de procéder.
dannyf

Réponses:

10

Il y a une chose importante dont vous devez vous souvenir lorsque vous travaillez avec du temps sur un Arudino de n'importe quelle forme:

  • Chaque opération prend du temps.

Votre fonction foo () prendra un certain temps. Quelle est cette heure, nous ne pouvons pas le dire.

Le moyen le plus fiable de gérer le temps est de ne compter que sur le temps de déclenchement, et non de déterminer quand le prochain déclenchement devrait avoir lieu.

Par exemple, prenez ce qui suit:

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

La variable lastsera le temps que la routine a déclenché * plus le temps nécessaire doSomethingà l'exécution. Disons que intervalc'est 100 et qu'il doSomethingfaut 10 ms pour s'exécuter, vous obtiendrez des déclenchements à 101 ms, 212 ms, 323 ms, etc. Pas les 100 ms que vous attendiez.

Donc, une chose que vous pouvez faire est de toujours utiliser le même temps indépendamment en vous en souvenant à un moment précis (comme le suggère Juraj):

uint32_t time = millis();

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

Maintenant, le temps qui doSomething()prend n'aura plus aucun effet. Vous obtiendrez donc des déclenchements à 101 ms, 202 ms, 303 ms, etc. Toujours pas tout à fait les 100 ms que vous vouliez - parce que vous cherchez plus de 100 ms qui se sont écoulées - et cela signifie 101 ms ou plus. Au lieu de cela, vous devez utiliser >=:

uint32_t time = millis();

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

Maintenant, en supposant que rien d'autre ne se passe dans votre boucle, vous obtenez des déclenchements à 100 ms, 200 ms, 300 ms, etc. Mais notez ce bit: "tant qu'il ne se passe rien d'autre dans votre boucle" ...

Que se passe-t-il si une opération de 5 ms se produit à 99 ms ...? Votre prochain déclenchement sera retardé jusqu'à 104 ms. C'est une dérive. Mais c'est facile à combattre. Au lieu de dire "l'heure enregistrée est maintenant", vous dites "l'heure enregistrée est 100 ms plus tard qu'elle ne l'était". Cela signifie que peu importe les retards que vous obtenez dans votre code, votre déclenchement sera toujours à des intervalles de 100 ms ou dérivera dans un intervalle de 100 ms.

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

Vous obtiendrez maintenant des déclenchements à 100 ms, 200 ms, 300 ms, etc. Ou s'il y a des retards dans d'autres bits de code, vous pouvez obtenir 100 ms, 204 ms, 300 ms, 408 ms, 503 ms, 600 ms, etc. Il essaie toujours de l'exécuter aussi près que l'intervalle que possible indépendamment des retards. Et si vous avez des retards supérieurs à l'intervalle, il exécutera automatiquement votre routine suffisamment de fois pour rattraper l'heure actuelle.

Avant de dériver . Maintenant, vous avez de la gigue .

Majenko
la source
1

Parce que vous réinitialisez la minuterie après l'opération.

static unsigned long PrevTime=millis();

unsigned long t = millis();

if (t - PrevTime > time) {
    foo();
    PrevTime = t;
}
Juraj
la source
non. notez que PrevTime est statique.
dannyf
4
@dannyf, oui, et?
Juraj
si vous saviez ce que signifie "statique", vous sauriez pourquoi votre réponse n'est pas correcte.
dannyf
Je sais ce que la statique fait avec la variable locale .. Je ne comprends pas pourquoi vous pensez que ma réponse a quelque chose à voir avec la statique. Je viens de déplacer la lecture actuelle en millis avant que foo () soit invoqué.
Juraj
-1

Pour ce que vous essayez de faire, delay () est le moyen approprié pour implémenter le code. La raison pour laquelle vous souhaitez utiliser le if (millis ()) est si vous voulez permettre à la boucle principale de continuer à boucler afin que votre code ou un autre code en dehors de cette boucle puisse effectuer un autre traitement.

Par exemple:

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();
   }
}

Cela exécuterait foo () sur l'intervalle spécifié tout en permettant à la boucle de continuer à s'exécuter entre les deux. J'ai mis le calcul de next_trigger_time avant l'appel à foo () pour aider à minimiser la dérive, mais c'est inévitable. Si la dérive est un problème important, utilisez une minuterie d'interruption ou une sorte de synchronisation horloge / minuterie. Souvenez-vous également que millis () se terminera après une certaine période de temps et je n'en ai pas tenu compte pour garder l'exemple de code simple.

ThatAintWorking
la source
Je déteste le mentionner: problème de retournement dans 52 jours.
J'ai déjà mentionné le problème de roulement à la fin de ma réponse.
ThatAintWorking
Eh bien, résolvez-le.
Mes frais de consultation standard de 100 $ / heure si vous voulez que j'écrive du code pour vous. Je pense que ce que j'ai écrit est suffisamment pertinent.
ThatAintWorking
1
Savez-vous que Majenko a publié une réponse plus complète et meilleure que la vôtre? Savez-vous que votre code ne se compile pas? Cela long m - millis()ne fait pas ce que vous avez l'intention de faire? C'est sur la maison.
-4

votre code est correct.

le problème que vous rencontrez est avec millis (): il sous-comptera légèrement (le sous-compte maximum est juste timide de 1 ms, par appel).

la solution est avec des tiques plus fines, comme micros () - mais qui sous-compteront également légèrement.

dannyf
la source
2
Veuillez fournir des éléments de preuve ou des références pour « cela sous-estimera légèrement ».
Edgar Bonet