Interrupciones de Hardware y software en Arduino

Una cosa bastante útil de la que me gustaría hablar son las Interrupciones en Arduino. Una interrupción es una señal ya sea de Hardware o Software que hace que nuestro Arduino se salga del programa principal y ejecute una función indicada.

Antes de empezar con ello, hay una serie de consideraciones importantes a tener en cuenta:

  • Tal y como he indicado antes, las interrupciones cortan el programa principal y lo que se esté haciendo en ese momento, por lo que no se recomienda abusar de ellas.
  • No se pueden solapar interrupciones en Arduino, por lo que si hay una interrupción en proceso todas las demás se ignorarán.
  • No se pueden usar funciones como Serial, delay, milis… ni otras que dependan de interrupciones o timers.
  • Si tu programa es muy dependiente de funciones como delay o milis debes de tener cuidado, ya que dichos contadores se paralizan durante la ejecución de la interrupción.
  • Sabiendo lo de arriba, es importante terminar la interrupción lo antes posible.
  • La función ejecutada en una interrupción no soporta argumentos o variables de retorno.
  • Es recomendable siempre definir las variables globales que vayamos a usar como volatile, ya que en caso contrario podrían ocurrir problemas extraños.

Para que nos sirven

Posiblemente te preguntarás que si tiene tantas desventajas, ¿para qué nos puede servir?. La respuesta como siempre es para lo que tu creas que te puede servir.

Algo para lo que es útil es por ejemplo para un evento que no puede esperar y se necesita reaccionar lo más rápido posible para que no se pierda. Supongamos que estás esperando por que se pulse un botón. En este caso tienes la opción de escanear el pin cada vez por tres para detectar la pulsación y rezar para que el controlador no esté ocupado en el momento que se pulse, o puedes crear una interrupción que se active al momento de pulsarse.

Tipos de interrupciones

A la hora de programar interrupciones en Arduino, disponemos de tres tipos de eventos a los cuales reaccionaran. Estos eventos son los siguientes:

  • Hardware, como por ejemplo el cambio de estado de un pin.
  • Software (Arduino sólo soporta la librería Timer)

Evento de Hardware

El primer disparador o trigger del que hablaremos, será el evento de Hardware. Tal y como el nombre indica, se produce cuando hay algún cambio en el Hardware, y más concretamente, en el estado de un Pin de Arduino.

Sólo se puede configurar un tipo de interrupción por pin, o por lo menos cuando yo ponía dos juntas no hacía caso a la primera.

Por supuesto no todos los hardwares son iguales, y los pines de los que dispondremos para interrupciones dependerán de cada Arduino. En el siguiente cuadro podemos observar una lista de los modelos, los números de interrupciones, y los pines a los que corresponden:

Modelo INT0 INT1 INT2 INT3 INT4 INT5
UNOPin 2Pin 3
MegaPin 2Pin 3Pin 21Pin 20Pin 19Pin 18
DUETodos
LeonardoPin 3Pin 2Pin 0Pin 1Pin 7

Pero tranquilo, todavía no te asustes pensando en tener que aprenderte la tabla. También puede usar la función digitalPinToInterrupt para recuperar el número de interrupción correspondiente al pin:

attachInterrupt(digitalPinToInterrupt(pin), ISR, mode)

Una vez sabido de qué pines disponemos para hacer nuestras interrupciones, nos falta saber qué condiciones harán dispararse las interrupciones, y en el caso de los pines, disponemos de las siguientes condiciones:

  • LOW: La interrupción salta cuando el pin está en estado LOW
  • HIGH: La interrupción salta cuando el pin está en estado HIGH (no te lo esperabas, ¿verdad? :P), por desgracia este trigger sólo está disponible en Arduino DUE.
  • CHANGE: La interrupción salta cuando el pin cambia de estado, ya sea LOW a HIGH, o viceversa.
  • RISING: La interrupción salta cuando el pin pasa de LOW a HIGH.
  • FALLING: Esta es la inversa de la de arriba, y por lo tanto salta cuando el pin pasa de HIGH a LOW.

Para que se entienda mejor, no hay nada como un pequeño ejemplo de código:

int contador = 0;
int n = contador;
double lastpush = 0;

void setup() {
  Serial.begin(9600);
  attachInterrupt(digitalPinToInterrupt(3), ServicioBoton, FALLING);
}
void loop() {
  if (n != contador)
  {
    Serial.println(contador);
    n = contador;
  }
}

void ServicioBoton() {
  if (millis() > lastpush+100) {
    contador++;
    lastpush = millis();
  }
}

En el código de arriba se configura el pin digital 2 como interrupción (interrupción 0), y se reacciona a la pulsación sumando uno al contador. Debido al efecto rebote del pulsador, la interrupción se ejecuta a veces en múltiples ocasiones, y para evitarlo le añadimos un delay de 100ms.

Sé que estaréis pensando «pero no dijiste que no se puede usar millis en una interrupción«, y así es, poder no se puede usar para temporizar o calcular tiempo porque no avanza, pero si se puede recuperar el valor que tiene en ese momento.

Interrupciones de Software

Otros microcontroladores disponen de distintos tipos de interrupciones de Software, pero en el caso de Arduino sólo disponemos de una: el temporizador.

Este tipo de interrupciones, se tiene que usar con cuidado al ser ejecutadas de forma continuada, ya que podrían entrar en un bucle infinito si tardase más en ejecutarse que el tiempo entre ejecuciones. Además, dependiendo del temporizador usado, afectaremos a unas funciones u otras de nuestro microcontrolador, por lo que es muy importante tener cuidado. Por ejemplo, el timer1 podría afectar al control de PWM, AnalogWrite y el control de los servos.

Al igual que las interrupciones por hardware, se recomienda que sean lo más breves posibles para evitar afectar al funcionamiento del programa principal.

Se pueden programar directamente los registros internos para crear temporizadores, pero para no complicarnos usaremos la librería TimerOne. Esta librería no está disponible en el programa de Arduino, pero la página oficial nos ofrece la posibilidad de descargarla, al igual que el gestor de plugins. Para instalarla seguiremos estos pasos:

  • Entramos en el menú Herramientas -> Administrar bibliotecas
  • Buscamos la librería TimerOne
  • Pulsamos el botón de instalar

Una vez instalada ya podremos empezar a usarla, y como ejemplo, os dejo este código en el que simplemente se enciende y apaga el led de la placada cada medio segundo.

#include <TimerOne.h>

void setup() {
  // Inicializamos el pin 13 como salida
  pinMode(LED_BUILTIN, OUTPUT);
  // Nos aseguramos que al comenzar el led esté apagado
  digitalWrite(LED_BUILTIN, LOW);
  
  // Inicializamos Timer1 para que ejecute cada 0,5 segundos (dos veces por segundo)
  Timer1.initialize(500000);
  // Ejecutar por cada interrupción la funcion parpadearLED()
  Timer1.attachInterrupt(parpadearLED);
}

void loop() {
}

// Usamos esta variable global para guardar el estado del led (apagado inicialmente)
bool estado = false;

void parpadearLED() {
  if (estado) {
    digitalWrite(LED_BUILTIN, LOW);
    estado = false;
  }
  else {
    digitalWrite(LED_BUILTIN, HIGH);
    estado = true;
  }
}

En el código de arriba creamos una interrupción en el Timer1 que se ejecute cada 500.000µs, y le añadimos la función parpadearLED. Esto simplemente hace que el led parpadee cada segundo.

Notas

Para finalizar, un par de notas acerca de las interrupciones:

  • Podemos utilizar las funciones noInterrupts() y Interrupts() para desactivar y reactivar las interrupciones
  • Si queremos dejar de usar una interrupción, podemos desactivarla con la función detachInterrupt(num Interrupt)

Como siempre, espero que os sirva, y si deseáis ver más información pasaros por la página oficial.

¡Un saludo!

Deja un comentario

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.