Interrupciones

Una Interrupt Service Routine ISR es básicamente la alteración del flujo natural de un programa, en este caso el loop(), si pensamos en este como secuencial. Existen varios tipos de interrupciones: Externas, temporizadas, bus ISD, bus I2C, UART, etc

La mayoría de uC tienen interrupciones. Las ISR le permiten responder a eventos especiales mientras hace otra cosa. Por ejemplo, si está cocinando la cena, puede poner las papas para cocinar durante 20 minutos. En lugar de mirar el reloj durante 20 minutos, puede configurar un temporizador e ir a ver la TV. Cuando suena el temporizador, "interrumpe" su acción de mirar TV para hacer algo con las papas.

Las ISR son útiles para hacer que las cosas sucedan automáticamente en los programas de uC y pueden ayudar a resolver problemas de tiempo. Las buenas directivas para usar una interrupción pueden incluir leer un codificador giratorio o monitoreo de la entrada del usuario.

Si quiere asegurar que un programa siempre captara los impulsos de un codificador giratorio, para que nunca falle un pulso, sería muy complicado escribir un programa para hacer cualquier otra cosa, porque el programa necesitaría sondear constantemente dicho sensor. Otros sensores también tienen una dinámica de interfaz similar, como tratar de leer un sensor de sonido que está tratando de captar un clic o un sensor de ranura infrarroja que intenta capturar una caída de moneda. En todas estas situaciones, usar una ISR puede liberar al uC para realizar otro trabajo sin perder la entrada.

Los ISR son tipos especiales de funciones que tienen algunas limitaciones únicas que la mayoría de las otras funciones no tienen. Un ISR no puede tener ningún parámetro, y no deberían devolver nada. En general, un ISR debe ser lo más corto y rápido posible. Si su boceto usa múltiples ISR, solo uno puede ejecutarse a la vez, otras ISR se ejecutarán después de que la actual finalice en un orden que depende de la prioridad que tengan. La función millis() depende de las interrupciones para contar, por lo que nunca se incrementará dentro de un ISR. Como la función delay() requiere que las ISR funcionen, no funcionará si se llama dentro de un ISR. La función micros() funciona inicialmente, pero comenzará a comportarse erráticamente después de 1~2 ms. La función delayMicroseconds() no usa ningún contador, por lo que funcionará normalmente.

Las principales razones por las que podrías usar ISR son:

Normalmente, las variables globales se usan para pasar datos entre un ISR y el programa loop(). Para asegurarse de que las variables compartidas entre un ISR y el loop() se actualicen correctamente, declarelas como volatile.

Prioridad de interrupciones y vector usado
PrioridadNombreAsignación
1Reset 
2Pin D2INT0_vect
3Pin D3INT1_vect
4pin D8~D13PCINT0_vect
5pin D0~D5PCINT1_vect
6pin D0~D7PCINT2_vect
7WDT_vect
8Timer/Conter2
9Timer/Conter2
10Timer/Conter2
11Timer/Conter1
12Timer/Conter1
13Timer/Conter1
14Timer/Conter1
15Timer/Conter0
16Timer/Conter0
17Timer/Conter0
18SPI serial transfer complete
19UART Rx complete
20UART data register empty
21UART Tx complete
22ADC converter complete
23EPROM Ready
24Analog comparator
25I2C
26Store program memory readySOM_READY_vect

Dentro de la función ISR, no funcionará delay() y el valor devuelto por la función millis() no aumentará. Los datos en serie recibidos durante la función pueden perderse. Debe declarar como volátil cualquier variable que modifique dentro de la función ISR.

1. Externas

Estas ISR son las mas usadas, se usan para detectar eventos externos. Por ejemplo si queremos que cada vez que alguien pulse un botón suceda alguna acción tenemos dos opciones. La primera alternativa y sin duda la peor de todas es quedarnos esperando que alguien pulse el botón, claramente no podríamos hacer otra cosa que esperar y estaríamos mal gastando toda la capacidad del uC en no hacer nada, simplemente esperando que alguien pulse el botón.

La segunda opción es usar las ISR de Arduino. Usar las ISR nos da como ventaja que podemos hacer cualquier otra cosa mientras esperamos que el evento ocurra, simplemente cuando el evento se produce se acciona una bandera de interrupción que detiene y altera el flujo natural del programa para atender esa interrupción, básicamente es un orden de prioridades.

Pines para interrupciones
TarjetaPines
UNO, Nano, Mini (328p)2 y 3
Mega2, 3, 18, 19 y 21
Micro, Leonard (32u4)0, 1, 2, 3 y 7
CeroTodos menos el 4
MKT10000, 1, 4, 5, 6, 7, 8, 9, A1 y A2
DUOTodos
101Todos (solo 2, 3, 7, 8, 10, 11, 12 y 13 con CHANGE)
EPS8266?
Interrupciones asignadas
TarjetaINT.0INT.1INT.2INT.3INT.4INT.5
UNO, Ethernet23    
Mega2321201918
32u432017 
DUOtodos los pines

Para DUO, MKR1000, 101 y Zero el numero de interrupción es el numero de pin.

1.1 digitalPinToInterrupt()

Esta función no permite traducir el pin digital real al número de interrupción específico. Normalmente, debe usar digitalPinToInterrupt(pin), en lugar de colocar un número de interrupción directamente en su boceto. Los pines específicos con interrupciones, y su asignación para interrumpir el número varía en cada tipo de placa. El uso directo de números de interrupción puede parecer simple, pero puede causar problemas de compatibilidad cuando el boceto se ejecuta en una placa diferente. Sin embargo, los bocetos más antiguos a menudo tienen números de interrupción directa. A menudo se usaron el número 0 (para el pin digital 2) o el número 1 (para el pin digital 3). La tabla a continuación muestra los pines de interrupción disponibles en varios tableros.

Tenga en cuenta que en la tabla a continuación, los números de interrupción se refieren al número que se pasará a attachInterrupt (). Por razones históricas, esta numeración no siempre se corresponde directamente con la numeración de interrupción en el chip ATmega (por ejemplo, int.0 corresponde a INT4 en el chip ATmega2560).

digitalPinToInterrupt(pin);

Si el pin no tiene una interrupción asignada la función devuelve -1.

digitalPinToInterrupt(3); //0 en el Arduino UNO

1.2 attachInterrup()

Activa la interrupción dada. La función requiere de 3 parámetros: numero de interrupción, función (ISR) y modo de acción.

attachInterrupt(digitalPinToInterrupt(pin), ISR, modo);	//recomendado
attachInterrupt(interrupt, ISR, modo);	                //no recomendado
attachInterrupt(pin, ISR, modo);	                    //Solo en Arduino Due, Zero, MKR1000 y 101.

Donde:

const byte ledPin = 13;
const byte botonPin = 2;
volatile byte estado = LOW;

void setup() {
	pinMode(ledPin, OUTPUT);
	pinMode(botonPin, INPUT_PULLUP);
	attachInterrupt(digitalPinToInterrupt(botonPin), cambia, CHANGE);
}
//Función de interrupción (ISR)
void cambia() {
	estado = !estado;
}
void loop() {
	digitalWrite(ledPin, estado);
}

Aqui otro ejemplo muy similar

void setup(){
	pinMode (13, OUTPUT);
	digitalWrite (2, HIGH);
	attachInterrupt(digitalPinToInterrupt(2), pulsa, CHANGE);
}

//Funcion de interrupcion (ISR)
void pulsa(){
	if (digitalRead(2)){
		digitalWrite(13, HIGH);
	}else{
		digitalWrite(13, LOW);
	}
}
void loop(){
	...
} 

1.3 detachInterrup()

Desactiva la interrupción dada.

detachInterrup(digitalPinToInterrupt(pin));
detachInterrup(pin);	//Solo en Arduino Due, Zero, MKR1000 y 101.
int contador = 0;
int n = 0;
void setup(){
    Serial.begin(9600);
    attachInterrupt(0, Pulsador, FALLING);
}
void Pulsador(){
   contador++;
}
void loop(){
    if (n != contador){
		Serial.println(contador);
        n = contador ;
    }
}

2. Temporizadas

Estas ISR son disparadas cuando se cumple un cierto tiempo pre-definido. Son muy usadas para controlar y leer sensores cada determinado tiempo y también para crear señales PWM para alimentar motores de corriente continua. Los timers tienen algo que se conoce como prescaler, son divisores de la frecuencia de clock, cuando dividimos la frecuencia ganamos en tiempo pero perdemos en precisión, la frecuencia del cristal se puede dividor por 1, 8, 64, 256.

El siguiente ejemplo ejecuta una ISR cada 250 ms y prende y apaga un LED cada vez que se la atiende.

#include <TimerOne.h>
bool led = LOW;
volatile unsigned int contador = 0; //La definimos como volatile

void setup(void){
       pinMode(13, OUTPUT);
       Timer1.initialize(250000);         //Disparo cada 250 ms
       Timer1.attachInterrupt(ISR_Blink); //Asocia ISR a ISR_Blink y la activa
       Serial.begin(9600);
   }
   void ISR_Blink(){
       led = !led;
       contador++;     //Contamos las veces que enciende LED
   }
   void loop(void){
       digitalWrite(13, led);
       noInterrupts();               // Suspende las interrupciones
       Serial.print("Ciclos = ");
       Serial.println(contador);
       interrupts();                 // Autoriza las interrupciones
       delay(100);
   }

3. Bus SPI

Pronto...

4. Bus I2C

Pronto...

5. Otros

Una ultima cosa.

5.1 Interrupts()

La ISR de "reinicio" no se puede deshabilitar. Sin embargo, las otras interrupciones se pueden desactivar temporalmente al borrar el indicador de ISR.

interrupts();
sei(); //Set Interrupts Flag

5.2 noInterrupts()

La interrupción de "reinicio" no se puede deshabilitar. Sin embargo, las otras interrupciones se pueden desactivar temporalmente al borrar el indicador de interrupción. Puede haber fragmentos de código de tiempo crítico que no desee interrumpir, por ejemplo, mediante una interrupción de temporizador. La desactivación temporal de las interrupciones garantiza que ISRcounter (un contador establecido dentro de un ISR) no cambie mientras estamos obteniendo su valor.

nointerrupts();
cli(); //Clear Interrupts Flag

El valor predeterminado en Arduino es para habilitar las interrupciones. No los deshabilite por períodos prolongados o algunas cosas como los temporizadores no funcionarán correctamente.

Que pasa si ocurre una interrupción mientras están desabilidas ? simplemente se recuerdan dentro del procesador. Cuando se habiliten nuevamente entonces esa interrupción se ejecutaran en orden de prioridad.

Un importante articulo sobre interrupciones de Nick Gammon en su foro de electrónica.