Gestión de interrupciones con Arduino

Interrupciones en Arduino


Cuando se diseña un programa, lo habitual es pensarlo como un flujo de instrucciones que se ejecutan una detrás de otra siguiendo una ejecución secuencial. En el contexto de los microprocesadores y los microcontroladores, si estuviéramos limitados sólo a este modelo secuencial, el desarrollo de aplicaciones sería más complicado. Os pongo un ejemplo muy simplificado: si el sistema que estamos diseñando tiene que atender una serie de actuadores (por ejemplo un botón), tendremos que andar sondeándolos periódicamente para ver si se pulsó el botón. ¿Pero qué ocurre si se pulsa un botón mientras el programa anda ocupado en otra tarea y justo en ese momento no se está sondeando la entrada del actuador? En este caso el sistema no será capaz de detectar la pulsación. La solución en este caso es el uso de interrupciones. Cuándo se dispara una interrupción, el procesador detiene la ejecución del programa que está corriendo, almacena el estado de los registros (incluido el contador de programa), y ejecuta un subprograma, llamado rutina de servicio de interrupción, que atiende la interrupción (en este caso la gestión de la pulsación del botón). Una vez finalizada la rutina, el microprocesador restaura los registros y continúa la ejecución del programa principal por el mismo lugar en el que fue interrumpido. Vamos a ver cómo podemos crear una rutina de interrupción en Arduino y hacer que se ejecute tras la pulsación del botón.

La siguiente figura muestra el esquema del circuito que vamos a usar. Como se puede observar, el circuito no puede ser más simple. Se trata de un botón conectado al puerto del pin 2 del Arduino y un LED conectado al puerto del pin 13 de Arduino.

Circuito interrupcion Arduino

El objetivo es hacer un programa en Arduino que encienda el LED cuando se pulse el botón. El siguiente código realiza esta acción sin usar interrupciones, es decir, a la manera clásica.

const int pin = 2;
const int pout = 13;

int estadoBoton;
 
void setup() {
  pinMode(pin, INPUT);
  pinMode(pout, OUTPUT);
}

void loop() {
  estadoBoton = digitalRead(pin);

  if (estadoBoton == HIGH) {
    digitalWrite(pout, HIGH);
  } else {
    digitalWrite(pout, LOW);
  }
}

El código es bastante simple: se configura el puerto 2 como entrada (aquí se conecta el botón) y el 13 como salida (aquí se conecta el LED). Dentro del bucle loop leemos constantemente el valor del puerto al que está conectado el botón y si está pulsado encendemos el LED. En caso contrario se apaga. Vamos a hacer algunos cambios para que el programa realice la misma función, pero usando interrupciones.

const int pin = 2;
const int pout = 13;

int estadoBoton;
 
void setup() {
  pinMode(pin, INPUT);
  pinMode(pout, OUTPUT);
  attachInterrupt(digitalPinToInterrupt(pin), enciendeLED, CHANGE);
}
 
void loop() {
  // nada que hacer aquí. circulen.
}
 
void enciendeLED() {
  estadoBoton = digitalRead(pin);

  if (estadoBoton == HIGH) {
    digitalWrite(pout, HIGH);
  } else {
    digitalWrite(pout, LOW);
  }
}

Lo primero que nos llama la atención es que en el bucle principal (función loop) no hay código. Centrémonos en la siguiente línea de código.

attachInterrupt(digitalPinToInterrupt(pin), enciendeLED, CHANGE);

La función attachInterrupt() es la encargada de asociar un evento de interrupción con una rutina de servicio de interrupción. El primer parámetro es la interrupción que vamos a tratar. Con digitalPinToInterrupt convertimos una entrada (un pin) en una interrupción. En este caso estamos creando una interrupción asociada a la pulsación del botón que está en el pin 2. El segundo parámetro permite indicar cuál es la rutina de servicio de interrupción, o lo que es lo mismo, la función que se invoca cuando se dispara dicha interrupción. Por último, podemos indicar cuándo se activa la interrupción. Las opciones son:

  • CHANGE: La interrupción se activa cuando cambia el estado del pin.
  • LOW: La interrupción se activa cuando el pin está a nivel bajo.
  • RISING: La interrupción se activa cuando el pin pasa de nivel bajo a alto.
  • FALLING: La interrupción se activa cuando el pin pasa de nivel alto a bajo.
  • HIGH: La interrupción se activa cuando el pin está en nivel alto. Sólo funciona en Arduino Due, Zero y MKR1000.

Si pruebas este código observarás que el LED se enciende tal y como esperábamos. Sin embargo, si pruebas a conectar el botón a otro pin, digamos el A0, comprobarás que, aunque modifiques el código para usar este pin, el código no funciona ¿por qué?

Básicamente porque aunque el microcontrolador soporta interrupciones en todos sus pines de entrada y salida, la plataforma Arduino sólo lo implementa en algunos de ellos. En concreto en los siguientes pines según el modelo de Arduino:

  • Uno, Nano y Mini : pines 2 y 3
  • Mega (todos los modelos): 2, 3, 18, 19, 20, 21
  • Micro, Leonardo: 0, 1, 2, 3, 7
  • Zero: todos los pines excepto el 4
  • MKR1000: 0, 1, 4, 5, 6, 7, 8, 9, A1, A2
  • Due: todos

¿Quiere eso decir que sólo podemos gestionar dos botones en un Arduino Uno? Para nada. Tenemos dos opciones. La primera es acceder a los registros del microcontrolador a bajo nivel, lo cuál puede llegar a ser algo enrevesado según lo que queramos hacer. La segunda opción es usar una librería externa. Hay varias, en este caso vamos a usar PinChangeInterrupt, que podemos instalar fácilmente desde el propio entorno de Arduino. Para ello accedemos a Programa > Incluir librería > Gestionar librería.

Buscamos pinchangeinterrupt y pulsamos el botón instalar.

Una vez instalada ya podemos usar interrupciones en cualquier pin usando la librería. El siguiente código hace los mismo que en anterior pero usando el pin A0.

#include <PinChangeInterrupt.h>

const int pin = A0;
const int pout = 13;

int estadoBoton;
 
void setup() {
  pinMode(pin, INPUT);
  pinMode(pout, OUTPUT);
  attachPCINT(digitalPinToPCINT(pin), enciendeLED, CHANGE);
}
 
void loop() {
  // nada que hacer aquí. circulen.
}
 
void enciendeLED() {
  estadoBoton = digitalRead(pin);

  if (estadoBoton == HIGH) {
    digitalWrite(pout, HIGH);
  } else {
    digitalWrite(pout, LOW);
  }
}

El código es casi idéntico salvo por la primera línea, que incluye las cabeceras de la librería. Y también la línea siguiente, que configura la interrupción.

attachPCINT(digitalPinToPCINT(pin), enciendeLED, CHANGE);

El formato de la función attachPCINT es igual a la de la función attachInterrupt que ya hemos visto más arriba. En el vídeo siguiente podemos ver el código anterior funcionando usando el pin A0.

Sé el primero en comentar en "Gestión de interrupciones con Arduino"

Deja un comentario.

Tu dirección de correo no será publicada.


*