ritardo); vs if (millis () - precedente> tempo); e deriva


8

Passando attraverso un vecchio progetto, avevo un codice su due Arduino Due che assomigliava a questo

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

prendendo a cuore la maggior parte della letteratura sull'uso delay();ho ricodificato questo come

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

Tuttavia, ciò sembra aver creato una situazione in cui i due dispositivi si spostano per un periodo di tempo in cui non lo erano in precedenza

La mia domanda è duplice:

  1. Perché dovrebbe if(millis()-PrevTime>time)causare più deriva di delay(time)?
  2. C'è un modo per prevenire questa deriva senza tornare a delay(time)?

1
Qual è l'ordine di grandezza del "periodo di tempo" durante il quale noti la deriva? I due dispositivi si trovano nella stessa posizione, quindi alla stessa temperatura? Funzionano su un oscillatore a cristallo o un risuonatore ceramico?
jose can uc

Personalmente preferisco la soluzione di Majenko, e la uso sempre (ho messo l'incremento prima delle altre istruzioni, ma questa è solo una preferenza). Si noti, tuttavia, che questo tempismo è PRECISAMENTE 100ms, mentre l'altro codice ( foo; delay;) ha un periodo più lungo di 100ms (è 100ms + tempo di foo). Quindi sperimenterai la deriva (ma è il delaySW implementato che sta andando alla deriva). In ogni caso, tieni presente che anche le implementazioni perfettamente uguali "vanno alla deriva", perché gli orologi non sono uguali; se hai bisogno di una sincronizzazione completa usa un segnale per, bene, sincronizzare i due programmi.
frarugi87,

I due dispositivi sono uno accanto all'altro, dopo essere passati da venerdì alle 17:00 a lunedì alle 9:00, c'è stata una deriva di 4 minuti. Sto decidendo che userò un pin digitale per sincronizzare gli ingressi secondo il tuo suggerimento
ATE-ENGE

"I due dispositivi sono uno accanto all'altro, ..." ciò non significa che il meccanismo di temporizzazione non sia accurato, stai parlando di un tasso di errore di ~ 800 ppm, alto per due oscillatori a cristallo ma ragionevole anche per un risonatore ceramico. devi confrontarlo con uno standard di tempismo ragionevolmente accurato per essere sicuro: i cristalli sono in genere entro 20ppm e tcxo può fare sub 1ppm. quello sarebbe il mio modo di farlo.
Dannyf,

Risposte:


10

C'è una cosa importante che devi ricordare quando lavori con il tempo su un Arudino di qualsiasi forma:

  • Ogni operazione richiede tempo.

La tua funzione foo () richiederà un certo tempo. Che cosa sia quel tempo, non possiamo dire.

Il modo più affidabile per gestire il tempo è fare affidamento solo sul tempo per l'attivazione, non per allenarsi quando dovrebbe essere l'attivazione successiva.

Ad esempio, prendi quanto segue:

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

La variabile lastsarà il tempo di attivazione della routine * più il tempo doSomethingimpiegato per l'esecuzione. Quindi diciamo interval100, e doSomethingimpiega 10ms per funzionare, otterrai trigger a 101ms, 212ms, 323ms, ecc. Non i 100ms che ti aspettavi.

Quindi una cosa che puoi fare è usare sempre lo stesso tempo a prescindere ricordandolo in un punto specifico (come suggerisce Juraj):

uint32_t time = millis();

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

Ora il tempo doSomething()impiegato non avrà alcun effetto su nulla. Quindi otterrai trigger a 101ms, 202ms, 303ms, ecc. Ancora non esattamente i 100ms che volevi - perché stai cercando che siano passati più di 100ms - e questo significa 101ms o più. Invece dovresti usare >=:

uint32_t time = millis();

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

Ora, supponendo che non succeda nient'altro nel tuo loop ottieni trigger a 100ms, 200ms, 300ms, ecc. Ma nota quel bit: "fintanto che non succede nient'altro nel tuo loop" ...

Cosa succede se si verifica un'operazione che richiede 5ms a 99ms ...? Il prossimo trigger verrà ritardato fino a 104 ms. Questa è una deriva. Ma è facile combattere. Invece di dire "Il tempo registrato è ora" dici "Il tempo registrato è 100ms più tardi di quello che era". Ciò significa che, indipendentemente dai ritardi che si ottengono nel codice, l'attivazione sarà sempre a intervalli di 100 ms o alla deriva entro un segno di spunta di 100 ms.

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

Ora otterrai trigger a 100ms, 200ms, 300ms, ecc. O se ci sono ritardi in altri bit di codice potresti ottenere 100ms, 204ms, 300ms, 408ms, 503ms, 600ms, ecc. Cerca sempre di eseguirlo il più vicino possibile l'intervallo possibile indipendentemente dai ritardi. E se hai ritardi maggiori dell'intervallo, eseguirà automaticamente la tua routine abbastanza volte da tenere il passo con l'ora corrente.

Prima che tu fossi andato alla deriva . Ora hai jitter .


1

Perché hai reimpostato il timer dopo l'operazione.

static unsigned long PrevTime=millis();

unsigned long t = millis();

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

no. notare che PrevTime è statico.
Dannyf,

4
@dannyf, sì e?
Juraj,

se sapessi cosa significa "statico", sapresti perché la tua risposta non è corretta.
Dannyf,

So cosa fa statica con la variabile locale .. Non capisco perché pensi che la mia risposta abbia qualcosa a che fare con la statica. Ho appena spostato la lettura corrente di millis prima di invocare foo ().
Juraj,

-1

Per quello che stai cercando di fare, delay () è il modo appropriato per implementare il codice. Il motivo per cui si desidera utilizzare if (millis ()) è se si desidera consentire al ciclo principale di continuare a eseguire il ciclo in modo che il codice o qualche altro codice al di fuori di quel ciclo possano eseguire altre elaborazioni.

Per esempio:

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

In questo modo verrebbe eseguito foo () sull'intervallo specificato, pur consentendo al loop di continuare a funzionare tra. Ho messo il calcolo di next_trigger_time prima della chiamata a foo () per aiutare a minimizzare la deriva, ma è inevitabile. Se la deriva è un problema significativo, utilizzare un timer di interruzione o una sorta di sincronizzazione orologio / timer. Ricorda anche che millis () andrà a finire dopo un certo periodo di tempo e non ho tenuto conto di ciò per mantenere semplice l'esempio di codice.


Odio menzionarlo: problema di rollover in 52 giorni.

Ho già menzionato il problema del rollover alla fine della mia risposta.
ThatAintWorking il

Bene, risolvilo.

La mia commissione di consulenza standard $ 100 / ora se vuoi che scriva il codice per te. Penso che ciò che ho scritto sia sufficientemente pertinente.
ThatAintWorking il

1
Sai che Majenko ha pubblicato una risposta più completa e migliore della tua? Sei a conoscenza del fatto che il tuo codice non viene compilato? Non long m - millis()fa quello che hai intenzione di fare? Quello è sulla casa.

-4

il tuo codice è corretto

il problema che si incontra è con millis (): verrà leggermente sottostimato (il sottotit massimo è solo di 1ms, per invocazione).

la soluzione è con segni di spunta più fini, come micros () - ma anche questo sarà leggermente sottostimato.


2
Per favore, fornisci alcune prove o qualche riferimento per " sottostimerà leggermente ".
Edgar Bonet,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.