Timers

El timer es un reloj de precisión que el PIC contiene, y que crea una interrupción cuando su registro se “desborda” (llega al máximo).

En ésta ocasión usaremos el PIC16f682A, porque contiene un TIMER0 (8 bits) y un TIMER1 (16 bits), que tendrán distintas duraciones.

En primer lugar cabe decir que el TIMER funciona de la siguiente forma: configuramos el registro, cargamos un número, el timer va sumando 1 a ése número según el oscilador y el preescaler que le asignemos, y cuando llega a su límite (256 o 65536), crea una interrupción y el PIC atiende a ésa ISR.

Concretamente, nos vamos a centrar por ahora en usar el TIMER como temporizador, porque también se puede usar como contador de eventos externos (suma 1 cada vez que cambia el estado de un pin). Aunque parezca un poco tonto, vamos a volver a hacer parpadear un LED cada segundo, pero ésta vez será con toda la precisión que pueda darnos un TIMER.

Empezaremos por el TIMER0. Éste se configura y controla desde el registro OPTION_REG (0x81h):

  • T0CS: selección de la fuente del timer (externa para contador, interna para temporizador). Se configura como externa con un 1 y como interna con 0.
  • T0SE: selecciona el flanco de conteo si la fuente es externa.
  • PSA: asigna el preescaler con un 1 al WDT y con 0 al modulo TIMER
  • PS (2 bits): asigna el valor del preescaler según unas tablas que se pueden ver en la datasheet.

También nos interesa el registro del módulo TIMER0, donde cargamos el número y se va sumando, TMR0 (0x01h). Y ya de paso el registro de interrupciones (INTCON, 0x0Bh) del cual usaremos el T0IE (5) y el T0IF(2).

Así pues, nos pondríamos a programar, pero ¿cuál es el valor que tengo que cargar al registro para que me dé el tiempo exacto que yo quiero? Para ello utilizamos una fórmula:

timer0

Pero no seamos optimistas, el tiempo que va a resultar no es en segundos, ya que el máximo que se puede conseguir con el TIMER0 es de 65.53 ms. Así que utilizaremos iteraciones para conseguir 1 segundo. Como habréis supuesto entonces, el valor de tiempo es de tiempo multiplicado por diez elevado a menos tres (tiempo(s)*10^-3 = tiempo(ms)), y el de frecuencia suele ser de 4Mhz por lo que será de 4*10^6. Lo que hacemos es despejar ValorTMR0.

Supongamos entonces que queremos una interrupción cada 50 ms, tendremos que sumar a un entero hasta que llegue a 20 veces, entonces tendremos 1 segundo y llevamos a cabo la acción.

Pero si calculamos el ValorTMR0 para 50ms no es un número entero y por lo tanto ya perdemos precisión. Dicho valor con un oscilador de 4MHz y un prescaler de 256 es 60’6875, así que podemos cargar el valor 61, que en hexadecimal es 0x3D.

Otra cosa que nos fastidia bastante es que no podemos usar el TIMER a la vez que una función delay, porque en el lapso de tiempo que ocurre un delay, el TIMER deja de contar y ése periodo se ha perdido. Ocurre lo mismo si el micro entra en sleep, por lo que hay que hacer un bucle infinito.

Vamos a empezar a programar.

#Include 
#Fuses NOPROTECT,NOWDT,INTRC_IO,PUT,NOLVP,BROWNOUT
//INTRC_IO indica que vamos a usar el oscilador interno del PIC, no un cristal
#use delay(clock=4000000)
#byte trisb=getenv("SFR:TRISB")
#byte portb=0x06
#byte TMR0=0x01
#Byte OPREG=0x81 //mucho ojo con no modificar otras configuraciones
#Bit GIE=0x0B.7
#Bit TMRI=0x0B.5
#Bit TMRF=0x0B.2
#bit led=0x06.0

int i=0; //usaremos ésto para la iteración

void TMR0INTSET(){
TMR0=0x3D; //Cargamos éste valor en el TIMER0 que empieza a contar desde ahí
GIE=1;
TMRI=1; //Pero ahora tiene una interrupción para cuando se desborde
TMRF=0;
}

#INT_TIMER0 //Ésta es la directiva para la interrupción
void tmr0isr(){
i++; //Suma 1 al contador cada 50ms
if(i>=20){ //si has llegado 20 o más veces hasta aquí
  led=!led //cambia el estado del led
  i=0; //y resetea la variable
}
TMR0INTSET(); //Una vez termine la ISR se vuelve a cargar y habilitar
}

void main(){
trisb=0x00; //Todo salidas
led=0; //que se inicie apagado
OPREG=0b00000111; //Reloj interno, prescaler modulo TIMER y con valor 256.
//También podemos usar la función setup_timer_0(RTCC_INTERNAL | RTCC_DIV_256);
TMR0INTSET(); //Cargamos el registro y habilitamos la interrupción
while(1); //Bucle infinito para que no entre en sleep
}

Otras funciones que se pueden usar son:

enable_interrupts(INT_TIMER0); //Para habilitar la interrupción
enable_interrupts(GLOBAL); //Es como poner en 1 GIE
set_TIMER0(Valor); //Carga el registro TIMER0 con ése valor

Con el TIMER1 funciona de la misma forma, se configura en el T1CON (0x10h), se activa su interrupción en el PIE(0x8C), TIMER1IE (0) y su bandera en el PIR(0x0C), TIMER1IF(0). Tiene un bit en T1CON capaz de activar y desactivar éste temporizador, lo cuál le da algo más de posibilidades. La fórmula cambia:

timer1

Pero dado que el TIMER1 tiene dos registros de 8 bits cada uno para cargar el valor, se hace necesario usar la función set_timer1(Valor). Con éste timer, oscilador de 4MHz y prescaler de 8 se puede llegar a un tiempo de 524.288ms, lo cual está muy bien. La otra buena noticia es que para valores pequeños de tiempo casi siempre se encontrarán valores enteros para cargar en el registro, por lo que no hay pérdida de precisión. La función para configurarlo sería por ejemplo:

setup_timer_1(T1_INTERNAL | T1_DIV_BY_8);

Ésta función tiene a bien ponerlo en marcha a la vez.