Programando Arduino: ¡Terminamos nuestro sketch!

Continuamos nuestro periplo por el mundo de la programación de microcontroladores Arduino. En nuestro último artículo, mejoramos las notificaciones de nuestro dispositivo Pomodoro y limpiamos un poco el código: ahora toca aprovechar el trabajo que hicimos entonces, añadiendo todas las características que no podríamos haber incorporado al principio.

Como recordatorio, hasta ahora hemos implementado un temporizador, que alterna entre periodos de trabajo (indicados con una luz roja, y que duraría 20 minutos) y de descanso (luz verde, durante 5 minutos). El paso de uno a otro se notifica con un parpadeo y una serie de pitidos. Una forma sencilla de mejorar este sistema sería, por ejemplo, encontrar la manera de elegir la duración de cada periodo.

Para ello, empezaremos incorporando dos diales, cada uno de los cuales es un potenciómetro, esencialmente una resistencia variable. Estos estarán conectados a sendos puertos de entrada analógicos, que nos permitirán medir el voltaje que entra en el pin (funcionando a la inversa que un pin de salida). Girando el dial, cambiamos el valor de la resistencia, y con ello ese voltaje entrante, que luego en el código podemos asociar a un tiempo determinado. De momento, vamos a conectar el hardware, obteniendo un dispositivo con este aspecto:

Como vemos, a0 y a1 serán los pines de entrada analógica. Declararemos las variables asociadas a estos (dialTrabajo = A0 y dialPausa = A1), y inicializaremos en el setup con la función pinMode(), esta vez indicando el modo INPUT.

El resto del código es prácticamente igual de sencillo: bastará con leer los pines y guardar la información en sendas variables, usando la función analogRead(); convertir este valor (entre 0 y 1023) en un valor de tiempo; y finalmente, sustituir los valores de tiempoTrabajo y tiempoPausa (con los que, recordemos, comparábamos el transcurso del tiempo para saber en qué punto del periodo correspondiente estábamos) con este nuevo valor. De esta forma, podremos alterar la duración de cada periodo instantaneamente, algo que no podríamos hacer si no fuera por las modificaciones que hicimos en la última entrada de blog. El código adicional quedaría así:

En la primera imagen vemos como hemos declarado las variables necesarias, y preparado los pines correspondientes. En la segunda, hemos introducido al comienzo del Loop el procedimiento necesario para ajustar el tiempo, asumiendo valores mínimos de 5 y 40 mins. Como vemos, hay un par de complicaciones en el código.

La primera es que tendremos que declarar las variables como long, en vez de int. Esto nos permite asignar más memoria a cada variable, para que puedan tener valores más grandes. Necesario, si vamos a estar trabajando con valores del orden de varios millones. La segunda, por su parte, tiene que ver con la manera en la que determinamos la conversión entre voltaje y tiempo, aunque en realidad no se trata más que de una ecuación lineal: dividimos el voltaje de entrada por su máximo (1023), multiplicamos esta proporción por el valor de tiempo máximo (en nuestro caso 40 mins, i.e. 2 400 000 milisegundos), y le sumamos el tiempo mínimo (5 mins, 300 000 milisegundos). Es decir, tenemos esta fórmula:

Con ello conseguimos un valor que varía linealmente entre esos valores de tiempo mínimo y máximo. Más adelante, podríamos variar también estos límites, aunque de momento no vamos a complicarnos más, y vamos a dejarlos como valores fijos.

Y ya está. Con esto, ya tendríamos una forma de cambiar en tiempo real la duración de cada periodo.

Vamos con una otra mejora: sería interesante tener alguna forma de pasar rápidamente de un periodo a otro, dándonos un poco más de control sobre el temporizador. Como antes, empezamos por añadir el hardware: un botón, conectado a tierra y a uno de los pines de entrada digitales por un lado, y al pin de voltaje 5V constantes de la placa:

Para esta función, usaremos algo que no hemos visto hasta ahora, los interrupts. Esencialmente, un interrupt es un método que le permite al programa estar atento a un pin y, cuando detecta una determinada señal en dicho pin, interrumpir el programa para ejecutar la función asignada. Vamos a ver como implementar un interrupt en nuestro caso.

Primero, creamos la función a ejecutar, que simplemente cambiará de estado (es decir, la variable workStatus) y reseteará la cuenta atrás, (igualando tiempoAct y tiempoRef):

Si nos fimamos, no hemos actualizado el valor de tiempoAct usando millis(): esto se debe a que, dentro de un interrupt, las funciones delay() y millis() no se comportan como deberían.

A continuación, tendremos que inicializar el pin que usaremos como interrupt (en nuestro caso, el 2: no todos los pins pueden cumplir esta función), y asociar la función que acabamos de crear a un interrupt asignado a ese pin. Para ello usamos la función attachInterrupt(), de la siguiente forma:

Como vemos, para usar esta función, tenemos que indicar el pin (la función digitalPinToInterrupt() se usa para llamar a la denominación interna del interrupt, independientemente de la placa. No es algo que deba preocuparnos en exceso ahora mismo), la función a disparar, y la señal que hara de disparador (en nuestro caso, RISING, es decir, cada vez que el pin pase de 0 a 1).

Y ya estaría: con esto, conseguimos que cada vez que se pulse el boton, se salte directamente a la siguiente “fase” del programa. Nos ha costado un poco, pero al final hemos conseguido un programa razonablemente viable: ahora solo quedaría… toda la parte del montaje físico. Pero eso, mejor, lo dejaremos para otro artículo.