Timer 0 - Como Temporizador en CCS

Como podríamos saber, los microcontroladores poseen en su mayoría varios temporizadores, pero en este caso hablare del Timer 0 ya que este se encuentra en los microcontroladores mas económicos y antiguos. Este puede funcionar como Contador o como Temporizador.
Si lo configuramos como Contador este debe adquirir pulsos desde el exterior y si lo configuramos como Temporizador utilizara el mismo oscilador del microcontrolador. 
Mostrare un ejemplo de utilización simple como temporizador, esta función sera útil cuando necesitamos realizar una tarea que corra en paralelo a nuestro programa principal, por ejemplo, el multiplexado de displays para visualizar algún valor. En este ejemplo que comentaba debemos atender el multiplexado de los display de forma independiente al programa principal, esto nos resulta útil porque por ejemplo en un reloj tenemos el segundero que cambia cada 1 segundo pero el muestreo de los displays se debe realizar a una frecuencia mucho mayor a 1 segundo para lograr una persistencia en la visión.
Si configuramos este timer en lenguaje ensamblador tendremos que manipular directamente los registros TMR0 (Banco 0) y OPTION (Banco 1), dentro del registro OPTION se encuentran los bit PS2, PS1 y PS0, estos serán los que configuran el Prescaler del Oscilador del microcontrolador. En otras palabras, como antes mencionaba cuando el Timer 0 funciona como temporizador utilizara los pulsos de reloj del mismo microcontrolador pero por ejemplo para un cristal de 4MHz (donde el bus del microcontrolador funcionara a 1MHz) debemos dividir esta frecuencia ya que es demasiado rápido para realizar un timer, entonces con el prescaler seteando los tres bit PS2 al PS0 logramos setear el factor de división que les mostrare en la siguiente tabla:



Hay otros bit's obviamente en los registros del Timer pero en este caso tratare de hacer lo mas sencillo posible el funcionamiento del mismo como temporizador y dejare esos bit's extra para su investigación.
Dentro del datasheet podemos encontrar la formula para calcular el tiempo con el que se creara la interrupción del timer.
El timer funciona como un contador de 8 bits, (del 0 al 255) cuando este llega al máximo produce un overflow (desbordamiento) y cada vez que se produce se genera una interrupción que reinicia el timer y comenzara de nuevo.
Para calcular el tiempo debemos, aparte del divisor de frecuencia de reloj, setear una variable que modificara el valor de cuenta (0 a 255) para que desborde y produzca la interrupción antes o después del tiempo que divide el prescaler.


Ti: Tiempo de interrupción
fosc: Frecuencia del oscilador
Vs: Valor de seteo del timer
P: Prescaler

Si nosotros no usamos el valor de seteo del timer, la formula quedara:



Ejemplos [con prescaler en 4]:
Sin valor de seteo:

Con valor de seteo:

Podemos concluir que al no usar valor de seteo del timer, la interrupción sera de 1,024ms y con el valor de seteo sera de 1ms, entonces aquí podemos ver la importancia de esta variable cuando uno requiere una interrupción a un periodo mas exacta (si quisiéramos que sea a 1ms).
NOTA: este calculo sera eficiente cuando trabajemos en lenguaje ensamblador, lamentablemente al trabajar en C perdemos algo de precisión y este calculo tendrá un error de aproximadamente 3%, por eso debemos medir nuestra salida con un frecuencimetro u osciloscopio para saber exactamente si la interrupción es correcta, solo tendremos que ajusta el valor de seteo.

Cuando utilicemos este timer debemos tener en cuenta que el código que se ejecutara en este temporizador debe encontrarse dentro de la funciona timer0(); donde esta tendra el vector de interrupción inmediatamente antes de la función con la nomenclatura #int_timer0.
Dentro de esta función se pondrá el código que queramos, al final de nuestro código (Dentro de esta función pondremos el valor de seteo del timer set_timer0(valor); y luego cerramos la llave de la función.

Dentro de la función principal, tendremos que configurar el timer y las interrupciones.
1) setup_timer_0(RTCC_INTERNAL|RTCC_DIV_4); aquí decimos que el timer funcionara como temporizador con el reloj del microcontrolador "RTCC_INTERNAL" y también que el prescaler sera dividiendo por 4 "RTCC_DIV_4".
2) set_timer0(6); este es el valor de seteo del timer, valor decimal 6.
3) enable_interrupts(INT_TIMER0); y enable_interrupts(GLOBAL); tenemos la habilitación de las interrupciones.

Ahora veremos el ejemplo codificado en C.
Podremos analizar que dentro de la funcion timer0() tenemos output_toggle(PIN_A0); y como había mencionado antes al final tenemos el seteo del timer set_timer0(6);
Con este valor la función se interrumpirá cada 1ms, al hacer toggle o swap al bit 0 del puerto y A, cambiara de 0 a 1 y de 1 a 0 cada 1ms, esto podremos verlo en el osciloscopio donde veremos una señal cuadrada de 2ms (1ms tON + 1ms tOFF) con un ciclo de trabajo del 50% y una frecuencia de 500Hz.  
NOTA: estos son valores teóricos, debemos tener en cuenta el 3% de error del calculo en el lenguaje C.

#include <16f628a .h="">
#FUSES NOWDT 
#FUSES XT 
#FUSES MCLR 
#use delay(clock=4000000) 
#int_timer0 
void timer0(){ 
   output_toggle(PIN_A0); 
   set_timer0(6); 

void main(){ 
   setup_timer_0(RTCC_INTERNAL|RTCC_DIV_4); 
   set_timer0(6); 
   enable_interrupts(INT_TIMER0); 
   enable_interrupts(GLOBAL); 
   while(TRUE){
   } 
}

12 comentarios:

  1. Hola que tal!
    Muy buen post tengo un par de dudas haber si me pueden ayudar a resolverlas
    partiendo de este mismo codigo xtal=4MHz Frec=500Hz overflow=1ms y demas:

    1.-¿como le puedo hacer para variar el duty cycle a mi antojo es decir variar
    el ancho de pulso a 25%,50%;80% etc.ojo pero manteniendo la misma FRECUENCIA
    que en este caso es 500Hz ya que he visto codigos con valores pero no se de
    donde los sacan por ejemplo hacen esto:

    contador++ ;incremento contador en uno
    if contador<=valor1 then PORTA.0=1 ;pongo en alto PIN_A0
    if contador>=valor2 then PORTA.0=0 ;pongo en bajo PIN_A0
    contador=0 ;reseteo contador

    Aqui mi duda es ¿como sacan esos valores(valor1,valor2),osea que planteamiento
    hacen o que me representan para relacionarlos con el DUTY CYCLE y el PERIODO
    y asi poder variar el ancho de pulso pero manteniendo fija la frecuencia.

    2.-Aqui el valor de seteo set_timer0(6) ; tambien se tiene que modificar
    o permaneceria igual? esto lo pregunto por que luego he visto que lo cargan
    asi set_timer0(255)<---aqui no se por que lo cargan al maximo 255 osea que
    me representa en la señal o cual es su funcion?
    sobre el overflow(desbordamiento) de 1ms ¿permaneceria igual o habria que tomar
    todo el periodo de 2ms?
    Gracias por responder...

    ResponderBorrar
    Respuestas
    1. Hola como estas?,
      Para variar el duty cycle, tenes que utilizar la salida PWM del microcontrolador (si es que la tiene) donde pones el valor en porcentaje a la funcion set_pwm1_duty(%%). Si no queres usar el PWM por hardware tenes que realizar una funcion PWM por software la cual podes verla en otro post que tengo sobre ese tema: http://electgpl.blogspot.com.ar/2014/10/multiple-pwm-sin-ccp.html
      Si usas el PWM por hardware podes setear una frecuncia unica de operacion (que no varia) y modificar el ciclo de trabajo solamente.
      Lo que hacen con esos if que me comentas es algo similar a lo que yo muestro en el post que pase recien.
      El valor que pones en set_timer0(x) es el valor hasta donde el timer va a contar, una vez que llege a 6 (de forma regresiva) el timer activa un flag diciendo que se ha terminado la cuenta. Para decirlo mas sencillo, el numero que pones ahi define cuanto tiempo tarda el timer. El valor es solo para la demora del timer.
      Depende como este configurado el timer, sera el overflow, Tenes que tener en cuenta otra cosa mas, el timer solo cuenta hasta un valor determinado a una velocidad que es funcion del prescaler y el clock, y genera interrupcion por desbordamiento cuando el contador llega al valor seteado en el programa, despues para que se genere una señal cuadrada a la salida que tenga un periodo de 1 o 2ms o lo que quieras, tenes que hacer que cada vez que se genera la interrupcion del timer realice alguna accion en el programa por ejemplo output_toggle() que lo que hace es invertir el bit de salida cada vez que pasa por esa funcion.

      Borrar
  2. Este comentario ha sido eliminado por el autor.

    ResponderBorrar
  3. buen dia! quisiera leer una serie de pulsos en una entrada digital de mi pic, pero leerla por solo 200 segundos y guardar la cantidad de pulsos, para luego volver a leer la misma entrada y hacer lo mismo y crear un cliclo infinito.... me pueden ayudar

    ResponderBorrar
    Respuestas
    1. Hola, como estas, si se puede.
      Mira este post: http://electgpl.blogspot.com.ar/2014/03/frecuencimetro-hasta-30khz.html
      Es un frecuencimetero que te da la cantidad de pulsos que entran en 1 segundo
      En lugar de poner delay_ms(250); pone delay_ms(50); y te va a decir cuantos pulsos entraron en 200ms.
      Saludos.

      Borrar
  4. hola Soy Daniel, vi ejemplos que usan TIMER1 y no utilizan set_timer1, entonces como es que funciona si no utilizan eso? o sin eso CCS tiene como default un valor? gracias

    ResponderBorrar
    Respuestas
    1. Hola, depende que haga el programa pero el CCS "entiende" valores por default.. no te va a dar error de compilación pero te va a asignar un valor por defecto.
      Lo ideal es que le cargues el valor.
      Saludos.

      Borrar
    2. por Ejemplo RS485 tiene dos opciones la libreria una interrupcion si usas la UART del PIC y otra por interrupcion Externa, Pero vi que leian el Mensaje mediante un timer1 activaban una bandera y luego con el if en el main si la bandera es verdadera haga tal cosa me explico.? Pero no vi en ninguna parte del programa la carga del timer1.

      #INT_TIMER1
      void timer1_isr()
      {
      if(rs485_get_message(msg, FALSE))
      {
      flag_rs485 = 1;
      }
      }

      void main()
      {
      output_a(0x01);

      address();
      port_b_pullups(0xF0);
      setup_timer_1(T1_INTERNAL | T1_DIV_BY_1);

      enable_interrupts(GLOBAL);
      enable_interrupts(INT_TIMER1);
      rs485_init();



      while (true)
      {
      if(flag_rs485)
      {
      disable_interrupts(global); //INT_TIMER1
      flag_rs485 = 0;

      .
      .
      .

      un ejemplo asi lo vi por eso preguntaba no entendi porque no lo carga

      Borrar
    3. Disculpa que te consulte, pero dentro de ese if del flag 485 no esta el set timer?, igual lo puede dejar por defecto pero no me imagino un programa que use un timer y no te importe el valor del timer... Ese programa compila? funciona practicamente o es un codigo de ejemplo o algo asi nunca probado con hardware?
      Saludos.

      Borrar
  5. Hola gracias por tu aporte, quisiera saber si existe alguna forma de variar constantemente los valores de set_timer0. Estoy intentando hacer la interrupcion por timer0 pero necesito tener la capacidad de variar el tiempo de desbordamiento a placer y no me esta dejando hacerlo. Espero me puedas guiar. Gracias de antemano

    ResponderBorrar
    Respuestas
    1. Hola, la verdad nunca lo he cambiado a demanda dentro del mismo programa, usualmente si tenes que usar el timer como clock, tal vez te conviene aplicar la metodología de tick counter, haciendo el timer como free running y luego ir consultando el tiempo ahora, mas el tiempo después, y calcular ese delta, como hace arduino con la función millis.
      Saludos

      Borrar
  6. soy nuevo usando el ccs. quisiera hacer un circuito con el pic12f675 y con un oscilador de 20mhz. que tenga un solo boton. al presionar la primera vez genere una frecuencia de 250 khz por 10 minutos y si presiono el mismo boton genero un barrido de señal desde 1khz hasta 250khz por 7 minutos. y si presiono el mismo boton por tercera vez se apaga el circuito. no se como comenzar agradeceria que me ayudaran por favor. soy nuevo usando C. gracias de antemano.

    ResponderBorrar