Felix Maocho

Para quien le interese lo que a nosotros nos interesa

Arduino – Manejo de interrupciones

Por Félix Maocho
9/1/2016

Objetivo de este post

 

Explicar en qué consisten las interrupciones y como se programan.

Una interrupción es un suceso que interrumpe la actividad normal de nuestro microprocesador, en cualquier momento, y hay que pasar a atenderlo convenientemente.

Material

Arduino UNO, 2 led, en la prueba se usan uno rojo y uno blanco, 1 pulsador .2 resistencias de 220 ohm para proteger los leds y la propia tarjeta de cortocircuitos, 1 tarjeta de prototipado o protoboard ,cables de conexión macho/macho

La vida normal y corriente esta llena de interrupciones. Estamos trabajando y suena el timbre de la puerta, interrumpimos nuestro trabajo y abrimos la puerta. Otro ejemplo, vamos de caza y de repente el perro hace la muestra y disparamos la escopeta. Uno mas, vamos circulando por una carretera y de repente se enciende una luz del depósito en el tablero de mandos, inmediatamente nos ponemos alerta, para parar en la siguiente estación de servicio.

Como ven, las interrupciones son acontecimientos que aparecen de improviso, (un amigo, la liebre, el depósito vacío), que hacen saltar una señal,(el timbre de la puerta, la muestra del perro, luz del salpicadero), que nos indica que debemos interrumpir nuestro quehacer de ese momento, para realizar una acción previamente prevista realizar, aunque no sabemos cuando será el momento adecuado para realizarla, (abrir la puerta, disparar, llenar el depósito).

Es importante resaltar, que podríamos arreglárnoslas para vivir sin interrupciones, podemos mirar periódicamente por la mirilla de la puerta, para ver si hay alguien esperando, cazar sin perro, levantando ni nosotros mismos las piezas, o mirar periódicamente la aguja del combustible. Como poderlo hacer, lo podemos hacer, pero hay que reconocer que nuestra vida sería más incómoda sin esas interrupciones que nos permiten trabajar tranquilamente mientras las interrupciones no ocurran.

Si hay la posibilidad de que cuando ocurra algo que es probable que pase, pero que desconocemos el instante en que va a pasar, se interrumpa automáticamente lo que estaos haciendo y se inicie una acción de adecuada respuesta, pues nuestra vida será más cómoda.

En Arduino ocurre exactamente lo mismo, si no existieran las interrupciones podríamos periódicamente investigar si ha ocurrido aquello que esperamos pero no sabemos cuando va ocurrir, incluso esa acción tiene técnicamente un nombre “polling” (sondeo) y el tiempo entre dos sondeos consecutivos se llama “polling rate”, (índice de sondeo) y en función del resultado del sondeo iniciar o no una determinada acción.

Por ejemplo en el post Utilizar los pin digitales como INPUT para el control de la corriente, así lo hacíamos. Encendíamos una luz mirando periódicamente si en un pin de “INPUT” aumentaba el voltaje un circuito por haber oprimído un pulsador, con el fin de saber cuando teníamos que encender y apagar un led. Eso mismo podemos hacerlo con las interrupciones, considerando que se ha producido el “evento” previsto, el cambio de voltaje en un circuito y en ese instante, aprieta el “gatillo” que inicia la acción prevista, “Si está apagado el led lo encendemos y viceversa”.

Claro está que nuestra tarjeta Arduino solo entiende de voltajes, por tanto no vale poner timbres, o encender luces, sino solo vigilar los cambios de voltajes en circuitos, esos son los “eventos” que ella puede percibir. Habitualmente serán los sensores, (de presencia, de movimiento, de nivel del depósito etc.), los que generen esos cambios de voltaje que se transforman en “eventos” que pueden “avisar” a Arduino.

Como funcionan las interrupciones

Cuando configuramos Arduino, por tanto en el programa “setup”, incluimos la petición de controlar un “evento”, que de producirse, aprieta un “gatillo”, técnicamente se llama “tigger”, (gatillo), que dispara la ejecución de una determinada función la “Interrupt Service Handler o ISH” (Servicio de gestión de interrupción), que llama y pone en marcha la función que realiza la “acción” prevista, que técnicamente se llama “call back function”, (función de respuesta de llamada). Así pues, para controladamente la función “loop” ahí donde se encuentre, realiza la función prevista y una vez realizada la “acción”, continúa “loop” en el punto donde lo dejo como si nada hubiera pasado.

Ventajas de las interrupciones.

Párrafo para inexpertos , (puedes saltarlo si eres mínimamente usuario de Arduino)

Las cosas se pueden resolver, como dijimos sin necesidad de interrupciones, pero más incómodamente. Nosotros las resolvimos el encender y apagar un led en el ejemplo del post Utilizar los pin digitales como INPUT para el control de la corriente pero un poco chapuceramente.

El primer problema que nos encontramos es que aunque Arduino comparado con un PC actual, es terriblemente lento, no lo es tanto comparado con el mundo físico y como se concentra en hacer poquitos cosas, lo hace baste eficientemente.

Aunque su procesador va a 16MHz, es decir realiza 16.000 operaciones por segundo, realiza muchas cosas internamente por lo que la velocidad máxima que se registra en la función “loop”, que es lo que a nosotros nos vale para algo, se aproxima más o menos en 1250 bucles por segundo, si este no hace prácticamente nada, Cuando hace cuatro cosas, como en nuestro caso, estudiar el pin y apagar o encendre un led, daría algo así como 300 bucles en un segundo, es decir tiene un “polling rate” aproximado de dos centésimas de segundo lo cual en el mundo digital no es mucho. Pero un señor apretado un botón, tarda un mínimo una y dos décimas de segundo en retirar el dedo, es decir, en condiciones normales, Arduino habrá detectado al menos 30 veces que el botón está apretado. ¿Cómo saber que sólo hemos apretado una vez el botón y no 30?. Nosotros en el programa hemos puesto un “delay(3000)” en el caso que detectemos que se ha apretado el botón, para dar tiempo a retirarlo.

No es la única solución, más útil, es poner una variable cuyo valor cambie al cambiar el voltaje, inicialmente esta con valor “0” en el primer “loop” que detecta que se ha oprimido el botón pone la variable a valor 1, mientras esté en valor 1 y el botón este oprimido, sabemos que no ha levantado el dedo y por lo tanto es la misma pulsación cuando detectemos que levanta el dedo, volvemos a poner la variable a valor “0” y así la dejamos hasta la siguiente pulsación.

Como ven, esta segunda solución que se me ha ocurrido ahora, es mejor, y demuestra algo que siempre el ocurrirá, en informática, hay muchos caminos para hacer la misma cosa, y no siempre el primero que se nos ocurre es el mejor.

La solución que dimos, bastante rupestre he de reconocerr, consiste en aproximar el “polling rate”a tiempos mas útiles en el mundo de la física, o sea a décimas de segundo instalando un “delay”. que retarda la ejecución del “loop”, algo que siempre podemos hacer y basta con jugar con el tiempo del “delay” para encontrar una solución más o menos satisfactoria.

Pero, resuelto ese problema, tenemos el contrario. ¿Y si el “polling rate” es tan grande que no nos enteramos que han apretado el pulsador en el intermedio?. Lo que pasa en el periodo de tiempo entre dos lecturas consecutivas, ocurre sin que el programa de control se entere, es decir podemos, (en principio, siendo muy rápidos), pisar y levantar el dedo entre dos sondeos consecutivos sin que la máquina se entere.

Pero dirás, ¡eso no es posible, no quedamos que Arduino es muy rápido!. – Pues según, si por ejemplo en la ejecución del ciclo hemos puesto un “delay” el ciclo se queda “suspendido” por el tiempo que se indique. En el programa que vimos, eso no puede pasar si no se ha pulsado el botón, pero si se ha pulsado, hay tres segundos de programa suspendido, tiempo más que suficiente para que quien ha encendido la linterna, se arrepienta inmediatamente y vuelva a apretar para apagarla dentro de los tres segundos de “polling rate” que tiene el bucle en ese caso, la linterna no responderá entonces.

Todos estos problemas se simplifican, igual que ocurre en la vida real utilizando interrupciones, tu haces lo que convenga con Arduino, que si se produce el “evento” que da lugar a una interrupción automáticamente para lo que esté haciendo en ese momento y dispara una única vez la acción prevista, para continuar con el normal funcionamiento del “loop”

Eventos que es capaz de detectar Arduino

Como dijimos, Arduino solo es capaz de controlar cambios de tensión al estudiar los pin como INPUT, pero ademas, sólo algunos pin pueden controlar excepciones, es decir que cuando detectan uno determinado evento, automáticamente detengan ejecución normal, (ordenadamente para poder volver en su momento al mismo punto), y disparar una función especial que se llama Interrupt Service Handler o ISH (Servicio de gestión de interrupción), que inicia la ejecución de una determinada función y una vez acabada esa función, rearrancar nuevamente la función “loop” ahí donde se hubiera quedado parada.

Para definir una interrupción necesitamos pues tres cosas:

  • Un pin de Arduino capaz de percibir los eventos que se produzcan
  • Un disparador, o “trigger” que ponga en marcha a excepción cundo se produce el evento
  • Definir la “call back function”, (función de vuelta de la llamada), a ejecutar

Pines capaces de detectar interrupciones

Solo dos pines, el pin 2 y el pin 3 de Arduino UNO están capacitados para controlar interrupciones que para mayor confusión e incomprensiblemente, se llaman interrupciones “0” y “1”. El pin 2 controla la interrupción “0” y el pin 3 la “1”

Otras tarjetas controladoras de la familia Arduino tiene bastantes mas pines capaces de controlar interrupciones, porque si por ejemplo, en un robot queremos controlar con interrupciones la falta de luz para encender faros, el choque con obstáculos para eludirlos, controlar la carga de la batería, para avisar cuando esté baja y el calentamiento del motor Para aumentar la refrigeración, necesitaremos una tarjeta capaz de detectar cuatro interrupciones y no dos, como tiene Arduino UNO y haberlas, haylas, pero claro está, son más caras. En este cuadro pongo las más conocidas y las interrupciones que controlan,

Arduino UNO ,     pin 2 = 0, pin 3 = 1
Arduino MEGA,   pin 2 = 0, pin 3 = 1, pin 3 =2, pin 20 =3, pin 19 =4, pin 18 = 58
Arduino DUE       Todos los pines del DUE pueden usarse para interrupciones.
Arduino Leonardo  pin 3 = 0, pin 2 = 1,  pin 0 = 2, pin 1 = 3, pin 7 = 4

Por tanto un primer problema de compatibilidad, para reutilizar un programa escrito para otra tarjeta Arduino, es averiguar por que pines funcionan sus excepciones, porque como vemos, el caos que reina en esta materia es monumental y si por ejemplo estás utilizando una Tarjeta Leonardo  pero el programa se escribió para una tarjeta Arduino UNO, pese a ser posterior y más potente no son 100$ compatibles pues las interrupciones “0” y la “1” el programa se referirá a los pines 2 y 3 mientra que en Leonardo tendremos que indicar respectivamentelos pines 3 y 2.  Una razón más, para a parametrizar y documentar en la medida de lo posible los programas, pues de estar bien pensado, basta cambiar el valor de los parámetros al inicio del programa, en este caso de “2” a “3” y de “3” a “2, para que el programa escrito para Arduino Uno funcione sin problemas con Leonardo.

Eventos que se pueden detectar

Arduino solo puede controlar un evento, estudiar el voltaje de una linea, y compararlo como lo tenía en la lectura anterior. Resuelto que pines pueden detectar interrupciones, estudiemos los disparadores que son capaces de actuar. Estos disparadores en inglés se llaman “trigger”, (gatillos), que quizá son una forma más gráfica de llamarlos

  • LOW, La interrupción se dispara cuando el pin pasa a LOW.
  • CHANGE. Se dispara cuando cambia de HIGH a LOW o viceversa.
  • RISING, Dispara cuando cambia de LOW a HIGH.
  • FALLING, Dispara cambia de HIGH a LOW.

Y siguen las inconsistencias, en el caso de Arduino DUE, (y alguna otra tarjeta controladora), hay uno más, HIGH se dispara cuando el pin pasa a HIGH.

En la practica solo es suficiente, “CHANGE”, cuando interesa controlar el cambio en voltaje, “RISING”, (la mas utilizada) cuando buscamos identificar cuando un circuito sube de voltaje, o “FALLING” en caso contrario, algo que por ejemplo, se utiliza en los detectores que buscan fallos de corriente.

Como ven, internamente Arduino, utiliza algo parecido al “truco” que indique que podíamos haber utilizado en el programa para detectar una pulsación sin utilizar excepciones, “recordar” el estado anterior del pin y comparar si ha habido cambios en el pin o no. No es que yo sea y un “genio”, sino muy al contrario, en informática el invento de los “trucos de programación” se pierden en “la noche de los tiempos”, o sea, allá por el año 1945 y que desde entonces TODOS, genios y simples mortales, los utilizas una y otra vez de una forma u otra.

La acción a ejecutar

De forma genérica la función que dispara una interrupción se llema técnicamente “call back function”, y contiene la acción que queramos ejecutar cuando se produzca el evento. Es de estructura igual a las ya conocidas funciones “setup()” y “loop()” pero en este caso tiene el nombre que quieras poner, por lo que igual podemos llamarla “int0()” como “encender_apagar()”, o “cualquier_cosa ()”

El nombre es libre y los dos paréntesis “()”, indica que ni se la pasan, ni entrega ningún valor o parámetro. Igual ocurre con “setup()” y “loop()”, algo que no siempre ocurre, pues si por ejemplo, definimos una función que haga avanzar un robot, que se llame “avanza” el nombre es libre y la podremos llamar también “anda”, pero en este caso tendremos que pasar un parámetro que diga cuantas unidades quieres que avance por ejemplo pondríamos “avamza(5)” si queremos que avance 5 unidades.

Configurar la interrupción en el “setup”

En la función “setup()” añadiremos una linea que informa que queremos controlar una interrupción que tiene el siguiente sintaxis:

attachInterrupt(int, call_back, trigger);

“attachInterrupt”, (adjutarinterrupcion) configura a Arduino para prepararse a recibir interrupciones, “int” es la interrupción, en el caso de Arduino UNO solo puede tener el valor “0” si utilizamos el pin 2 o “1” si utilizamos el pin 3, En nuestro ejemplo utilizaremos la interrupción “0” por lo que el cable del botón habrá de entrar en el pin 2. call_back” es el nombre con que bautizamos a la función que indica la acción a realizar. En nuestro caso concreto la vamos a llamar “cambia_luz”.

Por ultimo, “trigger” es uno entre los “gatillos” que posee Arduino. Con el modelo UNO podía ser uno de los siguientes LOW, CHANGE, RISING, o FALLING. En nuestro caso, como el voltaje pasa de 0v a 5v al pulsar el botón, utilizaremos “RISING”. Podíamos utilizar “CHANGE”, pero es más confuso, pues lanzaría la ejecución de la función, tanto cuando se pulsa como cuando se suelta. Es decir, salvo que usemos algún “truco” en el software que lo impida, habría que estar pulsando el botón para que luciera el led,, apagándose la linterna tan pronto como lo soltáramos, pues el voltaje pasaría de 5v a 0v y si utilizamos “FALLING”, funcionaría perfectamente, con el matiz, que la función se lanza no en el momento de apretar el botón, sino en el de soltarlo.

Función “call back”

Como hemos quedado la vamos a llamar es “cambia_luz” y enciende el led si esta apagado y viceversa”. Por tanto tendrá este aspecto

funcion call back (2)

Veamos pues como quedaría el progrma

encendr y apagar con interrupción

Observen

Mantenemos la luz roja, aunque para el programa no hace ninguna falta, ni este led le controla a Arduino de ninguna manera, pero vale para tener constancia física de que hemos cerrado el circuito al oprimir el pulsador, si está cerrado la luz roja luce. Así que podremos hacer pruebas de mantener el dedo oprimido sin dudar si el pulsador cierra o no el circuito al mantener el dedo oprimiendo o por el contrario, oprimir tan rápido como podamos.

El cambio más importante viene derivado que sólo los pin 2 y 3 pueden detectar interrupciones por lo que cambiamos el cable con el voltaje que va a ser investigado del pin 11 al pin 2. Todo lo demás sigue igual que en el ejemplo de encender un led con un interruptor

Sin embargo el programa si tiene mucha simplificación

programa interrupcion (2)

Observen

Antes de algunas variables hemos colocado el termino “volatile”. Si se fijan son todas las que participan en la función “cambia_luz” . “volatile” no es un tipo especial de variable, es una palabra clave para que Arduino sepa que la variable se utilizada en una función Call Back que ejecuta una excepción, por lo que puede ocurrir que esta la cambie de valor fuera de la función “loop”.

Eso significa que la variable debe ser almacenada en la RAM, (memoria principal, que se borra cuando se apaga) y no en lo habitual que es en la ROM (memoria que permanece cundo se apaga) mucho más barata y abundante, pero que hay que cargar y descargar por programa según se utiliza, pues al poder ser cambiada por programas diferentes, tiene que estar siempre presente en el procesador, pues si bien Aduino es monotarea, cada Call Back es un programa (casi) independiente del principal, o sea estamos ante la cosa más parecida en lo posible a algo multitarea, por lo que las variables pueden ser cambiadas de forma incontrolada en cualquier momento por cualquiera de las Call Back’s existentes

Así pues, TODAS las variables que utilicemos en las funciones “Call Back”, pero sobre todo las que se utilicen tanto en el “loop” como en las Call Back, que habrá tantas como excepciones declaremos, las definiremos como “volatile” aunque en muchos. caso no sea esríctamente  necesario por no utilizarse en el “loop” . Pues de confundirte en eso, pueden ocurrir problemas de encontrarse valores aleatorios de una variable por errores que pueden resultar muy difíciles acotar y detectar.

Todo lo demás hecho en el programa ya lo deberéis conocer, por lo que hablaré de ello. Sin embargo hay unas reglas generales que debes conocer sobre las funciones Call Back.

La función Call Back puede hacer cualquier cosa, pero mientras estas haciendo algo allí, tiene parado todos los procesos incluido el “loop”, por lo que por un lado estas dejando de hacer lo que tenías que hacer y además, puede estar ocurriendo cosas que Arduino se debería detectar pero de las que no se entera, porque loop esta parado,

Por ello la norma para las “Call Back” es:

  • 1) Haz lo que quieras, pero no te tardes, acaba cuanto antes.
  • 2) Sólo se ejecuta una interrupción a la vez, si se está ejecutando una interrupción y se activa una segunda, no tendrá efecto, hasta que la primera termine.
  • 3) Si cuando salta una interrupción, el ”loop” estas en un “delay”, Arduio no se entera de las interrupciones, “delay” e interrupciones SE LLEVAN MAL.
  • 4) La función “millis()” (que es un contador de tiempos, (que aun no sabes utilizar), no incrementa su valor mientras dura la interrupción, si pretendías que suene un pitido cada cuarto de hora, sonará después de los 15 minutos más el tiempo perdido en las interrupciones que haya habido.
  • 5) Una solución a estos problemas es utiliza la función “delayMicroseconds()”, (que tampoco sabes aun utilizar pero es parecida a “delay()”),  que no bloquea las interrupciones.
  • 6) Las comunicaciones asíncronas (comunicarse con otros aparatos por ejemplo tu PC), pueden resultar dañadas por las interrupciones.
  • 7) Puedes deshabilitar las interrupciones por  el programa  instrucción “detachInterrupt()”.

Bien hasta aquí todo lo que había que aprender sobre interrupciones, sólo falta practicar y experimentar. Le aconsejo sustituir en el programa “RISING” por los otros “trigger” y comprobar su funcionamiento si lo que espera o no que ocurra, pasa o no. Luego puede insertar un “delay(3000)” en la función “loop” y comprobar que pasa cuando aprieta el botón y esta haciendo un “delay”

Otro experimento que le aconsejo hacer es poner otro “pushbutton”, contolado por el pin3  y probar que ocurre cuando tienes dos interrupciones a la vez, Además puede poner un “delay(3000)” en una de las “call back” de las interrupcioes y ver que ocurre cuando se lanza la segunda interrupcion estando ocupado en la primer.

A manejar Arduino solo se aprende experimentando.

Mandatos utilizados

Resumimos las funciones y mandatos de Arduino que hemos utilizado. De no entender el funcionamiento o la sintaxis de alguna buscarla en el Manual en español en PDF

Documentación
Párrafo /* ………. */
Comentario // …………

Declaración de variables y parámetros
Declaración de numero entero int par = valor
La palabra clave “volatile”

Ciclo de trabajo de Arduino
Función Setup que solo se ejecuta al incoo del programa void setup() { ……….}
Función Loop que se repite indefinidamente void loop() { ……………….}
Función “Call Back”

Mandatos en la función setup
Definir pin como salida pinMode(salida, OUTPUT)
Configurar la interrupción attachInterrupt(0, cambia_luz, RISING); // peticion de interrupción

Mandatos en la función de interrupción
Condicional if if variable = valor {….}  else (….)
Abrir un pin digital digitalWrite(pin, HIGH);
Cotrolar  una interrupcion  attachInterrupt(int, call_back, trigger);

Operar con variables
Cambiar el valor de una variable var = valor

Félix Maocho

Anuncios

10 enero 2016 - Posted by | Robotica | , , , , ,

Aún no hay comentarios.

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s

A %d blogueros les gusta esto: