Cuenta pulsos (sin tu intervención) con la clase Blink

Cuenta pulsos (sin tu intervención) con la clase Blink

(Read this article in english here.)

El otro día me puse a pensar en una clase similar a Blink pero para contar pulsos. Y mientras imaginaba cómo la podría usar y cómo podría funcionar tuve una epifanía: “Esta clase que estoy pensando se parece mucho a la clase Blink, ¿será qué podría usar a ésta en lugar de escribir una nueva?”.

“Y si mando llamar al método .state_machine() en cada pulso recibido en lugar de cada tick temporal del sistema, ¿funcionará para lo que estoy pensando?”. ¡Eureka! Me ahorré una clase y reutilicé algo que ya había hecho. Por eso me encanta la programación orientada a objetos.

Tabla de contenidos

Primer ejemplo

El secreto para contar pulsos es precisamente mandar llamar al método .state_machine() en cada pulso que el sistema reciba, ya sea que el pulso venga de una interrupción o por encuesta (polling). ¡Y ya, eso es todo el cambio!

Es así que me puse a trabajar en un primer ejemplo sencillo como prueba de concepto. Dado que es fácil activar las interrupciones por pulsos en Arduino hice el siguiente experimento:

Blink pulses;

void pulse_ISR()
{
   pulses.state_machine();
}

void setup() 
{
   pinMode( 2, INPUT_PULLUP );
   attachInterrupt( digitalPinToInterrupt( 2 ), pulse_ISR, FALLING );

   pinMode( LED13, OUTPUT );
   pulses.begin( LED13, Blink::ePolarity::ACTIVE_HIGH );
   pulses.set( Blink::eMode::FOREVER, 5, 5 );
   pulses.start();
}

void loop()
{
   // ¡nada por el momento!
}

Para probar a este ejemplo utilicé una tarjeta Arduino Nano Every, un lector óptico de ranura, y conecté un relevador al pin D13:

Al fondo se puede apreciar la tarjeta Arduino Nano Every.

Función setup()

Antes que cualquier otra cosa debemos declarar un objeto del tipo Blink. Este objeto procesará los pulsos y la llamaremos pulses y será a quien estemos utilizando a lo largo de esta explicación:

Blink pulses;

Es necesario que el objeto pulses sea declarado a nivel global dado que interactúa con el hardware; en cualquier otro caso deberíamos evitar las variables globales.

En la función setup() realizamos 5 acciones:

  1. Configuramos el pin 2 como de entrada con resistencia de pull-up (esto último no es estrictamente necesario si tu circuito ya la incluye).
  2. Configuramos el pin 2 como fuente de interrupción cada vez que la señal en el pin vaya de alto a bajo (falling pulse). Y en cada pulso detectado se mandará llamar a la función callback pulse_ISR() (en un momento hablaré de ella).
  3. Configuramos como pin de salida al pin actuador, es decir, aquel que se activará/desactivará bajo control de la clase Blink. En el ejemplo éste será el pin D13.
  4. Configuramos al objeto pulses para que active/desactive al pin actuador cada 5 pulsos, siempre por siempre. Por supuesto tu puedes cambiar estos valores de acuerdo a la lógica de tu programa.
  5. Arranca la máquina de estados asociada al objeto pulses a través del método .start().

Función loop()

void loop()
{
   // ¡nada por el momento!
}

Nota algo muy importante en la función loop(), ¡no hay código! Toda la magia se realiza en el método .state_machine() asociado al objeto pulses. ¿Así o más fácil? ¿Cómo lo hubieras hecho tú sin utilizar a la clase Blink? ¿Cuántas líneas de código habrías escrito?

En este ejemplo no hemos tenido necesidad de incluir código más allá del necesario para la configuración. Esto no quiere decir que siempre será así: todo dependerá de la lógica de tu aplicación y de la configuración de los objetos Blink.

En el ejemplo 2 la función loop() va a incluir un poco de código.

Función pulse_ISR()

La función pulse_ISR() es lo que llamamos una función callback, esto es, es una función que inyectamos al sistema:

attachInterrupt( digitalPinToInterrupt( 2 ), pulse_ISR, FALLING );

para que posteriormente éste la llame cada vez que es necesario. Las funciones callback son muy útiles para que los programadores inyectemos nuestra propia funcionalidad en código que no es nuestro, o que debe ser independiente (o débilmente acoplado, loosely coupled).

De esta manera cada vez que se presente un pulso en el pin D2 el sistema llamará a la función callback pulse_ISR() y ésta a su vez mandará llamar a la máquina de estados asociada al objeto pulses. ¡Fácil!

NOTA: Quizás alguien se esté preguntado porqué no pusimos al método pulses.state_machine() como función callback directamente. La respuesta es simple, pero nada fácil: Todos los métodos incluyen, de manera oculta a nosotros, al apuntador this (en C++, Java, C#, etc), y por lo tanto ningún método, incluyendo .state_machine() cumpliría con la firma esperada por la función attachInterrupt(). Podríamos marcar a .state_machine() como static, pero ya no podríamos tener otros objetos de la clase Blink independientes en cuanto al conteo de pulsos.

Ejemplo 2

Para este ejemplo imaginé una máquina industrial que, luego de que el operador presiona un botón de inicio, un cilindro acoplado a un motor de baja velocidad comienza a dar vueltas, y luego de contar 5 se apagará hasta que el operario vuelva a presionar el botón de inicio:

Blink pulses;

void pulse_ISR()
{
   pulses.state_machine();
}



void setup() 
{
   pinMode( 2, INPUT_PULLUP );
   attachInterrupt( digitalPinToInterrupt( 2 ), pulse_ISR, FALLING );

   pinMode( LED13, OUTPUT );
   pulses.begin( LED13, Blink::ePolarity::ACTIVE_HIGH );
   pulses.set( Blink::eMode::ONCE, 5 );

   pinMode( 3, INPUT );
   // botón externo: final de carrera, inicio, etc...

}

void loop()
{
   if( digitalRead( 3 ) == 0 and not pulses.is_running() )
   {
      while( digitalRead( 3 ) == 0 ) ;
      // esperamos a que el botón se deje de presionar

      pulses.start();
      // activa al actuador e inicia la cuenta de pulsos
   }
}

La diferencia con el ejemplo anterior, además de que estamos agregando un botón extra, es que el objeto pulses ha sido configurado en el modo ONCE (en lugar de FOREVER); esto es, una vez que inicia el proceso (llamando al método .start() cada vez que el botón se presiona) el objeto pulses activará al motor del ejemplo a través del pin actuador (D13 en este ejemplo), y luego de que la cuenta de pulsos se completa, el propio objeto pulses “apaga” al motor y queda, nuevamente, a la espera de que el botón sea presionado.

Te vuelvo a preguntar, ¿cómo lo habrías hecho tú? ¿hubieras ocupado menos líneas de código?

Código fuente

Aquí puedes descargar una copia del código fuente de ambos ejemplos y de la clase Blink, y aquí puedes ver el desarrollo del mismo en Git.

Por cuestiones de funcionamiento de los sketches he tenido que duplicar el código. En cualquier otro sistema con una copia de la clase basta.

Ahora que si así lo deseas, copia la clase a la carpeta libraries/ de Arduino (no incluyas el ejemplo .ino).

Palabras finales

Recuerda que entre menos líneas de código tenga un programa, mejor, ya que será más fácil de analizar, depurar y modificar; en otras palabras, menos líneas de programa equivale a menos dolores de cabeza y más horas de sueño.

Espero que esta clase te sirva tanto como a mí y que me cuentes en los comentarios en cuáles situaciones la has ocupado o la ocuparías.


Si quieres saber más sobre la programación dirigida por eventos temporales en Arduino, revisa este artículo.

Si quieres saber más sobre la utilización del sistema operativo FreeRTOS con Arduino, revisa estos artículos:


Fiverr LinkedIn YouTube Instagram Facebook


¿Ya conoces mi curso gratuito de Arduino en tiempo real, utilizando el Arduino UNO o el Arduino Due y FreeRTOS?


¿Te gustaría ser de los primeros en recibir las actualizaciones a mi blog? ¡Suscríbete, es gratis!


Fco. Javier Rodríguez
Escrito por:

Fco. Javier Rodríguez

Soy Ingeniero Electrónico con 20+ años de experiencia en el diseño y desarrollo de productos electrónicos de consumo y a medida, y 12+ años como profesor. Egresado de la UNAM, también tengo el grado de Maestro en Ingeniería por la misma universidad. Mi perfil completo lo puede encontrar en: https://www.linkedin.com/in/fjrg76-dot-com/

Ver todas las entradas

1 COMENTARIO