bclose

Arduino y los Timers

Las interrupciones temporizadas

Objetivos

.

 
    • Presentar las interrupciones programadas o Timers.
    • Instalar una librería, la TimerOne.
    • Mostrar un ejemplo de programación de un evento con un Timer.
 

Material requerido.

 

  Tienda España Tienda Mexico
Kit Arduino Uno  Kit inicio UNO Kit inicio UNO
Kit Arduino MEGA Kit Inicio Mega Kit Inicio Mega

 

vueltas con las interrupciones temporizadas

 

Hasta ahora, hemos medido el tiempo en milisegundos y usado el delay para temporizar las cosas, así usábamos un delay de unos 250 ms para que el blinking LED se encendiese y se apagase, tranquilamente.

El problema del delay () es que congela Arduino el tiempo especificado. Mientras no sea mucho, bueno, podemos aceptarlo, pero imagínate que queremos hacer el blinking LED para un semáforo, donde los tiempos de espera van de digamos 30 segundos a 1 minuto.

Podemos pedirle que haga un delay de 60 segundos * 1.000 = 60.000 millis, pero claro esto supone que no podremos hacer ninguna otra cosa hasta que no pase un minuto, ni ver si nos pulsan un botón, o refrescar una pantalla con el tiempo que queda de semáforo.

Así que no parece muy buena idea usar delays en muchas situaciones. No es raro que queramos programar tareas periódicas en nuestros Arduinos en rangos que van desde unos microsegundos hasta varios minutos, pero queremos hacerlo de un modo que entre tanto podamos seguir trabajando.

Para eso tenemos las interrupciones programadas o Timers, para que nos toquen el timbre cuando hay que ejecutar una función programada, sin necesidad de estar continuamente comprobando si es la hora.

Ya vimos en una sesión anterior el concepto de interrupción hardware. Es el equivalente a un timbre que nos avisa de que alguien está en la puerta y debemos atenderle.

Arduino dispone además de una segunda clase de interrupciones, los Timers, que hacen lo mismo que las interrupciones hardware, pero en lugar de dispararse cuando se cumple un cierto proceso hardware en uno de los pines, se dispara cuando ha transcurrido un tiempo preciso, previamente programado.

Es el equivalente del despertador, que cada mañana suena a la misma hora.

 

Los contadores internos de los Timers

 

Nuestros Arduinos UNOS y MEGAs tienen un cristal que bate a 16 MHz, o 16.000.00 de veces por segundo.

Teóricamente podríamos fijar una interrupción cada 1/16000000 segundos, lo que no sería muy útil porque cada instrucción de Arduino necesita varios pulsos de reloj para ejecutarse (y algunos muchos pulsos).

Por eso cada Timer dispone de un registro interno que indica cada cuantos ticks del reloj debe dispararse. Básicamente el que nos interesa es un Timer cuyo registro es un entero sin signo de 16 bits, lo que le permite contar hasta 216  = 65.536

Si este contador se disparase cada tick del cristal, no nos serviría de mucho porque, el máximo tiempo que el contador podría abarcar sería de:

Margen de tiempo

Como es muy probable que necesitemos algo más de flexibilidad en los tiempos, los chicos que diseñaron el corazón e Arduino, el ATMEGA328, incluyeron unos divisores de esta frecuencia básica del cristal de cuarzo., de modo que solo salta un tick al contador cada ciertos ticks del patrón básico.

En Arduino UNO esos divisores pueden ser  1, 8, 64, 256  y 1024. De ese modo podemos “frenar” hasta mil veces la velocidad de incremento del contador de disparo.

Un cálculo rápido nos dice que el anterior máximo de disparo puede subir desde los 4 ms de antes a 1024 veces más, o sea alrededor de 5 segundos.

 
  • Aunque pueda pareceros poco tiempo, ya es un margen considerable para atender a las cuestiones urgentes con precisión.
  • Naturalmente existen librerías que a partir de esto van incrementando contadores externos para multiplicar el plazo de disparo.
 

Como la explicación que hemos planteado para mostrar que la interrupción programada en el Timer saltará cuando el registro interno, alcance el valor fijado previamente (después de ralentizarlos con el divisor elegido) es algo, que cualquier memo puede entender, necesitamos complicarlo de algún modo que impida que esto se llene de paletos enredando con la electrónica.

Naturalmente el mejor modo de mantener este asunto entre señores, es complicar innecesariamente los nombres de las cosas de modo que los susodichos paletos salgan aullando nada más ver lo complicado que parece todo.

Así pues en la jerga de Arduino, no hablamos de contadores de disparo, (Seria una vergüenza, no), en su lugar les llamaremos Compare Match Registers o CTRs y a los divisores les llamaremos prescalers. Chúpate esa

Además, si leéis la documentación técnica que abunda en internet sobre los Timer Interrupts, os hablarán inmediatamente de los registros internos del chip y de cómo calcular la frecuencia de la interrupción en Hz y otras pavadas parecidas.

Me han levantado un dolor de cabeza superior.

Mi consejo es que os saltéis todo esto por ahora y os centréis en el uso de alguna librería sensata para controlar las interrupciones programadas como la TimerOne y punto. Y a eso vamos.

 
  • No hace falta ninguna librería para programar un Timer en Arduino UNO. Basta con programar directamente los registros internos del ATMEGA328,  
  • Arduino soporta sus instrucciones y conoce todos los valores y funciones precisas, pero os obliga a conocer los entresijos a nivel de hardware del procesador y por ahora esto si que es como para salir aullando.
  • Así que de momento haced como todos, como que no existe y centraros en la librería TimerOne. Ya tendremos tiempo de volver  sobre el asunto en el doctorado.
 

 

La librería TimerOne

 

Hay varias versiones de esta librería corriendo por ahí. Yo he elegido esta, básicamente  porque parece soportar más modelos de Arduino (Incluyendo al MEGA) y porque parece que el código es más rápido que el original. Podéis descargarla Aquí TimerOne.zip

Las cosas importantes de la librería. Cuando la importéis tendréis esta línea:

#include <TimerOne.h>

Esto nos crea un objeto llamado Timer1 directamente, sin necesidad de instanciarlos. Lo siguiente es programar el intervalo de disparo en microsegundos:

Timer1.initialize(150000);  // 150 ms

Y ya solo falta hacer el attach de la interrupción con el servicio  de gestión o ISR:

Timer1.attachInterrupt( ISR_Callback) ;

Y con esto ya habéis programado una interrupción temporizada que saltara cada 150 ms y llamara la función ISR_Callback. ¿Fácil no?

Veamos ahora un programa que se aproveche de esto. Podeís descarlo completo aqui:

Contenido solo disponible para suscriptores. ¡Accede al contenido!

Empecemos definiendo cosas

#include <TimerOne.h>
const int led = 13;  // the pin with a LED
int ledState = LOW;    // El LED empieza apagado
volatile unsigned long blinkCount = 0; // La definimos como volatile

void setup(void)
   {
       pinMode(led, OUTPUT);
       Timer1.initialize(250000);         // Dispara cada 250 ms
       Timer1.attachInterrupt(ISR_Blink); // Activa la interrupcion y la asocia a ISR_Blink
       Serial.begin(9600);
   }

Y ahora veamos la Función de servicio, recordad que conviene que una ISR sea la mínima expresion:

void ISR_Blink()
   {   ledState = !ledState ;
       blinkCount++    ;     // Contador veces se enciende el LED
   }

Cada vez que se invoca, invierte la situación del LED y aumenta blinkCount en uno para llevar la cuenta de las veces que invertimos el estado. Veamos la función principal:

Contenido solo disponible para suscriptores. ¡Accede al contenido!

    

Fíjate en que desactivamos las interrupciones en el momento que vamos a copiar el valor del contador, para evitar que entre otra interrupción a medio leer, y las volvemos a activar al finalizar.

Después podemos seguir con el programa normal sin preocuparnos de comprobar si toca o no toca saltar a la interrupción programada. ISR_Blink se ejecuta limpiamente cada tanto tiempo solita. Además podemos usar delays tranquilamente, porque las interrupciones tienen prioridad y se ejecutaran aun cuando el delay esté activo.

Aqui os dejo un mini video con el resultado

 

¿A que parece que es demasiado bueno para ser cierto? Pues tienes razón, en la vida no existe la felicidad completa  y las interrupciones tienen ventajas e inconvenientes.

Entre las ventajas tenemos:

 
  • Código limpio y elegante. No tenemos que calcular en el loop si estaremos perdiéndonos algo o no. Cuando el tiempo programado se cumple la interrupción salta y se ejecuta limpiamente.
  • Conceptualmente la programación orientada a eventos es la predominante en los moderno sistemas operativos como Linux, Windows o OSX y si aprendéis a pensar así no os resultara difícil entender el concepto bajo Visual Basic o C#.
  • No importa que estemos en un delay, la interrupción salta impecable.
  • La medida del tiempo es muy precisa.
 

Pero como la felicidad completa no existe tenemos que hablar de los inconvenientes:

 
  • El primero y grave, es que si jugamos con los timers, muchas de las instrucciones que dependen de ellos dejaran de funcionar.
  • Entre estos están, los pines PWM y analogWrite() y la librería Servo. Dependiendo del modelo Arduino y del Timer que usemos la cosa es grave.
  • Si vuestro Timer entra en conflicto con algo puede ser muy complicado comprender el problema.
  • Si tu Servicio ISR tarda más en ejecutarse de lo que tarda en saltar la nueva interrupción (Y te puede pasar por un error de cálculo) antes de acabar puede volver a entrar porque ha disparado de nuevo el Timer. La situación alcanzara un nivel de peligro inmediato porque tu Arduino se colgará y no sabrás porque.
 

 

Resumen de la sesión

 

Hoy en nuestro curso incio para arduino hemos aprendido lo siguiente:

 

 

    • Hemos visto que además de las interrupciones por hardware podemos ejecutar interrupciones programadas mediante Timers.
    • Hemos visto una librería, la TimerOne con la que resulta asquerosamente sencillo programar una de estas interrupciones.
    • Hemos comentado que no conviene enredarnos por ahora en la programación interna del procesador.
    • Hemos creado un pequeño ejemplo de muestra, para provocar con una interrupción programada, un blinking LED.
 

 

 

Para porder realizar consultas a nuestros expertos, tienes que ser suscriptor. Suscribiendote nos ayudas a mantener este proyecto en marcha.

¡ Quiero Suscribirme !

Si ya eres premium y no puedes comentar haz login. Hacer login

(115) Comments

  • Avatar for Charly
    • Danilo

    Que tal gracias por el tutorial, podría utilizar un timer para el siguiente caso:
    Al activar un interruptor necesito que se envíe una señal a una frecuencia de 800 rpm o 13 Hz, con un pulso de activación de 1 ms, 2 ms y 3 ms durante 3 segundos cada uno, es decir todo ese ciclo debería durar 10 seg.
    Al terminar el ciclo se aumenta la frecuencia a 1000 rpm o 16 Hz y se repite nuevamente los pulsos de activación.
    Alguna recomendación por favor?
    Gracias de antemano

  • Avatar for Charly
    • Ivan Uriarte

    No tendrías por qué complicarte tanto, puedes usar la función millis() para hacer un contador.

  • Avatar for Charly
    • Saul

    hola, tal ves este no sea el lugar para esta pregunta, pero me surgió la curiosidad por saber si con esta librería puedo hacer que arruino pida que te identifiques con una tarjeta y que el lector RFID la lea cada 10 min…
    Gracias y por cierto que buen post.!

  • Avatar for Charly
    • Prueba TPV TPV

    Los GPS son de los relojes mas precisos que podemos comprar, por lo que si utilizas como reloj uno, deberias corregir los problemas de precison que mencionas sin problemas

  • Avatar for Charly

    Hola …. se que esta consulta que hizo Adrian ya tiene más de un año…. pero me interesa saber …. si utilizando como base de tiempo externa un GPS…. podría solucionar desvío de tiempo de Adrian para que el reloj no se desvíe tantos segundos…. prácticamente sea mas preciso al grado de tener algún desvió por no mas 10 micro segundos???…

    Gracias de antemano por las posibles respuestas…..

  • Avatar for Charly
    • Ivan Uriarte

    Aunque lo pares es probable que te haga conflicto al tenr la librería y hacer las declaraciones. Yo intentaría usar otro timer para las interrupciones.

  • Avatar for Charly
    • Mike

    Buenas! muchas gracias por subir este tutorial.
    Tengo una pregunta, una vez inicializado el Timer1 se puede hacer que deje de dispararse? Por ejemplo: hacer que si se cumple una condicion se deje de disparar este timer para poder usar interrupciones hardware y micros().
    Estoy haciendo un programa que quiero que pase de interrupciones hardware en algunos momentos(en esta etapa del programa uso micros sin problema) a interrupciones programadas en otros momentos, y me preocupa que aunque quite la interrupcion programada, si no «desinicializo» el timer1, este disparo entre en conflicto con micros, y deje de calcularme bien los tiempos que luego utilizo para inicializar el timer1.

    Saludos

  • Avatar for Charly
    • Ivan Uriarte

    Tiene pinta de que necesitas descargar una librería para controlar el timer2 http://forum.arduino.cc/index.php?topic=440256.0

  • Avatar for Charly
    • Joaquín

    Hola
    Cuando intento iniciar Timer2 del mismo modo que Timer1

    Timer1.initialize(40); // Dispara cada 4 microsegundos
    Timer1.attachInterrupt(ISR_Blink_G); // Activa la interrupcion y la asocia a ISR_Blink_G
    Timer2.initialize(250000); // Dispara cada 250 ms
    Timer2.attachInterrupt(ISR_Blink_R);

    me dice esto:
    error: ‘Timer2’ was not declared in this scope
    Error de compilación

  • Avatar for Charly
    • Ivan Uriarte

    Hola Joaquín, puedes usar otro de los timers.

  • Avatar for Charly
    • Joaquín

    Hola
    gracias por compartir tus conocimientos
    Estoy usando Timer1 para generar una señal de PWM de 25 KHz
    Timer1.initialize(40); // 40 microsegundos = 25 kHz
    Timer1.pwm(PWM_PIN, 0); // digitalWrite(PWM_PIN,LOW);
    y más adelante hago
    Timer1.pwm(PWM_PIN,duty);
    para que se aplique el PWM a 25 KHz
    en otra parte del código necesito activar un timer cada segundo, o medio segundo, para enviar datos por BT.
    ¿cómo podría hacerlo si ya tengo activado el tiemer cada 4 microsegundos?
    en definitiva, ¿cómo combinar muy distintos intervalos en un código?
    gracias
    Joaquín

  • Avatar for Charly
    • Ivan Uriarte

    Hola Pedro, podrías hacer una chapuza que fuese incrementando una variable con cada interrupción y hacer el cálculo para que sólo haga lo que quieres cuando coincida con 10 minutos, pero creo que hay RTC que pueden enviar un pulso cuando tu les digas y que podrías usar como interrupción, que es de lejos muchísima mejor solución.

  • Avatar for Charly
    • Ivan Uriarte

    Hola Luis, pruébalo a ver si funciona, tendrías que poner un tiempo de 0,00011111111111111112 segundos si no me equivoco.

  • Avatar for Charly

    Hola, quiero interrumpir una variable con una frecuencia de 9k Hz se podra hacer con que timer?

  • Avatar for Charly
    • Pedro Ospina

    Hola me gustaría saber cuánto es el maximo de tiempo para que funcione la interrupción se puede después de 10min?

Para porder realizar consultas a nuestros expertos, tienes que ser suscriptor. Suscribiendote nos ayudas a mantener este proyecto en marcha.

¡ Quiero Suscribirme !

Si ya eres premium y no puedes comentar haz login. Hacer login