miércoles, 9 de mayo de 2012

Arduino - Manejo de Interrupciones

Interrupciones

Una interrupción es una señal de un dispositivo conectado a una computadora o a un programa dentro de la computadora que causa que el programa principal se detenga y se realice algo más.

Después de que la señal de interrupción es capturada, la computadora reasume a correr el programa que estaba corriendo o inicia a correr otro programa. Basicamente, una sola computadora puede realizar solo una instrucción en un tiempo. Pero debido a que puede ser interrumpida, puede tomar turnos entre los programas o instrucciones que realiza. Esto se conoce como multitasking.

Un sistema operativo usualmente tiene algún código llamado interrupt handler. El interrupt handler da prioridades a las interrupciones y las guarda en una cola si más de una esta esperando a ser atendida. El sistema operativo tiene también otro programa llamado Scheduler, que se encarga de decidir cual programa recibe el control.

En general, hay dos tipos de interrupciones, de hardware y software. Una interrupción de software ocurre cuandouna aplicación termina o solicita ciertos servicios del sistema operativo. La interrupción de hardware ocurre cuando, por ejemplo, una operación de entrada o salida es completada como leer datos desde una memoria.

Podemos preguntarnos, que hay de diferente entre usar una entrada digital a realizar una interrupción. Simplemente, cuando se usa una entrada digital, tipicamente se lee el valor de dicha entrada realizando una instrucción, y después actuando dependiendo del valor con algún tipo de lógica. Dependiendo de la complejidad de la rutina, y la duración del cambo de estado en la entrada, es bastante posible no ver ocurrir el cambio en la entrada

Usando una interrupción, el código es literalmente interrumpido, y forzado a ramificarse y hacer otra ejecución, entonces el cambio de estado en la entrada no se pierde.

¿Cuál es el Beneficio?


Las interrupciones pueden ayudar a resolver problemas de timing que pueden ocurrir en la configuración del código o hardware.

Interrupciones en Arduino

EL procesador de cualquier Arduino tiene dos tipos de interrupciones, externas(external) y de cambio de pines(pin change). El Arduino uno cuenta con solo dos pins de interrupciones externas: INT0 e INT1, y estan asignadas a los pines 2 y 3. Estas interrupciones pueden ser puestas a activarse en subidas o bajadas de señal, o en bajo nivel. Las activaciones son interpretadas por el  hardware, y las interrupciones son muy rápidas. E

Por otro lado, las interrupciones de cambio de pin pueden ser activadas en muchos más pines. Para los Arduinos basados en ATmega168/328, pueden ser activados cualquiera de los 20 pines d eseñal del Arduino. Igualmente pueden ser activados en subidas o bajadas de señal, así que depende del código el establecer los pines que recibiran las interrupciones para determinar que paso, y manejarlo apropiadamente.

Demostración

Para demostrar la diferencia entre el usar las interrupciones y no usarlas, explicaré un ejemplo sencillo utilizando un led, un par de resistencias y un push button.

Para el ejemplo utilizaré mi Arduino Uno, y los pines digitales 2 y 4 como salida para controlar un LED, siguiendo el siguiente esquema:
(Nota: Aunque en la imagen dice Arduino Duemilanove, las conexiones son las mismas para el Arduino Uno, y cualquier Arduino con el  ATmega168/328)
Sin interrupciones

El código para esta demostración es el siguiente:

int pbIn = 2;          // Digital input on pin 2
int ledOut = 4;        // The output LED pin
int state = LOW;       // The input state

void setup()
{
  // Set up the digital Pin 2 to an Input and Pin 4 to an Output
  pinMode(pbIn, INPUT);
  pinMode(ledOut, OUTPUT);
}

void loop()
{
  state = digitalRead(pbIn);      //Read the button

  digitalWrite(ledOut, state);    //write the LED state

  //Simulate a long running process or complex task
  for (int i = 0; i < 100; i++)
  {
     // do nothing but waste some time
     delay(10);
  }
}

Como se puede observar es un código sencillo. Definimos el pin 2 como entrada y el 4 como salida, y una variable para controlar en que estado esta actualmente el botón. Lo interesante es que para poder comparar eficientemente la interrupción con éste código, agregamos un for que se repite 100 veces, dando un delay de 10 milisegundos cada vez.

Esto causará salidas impredecibles en el LED debido a que el for(que simula un proceso tardado) esta en un punto indeterminado en relación a cuando el botón es presonado. Algunas veces el LED cambia de estado inmediatamente, otras veces nada pasa, y otras veces es necesario mantener el botón presionado por un mayor tiempo para que el estado cambie y sea reconocido.

Usando Interrupciones

Ahora, modificando el código para agregar interrupciones, el LED cambia de estado cuando sea que el botón sea presionado, aunque el código sigue corriendo el mismo for tardado. El código es el siguiente:

int pbIn = 0;                  // Interrupt 0 is on DIGITAL PIN 2!
int ledOut = 4;                // The output LED pin
volatile int state = LOW;      // The input state toggle

void setup()
{
  // Set up the digital pin 2 to an Interrupt and Pin 4 to an Output
  pinMode(ledOut, OUTPUT);

  //Attach the interrupt to the input pin and monitor for ANY Change
  attachInterrupt(pbIn, stateChange, CHANGE);
}

void loop()
{
  //Simulate a long running process or complex task
  for (int i = 0; i < 100; i++)
  {
    // do nothing but waste some time
    delay(10);
  }
}

void stateChange()
{
  state = !state;
  digitalWrite(ledOut, state);
}

La palabra clave volatile es agregada a la variable state, esto causa que el compilador use la RAM en vez de un registro de almacenamiento. Esto es así debido a que el registro de almacenamiento puede ser temporalmente impreciso si es modificado por areas diferences a las del hilo principal. En el Arduino, esto se relaciona con el código que es activado por las interrupciones.

El método attachInterrupt(parámetro1, parámetro2, parámetro3) requiere lo siguiente:

  • parámetro1: A que interrupción se escuchará. Este es el número de la interrupción, no el número del pin digital.
  • parámetro2: A que función llamar, este debe ser un método que no tome parámetros y no retorne valores.
  • parámetro3: Que condición vigilar.
El arduino puede escuchar 4 tipos de condiciones(parámetro 3). Estos son:
  • LOW: La entrada esta en un estado LOW (0).
  • RISING: El estado de la entrada cambia de LOW a HIGH(0 a 1).
  • FALLING: El estado de la entrada cambia de HIGH a LOW(1 a 0).
  • CHANGE: El estado de la entrada cambia de HIGH a LOW, o de LOW a HIGH, es decir ha cambiado su estado.
Arduino tiene la habilidad de temporalmente ignorar todas las interrupciones. Esto es deseable en el caso en el que se tenga un código sensible que debe ser ejecutado sin interrupción. En este caso, se debe realizar un llamado al método noInterrupts(). Entonces ya que termine el código sensible, las interrupciones pueden reiniciarse con el método interrupts().

Las interrupciones también pueden ser cambiadas en cualquier momento usando el método attachInterrupt(). Tan pronto como esto sea hecho, si hay alguna otra interrupción asociada con el pin, será removida.

Además las interrupciones pueden también ser removidas usando el método detachInterrupt( número de la interrupción). 

Referencias:



  • http://www.codeproject.com/Articles/61023/Arduino-Platform-Interrupts-Introduction
  • http://arduino.cc/playground/Code/Interrupts
  • http://whatis.techtarget.com/definition/0,,sid9_gci212374,00.html
  • 8 comentarios:

    1. Por favor Dios, que abunde la gente como esta persona. Tio, felicidades, por fin tu, has conseguido que me entere de como funcionan las interrupciones externas. Un millon de gracias por explicarlo de esa forma macho. Estoy haciendo un proyecto, como hobby, nada importante y el caso es que estaba valorando si usar interrupciones externas en mi codigo pero gracias a tu explicacion me he decidido por no usarlas, ya que todo el codigo entero no es muy largo y no creo que necesite la interrupcion. Pero de todos modos gracias por tomarte tu tiempo escribiendo esto, a mi por lo menos me has ayudado, jeje. Un saludo.

      ResponderEliminar
    2. Me alegra haber podido ayudar.
      Saludos :)

      ResponderEliminar
    3. Gracias por el aporte, es bueno para iniciar a entender este tema.
      Tengo una duda, hice una variacion de este ejemplo de la siguiente forma:

      void setup()
      {
      pinMode(2,INPUT);
      pinMode(5, OUTPUT);
      pinMode(6, OUTPUT);
      pinMode(7, OUTPUT);

      attachInterrupt(0,AleaUnt,FALLING);
      }


      void loop() {
      for (int i=5 ; i <8 ; i++ )
      {
      digitalWrite(i, HIGH);
      delay(1000);
      }
      for (int i=5 ; i <8 ; i++ )
      {
      digitalWrite(i, LOW);
      delay(1000);
      } // wait for a second
      }

      void AleaUnt()
      {
      for (int x=0 ; x<20 ; x++)
      {
      for (int i=7 ; i >4 ; i-- )
      {
      digitalWrite(i, HIGH);
      delay(1000);
      digitalWrite(i, LOW);
      delay(1000);
      }
      }
      }

      Pero los delay de la rutina de interrupcion no los respeta. Me podrias decir cual es la falla con eso

      ResponderEliminar
      Respuestas
      1. Dentro de la rutina que atiende la interrupción (AleUnt() en este caso) no funciona delay() ni se incrementa milis(), se supone que una rutina de este tipo no debe tener retardos, debe ejecutarse lo más rápido posible y devolver el control al hilo principal de ejecución, cuando mucho se espera que modifique algunas variables que luego serán usadas en la función loop() donde si puedes usar y abusar de los retardos si lo deseas.

        Eliminar
    4. hola, sabes como interrumpir por serial?? gracias

      ResponderEliminar
    5. Hola! Antes que nada darte gracias por tan buena explicación, me ha sido de gran ayuda. Sin embargo, quisiera pedirte que por favor actualizes tu post, añadiendo información del uso de la función
      "digitalPinToInterrupt(pin)" del modo
      "attachInterrupt(digitalPinToInterrupt(pin), ISR, mode)". Así como que son 5 tipos de cambios pues te falto la condición 'HIGH'que es el completamente inverso a 'LOW' escucha cuando la entrada esta en HIGH. Esto para que tu post este mas completos para el resto de nuestros colegas.

      ResponderEliminar
    6. Buenas!

      Tengo dos interrupciones en mi programa. Si se está ejecutando la primera y se produce el hecho que activa la segunda: se detiene la primera interrupción y se ejecuta la segunda o se termina de ejecutar la primera y se comienza luego con la segunda?

      Gracias un saludo!!

      ResponderEliminar