Curso de Arduino – Manejo de Interrupciones -. Teoría
Por Félix Maocho
13/7/2017
.
Objetivo de este capítulo
Explicar que son las interrupciones para qué sirven y como se definen.
Conocer cuales son los pin capaces de producir interrupciones de acuerdo con el modelo de Tarjeta de Arduino que utilicemos
Aprender son y como se manejar el comando attachInterrupt los Trigger y las Call Back así como las variables volatilea .
Material necesario
Para este capítulo no se necesita ningún materianpues es exclusivamente teórico
Conocimientos previos necesarios
- Tener conocimiento de la Tarjeta Arduino UNO
- Saber manejar los digitales y analógicos tanto en INPUT como en OUTPUT .
Las interrupciones en la vida real
La vida está 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 atender a una actividad urgebnte, previamente prevista, (abrir la puerta, disparar, llenar el depósito). Lo que no sabemos es cuando será el momento adecuado para realizar la acción prevista.
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 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 es más incómoda sin esas interrupciones, que nos permiten vivir tranquilamente y ir haciendo nuestra labor habitual, hasta que de improviso una interrupción indique d que debemos trastocar nuestros planes de actuación, priorizando una acción específica de respuesta al motivo que ha producido lla interrupción.
Si existe el modo que cuando ocurra algo, que es probable que pase, pero que no sabemos el instante en que va a pasar, salte un aviso que nos permita interrumpir lo que estaos haciendo y iniciemos una acción de respuesta, nuestra vida será más cómoda.
Interrupciones vs polling
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 igualmente en función del resultado del sondeo contnuar lo que estamos haciendo, o iniciar una determinada acción.
Hasta ahora usábamos los pin digitales como INPUT para el control de algún suceso. Por ejemplo, encendíamos un led, mirando periódicamente si en un pin de INPUT aumentaba el voltaje de un circuito, por haberse oprimido un pulsador. Sabíamos que podían pulsar el botón, en cualquier instante, pero no podíamos saber cuando se iba hacer, Solución mirar frecuente, el estado de esa línea. O sea utilizar la técnica de polling, procurando tener el menor polling rate posible, con el objetivo de averiguar cuando teníamos que encender y apagar un led.
El polling tiene diversos problemas inevitables:
- 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 solventar en lo posible el problema, los microprocesadores son capaces de detectar interrupciones, lo que permite asociar una función a la ocurrencia de un determinado evento. Esta función se denomina tcnicamente ISR (Interruption Service Rutine), pero más frecuentemente la veremos denominada como Call Back,. (Devolver la llamada).
Por ejemplo cuando se produzca un determinado evento, como el cambio de voltaje en un circuito, se dispare una alarma que inicie la acción prevista, “Si está apagado el led lo encendemos y viceversa”.
Como Arduino solo entiende de voltajes, no vale poner timbres, o encender luces, sino solo vigilar los cambios de voltajes en circuitos, esos son los eventos que la tarjeta puede percibir. Habitualmente son 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 avisan a Arduino.
En programación, una interrupción o IRQ, (interrupt request o petición de interrupción) es una señal que recibida por el procesador o CPU, avisa que debe interrumpir el programa y pasar a ejecutar un código específico asociado a esa interrupción.
Una interrupción es pues una suspensión temporal del proceso, para realizar una subrutina asociada a la interrupción, que es independiente del programa principal. Una vez acaba de realizar esta subrutina, el procesador reanuda la ejecución del programa en el mismo punto donde lo dejo y con los mismos valores en las variables.
Los dispositivos que deseen poder avisar a la CPU de un evento precisan tener asignada una línea única denominada IRQ. A veces hay además un controlador de interrupciones capaz de establecer prioridades. Cuando varias petición de interrupción se activan a la vez, el controlador aplica esas prioridades para escoger la interrupción sobre la que informará al procesador principal. También puede darse el caso que una rutina de interrupción sea a su vez interrumpida para realizar otra rutina de mayor prioridad a la que se estaba ejecutando.ç. Si existe este controlador de interrupciones, y el mismo procesador controla las llamadas por teléfono y los sensores de incendios, podremos anteponer una señal de emergencia por incendio, antes de que cabemos de hablar por teléfono, que es una acción ejecutada por otra interrupción, evidente menos prioritaria. Si no existe el control de interrupciones, no se lanzará el aviso de incendio hasta que finalicemos de hablar por teléfono
Arduino Uno tiene dos pines específicos, el Pin 2 y el Pin 3, capaces de detectar interrupciones en cualquier instante, independientemente de lo que en ese momento esté haciendo la función principal loop.
Por poner otro ejemplo, en un proyecto de vigilancia domótica, puedes poner un sensor en la puerta de la calle, asociado al Pin2 y un sensor de riego ,asociado al Pin3 y según se abran la puerta de la calle o se detecte la tierra seca, lanzaremos dos tareas diferentes. En tarjetas de la familia Arduino más potentes, (y caras), existen mas pines que controlan interrupciones, por lo que en nuestro ejemplo podrías por otras alarmas, como para inundaciones, viento, etc.
En Arduino UNO los pines 2 y 3 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, quees la prioritaria y el pin 3 la 1 ,Otras tarjetas controladoras más caras de la familia Arduino tienen mayor número de interrupciones que se precisaran en proyectos medianamente complejos. 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 → 5
- 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
Adicionalmente todas las tarjetas tiene otra interrupción es el pin de reset, aunque este con un cometido fijo e invariable, sólo se dispara cuando detecta voltaje LOW y al accion que provoca viene preprogramada y no se puede cambiar el arranque y vuelta empezar del microcontrolador.
Hay un problema de compatibilidad, para reutilizar un programa escrito para otra tarjeta Arduino, tendremos que 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 1 se refieren a los pines 2 y 3, mientra que en Leonardo es al revés, 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 debidamente parametrizado y documentado, basta cambiar el valor de los parámetros al inicio del programa, en este caso del 2 al 3 y del 3 al 2, para que el programa escrito para Arduino Uno funcione sin problemas con Leonardo.
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). Cuando esto ocurre se para controladamente la función loop ahí donde se encuentre, realiza la función Call Back prevista y una vez acabada esta función, continúa la función loop en el punto donde lo dejo como si nada hubiera pasado.
comando attachInterrupt
La forma de informar a Arduino que utilizaremos interrupciones es la habitual, en la función setup() incluimos una instrucción attachInterrupt por cada una de las interrupciones que queramos atender. Este mandato especifica la función a invocar cuando se produce una interrupción, que tiene el siguiente sintaxis:
attachInterrupt(int, call_back, trigger);
Donde
- attachInterrupt (adjuntar interrupción), es el nombre del comando que avisa a Arduino que vamos a utilizar interrupciones
- int es el número de interrupción. 0 si utilizamos el pin2, 1 si utilizamos el pin 3
- call_back es el nombre de la función que pondremos en marcha en caso de que se produzca a interrupción.
- trigger es el evento o disparador que provoca la interrupción.
Eventos que es capaz de detectar Arduino
Arduino solo puede controlar un tipo de eventos, 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, (que es la mas utilizada) si 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 optimizado el mismo truco, el conocido polling que utilicé 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. la diferencia es que lo hace entre los ciclos de reloj que gobiernan el procesador, por lo que consigue un polling rate imbatible y con ello el fin de muchos de los problemas que presenta el polling manual
No es que yo sea y un genio, sino que en informática 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
La función que dispara una interrupción se llema técnicamente call back function, y ejecuta la acción adecuada al evento que genera a interrupción. Tiene una esc tructura igual cualquier función conocida, como lsas funciones setup() y loop(), su nombre es el que deseemos poner podemos llamarla int0(), encender_apagar(), o cualquier_cosa()
Los dos paréntesis “()”, indica que ni se la pasan, ni entrega ningún valor o parámetro. Igual ocurre con setup() y algo que no siempre ocurre, pues si por ejemplo, puede que deseemos pasarla algun parámetro de pediendo del momento en que ocurra, por ejemlplo definiremos el giro de un robot, que se llame giro, en funcion de la trayectoria que queramos llevar en ese instante, para asi rodear el obstáculo pero desviarnos lo menos posible. No obstante esta opción es posible pero no es corriente utilizarla.
Las norma para las Call Back son;
- 1) Haz lo que quieras, pero no 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. Hay equipos que priorizan las interrupciones y la interrupcion 0 se ejecuta antes de la 1 pero no es lo normal. Mas adelante veremos que pasa con Arduino UNO en concreto
- 3) delay e interrupciones SE LLEVAN MAL. A este punto dedicare mñas atención más adelante., pero en principio evita utilizar el comando delay
4) La función millis(). (el contador de tiempo de ejecución), 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. Como también existen interrupciones del sistema que son transparentes para el usuario, es el motivo por el que millis sólo mide un tiempo aproximado que tiende a retrasar siempre, Si necesitas tiempos perfectos utiliza un componente que sea reloj digital RTC (Real Time Clock), o reloj de tiempo real. Que lleva la cuenta de segundos minutos y horas además de día mes y año automáticamente como por ejemplo el DS1307 de Maxim.
- 5) Una solución a estos problemas es utiliza la función delayMicroseconds(), que no se bloquea con las interrupciones.
- 6) Las comunicaciones asíncronas (comunicarse con otros aparatos por ejemplo tu PC), pueden resultar dañadas por las interrupciones. Si se reciben datos cuando la CPU atiende una interrupcion pueden perderse.
- 7) Puedes deshabilitar las interrupciones por el programa mediante el comando detachInterrupt(), aunque nunca he encontrado un programa que saque provecho de esta posibilidad
Variables volátile
Hay que tener en cuenta otro punto muy importante. Las rutinas de excepción las llamadas Call Backs precisan un tipo de variables específicas llamadas volatile. volatile indica que la variable que definimos a continuación puede ser cambiada dentro de una interrupción. Llegada la interrupcion se finaliza el comando que se estuviera procesando en ese momento y se procede a guardar en la memoria ROM, (memoria que permanece cuando se apaga Arduino), el puntero que indica informa donde se quedo en el proceso loop y todas las variables que en ese momento estén en la CPU con el valor que tengan en ese momento para poder proseguir cuando se acabe la Call Back en el mismo punto donde se paro
Se guarda en la ROM, con el fin de dejar sitio libre en la RAM (memoria principal, que se borra cuando se apaga), que es donde se puede cambiar el valor de las variables. Si no se indicara de alguna forma cuales son las variables que participan en la Call Back el programa o no las tenía en ese momento en la RAM, o las guardaría en la ROM y no las dejaría modificar.
Por ello las variables que utilizan las Call Back se marcan como volatile. No es que definamos un tipo especial de variables, son las mismas variables, sino que un avisa a Arduino lpara que esas variables las tenga permanentemente en la RAM, de ahí el nombre, son volátiles, porque pueden modificarse e incluso desaparecer si se vva la luz. a diferencia del resto de las variables que permanecen en la ROM, una memoria barata y abundante que permanece estable aun desenchufada, (razón por la cual se conservan los programas en la tarjeta de una vez a otra quer las uses), pero que no permite el cambio dinámico del contenido, hay que mandar borrarlo y reescribirlo con el nuevo valor. El programa, (la función loop), cundo as necesita las llama y borra de la rom y las vuelve a depositar con su nuevo valor en la cuando las abandona. Las variables volatile, puede necesitarlas en cualquier momento una función Call Back.
El proceso es bastante trasparente a el usuario que simplemente las etiqueta como volatile e incluso puede hacerlo con cualquier variables, pero hay que tener en cuenta que las variables volatile permanecen siempre almacenadas en la RAM, memoria escas y cara y no en la ROM, que es una memoria mucho más barata y abundante, ocupando un espacio en la RAM que roba para la ejecucón de los comandos del programa. Por otra parte si etiquetas indebidamente una variable como volatile, pudiera ser modificada inesperadamente por la Call Back algo que tampoco es deseable.
Por tanto, TODAS las variables que utilicemos en las funciones Call Back, y especialmente las que se utilicen tanto en el loop como en las Call Back, que habrá tantas como excepciones declaremos, las definiremos como volatile.
Por otra parte no debemos “reciclar “ variables volátile para varios usos, algo que podemos hacer con variables normales. Por ejemplo se suelen reciclar los índices de las funciones recursivas. No hacerlo con varoiables del loop y del Call Back, puesto que al poderlas cambiar en diferentes programas, podemos perder el control de su valor. Además 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) y esto supone una pérdida de eficiencia.
Las variables de las Call Back deben ser específicas de la rutina y solo excepcionalmente utilizar una variable común para trasladar valores del loop a la Call Back o viceversa si es estrictamente imprescindible.
Confundirte, puede dar lugar a problemas de encontrarse valores inesperados en una variable por errores que pueden resultar muy difíciles acotar y detectar.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.
La variables volatile se inicilizan en el área de variables con el resto de las variables globales y su sintaxis es:
volatile definición_de_variable
Donde
- volatile es la etiqueta que informa que se guarde en la RAM permanentemente
- definición de variable es la definicion habitual de la variabls tipo nombre valor_inicial
Por ejemplo
volatile int estado = HIGH // Tipo entero corto
volatile long cambio = 0 // Tipo entero largo
Como vemos gestionar una interrupción exige, determinadas acciones en todas las partes del programa:
- En el área de definicion de variables, definir las variables como volatile
- En la funcion setup, asociar el número de la interrupcion con la Call Back y el evento o Trigger que la dispara a través del comando attachInterrupt
- En la funcion loop no hacemos nada puesto que esta funcion no la controla loop
- En el area de definicion de funciones tendremos que definir las Call Back asociadas a las interrupciones
Bien hasta aquí todo lo que había que aprender sobre interrupciones, sólo falta practicar y experimentar.
A manejar Arduino solo se aprende experimentando. Por ello vamos a hacer un programa que sea muy sencillo pero que permita experimentar con las interrupciones pero ello lo haremos en otro capítulo
Repaso
Como repaso final te diré lo que has aprendido hoy
- Saber que es el concepto de Interrupción
- Saber el signigicado de polling y polling rate
- Saber cuales son los principales inconvenientes de polling
- Saber que es que ocurra un evento que hace de gatillo, (trigger) de una respuesta, (Call Back)
- Conocer cual es la actuación de la CPU ante un a interrupción
- Saber distinguir entre memoria RAM y memoria ROM
- Saber cuales son los pin capaces de interrupciones en los principales modelos de Arduino
- Saber que es el control de interrupciones y cual son las diferencias entre que exista o no
- Conocer el comando volatile
- Conocer el comando attachInterrupt
- Conocer el comando detachInterrupt
- Conocer los eventos o trigger que puede detectar Arduino
- Conocer las n normas que deben tenerse en cuenta al crear una Call Back
- Saber las diferencias entre utilizar delay, millis y delayMicroseconds
- Conocer por que millis tiende a atrasar
- Sectores del programa en los que hay que actuar para realizar una interrupción
Ejercicios para resolver
En este capítulo no hay ejercios , quedan pendientes para el próximo capitulo
Reblogueó esto en tomtom_max.