viernes, 16 de febrero de 2024

TMR0 Configuración y usos

 Contador y Temporizador con PIC16F


Este sitio fue creado para compartir el mutuo interés en la electrónica y en especial la programación de microcontroladores PIC. Agradecido por visitar mi blog te quiero recordar que estoy atento a critica constructiva relacionada con esta publicación. 

En esta ocasión veremos como hacer uso del modulo TMR0 del PIC16F en aplicaciones que requieran un contador de eventos, y también un temporizador para la gestión de tareas en el programa. 

La programación se realizara utilizando MPLABX y el compilador XC8 ambos disponibles en la pagina de microchip. Aquí dejo los enlaces de las versiones utilizadas para nuestro ejemplo:  <<MPLABX v4.01>>   <<XC8 v1.45>>

Mencionar también que es necesario contar con conocimientos mínimos sobre programación en lenguaje C y el uso de microcontroladores PIC16.


Introducción al Tema
Los contadores y temporizadores son recursos de hardware que están presentes en la mayoría de los microcontroladores. Con este elemento es posible contar pulsos de origen interno o externo con la mínima intervención del programa, con la configuración de dos modos:
  • Temporizador: opera como contador de pulsos internos del oscilador principal
  • Contador: opera como contador pulsos que provienen desde una entrada externa.

Fig1. Contador / Temporizador

El registro contador se incrementa con cada pulso hasta su desbordamiento, o en algunos llega a un valor predefinido, esta condición se notifica con una bandera de interrupción para que el programa lleve gestione la actividad.  

Gracias a un temporizador el desarrollador puede programar la ejecución de tareas controlando el tiempo de forma precisa.
Los contadores facilitan la creación de rutinas para medir el ancho o cantidad de pulsos externos que transcurren de forma periódica o no periódica.

Los microcontroladores PIC16F de gama media, disponen de al menos tres módulos de conteo y están designados como:

  • TMR0: Temporizador y/o Contador de 8-bit
  • TMR1: Temporizador y/o Contador de 16-bit
  • TMR2: Temporizador de 8-bit

Descripción del TMR0 

La figura 2 corresponde al diagrama de bloques del módulo TMR0, toda la informacion descrita en esta sección proviene de la hora de datos correspondiente al PIC16F887. 

Fig2. Modulo TMR0 del PIC16F

Como ya vimos este modulo es un Temporizador y/o Contador de 8-bit debido al ancho que tiene el registro de conteo TMR0, el esquema identifica las diferentes partes con el siguiente detalle: 

  1. Esta entrada se utiliza cuando el modulo opera en modo temporizador (T0CS= 0), donde la frecuencia de trabajo genera un tren de impulsos con periodo igual al ciclo de instrucción.
  2. Es la entrada asociada al pin T0CKI, por donde ingresan los pulsos cuando el modulo opera como contador (T0CS=1), como estos pulsos no siempre son periódicos, la frecuencia de trabajo no es un factor que influya en la secuencia del contador, pero si es posible determinar el flanco de validación de cada impulso a través del bit T0SE, por defecto esta configurado como flanco descendente (T0SE=1), algo a tomar en cuenta es que el pin debe configurarse como una entrada digital, y deben desactivarse cualquier funcionalidad asociada a un canal o comparador analógico,
  3. La sección corresponde al bloque de pre-escala programable, se trata de un divisor de frecuencia con ocho escalas seleccionables a través de los bits PS2, PS1 y PS0, las escalas permitidas se muestran en la figura 3, registro. El uso de esta etapa es opcional, siendo necesaria su activación con el bit PSA=0.
  4. El punto corresponde al registro contador que incrementa su valor con cada pulso que proviene directamente de la entrada Fosc/4 / T0CKI o el bloque de pre-escala, en cualquier modo operativo este registro se autoincremento desde un valor 0 hasta el 255, para luego reiniciar
  5. Finalmente cada vez que el registro contador llega a su valor limite de 255, con el siguiente pulso de entrada se reinicia a 000, este hecho se conoce como desbordamiento, y ante su ocurrencia la bandera T0IF se activa.  La bandera deberá limpiarse una vez atendido el evento. 

Fig3. Registros SFR del modulo TMR0

Debe tomar en cuenta que los registros SFR que se muestran en la figura 3 tiene valores por defecto, en muchos casos podría no ser necesario limpiar los bits.

Esquema del circuito

De aquí en vamos revisaremos algunos ejemplos de programación utilizando el modulo TMR0, para lo cual en esta ocasión utilice el circuito que se muestra en la figura 4. Este circuito servirá para ensayar el código que trabajaremos en las siguientes secciones,  tome como referencia el microcontrolador PIC16F687 utilizando el software de simulación Proteus versión 8.

Fig4. Esquema del circuito PIC16F687

Configuración de un Contador

Para configurar el modulo como un contador de pulsos, debe llevarse a cabo la siguiente configuracion considerando el circuito de la figura 4.

  • Como la entrada T0CKI esta asociado al pin RA2, este debe configurarse como una entrada digital utilizando a través del registro TRISA.
  • El modo contador controlado por el bit T0CS=1, por defecto ya esta activo, por lo que no necesita modificar el bit.
  • Si va utilizar la etapa de pre-escala, entonces debe establecer el bit PSA=0, caso contrario aplicara el valor por defecto.
  • Deberá seleccionar una escala de división con los bits PS2:0 si hace uso de la etapa de pre-escala, por defecto la selección es 256:1.
  • Asignar el valor requerido para el registro TMR0, utilizando la ecuación que se muestra en la figura 5.
  • Limpiar la bandera T0IF, y esperar la ocurrencia del evento, verificando si la bandera se activa en un futuro. 

Fig5. Cálculos del Contador

Veamos un ejemplo practico para el contador. Considerando que el PIC16F628 trabaja con su oscilador interno de 4MHz, configurar el modulo TMR0 para indicar la llegada de 15 pulsos no periódicos en el pin RA4, e indicar el evento activando un LED conectado al pin RC0.

#pragma config FOSC = INTRCIO, WDTE = OFF
#include <xc.h>
#include <stdio.h>
void setup()
//Procedimiento de configuracion
{
  OSCCONbits.IRCF = 0b110;
//Selecciona 4MHz por Defecto
  ANSEL = 0x00;
//Desactiva los canales AN0-AN7
  TRISAbits.TRISA2 = 1;
//Pin RA2/T0CKI como entrada
  TRISCbits.TRISC0 = 0;
//Pin RC0 como salida
  OPTION_REGbits.T0CS = 1;
//Modo Contador
  OPTION_REGbits.PSA = 1;
//Sin prescala, 1:1
  TMR0 = 241;
//(256-pulsos/pre) = 256 - 15/1
  INTCONbits.T0IF = 0;
//Limpia bandera
}
void main()
{
  setup();
//Procedimiento de configuracion
  PORTCbits.RC0 = 0;
//Apaga el LED
  while(1)
  {
    if(INTCONbits.T0IF == 1)
//Verifica bandera
    {
       PORTCbits.RC0 = 1;
//Activa el LED
       INTCONbits.T0IF = 0;
//Limpia bandera
    }
  } 
}

Cambiando un poco las cosas, modificaremos el programa para contar la llegada de 1724 pulsos (pulsos de salida por cada litro que emite un flujo metro FS-3400),  e indicar el evento (1 litro) activando el LED conectado al pin RC0.

Para este requerimiento el único cambio necesario sera en el procedimiento de configuracion setup,  con las siguientes consideraciones:

Debido a que la cantidad de pulsos superan el valor del registro contador TMR0, el cual puede contar hasta 256, sera necesario utilizar la etapa de pre-escala, pero esta etapa trabaja con escalas ya definidas por lo que no conseguiremos un numero exacto con ninguna de ellas, salvo la 2:1 o 4:1 pero estos el resultado superaría la capacidad del contador TMR0. Ahora si utilizamos 8:1, tendríamos 1724/8=215.5 y si fuera 64:1, seria 1724/64=26.93, entonces con cualquiera de estas relaciones habrá un error debido a que el registro contador se carga solo con la parte entera. 

Tomemos el primer caso para nuestro analisis,  si utilizamos 8:1(1724/8=215.5), el registro contador redondeado podrá ser 215 o 216, si fuera 215, la ocurrencia seria 215*8 = 1720, cuatro pulsos menos de lo esperado; Y si el contador se ajusta con 216, la ocurrencia nos da 1728, cuatro pulsos mas de los esperado. En resumen con cualquiera de los valores el error provocado seria un 0.23%, si el error es aceptable el procedimiento de configuracion seria el siguiente:

void setup() //Procedimiento de configuracion
{
  OSCCONbits.IRCF = 0b110;
//Selecciona 4MHz por Defecto
  ANSEL = 0x00;
//Desactiva los canales AN0-AN7
  TRISAbits.TRISA2 = 1;
//Pin RA2/T0CKI como entrada
  TRISCbits.TRISC0 = 0;
//Pin RC0 como salida
  OPTION_REGbits.T0CS = 1;
//Modo Contador
  OPTION_REGbits.PSA = 0; //con pre-escala
  OPTION_REGbits.PS = 0b010;
//Escala 8:1
  TMR0 = 41;
//256-(1724/8) = 256 - 215 = 41
  INTCONbits.T0IF = 0;
//Limpia bandera
}

En caso de no ser aceptable el error, se tendría que pensar en llevar el conteo de pulsos individuales o en todo caso ajustar la configuracion del registro contador durante la ultima actualización del evento. No se abordara este punto.

Configuración del Temporizador

Para configurar el modulo como temporizador, debemos llevar a cabo la siguiente configuracion:

  • Seleccionar el modo temporizador estableciendo el bit T0CS=0
  • Si va utilizar la etapa de pre-escala, entonces debe establecer el bit PSA=0
  • Deberá seleccionar una escala de división con los bits PS2:0 si hace uso de la etapa de pre-escala.
  • Asignar el valor requerido para el registro TMR0, utilizando la ecuación que se muestra en la figura 7.
  • Limpiar la bandera T0IF, es la ocurrencia del evento, verificando si la bandera se activo.
  • Después de cada evento, la bandera debe limpiarse y el valor del registro TMR0 debe ser nuevamente asignado para un siguiente ciclo.

Fig7. Calculo temporizador

Veamos un ejemplo practico para el uso del un temporizador considerando que el PIC16F trabaja a una frecuencia de 4MHz, ver figura 4. Configurar el modulo TMR0 para un tiempo de 1ms y reflejar con este tiempo el cambio de nivel en el pin RC0.

Para este caso seleccionamos un valor de pre-escala, en este caso 8:1 debido a que sin pre-escala lo máximo para ajustar seria (4/Fosc)* (256-0) = 0.000256 o 256us, y el requerimiento es 0.001 o 1ms. El programa quedaría así:

#pragma config FOSC = INTRCIO, WDTE = OFF
#include <xc.h>
#include <stdio.h>
char ledst;
//variable que mantiene estado del led
void setup()
{
  OSCCONbits.IRCF = 0b110;
//Ajusta INTOSC=4MHz
  TRISCbits.TRISC0 = 0;
//Pin RC0 como salida
  OPTION_REGbits.T0CS = 0;
//Modo termporizador
  OPTION_REGbits.PSA = 0;
//Con pre-escala
  OPTION_REGbits.PS = 0b010;
//Pre-escala 8:1
  TMR0 = 131;
//256-(time/(pre*(4/Fosc))) time=0.001s
  INTCONbits.T0IF = 0;
//Limpia bandera
}
void main()
{
  setup();
  while(1)
  {
    if(INTCONbits.T0IF == 1)
//Verifica bandera de 1ms
    {
        TMR0 = 131;
//Ajusta el registro
        PORTCbits.RC0 = ledst;
//Pin se carga con el estado
        ledst = !ledst;
//Invierte el valor logico
        INTCONbits.T0IF = 0;
//Limpia bandera de 1ms
    }
  }
}

Realizada la simulación en Proteus, la figura 8 muestra una captura del osciloscopio conectado al pin RC0, donde se observa la señal con los tiempos calculados, cabe mencionar que el periodo total es 2ms, con una frecuencia de 500 Hz.

Fig8. Captura del pin RC0, escala 1ms
 

Modificaremos el programa para que la señal a través de pin RC0, tenga un periodo de 1 segundo, es decir 1Hz de frecuencia.

Vamos a analisis el nuevo requerimiento de tiempo para 1 segundo. si consideramos la maxima relación 256:1, aplicando la ecuación de figura 7 el tiempo máximo posible, seria 0.065536 segundos, este valor esta lejos de lo requerido por lo tanto sera necesario hacer uso de una variable contador dentro del programa, manteniendo la configuracion del temporizador para 1ms. 

La variable deberá contar la cantidad de 1ms necesarias para general una señal con frecuencia de 1Hz, este valor seria 500, que corresponde la mitad del periodo total, recuerde que la primera mitad de ciclo la luz esta apagad y le otra mitad estará encendida, consiguiendo un destello cada segundo. 

La al código anterior sera únicamente en el programa principal, quedando de la siguiente manera:

void main()
{
  unsigned int cnt = 0;
//Contador de 1ms
  setup();
  while(1)
  {
    if(INTCONbits.T0IF == 1)
//Verifica bandera de 1ms
    {
      TMR0 = 131;
      cnt ++;
      if(cnt > 499)
//hasta llegar a 500 x 1ms
      {

        cnt = 0;
//reinicia la variable contador
        PORTCbits.RC0 = ledst;
        ledst = !ledst;
//Invierte el calor logico
      }
      INTCONbits.T0IF = 0;
//Limpia bandera de 1ms
    }
  }
}

TMR0 con interrupciones

Sin duda que unos de los recursos mas valiosos que posee el microcontrolador, son las interrupciones, gracias a ello es posible manejar los eventos asociados el modulo TMR0 con la mínima intervención del programa principal. 

Como ejemplo practico vamos hacer uso del  temporizador utilizando la interrupción, en base al ejemplo anterior que permitía destellar un LED con frecuencia de un segundo. Note que se adicionara la rutina de servicio a la interrupción ISR para atender el evento del temporizador.

#pragma config FOSC = INTRCIO, WDTE = OFF
#include <xc.h>
#include <stdio.h>
char ledst = 0;
//variable que mantiene estado del led
unsigned int cnt = 0;
//variable contador
volatile char tick1ms;
//bandera indicador 1ms
void __interrupt() isr()
//Rutina de interrupción
{
  if(INTCONbits.T0IF)
//Activa cada 1ms
  {
    TMR0 = 131;
//Reinicia contador
    INTCONbits.T0IF = 0; 
//Limpia bandera
    tick1ms = 1;
//Activa bandera 1ms
  }
}
void setup()
{
  OSCCONbits.IRCF = 0b110;
//Ajusta INTOSC=4MHz
  TRISCbits.TRISC0 = 0;
//Pin RC0 como salida
  OPTION_REGbits.T0CS = 0;
//Modo Termporizador
  OPTION_REGbits.PSA = 0;
//Con pre-escala
  OPTION_REGbits.PS = 0b010;
//Pre-escala 8:1
  TMR0 = 131;
//256-(time/(pre*(4/Fosc))) time=0.001 seg
  INTCONbits.T0IF = 0;
//Limpia bandera
  INTCONbits.T0IE = 1;
//Habilita la interrupción del TMR0
  INTCONbits.GIE = 1;
//Habilitador Global ISR
}
void main()
{
    setup();
    while(1)
    {
        if(tick1ms)
//Verifica bandera de 1ms
        {
          tick1ms = 0;
          cnt ++;
//incrementa variable contador
          if(cnt > 499)
//hasta llegar a 500 x 1ms
          {
            cnt = 0;
            PORTCbits.RC0 = ledst;
            ledst = !ledst;
//Invierte el calor logico
          }
        }
    }
}

Como ultimo caso practico modificaremos el programa para el LED se  mantenga activo solo un 20% del periodo, es decir 200ms. Para este caso solo bastara modificar el programa principal y realizar una comparativa del valor en el contador para determinar en que instante activar y apagar el LED.

void main()
{
    setup();
    while(1)
    {
        if(tick1ms)
//Verifica bandera de 1ms
        {
          tick1ms = 0;
          cnt ++;
          if(cnt > 999) cnt = 0;
//hasta 1000 x 1ms
          if(cnt == 200) PORTCbits.RC0 = 1;
//Activa led
          if(cnt == 400) PORTCbits.RC0 = 0;
//Apaga led
        }
    }
}

Conclusiones y recomendaciones

En general hemos visto como configurar el modulo TMR0 ya sea como contador y temporizador, los códigos de ejemplo presentados fueron ensayados en el simulador Proteus, con los resultados esperados.  Si quiere ver como configurar el modulo TMR1 te invito a revisar esta publicación. <<TMR1>>
Como recomendación, solo indicar que en todos los ejemplos no se tomo en cuenta la latencia o tiempo para atender el evento, para aclarar este punto recordemos que el registro TMR0 del temporizador incrementa su valor continuamente, y en el peor de los casos este tiempo sera un ciclo de instrucción(sin pre-escala), entonces si la latencia supera el tiempo necesario para el incremento del contador, este ya no sera 0 al momento de reconfigurar, por lo tanto sera necesario agregar el valor que contiene para ajustar el tiempo del siguiente ciclo, en todo caso podría aplicar la siguiente linea para hacer la corrección.

TMR0 = TMR0 + 131; //Reinicia contador, considerando la cuenta actual
 
Sin mas que mencionar agradezco tu visita al blog y espero que el ejemplo visto pueda ser útil en tu formación y el proyecto que desarrollas.
Atte. Pablo Zárate Arancibia
email: pablinza@me.com / pablinzte@gmail.com, @pablinzar
Santa Cruz de la Sierra - Bolivia