Autor: Oscar Gonzalez
Tiempo de lectura: 8 minutos
Aprenderás cómo funcionan las interrupciones con Arduino y verás códigos de ejemplos prácticos para usar las interrupciones
En ésta guía te voy a enseñar qué son y cómo funcionan las interrupciones con Arduino y también cómo utilizarlas.
Las interrupciones son una funcionalidad muy importante pero no solo en el entorno Arduino, sino en los microcontroladores en general, ya que facilitan priorizar tareas de una forma bastante sencilla.
En ésta guía te mostraré ejemplos con placas Arduino, pero las indicaciones también funcionarán con otros microcontroladores como ESP8266 o ESP32 por ejemplo.
Lo primero que debes saber es que una interrupción no es más que eso: una interrupción
Eso quiere decir que el programa principal se interrumpe cuando ocurre un evento. Puede haber varios tipos de eventos y te hablaré de ellos en un momento, pero primero te pongo un ejemplo de un programa normal.
Imagínate que tienes un sencillo programa que lee el estado de un pin donde tienes un pulsador conectado. En otro pin tienes un diodo LED.
Lo que pretendemos aquí es encender el LED cuando el botón sea pulsado. El circuito sería algo como esto:
Para montarlo, tenemos esta configuración de pines:
Estoy obviando las demás conexiones que simplemente son de alimentación, ya que la que nos interesan, son estas dos.
Ahora veamos el código normal (por el momento sin interrupciones) para que al pulsar el botón, se encienda el diodo LED.
/*
Encender un LED con un botón - Oscar Gonzalez - BricoGeek.com
*/
int led = 13; // Pin donde está conectado el lado positivo del LED
int boton = 3; // Pin donde está conectado el pulsador
void setup() {
// Modo de pines
pinMode(led, OUTPUT);
pinMode(boton, INPUT);
}
void loop() {
if (digitalRead(boton) == HIGH) // Si el botón está pulsado
{
digitalWrite(led, HIGH); // encendemos el LED
}
else // Sino..
{
digitalWrite(led, LOW); // apagamos el LED
}
}
El código no puede ser más simple. En la función Setup preparamos los pines y luego en el Loop comprobamos el estado del pin del botón para saber si hay que encender o no el LED.
Todo esto funciona bien, pero tiene un gran problema: Tenemos que estar comprobando constantemente el pin.
Es decir, el microcontrolador debe estar todo el rato trabajando en comprobar ese pin sin poder hacer otra cosa. Ahora imagínate que en tu circuito, a parte del botón, tienes un sensor que debes consultar para saber sus valores. En esa situación sería muy difícil hacerlo ya que no queda espacio de tiempo para hacer otra cosa.
Aquí es donde entran las interrupciones por la puerta grande!
Ahora vamos hacer el mismo circuito pero esta vez usaremos una interrupción para controlar el diodo LED. Es un mecanismo muy potente que permite "vigilar" el cambio de estado de los pines.
Vamos a utilizar la función attachInterrupt(interrupt, ISR, mode)
Esta función tiene 3 parámetros:
Como el número de la interrupción varía en función de cada placa, es más cómodo indicar el pin con digitalPinToInterrupt(pin). Aún así, abajo te dejaré una table con el número de interrupción de varias placas.
mode: define cómo debe ser lanzada la interrupción. Son posibles 4 constantes:
Las placas Arduino Due, Zero y MKR1000 también soportan el modo HIGH que salta cuando el pin está a nivel alto.
El código resultante usando una interrupción quedará así:
/*
Encender un LED con un botón con interrupciones - Oscar Gonzalez - BricoGeek.com
*/
int led = 13; // Pin donde está conectado el lado positivo del LED
int boton = 3; // Pin donde está conectado el pulsador
void setup() {
pinMode(led, OUTPUT);
pinMode(boton, INPUT);
attachInterrupt(digitalPinToInterrupt(boton), controlar, CHANGE);
}
void loop() {
// Aquí no hay nada que hacer, podemos hacer otras cosas!
}
// Interrupt service routine for interrupt 0
void controlar() {
if (digitalRead(boton) == HIGH) // Si el botón está pulsado
{
digitalWrite(led, HIGH); // encendemos el LED
}
else // Sino..
{
digitalWrite(led, LOW); // apagamos el LED
}
}
Como te decía, cada placa tiene unos pocos pines para usar con interrupciones. Aquí abajo verás unos cuantos modelos y los pines que lo soportan:
Modelo Arduino | Pines con Interrupción |
Arduino Uno, Nano, Mini, etc (con ATMega328) | 2, 3 |
Uno WiFi Rev.2 | Todos los pines |
Arduino Mega, Mega 2560, Mega ADK | 2, 3, 18, 19, 20, 21 |
Arduino Micro, Leonardo etc (con ATMega32u4) | 0, 1, 2, 3, 7 |
Arduino Zero | Todos los pines menos el 4 |
Arduino DUE | Todos los pines |
Arduino MKR (todos los modelos) | 0, 1, 4, 5, 6, 7, 8, 9, A1, A2 |
Arduino Nano 33 IoT | 2, 3, 9, 10, 11, 13, A1, A5, A7 |
Arduino Nano 33 BLE, Nano 33 BLE Sense | Todos los pines |
Hasta ahora hemos visto cómo utilizar interrupciones en Arduino mediante eventos externos usando los pines de entrada. Pero ¿qué pasa si queremos hacer algo de forma periódica?
Por supuesto, puedes utilizar todo tipo de trucos en tu código para contar el tiempo, como por ejemplo usando la función millis(), pero eso tiene el mismo problema que no usar interrupciones, ya que debes constantemente comprobar si el tiempo ha transcurrido.
Para éste tipo de caso, cuando necesitas ejecutar algo de forma periódica, Arduino tiene también un mecanismo de interrupciones utilizar el Timer.
El temporizador de Arduino o Timer, es un circuito que mide los ciclos de reloj a partir de un flanco (de subida o bajada). Tan pronto como el contador llegue a un número de ciclos determinado, el temporizador lanza una interrupción.
Esto es extremadamente útil para todo tipo de proyectos ya que al igual que las interrupciones con pines, las interrupciones con Timer en Arduino puede ejecutar de una forma eficaz una función cada cierto tiempo sin tener que preocuparte de nada más.
Las interrupciones son útiles para:
y mucho más...
Las interrupciones con Timer en Arduino son algo algo más complicadas que las que solo utilizan pines de entrada.
Eso es por que necesitas conocer algunos conceptos previos.
Lo más importante es caber qué placa Arduino estamos utilizando para luego poder configurar correctamente los timers. Aquí vamos a utilizar como ejemplo un Arduino UNO.
El Arduino Uno tiene tres temporizadores llamados timer0, timer1 y timer2. Cada uno de los temporizadores tiene un contador que se incrementa en cada ciclo del reloj del temporizador. Las interrupciones del temporizador se activan cuando el contador alcanza un valor específico, almacenado en el registro correspondiente.
Una vez que el contador de un temporizador alcanza este valor, se borrará (se pone a cero) en el siguiente ciclo del reloj del temporizador, luego continuará contando hasta el valor de coincidencia de comparación nuevamente.
Al elegir el valor de coincidencia y configurar la velocidad a la que el temporizador incrementa el contador, puedes controlar la frecuencia de las interrupciones del temporizador. Por eso es importante saber qué placa Arduino estás utilizando ya que algunas funcionan a distintas frecuencias de reloj.
El primer parámetro importante es la velocidad a la que el temporizador incrementa el contador. El reloj interno del Arduino UNO funciona a 16 MHz (Megahercios), esta es la velocidad más rápida a la que los temporizadores pueden incrementar sus contadores.
A 16 MHz, cada marca del contador representa 1/16 000 000 de segundo (aproximadamente 63 nanosegundos), por lo que un contador tardará 10/16 000 000 segundos en alcanzar un valor de 9 (los contadores empiezan siempre en 0) y 100/16 000 000 segundos en alcanzar un valor del 99.
Está claro que a ésta velocidad es demasiado rápido, además Timer0 y timer2 son temporizadores de 8 bits, lo que significa que pueden almacenar un valor de contador máximo de 255.
Timer1 es un temporizador de 16 bits, lo que significa que puede almacenar un valor de contador máximo de 65535. Recuerda que una vez que un contador alcanza su valor máximo, volverá a cero (esto se llama Overflow o desbordamiento).
Esto significa que a 16 MHz, incluso si configuramos el registro de coincidencia de comparación al valor máximo del contador, se producirán interrupciones cada 256/16 000 000 segundos (16 %u03BCs) para los contadores de 8 bits, y cada 65 536/16 000 000 (4 ms) segundos para el Contador de 16 bits.
Claramente, esto no es muy útil si solo desea interrumpir una vez por segundo! ¿Entonces qué podemos hacer?
Para hacer más cómodo y sobre todo útil, puede controlar la velocidad de incremento del contador usando algo llamado preescalador (prescaler). Un prescaler dicta la velocidad de su temporizador de acuerdo con la siguiente ecuación:
El prescaler puesto a 1 incrementará el contador a 16 MHz, un prescaler a 8 lo incrementará a 2 MHz, a 64 = 250 kHz, y así sucesivamente. El prescaler puede tener estos valores: 1, 8, 64, 256 y 1024.
Aquí abajo te voy a dejar una tabla con los rangos con los que puedes jugar con Timer1 y Timer2 a 16MHz
Timer1 | Timer2 | |
Prescaler | 1, 8, 32, 64, 128, 256, 1024 | 1, 8, 32, 64, 128, 256, 1024 |
Periodo máximo (ns) | 0.016, 0.128, 0.512, 1.024, 2.048, 4.096, 16.384 | 4.098, 32.784, 131.136, 262.272, 524.544, 1049.088, 4196.352 |
Resolución (ms) | 0.063, 0.5, 2, 4, 8, 16, 64 | 0.063, 0.5, 2, 4, 8, 16, 64 |
En este momento podríamos profundizar largamente sobre los registros del microcontrolador para controlar las interrupciones con timers, pero sería complicarse demasiado. Además, si estás leyendo esto muy probablemente lo único que quieres es un ejemplo práctico que puedas utilizar rápidamente.
Pues tus deseos se han hecho realidad y te dejo justo aquí abajo un ejemplo para hacer parpadear el LED del pin 13 a cada según utilizando solamente los registros internos del procesador.
Luego un poco más adelante, veremos una forma mucho, pero mucho más cómoda de utilizar las interrupciones con Timers sin complicarse demasiado simplemente utilizando una librería.
#include <avr/io.h>
#include <avr/interrupt.h>
const int LED = 13;
unsigned int comparador = 0xF424;
volatile int contador;
// Define la interrupción
ISR(TIMER1_COMPA_vect)
{
contador ;
flash();
}
void setup()
{
pinMode(LED, salida);
digitalWrite(LED, LOW);
cli();
// Inicializa registros Timer1
TCCR1A = 0;
TCCR1B = 0;
// Modo CTC. 0xF424 = 62500
OCR1A = comparador;
// Ajuystamos preescaler para obtener un intervalo de 1 segundo
TCCR1B = (1<<WGM12) | (1<<CS12);
TIMSK1 = (1<<OCIE1A);
sei();
}
void loop()
{
delay(200); // No hacemos nada, solo perder el tiempo ;)
}
void flash()
{
static boolean salida = HIGH;
digitalWrite(LED, salida);
salida = !salida; // Invertimos el valor de la salida
}
Has podido ver hasta ahora y de forma muy resumida, cómo utilizar diferentes tipos de interrupciones con tu placa Arduino. Pero como has podido comprobar, se puede complicar bastante y precisamente la plataforma Arduino lo que quiere es facilitarte la vida.
Por supuesto, tendrás mucho más control si lo haces todo a mano, pero para que sea todo mucho más sencillo, existen varias librerías para gestionar las interrupciones.
Aquí te dejo dos opciones, que probablemente son las más populares actualmente.
Es quizás la librería para interrupciones con Arduino más sencilla de utilizar y tiene una buen documentación.
Ésta librería para interrupciones es quizás de las más completas e incluye diferentes ejemplos en los que te podrás basar. Soporta también diferentes microcontroladores como el ATMega16U4, ATMega32U4 y ATMega328 entre otros.