Interrupciones Con Arduino
Interrupciones Con Arduino
12 respuestas
Si queremos detectar un cambio de estado en esta entrada, el método que hemos usado
hasta ahora es emplear las entradas digitales para consultar repetidamente el valor de la
entrada, con un intervalo de tiempo (delay) entre consultas.
En programación, una interrupción es una señal recibida por el procesador o MCU, para
indicarle que debe «interrumpir» el curso de ejecución actual y pasar a ejecutar código
específico para tratar esta situación.
Todos los dispositivos que deseen comunicarse con el procesador por medio de
interrupciones deben tener asignada una línea única capaz de avisar al CPU cuando le
requiere para realizar una operación. Esta línea se denomina IRQ.
Tipos de Interrupciones:
https://es.wikipedia.org/wiki/Interrupci%C3%B3n
http://programarfacil.com/blog/arduino-blog/interrupciones-con-arduino-ejemplo-
practico/
https://en.wikipedia.org/wiki/Interrupt
Interrupciones en Arduino
Las interrupciones pueden ocurrir por un cambio en un puerto (solo en aquellos que soporten
interrupciones HW), overflow en un timer, comunicación serie (USART), etc…
Más información:
http://www.prometec.net/interrupciones/
http://programarfacil.com/blog/arduino-blog/interrupciones-con-arduino-ejemplo-
practico/
http://www.sites.upiicsa.ipn.mx/polilibros/portal/Polilibros/P_terminados/PolilibroFC/Uni
dad_V/Unidad%20V_2.htm
http://playground.arduino.cc/Code/Interrupts
Las interrupciones son muy útiles para hacer que las cosas ocurran automáticamente en los
programas del microcontrolador y pueden resolver problemas de temporización.
Las tareas más usuales en las que usar interrupciones son en la monitorización de entradas
de usuario o entradas externas críticas en el tiempo, así como en lectura de periféricos con
requisitos de temporización muy específicos donde queramos capturar un evento que tiene
una duración muy corta inferior al tiempo de loop de nuestro programa.
Los vectores de interrupción es una tabla en memoria que contiene la dirección de memoria
de la primera instrucción de interrupción. ATmega328p Interrupt vectors, además esta tabla
establece la prioridad de las interrupciones:
Y así funciona el ISR vector table:
Las librería de avr-libc que se usa para manejar las interrupciones es <avr/interrupt.h>:
http://www.nongnu.org/avr-libc/user-manual/group__avr__interrupts.html
http://www.atmel.com/webdoc/AVRLibcReferenceManual/group__avr__interrupts.html
Más información:
http://courses.cs.washington.edu/courses/csep567/10wi/lectures/Lecture7.pdf
http://ee-classes.usc.edu/ee459/library/documents/avr_intr_vectors/
http://www.luisllamas.es/2016/04/que-son-y-como-usar-interrupciones-en-arduino/
http://avrmicrotutor.blogspot.com.es/2011/08/basic-understanding-of-
microcontroller.html
https://felixmaocho.wordpress.com/2013/08/05/arduino-manejo-de-interrupciones/
Para las interrupciones externas o hardware, solo hay dos pines que las soportan en los
ATmega 328 (p.e. Arduino UNO), son las INT0 y INT1 que están mapeadas a los pines 2 y 3.
Estas interrupciones se pueden configurar con disparadores en RISING o FALLING para
flancos o en nivel LOW. Los disparadores son interpretados por hardware y la interrupción es
muy rápida.
El Arduino mega tiene más pines disponibles para interrupciones externas. Pines de External
Interrupts para Mega: 2 (interrupt 0), 3 (interrupt 1), 18 (interrupt 5), 19 (interrupt 4), 20
(interrupt 3), and 21 (interrupt 2). Estos pines pueden ser configurados para disparar una
interrupción al detectar un nivel bajo, un flanco ascendente, un flanco descendente o un
cambio de nivel.
En el pin de reset también hay otra interrupción que sólo se dispara cuando detecta voltaje
LOW y provoca el reset del microcontrolador.
Esto quiere decir que el Arduino UNO puede definir dos interrupciones hardware llamadas 0 y
1, conectadas a los pines 2 y 3
Para saber qué número de interrupción estás asociada a un pin, debemos usar la
función digitalPinToInterrupt(pin). El número de interrupción su mapeo en los pines
dependerá del MCI. El uso de número de interrupción puede provocar problemas de
compatibilidad cuando el sketch funciona en diferentes placas.
Uno, Ethernet 2 3
Mega2560 2 3 21 20 19 18
Arduino Due tiene grandes capacidades a nivel de interrupciones que permiten asociar una
interrupción a cada uno de los pines disponibles. Arduino Zero permite asociar una
interrupción a todos los pines excepto para el pin 4.
Las interrupciones de hardware, también conocidas como INT0 e INT1, llaman a una rutina de
servicio de interrupción cuando algo sucede con uno de los pines asociados. La ventaja es
que Arduino tiene una simple rutina de configuración para conectar la rutina de servicio de
interrupción al evento: attachInterrupt (). La desventaja es que sólo funciona con dos pines
específicos: pin digital 2 y 3 en la placa Arduino.
El Atmega328p solo tiene dos interrupciones de hardware INT0 e INT1, sin embargo los
microcontroladores AVR pueden tener una interrupción ante un cambio en cualquier pin, es lo
que denomina pin change interrupt. http://playground.arduino.cc/Code/Interrupts. Estas
interrupciones no son soportadas directamente por Arduino y necesitan ser accedidas a través
de una librería adicional.
Las interrupciones de cambio de pin pueden habilitarse en más pines. Para los ATmega
328, pueden habilitarse en cualquiera de los pines de señal disponibles. Estas son disparadas
igual en flancos RISING o FALLING, pero depende del código de la interrupción configurar el
pin que recibe la interrupción y determinar qué ha pasado. Las interrupciones de cambio de
pin están agrupadas en 3 puertos de la MCU, por lo tanto hay 3 vectores de interrupciones
para todo el conjunto de pines. Esto hace el trabajo de resolver la acción en una interrupción
más complicada.
Si necesita más pines para interrupciones, hay un mecanismo para generar una interrupción
cuando se cambia cualquier pin en uno de los puertos de 8 bits. No sabes pin, sino sólo en
qué puerto. Si se genera una interrupción cuando se cambia uno de los pines ADC0 a ADC5
(utilizado como entrada digital). En ese caso, se llama a la rutina de servicio de interrupción
ISR (PCINT1_vect). En la rutina se puede averiguar cuál de los pines específicos dentro de
ese puerto ha sido el que ha cambiado.
“The Pin Change Interrupt Request 2 (PCI2) will trigger if any enabled PCINT[23:16] pin
toggles. The Pin Change Interrupt Request 1 (PCI1) will trigger if any enabled PCINT[14:8] pin
toggles. The Pin Change Interrupt Request 0 (PCI0) will trigger if any enabled PCINT[7:0] pin
toggles.”
PCINT son unos pines determinados que se puede ver en la página 19 a que corresponden
de http://www.atmel.com/Images/Atmel-42735-8-bit-AVR-Microcontroller-ATmega328-
328P_datasheet.pdf
Los pines de interrupción deben estar configurados como INPUT, las resistencias pullup
pueden habilitarse para poder detectar interruptores simples.
Es cierto que solo se puede configurar una rutina de interrupción para cada grupo de pines en
un puerto, pero hay muchos casos donde es suficiente.
Más información:
https://thewanderingengineer.com/2014/08/11/arduino-pin-change-interrupts/
http://www.geertlangereis.nl/Electronics/Pin_Change_Interrupts/PinChange_en.html
http://playground.arduino.cc/Main/PinChangeInterrupt
http://www.ermicro.com/blog/?p=2292
http://gammon.com.au/interrupts
http://playground.arduino.cc//Main/PinChangeIntExample
http://www.avr-tutorials.com/interrupts/about-avr-8-bit-microcontrollers-interrupts
http://playground.arduino.cc/Main/PinChangeInt
https://github.com/GreyGnome/PinChangeInty
nueva versión EnableInterrupt https://github.com/GreyGnome/EnableInterrupt
wiki https://github.com/GreyGnome/EnableInterrupt/wiki
Cada interrupción está asociada con dos bits, un bit de indicador de interrupción (flag) y un bit
habilitado de interrupción (enabled). Estos bits se encuentran en los registros de E/S
asociados con la interrupción específica:
En resumen, básicamente, tanto la Interrupt Flag como la Interrupt Enabled son necesarias
para que se genere una solicitud de interrupción como se muestra en la figura siguiente.
Aparte de los bits habilitados para las interrupciones específicas, el bit de enable de
interrupción global DEBE ser habilitado para que las interrupciones se activen en el
microcontrolador.
Para el microcontrolador AVR de 8 bits, este bit se encuentra en el registro de estado de E/S
(SREG). La interrupción global habilitada es bit 7, en el SREG.
Interrupciones Internas en Arduino
Las interrupciones internas en Arduino son aquellas interrupciones relacionadas con los timers
y que también son denominadas interrupciones de eventos programados.
Arduino tiene tres timers. Son el timer cero, timer uno, timer dos.Timer cero y dos son de 8 bits
y el temporizador uno es de 16 bits.
Ya vimos que las interrupciones de los timers se usan para PWM y también con la librería
MSTimer2: http://www.pjrc.com/teensy/td_libs_MsTimer2.html
Una ISR debe ser tan corta y rápida como sea posible, puesto que durante su ejecución se
paraliza el curso normal del programa y las interrupciones se deshabilitan.
Se se usan varias ISR en el sketch, solo una se puede ejecutar y otras interrupciones serán
ejecutadas después de que la ISR actual finalice, en un orden que depende las prioridad de
las interrupciones y que depender de interrupt handler.
La función millis() no funciona dentro del ISR puesto que usa interrupciones para su uso. La
función micros() funciona dentro de ISR pero después de 1-2 ms se empieza a comportar de
forma extraña. delayMicroseconds() no usa ningún contador y funcionará correctamente
dentro del ISR. Por lo tanto si la ISR dura mucho tiempo provocará retrasos en el reloj interno,
puesto que millis() no avanza mientras se ejecuta el ISR.
Para pasar datos entre el programa principal y el ISR se usan las variables globales, pero para
que estas variables se actualicen correctamente deben declararse con el modificador
“volatile”: https://www.arduino.cc/en/Reference/Volatile
Volatile es una palabra reservada que se pone delante de la definición de una variable para
modificar la forma en que el compilador y el programa trata esa variable.
Declarar una variable volátil es una directiva para el compilador. Específicamente, dice al
compilador que cargue la variable desde la RAM y no desde un registro de almacenamiento,
que es una ubicación de memoria temporal donde se almacenan y manipulan las variables del
programa. Bajo ciertas condiciones, el valor de una variable almacenada en registros puede
ser inexacto.
Una variable debe ser declarada volátil siempre que su valor pueda ser cambiado por algo
más allá del control de la sección de código en la que aparece, como un subproceso que se
ejecuta simultáneamente. En Arduino, el único lugar en el que es probable que ocurra es en
secciones de código asociadas con interrupciones, las llamadas rutinas de servicio de
interrupción (ISR).
Para poder modificar una variable externa a la ISR dentro de la misma debemos declararla
como “volatile”. El indicador “volatile” indica al compilador que la variable tiene que ser
consultada siempre antes de ser usada, dado que puede haber sido modificada de forma
ajena al flujo normal del programa (lo que, precisamente, hace una interrupción). Al indicar
una variable como Volatile el compilador desactiva ciertas optimizaciones, lo que supone una
pérdida de eficiencia. Por tanto, sólo debemos marcar como volatile las variables que
realmente lo requieran, es decir, las que se usan tanto en el bucle principal como dentro de la
ISR.
Más información de
interrupciones: http://cs4hs.cs.pub.ro/wiki/roboticsisfun/chapter2/ch2_10_interrupts
El core de Arduino ofrece una serie de instrucciones para programar las interrupciones
externas, pero no las de pin change ni las de temporizadores:
interrupts() – https://www.arduino.cc/en/Reference/Interrupts
Habilita las interrupciones (antes han debido ser inhabilitadas con noInterrupts()). Las
interrupciones permiten a ciertas tareas importantes que se ejecuten en segundo plano
y defecto las interrupciones están habilitadas. Algunas funciones no funcionarán si se
deshabilitan las interrupciones y las comunicaciones entrantes serán ignoradas,
también podrán modificar ligeramente la temporización del código. Las interrupciones
se pueden deshabilitar para casos particulares de secciones críticas del código.
noInterrupts() – https://www.arduino.cc/en/Reference/NoInterrupts
Deshabilita las interrupciones. Las interrupciones pueden ser habilitadas de nuevo con
interrupts().
attachInterrupt() – https://www.arduino.cc/en/Reference/AttachInterrupt
Me permite configurar una interrupción externa, pero no otro tipo de interrupciones. El
primer parámetro es el número de interrupción que va asociado a un pin, luego la
función ISR y finalmente el modo.
detachInterrupt() – https://www.arduino.cc/en/Reference/DetachInterrupt
Deshabilita la interrupción. El parámetro que se le pasa es el número de la
interrupción.
digitalPinToInterrupt(pin) traduce el pin al número de interrupción específica.
usingInterrupt() – https://www.arduino.cc/en/Reference/SPIusingInterrupt
Deshabilita la interrupción externa pasada como parámetro en la llamada
a SPI.beginTransaction() y se habilita de nuevo en endTransaction() para prevenir
conflictos en las transacciones del bus SPI
En attachInterrupt() los modos disponibles que definen cuando una interrupción externa es
disparada, se hace mediante 4 constantes:
Normalmente, las variables globales se utilizan para pasar datos entre un ISR y el programa
principal. Para asegurarse de que las variables compartidas entre un ISR y el programa
principal se actualizan correctamente, declararlas como volátiles.
La función delay() no deshabilita las interrupciones, por lo tanto los datos recibidos por el serial
Rx son guardados, los valores PWM funcionan y las interrupciones externas funcionan. Sin
embargo la función delayMicroseconds() deshabilita las interrupciones mientras está
ejecutándose.
Más información:
https://www.arduino.cc/en/Reference/AttachInterrupt
http://www.engblaze.com/we-interrupt-this-program-to-bring-you-a-tutorial-on-arduino-
interrupts/
Para manejar las interrupciones por cambio de pin disponemos de varias librerías:
Ver http://playground.arduino.cc/Main/LibraryList#Interrupts
Multitarea
Utilizar interrupciones nos permitirá olvidarnos de controlar ciertos pines. Esto muy importante
ya que dentro de una aplicación o programa, no vamos a hacer una única cosa. Por ejemplo,
queremos que un LED se encienda o se apague cuando pulsamos un botón. Esto es
relativamente sencillo pero cuando además queremos que otro LED parpadee, la cosa se
complica.
Todas las tareas que hemos realizado hasta ahora han sido síncronas. Es decir, solicitamos
unos datos (puerto serie, ethernet, etc…), esperamos la respuesta y los mostramos en
pantalla. La contraposición es un proceso asíncrono, nosotros lanzamos la petición y en
cuanto se pueda se realizará y se mostrará el resultado sin esperar.
Si nos encontramos con respuestas lentas, no es buena técnica esperar de forma “síncrona”
porque seguramente obtengamos un error de que se ha excedido el tiempo de espera
“timeout”. Si estamos descargando datos de gran volumen, tampoco es buena técnica que
dejemos la aplicación “congelada” mientras se descargan los datos. Lo ideal es lanzar la
descarga de fondo, es decir, de forma “asíncrona”.
Un proceso síncrono es aquel que se ejecuta y hasta que no finaliza solo se ejecuta ese
proceso (todo en el mismo loop), mientras que uno asíncrono comienza la ejecución y
continúan ejecutándose otras tareas por lo que el proceso total se completa en varios loops.
Son dos formas de atacar un problema. Este nuevo concepto es muy interesante y abre
muchas posibilidades a nuestros programas que requieren conexiones remotas.
https://learn.adafruit.com/multi-tasking-the-arduino-part-1
https://learn.adafruit.com/multi-tasking-the-arduino-part-1/using-millis-for-timing
http://harteware.blogspot.com.es/2010/11/protothread-powerfull-library.html
Hacer un ejercicio simple donde asociemos al pin 2 una interrupción que encienda y apague el
pin 13.
Código:
En el siguiente código definimos el pin digital 10 como salida, para emular una onda cuadrada
de periodo 300ms (150ms ON y 150ms OFF). Para visualizar el funcionamiento de la
interrupción, en cada flanco activo del pulso simulado, encendemos/apagamos el LED
integrado en la placa, por lo que el LED parpadea a intervalos de 600ms (300ms ON y 300ms
OFF). No estamos encendiendo el LED con una salida digital, si no que es la interrupción que
salta la que enciende y apaga el LED (el pin digital solo emula una señal externa).
1
2
3 const int emuPin = 10;
4
const int LEDPin = 13;
5 const int intPin = 2;
6 volatile int state = LOW;
7
8 void setup() {
9 pinMode(emuPin, OUTPUT);
10 pinMode(LEDPin, OUTPUT);
pinMode(intPin, INPUT_PULLUP);
11 attachInterrupt(digitalPinToInterrupt(intPin), blink, RISING);
12 }
13
14 void loop() {
15 //esta parte es para emular la salida
digitalWrite(emuPin, HIGH);
16 delay(150);
17 digitalWrite(emuPin, LOW);
18 delay(150);
19 }
20
21 void blink() {
state = !state;
22 digitalWrite(LEDPin, state);
23 }
24
25
El siguiente código empleamos el mismo pin digital para emular una onda cuadrada, esta vez
de intervalo 2s (1s ON y 1s OFF). En cada interrupción actualizamos el valor de un contador.
Posteriormente, en el bucle principal, comprobamos el valor del contador, y si ha sido
modificado mostramos el nuevo valor. Al ejecutar el código, veremos que en el monitor serie
se imprimen números consecutivos a intervalos de dos segundos.