Archivo de la etiqueta: timer

Uso de Librerías Arduino

La instalación, actualización y manejo librerías en Arduino es un aspecto importante a la hora de usar Arduino, veamos unos ejemplos. Para aprender a instalar librerías lo mejor es practicar, veamos unos ejemplos de instalación de algunas librerías muy útiles.

Cuando se va a instalar una librería, la primera tarea es leer la documentación y aprender a usarla, ya sea leyendo el código, viendo los ejemplos o revisando la documentación si está disponible.

Pasos para realizar los ejemplos propuestos:

  • Leer la documentación y entender que hace la librería.
  • Buscar e instalar la librería desde el gestor de librerías, si está disponible.
  • Descargar el .zip del repositorio de github si no está en el gestor de librerías. Botón Download zip
  • Abrir alguno de los ejemplo suministrados por las librerías, leer el código, entender que hace y ejecutarlo en Arduino.

Desinstalar librerías: Para desinstalar una librería simplemente borrar el directorio de la librería situado en la ruta configurada en las preferencias de Arduino y reiniciar el IDE.

Librería MsTimer2

MsTimer2 nos ofrece muchas utilidades de temporización muy útiles en el trabajo diario con Arduino.

Instalar la librería MsTimer2 desde el gestor de librerías. Ejecutar el programa de ejemplo incluido en la librería para hacer blink (FlashLed) en el led integrado sin necesidad de usar la instrucción delay().

Esquema de conexiones:

Documentación de la librería: http://playground.arduino.cc/Main/MsTimer2

Nueva versión de la librería: http://www.pjrc.com/teensy/td_libs_MsTimer2.html

Repositorio: https://github.com/PaulStoffregen/MsTimer2 

Librería Timer

La librería Timer es otra de librería de muy interesante de temporización más flexible pero menos exacta de MsTimer2. Esta librería no está disponible en el gestor de librerías.

Documentación de la librería: http://www.doctormonk.com/2012/01/arduino-timer-library.html

Repositorio: https://github.com/JChristensen/Timer 

Instalar la librería timer y ejecutar los ejemplos blink2 y read_A0_flashLED.

Esquema conexión blink2 (prestar atención al código y poner los leds a los pines correctos):

Esquema conexión read_A0_flashLED (prestar atención al código y poner el led al pin correcto o cambiar el código):

Comparación comportamiento librería msTimer2 y Timer: https://github.com/jecrespo/Aprendiendo-Arduino/tree/master/Otros/compara_timers 

Librería Time

La librería Time que añade funcionalidades de mantenimiento de fecha y hora en Arduino sin necesidad de un hardware externo. También permite obtener fecha y hora como segundo, minuto, hora, día, mes y año.

Librería: http://playground.arduino.cc/Code/Time

Documentación: http://www.pjrc.com/teensy/td_libs_Time.html

Repositorio: https://github.com/PaulStoffregen/Time 

Instalar la librería Time desde el gestor de librerías y ejecutar el ejemplo TimeSerial. Para ver la hora que debemos mandar a Arduino en el ejemplo ver https://www.epochconverter.com/ 

Librería para sonda temperatura/humedad DHT.

Librería para manejar un elemento HW como la sonda de temperatura y humedad DHT22.

Señal de comunicación:

Web del sensor: http://www.seeedstudio.com/depot/grove-temperaturehumidity-sensor-pro-p-838.html 

Documentación: http://www.seeedstudio.com/wiki/Grove_-_Temperature_and_Humidity_Sensor_Pro

Datasheet: https://arduino-info.wikispaces.com/file/view/DHT22.pdf/556588503/DHT22.pdf 

Repositorio: https://github.com/Seeed-Studio/Grove_Temperature_And_Humidity_Sensor

En este caso en el gestor de librerías tenemos dos librerías disponibles:

Pero la tienda donde lo compro me recomienda usar la librería https://github.com/Seeed-Studio/Grove_Temperature_And_Humidity_Sensor

Además de estas tres librerías, existen muchas más. Cualquiera de las librerías para la sonda DHT22 vale para usarla, pero cada una de ellas funciona diferente y cada una de ellas tiene sus ventajas e inconvenientes.

Mi recomendación es la librería DHTstable de Rob Tillaart: https://github.com/RobTillaart/Arduino/tree/master/libraries/DHTstable 

Versión modificada de la librería: https://github.com/jecrespo/DHTlib 

Probar varias librerías y comparar su uso.

Librería Pantalla LCD I2C

Ver este ejemplo: https://aprendiendoarduino.wordpress.com/2018/10/17/pantalla-lcd-i2c-en-arduino/

Instalar la librería LiquidCrystal_I2C del gestor de librerías. Más información https://github.com/marcoschwartz/LiquidCrystal_I2C

En función de la pantalla que compremos TFT, LCD, OLED, etc… hay que buscar que chip disponen y seleccionar la librería adecuada:

Librerías Arduino

Las librerías son trozos de código hechos por terceros que usamos en nuestro sketch. Esto nos facilita mucho la programación y hace que nuestro programa sea más sencillo de hacer y de entender. En este curso no veremos como hacer o modificar una librería pero en este curso debemos ser capaces de buscar una librería, instalarla, aprender a usar cualquier librería y usarla en un sketch.

Las librerías normalmente incluyen los siguientes archivos comprimidos en un archivo ZIP o dentro de un directorio. Estas siempre contienen:

  • Un archivo .cpp (código de C++)
  • Un archivo .h o encabezado de C, que contiene las propiedades y métodos o funciones de la librería.
  • Un archivo Keywords.txt, que contiene las palabras clave que se resaltan en el IDE (opcional).
  • Muy posiblemente la librería incluye un archivo readme con información adicional de lo que hace y con instrucciones de como usarla.
  • Directorio denominado examples con varios sketchs de ejemplo que nos ayudará a entender cómo usar la librería (opcional).

Como instalar librerías: http://arduino.cc/en/Guide/Libraries

Hay varios métodos de instalar librerías:

  • Mediante el IDE de Arduino de forma automática. Admite la instalación desde un fichero zip o desde una carpeta ya descomprimida.
  • Instalación Manual. Descomprimiendo en un directorio la librería y copiandolo en el directorio de librerías. Generalmente Mi Documentos – Arduino – libraries. Aquí se guardan las librerías contribuidas por el usuario como lo denomina el IDE.
  • Desde el gestor de librerías. A partir de la versión 1.6.2 del IDE de Arduino se incorpora el gestor de librerías que facilita el trabajo. Esta herramienta es accesible desde Programa → Incluir Librería → Gestionar Librerías. Desde aquí podemos ver las librerías instaladas, buscar librerías disponibles, instalar librerías y actualizarlas.
    Esta herramienta también nos permite gestionar las librerías instaladas manualmente.
    Desde C:\Users\nombre_usuario\AppData\Local\Arduino15, podemos ver en formato json el listado de librerías y placas disponibles desde el gestor de librerías y tarjetas.

La librerías instaladas se guardan en el directorio indicado desde las preferencias del IDE.

Todas las librerías disponibles en el gestor de librerías pueden encontrarse en http://www.arduinolibraries.info/

IMPORTANTE: Para añadir una librería a nuestro proyecto simplemente se añade a nuestro código la palabra clave #include seguido del nombre de la librería.

Más información: https://aprendiendoarduino.wordpress.com/2017/06/20/librerias-arduino-3/

Práctica: Instalación de Librerías

Instalar las librerías:

Más información: https://aprendiendoarduino.wordpress.com/2017/06/20/uso-de-librerias-arduino/

Práctica: Comparación de Timers

Compara el funcionamiento y limitaciones de las librerías MsTimer2.h y Timer.h

Ejecutar el ejercicio04 y comparar el funcionamiento de los dos timers.

Solución Ejercicio04: https://github.com/jecrespo/aprendiendoarduino-Curso_Arduino_Avanzado_2017/tree/master/Ejercicio04-Compara_Timers

MsTimer2 – Solo permite un temporizador que se ejecuta gracias a la interrupción asociada al timer 2 que dispone Arduino. Tiene prioridad de ejecución por encima de cualquier otra operación que se esté ejecutando.

Timer – Permite muchos temporizadores. Ejecuta la instrucción temporizada cuando puede en función de cuando se llama a “t.update();”, si hay retrasos en el loop la función se retrasa.

¿Alguna otra librería que queráis aprender a manejar? Enviar correo a aprendiendoarduino@gmail.com

Uso de Librerías Arduino

La instalación, actualización y manejo librerías en Arduino es un aspecto importante a la hora de usar Arduino, veamos unos ejemplos. Para aprender a instalar librerías lo mejor es practicar, veamos unos ejemplos de instalación de algunas librerías muy útiles.

Cuando se va a instalar una librería, la primera tarea es leer la documentación y aprender a usarla, ya sea leyendo el código, viendo los ejemplos o revisando la documentación si está disponible.

Pasos para realizar los ejemplos propuestos:

  • Leer la documentación y entender que hace la librería.
  • Buscar e instalar la librería desde el gestor de librerías, si está disponible.
  • Descargar el .zip del repositorio de github si no está en el gestor de librerías. Botón Download zip
  • Abrir alguno de los ejemplo suministrados por las librerías, leer el código, entender que hace y ejecutarlo en Arduino.

Desinstalar librerías: Para desinstalar una librería simplemente borrar el directorio de la librería situado en la ruta configurada en las preferencias de Arduino y reiniciar el IDE.

Librería MsTimer2

MsTimer2 nos ofrece muchas utilidades de temporización muy útiles en el trabajo diario con Arduino.

Instalar la librería MsTimer2 desde el gestor de librerías. Ejecutar el programa de ejemplo incluido en la librería para hacer blink (FlashLed) en el led integrado sin necesidad de usar la instrucción delay().

Esquema de conexiones:

Documentación de la librería: http://playground.arduino.cc/Main/MsTimer2

Nueva versión de la librería: http://www.pjrc.com/teensy/td_libs_MsTimer2.html

Repositorio: https://github.com/PaulStoffregen/MsTimer2

Librería Timer

La librería Timer es otra de librería de muy interesante de temporización más flexible pero menos exacta de MsTimer2. Esta librería no está disponible en el gestor de librerías.

Documentación de la librería: http://www.doctormonk.com/2012/01/arduino-timer-library.html

Repositorio: https://github.com/JChristensen/Timer

Instalar la librería timer y ejecutar los ejemplos blink2 y read_A0_flashLED.

Esquema conexión blink2 (prestar atención al código y poner los leds a los pines correctos):

Esquema conexión read_A0_flashLED (prestar atención al código y poner el led al pin correcto o cambiar el código):

Comparación comportamiento librería msTimer2 y Timer: https://github.com/jecrespo/Aprendiendo-Arduino/tree/master/Otros/compara_timers

Librería Time

La librería Time que añade funcionalidades de mantenimiento de fecha y hora en Arduino sin necesidad de un hardware externo. También permite obtener fecha y hora como segundo, minuto, hora, día, mes y año.

Librería: http://playground.arduino.cc/Code/Time

Documentación: http://www.pjrc.com/teensy/td_libs_Time.html

Repositorio: https://github.com/PaulStoffregen/Time

Instalar la librería Time desde el gestor y ejecutar el ejemplo TimeSerial. Para ver la hora que debemos mandar a Arduino en el ejemplo ver https://www.epochconverter.com/

Librería para sonda temperatura/humedad DHT.

Librería para manejar un elemento HW como la sonda de temperatura y humedad DHT22.

Señal de comunicación:

Web del sensor: http://www.seeedstudio.com/depot/grove-temperaturehumidity-sensor-pro-p-838.html

Documentación: http://www.seeedstudio.com/wiki/Grove_-_Temperature_and_Humidity_Sensor_Pro

Datasheet: https://arduino-info.wikispaces.com/file/view/DHT22.pdf/556588503/DHT22.pdf

Repositorio: https://github.com/Seeed-Studio/Grove_Temperature_And_Humidity_Sensor

En este caso en el gestor de librerías tenemos dos librerías disponibles:

Pero la tienda donde lo compro me recomienda usar la librería https://github.com/Seeed-Studio/Grove_Temperature_And_Humidity_Sensor

Además de estas tres librerías, existen muchas más. Cualquiera de las librerías para la sonda DHT22 vale para usarla, pero cada una de ellas funciona diferente y cada una de ellas tiene sus ventajas e inconvenientes.

Arduino Watchdog

En electrónica, un perro guardián (en inglés watchdog) es un mecanismo de seguridad que provoca un reset del sistema en caso de que éste se haya bloqueado.

Consiste en un temporizador que irá continuamente decrementando un contador, inicialmente con un valor relativamente alto. Cuando este contador llegue a cero, se reiniciará el sistema, así que se debe diseñar una subrutina en el programa de manera que refresque o reinicie al perro guardián antes de que provoque el reset. Si el programa falla o se bloquea, al no actualizar el contador del perro guardián a su valor de inicio, éste llegará a decrementarse hasta cero y se reiniciará el sistema mediante una interrupción.

El watchdog es un timer que va dentro de la arquitectura del microcontrolador y es independiente del resto de timers. Ver diagrama de bloques:

La interrupción del watchdog como vimos anteriormente es la más prioritaria en la tabla de los vectores de interrupción.

El Watchdog Timer en el microprocesador del Arduino solo tiene una fuente de reloj que es su propio oscilador interno de 128kHz (a diferencia de los temporizadores internos de 8/16bit, que pueden usar el reloj del sistema de 16Mhz o un reloj externo). Es este oscilador separado permite que el WDT funcione en el modo de potencia más baja: SLEEP_MODE_PWR_DOWN .

Toda la información del wathcdog en el ATmega328p está en la página 75 de http://www.atmel.com/Images/Atmel-42735-8-bit-AVR-Microcontroller-ATmega328-328P_datasheet.pdf

Los registros que maneja el watchdog son:

  • MCU Status Register – To make use of the Reset Flags to identify a reset condition, the user should read and then Reset the MCUSR as early as possible in the program. If the register is cleared before another reset occurs, the source of the reset can be found by examining the Reset Flags.
    Name: MCUSR

  • WDTCSR – Watchdog Timer Control Register
    Name: WDTCSR

Configuración del watchdog timer:

(1) WDTON es un fuse que habilita o deshabilita el watchdog.

Los tres modos de funcionamiento son:

  • Interrupt Mode – La rutina del vector de interrupción WDT_vect se llamará cuando el WDT expire. Puede utilizarse para despertar al microcontrolador desde los modos de sleep, incluido el modo de suspensión de menor potencia (SLEEP_MODE_PWR_DOWN), en el que no están disponibles otros temporizadores.
  • System reset Mode: cuando se produce un timeout del tiempo de espera del watchdog, se reiniciará el microcontrolador. Para usar en el manejo de bloqueos o culegues del código.
  • Interrupt and System Reset Mode – Primero se llamará a la ISR de la interrupción WDT_vect, cuando se complete se va al System Reset Mode.

La selección del prescale se hace mediante estos bits y son los únicos tiempos permitidos al watchdog:

Para gestionar el watchdog con Arduino debe utilizarse la librería de wdt.h y sólo tres funciones:

  • wdt_disable() para desactivar el watchdog, necesario llamarlo antes de configurar un nuevo watchdog
  • wdt_enable(tiempo) que configura y activa el watchdog al tiempo que se indica como parámetro (tiempo)
  • wdt_reset() que renueva el intervalo que cuenta el perro guardián.

La documentación de la librería <avr/wdt.h>: Watchdog timer handling

La librería wdt.h se encuentra en la ruta: C:\Program Files (x86)\Arduino\hardware\tools\avr\avr\include\avr

También disponemos de una librería para manejar el wdt: https://github.com/WickedDevice/SoftReset

NOTA: los bootloader antiguos de Arduino no tienen soporte para watchdog y en ese caso hay que actualizar el bootloader o poner el optiboot: https://github.com/Optiboot/optiboot. Optiboot está instalado por defecto en el Arduino y puede usarse para actualizar los Arduino antiguos.

Más información:

Resetear Arduino

Al pulsar el botón de reinicio, se resetea el AVR. Además, el IDE de Arduino envía una señal especial que hace que la tarjeta Arduino se resete.

Cómo deshabilitar el autoreset on serial connnection: http://playground.arduino.cc/Main/DisablingAutoResetOnSerialConnection

Después de encender o reiniciar el AVR, inmediatamente comienza a ejecutar el bootloader. El bootloader de Arduino observa durante unos segundos para ver si se está descargando un nuevo sketch desde el IDE de Arduino. Si es así, borra todo lo que estaba en flash y quema en el nuevo sketch. A continuación, el gestor de arranque comienza a ejecutar cualquier sketch que se encuentre en el flash.

Si queremos saber cuántas veces se ha reiniciado un Arduino, podemos guardarlo en la EEPROM, e incluso saber la razón del reinicio mirando el registro MCUSR.

Otras formas de resetear Arduino:

  • WDT, es la forma más fiable de resetear el AVR el mismo
  • Conectar un cable al pin de reset desde alguna fuente externa o desde un pin del mismo Arduino con una resistenca de 1K.
  • Ejecutar la dirección 0 (jmp 000) que vuelve a la dirección 0 pero no resetea completamente Arduino y no todas las configuraciones por defecto son restauradas, como por ejemplo el estado de los pines.

Código:

void software_Reset()
// Restarts program from beginning but
// does not reset the peripherals and registers
{
 asm volatile (»  jmp 0″);  
}

void(* resetFunc) (void) = 0; //declare reset function at address 0

Para más información sobre System Reset ver página 72 de http://www.atmel.com/Images/Atmel-42735-8-bit-AVR-Microcontroller-ATmega328-328P_datasheet.pdf

Más información:

Ejercicio Watchdog Arduino

Comprobar el funcionamiento del watchdog con este ejercicio.

Ejercicio52: https://github.com/jecrespo/Aprendiendo-Arduino/tree/master/Ejercicio53-Watchdog

Interrupciones

Si queremos detectar un cambio de estado en esta entrada, el método que hemos usado hasta ahora es emplear las entradas digitales para consultar repetidamente el valor de la entrada, con un intervalo de tiempo (delay) entre consultas.

Este mecanismo se denomina “poll”, y tiene 3 claras desventajas.

  • 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 resolver este tipo de problemas, los microprocesadores incorporan el concepto de interrupción, que es un mecanismo que permite asociar una función a la ocurrencia de un determinado evento. Esta función de callback asociada se denomina ISR (Interruption Service Rutine).

En programación, una interrupción es una señal recibida por el procesador o MCU, para indicarle que debe «interrumpir» el curso de ejecución actual y pasar a ejecutar código específico para tratar esta situación.

Una interrupción es una suspensión temporal de la ejecución de un proceso, para pasar a ejecutar una subrutina de servicio de interrupción, la cual, por lo general, no forma parte del programa. Una vez finalizada dicha subrutina, se reanuda la ejecución del programa. Las interrupciones HW son generadas por los dispositivos periféricos habilitando una señal del CPU (llamada IRQ del inglés «interrupt request») para solicitar atención del mismo.

Todos los dispositivos que deseen comunicarse con el procesador por medio de interrupciones deben tener asignada una línea única capaz de avisar al CPU cuando le requiere para realizar una operación. Esta línea se denomina IRQ.

Las IRQ son líneas que llegan al controlador de interrupciones, un componente de hardware dedicado a la gestión de las interrupciones, y que está integrado en la MCU.

El controlador de interrupciones debe ser capaz de habilitar o inhibir las líneas de interrupción y establecer prioridades entre las mismas. Cuando varias líneas de petición de interrupción se activan a la vez, el controlador de interrupciones utilizará estas prioridades para escoger la interrupción sobre la que informará al procesador principal. También puede darse el caso de que una rutina de tratamiento de interrupción sea interrumpida para realizar otra rutina de tratamiento de una interrupción de mayor prioridad a la que se estaba ejecutando.

Procesamiento de una Interrupción:

  1. Terminar la ejecución de la instrucción máquina en curso.
  2. Salvar el estado del procesador (valores de registros y flags) y el valor del contador de programa en la pila, de manera que en la CPU, al terminar el proceso de interrupción, pueda seguir ejecutando el programa a partir de la última instrucción.
  3. La CPU salta a la dirección donde está almacenada la rutina de servicio de interrupción (Interrupt Service Routine, o abreviado ISR) y ejecuta esa rutina que tiene como objetivo atender al dispositivo que generó la interrupción.
  4. Una vez que la rutina de la interrupción termina, el procesador restaura el estado que había guardado en la pila en el paso 2 y retorna al programa que se estaba usando anteriormente.

Tipos de Interrupciones:

  • Interrupciones HW o externas: Estas son asíncronas a la ejecución del procesador, es decir, se pueden producir en cualquier momento independientemente de lo que esté haciendo el CPU en ese momento. Las causas que las producen son externas al procesador y a menudo suelen estar ligadas con los distintos dispositivos de entrada o salida.
  • Interrupciones SW: Las interrupciones por software son aquellas generadas por un programa en ejecución. Para generarlas, existen distintas instrucciones en el código máquina que permiten al programador producir una interrupción. Arduino no soporta las interrupciones por software
  • Un evento programado o Timer. Son las interrupciones asociadas a los timers y gracias a ellas funciona millis().
  • Excepciones: Son aquellas que se producen de forma síncrona a la ejecución del procesador típicamente causada por una condición de error en un programa. Normalmente son causadas al realizarse operaciones no permitidas tales como la división entre 0, el desbordamiento, el acceso a una posición de memoria no permitida, etc.

Definiciones genéricas de Interrupciones:

Interrupciones en Arduino

Internamente, Arduino (mejor dicho el microcontrolador AtMega) tiene ciertas interrupciones configuradas que lanza según la situación. Para la transmisión de datos a través del puerto serie, para resetear la placa antes de cargar un programa, comunicación I2C, etc…

El uso de interrupciones es casi obligatorio en un programa avanzado de un microcontrolador. Básicamente cuando un evento ocurre se levanta una bandera y la ejecución se traslada a una rama de código diferente. De esta forma no es necesario esperar un loop a comprobar que un evento ha ocurrido para ejecutar una acción.

Las interrupciones pueden ocurrir por un cambio en un puerto (solo en aquellos que soporten interrupciones HW), overflow en un timer, comunicación serie (USART), etc…

Normalmente no se ve, pero las interrupciones ocurren constantemente durante la operación normal de Arduino. Por ejemplo las interrupciones ayudan a las funciones delay() y millis() así como a la función Serial.read().

El procesador dentro de cualquier Arduino tiene dos tipos de interrupciones: “externas” y “cambio de pin”.

Más información:

Las interrupciones son muy útiles para hacer que las cosas ocurran automáticamente en los programas del microcontrolador y pueden resolver problemas de temporización.

Las tareas más usuales en las que usar interrupciones son en la monitorización de entradas de usuario o entradas externas críticas en el tiempo, así como en lectura de periféricos con requisitos de temporización muy específicos donde queramos capturar un evento que tiene una duración muy corta inferior al tiempo de loop de nuestro programa.

Para definir una interrupción necesitamos tres cosas:

  • Un pin de Arduino que recibirá la señal de disparo
  • Una condición de disparo
  • Una función que se ejecutará, cuando se dispara la interrupción (Llamada call back function).

En cuanto a la condición de disparo puede ser:

  • LOW, La interrupción se dispara cuando el pin es LOW.
  • CHANGE, Se dispara cuando pase de HIGH a LOW o viceversa.
  • RISING, Dispara en el flanco de subida (Cuando pasa de LOW a HIGH).
  • FALLING, Dispara en el flanco de bajada (Cuando pasa de HIGH a LOW).
  • Y una solo para el DUE: HIGH se dispara cuando el pin esta HIGH.

Para saber todo sobre las interrupciones en el ATmega328p, debemos consultar su información en la página 32 y 82 de http://www.atmel.com/Images/Atmel-42735-8-bit-AVR-Microcontroller-ATmega328-328P_datasheet.pdf

Los vectores de interrupción es una tabla en memoria que contiene la dirección de memoria de la primera instrucción de interrupción. ATmega328p Interrupt vectors, además esta tabla establece la prioridad de las interrupciones:

Y así funciona el ISR vector table:

Las librería de avr-libc que se usa para manejar las interrupciones es <avr/interrupt.h>:

Más información:

Interrupciones Externas en Arduino

Estas interrupciones hardware, se diseñaron por la necesidad de reaccionar a suficiente velocidad en tiempos inimaginablemente cortos a los que la electrónica trabaja habitualmente y a los que ni siquiera el software era capaz de reaccionar.

Para las interrupciones externas o hardware, solo hay dos pines que las soportan en los ATmega 328 (p.e. Arduino UNO), son las INT0 y INT1 que están mapeadas a los pines 2 y 3. Estas interrupciones se pueden configurar con disparadores en RISING o FALLING para flancos o en nivel LOW. Los disparadores son interpretados por hardware y la interrupción es muy rápida.

El Arduino mega tiene más pines disponibles para interrupciones externas. Pines de External Interrupts para Mega: 2 (interrupt 0), 3 (interrupt 1), 18 (interrupt 5), 19 (interrupt 4), 20 (interrupt 3), and 21 (interrupt 2). Estos pines pueden ser configurados para disparar una interrupción al detectar un nivel bajo, un flanco ascendente, un flanco descendente o un cambio de nivel.

En el pin de reset también hay otra interrupción que sólo se dispara cuando detecta voltaje LOW y provoca el reset del microcontrolador.

Para configurar una interrupción en Arduino se usa la función attachInterrupt(). El primer parámetro a configurar es el número de interrupción, normalmente se usa el nº de pin para traducir al nº de interrupción.

Tabla de pines que soportan interrupción HW en Arduino:

Board Digital Pins Usable For Interrupts
 Uno, Nano, Mini, other 328-based 2, 3
 Mega, Mega2560, MegaADK 2, 3, 18, 19, 20, 21
 Micro, Leonardo, other 32u4-based 0, 1, 2, 3, 7
 Zero all digital pins, except 4
 MKR1000 Rev.1 0, 1, 4, 5, 6, 7, 8, 9, A1, A2
 Due all digital pins

Esto quiere decir que el Arduino UNO puede definir dos interrupciones hardware llamadas 0 y 1, conectadas a los pines 2 y 3

Para saber qué número de interrupción estás asociada a un pin, debemos usar la función  digitalPinToInterrupt(pin). El número de interrupción su mapeo en los pines dependerá del MCI. El uso de número de interrupción puede provocar problemas de compatibilidad cuando el sketch funciona en diferentes placas.

Tabla de interrupciones y número de pin asociado:

Board int.0 int.1 int.2 int.3 int.4 int.5
Uno, Ethernet 2 3
Mega2560 2 3 21 20 19 18
32u4 based (e.g Leonardo, Micro) 3 2 0 1 7

Arduino Due tiene grandes capacidades a nivel de interrupciones que permiten asociar una interrupción a cada uno de los pines disponibles. Arduino Zero permite asociar una interrupción a todos los pines excepto para el pin 4.

Aspectos importantes a tener en cuenta con el uso de interrupciones:

  • Dentro de la función llamada desde la interrupción, la función delay() no funciona y el valor devuelto por millis() no aumenta. La razón es que estas funciones hacen uso de interrupciones que no se disparan mientras está disparada una interrupción externa.
  • Los datos recibidos por el puerto serie se pueden perder mientras se está en la función llamada por la interrupción.
  • Se deben declarar como “volatile” cualquier variable que sea modificada dentro de la función llamada por una interrupción: https://www.arduino.cc/en/Reference/Volatile

Tutorial de Interrupciones externas en MCUs AVR de 8 bits: http://www.avr-tutorials.com/interrupts/The-AVR-8-Bits-Microcontrollers-External-Interrupts

Programación de interrupciones externas en C: http://www.avr-tutorials.com/interrupts/avr-external-interrupt-c-programming

Las interrupciones de hardware, también conocidas como INT0 e INT1, llaman a una rutina de servicio de interrupción cuando algo sucede con uno de los pines asociados. La ventaja es que Arduino tiene una simple rutina de configuración para conectar la rutina de servicio de interrupción al evento: attachInterrupt (). La desventaja es que sólo funciona con dos pines específicos: pin digital 2 y 3 en la placa Arduino.

El Atmega328p solo tiene dos interrupciones de hardware INT0 e INT1, sin embargo los microcontroladores AVR pueden tener una interrupción ante un cambio en cualquier pin, es lo que denomina pin change interrupt. http://playground.arduino.cc/Code/Interrupts. Estas interrupciones no son soportadas directamente por Arduino y necesitan ser accedidas a través de una librería adicional.

Las interrupciones de cambio de pin pueden habilitarse en más pines. Para los ATmega 328, pueden habilitarse en cualquiera de los pines de señal disponibles. Estas son disparadas igual en flancos RISING o FALLING, pero depende del código de la interrupción configurar el pin que recibe la interrupción y determinar qué ha pasado. Las interrupciones de cambio de pin están agrupadas en 3 puertos de la MCU, por lo tanto hay 3 vectores de interrupciones para todo el conjunto de pines. Esto hace el trabajo de resolver la acción en una interrupción más complicada.

Si necesita más pines para interrupciones, hay un mecanismo para generar una interrupción cuando se cambia cualquier pin en uno de los puertos de 8 bits. No sabes pin, sino sólo en qué puerto. Si se genera una interrupción cuando se cambia uno de los pines ADC0 a ADC5 (utilizado como entrada digital). En ese caso, se llama a la rutina de servicio de interrupción ISR (PCINT1_vect). En la rutina se puede averiguar cuál de los pines específicos dentro de ese puerto ha sido el que ha cambiado.

En primer lugar, los indicadores de habilitación de interrupción de cambio de pin deben ajustarse en el registro PCICR. Estos son los bits PCIE0, PCIE1 y PCIE2 para los grupos de pines PCINT7..0, PCINT14..8 y PCINT23..16 respectivamente. Los pines individuales se pueden activar o deshabilitar en los registros PCMSK0, PCMSK1 y PCMSK2. En el circuito Arduino, en combinación con la hoja de datos Atmel Atmega328, se puede ver que el pin PCINT0 corresponde al pin 0 en el puerto B (llamado PB0). Este es el pin 8 en el Arduino Uno. Otro ejemplo es el pin A0 de la tarjeta Arduino, que puede utilizarse como una entrada digital:

  • Pin Change Interrupt Request 0 (pins D8 to D13) (PCINT0_vect)
  • Pin Change Interrupt Request 1 (pins A0 to A5)  (PCINT1_vect)
  • Pin Change Interrupt Request 2 (pins D0 to D7)  (PCINT2_vect)

“The Pin Change Interrupt Request 2 (PCI2) will trigger if any enabled PCINT[23:16] pin toggles. The Pin Change Interrupt Request 1 (PCI1) will trigger if any enabled PCINT[14:8] pin toggles. The Pin Change Interrupt Request 0 (PCI0) will trigger if any enabled PCINT[7:0] pin toggles.”

PCINT son unos pines determinados que se puede ver en la página 19 a que corresponden de http://www.atmel.com/Images/Atmel-42735-8-bit-AVR-Microcontroller-ATmega328-328P_datasheet.pdf

Los pines de interrupción deben estar configurados como INPUT, las resistencias pullup pueden habilitarse para poder detectar interruptores simples.

Es cierto que solo se puede configurar una rutina de interrupción para cada grupo de pines en un puerto, pero hay muchos casos donde es suficiente.

Más información:

También disponemos de la PinChangeInt Library. PinChangeInt implementa interrupciones de cambio de pin para el entorno Arduino. Esta biblioteca fue diseñada para el Arduino Uno / Duemilanove, y funciona bien en el Nano. El soporte MEGA está incluido pero no de una forma completa.

Registros para las interrupciones externas y pin change son:

  • External Interrupt Control Register A: The External Interrupt Control Register A contains control bits for interrupt sense control.
    Name: EICRA
  • External Interrupt Mask Register: When addressing I/O Registers as data space using LD and ST instructions, the provided offset must be used. When using the I/O specific commands IN and OUT, the offset is reduced by 0x20, resulting in an I/O address offset within 0x00 – 0x3F.
    Name: EIMSK
  • External Interrupt Flag Register: When addressing I/O Registers as data space using LD and ST instructions, the provided offset must be used. When using the I/O specific commands IN and OUT, the offset is reduced by 0x20, resulting in an I/O address offset within 0x00 – 0x3F.
    Name: EIFR
  • Pin Change Interrupt Control Register
    Name: PCICR
  • Pin Change Interrupt Flag Register: When addressing I/O Registers as data space using LD and ST instructions, the provided offset must be used. When using the I/O specific commands IN and OUT, the offset is reduced by 0x20, resulting in an I/O address offset within 0x00 – 0x3F.
    Name: PCIFR
  • Pin Change Mask Register 2
    Name: PCMSK2
  • Pin Change Mask Register 1
    Name: PCMSK1
  • Pin Change Mask Register 0
    Name: PCMSK0

Cada interrupción está asociada con dos bits, un bit de indicador de interrupción (flag) y un bit habilitado de interrupción (enabled). Estos bits se encuentran en los registros de E/S asociados con la interrupción específica:

  • El bit de indicador de interrupción se establece siempre que se produce el evento de interrupción, independientemente de que la interrupción esté o no habilitada. Registro EIFR
  • El bit activado por interrupción se utiliza para activar o desactivar una interrupción específica. Básicamente se le dice al microcontrolador si debe o no responder a la interrupción si se dispara. Registro EIMSK

En resumen, básicamente, tanto la Interrupt Flag como la Interrupt Enabled son necesarias para que se genere una solicitud de interrupción como se muestra en la figura siguiente.

Aparte de los bits habilitados para las interrupciones específicas, el bit de enable de interrupción global DEBE ser habilitado para que las interrupciones se activen en el microcontrolador.

Para el microcontrolador AVR de 8 bits, este bit se encuentra en el registro de estado de E/S (SREG). La interrupción global habilitada es bit 7, en el SREG.

Interrupciones Internas en Arduino

Las interrupciones internas en Arduino son aquellas interrupciones relacionadas con los timers y que también son denominadas interrupciones de eventos programados.

Arduino tiene tres timers. Son el timer cero, timer uno, timer dos.Timer cero y dos son de 8 bits y el temporizador uno es de 16 bits.

Crearemos una interrupción interna utilizando la interrupción de un temporizador. Para ello necesitaremos una librería adecuada para el temporizador que usemos. Podemos crear tres tipos de interrupción interna, son interrupción de desbordamiento de temporizador (timer overflow), interrupción de comparación de salida (Output Compare Match Interrupt), interrupción de captura de entrada (Timer Input Capture Interrupt).

Ya vimos que las interrupciones de los timers se usan para PWM y también con la librería MSTimer2: http://www.pjrc.com/teensy/td_libs_MsTimer2.html

En el apartado de timers trataremos más a fondo los temporizadores de Arduino y sus interrupciones asociadas.

Problemas y soluciones con las interrupciones en Arduino: http://rcarduino.blogspot.com.es/2013/04/the-problem-and-solutions-with-arduino.html

ISR (Interrupt Service Routines)

La función de callback asociada a una interrupción se denomina ISR (Interruption Service Rutine). ISR es una función especial que tiene algunas limitaciones, una ISR no puede tener ningún parámetro en la llamada y no pueden devolver ninguna función.

Dos ISR no pueden ejecutarse de forma simultánea. En caso de dispararse otra interrupción mientras se ejecuta una ISR, la función ISR se ejecuta una a continuación de otra.

Una ISR debe ser tan corta y rápida como sea posible, puesto que durante su ejecución se paraliza el curso normal del programa y las interrupciones se deshabilitan.

Se se usan varias ISR en el sketch, solo una se puede ejecutar y otras interrupciones serán ejecutadas después de que la ISR actual finalice, en un orden que depende las prioridad de las interrupciones y que depender de interrupt handler.

La función millis() no funciona dentro del ISR puesto que usa interrupciones para su uso. La función micros() funciona dentro de ISR pero después de 1-2 ms se empieza a comportar de forma extraña. delayMicroseconds() no usa ningún contador y funcionará correctamente dentro del ISR. Por lo tanto si la ISR dura mucho tiempo provocará retrasos en el reloj interno, puesto que millis() no avanza mientras se ejecuta el ISR.

Para pasar datos entre el programa principal y el ISR se usan las variables globales, pero para que estas variables se actualicen correctamente deben declararse con el modificador “volatile”: https://www.arduino.cc/en/Reference/Volatile

Volatile es una palabra reservada que se pone delante de la definición de una variable para modificar la forma en que el compilador y el programa trata esa variable.

Declarar una variable volátil es una directiva para el compilador. Específicamente, dice al compilador que cargue la variable desde la RAM y no desde un registro de almacenamiento, que es una ubicación de memoria temporal donde se almacenan y manipulan las variables del programa. Bajo ciertas condiciones, el valor de una variable almacenada en registros puede ser inexacto.

Una variable debe ser declarada volátil siempre que su valor pueda ser cambiado por algo más allá del control de la sección de código en la que aparece, como un subproceso que se ejecuta simultáneamente. En Arduino, el único lugar en el que es probable que ocurra es en secciones de código asociadas con interrupciones, las llamadas rutinas de servicio de interrupción (ISR).

Para poder modificar una variable externa a la ISR dentro de la misma debemos declararla como “volatile”. 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). Al indicar una variable como Volatile el compilador desactiva ciertas optimizaciones, lo que supone una pérdida de eficiencia. Por tanto, 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.

Esta es la secuencia cuando se dispara una interrupción:

  1. El microcontrolador completa la instrucción que está siendo ejecutada.
  2. El programa de Arduino que se está ejecutando, transfiere el control a la Interrupt Service Routine (ISR). Cada interrupción tiene asociada una ISR que es una función que le dice al microcontrolador que hacer cuando ocurre una interrupción.
  3. Se ejecuta la ISR mediante la carga de la dirección de comienzo de la ISR en el contador del programa.
  4. La ejecución del ISR continua hasta que se encuentra el RETI (return from the interrupt instruction). Más información: http://www.atmel.com/webdoc/avrassembler/avrassembler.wb_RETI.html
  5. Cuando ha finalizado ISR, el microcontrolador continua la ejecución del programa donde lo dejó antes de que ocurriera la interrupción.

Más información en: http://gammon.com.au/interrupts

Hay muchos I/O registros en el microcontrolador asociados a las interrupciones externas. Estos registros almacenan los flag de las interrupciones, los bits de enable, así como información de control de cada interrupción externa.

Más información de interrupciones: http://cs4hs.cs.pub.ro/wiki/roboticsisfun/chapter2/ch2_10_interrupts

Funciones de Interrupciones en Arduino

El core de Arduino ofrece una serie de instrucciones para programar las interrupciones externas, pero no las de pin change ni las de temporizadores:

  • interrupts() – https://www.arduino.cc/en/Reference/Interrupts
    Habilita las interrupciones (antes han debido ser inhabilitadas con noInterrupts()). Las interrupciones permiten a ciertas tareas importantes que se ejecuten en segundo plano y defecto las interrupciones están habilitadas. Algunas funciones no funcionarán si se deshabilitan las interrupciones y las comunicaciones entrantes serán ignoradas, también podrán modificar ligeramente la temporización del código. Las interrupciones se pueden deshabilitar para casos particulares de secciones críticas del código.
  • noInterrupts() – https://www.arduino.cc/en/Reference/NoInterrupts
    Deshabilita las interrupciones. Las interrupciones pueden ser habilitadas de nuevo con interrupts().
  • attachInterrupt() – https://www.arduino.cc/en/Reference/AttachInterrupt
    Me permite configurar una interrupción externa, pero no otro tipo de interrupciones. El primer parámetro es el número de interrupción que va asociado a un pin, luego la función ISR y finalmente el modo.
  • detachInterrupt() – https://www.arduino.cc/en/Reference/DetachInterrupt
    Deshabilita la interrupción. El parámetro que se le pasa es el número de la interrupción.
  • digitalPinToInterrupt(pin) traduce el pin al número de interrupción específica.
  • usingInterrupt() – https://www.arduino.cc/en/Reference/SPIusingInterrupt
    Deshabilita la interrupción externa pasada como parámetro en la llamada a SPI.beginTransaction() y se habilita de nuevo en endTransaction() para prevenir conflictos en las transacciones del bus SPI

En attachInterrupt()  los modos disponibles que definen cuando una interrupción externa es disparada, se hace mediante 4 constantes:

  • LOW, La interrupción se dispara cuando el pin es LOW.
  • CHANGE, Se dispara cuando pase de HIGH a LOW o viceversa, es decir un cambio en el estado del pin.
  • RISING, Dispara en el flanco de subida (Cuando pasa de LOW a HIGH).
  • FALLING, Dispara en el flanco de bajada (Cuando pasa de HIGH a LOW).
  • Y solo para el DUE: HIGH se dispara cuando el pin esta HIGH.

Normalmente, las variables globales se utilizan para pasar datos entre un ISR y el programa principal. Para asegurarse de que las variables compartidas entre un ISR y el programa principal se actualizan correctamente, declararlas como volátiles.

La función delay() no deshabilita las interrupciones, por lo tanto los datos recibidos por el serial Rx son guardados, los valores PWM funcionan y las interrupciones externas funcionan. Sin embargo la función delayMicroseconds() deshabilita las interrupciones mientras está ejecutándose.

Más información:

Para manejar las interrupciones por cambio de pin disponemos de varias librerías: Ver http://playground.arduino.cc/Main/LibraryList#Interrupts

Multitarea

Utilizar interrupciones nos permitirá olvidarnos de controlar ciertos pines. Esto muy importante ya que dentro de una aplicación o programa, no vamos a hacer una única cosa. Por ejemplo, queremos que un LED se encienda o se apague cuando pulsamos un botón. Esto es relativamente sencillo pero cuando además queremos que otro LED parpadee, la cosa se complica.

La probabilidad de capturar el evento cuando se pulsa el botón disminuye con el aumento de tiempo de parpadeo. En estos casos, y en muchos otros, nos interesa liberar el procesador de Arduino para que solo cuando se pulse el botón, haga una acción determinada. Así no tendremos que estar constantemente comprobando el pin X si ha pulsado el botón.

Precisamente este es el sentido de las interrupciones, poder hacer otras cosas mientras no suceda el evento, pero cuando ese evento externo esté presente, que ejecute rápidamente el código asociado.

Los controladores de interrupciones: administran la ejecución de tareas por interrupciones, lo cual permite la multitarea.

Todas las tareas que hemos realizado hasta ahora han sido síncronas. Es decir, solicitamos unos datos (puerto serie, ethernet, etc…), esperamos la respuesta y los mostramos en pantalla. La contraposición es un proceso asíncrono, nosotros lanzamos la petición y en cuanto se pueda se realizará y se mostrará el resultado sin esperar.

Si nos encontramos con respuestas lentas, no es buena técnica esperar de forma «síncrona» porque seguramente obtengamos un error de que se ha excedido el tiempo de espera «timeout». Si estamos descargando datos de gran volumen, tampoco es buena técnica que dejemos la aplicación «congelada» mientras se descargan los datos. Lo ideal es lanzar la descarga de fondo, es decir, de forma «asíncrona».

Un proceso síncrono es aquel que se ejecuta y hasta que no finaliza solo se ejecuta ese proceso (todo en el mismo loop), mientras que uno asíncrono comienza la ejecución y continúan ejecutándose otras tareas por lo que el proceso total se completa en varios loops. Son dos formas de atacar un problema. Este nuevo concepto es muy interesante y abre muchas posibilidades a nuestros programas que requieren conexiones remotas.

Multitask en Arduino, muy recomendable:

Ejercicios Interrupciones Arduino

Hacer un ejercicio simple donde asociemos al pin 2 una interrupción que encienda y apague el pin 13.

Código:

const byte ledPin = 13;
const byte interruptPin = 2;
volatile byte state = LOW;

void setup() {
pinMode(ledPin, OUTPUT);
pinMode(interruptPin, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(interruptPin), blink, CHANGE);
}

void loop() {
digitalWrite(ledPin, state);
}

void blink() {
state = !state;
}

En el siguiente código definimos el pin digital 10 como salida, para emular una onda cuadrada de periodo 300ms (150ms ON y 150ms OFF). Para visualizar el funcionamiento de la interrupción, en cada flanco activo del pulso simulado, encendemos/apagamos el LED integrado en la placa, por lo que el LED parpadea a intervalos de 600ms (300ms ON y 300ms OFF). No estamos encendiendo el LED con una salida digital, si no que es la interrupción que salta la que enciende y apaga el LED (el pin digital solo emula una señal externa).

const int emuPin = 10;
 
const int LEDPin = 13;
const int intPin = 2;
volatile int state = LOW;
 
void setup() {
	pinMode(emuPin, OUTPUT);
	pinMode(LEDPin, OUTPUT);
	pinMode(intPin, INPUT_PULLUP);
	attachInterrupt(digitalPinToInterrupt(intPin), blink, RISING);
}
 
void loop() {
	//esta parte es para emular la salida
	digitalWrite(emuPin, HIGH);
	delay(150);
	digitalWrite(emuPin, LOW);
	delay(150);
}
 
void blink() {
	state = !state;
	digitalWrite(LEDPin, state);
}

El siguiente código empleamos el mismo pin digital para emular una onda cuadrada, esta vez de intervalo 2s (1s ON y 1s OFF). En cada interrupción actualizamos el valor de un contador. Posteriormente, en el bucle principal, comprobamos el valor del contador, y si ha sido modificado mostramos el nuevo valor. Al ejecutar el código, veremos que en el monitor serie se imprimen números consecutivos a intervalos de dos segundos.

const int emuPin = 10;
 
const int intPin = 2;
volatile int ISRCounter = 0;
int counter = 0;
 
 
void setup()
{
	pinMode(emuPin, OUTPUT);
	
	pinMode(intPin, INPUT_PULLUP);
	Serial.begin(9600);
	attachInterrupt(digitalPinToInterrupt(intPin), interruptCount, LOW);
}
 
void loop()
{
	//esta parte es para emular la salida
	digitalWrite(emuPin, HIGH);
	delay(1000);
	digitalWrite(emuPin, LOW);
	delay(1000);
 
 
	if (counter != ISRCounter)
	{
		counter = ISRCounter;
		Serial.println(counter);
	}
}
 
void interruptCount()
{
	ISRCounter++;
	timeCounter = millis();
}