Tutorial de Funciones de Interrupción en Arduino

Interrupciones Arduino

¿Qué son las interrupciones de Arduino? ¿Cómo se usan? ¿Qué deberías saber sobre ellos?

En este tutorial de Interrupciones Arduino te mostraré un ejemplo de cuando puedes usar las interrupciones y como manejarlas. También te daré una lista de puntos importantes a los que debes prestar atención, porque, como verás, las interrupciones son algo que debes manejar con cuidado.

¿Qué es un pin de interrupción?

Una analogía de la vida real

Ejemplo 1

Usemos una analogía de la vida real. Imagina que estás esperando un correo electrónico importante. No sabes cuándo llegará, pero quieres asegurarte de que lo lees tan pronto como llegue a tu buzón.

La solución más básica es revisar frecuentemente tu buzón – digamos, cada 5 minutos – para asegurarte de que el máximo retraso entre la recepción del correo electrónico y su lectura sea de 5 minutos. Pero esta no es realmente una solución ideal. Primero, pasarás todo el tiempo refrescando tu buzón y no harás nada productivo mientras tanto. Y segundo, esto es relativamente ineficiente. Cuando llegue el correo electrónico, tendrás hasta 5 minutos de retraso antes de leerlo.

Esta técnica se llama «polling». En una frecuencia dada, estás sondeando el estado de algo para ver si llegó una nueva información. A escala humana se ve que no vale la pena.

La otra forma posible de hacerlo es usar interrupciones. Para nosotros los humanos, esto significa activar las notificaciones. Tan pronto como el correo electrónico haya llegado, aparecerá una ventana emergente en tu teléfono/ordenador diciendo que el correo electrónico está aquí. Ahora puedes revisar tu correo electrónico, y el retraso entre la recepción y la lectura del correo electrónico es básicamente cero.

Agreguemos más detalles a esta analogía: el correo electrónico que estás a punto de recibir contiene una oferta especial para obtener un descuento en un sitio web determinado – y esta oferta está disponible sólo por 2 minutos. Si utilizas la técnica de «sondeo», existe la posibilidad de que se te pasen algunos datos (en este ejemplo, te perderás el descuento). Con las interrupciones, puedes estar seguro de que no te lo perderás.

Ejemplo 2

Otro ejemplo: estás esperando para hablar con el cartero sobre algo. Ahora llegará entre las 9 y las 11 de la mañana. La primera opción, el sondeo, puedes seguir yendo a tu puerta para comprobar si ha llegado. Pero tal vez no lo verás, porque no siempre puedes estar en tu ventana mirando a la calle.

Segunda opción – interrupciones – pones una nota en tu puerta diciendo «Estimado Sr. Cartero, por favor toque el timbre cuando vea esto». Tan pronto como llegue el cartero, tocará el timbre y podrás hablar con él y no perderás su visita.

En ambos escenarios, detienes tu acción actual. Por eso se llama interrupción. Tienes que detener lo que estás haciendo para manejar la interrupción, y sólo después de que hayas terminado con ella, puedes reanudar tu acción.

Interrupciones en Arduino

Arduino interrumpe el trabajo de forma similar.

Por ejemplo, si se espera a que un usuario presione un botón, se puede monitorear el botón a una alta frecuencia, o usar interrupciones. Con las interrupciones, estás seguro de que no se te pasará el disparador.

El monitoreo de las interrupciones de Arduino se hace por hardware, no por software. Tan pronto como se presiona el botón, la señal del hardware en el pin activa una función dentro del código de Arduino. Esto detiene la ejecución del programa. Después de que la función disparada se hace, la ejecución principal se reanuda.

Ten en cuenta que para las analogías de la vida real anteriores, las interrupciones tienen mucho más sentido que la técnica de sondeo. Sin embargo, quiero señalar que a veces, el sondeo puede ser una mejor opción. A escala humana, las interrupciones tienen mucho más sentido. A escala de microcontrolador, donde la frecuencia de ejecución es mucho mayor, a veces se complica más que eso y la elección no siempre es obvia. Discutiremos más sobre esto más adelante en este post.

Características importantes

Aquí hay algunas características importantes sobre las interrupciones:

  • Las interrupciones pueden venir de varias fuentes. En este caso, estamos usando una interrupción de hardware que se activa por un cambio de estado en uno de los pines digitales.
  • La mayoría de los diseños de Arduino tienen dos interrupciones de hardware (denominadas «interrupción 0» e «interrupción 1») conectadas por hardware a los pines digitales de E/S 2 y 3, respectivamente.
  • El Arduino Mega tiene seis interrupciones de hardware incluyendo las interrupciones adicionales («interrupción2» hasta «interrupción5») en los pines 21, 20, 19 y 18.
  • Se puede definir una rutina usando una función especial llamada «Rutina de Servicio de Interrupción» (normalmente conocida como ISR).
  • Puede definir la rutina y especificar condiciones en el flanco ascendente, en el flanco descendente o en ambos. En estas condiciones específicas, la interrupción sería atendida.
  • Es posible hacer que esa función se ejecute automáticamente, cada vez que ocurra un evento en una clavija de entrada.

Pines de interrupción en Arduino

Los pines de interrupción de Arduino están usando pines digitales. Sin embargo, normalmente no se pueden usar todos los pines digitales disponibles. Sólo algunos de ellos tienen la funcionalidad habilitada.

Estos son los pines que puedes usar para las interrupciones en las placas principales de Arduino:

  • Arduino Uno, Nano, Mini: pines 2, 3
  • Arduino Mega: pines 2, 3, 18, 19, 20, 21
  • Arduino Micro, Leonardo: pines 0, 1, 2, 3, 7
  • Arduino Zero: Todos los pines digitales excepto el 4
  • Arduino Due: Todos los pines digitales

Funciones

interrupts()

  • Esta función vuelve a habilitar las interrupciones después de que hayan sido deshabilitadas por noInterrupts().

noInterrupts()

  • Esta función desactiva las interrupciones.
  • Las interrupciones se pueden volver a habilitar utilizando interrupts().

attachInterrupt(digitalPinToInterrupt(pin), ISR, mode)

  • El primer parámetro para attachInterrupt es el número de pin.
  • digitalPinToInterrupt(pin) debe utilizarse para especificar el pin en el que se utiliza la interrupción externa.
    pin es el número de pin al que se debe habilitar la funcionalidad de interrupción externa.
    Para Arduino UNO, sólo se pueden utilizar los pines 2 y 3 para las interrupciones externas.
  • ISR : El ISR al que se llama cuando ocurre un evento de interrupción. Esta función no debe tomar ningún parámetro y no debe devolver nada.
  • Mode Decide qué evento debe causar una interrupción. Hay 4 opciones disponibles para seleccionar.
    • LOW Interrupción generada cuando el pin está BAJO.
    • CHANGE Interrupción generada cuando el pin cambia de valor.
    • FALLING Interrupción generada cuando el pin cambia de ALTO a BAJO.
    • RISING   Interrupción generada cuando el pin cambia de BAJO a ALTO.

detachInterrupt(digitalPinToInterrupt(pin))

  • Desactiva la interrupción especificada por el digitalPinToInterrupt(pin)

Encendido y apagado de interrupciones

/* Interrupt Functions */

/* Setup is run once at the start (Power-On or Reset) of sketch */
void setup()
{

}

/* Loop runs over and over after the startup function */
void loop()
{
noInterrupts();
/* critical, time-sensitive code here */
interrupts();
/* other code here */
}

Interrupciones de Arduino, Ejemplo de código

Para este tutorial usaremos un ejemplo básico. El objetivo del programa es cambiar el estado de un LED cuando el usuario presiona un botón.

Esquema

esquema de conexiones

Nota que estamos usando la clavija 3 para el botón. Como ya se ha dicho, en Arduino Uno sólo se pueden usar los pines 2 y 3 para las interrupciones. Presta atención cuando tengas que elegir un pin para una interrupción. Si el pin no es compatible con las interrupciones, tu programa no funcionará (pero aún así compila), y pasarás bastante tiempo rascándote la cabeza mientras intentas encontrar una solución.

Tipos de interrupciones

Las interrupciones de Arduino se activan cuando hay un cambio en la señal digital que se quiere monitorizar. Pero puedes elegir exactamente lo que quieres monitorizar. Para ello tendrás que modificar el 3er parámetro de la función attachInterrupt():

  • RISING: La interrupción se disparará cuando la señal pase de LOW a HIGH
  • FALLING: La interrupción se activará cuando la señal pase de HIGH a LOW.
  • CHANGE: La interrupción se disparará cuando la señal cambie (LOW a HIGH o HIGH a LOW)
  • LOW: La interrupción se activará cuando la señal sea LOW

diagrama interrupciones Arduino

Prácticamente hablando, podrías controlar cuando el usuario presiona los botones, o cuando suelta el botón, o ambos.

Si has añadido una resistencia de pulsación al botón – lo que significa que su estado normal es LOW – entonces monitorear cuando se presiona significa que tienes que usar RISING. Si has añadido una resistencia de pull-up, el estado del botón ya es HIGH, y tienes que usar FALLING para monitorear cuando se presiona (conectado al suelo).

Código de Arduino sin interrupciones

#define LED_PIN 9
#define BUTTON_PIN 3

byte ledState = LOW;

void setup() {
  pinMode(LED_PIN, OUTPUT);
  pinMode(BUTTON_PIN, INPUT);
}

void loop() {
  if (digitalRead(BUTTON_PIN), HIGH) {
    ledState = !ledState;
  }

  digitalWrite(LED_PIN, ledState);
}

No hay nada realmente nuevo aquí. Inicializamos el pin del LED como OUTPUT y el pin del botón como INPUT. En el bucle() monitoreamos el estado del botón y modificamos el estado del LED en consecuencia. Tengan en cuenta que para simplificar, no he usado un rebote en el botón.

Código de Arduino con interrupciones

#define LED_PIN 9
#define BUTTON_PIN 3

volatile byte ledState = LOW;

void setup() {
  pinMode(LED_PIN, OUTPUT);
  pinMode(BUTTON_PIN, INPUT);

  attachInterrupt(digitalPinToInterrupt(BUTTON_PIN), blinkLed, RISING);
}

void loop() {
  // nothing here!
}

void blinkLed() {
  ledState = !ledState;
  digitalWrite(LED_PIN, ledState);
}

Aquí cambiamos la forma en que estamos monitoreando el botón. En lugar de sondear sus estados, ahora hay una función de interrupción conectada a la clavija. Cuando la señal en el pin del botón se eleva – lo que significa que va de LOW (BAJA) a HIGH (ALTA), la ejecución del programa actual – función loop() – se detendrá y se llamará a la función blinkLed(). Una vez que blinkLed() haya terminado, el loop() puede continuar.

Aquí, la principal ventaja que se obtiene es que no hay más sondeo para el botón en la función loop(). Tan pronto como se presione el botón, se llamará a blinkLed(), y no necesitas preocuparte por ello en el bucle().

Como habrás notado, usamos la palabra clave «volátil» delante de la variable ledState. Más tarde te explicaré en este post por qué necesitamos eso.

Tienes que usar la función attachInterrupt() para adjuntar una función a un pin de interrupción. Esta función toma 3 parámetros: el pin de interrupción, la función a llamar y el tipo de interrupción.

Cinco cosas que debes saber sobre las interrupciones de Arduino

Realiza las interrupciones rápido

Como puedes adivinar, debes hacer la función de interrupción lo más rápido posible, porque detiene la ejecución principal de tu programa. No puedes hacer cálculos pesados. Además, sólo se puede manejar una interrupción a la vez.

Lo que recomiendo es que sólo cambies las variables de estado dentro de las funciones de interrupción. En el bucle principal, buscas esas variables de estado y haces cualquier cálculo o acción requerida.

Digamos que quieres mover un motor, y esta acción se desencadena por una interrupción. En este caso, podrías tener una variable llamada «shouldMoveMotor» que se establece como «true» en la función de interrupción.

En tu programa principal, compruebas el estado de «shouldMoveMotor». Cuando es verdadero, empiezas a mover el motor.

#define BUTTON_PIN 3

volatile bool shouldMoveMotor = false;

void setup() {
  pinMode(BUTTON_PIN, INPUT);
  attachInterrupt(digitalPinToInterrupt(BUTTON_PIN), triggerMoveMotor, RISING);
}

void loop() {
  if (shouldMoveMotor) {
    shouldMoveMotor = false;
    moveMotor();
  }
}

void triggerMoveMotor() {
  shouldMoveMotor = true;
}

void moveMotor() {
  // this function may contains code that
  // requires heavy computation, or takes
  // a long time to execute
}

Y puedes hacer exactamente lo mismo para un cálculo pesado, por ejemplo si el cálculo toma más de unos pocos microsegundos para completarse.

Si no mantienes las interrupciones rápidamente, podrías perder importantes plazos en tu código. En el caso de un robot móvil con dos ruedas, eso puede hacer que el movimiento del motor sea brusco. Para la comunicación entre dispositivos, puede que se pierdan algunos datos, etc.

Cuando necesitas lidiar con restricciones en tiempo real, esta regla se vuelve aún más importante.

Funcionalidades de tiempo e interrupciones

Una regla básica: no uses las funciones de tiempo en tus interrupciones. Aquí hay más detalles sobre las 4 principales funciones de tiempo:

  • millis(): esto devolverá el tiempo transcurrido desde que el programa de Arduino se ha iniciado, en milisegundos. Esta función se basa en algunas otras interrupciones para contar, y como estás dentro de una interrupción, otras interrupciones no se están ejecutando. Por lo tanto, si usas millis(), obtendrás el último valor almacenado, que será correcto, pero cuando estés dentro de la función de interrupción, el valor de millis() nunca aumentará.
  • delay(): esta simplemente no funcionará, ya que también depende de las interrupciones. Además, incluso si fuera posible, no deberías usarla porque ahora sabes que tienes que mantener las interrupciones muy rápido.
  • micros(): esta función es la misma que millis(), pero devuelve el tiempo en microsegundos. Sin embargo, al contrario que millis(), micros() funcionará al principio de una interrupción. Pero después de 1 o 2
  • milisegundos, el comportamiento no será exacto y puede tener una deriva permanente cada vez que use micros() después. De nuevo, el consejo es el mismo: haz tus interrupciones cortas y rápidas.
  • delayMicroseconds(): este funcionará como siempre, pero no lo uses. Como has visto antes, hay demasiadas cosas que pueden salir mal si te quedas demasiado tiempo en una interrupción.

Con todo, deberías evitar usar esas funciones.

Tal vez usar millis() o micros() puede ser útil a veces, si quieres hacer una comparación de la duración (por ejemplo para rebotar un botón). Pero también puedes hacerlo en tu código, usando la interrupción sólo para notificar un cambio en el estado de la señal monitorizada.

No uses la biblioteca de serie dentro de las interrupciones

La biblioteca de serie es muy útil para depurar y comunicar entre tu placa Arduino y otra placa o dispositivo. Pero no es muy adecuada para las funciones de interrupción.

Cuando estás dentro de una interrupción, los datos Seriales recibidos pueden perderse. Por lo tanto, no es una buena idea usar las funciones de lectura del Serial. También si haces la interrupción demasiado larga, y lees desde el Serial después de eso en tu código principal, todavía puedes haber perdido algunas partes de los datos.

Puedes usar Serial.print() dentro de una interrupción para depurar, por ejemplo si no estás seguro de cuándo se activa la interrupción. Pero también tiene su propia fuente de problemas.

La mejor manera de imprimir algo de una interrupción, es simplemente poner una señal dentro de la interrupción, y sondear esta bandera dentro del programa principal loop(). Cuando la señal se enciende, se imprime algo, y se apaga la señal. Hacer eso te ahorrará posibles dolores de cabeza.

Variables volátiles

Si modificas una variable dentro de una interrupción, entonces debes declarar esta variable como volátil.

El compilador hace muchas cosas para optimizar el código y la velocidad del programa. Esto es algo bueno, pero aquí tenemos que decirle que «disminuya la velocidad» en la optimización.

Por ejemplo, si el compilador ve una declaración de variable, pero la variable no se usa en ninguna parte del código (excepto en las interrupciones), puede eliminar esa variable. Con una variable volátil estás seguro de que no ocurrirá, la variable se almacenará de todos modos.

Además, cuando usas volátil le dice al controlador que recargue la variable siempre que sea referenciada. A veces el compilador usará copias de las variables para ir más rápido. Aquí quieres asegurarte de que cada vez que accedas/modifiques la variable, ya sea en el programa principal o dentro de una interrupción, obtengas la variable real y no una copia.

Ten en cuenta que sólo las variables que se usan dentro y fuera de una interrupción deben ser declaradas como volátiles. No querrás ralentizar innecesariamente tu código.
Los parámetros de las interrupciones y el valor devuelto

Una función de interrupción no puede tomar ningún parámetro, y no devuelve ningún valor. Básicamente si tuvieras que escribir un prototipo para una interrupción, esto sería algo como la función de interrupción de vacío (interruptFunction).

Por lo tanto, la única manera de compartir datos con el programa principal es a través de variables volátiles globales. En una interrupción también puedes obtener y establecer datos de los pines del hardware, siempre y cuando mantengas el programa corto. Por ejemplo, el uso de digitalRead() o digitalWrite() puede estar bien si no se abusa de él.

Conclusión

Las interrupciones de Arduino son muy útiles cuando quieres asegurarte de no perder ningún cambio en la señal que monitorizas (en un pin digital principalmente).

Sin embargo, durante este post habras visto que hay muchas reglas y limitaciones al usar las interrupciones. Esto es algo que debes manejar con cuidado, y no usar demasiado. A veces, el uso de peticiones simples puede ser más apropiado, si por ejemplo logras escribir un eficiente y determinante programa multitarea de Arduino.

Las interrupciones también pueden ser usadas sólo para activar una señal o bandera, y sigue usando la técnica de sondeo dentro de su bucle principal() – pero esta vez, en lugar de monitorear el pin de hardware, monitoreas la señal de software.

La solución principal para ti, si quieres usar interrupciones en tu código: mantén tus interrupciones cortas. Así evitarás muchos problemas innecesarios y difíciles de depurar.

Pin It on Pinterest

Shares