tiempo de retardo); vs if (millis () - anterior> tiempo); y deriva


8

Al pasar por un proyecto antiguo, tenía un código en dos Arduino Due que se veía así

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

teniendo en cuenta la mayoría de la literatura sobre el uso delay();que recodifiqué esto como

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

Sin embargo, esto parece haber creado una situación en la que los dos dispositivos derivan durante un período de tiempo en el que no

Mi pregunta es doble:

  1. ¿Por qué if(millis()-PrevTime>time)causaría más deriva que delay(time)?
  2. ¿Hay alguna manera de evitar esta deriva sin volver a delay(time)?

1
¿Cuál es el orden de magnitud del "período de tiempo" durante el cual notas la deriva? ¿Están los dos dispositivos en la misma ubicación, por lo tanto, a la misma temperatura? ¿Funcionan con un oscilador de cristal o un resonador de cerámica?
jose can uc

Personalmente, prefiero la solución de Majenko, y siempre la uso (pongo el incremento antes que las otras instrucciones, pero esto es solo una preferencia). Sin embargo, tenga en cuenta que este tiempo es PRECISAMENTE 100 ms, mientras que el otro código ( foo; delay;) tiene un período superior a 100 ms (es 100 ms + tiempo de foo). Entonces experimentarás deriva (pero es el delaySW implementado el que está derivando). En cualquier caso, tenga en cuenta que incluso las implementaciones perfectamente iguales "derivan", porque los relojes no son iguales; si necesita una sincronización completa, use una señal para, bueno, sincronizar los dos programas.
frarugi87

Los dos dispositivos están uno al lado del otro, después de funcionar desde el viernes a las 17:00 hasta el lunes a las 9:00 hubo una deriva de 4 minutos. Estoy decidiendo que voy a usar un pin digital para sincronizar las entradas según su sugerencia
ATE-ENGE

"Los dos dispositivos están uno al lado del otro ...", eso no significa que el mecanismo de sincronización no sea preciso, está hablando de una tasa de error de ~ 800ppm, alta para dos osciladores de cristal pero razonable incluso para un resonador de cerámica. tienes que compararlo con un estándar de tiempo razonablemente preciso para estar seguro: los cristales están típicamente dentro de 20ppm, y los tcxo pueden hacer menos de 1ppm. esa sería mi forma de hacerlo.
dannyf

Respuestas:


10

Hay una cosa importante que debe recordar al trabajar con tiempo en un Arudino de cualquier forma:

  • Cada operación lleva tiempo.

Su función foo () tomará una cantidad de tiempo. Cuál es ese momento, no podemos decir.

La forma más confiable de lidiar con el tiempo es confiar solo en el tiempo de activación, no en determinar cuándo debería ser la próxima activación.

Por ejemplo, tome lo siguiente:

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

La variable lastserá el tiempo que se activó la rutina * más el tiempo que doSomethingtardó en ejecutarse. Digamos que intervales 100, y doSomethingtarda 10 ms en ejecutarse, obtendrá disparos a 101 ms, 212 ms, 323 ms, etc. No los 100 ms que esperaba.

Entonces, una cosa que puede hacer es usar siempre el mismo tiempo, independientemente de recordarlo en un momento específico (como sugiere Juraj):

uint32_t time = millis();

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

Ahora el tiempo que doSomething()lleva no tendrá efecto en nada. Por lo tanto, obtendrá disparos a 101 ms, 202 ms, 303 ms, etc. Aún no alcanza los 100 ms que deseaba, porque está buscando que hayan pasado más de 100 ms, y eso significa 101 ms o más. En su lugar, debe usar >=:

uint32_t time = millis();

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

Ahora, suponiendo que no suceda nada más en su bucle, obtiene disparos a 100 ms, 200 ms, 300 ms, etc. Pero tenga en cuenta ese bit: "mientras no suceda nada más en su bucle" ...

¿Qué sucede si una operación que dura 5 ms ocurre a 99 ms ...? Su próximo disparo se retrasará hasta 104 ms. Eso es una deriva. Pero es fácil de combatir. En lugar de decir "El tiempo grabado es ahora", usted dice "El tiempo grabado es 100 ms más tarde de lo que era". Eso significa que, independientemente de los retrasos que obtenga en su código, su activación siempre será a intervalos de 100 ms, o se desplazará dentro de un tic de 100 ms.

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

Ahora obtendrá disparos a 100 ms, 200 ms, 300 ms, etc. O si hay retrasos en otros bits de código, puede obtener 100 ms, 204 ms, 300 ms, 408 ms, 503 ms, 600 ms, etc. Siempre intenta ejecutarlo lo más cerca posible el intervalo posible, independientemente de los retrasos. Y si tiene retrasos que son mayores que el intervalo, ejecutará automáticamente su rutina suficientes veces para ponerse al día con la hora actual.

Antes de que tuvieras deriva . Ahora tienes nerviosismo .


1

Porque reinicia el temporizador después de la operación.

static unsigned long PrevTime=millis();

unsigned long t = millis();

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

No. observe que PrevTime es estático.
dannyf

44
@dannyf, sí, y?
Juraj

Si supiera lo que significa "estático", sabría por qué su respuesta no es correcta.
dannyf

Sé lo que hace la estática con la variable local. No entiendo por qué crees que mi respuesta tiene algo que ver con la estática. Acabo de mover la lectura actual de millis antes de que se invoque foo ().
Juraj

-1

Para lo que está intentando hacer, delay () es la forma adecuada de implementar el código. La razón por la que desearía usar if (millis ()) es si desea permitir que el bucle principal continúe en bucle para que su código o algún otro código fuera de ese bucle pueda realizar otro procesamiento.

Por ejemplo:

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

Esto ejecutaría foo () en el intervalo especificado mientras permite que el ciclo continúe ejecutándose en el medio. Puse el cálculo de next_trigger_time antes de la llamada a foo () para ayudar a minimizar la deriva, pero es inevitable. Si la deriva es una preocupación importante, use un temporizador de interrupción o algún tipo de sincronización reloj / temporizador. También recuerde que millis () se ajustará después de un período de tiempo y no lo tomé en cuenta para mantener el ejemplo de código simple.


Odio mencionar esto: problema de reinversión en 52 días.

Ya mencioné el problema de reinversión al final de mi respuesta.
ThatAintWorking

Bueno, resuélvelo.

Mi tarifa de consultoría estándar es de $ 100 / hora si desea que le escriba un código. Creo que lo que he escrito es lo suficientemente relevante.
ThatAintWorking

1
¿Sabías que Majenko publicó una respuesta más completa y mejor que la tuya? ¿Eres consciente de que tu código no se compila? ¿Eso long m - millis()no hace lo que pretendes hacer? Eso está en la casa.

-4

Su código es correcto.

el problema con el que se encuentra es con millis (): tendrá un recuento insuficiente levemente (el recuento insuficiente máximo es apenas de 1 ms, por invocación).

la solución es con garrapatas más finas, como micros (), pero eso también contará un poco.


2
Por favor, proporcione alguna evidencia o alguna referencia para " contará ligeramente por debajo ".
Edgar Bonet
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.