Interrupciones
Una de las mejores estrategias antibloqueo son aquellas que vienen con la placa en lugar de nosotros desarrollar una solución de programación compleja y elaborada.
En este caso vamos a hablar de las interrupciones con Arduino.
AVISO: El concepto de interrupción es más avanzado en cuanto a la programación y se debería utilizar ante todo, para lectura de sensores que requieren de una importancia especial. Como la interrupción de un proceso de una máquina que realiza un trabajo peligroso o la necesidad de leer en tiempo real el estado de un sensor, cuando el código en bucle no atiende con prioridad a las lecturas del mismo.
Interrupciones
Las interrupciones son señales que permiten al chip parar el flujo normal de un sketch y manejar tareas que requieren una atención inmediata antes de continuar con lo que estaba haciendo.
IMPORTANTE: No todos los pines pueden ejecutar interrupciones. Solo algunos pines permiten la ejecución de estas entradas especiales asociadas a una interrupción.
Board | INT 0 | INT 1 | INT 2 | INT 3 | INT 4 | INT 5 |
Uno, Ethernet | 2 | 3 | ||||
Mega2560 | 2 | 3 | 21 | 20 | 19 | 18 |
Leonardo | 3 | 2 | 0 | 1 | 7 |
En esta tabla podemos observar que para cada placa la interrupción 0 se corresponde con el número del pin al que tiene asociado el modo interrupción.
Para solucionar este modelo de forma rápida usaremos la función digitalPinToInterrupt(), en la que nosotros introduciremos el pin entre paréntesis de donde queremos conectar nuestro sensor de interrupción y el compilador ya se encargará de asociarlo a su corrrespondiente índice de la tabla en función de la placa. Si usamos un pin que no aparece contemplado en la tabla, el programa no funcionará.
La finalidad de estos pines son las de interrumpir el desarrollo del programa para ejecutar una función que declararemos de forma externa.
Las interrupciones suelen ser funciones muy breves, ya que una interrupción que se prolonga mucho tiempo puede causar que otras interrupciones se retrasen. Tampoco conviene que la interrupción bloquee el programa.
Las funciones que más tiempo de procesamiento conllevan son los bucles y la escritura de mensaje por el puerto serie, por lo que deben evitarse en su mayoría.
Modos de interrupcion
Al fin y al cabo, un pin es la lectura de una señal digital. En este caso , los eventos que vamos a poder recopilar van a ser los siguientes. En el siguiente enlace tenemos la documentación de los distintos modos de interrupción.
- CHANGE -> El evento se activa cuando la señal cambia de estado de alto a bajo o de estado bajo a alto
- RISING -> El evento se ejecuta solo cuando la señal pasa de estado bajo a alto
- FALLING -> El evento se ejecuta solo cuando la señal pasa de estado alto a bajo
- LOW -> El evento se ejecuta siempre que la señal se encuentra en estado bajo
- HIGH ( Arduino DUE ) -> El evento se ejecuta siempre que la señal se encuentra en estado alto
Lectura en tiempo real
Como podremos ver en el siguiente ejemplo, no vamos a definir absolutamente nada en el bucle, ya que la interrupción no se ejecuta dentro del bucle, sino que interrumpe el funcionamiento iterativo para salir tal y como se ha declarado.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | boolean state; int LEDpin = 4; void isr(){ digitalWrite(LEDpin, state); Serial.println( "CHANGE" ); }; void setup() { Serial.begin(9600); attachInterrupt(digitalPinToInterrupt(2),isr, CHANGE); } void loop() { } |
Podemos probar su funcionamiento si escogemos la detección con RISING o FALLING.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | int sum = 0; void isr(){ sum= sum +1; Serial.println(sum); }; void setup() { Serial.begin(9600); attachInterrupt(digitalPinToInterrupt(2),isr, RISING); } void loop() { } |
Volatile Keyword
A la hora de especificar una variable que puede ser ejecutada dentro de un programa, es posible que Arduino almacene esa variable en un registro temporal, de manera que puede perderse ese registro durante la ejecución del programa. Para ello, existe la palabra volatile que especifica al compilador que esa variable se almacene en la memoria RAM para que no se sustituya por otro contenido.
Esto solo es necesario cuando la variable es modificada en sistemas concurrentes y en el caso de Arduino solo tiene sentido en las llamadas a interrupciones.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | int pin = 13; volatile int state = LOW; void setup() { pinMode(pin, OUTPUT); attachInterrupt(0, blink, CHANGE); } void loop() { digitalWrite(pin, state); } void blink() { state = !state; } |
Interrupción de un proceso
Para aplicar este modelo de interrupciones, imaginemos que una máquina esta desarrollando una acción que puede ser peligrosa y nos gustaría cortar la actividad en el momento que pulsamos un botón de peligro.
Es bastante posible que nuestro proceso este operando en un bucle propio que no sea el principal, por lo que tenemos que hacer que en cada iteración haga una lectura de una variable de habilitación que determine si ha de continuar o cortar de inmediato con la instrucción break.
Además habilitaremos una nueva interrupción en el pin 3 para rearmar el bucle y continuar con su proceso.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | volatile bool stop = false ; void isr_stop(){ Serial.println( " Loop OFF " ); stop = true ; }; void isr_reset(){ Serial.println( " Loop ON" ); stop = false ; }; void setup() { Serial.begin(9600); pinMode(buttonRESET, INPUT); attachInterrupt(digitalPinToInterrupt(2),isr_stop, RISING); attachInterrupt(digitalPinToInterrupt(3),isr_reset, RISING); } void loop() { if (stop){ for ( int i = 1; i < 1000; i++) { Serial.println(i); if (stop){ break ; } } } } |
Más teoría sobre interrupciones
El nucleo del software usa interrupciones para manejar los datos que se introducen por el puerto serie, para medir el tiempo de delay() o millis() y asociar un trigger (disparador) a la función attachInterrupt.
Librerías como Wire, Servo usa interrupciones cuando un evento ocurre, de manera que el código no tiene que chequear continuamente la aparición de un evento.
Existe una función interrupts(), que se ha de ejecutar después de noInterrupts(). Esto hace que puedan volver a funcionar las interrupciones de forma correcta, ya que algunas funciones pueden no funcionar de forma correcta (por ejemplo, las comunicaciones) ante la presencia de interrupciones y se pueden deshabilitar con noInterrupts().
Timers
Una placa Arduino estándar tiene tres timers (Arduino Mega tiene 6) que gestionan las tareas. (time-based tasks).
- Timer0 → millis(), delay(), analogWrite (5,6)
- Timer1 → analogWrite(9,10) Servo Library
- Timer2 → analogWrite(3,11)
For 16MHz:
Prescale | Time per counter tick | Max Period |
1 | 0.0625 uS | 8.192 mS |
8 | 0.5 uS | 65.536 mS |
64 | 4 uS | 524.288 mS |
256 | 16 uS | 2097.152 mS |
1024 | 64uS | 8388.608mS |
In general:
- Max Period = (Prescale)*(1/Frequency)*(2^17)
- Time per Tick = (Prescale)*(1/Frequency)
Los Timers son contadores que cuentan pulsos en función de un origen de tiempos (timebase). El timer consiste en un contador digital de 8-bit ó 16-bit. Por defecto la frecuencia de reloj es de 16Mhz , que es el número de ejecución de instrucciones y se puede reducir por un divisor (Prescale). Por defecto todos los timers tienen un valor de preescalado de 64. Los parámetros de estos timers se encuentran en unos registros timer frequency que podemos modificar y nos permite determinar los periodos de una señal PWM.
Puedes acceder además a los ejemplos de código que desarrollaremos durante el curso a través de nuestro Github.