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.

Imagen de Arduino UNOArduino UNO o equivalente.

 

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. Empecemos definiendo cosas  Prog_57_2

#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 funcion principal:

void loop(void)
   {
       unsigned long N;  // Haremos copia del blinkCount
       digitalWrite(led, ledState);  // Asignamos el valor del
                                     // status a la salida
       noInterrupts();               // Suspende las interrupciones
       N = blinkCount;
       interrupts();                 // Autoriza las interrupciones

       Serial.print("Ciclos = ");
       Serial.println(N);
       delay(100);
   }

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

 

 

 

    • 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.
 

 

 

(69) Comments

  • Buen dia… excelentes sus tutoriales….
    Puedo utilizar mas de un timer en un mismo programa…?

    • Hola Carlos, el microcontrolador que usa Arduino UNO dispone de 3 Timers pero se usan para funciones del core de Arduino. Los Timer son 0,1 y 2.
      El Timer 0 es utilizado,por ejemplo, por la función millis().
      El Timer 1 y 2 es usado para PWM (si no estás utilizando PWM, pues puedes usarlo).
      A parte hay otras librerías que también lo usan. librerías que también los usan.

  • Buenas,
    Con un arduino Mega estoy controlando la velocidad de 2 motores dc. Es un control PID( de velocidad) y la alimentacion con PWM, 2 encoders para medir la velocidad. Cada pulso que envía el encoder lo recibe el arduino con una interrupcion. El problema viene cuando pongo a correr el programa los motores se activan un par de segundos y se desactivan. Mi pregunta es:¿las interrupciones pueden estar pausando la alimentacion PWM de los motores? si es asi,¿como puedo solucionar eso?
    Desde ya, muchas gracias

    • LEONARDO

    Buen dia

    Buenos comentarios

    Tengo un problema, a ver que solución tienen para mi Proyecto “Microrobot Explorador”
    Tengo 8 sensores ultrasonicos Hcr04 funcionando.conectado a dos servos con las siguientes función de “robot adelante” funcionando, cuando detecta un obstáculo va a la función “robot parado” y retrocede con la función “robot atras” funcionando con delay, pero como hago para quitar la función delay ya que los 8 sensores se cuelga al ,momento de sensor la distancia. la idea es generar un tiempo independiente de parado por lo menos de 2 segundos, un tiempo de retroceso de 4 segundos y un tiempo de 2 segundos para girar derecha o izquierda dependiendo de la lógica de los sensores

    Muchas gracias

    • Hola Leonardo, la manera más sencilla de evitar delays es usar la función milis(); que cuenta el tiempo transcurrido en milisegundos desde que se ejecuto el programa. Así puedes compararlo en cada ejecución con el valor que quieras y calcular tiempos sin usar los delays. Un saludo.

    • Toño

    Vaya columpiada, Juan. Hay que leer con más detenimiento para no meter la pata así. Y no estar a la que salta tampoco está mal. Por mi parte os agradezco todos estos tutoriales, a mi me han sido muy útiles.

  • No me parece bien que llames paletos a gente que empezando con la electrónica intente resolver sus dudas buscando en Internet en páginas como esta. Igualmente que intentes premeditadamente complicarles las cosas para que: “los susodichos paletos salgan aullando nada más ver lo complicado que parece todo”. Un poco de respeto a los lectores que son lo primero, por favor revisa tu pedagogía. Una cosita más, los cristales de cuarzo oscilan no baten, la que bate es la batidora que tenemos todos en casa para hacer mayonesa. Y ademas 16 MHz es igual a 16 seguido de seis ceros, no de cinco.

    Un saludo.
    Juan

    • Juan, Siento que creas que me burlaba de los novatos que se acercan a internet buscando resolver sus dudas. Muy al contrario todo el escrito esta pensado para riciculizar el intento de complicarle las cosas al novato y mas porque no lo considero necesario. Nuestro intento ha sido siempre simplificar en lo posible todo el acceso a unas cuestiones que son sencillas a poco bien que te las presenten y te recuerdo que novatos hemos sido todos en un momento dado.
      Por eso me gustaria insistri en que lamento si el tono usado te parece desafortunado por la intencion nunca fue ridiculizar a los nuevos sino todo lo contrario: ridiculizar a los que llevando ya mucho tiempo complican sin necesidad las cosas.
      Por eso te pido disculpas si te hemos ofendido, pero te pediria si te parece, que leas el tema de nuevo teniendo claro cual era nuestra intencion desd el principio y creo que nos entenderas un poquito mejor
      Un saludo

  • Que tal amable gente de Prometec, esperando a resolver mi problema dejo aquí la siguiente cuestión. He combinado una serie de tutoriales para Sensar temperatura con un LM35DZ y que me lo muestre por el puerto Serial. Cuando esa temperatura es mayor que; 15 grados, mi sistema es estable y no pasa nada, pero si la temperatura es igual o menor a 15 grados entonces me mande un mensaje de alerta. Despues de este primer mensaje quisiera que esperará 1 min y entonces si la temperatura sigue igual o bajo 15 grados que me vuelva a mandar el mensaje de alerta, si la temperatura está por encima de lo establecido entonces solo sigue trabajando (sensando).

    Dejo aquí el programa y lo que me hace hasta este momento el programa es que una vez que detecta que la temperatura es igual o menor que 15 grados, no deja de mandarme los mensajes. Sé que me llevaré más conocimiento después e esto, así que les agradezco de antemano.

    #define pin3 3

    float aux;
    float temp;
    int valor2; // Esto es una variable entera
    int contador=0; // Variable contador igual a cero en el inicio.
    int activado=0; // Al principio no ha sido activado.
    long inicio, final, actual; // Tiempos.
    void setup()
    {
    pinMode(pin3, INPUT);
    pinMode(8, OUTPUT); //temperatura baja
    pinMode(7, OUTPUT); //temperatura estable
    Serial.begin(9600); //Configura velocidad del puerto serie del Arduino
    Serial.println(“LISTO”);
    delay (1000);
    }

    void loop()
    {
    aux = ((analogRead(0)-analogRead(1))*500.0)/1023.0;

    if (temp != aux)
    temp = aux;
    Serial.print(” Temp: “);
    Serial.print(temp);
    Serial.println(” Grados Celsius”);

    if (temp>=16){
    digitalWrite(7, HIGH);
    digitalWrite(8, LOW);
    }

    if (temp final) ) { // Si fue activado=1 y el tiempo actual es mayor que el final….
    loop(); // haz un parpadeo.
    }
    }

    Nota: He puesto un puente del pin 8 al pin 3 para que cuando la temperatura sea baja ponga en 1 el pin 3 y haga la instrucción tiempo, dado que el pin debe estar en HIGH, esta rutina ya la he probado encendiendo un LED, despues de un pulso, espera el tiempo que en este caso declaré como 1min = 60000ms, y si detecta que sigue presionado repite el proceso.

    • Marcos

    Hola, podria usar esta libreria en el mismo sketch con la libreria de IRLremote ? quiero hacer que a traves de un control pueda encender Lamparas y utilizaria la libreria Timer para enceder cierto tiempo una tira Led.
    Saludos

    • Hola Marcos no parece que deberias tener ningun problema pero no saldremos de dudas hasta que lo pruebes

    • Michelle

    Hola gracias por contestar,soy Michelle ( me equivoque con la a) no entendí bien lo del reloj me podrías explicar mas a detalle, lo siento pero soy nueva en esto, estoy estudiando carrera técnica en mecatronica pero no se trabar mucho con el arduino. E Ivan lo de restar el valor…… ?

    • Hola Michelle, yo creo que la forma más sencilla de hacerlo es usar la función millis() como te dije. Esta función te da el tiempo transcurrido en milisegundos desde que empezó el programa, pero puedes reiniciarlo cada 15 segundos junto con el contador en el que cuentes las pulsaciones.

      Echa un ojo a la función millis() y a la sesión dedicada a los pulsadores y si tienes alguna duda me dices y te echamos una mano. Un saludo!

    • Michella

    Hola, necesito programar un proyecto que consiste en contar las pulsaciones durante 15s y las compare para ver si están dentro del rango establecidos, si no lo están, sonara una alarma. como podría programarlo? Gracias!!!

    • Hola Michella, creo que la forma más sencilla sería utilizar la función milis() para guardar el tiempo que ha pasado en una variable, y que resetees su valor y el de las pulsaciones cada 15 segundos. Si las pulsaciones no superan el límite en ese tiempo, haces sonar la alarma.

      Un saludo.

    • Hola Michella, 15 segundos e smucho tiempo para un procesador. Yo probaria con un reloj RTC para medir el tiempo

    • Jordan

    Hola, me podrían ayudar, necesito un programa que con un pulso en una entrada digital me entregue 6 pulsos de salida, que esto ocurra cada vez que se le entregue el pulso de entrada, creo que es bastante simple pero soy nuevo, muchas gracias.

    • Hola Jordan, no nos das muchas pistas

    • Bruno

    Hola, alguien sabe como hacer que al leer una entrada digital, se active una salida, pero esta salida me debe entregar 6 pulsos con intervalos de medio segundo aproximadamente

    • hola bruno. puedes escribir una funcion que envie esos pulsos con los intervalos que necesites usando el reloj interno sin necesidad del timer y sera bastante mas sensillo

    • JOnathan Rivera

    Hola me puedes ayudar con un proyecto que quiero realizar quiero hacer una alarma para mi colegio que suene cada 45 min

    • Hola Jonathan! Supongo que necesitas que la alarma suene a horas concretas. Mira te dejo un enlace a una sesión que te puede ayudar, donde usamos un reloj en tiempo real (módulo rtc) para saber la hora y la fecha en todo momento. Y además al final tienes una librería que precisamente sirve para manejar alarmas. Espero que te sirva. http://www.prometec.net/relojes-rtc/

      Un saludote!

    • Buenos dias jonathan

      Sera un plcer ayudarte con tu proyecto. Basta con que lo postees en el foro y asi entre todos vamos echandole una ojeada y ayudando en lo que podamos , vale?

  • Hola, muy buen articulo. Solo una duda, no se cuan descabellada, puedo reconfigurar un timer dentro del mismo? O sea modificarle la base de tiempo.

    Saludos y desde ya gracias por su respuesta.

    • Hola Rene, No se muy bien lo que quieres decir, pero siempre puedes reprogramar los timers cuando lo necistes

Give a Reply

WordPress Anti-Spam by WP-SpamShield