Timer 2: Introducción

.

 


El microcontrolador de las placas Arduino dispone de 3 módulos Contador/Timers (dos de 8 bits y uno de 16 bits). Se trata de hardware independiente al microprocesador que puede ejecutar tareas paralelas sin detener el flujo del programa. Algunas de éstas tareas son contador, medida precisa de tiempos, generador de ondas, .... Estos Timers son usados internamente por el core de Arduino para sus funciones internas, como pueden ser millis, delay, analogWrite,...

Aquí vamos a ver cómo trabajar con el Timer 2, el cual es de 8 Bits. El Timer 2 es usado internamente en el core Arduino para generar el PWM por los pines 3 y 11, por lo que no se podrá usar dicha función en conjunto con el código propuesto más adelante.

Para empezar vamos a ver el funcionamiento de dicho contador en modo Normal (Normal mode).
Se trata del modo más sencillo, en el cual el el contador se incrementa a cada pulso de reloj  hasta llegar a su máximo (8 bits=255) y se reinicia de nuevo desde 0. Es decir 0,1,2,...255 -> 0,1,...,255 -> 0,1,...
En el momento que el contador vuelve a 0 (registro TCNT2), se activa el  flag de  "overflow" (TOV2).  Se puede configurar una interrupción (overflow del timer) cuando ésto ocurre. Dicha interrupcion se encarga de resetear dicho el flag.

Notas:
El flag es un bit que cambia su estado, como si fuera una "alarma" para avisar al programador que un evento ha ocurrido. Dicho bit (TOV2) de overflow se encuentra en el registro TIFR2.
Una interrupción es una señal recibida por el microcontrolador, que interrumpe la ejecución del código actual y cambia a la rutina que atiende dicha interrupción.

Normalmente el cristal usado en la mayoría de las placas Arduino es de 16 MHz (cuidado que algunas como LilyPad van a 8Mhz), lo que significa que la resolución máxima es de 1/16.000.000= 62.5 ns. Es decir, que el contador alimentado por la frecuencia reloj del micro, incrementa en uno su valor cada 62.5 ns, y tendría su overflow cada 256*1/16000000=0.016ms.

Se puede observar que es un tiempo muy pequeño. Para poder tener más rangos, el timer dispone de PRESCALER. Ésto es simplemente un divisor de la frecuencia del reloj que le esta alimentando al timer. Los PRESCALER disponibles son 1,8,32,64,128,256 y 1024.


El lenguaje Arduino no dispone de funciones propias para configurar los timer. Los registros internos del microcontrolador para configurar el Timer2 son TCCR2A, TCCR2B, TNT2, OCR2A,OCR2B, TIMSK2,TIFR2,ASSR y GTCCR. Lo mejor para ampliar conocimientos acerca de todas las posibilidades que existen es el datasheet del propio microcontrolador.

En el registro TCCT2B se disponen los bits CS22,CS21 y CS20 (bit 2,1 y 0). Dichos bits son los encargados de configurar el PRESCALER del timer.
Veamos las combinaciones de CS22 - CS21 - CS20 respectivamente.
  • 0 - 0 - 0 => No Clock
  • 0 - 0 - 1 => No Prescaling  [0.016 ms]
  • 0 - 1 - 0 => Prescaler=8  [0.128 ms]
  • 0 - 1 - 1 => Prescaler=32  [0.512 ms]
  • 1 - 0 - 0  => Prescaler=64  [1.024 ms]
  • 1 - 0 - 1  => Prescaler=128  [2.048 ms]
  • 1 - 1 - 0  => Prescaler=256  [4.096 ms]
  • 1 - 1 - 1  => Prescaler=1024  [16.384 ms]

He preparado un programa, el cual conmuta el Pin 13 de Arduino cada vez que se produce la interrupción de Overflow. He recogido la salida de dicho pin con un Analizador Lógico. Lo que se observa, es una señal cuadrada, es decir el pin esta conmutando de estado ALTO="1" a estado BAJO="0" según la configuración de los bits CS22-CS21-CS20.


(click para agrandar)

Para lograr el máximo rendimiento del código, he usado control directo de los registros del microcontrolador para conmutar el pin 13 y así no meter "retrasos" debido a la ejecución de código. Para entendernos, la instrucción digitalWrite es "lenta".
Los tres registros internos usados por el microcontrolador para el control de los puertos son:
  • DDRx   => Configura los pines del puerto como entrada ("0") o salida ("1").
  • PORTx => Controla si el estado ALTO y BAJO de los pines de dicho puerto.
  • PINx    => Estado de los pines.
siendo x alguno de los tres puertos disponibles en el micro: B,C ó D.
Para saber la equivalencia entre los pines de Arduino con los del micro, visitar: http://www.arduino.cc/en/Reference/PortManipulation
Cómo se puede ver, el pin 13 de Arduino equivale al PB5 (puerto B, bit 5).

Truco => Si escribes sobre el registro PINx cuando esta declarado cómo salida, el pin conmutará.


_BV() usado en el código es un macro definido en avr/sfr_defs.h, el cual se añade indirectamente al incluir avr/io.h.  Para hacerlo fácil, lo puedes usar sin añadir dichos includes, ya que esta incluido en el core de Arduino.
Dicho macro esta definido como: #define _BV( bit ) ( 1<<(bit) )
Lo que hace es poner a "1" el bit definido dentro del paréntesis de la función.

No he usado dicho macro para la configuración de los registros del Timer 2 dentro del setup() para intentar ser más claro y entenderlo mejor mientras se esta leyendo el datasheet del micro.

Queda de ver una cosa.... ¿Cómo configuro las interrupción de overflow del timer 2?
Para "recoger" las interrupciones generadas por el microcontrolador, nuestro código debe disponer de la siguiente estructura:


#include <avr/interrupt.h>

ISR(TIMER2_OVF_vect)
{
// código a ejecutar cuando se activa la interrupción
}

ISR(int) define la función que se ejecutará cuando se active la interrupción int, en este caso concreto, la interrupción de overflow del Timer 2 (TIMER2_OV_vect). Existen más interrupciones cómo por ejemplo: INT0_vect, USART_RX_vect, ...
Para ampliar información, visitar http://www.nongnu.org/avr-libc/user-manual/group__avr__interrupts.html .




Código del sketch:

#include <avr/interrupt.h>

//-----------------------------------------------------
// Rutina de Interrupcion [TIMER2_OVF_vect es la rutina que se ejecuta cuando el Timer2 ->Overflow]
// Arduino tiene un cristal de 16 Mhz
// Por lo que tenemos una interrupcion cada => 1/ ((16000000 / PRESCALER) / 256)
ISR(TIMER2_OVF_vect) 
{
  PINB |=_BV(PB5);
}



void setup() 
{
  DDRB=_BV(PB5);   // Pin 13 Arduino equivale a PB5

  // NORMAL MODE
  TCCR2A = (1<<COM2A1) | (0<<COM2A0) | (0<<COM2B1)| (0<<COM2B0) | (0<<3) | (0<<2) | (0<<WGM21) | (0<<WGM20);  

  TCCR2B = (0<<FOC2A) | (0<<FOC2B) | (0<<5) | (0<<4) | (0<<WGM22) | (1<<CS22) | (0<<CS21) | (0<<CS20); 
  
  // Activo Timer2 Overflow Interrupt
  TIMSK2 =(0<<7) | (0<<6) | (0<<5) | (0<<4) | (0<<3) | (0<<OCIE2B) | (0<<OCIE2A) | (1<<TOIE2);  
  
  // Oscilador interno
  ASSR = (0<<7) | (0<<EXCLK) | (0<<AS2) | (0<<TCN2UB) | (0<<OCR2AUB) | (0<<OCR2BUB) | (0<<TCR2AUB) | (0<<TCR2BUB) ;
  
  //Activo interrupciones globales.
  sei();

}


void loop() 
{
  

}







Veámos ahora un ejemplo de cómo usar el Timer2 para controlar diferentes tareas. En este ejemplo, conmutaremos el estado de dos pines (pin 13 y pin 1 de Arduino) a dos frecuencias diferentes. Es decir, haremos que el led conectado al pin 13 parpadee cada 10 ms y que el pin 1 cada 25 ms.
Para ello configuramos nuestro Timer2 para que active la interrupción cada 1ms. Dentro de la interrupción aumentará una variable llamada ticks. Dicha variable será la base para controlar la ejecución del resto de mi programa.
Por lo que el flujo de mi programa vendrá controlado basado en unidades de 1ms (variable ticks). Esto es la base de cómo hacer "tareas multiples" con un microcontrolador.



(click para agrandar)



Código del sketch 2:

#include <avr/interrupt.h>

//DECLARACION DE VARIABLES GLOBALES
volatile unsigned long ticks;
unsigned long Tarea1;
unsigned long Tarea2;

//-----------------------------------------------------
// Rutina de Interrupcion [TIMER2_OVF_vect es la rutina que se ejecuta cuando el Timer2 ->Overflow]
// Arduino tiene un cristal de 16 Mhz
// Por lo que tenemos una interrupcion cada => 1/ ((16000000 / PRESCALER) / 256)
ISR(TIMER2_OVF_vect) 
{
  ticks++;
}



void setup() 
{
  DDRB=_BV(PB5);   // Pin 13 Arduino equivale a PB5
  DDRD=_BV(PD1);   // Pin 2 Arduino equivale a PD1

  // NORMAL MODE
  TCCR2A = (1<<COM2A1) | (0<<COM2A0) | (0<<COM2B1)| (0<<COM2B0) | (0<<3) | (0<<2) | (0<<WGM21) | (0<<WGM20);  
 
  TCCR2B = (0<<FOC2A) | (0<<FOC2B) | (0<<5) | (0<<4) | (0<<WGM22) | (1<<CS22) | (0<<CS21) | (0<<CS20); 
  //Configurado a 1.024 ms
  
  // Activo Timer2 Overflow Interrupt
  TIMSK2 =(0<<7) | (0<<6) | (0<<5) | (0<<4) | (0<<3) | (0<<OCIE2B) | (0<<OCIE2A) | (1<<TOIE2);  
  
  
  // Oscilador interno
  ASSR = (0<<7) | (0<<EXCLK) | (0<<AS2) | (0<<TCN2UB) | (0<<OCR2AUB) | (0<<OCR2BUB) | (0<<TCR2AUB) | (0<<TCR2BUB) ;

  // Inicializo variables
  Tarea1=10;
  Tarea2=25;  
  
  //Activo interrupciones globales.
  sei();


}


void loop() 
{
   
  
  if (ticks>=Tarea1)
  {
    PINB |=_BV(PB5);   //Conmuto pin 13
    Tarea1+=10;        //Configuro para que se ejecute de nuevo la Tarea1 dentro de 10 ticks
  }
  
  if (ticks>=Tarea2)
  {
    PIND |=_BV(PD1);  //Conmuto pin 2
    Tarea2+=25;       //Configuro para que se ejecute de nuevo la Tarea2 dentro de 25 ticks
  }
  
  
}



Espero que ésto ayude y no dudes en contactar conmigo si encuentras algun error.




Links recomendados:
http://www.avrfreaks.net/index.php?name=PNphpBB2&file=viewtopic&t=50106   [Newbie's Guide to AVR Timers]
http://arduino.cc/en/Tutorial/SecretsOfArduinoPWM ó http://arduino.cc/es/Tutorial/SecretsOfArduinoPWM [castellano]
http://www.nongnu.org/avr-libc       [Libreria de C para AVR usada por el core de Arduino]
http://www.tte-systems.com/books/pttes [ Libro Patterns for Time-Triggered Embedded Systems ]







.

16 comentarios:

  1. Muchas gracias!
    Me ha servido de mucho tu explicación.
    Gracias por compartir tu conocimiento.

    ResponderEliminar
  2. reyjlopez@gmail.com5 de julio de 2011, 11:09

    Hola, me podrias compartir el nombre del software que utilizas, por favor, jaja para ver si de una buena vez entiendo este asunto.

    ResponderEliminar
  3. ¿Qué software? He capturado las señales con un analizador lógico (si es lo que te refieres). (http://www.saleae.com)

    ResponderEliminar
  4. Hola, he logrado hacer dormir el micro atmega328 a través de la interfaz IDE 1.0 de Arduino sin problemas...lo que quiero hacer es enviar a dormir en modo Power-Save, el cual mantiene funcionando el Timer2, y que duerma durante 4 segundos, luego se despierte, envie un dato y vuelva a dormir durante 4 segundos, ojala más...

    ¿Me podrías ayudar con algo de código para hacer que el micro se despierte cada vez que se desborde el timer2 luego de 4 o 8 segundos?

    Gracias desde ya....un abrazo

    ResponderEliminar
  5. Hola pcortes, has visto la otra entrada en el blog relacionada con lo que pides?
    http://real2electronics.blogspot.com/2009/09/power-sleep-mode.html

    ResponderEliminar
  6. Hola Igor, la verdad es que no habia visto esta web, pero había visto tu comentario en el foro de Arduino, pero igual no me resultó...e SLEEP MODE TEST CODE el led queda encendido...al parecer digitalWrite(13,!digitalRead(13)); no está haciendo su trabajo.


    Encontré esta web http://interface.khm.de/index.php/lab/experiments/sleep_watchdog_battery/ y la verdad es que al parecer funciona, pero parece que alguien usó mi tester y tengo quemado el fusible de los mA, así que mañana haré las pruebas a ver si realmente me baja el consumo.

    Muchas gracias por tan pronta respuesta.

    Saludos desde Chile

    ResponderEliminar
  7. Hola gracias por compartir conocimiento.
    Me podrias ayudar con el uso de una interrupcion anidada dentro de otra
    es que tengo el timer2 leyendo un teclado cada overflow, pero deseo que lea un acelerometro
    que usa la interfaz I2C y la interrupcion TWI.
    De momento se de la ISR_NOBLOCk

    ResponderEliminar
  8. hola amigo gracias por compartir tus conocimientos. Estoy tratsando de arrancar 4 sepic en paralelo con cuatro señales pwm desfasada 10 us cada una de ellas respectivamente (10us 20us 30us) he logrado algunos avences pero no logro sincronizar las 4. pe podrias dar alguna idea???
    otra cosita los diente de sierra son independientes???

    ResponderEliminar
  9. Buenas tardes, llegué aquí a través del foro de "aumentar velcidad de muestreo". Necesito hacer un muestreo periódico, a aproximadamente 25KHz, y además necesito seguir haciendo un par de cosas en paralelo con el muestreo.
    Para muestrear a esta frecuencia no me coincide ninguno de los tiempos con los overlflow. Recuerdo que con el famoso Atmel 8951 uno podía configurarle el contador del timer, para que en lugar que empiece desde cero, lo configuraba a gusto, para poder hacer interrupciones por overflow de la duración deseada. Es eso posible de realizar con el AtMega328 del Arduino?
    Agradezco de antemano por la ayuda que hasta el momento ya me han brindado en el foro, y espero que se entienda mi pregunta y que alguien me la pueda responder. Saludos.

    ResponderEliminar
    Respuestas
    1. Efectivamente puedes poner un valor inicial. Lo mejor es ojear el datasheet del uC.

      Eliminar
  10. muy bien explicado, gracias por compartir tus conocimientos

    ResponderEliminar
  11. felicitaciones por tu web.

    Estoy buscando la manera de controlar dos transistores T1 y T2 desde un pulso PWM generado por un arduino.

    Quiero usar el tiemer 1 de 16bits, usar el pin 9 para el T1 y el pin 10 para el T2.

    Cuando el PWM esté alto activará el T1 y cuando el PWM esté bajo que active el T2

    La frecuencia de los dos pins debe ser la misma, el pin 9 que contola el T1 es el principal.
    Los pulsos del pin 10 deben estar invertidos respecto a los que salen por el pin 9.

    No se si me he explicado, podeis ayudarme?
    un millon de gracias por vuestra esperada respuesta.

    ResponderEliminar
  12. hola que tal muchas gracias por tu aporte, me surge una duda puedo usar dos timers a la vez???

    ResponderEliminar
  13. Esta instrucción pone el bit en 1, pero cuando lo pone en cero ?

    PINB |=_BV(PB5);

    NO debería ser asi_

    PINB ^=_BV(PB5);

    ResponderEliminar
  14. Gracias excelente info. necesitaría saber si es posible con una interrupción leer y enviar datos por dos UARTS de un arduino mega (en una lee y lo que lee lo envía por la otra) mientras se sigue ejecutando normalmente un programa, gracias de antemano.

    ResponderEliminar