¿Puedo hacer que delayMicroseconds sea más preciso?

8

Estoy tratando de explotar los datos DMX y eso requiere 4 pulsos. No tengo mucha suerte con los resultados que estoy comprobando para ver qué tan bueno es el Arduino para retrasar ... Parece ser bastante terrible en eso.

Aquí hay una pequeña prueba rápida que hice:

unsigned long ptime;

void setup() {
  Serial.begin(9600);
}

void loop() {
  ptime = micros();
  delayMicroseconds(4);
  Serial.println(micros() - ptime);

  delay(1000); //just to keep the serial monitor from going nuts
}

Y los resultados:

8 4 8 4 4 4 4 4 8 8 8 8 4 8 8 4 4 8 4 4 4 8 8 8 4 8 4

Me sorprendió lo mala que es su precisión. ¡Es el doble del tiempo que quería retrasar, pero ni siquiera es consistente con donde podría dividir por 2!

¿Hay algo que pueda hacer para obtener resultados correctos y consistentes?

bwoogie
fuente
¿Por qué estás golpeando bit en lugar de acceder directamente al UART?
Ignacio Vazquez-Abrams
Entonces puedo tener más de una salida.
bwoogie
El Mega tiene cuatro UART. Incluso si tiene uno de forma permanente para la programación, todavía tiene tres universos.
Ignacio Vazquez-Abrams
Solo estoy probando con el mega porque eso es lo que tengo actualmente disponible, el proyecto final tendrá un ATMEGA328
bwoogie
1
El problema aquí es cómo mides.
Gerben

Respuestas:

7

Como se explicó en las respuestas anteriores, su problema real no es la precisión delayMicroseconds(), sino la resolución de micros().

Sin embargo, para responder a su pregunta real , hay una alternativa más precisa para delayMicroseconds(): la función _delay_us()del AVR-libc es precisa para el ciclo y, por ejemplo

_delay_us(1.125);

hace exactamente lo que dice. La advertencia principal es que el argumento tiene que ser una constante de tiempo de compilación. Debe hacerlo #include <util/delay.h>para tener acceso a esta función.

Tenga en cuenta también que debe bloquear las interrupciones si desea algún tipo de demora precisa.

Editar : Como ejemplo, si tuviera que generar un pulso de 4 µs en PD2 (pin 19 en el Mega), procedería de la siguiente manera. Primero, observe que el siguiente código

noInterrupts();
PORTD |= _BV(PD2);   // PD2 HIGH
PORTD &= ~_BV(PD2);  // PD2 LOW
interrupts();

hace un pulso de 0.125 µs de largo (2 ciclos de CPU), porque ese es el tiempo que lleva ejecutar la instrucción que establece el puerto LOW. Luego, solo agregue el tiempo faltante en un retraso:

noInterrupts();
PORTD |= _BV(PD2);   // PD2 HIGH
_delay_us(3.875);
PORTD &= ~_BV(PD2);  // PD2 LOW
interrupts();

y tiene un ancho de pulso con precisión de ciclo. Vale la pena señalar que esto no se puede lograr digitalWrite(), ya que una llamada a esta función tarda unos 5 µs.

Edgar Bonet
fuente
3

Los resultados de su prueba son engañosos. delayMicroseconds()en realidad se retrasa con bastante precisión (para retrasos de más de 2 o 3 microsegundos). Puede examinar su código fuente en el archivo /usr/share/arduino/hardware/arduino/cores/arduino/wiring.c (en un sistema Linux; o en alguna ruta similar en otros sistemas).

Sin embargo, la resolución de micros()es de cuatro microsegundos. (Ver, por ejemplo, la página de garretlab sobremicros() ). Por lo tanto, nunca verá una lectura entre 4 microsegundos y 8 microsegundos. El retraso real puede ser de unos pocos ciclos durante 4 microsegundos, pero su código lo informará como 8.

Intente hacer 10 o 20 delayMicroseconds(4);llamadas seguidas (duplicando el código, no utilizando un bucle) y luego informe el resultado de micros().

James Waldby - jwpat7
fuente
Con 10 retrasos de 4
obtengo
¿Qué obtienes con, digamos, 10 demoras de 5? :) También tenga en cuenta que, para bitbanging, es posible que necesite usar accesos de puerto bastante directos, es decir, no digitalWrite(), lo que requiere varios microsegundos para ejecutarse.
James Waldby - jwpat7
40 y 44 ... ¿Pero no debería ser un redondeo? ¿Que me estoy perdiendo aqui?
bwoogie
Por "redondeo" quieres decir delayMicroseconds()? No veo eso como algo mejor que redondear. ¶ Con respecto a la fuente de inexactitud, si la rutina se alinea, el tiempo depende del código circundante. Puede leer las listas de ensamblaje o desensamblaje para ver. (Consulte la sección "Creación de listas de ensamblaje" en mi respuesta a la pregunta Equivalente para PORTB en Arduino Mega 2560 , que de todos modos puede ser relevante para su proyecto de bitbanging
James Waldby - jwpat7
2

Estoy comprobando para ver qué tan bueno es el Arduino para retrasar ... Parece ser bastante terrible en eso.

micros()tiene una resolución bien documentada de 4 µs.

Puede mejorar la resolución cambiando el preescalador para el Temporizador 0 (por supuesto, eso arroja las cifras, pero puede compensar eso).

Alternativamente, use el Temporizador 1 o el Temporizador 2 con un preescalador de 1, que le brinda una resolución de 62.5 ns.


 Serial.begin(9600);

Eso va a ser lento de todos modos.


8 4 8 4 4 4 4 4 8 8 8 8 4 8 8 4 4 8 4 4 4 8 8 8 4 8 4

Su salida es exactamente consistente con la resolución de 4 µs micros()junto con el hecho de que a veces obtendría dos "ticks" y otras una, dependiendo exactamente de cuándo comenzó la sincronización.


Su código es un ejemplo interesante de error de medición. delayMicroseconds(4);se retrasará por cerca de 4 µs. Sin embargo, sus intentos de medirlo tienen la culpa.

Además, si se produce una interrupción, alargará un poco el intervalo. Debe desactivar las interrupciones si desea un retraso exacto.

Nick Gammon
fuente
1

Cuando se mide con un osciloscopio, descubrí que:

delayMicroseconds(0)= delayMicroseconds(1)= 4 μs de retraso real.

Entonces, si desea un retraso de 35 μs, necesita:

delayMicroseconds(31);
sigi
fuente
0

¿Hay algo que pueda hacer para obtener resultados correctos y consistentes?

La implementación de Arduino es bastante genérica, por lo que puede no ser tan efectiva en algunas aplicaciones. Hay algunas formas de demoras cortas, cada una con sus propias deficiencias.

  1. Use nop. Cada una es una instrucción, así que somos 16 de nosotros.

  2. Use tcnt0 directamente. Cada uno es 4us, ya que el preescalador está configurado en 64. Puede cambiar el preescalador para lograr la resolución 16a.

  3. Use ticks, puede implementar un clon de systick y usarlo como base del retraso. Ofrece una resolución más fina más precisión.

editar:

Usé el siguiente bloque para cronometrar los diversos enfoques:

time0=TCNT0;
delay4us();             //65
//t0delayus(4*16);          //77
//_delay_us(4);             //65
//delayMicroseconds(4);     //45
time1=TCNT0 - time0;        //64 expected

antes de eso, había restablecido el preescalador timer0 a 1: 1, por lo que cada tick TCNT0 es 1/16 de microsegundo.

  1. delay4us () se crea a partir de NOP (); produjo un retraso de 65 ticks, o un poco más de 4us;
  2. t0delayus () se genera a partir de los retrasos del temporizador0. produjo un retraso de 77 garrapatas; un poco peor que delay4us ()
  3. _delay_us () es una función gcc-avr. rendimiento a la par con delay4us ();
  4. delayMicroseconds () produjo un retraso de 45 ticks. Por la forma en que Arduino implementó sus funciones de temporización, tiende a contar menos, a menos que en un entorno con otras interrupciones.

Espero eso ayude.

dannyf
fuente
Tenga en cuenta que el resultado esperado es 65 ticks, no 64, porque la lectura TCNT0toma 1 ciclo de CPU.
Edgar Bonet