Interrupciones

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.

Este mecanismo se denomina “poll”, y tiene 3 claras desventajas.

  • Suponer un continuo consumo de procesador y de energía, al tener que preguntar continuamente por el estado de la entrada.
  • Si la acción necesita ser atendida inmediatamente, por ejemplo en una alerta de colisión, esperar hasta el punto de programa donde se realiza la consulta puede ser inaceptable.
  • Si el pulso es muy corto, o si el procesador está ocupado haciendo otra tarea mientras se produce, es posible que nos saltemos el disparo y nunca lleguemos a verlo.

Para resolver este tipo de problemas, los microprocesadores incorporan el concepto de interrupción, que es un mecanismo que permite asociar una función a la ocurrencia de un determinado evento. Esta función de callback asociada se denomina ISR (Interruption Service Rutine).

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.

Una interrupción es una suspensión temporal de la ejecución de un proceso, para pasar a ejecutar una subrutina de servicio de interrupción, la cual, por lo general, no forma parte del programa. Una vez finalizada dicha subrutina, se reanuda la ejecución del programa. Las interrupciones HW son generadas por los dispositivos periféricos habilitando una señal del CPU (llamada IRQ del inglés «interrupt request») para solicitar atención del mismo.

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.

Las IRQ son líneas que llegan al controlador de interrupciones, un componente de hardware dedicado a la gestión de las interrupciones, y que está integrado en la MCU.

El controlador de interrupciones debe ser capaz de habilitar o inhibir las líneas de interrupción y establecer prioridades entre las mismas. Cuando varias líneas de petición de interrupción se activan a la vez, el controlador de interrupciones utilizará estas prioridades para escoger la interrupción sobre la que informará al procesador principal. También puede darse el caso de que una rutina de tratamiento de interrupción sea interrumpida para realizar otra rutina de tratamiento de una interrupción de mayor prioridad a la que se estaba ejecutando.

Procesamiento de una Interrupción:

  1. Terminar la ejecución de la instrucción máquina en curso.
  2. Salvar el estado del procesador (valores de registros y flags) y el valor del contador de programa en la pila, de manera que en la CPU, al terminar el proceso de interrupción, pueda seguir ejecutando el programa a partir de la última instrucción.
  3. La CPU salta a la dirección donde está almacenada la rutina de servicio de interrupción (Interrupt Service Routine, o abreviado ISR) y ejecuta esa rutina que tiene como objetivo atender al dispositivo que generó la interrupción.
  4. Una vez que la rutina de la interrupción termina, el procesador restaura el estado que había guardado en la pila en el paso 2 y retorna al programa que se estaba usando anteriormente.

Tipos de Interrupciones:

  • Interrupciones HW o externas: Estas son asíncronas a la ejecución del procesador, es decir, se pueden producir en cualquier momento independientemente de lo que esté haciendo el CPU en ese momento. Las causas que las producen son externas al procesador y a menudo suelen estar ligadas con los distintos dispositivos de entrada o salida.
  • Interrupciones SW: Las interrupciones por software son aquellas generadas por un programa en ejecución. Para generarlas, existen distintas instrucciones en el código máquina que permiten al programador producir una interrupción. Arduino no soporta las interrupciones por software
  • Un evento programado o Timer. Son las interrupciones asociadas a los timers y gracias a ellas funciona millis().
  • Excepciones: Son aquellas que se producen de forma síncrona a la ejecución del procesador típicamente causada por una condición de error en un programa. Normalmente son causadas al realizarse operaciones no permitidas tales como la división entre 0, el desbordamiento, el acceso a una posición de memoria no permitida, etc.

Definiciones genéricas de Interrupciones:

Interrupciones en Arduino

Internamente, Arduino (mejor dicho el microcontrolador AtMega) tiene ciertas interrupciones configuradas que lanza según la situación. Para la transmisión de datos a través del puerto serie, para resetear la placa antes de cargar un programa, comunicación I2C, etc…

El uso de interrupciones es casi obligatorio en un programa avanzado de un microcontrolador. Básicamente cuando un evento ocurre se levanta una bandera y la ejecución se traslada a una rama de código diferente. De esta forma no es necesario esperar un loop a comprobar que un evento ha ocurrido para ejecutar una acción.

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…

Normalmente no se ve, pero las interrupciones ocurren constantemente durante la operación normal de Arduino. Por ejemplo las interrupciones ayudan a las funciones delay() y millis() así como a la función Serial.read().

El procesador dentro de cualquier Arduino tiene dos tipos de interrupciones: “externas” y “cambio de pin”.

Más información:

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.

Para definir una interrupción necesitamos tres cosas:

  • Un pin de Arduino que recibirá la señal de disparo
  • Una condición de disparo
  • Una función que se ejecutará, cuando se dispara la interrupción (Llamada call back function).

En cuanto a la condición de disparo puede ser:

  • LOW, La interrupción se dispara cuando el pin es LOW.
  • CHANGE, Se dispara cuando pase de HIGH a LOW o viceversa.
  • RISING, Dispara en el flanco de subida (Cuando pasa de LOW a HIGH).
  • FALLING, Dispara en el flanco de bajada (Cuando pasa de HIGH a LOW).
  • Y una solo para el DUE: HIGH se dispara cuando el pin esta HIGH.

Para saber todo sobre las interrupciones en el ATmega328p, debemos consultar su información en la página 32 y 82 de http://www.atmel.com/Images/Atmel-42735-8-bit-AVR-Microcontroller-ATmega328-328P_datasheet.pdf

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>:

Más información:

Interrupciones Externas en Arduino

Estas interrupciones hardware, se diseñaron por la necesidad de reaccionar a suficiente velocidad en tiempos inimaginablemente cortos a los que la electrónica trabaja habitualmente y a los que ni siquiera el software era capaz de reaccionar.

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.

Para configurar una interrupción en Arduino se usa la función attachInterrupt(). El primer parámetro a configurar es el número de interrupción, normalmente se usa el nº de pin para traducir al nº de interrupción.

Tabla de pines que soportan interrupción HW en Arduino:

Board Digital Pins Usable For Interrupts
 Uno, Nano, Mini, other 328-based 2, 3
 Mega, Mega2560, MegaADK 2, 3, 18, 19, 20, 21
 Micro, Leonardo, other 32u4-based 0, 1, 2, 3, 7
 Zero all digital pins, except 4
 MKR1000 Rev.1 0, 1, 4, 5, 6, 7, 8, 9, A1, A2
 Due all digital pins

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.

Tabla de interrupciones y número de pin asociado:

Board int.0 int.1 int.2 int.3 int.4 int.5
Uno, Ethernet 2 3
Mega2560 2 3 21 20 19 18
32u4 based (e.g Leonardo, Micro) 3 2 0 1 7

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.

Aspectos importantes a tener en cuenta con el uso de interrupciones:

  • Dentro de la función llamada desde la interrupción, la función delay() no funciona y el valor devuelto por millis() no aumenta. La razón es que estas funciones hacen uso de interrupciones que no se disparan mientras está disparada una interrupción externa.
  • Los datos recibidos por el puerto serie se pueden perder mientras se está en la función llamada por la interrupción.
  • Se deben declarar como “volatile” cualquier variable que sea modificada dentro de la función llamada por una interrupción: https://www.arduino.cc/en/Reference/Volatile

Tutorial de Interrupciones externas en MCUs AVR de 8 bits: http://www.avr-tutorials.com/interrupts/The-AVR-8-Bits-Microcontrollers-External-Interrupts

Programación de interrupciones externas en C: http://www.avr-tutorials.com/interrupts/avr-external-interrupt-c-programming

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.

En primer lugar, los indicadores de habilitación de interrupción de cambio de pin deben ajustarse en el registro PCICR. Estos son los bits PCIE0, PCIE1 y PCIE2 para los grupos de pines PCINT7..0, PCINT14..8 y PCINT23..16 respectivamente. Los pines individuales se pueden activar o deshabilitar en los registros PCMSK0, PCMSK1 y PCMSK2. En el circuito Arduino, en combinación con la hoja de datos Atmel Atmega328, se puede ver que el pin PCINT0 corresponde al pin 0 en el puerto B (llamado PB0). Este es el pin 8 en el Arduino Uno. Otro ejemplo es el pin A0 de la tarjeta Arduino, que puede utilizarse como una entrada digital:

  • Pin Change Interrupt Request 0 (pins D8 to D13) (PCINT0_vect)
  • Pin Change Interrupt Request 1 (pins A0 to A5)  (PCINT1_vect)
  • Pin Change Interrupt Request 2 (pins D0 to D7)  (PCINT2_vect)

“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:

También disponemos de la PinChangeInt Library. PinChangeInt implementa interrupciones de cambio de pin para el entorno Arduino. Esta biblioteca fue diseñada para el Arduino Uno / Duemilanove, y funciona bien en el Nano. El soporte MEGA está incluido pero no de una forma completa.

Registros para las interrupciones externas y pin change son:

  • External Interrupt Control Register A: The External Interrupt Control Register A contains control bits for interrupt sense control.
    Name: EICRA
  • External Interrupt Mask Register: When addressing I/O Registers as data space using LD and ST instructions, the provided offset must be used. When using the I/O specific commands IN and OUT, the offset is reduced by 0x20, resulting in an I/O address offset within 0x00 – 0x3F.
    Name: EIMSK
  • External Interrupt Flag Register: When addressing I/O Registers as data space using LD and ST instructions, the provided offset must be used. When using the I/O specific commands IN and OUT, the offset is reduced by 0x20, resulting in an I/O address offset within 0x00 – 0x3F.
    Name: EIFR
  • Pin Change Interrupt Control Register
    Name: PCICR
  • Pin Change Interrupt Flag Register: When addressing I/O Registers as data space using LD and ST instructions, the provided offset must be used. When using the I/O specific commands IN and OUT, the offset is reduced by 0x20, resulting in an I/O address offset within 0x00 – 0x3F.
    Name: PCIFR
  • Pin Change Mask Register 2
    Name: PCMSK2
  • Pin Change Mask Register 1
    Name: PCMSK1
  • Pin Change Mask Register 0
    Name: PCMSK0

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:

  • El bit de indicador de interrupción se establece siempre que se produce el evento de interrupción, independientemente de que la interrupción esté o no habilitada. Registro EIFR
  • El bit activado por interrupción se utiliza para activar o desactivar una interrupción específica. Básicamente se le dice al microcontrolador si debe o no responder a la interrupción si se dispara. Registro EIMSK

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.

Crearemos una interrupción interna utilizando la interrupción de un temporizador. Para ello necesitaremos una librería adecuada para el temporizador que usemos. Podemos crear tres tipos de interrupción interna, son interrupción de desbordamiento de temporizador (timer overflow), interrupción de comparación de salida (Output Compare Match Interrupt), interrupción de captura de entrada (Timer Input Capture Interrupt).

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

En el apartado de timers trataremos más a fondo los temporizadores de Arduino y sus interrupciones asociadas.

Problemas y soluciones con las interrupciones en Arduino: http://rcarduino.blogspot.com.es/2013/04/the-problem-and-solutions-with-arduino.html

ISR (Interrupt Service Routines)

La función de callback asociada a una interrupción se denomina ISR (Interruption Service Rutine). ISR es una función especial que tiene algunas limitaciones, una ISR no puede tener ningún parámetro en la llamada y no pueden devolver ninguna función.

Dos ISR no pueden ejecutarse de forma simultánea. En caso de dispararse otra interrupción mientras se ejecuta una ISR, la función ISR se ejecuta una a continuación de otra.

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.

Esta es la secuencia cuando se dispara una interrupción:

  1. El microcontrolador completa la instrucción que está siendo ejecutada.
  2. El programa de Arduino que se está ejecutando, transfiere el control a la Interrupt Service Routine (ISR). Cada interrupción tiene asociada una ISR que es una función que le dice al microcontrolador que hacer cuando ocurre una interrupción.
  3. Se ejecuta la ISR mediante la carga de la dirección de comienzo de la ISR en el contador del programa.
  4. La ejecución del ISR continua hasta que se encuentra el RETI (return from the interrupt instruction). Más información: http://www.atmel.com/webdoc/avrassembler/avrassembler.wb_RETI.html
  5. Cuando ha finalizado ISR, el microcontrolador continua la ejecución del programa donde lo dejó antes de que ocurriera la interrupción.

Más información en: http://gammon.com.au/interrupts

Hay muchos I/O registros en el microcontrolador asociados a las interrupciones externas. Estos registros almacenan los flag de las interrupciones, los bits de enable, así como información de control de cada interrupción externa.

Más información de interrupciones: http://cs4hs.cs.pub.ro/wiki/roboticsisfun/chapter2/ch2_10_interrupts

Funciones de Interrupciones en Arduino

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:

  • LOW, La interrupción se dispara cuando el pin es LOW.
  • CHANGE, Se dispara cuando pase de HIGH a LOW o viceversa, es decir un cambio en el estado del pin.
  • RISING, Dispara en el flanco de subida (Cuando pasa de LOW a HIGH).
  • FALLING, Dispara en el flanco de bajada (Cuando pasa de HIGH a LOW).
  • Y solo para el DUE: HIGH se dispara cuando el pin esta HIGH.

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:

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.

La probabilidad de capturar el evento cuando se pulsa el botón disminuye con el aumento de tiempo de parpadeo. En estos casos, y en muchos otros, nos interesa liberar el procesador de Arduino para que solo cuando se pulse el botón, haga una acción determinada. Así no tendremos que estar constantemente comprobando el pin X si ha pulsado el botón.

Precisamente este es el sentido de las interrupciones, poder hacer otras cosas mientras no suceda el evento, pero cuando ese evento externo esté presente, que ejecute rápidamente el código asociado.

Los controladores de interrupciones: administran la ejecución de tareas por interrupciones, lo cual permite la multitarea.

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.

Multitask en Arduino, muy recomendable:

Ejercicios Interrupciones Arduino

Hacer un ejercicio simple donde asociemos al pin 2 una interrupción que encienda y apague el pin 13.

Código:

const byte ledPin = 13;
const byte interruptPin = 2;
volatile byte state = LOW;

void setup() {
pinMode(ledPin, OUTPUT);
pinMode(interruptPin, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(interruptPin), blink, CHANGE);
}

void loop() {
digitalWrite(ledPin, state);
}

void blink() {
state = !state;
}

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).

const int emuPin = 10;
 
const int LEDPin = 13;
const int intPin = 2;
volatile int state = LOW;
 
void setup() {
	pinMode(emuPin, OUTPUT);
	pinMode(LEDPin, OUTPUT);
	pinMode(intPin, INPUT_PULLUP);
	attachInterrupt(digitalPinToInterrupt(intPin), blink, RISING);
}
 
void loop() {
	//esta parte es para emular la salida
	digitalWrite(emuPin, HIGH);
	delay(150);
	digitalWrite(emuPin, LOW);
	delay(150);
}
 
void blink() {
	state = !state;
	digitalWrite(LEDPin, state);
}

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.

const int emuPin = 10;
 
const int intPin = 2;
volatile int ISRCounter = 0;
int counter = 0;
 
 
void setup()
{
	pinMode(emuPin, OUTPUT);
	
	pinMode(intPin, INPUT_PULLUP);
	Serial.begin(9600);
	attachInterrupt(digitalPinToInterrupt(intPin), interruptCount, LOW);
}
 
void loop()
{
	//esta parte es para emular la salida
	digitalWrite(emuPin, HIGH);
	delay(1000);
	digitalWrite(emuPin, LOW);
	delay(1000);
 
 
	if (counter != ISRCounter)
	{
		counter = ISRCounter;
		Serial.println(counter);
	}
}
 
void interruptCount()
{
	ISRCounter++;
	timeCounter = millis();
}

12 comentarios en “Interrupciones

  1. felixmaocho

    En primer lugar asombrado de tus conocimientos sobre este tema. En segundo lugar asombrado que alguien que cláramente tiene muchlsimos más conocimientos que yo, tenga a bien poner en su en su post un link hacia mi blog. Muchas gracias, me confirma que grandes tonterías no digo.

    En tercér lugar, utilizaré este post, para empaparme en todo lo referentes a las interrupciones y con ello, reelaborar un nuevo post sobre el tema, que como todos los míos, sólo pretenden dar a conocer a los dummies, las posibilidades de Arduino y ponerlos en eápidamebnte en condiciones de hacer pequeños proyectos que les animen a seguir profundizando en el tema.

    En cuarto lugar, exploraré sistemáticamente tu blog con la seguridad de haber encontrado un lugar de gran nivel que soy capaz de entender.

    Nuevamente muchas gracias por todo.

    Le gusta a 1 persona

    Responder
  2. felixmaocho

    Con toda modestia, pues la audiencia de mi blog no es para tirar cohetes, te ofrezco mi blog para publicitar, por supuesto gratuitamente, tus proximos cursos,

    Personalmente tan solo siento que sean presenciales y en Logroño, ¿No has pensado en un manual de auto aprendizaje básico, en ebook o pdf, vendido conjuntamente con un kit especialmente preparado para seguir las clases y ejemplos prácticos que se vendiera por Internet? Ya tendrías alguien que lo publicitara y un cliente, pues tengo muchos amigos que se jubilan y es el regalo perfecto.

    Me gusta

    Responder
  3. eduardo vergara

    buenisimo post muchas gracias por poner todos tus conocimientos a disposición de la comunidad, como corrección cuando comienza el tema «Interrupciones Internas en Arduino», el segundo parrafo dice «Timer cero y UNO son de 8 bits y el temporizador uno es de 16 bits.», siendo que el timer cero y DOS son de 8 bits si es que se refiere al tipico atmega328. sé que es un error menor pero queria notificarlo. Saludos

    Me gusta

    Responder
  4. Pingback: Saber Más Avanzado 2017 | Aprendiendo Arduino

  5. Pingback: Saber Más Iniciación Arduino 2017 | Aprendiendo Arduino

  6. Pingback: Saber Más Arduino Empresas | Aprendiendo Arduino

  7. Felix

    Buenos días, me gustaría que me resolvieras una pequeña duda. Tengo en una placa Mega las 6 interrupciones conectadas a lectura de pulsos de 6 motores, y me gustaría tener una única subrutina de atención a interrupciones para los 6 motores. ¿Cómo podría saber el origen de la interrupción que estoy atendiendo?. Evidentemente la solución es poner una subrutina diferente a cada interrupción, aunque vayan a hacer lo mismo, pero ¿se puede hacer en una única subrutina?
    Muchas gracias por adelantado.

    Le gusta a 1 persona

    Responder
    1. jecrespom Autor

      Hola Félix,

      Cuando ocurre una interrupción, en función del pin que la provoca llama al vector de interrupción correspondiente a la interrupción de ese pin. 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 (ISR) y se ejecuta la rutina.

      Puedes poner en todos los vectores de interrupción la misma rutina, pero si quieres saber el origen de la interrupción tendrás que comprobar en el ISR la condición de disparo de los pines de interrupción, lo que creo que complica la rutina y podría darse el caso que la condición de disparo de la interrupción ya no se cumpla al comprobarlo en el ISR

      Otra opción es tratar de buscar si algún registro te da esa información, pero he buscado por encima y creo que no es posible.

      Creo que lo más práctico es usar 6 ISR una para cada interrupción, pero está bien buscar más allá, así que me he permitido preguntarlo en stackexchange que hay gente muy especializada. Puedes seguir las respuestas en https://arduino.stackexchange.com/questions/50405/how-to-know-the-external-interrupt-origin-from-the-isr

      Me gusta

      Responder
  8. Pingback: Saber más Arduino Avanzado 2018 | Aprendiendo Arduino

Deja un comentario

Este sitio utiliza Akismet para reducir el spam. Conoce cómo se procesan los datos de tus comentarios.