Codificación de temporizadores y retrasos en Arduino

Actualización del 5 de septiembre de 2019: eliminar las llamadas de delay () es el primer paso para lograr tareas múltiples en cualquier placa Arduino. El multitarea simple instructable en Arduino en cualquier tablero cubre todos los demás pasos necesarios.

Actualización del 5 de mayo de 2019: Renombrado isFinished () a justFinished (), ya que solo devuelve TRUE una vez justo después de que finalice el retraso. Ejemplo de retraso de congelación / pausa agregado

Introducción

No use el retraso ()

El uso de delay () hace que su sistema se atasque mientras espera que expire el retraso. Sin embargo, reemplazar las demoras requiere un poco de cuidado. Esta página explica paso a paso cómo reemplazar Arduino delay () con una versión sin bloqueo que le permite al código continuar ejecutándose mientras espera que se agote el tiempo de espera.

Aquí hay una serie de bocetos simples, cada uno de los cuales enciende un LED cuando la placa Arduino se enciende (o reinicia) y luego 10 segundos más tarde la apaga. El primero es un ejemplo de cómo NO debe escribir el código. El segundo es un ejemplo de código que funciona y el tercero es un ejemplo del uso de la biblioteca millisDelay para simplificar el código. También hay ejemplos de temporizadores de disparo único y repetidores.

Si ya comprende por qué no debe usar delay () y está familiarizado con Arduino, la importancia de usar longs sin signo, desbordamiento y sustracción sin signo, puede pasar a Usar la biblioteca millisDelay (Paso 4)

La biblioteca millisDelay proporciona retrasos y temporizadores de funcionalidad, es simple de usar y fácil de entender para los nuevos en Arduino.

Este instructable también está en línea en Cómo codificar temporizadores y retrasos en Arduino

Paso 1: Cómo no codificar un retraso en Arduino

Aquí es cómo NO codificar un retraso en un boceto.

 int led = 13; 
unsigned long delayStart = 0; // la hora en que comenzó el retraso bool delayRunning = false; // verdadero si todavía está esperando que termine el retraso
configuración nula () {pinMode (led, OUTPUT); // inicializa el pin digital como salida. digitalWrite (led, ALTO); // enciende el led delayStart = millis (); // start delay delayRunning = true; // no ha terminado aún }
void loop () {// comprueba si el retraso ha expirado después de 10 segundos == 10000 ms si (delayRunning && ((millis () - delayStart)> = 10000)) {delayRunning = false; // // evita que este código se ejecute más de una vez digitalWrite (led, LOW); // apaga el led Serial.println ("LED apagado"); } // Otro código de bucle aquí. . . Serial.println ("Ejecutar otro código"); }

En el método setup (), que Arduino llama una vez al iniciarse, el led se enciende. Una vez que finaliza la configuración (), Arduino llama al método loop () una y otra vez. Aquí es donde va la mayoría de su código, leyendo los sensores que envían la salida, etc. En el bosquejo anterior, se llama al primer bucle de tiempo (), el retraso (10000) detiene todo durante 10 segundos antes de apagar el LED y continuar. Si ejecuta este código, verá que Ejecutar otro código no se imprime durante 10 segundos después del inicio, pero después de que el led se apaga (ledOn es igual a falso), se imprime muy rápido a medida que se llama al loop () una y otra vez de nuevo.

El punto a tener en cuenta aquí es que realmente no debería la función delay () en absoluto en el código loop (). A veces es conveniente usar delay () en el código setup () y, a menudo, puede salirse con la suya usando retrasos muy pequeños de unos pocos milisegundos en el código loop (), pero realmente debería evitar usar el en absoluto en el método loop ()

Paso 2: Cómo escribir un retraso sin bloqueo en Arduino

El boceto anterior utilizaba un retraso de bloqueo, es decir, uno que impedía por completo que el código hiciera cualquier otra cosa mientras el retraso esperaba expirar. El siguiente boceto le muestra cómo escribir un retraso sin bloqueo que permita que el código continúe ejecutándose mientras espera que expire el retraso.

 int led = 13; 
unsigned long delayStart = 0; // la hora en que comenzó el retraso bool delayRunning = false; // verdadero si todavía está esperando el retraso para finalizar la configuración nula () {pinMode (led, OUTPUT); // inicializa el pin digital como salida. digitalWrite (led, ALTO); // enciende el led delayStart = millis (); // start delay delayRunning = true; // aún no terminado} void loop () {// comprueba si el retraso ha expirado después de 10 segundos == 10000mS if (delayRunning && ((millis () - delayStart)> = 10000)) {delayRunning = false; // // evita que este código se ejecute más de una vez digitalWrite (led, LOW); // apaga el led Serial.println ("LED apagado"); } // Otro código de bucle aquí. . . Serial.println ("Ejecutar otro código"); }

En el boceto anterior, en el método setup (), la variable delayStart se establece en el valor actual de millis () .

millis () es un método incorporado que devuelve el número de milisegundos desde que se encendió la placa. Comienza como 0 cada vez que se restablece la placa y se incrementa cada milisegundo por un contador de hardware de la CPU. Más sobre millis () más adelante. Una vez que finaliza la configuración (), Arduino llama al método loop () una y otra vez.

Cada bucle de tiempo () se llama verificaciones de código
a) que el retraso aún se está ejecutando, y
b) si el millis () se ha movido en 10000 mS (10 segundos) desde el valor almacenado en delayStart .

Cuando el tiempo avanza por 10000mS o más, entonces delayRunning se establece en falso para evitar que el código en la instrucción if se ejecute nuevamente y el led se apague.

Si ejecuta este boceto, verá Ejecutar otro código impreso muy rápidamente y después de 10 segundos, el LED se apagará y, si es rápido, es posible que vea el mensaje LED apagado antes de que se desplace de la pantalla.

Consulte el Paso 4 a continuación para ver cómo la biblioteca millisDelay simplifica este código

Paso 3: Largo sin signo, Desbordamiento y Resta sin signo

Si está familiarizado con longs sin signo, desbordamiento, aritmética sin signo y la importancia de usar una variable larga sin signo, puede pasar al Paso 4 con la biblioteca millisDelay.

La parte importante del boceto anterior es la prueba.

 (millis () - delayStart)> = 10000 

Esta prueba debe codificarse de esta manera muy específica para que funcione.

Largo sin signo y desbordamiento

La variable delayStart y el número devuelto por la función incorporada millis () es un largo sin signo . Es un número desde 0 hasta 4, 294, 967, 295.

Si agrega 1 a un valor largo sin signo con el valor máximo de 4, 294, 967, 295, la respuesta será 0 (cero). Ese es el número desbordado y envuelto de nuevo a 0. Se puede imaginar que el bit de desbordamiento simplemente se cae. por ejemplo, en un 3-bit sin signo, 111 es el valor máximo (7), sumando 1 da 1000 (8), pero el 1 inicial desborda el almacenamiento de 3 bits y se cae, volviendo a 000.

Esto significa que, eventualmente, cuando la CPU agregue una variable más que contenga el resultado millis (), se ajustará a 0. Es decir, millis () comenzará a contar desde 0 nuevamente. Esto sucederá si deja su placa Arduino funcionando durante 4, 294, 967, 295 mS, es decir, aproximadamente 49 días 17 horas, digamos 50 días.

Ahora consideremos otra forma de codificar la prueba (millis () - delayStart)> = 10000

Aritméticamente esta prueba es igual a millis ()> = (delayStart + 10000)

Sin embargo, si comienza el retraso después de casi 50 días, por ejemplo, cuando millis () devuelve 4.294.966.300 mS, delayStart + 10000 se desbordará a 995 y la prueba, millis ()> = (delayStart + 10000), será verdadera de inmediato No habrá demora en absoluto. Entonces, esta forma de prueba no siempre funciona.

Desafortunadamente, es poco probable que se encuentre con esto durante las pruebas, pero puede surgir inesperadamente en un dispositivo de funcionamiento prolongado, como una puerta de garaje que controla el funcionamiento continuamente durante meses. Obtendrá un problema similar si intenta usar
delayEnd = millis () + 10000
y luego la prueba (millis ()> = delayEnd)

Finalmente, la variable delayStart debe ser larga sin signo . Si, por el contrario, utiliza un largo (es decir, int largo ) o int o unsigned int, el valor máximo que pueden contener es menor que el largo sin signo devuelto por millis () . Finalmente, el valor recuperado de millis () desbordará la variable más pequeña en la que se está almacenando y verá que el tiempo ha retrocedido repentinamente. Por ejemplo, si usa un int sin firmar para startDelay, esto sucederá después de 65 segundos en una placa Uno.

Resta sin firmar

Otro punto de interés es lo que sucede como resultado de millis () - delayStart cuando delayStart es, por ejemplo, 4, 294, 966, 300 y queremos un retraso de 10000mS.

millis () se ajustará a 0 antes de que eso suceda. Recuerde que agregar 1 al valor máximo de un largo sin signo puede almacenar las envolturas alrededor de 0. Entonces, una forma de ver el cálculo de millis () - delayStart, donde millis () se ha ajustado y es más pequeño que delayStart, es decir "W ¿Qué número debo agregar a delayStart para que sea igual a millis () (después del desbordamiento)? ", es decir, ¿qué es X en la ecuación delayStart + X == millis ()

Por ejemplo, usando una variable sin signo de 3 bits nuevamente, para calcular 2 - 4 (sin signo), piense en una cara de reloj que comience en 0 y agregue 1 hasta 111 (7) y luego vuelva a 0. Ahora para pasar de 4 a 2 necesita agregar 6 (5, 6, 7, 0, 1, 2) para que 2-4 = 6 y así es como funciona el cálculo, aunque la CPU realizará el cálculo de manera diferente.

Entonces, la diferencia de dos largos sin signo siempre será un número positivo en el rango de 0 a 4, 294, 967, 295. Por ejemplo, si startDelay es 1 y millis () se ha ajustado a 0 (después de 50 días), millis () - startDelay será igual a 4, 294, 967, 295. Esto significa que puede especificar un DELAY_TIME en cualquier lugar dentro del rango de 0 a 4, 294, 967, 295 mS y (millis () - delayStart)> = DELAY_TIME siempre funcionará como se esperaba, independientemente de cuándo se inicie el retraso.

Paso 4: uso de la biblioteca MillisDelay

Para instalar la biblioteca millisDelay. Descargado el archivo millisDelay.zip.

Descomprima este archivo en su directorio Arduino / bibliotecas (abra la ventana de preferencias IDE File-> para ver dónde está su directorio local Arduino). Algunas veces las instrucciones de Cómo instalar una biblioteca: trabajo de instalación automática, pero no siempre. Descomprimir el archivo manualmente es lo más seguro. Una vez que instale la biblioteca millisDelay, habrá tres ejemplos interactivos disponibles que puede cargar en su placa Arduino y luego abrir el Monitor de serie (dentro de 5 segundos) a 9600 baudios para usarlos. Aquí está el boceto de demora sin bloqueo anterior reescrito usando la biblioteca millisDelay.

 #include "millisDelay.h" int led = 13; millisDelay ledDelay; configuración nula () {pinMode (led, OUTPUT); // inicializa el pin digital como salida. digitalWrite (led, ALTO); // enciende el led ledDelay.start (10000); // iniciar un retraso de 10 segundos} void loop () {// comprobar si el retraso ha excedido el tiempo de espera si (ledDelay.justFinished ()) {digitalWrite (led, LOW); // apaga el led Serial.println ("LED apagado"); } // Otro código de bucle aquí. . . Serial.println ("Ejecutar otro código"); } 

Si observa el código de la biblioteca millisDelay, verá que el código del boceto anterior acaba de moverse a los métodos start () y Just Finished () en la biblioteca.

¿Es esto un ledDelay o un ledTimer? Puedes usar el término que quieras. Tiendo a usar ... retraso para retrasos de un solo disparo que se ejecutan una vez y uso ... temporizador para repetir.

Paso 5: Ejemplos de retraso y temporizador

Aquí hay dos bocetos básicos de retraso y temporizador y sus equivalentes de biblioteca millisDelay. Estos ejemplos son para un retraso único (disparo único) y un retraso / temporizador de repetición.

Retardo de un solo disparo

Un retraso de disparo único es uno que solo se ejecuta una vez y luego se detiene. Es el reemplazo más directo para el método Arduino delay () . Empiezas el retraso y luego, cuando termina, haces algo. BasicSingleShotDelay es el código simple y SingleShotMillisDelay utiliza la biblioteca millisDelay.

BasicSingleShotDelay

Este boceto está disponible en BasicSingleShotDelay.ino

 int led = 13; // El pin 13 tiene un LED conectado en la mayoría de las placas Arduino. 
unsigned long DELAY_TIME = 10000; // 10 segundos sin signo, delayStart = 0; // la hora en que comenzó el retraso bool delayRunning = false; // verdadero si todavía está esperando el retraso para finalizar la configuración nula () {pinMode (led, OUTPUT); // inicializa el pin digital como salida. digitalWrite (led, ALTO); // enciende el led // start delay delayStart = millis (); delayRunning = true; } void loop () {// comprueba si el retraso ha excedido el tiempo de espera si (delayRunning && ((millis () - delayStart)> = DELAY_TIME)) {delayRunning = false; // retraso finalizado: disparo único, una vez solo digitalWrite (led, LOW); // apaga el led}}

En el código anterior, el bucle () continúa ejecutándose sin atascarse esperando que expire el retraso.
Durante cada pasada del bucle (), la diferencia entre el actual millis () y el tiempo de delayStart se compara con el DELAY_TIME . Cuando el temporizador excede el valor del intervalo, se toma la acción deseada. En este ejemplo, el temporizador de retraso se detiene y el LED se apaga.

SingleShotMillisDelay

Aquí está el boceto BasicSingleShotDelay reescrito usando la biblioteca millisDelay. Este boceto está disponible en SingleShotMillisDelay.ino

Aquí está la versión millisDelay donde el código anterior se ha incluido en los métodos de clase de la clase millisDelay.

 #incluir 

int led = 13; // El pin 13 tiene un LED conectado en la mayoría de las placas Arduino. millisDelay ledDelay; void setup () {// inicializa el pin digital como salida. pinMode (led, SALIDA); digitalWrite (led, ALTO); // enciende el led // demora de inicio ledDelay.start (10000); } void loop () {// comprueba si la demora ha excedido el tiempo de espera if (ledDelay.justFinished ()) {digitalWrite (led, LOW); // apaga el led}}

Temporizadores repetidos

Estos son ejemplos simples de un retraso / temporizador repetitivo. BasicRepeatingDelay es el código simple y RepeatingMillisDelay usa la biblioteca millisDelay.

BasicRepeatingDelay

Este boceto está disponible en BasicRepeatingDelay.ino

 int led = 13; // El pin 13 tiene un LED conectado en la mayoría de las placas Arduino. 
unsigned long DELAY_TIME = 1500; // 1, 5 segundos sin signo, delayStart = 0; // la hora en que comenzó el retraso bool delayRunning = false; // verdadero si todavía está esperando el retraso para terminar bool ledOn = false; // realiza un seguimiento de la configuración vacía del estado del led () {pinMode (led, OUTPUT); // inicializa el pin digital como salida. digitalWrite (led, BAJO); // apaga el led ledOn = false; // inicio demora delayStart = millis (); delayRunning = true; } void loop () {// comprueba si el retraso ha expirado si (delayRunning && ((millis () - delayStart)> = DELAY_TIME)) {delayStart + = DELAY_TIME; // esto evita la deriva en los retrasos // alterna el led ledOn =! ledOn; if (ledOn) {digitalWrite (led, HIGH); // enciende el led} else {digitalWrite (led, LOW); // apaga el led}}}

La razón para usar
delayStart + = DELAY_TIME;
para restablecer el retraso para que se ejecute nuevamente, es posible que el millis () - delayStart sea> DELAY_TIME porque el millis () se ha incrementado o debido a algún otro código en el bucle () que lo ralentiza. Por ejemplo, una declaración de impresión larga. (Consulte Agregar un monitor de bucle en el paso 7)

Otro punto es iniciar el retraso al final del inicio () . Esto garantiza que el temporizador sea preciso al comienzo del bucle (), incluso si el inicio () tarda un tiempo en ejecutarse.

RepeatingMillisDelay

Aquí está el boceto BasicRepeatingDelay reescrito usando la biblioteca millisDelay. Este boceto está disponible en RepeatingMillisDelay.ino

 #incluir 
int led = 13; // El pin 13 tiene un LED conectado en la mayoría de las placas Arduino. bool ledOn = falso; // realiza un seguimiento del estado led millisDelay ledDelay; void setup () {// inicializa el pin digital como salida. pinMode (led, SALIDA); // inicializa el pin digital como salida. digitalWrite (led, BAJO); // apaga el led ledOn = false; // retraso de inicio ledDelay.start (1500); } void loop () {// comprueba si la demora ha excedido el tiempo de espera if (ledDelay.justFinished ()) {ledDelay.repeat (); // iniciar el retraso de nuevo sin deriva // alternar el led ledOn =! ledOn; if (ledOn) {digitalWrite (led, HIGH); // enciende el led} else {digitalWrite (led, LOW); // apaga el led}}}

Paso 6: Otras funciones de la biblioteca MillisDelay

Además de las funciones start (delay), just Finished () y repeat () ilustradas anteriormente, la biblioteca millisDelay también tiene
stop () para detener el tiempo de espera de retraso,

isRunning () para verificar si aún no ha excedido el tiempo de espera y no se ha detenido,

restart () para reiniciar el retraso desde ahora, usando el mismo intervalo de retraso,

terminar () para forzar que el retraso expire temprano,

restantes () para devolver el número de milisegundos hasta que finalice el retraso y

delay () para devolver el valor de delay que se pasó a start ()

Versión de microsegundos de la biblioteca

millisDelay cuenta el retraso en milisegundos. También puede cronometrar por microsegundos. Se deja como ejercicio al lector escribir esa clase. ( Sugerencia: cambie el nombre de la clase a microDelay y reemplace las ocurrencias de millis () con micros () )

Congelar / Pausar un retraso

Puede congelar o pausar un retraso guardando los milisegundos restantes () y deteniendo el retraso y luego descongelarlo reiniciando con el mS restante como retraso. Por ejemplo, vea el ejemplo FreezeDelay.ino

 mainRemainingTime = mainDelay.remaining (); // recuerda cuánto tiempo falta para ejecutarse en el retraso principal mainDelay.stop (); // stop mainDelay NOTA: mainDelay.justFinished () NUNCA es verdadero después de stop () ... mainDelay.start (mainRemainingTime); // reiniciar después de congelar 

Paso 7: Palabra de advertencia: agregar un monitor de bucle

Desafortunadamente, muchas de las bibliotecas Arduino estándar usan delay () o introducen pausas, como AnalogRead y SoftwareSerial. Por lo general, los retrasos que estos introducen son pequeños, pero pueden acumularse, así que le sugiero que agregue un monitor en la parte superior de su bucle () para verificar qué tan rápido se ejecuta.

El monitor de bucle es muy similar al ejemplo de parpadeo. Un pequeño fragmento de código en la parte superior del método loop () solo alterna el Led cada vez que se ejecuta loop () . Luego puede usar un multímetro digital con una escala de Hz para medir la frecuencia de la salida en el pin LED (pin 13 en este caso)

El código es: -

 // Monitor de bucle: comprueba que el bucle () se ejecuta al menos una vez cada 1 ms 
// (c) 2013 Forward Computing and Control Pty. Ltd. // www.forward.com.au> // // Este código de ejemplo está en el dominio público. int led = 13; // no lo use en FioV3 cuando la batería esté conectada // El pin 13 tiene un LED conectado en la mayoría de las placas Arduino. // si usa Arduino IDE 1.5 o superior, puede usar // LED_BUILTIN predefinido en lugar de 'led' // la rutina de configuración se ejecuta una vez cuando presiona reset: void setup () {// inicializa el pin digital como salida. pinMode (led, SALIDA); // agregue su otro código de configuración aquí} // la rutina del bucle se ejecuta una y otra vez para siempre: void loop () {// alterna la salida del led en cada bucle La frecuencia del led debe medir> 500Hz (es decir, <1mS apagado y <1mS encendido ) if (digitalRead (led)) {digitalWrite (led, LOW); // apaga el LED haciendo el voltaje BAJO} else {digitalWrite (led, HIGH); // enciende el LED (ALTO es el nivel de voltaje)} // agrega el resto del código de bucle aquí}

Puede descargar el código del monitor aquí. Cuando ejecuto este código en mi placa Uno, el multímetro en el rango de Hz conectado entre el pin 13 y GND lee 57.6Khz. es decir, aproximadamente 100 veces> 500 hz.

A medida que agrega su código al bucle (), la lectura de Hz se reducirá. Solo verifique que se mantenga muy por encima de 500Hz (ejecución de 1 ms por bucle ()) en todas las situaciones.

Artículos Relacionados