Arduino: Usando la función millis() en lugar de delay()

El código que sigue es un típico ejemplo de escritura a través de la comunicación serie de Arduino. Escribe “Hola” a través del puerto COM serie (visible con el Monitor Serie de Arduino, en la pestaña Herramientas), y espera durante 1000 milisegundos (1 segundo) al final de cada iteración del bucle.

La función delay() es muy fácil de usar para crear esperas, pero tiene un inconveniente: deja al microcontrolador «atrapado» dentro de la ejecución de esta función durante el tiempo que se ha indicado. Si hubiese un cambio en un pin que debería detectar, o si llegase información a través de cualquiera de las comunicaciones posibles (serie, I2C o SPI) el microcontrolador sólo se enteraría luego de completarse el retardo.

Una solución es crear un retardo que no deje insensible al sistema durante un tiempo tan extenso.

La función millis()

millis() devuelve el número de milisegundos desde que la placa Arduino empezó a ejecutar, luego de un reinicio o el encendido. Este número se desbordará (volverá a cero), después de aproximadamente 50 días.

Retorna la cantidad de milisegundos en un valor long sin signo (unsigned long).

Nota: Tenga en cuenta que como el valor de retorno para millis() es un long sin signo (unsigned long), pueden producirse errores lógicos si un programador intenta hacer operaciones aritméticas con tipos de datos más pequeños, como de tipo int. Incluso con los long con signo se pueden producir errores ya que su valor máximo es la mitad que la de su contraparte sin signo.


Ejemplo de texto «Hola» utilizando la función millis()

Hacerlo de esta manera solo tiene sentido como ejemplo, ya que es evidente que en este caso directamente se puede usar una función delay(1000). Pero de todos modos la diferencia entre este ejemplo y el código que usa la función delay(1000) es que el ciclo del código que usa la función millis() se ejecutará una vez por segundo con la máxima precisión posible. El bucle en un código con delay(1000) se ejecutará en algo más de tiempo, ya que se produce un retardo al ejecutar Serial.println(«Hola»). Ocurrirá igual con cualquier otra serie de instrucciones que se incluyan dentro del bucle.

¿Por qué usar millis() en lugar de delay()?

Presentaré dos ventajas al utilizar millis() para crear retardos, en comparación con el uso habitual de delay().


1. Cronometraje preciso

La primera ventaja que discutiremos es la exactitud en el tiempo.

Con millis() podemos garantizar que el bucle se ejecute tantas veces como queramos dentro del retardo sin afectar su extensión, independientemente del tiempo de ejecución (obviamente, siempre que el tiempo de ejecución de todas las instrucciones sea menor al retardo deseado).

Con delay() esto no es posible, ya que no sabemos cuánto tiempo durará el tiempo de ejecución de todas las instrucciones de programa que están dentro del ciclo.

Una sincronización precisa como esta es muy útil cuando se muestrea a una cierta frecuencia, o cuando se utilizan filtros, entre otras cosas.

2. Sin bloqueo

La ventaja principal de la función millis() es que no nos impide ejecutar otro código mientras estamos «esperando».

Ejemplo: digamos que queremos imprimir «Hola» en el puerto serie una vez por segundo mientras hacemos otras cosas. Esto no es posible con delay(), ya que entrar a la función delay() pausa todo el código.

Aquí hay una manera de hacer esto:

Este fragmento de código es bastante similar al primer ejemplo que mostramos, excepto que este no bloquea el resto del programa mientras no se está imprimiendo hacia la línea serie.

3. Un simple secuenciador

Vamos a escribir un ejemplo simple en el que creamos un planificador que imprime ciertos trozos de texto a través de la línea serie a diferentes intervalos.

Así es como se ven los primeros 60 segundos en el Monitor Serie:

Esta es una manera agradable y fácil de sincronizar las ejecuciones en su código. También se puede ejecutar otras partes de código simultáneamente.

4. Una nueva función delay()

La función delay() estándar podemos reemplazarla por una implementada con la función millis(). Si bien parece que es lo mismo, ya veremos a continuación qué ventaja nos puede ofrecer su estructura de programa:

Además de esperar el tiempo indicado, esta función monitorea un pin de entrada del Arduino que haya sido cableado para detectar que ha sucedido algún evento externo. Si esa señal va a nivel BAJO (LOW), se interrumpe el retardo.

Para conectar varias entradas capaces de interrumpir el ciclo de retardo, se pueden agregar más elementos en la comparación de cierre del while, por ejemplo

while (Contador<=millis() && digitalRead(12)==HIGH) && digitalRead(11)==HIGH);

O se puede conectar a la entrada por el pin 12 (u otro pin elegido) un circuito como el que sigue, un AND realizado con lógica de diodos de señal (1N914 o 1N4148), que tiene la ventaja de que se puede ampliar indefinidamente. También se puede implementar con un circuito integrado compuerta AND.

Nota: quede claro que el circuito se representa con pulsadores, pero cada una de estas entradas puede ser un microswith, los contactos de un relé, o cualquier otro sensor que cierre circuito hacia GND (tierra o común).

Función micros() y desbordamiento

Al igual que delay() tiene una versión en microsegundos llamada delayMicroseconds(), la función millis() tiene como compañera para tiempos breves la función micros(). Si necesitamos una mejor resolución, micros() puede ser el camino a seguir. Sin embargo, tenga en cuenta que el valor devuelto por micros() se desbordará (volverá a cero) después de aproximadamente 70 minutos, en comparación con los 50 días de millis(). “Desbordamiento” significa que el conteo llega a su máximo, y entonces los valores de retorno de la función recomienzan desde cero.

Resumen

millis() y micros() son funciones realmente útiles para usar en tareas relacionadas con el tiempo. La opción inicial y típica de un programador de Arduino es usar delay(), que no siempre funcionará tan bien, principalmente cuando se programan muchas funciones que tienen que ver con el tiempo y existen eventos que no se pueden perder.