Las temporizaciones son muy usadas en todo tipo de equipos electrónicos modernos. Desde computadores, teléfonos celulares, hasta lavadoras de ropa y autos modernos. En la industria, casi todos los equipos electrónicos usan temporizaciones. En este artículo aprenderemos como generar y usar temporizaciones y tiempos con la tarjeta Arduino uno.
En la Figura 1 podemos ver la tarjeta Arduino Uno y el la Figura 2 el IDE o Interface de Desarrollo, donde se escriben programas para Arduino Uno, comúnmente llamados Sketch. Esta IDE puede ser descargada del sitio web de Arduino.
El tiempo es algo muy mesurable, es decir podemos comenzar a medir el intervalo de tiempo que toma determinada acción o evento. También, podemos programar el tiempo que dure una determinada acción o evento. Para esto podemos usar funciones que miden o retardan el tiempo. En la Figura 3 podemos observar como la placa Arduino Uno, la podemos convertir en un temporizador. Las temporizaciones pueden ser de nivel alto como muestra la Figura 4 o de nivel bajo como muestra la Figura 5.
Esto depende del actuador que se quiere controlar, pues muchos se activan por nivel alto y otros por nivel bajo. La IDE de Arduino, cuenta con funciones para hacer temporizaciones. En este artículo vamos a estudiar estas funciones.
LA FUNCION delay().
La función delay(), retarda o pausa el programa por una cantidad de tiempo en milisegundos. El valor del tiempo en milisegundos, es pasada como parámetro a la función delay(). Recordemos que un segundo tiene 1000 milisegundos. La función delay(), tiene el siguiente prototipo:
delay(ms)
Parámetro:
ms: el número de milisegundos a pausar el programa.
Retorno:
Esta función no retorna ningún valor.
ENCENDER Y APAGAR UN LED (BLINK) REPETIDAMENTE.
Para realizar esta práctica de código ejemplo, es necesario la tarjeta Arduino Uno, como muestra la Figura 6.
EL LED para verificar el programa, viene instalado en la tarjeta. En la Figura 6, está señalado con un círculo rojo. El LED prendera (on) y apagara (off) por un determinado tiempo. Usted puede alterar el valor pasado a la función delay(), para experimentar con este programa. El código ejemplo para esta práctica, puede ser encontrado en el menú:Archivo->Ejemplos->01.Basic->Blink . Observe la Figura 7.
El siguiente es el código ejemplo:
// the setup function runs once when you press reset or power the board void setup() { // initialize digital pin LED_BUILTIN as an output. pinMode(LED_BUILTIN, OUTPUT); } // the loop function runs over and over again forever void loop() { digitalWrite(LED_BUILTIN, HIGH); // turn the LED on (HIGH is the voltage level) delay(1000); // wait for a second digitalWrite(LED_BUILTIN, LOW); // turn the LED off by making the voltage LOW delay(1000); // wait for a second }
En la función setup(), se inicializa el pin LED_BUILTIN, como salida. Las placas Arduino Uno, usan el pin 13 para conectar un LED, que esta instado en la placa por defecto. Para esto utilizamos la función:
pinMode(LED_BUILTIN, OUTPUT);
Después, en la función loop(), encendemos el LED. Para esto usamos la siguiente línea de código:
digitalWrite(LED_BUILTIN, HIGH);
Ahora, retardamos el programa por un Segundo (1000 milisegundos). Esto lo hacemos con la línea de código:
delay(1000);
Después apagamos el LED. Esto lo hacemos con la línea de código:
digitalWrite(LED_BUILTIN, LOW);
Finalmente, volvemos a retardar el programa por un segundo. Y así, continua el programa indefinidamente, haciendo parpadear (blink) el LED.
LA FUNCION delayMicroseconds().
La función delayMicroseconds(), retarda o pausa el programa por una cantidad de tiempo en microsegundos. El valor del tiempo en microsegundos, es pasada como parámetro a la función delay(). Recordemos que un segundo tiene 1.000.000 de microsegundos. La función delayMicroseconds(), tiene el siguiente prototipo:
delayMicroseconds(us)
Parámetro:
us: el número de microsegundos a pausar el programa. Este parámetro es unsigned int.
Retorno:
Esta función no retorna ningún valor.
Como el parámetro pasado a la función delayMicroseconds(), es del tipo unsigend int, la IDE de Arduino recomienda que el valor pasado a esta función, no sea mayor a 16,383 microsegundos. Para retardos mayores, es mejor usar la función delay(), vista anteriormente en este artículo.
TEMPORIZACION USANDO LA FUNCION delayMicroseconds().
Para verificar este programa podemos montar un circuito como mostrado en la Figura 8.
El siguiente es un ejemplo, donde usamos la función delayMicroseconds(), para crear un retardo en microsegundos:
Int outPin = 8; void setup() { // initialize digital pin outPin as an output. pinMode(outPin, OUTPUT); } // the loop function runs over and over again forever void loop() { digitalWrite(outPin, HIGH); // turn the LED on (HIGH is the voltage level) delayMicroseconds (50); // wait for 50 microseconds. digitalWrite(outPin, LOW); // turn the LED off by making the voltage LOW delayMicroseconds (50); // wait for 50 microseconds. }
Aunque la funciones delay() y delayMicroseconds(), se pueden usar para hacer retardos, no es recomendado utilizarlas cuando los retardos son muy grandes, porque estas funciones toman el control del programa y no permiten que se ejecuten otras tareas, como por ejemplo: leer sensores, escanear si algún pulsador fue presionado, encender y apagar actuadores (salidas) en tiempos precisos. Para evitar el anterior problema, en necesario usar una función llamada millis(), la cual permite crear retardos y temporizaciones de forma que el programa principal pueda seguir ejecutándose y sea posible hacer otras tareas.
LA FUNCION millis().
La función millis(), retorna el número de milisegundos desde que la tarjeta Arduino está ejecutando el actual programa. Este número recomienza, es decir vuelve a cero, después de aproximadamente 50 días. La función millis (), tiene el siguiente prototipo:
millis()
Parámetro:
Ninguno.
Retorno:
El número de milisegundos desde que el programa comenzó. El valor retornado es del tipo unsigned long. Así, el valor retornado es de 32 bits. Por este motivo, es necesario tener cuidado al hacer operaciones aritméticas, con el valor retornado por esta función.
ENCENDER Y APAGAR UN LED (BLINK) SIN USAR LA FUNCION delay().
Para realizar esta práctica es necesario la tarjeta Arduino Uno, como el mostrado en la Figura 6. El código ejemplo puedes ser encontrado en el menú:Archivo->Ejemplos->02.Digital->BlinkWithoutDelay . Observe la Figura 9.
El siguiente es el código ejemplo:
// constants won't change. Used here to set a pin number: const int ledPin = LED_BUILTIN;// the number of the LED pin // Variables will change: int ledState = LOW; // ledState used to set the LED // Generally, you should use "unsigned long" for variables that hold time // The value will quickly become too large for an int to store unsigned long previousMillis = 0; // will store last time LED was updated // constants won't change: const long interval = 1000; // interval at which to blink (milliseconds) void setup() { // set the digital pin as output: pinMode(ledPin, OUTPUT); } void loop() { // here is where you'd put code that needs to be running all the time. // check to see if it's time to blink the LED; that is, if the difference // between the current time and last time you blinked the LED is bigger than // the interval at which you want to blink the LED. unsigned long currentMillis = millis(); if (currentMillis - previousMillis >= interval) { // save the last time you blinked the LED previousMillis = currentMillis; // if the LED is off turn it on and vice-versa: if (ledState == LOW) { ledState = HIGH; } else { ledState = LOW; } // set the LED with the ledState of the variable: digitalWrite(ledPin, ledState); } }
Todas las tarjetas de Arduino, tienen un LED instalado en la propia tarjeta, para propósito de uso y testes. En el caso de la tarjeta Arduino Uno, este LED está conectado al pin 13. Con la siguiente línea de código podemos renombrar este LED, que por defecto el compilador lo llama LED_BUILTIN:
const int ledPin = LED_BUILTIN; // the number of the LED pin
Después, declaramos una variable para almacenar el estado del LED. El nombre dado a esta variable es:ledState . La siguiente línea de código, hace esto:
int ledState = LOW; // ledState used to set the LED
Para no usar la función delay(), necesitamos una variable para almacena la última vez que el LED cambio de estado. Esta variable debe ser del tipo unsigned long, (32 BITS), porque los tiempos que vamos a usar, tienen este tamaño. La siguiente línea de código, hace esto:
unsigned long previousMillis = 0; // will store last time LED was updated
La variable declarada, se llama previousMillis y la inicializamos con el valor de 0.
Después, declaramos una constante llamada interval, a la cual le asignamos el valor de 1000. La vamos usar para el intervalo al cual prendera y apagara el LED (blink). Esto lo hacemos con la siguiente línea de código:
const long interval = 1000; // interval at which to blink (milliseconds)
Después, encontramos la función setup(), en la cual se coloca o configura el pin del LED, como salida. Para esto usamos la función pinMode(), la cual recibe 2 parámetros. El primero es el número del pin donde está conectado el LED. El segundo indica si va a ser entrada (INPUT) o salida (OUTPUT). Como el LED es salida, por eso le enviamos a la función pinMode(), el valor OUTPUT. Esto lo hacemos con la siguiente línea de código:
pinMode(ledPin, OUTPUT);
Después, encontramos la función loop(), la cual siempre es llamada y es donde escribimos el programa. La primera línea de código declara una variable de tipo unsigned long, llamada currentMillis, para almacenar el tiempo actual o corriente, retornado por la función millis(). Recordemos que esta función retorna el tiempo desde que se encendió o prendió (on) la tarjeta Arduino por última vez. Para conocer este tiempo, que es dado o retornado en milisegundo usamos la siguiente línea de código:
unsigned long currentMillis = millis();
Después, verificamos, si el tiempo actual, retornado por la función millis() y almacenado en la variable currentMillis, es mayor o igual (>=) al valor cuando cambio el estado del LED por última vez y que es almacenado en la variable previousMillis. Así, se resta o sustrae de currentMillis el valor de la variable previousMillis. Si este resultado es mayor o igual al valor asignado a la constante interval, que es de 1000 milisegundos, entonces la instrucción: if(currentMillis – previousMillis >= interval) se cumple o es verdadera y el código almacenado entre sus corchetes {}, se ejecutara. Esta verificación para ver este tiempo, sin usar la función delay(), la hacemos con la siguiente línea de código:
if (currentMillis - previousMillis >= interval) {
Después, actualizamos el valor del tiempo en la variable previousMillis, por que el próximo paso es cambiar el estado del LED. Esta actualización del tiempo lo hacemos con la siguiente línea de código:
previousMillis = currentMillis;
Ahora, es el momento de cambiar el estado del LED. Para esto verificamos el estado actual del LED. Si este es bajo (LOW), entonces lo cambiamos para alto (HIGH). Esto lo hacemos con las siguientes líneas de código:
if (ledState == LOW) { ledState = HIGH; } else { ledState = LOW; }
Después, cambiamos el estado sobre el pin asignado al LED. Aquí, es necesario usar la función digitalWrite(), la cual se encarga de colocar el estado de un pin para alto o bajo, según se le indique en el segundo parámetro. El primer parámetro es el pin donde se va a colocar el estado. Esto lo hacemos con la siguiente línea de código:
digitalWrite(ledPin, ledState);
Así, podemos ver que el LED parpadeara (blink), a cada segundo. Puede alterar el valor en la constante interval, para testar el programa. Haga esto en la línea de programa:
const long interval = 1000; // interval at which to blink (milliseconds)
La ventaja de este tipo de código, mostrado en este ejemplo, es poder hacer otras tareas de código, como leer sensores, sin interrumpir el programa. Por ejemplo, muchas impresoras 3D, trabajan con tarjetas Arduino, y es necesario estar velicando los interruptores fines de carrera, constantemente, para parar los motores de paso. Esto no se puede hacer si se usa la función delay(), con tiempos muy grandes. Otro, ejemplo son muchos Drones, los cuales también, trabajan con técnicas y códigos basados en Arduino, y es necesario, estar leyendo sensores, verificar si llego algún comando por el radio, etc. Por esto, es necesario usar alguna forma de usar la función millis(), para hacer retardos y temporizaciones.
LEYENDO EL VALOR DEL TIEMPO DE LA FUNCION millis() Y ENVIANDOLA AL MONITOR SERIAL.
Esta práctica y código ejemplo, se lee el valor del tiempo, desde que la tarjeta Arduino fue conectado y este valor es enviado por el puerto serial. Así, es posible abrir el monitor serial y observar el valor. Para abrir el monitor serial, vea la Figura 10, la cual indica el menú.
Este programa nos permite ver cómo trabaja la función millis(). El siguiente es el código ejemplo:
unsigned long time; void setup() { Serial.begin(9600); } void loop() { Serial.print("Time: "); time = millis(); //prints time since program started. Serial.println(time); // wait a second so as not to send massive amounts of datas. delay(1000); }
Este programa comienza declarando una variable llamada time, para almacenar el valor del tiempo devuelto por la función milli(). Esto lo hacemos con la siguiente línea de código. Observe que ella es declarada comounsigned long .
unsigned long time;
Después encontramos la función setup(). En ella ejecutamos una función para configurar el puerto serial a una velocidad de transmisión y recepción de 9600 baudios o bits por segundo. Este puerto lo vamos a usar para enviar datos hacia el computador, específicamente para el monitor serial. Esto lo hacemos con la línea de código:
Serial.begin(9600);
Después es llamada la función loop(), la cual comienza llamando la función:
Serial.print("Time: ");
Con la anterior función, enviamos el texto “Time: “ al computador. Después leemos el tiempo actual o corriente y lo almacenamos en la variable time. Esto lo hacemos con la siguiente línea de código:
time = millis();
Después, enviamos el valor retornado por la función millis(), y almacenado en la variabletime, por el Puerto serial. Así, lo podemos observar en el monitor serial. Esto lo hacemos con la siguiente línea de código:
Serial.println(time);
Finalmente, hacemos un retardo, para no enviar demasiados datos al computador. Esto lo hacemos con la siguiente línea de código:
delay(1000);
Como podemos observar, este programa es más didáctico. Es para comprender como trabaja la función millis(). Y usamos la función delay(), con un retardo de 1000 milisegundos o 1 segundo, para poder ver el resultado en el computador. Pero, para aplicaciones reales y más complejas, es necesario hacer los retardos con la función millis().
EVITANDO EL RUIDO DE LOS PULSADORES O INTERRUPTORES.
Para realizar esta práctica es necesario montar un circuito como el mostrado en la Figura 11.
Los interruptores y pulsadores generalmente cuando son presionados y liberados, emiten un rebote mecánico, el cual envía muchos pulsos a los circuitos a los cuales están conectados. Vea la Figura 12.
En algunos casos esto no es un problema, pero en otros, esto puede ser un problema. Eso depende de la aplicación donde son instalados estos interruptores y pulsadores. Para evitar esto, podemos usar un programa como el mostrado aquí. El código ejemplo puedes ser encontrado en el menú: Archivo->Ejemplos->02.Digital->Debounce. Observe la Figura 13.
El siguiente es el código ejemplo:
// constants won't change. They're used here to set pin numbers: const int buttonPin = 2; // the number of the pushbutton pin const int ledPin = 13; // the number of the LED pin // Variables will change: int ledState = HIGH; // the current state of the output pin int buttonState; // the current reading from the input pin int lastButtonState = LOW; // the previous reading from the input pin // the following variables are unsigned longs because the time, measured in // milliseconds, will quickly become a bigger number than can be stored in an int. unsigned long lastDebounceTime = 0; // the last time the output pin was toggled unsigned long debounceDelay = 50; // the debounce time; increase if the output flickers void setup() { pinMode(buttonPin, INPUT); pinMode(ledPin, OUTPUT); // set initial LED state digitalWrite(ledPin, ledState); } void loop() { // read the state of the switch into a local variable: int reading = digitalRead(buttonPin); // check to see if you just pressed the button // (i.e. the input went from LOW to HIGH), and you've waited long enough // since the last press to ignore any noise: // If the switch changed, due to noise or pressing: if (reading != lastButtonState) { // reset the debouncing timer lastDebounceTime = millis(); } if ((millis() - lastDebounceTime) > debounceDelay) { // whatever the reading is at, it's been there for longer than the debounce // delay, so take it as the actual current state: // if the button state has changed: if (reading != buttonState) { buttonState = reading; // only toggle the LED if the new button state is HIGH if (buttonState == HIGH) { ledState = !ledState; } } } // set the LED: digitalWrite(ledPin, ledState); // save the reading. Next time through the loop, it'll be the lastButtonState: lastButtonState = reading; }
El programa comienza declarando 2 constantes. Una constante para el pin donde está conectado el pulsador. Esto se hace con la línea de programa:
const int buttonPin = 2; // the number of the pushbutton pin
La otra constante, es para indicar al programa en que pin está conectado el LED. Esto lo hacemos con la línea de programa:
const int ledPin = 13; // the number of the LED pin
Después, son declaradas las variables que se usaran en el programa. La primera variable es ledState, la cual almacenara el estado del LED. Note que esta variable la inicializamos con un valor alto (HIGH). Esto se hace con la siguiente línea de programa:
int ledState = HIGH; // the current state of the output pin
Después, declaramos la variable buttonState. Esta la usamos para almacenar el estado actual de lectura de pulsador. Note que esta variable es del tipo int. La siguiente línea de código hace esto:
int buttonState; // the current reading from the input pin
Después, declaramos la variable lastButtonState. Esta la usamos para almacenar el valor del estado previo del pulsador. La siguiente línea de programa hace esto:
int lastButtonState = LOW; // the previous reading from the input pin
Después, declaramos la variable lastDebounceTime. Esta la utilizamos para almacenar el último momento en que el pin del LED cambio (toggled). Esto lo hacemos con la siguiente línea de programa:
unsigned long lastDebounceTime = 0; // the last time the output pin was toggled
Después, declaramos la variable debounceDelay. Esta la usamos para dar un retardo de lecturas en pulsador y así, tener una lectura estable, evitando el anti revote o ruido. Esta variable es declarada con la siguiente línea de programa:
unsigned long debounceDelay = 50; // the debounce time; increase if the output flickers
Después, entramos en la función setup(), la cual solo se ejecuta en el inicio, es decir cuando conectamos la tarjeta Arduino, a la fuente de voltaje o después de unreset . Por este motivo es en esta función donde hacemos las configuraciones del microcontrolador, como por ejemplo que pines serán salidas o entradas, si se usa el puerto serial, a cuantos baudios por segundo, va a trabajar, etc. Así, con la siguiente línea de código, configuramos el pin buttonPin como entrada.
pinMode(buttonPin, INPUT);
Después, configuramos el pin ledPin, como salida, pues es en este pin, donde esta el LED que viene con la tarjeta Arduino Uno. Esto lo hacemos con la siguiente línea de código:
pinMode(ledPin, OUTPUT);
Después, colocamos la salida del LED, para el valor almacenado en la variableledState . Como esta variable fue inicializada con el valor HIGH, o sea 1, entonces esta salida se colocara a nivel alto. Esto lo hacemos con la siguiente línea de código:
digitalWrite(ledPin, ledState);
Después, leemos el estado del pulsador en una variable local. Las variables locales son declaradas dentro de una rutina o función, y solo se pueden usar dentro de esa función. Estas variables locales son útiles para almacenar valores que son muy temporales, es decir que las necesitamos solo por unos momentos. La siguiente línea de código hace esto:
int reading = digitalRead(buttonPin);
Después, verificamos si el estado del pulsador, recientemente leído, es diferente del último estado en que cambio el pulsador. Esto se hace con la siguiente línea de código:
if (reading != lastButtonState) {
Si la condición se cumple, entonces actualizamos el timer que usamos para evitar el ruido del pulsador. Esto lo hacemos con la siguiente línea de código:
lastDebounceTime = millis();
Después, restamos del actual tiempo retornado de la función millis(), el valor del tiempo de la última vez que el pulsador cambio de estado, almacenado en la variable lastDebounceTime y verificamos si este valor es mayor que el valor almacenado en la variabledebounceDelay, el cual fue programado para 50 milisegundos. Esto lo hacemos con la siguiente línea de código:
if ((millis() - lastDebounceTime) > debounceDelay) {
Si el tiempo es mayor a 50 milisegundos, el microcontrolador va a ejecutar el código que se encuentra entre los corchetes de la instrucción if. La primera acción es verificar si el estado del pulsador cambio. Esto lo hacemos con la siguiente línea de código:
if (reading != buttonState) {
Si el estado del pulsador cambio, almacenamos este valor en la variable buttonState. Esto lo hacemos con la siguiente línea de código:
buttonState = reading;
Después, verificamos si el cambio fue para alto (HIGH). Esto lo hacemos con la siguiente línea de código:
if (buttonState == HIGH) {
Si el cambio fue para alto, entonces es necesario cambiar el estado del LED (toggle). Esto lo hacemos con la siguiente línea de código:
ledState = !ledState;
Con esta instrucción invertimos el valor de la variable ledState. Es decir si esta alto, es cambiado para bajo y si está bajo, es cambiado para alto. Después, el valor de la variable ledState es colocado para el LED. Esto lo hacemos con la siguiente línea de código:
digitalWrite(ledPin, ledState);
Después, salvamos o memorizamos el estado del pulsador. Esto lo hacemos con la siguiente línea de código:
lastButtonState = reading;
Así, la próxima vez que se repita el loop, el verifica si el estado del pulsador cambio.
Esta técnica es muy utilizada para evitar el rebote de los interruptores de fin de carrera en máquinas herramientas. La ventaja de este ejemplo, es permitir ejecutar más tareas sin usar la función delay().
CONCLUCION
Como podemos notar, es muy fácil hacer retardos con la placa Arduino. Bastando solamente usar la función delay(). Pero como no es aconsejable usar esta función, si los retardos son muy grandes, entonces podemos hacer retardos usando la función millis(). Bastando, solo de alguna manera almacenar en alguna variable la última vez que un evento sucedió y luego restar al actual valor del tiempo del microcontrolador, el tiempo almacenado en esta variable. Hay que tener en cuenta que, los valores de tiempo retornados por la función millis(), son del tipo unsigned long, que en compilador de Arduino son de 32 bits. Existen librerías que automatizan este proceso de crear retardos y temporizadores, haciendo muy fácil escribir código. Aunque en este artículo, he descrito los ejemplos línea por línea, es muy importante entender claramente cómo funcionan los retardos en Arduino, para hacer proyectos más complejos. Pues un error muy común en programación, es almacenar el valor retornado por la función millis(), que es del tipo unsigend long, es decir de32 bits, en un variables del tipo unsigned int o del tipo int, las cuales ambas son de 16 bits. Así, cuando testamos para ver si el tiempo que deseamos temporizar, ya término (elapsed), con una línea de código como la siguiente:
if ((millis() - lastDebounceTime) > debounceDelay) {
Los resultados no serán los esperados y el programa presentara fallas. Desafortunadamente, los compiladores no detectan esto, pues es normal en algunos casos querer almacenar valores grandes en valores más pequeños. Pero para la aritmética de teste de tiempos usada en la instrucción: if ((millis() - lastDebounceTime) > debounceDelay), es necesario que todos sus valores sean de 32 bits, o sea unsigned long.