Programando Arduino: continuando con nuestro caso de uso

En nuestro anterior artículo, programamos un pequeño sketch de Arduino, a modo de toma de contacto con este tipo de microcontroladores. Se trataba de un dispositivo de alarma, específicamente uno que nos ayudase a usar la técnica Pomodoro, una herramienta para ayudar a la concentración y gestión del tiempo.

Nuestro dispositivo, que como ya hemos dicho es extremadamente básico, se limitaba a alternar entre dos LEDs de colores: funcional, pero un poco frustrante en su simpleza. Hoy vamos a intentar construir sobre ese programa, añadiendo algunas funcionalidades.

Primero, recordemos qué pinta tenía nuestro sketch hasta ahora:

Una fucnión setup() en la que declaramos los pines de salida para los LEDs, y una función loop() que los apaga y enciende de forma alterna, con un periodo entre estados de 25 y 5 minutos.

Bien, lo primero que podemos hacer es intentar destacar más el paso de un estado a otro: que el dispositivo nos llame la atención cuando sea hora de tomarse un descanso, o de volver al trabajo. La manera más sencilla de conseguir esto sin tener que añadir más hardware o tocar mucho el código sería, por ejemplo, hacer que los LEDs parpadeasen un momento, cuando termina uno de los delays. Vamos a probar a desarrollar eso, pero antes, un poco de limpieza de código.

Como seguramente queramos añadir más elementos, más variables, más piezas de hardware, etc, nos interesa que el código sea lo más claro posible. Ahora mismo no hay mucho que limpiar, pero si podemos intentar guardar todos los elementos que podamos como variables independientes, declaradas al principio del programa, para poder cambiarlas de manera sencilla. Así, por ejemplo, conviene que declaremos al principio sendas variables LEDpinRojo y LEDpinVerde, para guardar los números de los pines correspondiente, y quizá tiempoTrabajo y tiempoPausa. Nos quedaría algo así:

Así, cada vez que queramos cambiar algo, podemos hacerlo en las primeras lineas, y el cambio afectará a todo el código. Procuraremos que todo lo que hagamos a partir de ahora sea siguiendo la misma estrategia.

En ese sentido, la “notificación” de la que hablábamos antes, en vez de describirla en el Loop, vamos a convertirla en una función aparte. Declararemos una función notification(), en la que (usando las herramientas que ya hemos visto, digitalWrite() y delay()), ambas luces se enciendan y apaguen varias veces. Bastará con describir ese parpadeo, dentro de un bucle for, que dejamos listo para reproducirse 5 veces. Nos quedaría algo así:

Y el resultado:

Vemos que, en principio, funciona sin problemas, pero quizá podamos mejorar un poco más la notificación, hacerla más llamativa. Para ello, vamos a introducir un elemento sonoro, muy en la linea del método Pomodoro original (que utiliza una alarma de cocina).

Bastará con incorporar un zumbador piezoelectrico, que conectaremos a uno de los puertos digitales, el 9, que guardaremos en el código bajo el nombre “pinPiezo”. A continuación usaremos las funciones tone(), que emite un pitido a una frecuencia determinada por el plin indicado, y noTone(), que silencia el pin indicado. Como ya tenemos una función dedicada a la notificación, simplemente añadimos tone(pinPiezo, 440) al mismo tiempo que se encienden los LEDs, y noTone(pinPiezo) cuando se apagan:

Lo probamos, y en efecto funciona: y resulta una notificación mucho más efectiva que tan solo una señal luminosa. Como vemos, trabajando de forma modular resulta bastante sencillo añadir elementos al código, con un mínimo de modificaciones.

Para el siguiente conjunto de mejoras, sin embargo, vamos a tener que reescribir el núcleo de nuestro código. Hasta ahora, hemos usado la función delay() para detener el programa durante cada uno de los periodos de trabajo y descanso: esto es una forma sencilla de resolver el problema, pero nos impide interactuar con el dispositivo durante ese periodo. Por tanto, vamos a plantear una alternativa, algo más compleja, pero que nos de una mayor libertad en ese sentido.

Primero, vamos a definir una nueva variable, workStatus, que nos diga si nos encontramos en un periodo de trabajo (workStatus = 0) o de pausa (workStatus = 1). Reescribiremos la función loop() para que, en vez de encender y apagar LEDs de acuerdo a un bucle fijo, compruebe el valor de workStatus y ajuste los LEDs acorde a eso, sin ningún delay() de por medio. Para ello, introducimos una estructura de control if-then: si workStatus es 0, apaga la luz verde, enciende la roja; si es 1, lo contrario.

Esto nos permite separar la tarea de decidir qué luz está encendida de la tarea de contar el tiempo que ha pasado y decidir cuando cambiarla, y nos permite jugar un poco más con eso último. Sin embargo, ahora nos toca encontrar la manera de decidir cuando toca cambiar entre uno y otro. Para ello, vamos a usar la función millis(). Esta función nos da el número de milisegundos transmitidos desde que se encendió la placa, es decir, funciona como un reloj interno. De esta forma, si llamamos a la función y la usamos para fijar un tiempo de referencia (al que llamaremos, muy originalmente, tiempoRef), podemos calcular en cualquier momento cuantos milisegundos han pasado desde ese punto, llamando de nuevo a la función millis(), y calculando la diferencia. Bastará por comparar esta diferencia con tiempoTrabajo o tiempoPausa para determinar que porcentaje del periodo de trabajo o pausa ha transcurrido, y si toca cambiar.

Implementar esto a nivel de código es tan sencillo como empezar declarando las variables tiempoRef y tiempoAct. A continuación, en el Setup, tomaremos un primer tiempo de referencia. Una vez comience el Loop, lo primero que hacemos es tomar el tiempo actual, al comienzo de cada ciclo. Ahora tocaría comparar la diferencia entre uno y otro con tiempoTrabajo o tiempoPausa. Cual usemos va a depender de la variable workStatus, así que tendremos que usar una estructura if: la misma, de hecho, que para determinar el color de los LEDs –así que aprovechamos la que ya pusimos antes.

Ahora, bastará con anidar los ifs: si la diferencia entre el tiempo actual y el tiempo de referencia es mayor que la longitud del periodo que toque, hacemos tres cosas: cambiamos workStatus, llamamos a la función notificación, y tomamos un nuevo tiempoRef, que nos sirva para la siguiente vuelta de bucle.

Descrito así puede parecer un tanto lioso, pero se ve más claro en el propio programa:

Con esto, hemos conseguido un programa que… no añade nada a la versión anterior. ¿Parece extraño? En realidad, estas modificaciones, aunque poco “vistosas”, nos van a permitir añadir funcionalidades que antes no podríamos, y están más alineadas con buenas prácticas de programación – arquitectura modular, utilización de variables, etc. Se trata de una lección necesaria cuando uno empieza a programar: la mayor parte del trabajo, por lo general, no se ve, pero es lo que sustenta la totalidad del proyecto. Por hoy, al menos, vamos a contentarnos con eso, y dejaremos las nuevas características otro día.