Interrupciones!
Interrupciones, ¿qué son? Hay personas que intermitentemente te distraen de tu trabajo. Jaja, bueno, quizás… pero lo que en realidad queremos saber es que son las interrupciones en un contexto de microprocesadores y electrónica embebida.
Así que, ¿qué son las interrupciones en ese contexto? Bueno, hay una forma por la cual un procesador puede ejecutar su programa principal mientras monitorea continuamente la ocurrencia de algún tipo de evento, o interrupción. Este evento incluso puede ser activado por cualquier tipo de sensor o entrada, como un botón, o incluso internamente por un timer que cuente hasta un número en particular.
Vemos el evento, ¿y luego qué?
¿Qué pasa cuando el evento y la interrupción ocurren? El procesador toma nota inmediata, guarda su estado de ejecución, ejecuta un pequeño código regularmente llamado manejador de interrupción (interrupt handler) o rutina de servicio de interrupción (interrupt service routine), y luego vuelve a cualquier cosa que estaba haciendo antes. ¿Cómo sabe que código ejecutar? Esto se configura en el có de tu programa. El programador define donde el procesador debería empezar a ejecutar el código si ocurre una interrupción en particular. En Arduino, usamos una función llamada attachInterrupt() para hacer esto. Esta función toma 3 parámetros. El primero es el número de la interrupción, lo que dice al microcontrolador que pin monitorear. El segundo parámetro de esta función es la localización del código que queremos que se ejecute cuando una interrupción es activada. Y el tercero dice que tipo de activación buscar, un alto lógico, un bajo lógico o una transición entre los 2. Veamos un simple ejemplo de código para que todo esto tenga sentido:
/* Simple Interrupt example by: Jordan McConnell SparkFun Electronics created on 10/29/11 license: Beerware- feel free to use this code and maintain attribution. If we ever meet and you are overcome with gratitude, feel free to express your feelings via beverage. */ int ledPin = 13; // LED is attached to digital pin 13 int x = 0; // variable to be updated by the interrupt void setup() { //enable interrupt 0 (pin 2) which is connected to a button //jump to the increment function on falling edge attachInterrupt(0, increment, FALLING); Serial.begin(9600); //turn on serial communication } void loop() { digitalWrite(ledPin, LOW); delay(3000); //pretend to be doing something useful Serial.println(x, DEC); //print x to serial monitor } // Interrupt service routine for interrupt 0 void increment() { x++; digitalWrite(ledPin, HIGH); }
Antes de ejecutar este código, asegúrate de conectar un lado del botón al pin 2 y el otro lado del botón a tierra. El loop principal de este programa apaga el LED cada 3 segundos. Mientras esto ocurre, este programa vigila el pin digital 2 (que corresponde a la interrupción 0) para un flanco de bajada. En otras palabras, busca un cambio de voltaje que vaya de un alto lógico (5V) a un bajo lógico (0V), o tierra, que ocurre cuando el botón es presionado. Cuando esto ocurre se llama a la función incremento. El código dentro de esta función es ejecutado, la variable x es incrementada y el LED se enciende. Luego el programa retorna donde estaba en el loop principal anterior. Si te pones a jugar te darás cuenta que el LED se enciende por lo que aparenta ser un período de tiempo aleatorio pero nunca mayor a 3 segundos. Cuanto tiempo se encienda el LED depende de donde se interrumpa el código en el loop principal. Por ejemplo, si la interrupción fue activada justo en el medio de la función delay, el LED debería encenderse por cerca de 1.5 segundos luego de presionado el botón.
¿Cuáles son las ventajas de este método?
Hasta este punto te deberías estar preguntando, ¿por qué usar las interrupciones? ¿Por qué no simplemente usas un digitalRead en el pin 2 para ver su estado? ¿Hará lo mismo? La respuesta depende de la situación. Si sólo te preocupas de cual es el estado del pin en un cierto punto de tu código o tiempo, entonces el digitalRead probablemente sea suficiente. Si querías que el pin sea monitoreado constantemente, podrías comprobarlo frecuentemente con el digitalRead. Sin embargo, podrías perder fácilmente la información entre los períodos en que esta se lee. Esta información perdida podría ser vital en muchos sistemas en tiempo real. Sin mencionar que mientras más seguido revises la información, más tiempo de procesamiento se va gastando en hacer eso que en ejecutar un código útil.
El ejemplo del auto…
Tomemos el sistema que monitorea y controla el freno anti-lock de los autos como un ejemplo cr&icute;tico de tiempo. Si el sensor detecta que el auto esta apunto de perder tracción, en realidad no importa que parte del programa se está ejecutando, por que se necesita hacer algo en esta situación inmediatamente para asegurar que se mantenga la tracción y ojalá evitar un accidente o cosas peores. Si tu solo acababas de leer la información del sensor en esta situación, el estado del sensor podría ser verificado muy lentamente y el evento podría ser perdido completamente. La belleza de las interrupciones es que pueden provocar una ejecución inmediata, cuando sea necesario.
Una comprobación rápida puede resolver el problema
Un problema común con las interrupciones es que pueden ser activadas muchas veces para un evento único. Si ejecutamos el código de arriba, notarás que si presionas el botón una sola vez, el x se incrementará muchas veces. Para explorar por que sucede esto, debemos mirar la señál misma. Si tomamos un osciloscopio para monitorear el voltaje del pin en el momento en que presionamos el botón, se vería algo como esto:
Mientras la transición del pin es de alto a bajo, durante el proceso hay muchos “spikes” que pueden causar múltiples interrupciones. Hay muchas formas de remediar esto. A menudo lo puedes resolver con un hardware añadiendo un filtro RC apropiado para suavizar la transición o puedes arreglarlo por software ignorando temporalmente las interrupciones consecutivas por un pequeño período de tiempo luego de la activación de primera interrupción. Volviendo a nuestro código, adicionemos un arreglo que permita a la variable x que sólo sea incrementada cada vez que se presiona el botón en vez de que se incremente muchas veces.
/* Simple Interrupt example 2 by: Jordan McConnell SparkFun Electronics created on 10/29/11 license: Beerware- feel free to use this code and maintain attribution. If we ever meet and you are overcome with gratitude, feel free to express your feelings via beverage. */ int ledPin = 13; // LED is attached to digital pin 13 int x = 0; // variable to be updated by the interrupt //variables to keep track of the timing of recent interrupts unsigned long button_time = 0; unsigned long last_button_time = 0; void setup() { //enable interrupt 0 which uses pin 2 //jump to the increment function on falling edge attachInterrupt(0, increment, FALLING); Serial.begin(9600); //turn on serial communication } void loop() { digitalWrite(ledPin, LOW); delay(3000); //pretend to be doing something useful Serial.println(x, DEC); //print x to serial monitor } // Interrupt service routine for interrupt 0 void increment() { button_time = millis(); //check to see if increment() was called in the last 250 milliseconds if (button_time - last_button_time > 250){ x++; digitalWrite(ledPin, HIGH); last_button_time = button_time; } }
Este arreglo funciona porque cada vez que el manejador de interrupciones es ejecutado, compara el tiempo actual adquirido por la función millis() con el tiempo en el que se llamo por última vez al manejador. Si es dentro de una ventana de tiempo definida; en este caso un cuarto de segundo, el procesador inmediatamente vuelve a lo que estaba haciendo. Si no es así, ejecuta el código dentro del if declarado e incrementa la variable x, encendiendo el LED y actualizando la variable last_button_time
para que la función tenga un nuevo valor para comparar cuando sea activada en el futuro.
Directo al grano
Debes saber que es importante mantener el manejador de interrupciones lo mas corto posible porque evita que el procesador siga corriendo su código normal, previniendo que se dejen de atender nuevas interrupciones que puedan ocurrir mientras se ejecuta el código del manejador. Lo mejor es simplemente actualizar el estado de las variables y luego salir, y dejar que tu programa normal haga el resto del trabajo necesario. También es necesario tener en cuenta que la función delay() no trabaja dentro del manejador de interrupciones y no debería ser usada.
Casos especiales
Es posible reasignar interrupciones usando la función attachInterrupt() nuevamente, pero también es posible removerlas llamando a la función detachInterrupt(). Si hay una sección de código que es sensible al tiempo, y es importante que las interrupciones no sean llamadas en ese período de tiempo, puedes desactivar las interrupciones temporalmente con la función noInterrupts() y luego volver a activarlas con la función interrupts(). Esto definitivamente es muy útil para muchas situaciones.
Gracias por leer
Bueno, hemos aprendido un poco sobre lo básico de las interrupciones y esperamos que se hayan dado cuenta de lo útil que pueden ser. Considera integrar interrupciones en tu próximo proyecto y siempre siéntete libre de compartirlas con nosotros! Puede que logres una portada por tus proyectos.
más info.
Muchas gracias por las explicaciones. La verdad es que has explicado todo clarísimo.
No obstante tengo una duda, si quiero leer 6 canales o entradas y por lo tanto 6 interrupciones, como me lo hago para manejarlo puesto que tengo un ARDUINO UNO, que solo tiene dos interrupciones externas. Cómo gestiono más de dos interrupciones?
Muchas gracias una vez más.
Salu2