Interrupciones en Arduino con AttachInterrupt

Las interrupciones son un mecanismo muy importante de Arduino que permite a los dispositivos externos interactuar con el controlador cuando ocurren diferentes eventos.

Instalando el controlador de interrupciones de hardware en el sketch, podremos responder a la activación o desactivación de un botón, al pulsar el teclado, el ratón, los tics del temporizador RTC, obtener nuevos datos en UART, I2C o SPI.

En este artículo aprenderemos cómo funcionan las interrupciones en las placas Arduino Uno, Mega o Nano y daremos un ejemplo del uso de la función attachInterrupt() de Arduino.

Interrupciones en Arduino

Una interrupción es una señal que indica al procesador cuándo ocurre un evento que requiere atención inmediata. El procesador debe responder a esta señal interrumpiendo las instrucciones actuales y pasando el control al controlador de interrupciones (ISR, Interrupt Service Routine). El controlador es una función común que escribimos nosotros mismos y colocamos allí el código que debe responder al evento.

Flujo del programa con interrupción ISR en Arduino

Después de la interrupción del servicio ISR, la función termina y el procesador continúa con las instrucciones interrumpidas – continúa ejecutando el código desde el punto donde fue detenido. Todo esto sucede automáticamente, por lo que nuestra tarea es sólo escribir el controlador de interrupciones, sin complicarnos demasiado.

Tendrás que entender el esquema, el funcionamiento de los dispositivos conectados, y la frecuencia con la que se pueden desencadenar las interrupciones y las peculiaridades de las mismas. Todo esto es la principal dificultad del uso de las interrupciones.

Interrupciones arduino de hardware y software

Las interrupciones en Arduino pueden ser divididas en varios tipos:

  • Interrupciones de hardware: Interrupción a nivel de arquitectura del microprocesador. El evento en sí puede ser un momento productivo desde un dispositivo externo, por ejemplo, al pulsar un botón del teclado, al mover el ratón de un ordenador, etc.
  • Interrupciones de software: Se ejecutan dentro del programa utilizando instrucciones especiales. Se utilizan para llamar al controlador de interrupciones (ISR).
  • Interrupciones internas (síncronas) . Una interrupción interna ocurre como resultado de un cambio, violación o error en la ejecución del programa (por ejemplo, cuando se accede a una dirección no válida, código de operación no válido, etc.).

Por qué necesitamos interrupciones de hardware

Las interrupciones son útiles en los programas de Arduino porque ayudan a resolver problemas de sincronización. Por ejemplo, cuando se trabaja con interrupciones UART, no se permite hacer un seguimiento de la llegada de cada carácter. Un dispositivo de hardware externo envía una señal de interrupción, el procesador llama inmediatamente al manejador de interrupciones, que captura el símbolo en el tiempo. Esto ahorra tiempo de CPU, que se gastaría en comprobar el estado de UART sin interrupciones, en su lugar todas las acciones necesarias son realizadas por el manejador de interrupciones, sin afectar al programa principal. No se requieren características especiales del dispositivo de hardware.

Las principales razones por las que se deben desencadenar las interrupciones son las siguientes

  • Determinación del cambio de estado de salida;
  • Interrupción del temporizador;
  • Interrupciones a SPI, I2C, USART;
  • Conversión analógico-digital;
  • EEPROM lista para usar, memoria flash.

Cómo se implementan las interrupciones en Arduino

Cuando se recibe una señal de interrupción, la operación en el bucle loop() se detiene. Se inicia la ejecución de la función, que se declara ejecutada durante la interrupción. La función declarada no puede aceptar valores de entrada y valores de retorno al final de la operación. El código en sí mismo en el bucle principal del programa no se ve afectado por la interrupción. La función estándar attachInterrupt() se utiliza para trabajar con interrupciones en Arduino.

Diferencia en la implementación de interrupciones en diferentes placas Arduino

pines de interrupcion por modelo de arduino

Dependiendo de la implementación de hardware de un modelo particular de Arduino, hay varias interrupciones posibles. La placa Arduino Uno tiene 2 interrupciones en el segundo y tercer pin, pero si se necesitan más de dos salidas, la placa soporta un modo especial de «cambio de pin». Este modo funciona cambiando la entrada de todos los pines.

La diferencia del modo de interrupción al cambiar la entrada es que las interrupciones se pueden generar en cualquiera de los ocho pines. El procesamiento en este caso será más complicado y más largo, ya que tendrá que controlar el último estado de cada uno de los contactos.

Otras placas tienen un mayor número de interrupciones. Por ejemplo, la placa Arduino Mega 2560 tiene 6 pines que pueden manejar interrupciones externas y la Arduino Due puede hacerlo en todos sus pines. Para todas las tarjetas Arduino, cuando se trabaja con la función attachInterrupt (interrupción, función, modo), el argumento Inerrupt 0 está asociado al pin 2 digital.

Ejemplos de interrupciones en Arduino

Ahora pasemos a la práctica y hablemos de cómo utilizar las interrupciones en nuestros proyectos.

Sintaxis attachInterrupt()

La función attachInterrupt se utiliza para manejar las interrupciones. Se usa para conectar una interrupción externa al controlador.

Sintaxis de la llamada: attachInterrupt(interrupt, function, mode)

Argumentos de la función:

  • interrupt – número de la interrupción llamada (estándar 0 – para el 2º pin, para la placa Arduino Uno 1 – para el 3º pin),
  • function – el nombre de la función que se llamará en la interrupción (es importante que la función no acepte ni devuelva ningún valor),
  • mode – es una condición para activar una interrupción.

Se pueden ajustar las siguientes condiciones de funcionamiento:

  • LOW – se realiza en un nivel de señal bajo cuando el contacto es cero. La interrupción puede repetirse cíclicamente, por ejemplo, cuando se pulsa el botón.
  • CHANGE – la interrupción ocurre cuando la señal cambia de alta a baja o viceversa. Se realiza una vez en cualquier cambio de señal.
  • RISING – interrumpe una vez al cambiar la señal de LOW a HIGH.
  • FALLING – interrumpe una vez al cambiar la señal de HIGH a LOW.

Observaciones importantes

Al trabajar con interrupciones se deben tener en cuenta las siguientes limitaciones importantes:

  • La función del controlador no debe ser ejecutada por mucho tiempo. El punto es que Arduino no puede procesar varias interrupciones simultáneamente. Mientras se ejecuta la función de su controlador, todas las demás interrupciones serán ignoradas y es posible que se pierdan eventos importantes. Si necesitas hacer algo grande, simplemente pasa el procesamiento de eventos en el bucle principal(). En el controlador de bucle sólo se puede fijar un indicador de evento, y en el bucle se puede verificar el indicador y procesarlo.
  • Hay que tener mucho cuidado con las variables. El compilador inteligente de C++ puede «reoptimizar» su programa eliminando las variables que considere innecesarias. El compilador simplemente no verá que has establecido variables en una parte y las usas en otra. Para eliminar esta probabilidad en el caso de tipos de datos básicos, puedes utilizar la palabra reservada volatile, por ejemplo: volatile boolean state = 0. Pero este método no funcionará con estructuras de datos complejas.
  • No se recomienda utilizar un gran número de interrupciones (intenta no utilizar más de 6-8). Un gran número de varios eventos requieren una complicación compleja del código y, por lo tanto, conduce a errores. Además, debes entender que no hay precisión de ejecución en sistemas con gran cantidad de interrupciones – nunca entenderás exactamente cuál es el intervalo entre llamadas de comandos importantes.
  • No se puede usar delay() en los controladores. El mecanismo para determinar el intervalo de retardo utiliza temporizadores, y también trabajan en las interrupciones que su controlador bloqueará. Como resultado, todos estarán esperando a alguien y el programa se colgará. Por la misma razón, no puedes utilizar protocolos de comunicación basados en interrupciones (por ejemplo, i2c).

Ejemplos de cómo usar arduino attachInterrupt

Pongámonos a practicar y consideremos un ejemplo sencillo de cómo usar las interrupciones. En el ejemplo definimos una función de controlador que cambiará el estado del pin 13 al que tradicionalmente conectamos el LED cuando se cambia la señal en el pin 2 del Arduino Uno.

#define PIN_LED 13 

volatile boolean actionState = LOW;
void setup() {
pinMode(PIN_LED, OUTPUT);
// Establece una interrupción 
// La función myEventListener se activa cuando 
// en el pin 2 (la interrupción 0 está relacionada con el pin 2) 
// la señal cambia (no importa en qué dirección) 
attachInterrupt (0, myEventListener, CHANGE); 
} 

void loop () { 
// No hacemos nada en la función de bucle porque todo el código de gestión de eventos 
//estará en la función myEventListener.
} 

void myEventListener () { 
actionState! = actionState; // 
// Se realizan otras acciones, como encender o apagar el LED. 
digitalWrite (PIN_LED, actionState); 
}

Veamos algunos ejemplos de interrupciones más complejas y sus controladores: para el temporizador y los botones.

Interrupciones al presionar un botón: Efecto Rebote

En las interrupciones generadas al presionar un botón (push button) se suelen presentar problema de rebotes, ya que los contactos oscilarán, causando una serie de disparadores, antes de que entren en contacto entre sí al pulsar el botón. Hay dos maneras de combatir estos «ruidos» o «efecto rebote»: por hardware, es decir, soldando un capacitor al botón, o por software.

Puedes deshacerte de los ruidos usando la función milis, que permite registrar el tiempo transcurrido desde la primera operación del botón.

// cuando se presiona el botón 
if (digitalRead(2) == HIGH) {
// Si han pasado más de 100 milisegundos desde la presión anterior 
if (millis() - previousMillis >= 100) { 
// Se recuerda el tiempo del primer disparo 
    previousMillis = millis(); 
if (led == oldled) {// comprueba que el estado del botón no ha cambiado 
led =! led; 
}

Este código nos permite eliminar el rebote y no bloquea la ejecución del programa, como en el caso de la función delay, que es inaceptable en las interrupciones.

Interrupciones con Timer

Un temporizador es un contador que cuenta a alguna frecuencia derivada de los procesadores de 16 MHz. Es posible configurar el divisor de frecuencia para obtener el modo de conteo deseado. También es posible configurar el contador para generar interrupciones cuando se alcanza el punto de ajuste.

Las interrupciones mediante Timer permiten realizar interrupciones una vez por milisegundo. El Arduino tiene 3 temporizadores – Timer0, Timer1 y Timer2. El Timer0 se utiliza para generar interrupciones una vez por milisegundo, y el contador se actualiza y pasa a la función de milis (). Este temporizador es de ocho bits y cuenta de 0 a 255. Se genera una interrupción cuando se alcanza el valor de 255. Por defecto, el divisor del reloj es 65 para obtener una frecuencia cercana a 1 kHz.

Los registros de comparación se utilizan para comparar el estado del temporizador y los datos almacenados. En este ejemplo, el código generará una interrupción cuando el contador llegue a 0xAF.

OCR0A = 0xAF;

TIMSK0 |= _BV(OCIE0A);

Es necesario determinar el manejador de la interrupción para el vector de la interrupción por medio de un temporizador. El vector de interrupción es un puntero a la ubicación del comando a ejecutar cuando se invoca la interrupción. Varios vectores de interrupción se combinan en una tabla de vectores de interrupción. El temporizador en este caso se llamará TIMER0_COMPA_vect. En este controlador se realizarán las mismas acciones que en el loop().

SIGNAL(TIMER0_COMPA_vect) {
unsigned long currentMillis = millis();

sweeper1.Update(currentMillis);

if(digitalRead(2) == HIGH) {
   sweeper2.Update(currentMillis);
   led1.Update(currentMillis);
}

led2.Update(currentMillis);
led3.Update(currentMillis);

}

// La función loop () queda vacía.

void loop() {
}

En resumen

Las interrupciónes de Arduino son un tema bastante complicado porque hay que pensar integramente en toda la arquitectura del proyecto, imaginar cómo se ejecutará el código, cuáles son los posibles eventos, qué sucederá cuando se interrumpa el código principal, etc.

No pretendemos en este artículo revelar todas las peculiaridades de trabajar con esta construcción del lenguaje; el objetivo principal era introducirnos en los principales casos de uso. En otros artículos desarrollaremos los conceptos sobre las interrupciones con más detalle.

Otros artículos relacionados de nuestro Curso Arduino que pueden interesarte: