Está en la página 1de 16

Los timers o temporizadores son características estándar de casi todos

los microcontroladores, así que es muy importante aprender su uso.


Los microcontrolador AVR tiene temporizadores muy poderosos y
multifuncionales, pueden medir tiempo, generar frecuencias, contar
eventos externos, hacer comparaciones, sincronizar tareas, generar una
onda PWM, etc.

El tema de los timers es un tanto complicado y extenso, en esta serie de


post se trata de dar una breve explicación del funcionamiento y
programación del timer0 en el ATmega16.

El timer0 del ATmega16 es un contador/temporizador de 8 bits (puede


contar hasta 255) síncrono, es decir, que depende del reloj del CPU (sólo
su reloj, no el CPU) para funcionar. Es un módulo independiente del
núcleo del AVR lo que reduce la carga del micro.

Cada timer tiene asociado un reloj que fija el paso que debe marcar y
una o más unidades compradoras. El reloj del timer0 es derivado del reloj
del CPU y puede tener dos fuentes distintas: si se usa la fuente de reloj
interna del CPU, el reloj del CPU pasa primero por un divisor de
frecuencia (prescaler) y la salida de este divisor va al timer, si se usa una
fuente de reloj externa por el pin T0, primero será sincronizada con el
reloj interno antes de pasar al prescaler.
Los valores de división (prescaler) ya están establecidos para cada timer,
en el caso del timer0 la frecuencia del CPU puede ser dividida por un
factor de 1, 8, 64, 256 o 1024, generando así la frecuencia de conteo.
Por cada unidad comparadora existe un pin asociado a ella, normalmente
marcado como OCx (Output Compare x) que es donde se puede generar
una frecuencia de salida por la unidad comparadora del timer.
El timer0 tiene 4 modos de funcionamiento que se pueden configurar
programando sus registros asociados:

 Modo Normal:  El timer cuenta desde 0 a 255 y se desborda reiniciando la


cuenta. Puede generar interrupción al desbordarse o cuando la comparación
del conteo concuerde con un valor determinado.

 Modo CTC:  En este modo el timer0 es reiniciado a 0 cuando una


comparación entre el timer y un valor determinado coincide. Opcionalmente
puede configurarse para que al haber una coincidencia, genera una
interrupción o cambie el estado de un pin.

 Modo Fast PWM: Este modo permite generar una onda PWM de alta
frecuencia. El timer cuenta desde 0 a 255 y reinicia la cuenta. Con cada
cuenta el valor del timer0 se compara con un valor determinado que cuando
coinciden cambia el estado de uno de los pines de salida PWM, y cuando se
reinicia el timer este pin vuelve a cambiar su estado.

 Modo Phase Correct PWM: Este modo ofrece una onda PWM de alta
resolución a diferencia del modo Fast PWM. El timer cuenta hacia adelante y
hacia atrás antes de hacer el cambio de estado del pin PWM, es decir cuenta
de 0 a 255 al llegar a 255 cuenta de 255 a 0, obteniendo una salida PWM más
limpia pero de menor frecuencia.

Registros del Timer/Counter0


Para controlar los modos de funcionamiento y la frecuencia de trabajo, el
timer0 tiene asociados varios registros los cuales se describen mas
adelante. A parte de los registros, el timer0 tiene la capacidad recibir una
frecuencia de reloj externa mediante el pin T0 (PB0) y de cambiar el
estado de la salida en el pin OC0, que es la salida del comparador del
timer:

TCCR0:  (Timer/Counter Control Register) Este registro configura la


frecuencia a la que trabajará el timer, el modo de trabajo y si el timer
controlará la salida del pin asociado a el, en este caso el pin OC0.

Registro
TCCR0 (Timer/Counter Control Register)

 FOC0:  Escribiendo un 1 en este bit, fuerza a que se realice una


coincidencia de comparación y actúa sobre el pin OC0 si se usa.

 WGM01:0: Configura el modo de funcionamiento del timer0.


 COM01:0: Estos bits configuran el uso del pin OC0 con el modulo de
comparación dependiendo del modo que se trabaje.

Salida
del comparador en modo NO-PWM (Normal ó CTC)

Salida
del comparador en modo FAST PWM

Salida
del comparador en modo PHASE CORRECT PWM
 CS02:0: Con estos bits se selecciona la fuente del reloj para el timer,
puede ser interna con un prescaler o externa con una señal de reloj en el pin
T0.

Config
uración del prescaler para frecuencia del Timer/Counter0

TCNT0:  (Timer/Counter Register) Este registro es el que guarda la


cuenta del Timer/Counter0 (0 a 255).

Registro
TCNT0 (Timer/Counter Register)

OCR0:  (Output Compare Register) Aquí se guarda el valor a comparar


con el registro TCNT0. Cuando el valor del registro TCNT0 coincide con
el valor guardado en este registro, se realiza el evento programado para
la coincidencia en comparación, ya sea generar una interrupción o
cambiar el estado del pin OC0.

Registr
o OCR0

TIMSK:  (Timer Interrup Mask Register) En este registro se encuentran


los bits de habilitación de interrupciones para cada timer, a nosotros nos
interesa el bit  TOIE0(0) y OCIE0(1)  que son los correspondientes al
timer0.

Regitro
TIMSK (Timer/Counter Interrup Mask Register)

 TOIE0: Escribir a 1 en este bit habilita la interrupción por desbordamiento


del TimerCounter0 (Timer/Counter Overflow Interrupt Enable 0) si también
se habilitan las interrupciones globales con el bit I en SREG.

 OCIE0:Escribir a 1 en este bit habilita la interrupción por coincidencia en


la comparación del Timer/Counter0con el registro OCR0 (Output Compare
Interrupt Enable 0), si también se habilitan las interrupciones globales con el
bit I en SREG.
TIFR:  (Timer Interrup Flag Register) En este registro se encuentran las
banderas de interrupciones para cada timer, a nosotros nos interesa el bit
TOV0(0) y OCF0(1)  que son los correspondientes al timer0.

Registro
TIFR

 TOV0: Cuando este bit se pone a 1 se genera la interrupción por


desbordamiento del Timer/Counter0. Este bit se limpia cuando cuando entra a
la función que atiende la interrupción o escribiendo un 1 en este bit.

 OCF0: Cuando este bit se pone a 1 se genera la interrupción por


coincidencia en la comparación del Timer/Counter0 con el registro OCR0.
Este bit se limpia cuando cuando entra a la función que atiende la interrupción
o escribiendo un 1 en este bit.
Como se mencionó anteriormente, los timers son complicados, así que
en este primer post dedicado al Timer/Counter0 se verá su
funcionamiento en modo Normal con ejemplos que usen poleo e
interrupciones y fuentes de reloj internas y externas. Se hace esto de
explicar un modo en cada post con el fin de profundizar y utilizar todas
las características del timer.

Timer/Counter0 en Modo Normal

En este modo el timer0 cuenta desde BOTTOM hasta TOP (BOTTOM =


0, TOP = 255) y al pasar la cuenta empieza de nuevo desde BOTTOM. El
timer puede generar interrupciones de desbordamiento (al pasar de TOP
a BOTTOM) y de coincidencia en comparación con el registro OCR0.

En estos primero ejemplos se usara el reloj del CPU como fuente de reloj
para el timer, descartando el pin T0.

Para saber a que frecuencia y con que periodo trabajará el timer, es


necesario saber algunas fórmulas para calcular tiempos con exactitud.

Para calcular la frecuencia del timer, se divide la frecuencia del CPU


entre el prescaler deseado:

Fórmula para calcular la Frecuencia del


Timer
Por lo que el periodo de la cuenta del timer sería el inverso a la
frecuencia del timer:

Fórmula para calcular el Periodo de cuenta del


Timer

Esto significa que el timer se incrementara cada Ttimer  segundos, si


queremos saber cada cuanto tiempo se desbordará el timer, se multiplica
el periodo del timer por la resolución del timer, que en este caso es de 8
bits (256):

Fórmula para calcular el


tiempo de desbordamiento del Timer

Con la fórmula anterior podemos saber cada cuanto tiempo se desborda


el timer, pero si queremos saber el que valor que debe de tener el timer
para un determinado tiempo dentro del rango Toverflow se aplica una
sencilla regla de 3:
Fórm
ula para calcular el valor del Timer para determinado tiempo.

Por ejemplo: Supongamos que tenemos nuestro micro con una


frecuencia de 8 Mhz y un prescaler para el timer de 1024, la frecuencia
del timer sería:

                                Ft = 8000000 Hz / 1024  =  7812.5 Hz


                                Tt = 1 /7812.5 Hz = 0.000128 segundos =
128 microsegundos
                                Tovf = 128 microsegundos * 256 = 32768
microsegundos = 32.8 milisegundos
Con estos valores el timer se desbordaría cada 32.8 milisegundos, si
queremos medir en intervalos de 10 milisegundos, aplicamos la regla de
3 para saber que valor tendrá el timer cuando hayan transcurrido 10 ms:

Cuenta del Timer =  10 ms * 256 / 32.8 + 1 = 79.4


Esto quiere decir que cuando el timer haya contado 79 pasos habrán
transcurrido aproximadamente 10 ms.

Ejemplo1: configuraremos el timç
er0 con un prescaler de 1024 con una frecuencia de CPU = 8Mhz para
que el timer se desborde cada 32ms, y una vez que se desborde
cambaremos el estado de un led en el puerto PB0:
1
2 //
3  
4 #include <avr/io.h>
5  
6 int main(void) {
7  
8     // Timer0 modo normal, sin usar el pin OC0, prescaler de 1024
9     TCCR0 = (1 << CS00) | (1 << CS02);
10  
    // Puerto PB0 como salida.
11     DDRB = _BV(PB0);
12     PORTB = 0;
13  
14     while(1) { 
15  
16         // si ya hubo desbordamiento del timer0:
        if( (TIFR << (1 << TOV0)) ){
17
18  
            PORTB ^= (1 << PB0); // conmuta el led
19             TIFR |= (1 << TOV0); // limpia la bandera
20         }
21     }
22  
23     return 0;
}
24
25  
//
26
27

Primeramente el programa configura el timer y el puerto PB0 como salida


y en el ciclo infinito checa constantemente la bandera de interrupción por
desbordamiento del timer, si está en 1 quiere decir que el timer se ha
desbordado (pasó de 255 a 0) y se conmuta el estado del led para
despues limpiar la bandera de interrupción.
Ejemplo2:  Este ejemplo tiene el mismo objetivo que el anterior, cambiar
el estado del led al desbordarse el timer, solo que aquí en vez de estar
checando la bandera de desbordamiento, se usa la interrupción por
desbordamiento del timer0 que se ejecuta cada 32 ms, lo que libera al
CPU para ejecutar otras tareas.
1
2
3 //
4  
#include
5 #include
6  
7 int main(void) {
8  
9     // Timer0 modo normal, sin usar el pin OC0, prescaler de 1024
10     TCCR0 = (1 << CS00) | (1 << CS02);
11  
12     // habilta interrupción por desbordamiento de timer0
    TIMSK = (1 << TOIE0);
13
 
14     // habilita interrupciones globales
15     sei();
16  
17     // Puerto PB0 como salida.
18     DDRB = (1 << PB0);;
    PORTB = 0;
19
20  
    while(1) {} // CPU libre
21
 
22     return 0;
23 }
24  
25 // función para atender la interrupción
26 ISR(TIMER0_OVF_vect){
    // se conmuta el estado del led
27     PORTB ^= (1 << PB0);
28 }
29  
30 //
31
32

Ejemplo3:  En este ejemplo se usa el timer para contar intervalos de 10


milisegundos, que se almacenarán en una variable para generar un
retardo de 1 segundo para parpadear un led. Se necesita saber cual es el
valor del timer para 10 ms, se usa la regla de 3:
Cuenta del Timer =  10 ms * 256 / 32.8 + 1 = 79.4, cuando el timer
cuente 79 pasos habran transcurrido 10 ms.
1
2
3 //
4  
5 #include
#define RETARDO 1000  // 1000 milisegundos - 1 segundo
6
 
7 int main(void) {
8  
9     int milis=0;
10  
11     // Timer en modo normal, sin salida por OC0 con prescaler de 1024
12     TCCR0 = (1 << CS00) | (1 << CS02);
13  
    // Puerto PB0 como salida.
14     DDRB = (1 <= 79) // si ya pasaron 10 milisegundos
15         {
16             TCNT0=0;
17             if((milis+=10) >= RETARDO) // si ya pasaron 1000 milisegundos
18             {
                // cambia el estado del led
19                 PORTB ^= (1 << PB0);
20                 // reinicia el contador de milisegundos
21                 milis=0;
22             }
        }
23
24  
    }
25
 
26     return 0;
27 }
28  
29 //
30
31

Como se ve, se tiene que estar haciendo la comparación del valor del
timer constantemente para saber si ya pasaron 10 ms, esto le toma
tiempo al procesador. Para ahorrar ese tiempo muerto se pueden
emplear dos métodos: el primero es haciendo que el timer se desborde
cada 10 ms, o sea, cada que pasen 79 incrementos y así hacer todo
desde la función de interrupción por desbordamiento, y el segundo es
usar la interrupción por coincidencia en comparación con el timer0 y el
registro OCR0, así el registro de comparación se carga con el valor 79
(para 10 ms) y cuando el valor del timer0 coincida con el valor del
registro OCR0, se ejecutará el código en dicha interrupción. Es necesario
reiniciar el valor del timer a 0 en la función de interrupción para que
vuelva a contar, de lo contrario el timer seguirá su cuenta hasta el
desbordamiento y no se ejecutara la interrupción en ese lapso.

Aquí pongo el código de los dos programas:

Programa 1: Interrupción por desbordamiento.


1
2
//
3
 
4 #define RETARDO 1000  // 1000 milisegundos - 1 segundo
5 #define F_CPU 8000000
6 #define LED PB0
7  
8 #include
#include
9
10  
volatile int milis=0;
11
 
12 int main(void) {
13  
14     // Timer en modo normal, sin salida por OC0 con preescaler de 1024
15     TCCR0 = (1 << CS00) | (1 << CS02);
16  
17     // Activa interrupción por debordamiento del timer0
    TIMSK = (1 << TOV0);
18
 
19     // puerto PB0 como salida
20     DDRB = (1 <= RETARDO){
21         milis=0;
22         PORTB ^= (1 << LED);
    }
23
}
24
 
25 //
26
27

Programa 2: Interrupción por Coincidencia en comparación.


1
2
//
3
 
4 #define RETARDO 1000  // 1000 milisegundos - 1 segundo
5 #define F_CPU 8000000
6 #define LED PB0
7  
8 #include
#include
9
10  
volatile int milis=0;
11
 
12 int main(void) {
13  
14     // Timer en modo normal, sin salida por OC0 con preescaler de 1024
15     TCCR0 = (1 << CS00) | (1 << CS02);
16  
17     // Activa interrupción por comparación del timer0
    TIMSK = (1 << OCIE0);
18
 
19     // puerto PB0 como salida
20     DDRB = (1 <= RETARDO){
21     milis=0;
22     PORTB ^= 0xFF;
    }
23
}
24
 
25 //
26
27

Como se ve en este segundo ejemplo, el timer se tiene que reiniciar cada


que la comparación coincide, para evitar esto, se usa el modo CTC del
timer, que automáticamente limpia el timer cuando coincide con el
registro OCR0, este modo se explicará en el siguiente post relacionado a
el timer0 del ATmega16.
ila_int0.asm
 *
 *  Created: 16/11/2013 19:03:23
 *   Author: NEBURESS.KYLL
 */

 .include "m48def.inc"

 .def temp=r16
 .def desplazar0=r17
 .def desplazar1=r18
 .def temp2=r19
 .def temp3=r20

 ;AQUI SE INDICA LA POSICION DONDE COMENZARA EL PROGRAMA


 ;ADEMAS DE LA DIRECCION DE LAS INTERRUPCIONES PARA REALIZAR LAS RUTINAS INDICADAS

 .org 0x00
 rjmp START ; Reset Handler
 .org 0x001
 rjmp EXT_INT0 ; IRQ0 Handler
 .org 0x002
 rjmp EXT_INT1 ; IRQ1 Handler

 ;INICIA EL PROGRAMA
START: ldi temp, high(RAMEND); Main program start
       out SPH,temp ; Set Stack Pointer to top of RAM
       ldi temp, low(RAMEND)
       out SPL,temp

      
       ldi temp,0x00
       out DDRD,temp                            ;SE DECLARA AL PUERTO D COMO ENTRADA
       ldi temp,0xFF                           
       out PORTD,temp                           ;SE HABILITAN LAS RESISTENCIAS DE PULL UP

       ldi temp,0xFF


       out DDRB,temp                            ;SE DECLARA EL PUERTO B COMO SALIDA

       ldi temp,0x3F


       out DDRC,temp                            ;SE DECLARA AL PUERTO C COMO SALIDA

       clr temp                                 ;SE LIMPIA LA VARIABLE temp


       ldi temp,(1<<INT1)|(1<<INT0)             ;SE ASIGNA A temp EL VALOR DE 0x03
       sts 0x3d,temp                            ;SE ASIGNA EL VALOR DE temp AL REGISTRO EIMSK
                                                ;HABILITANDOSE LAS INTERRUPCIONES INT0 E INT1
       clr temp                                 ;SE LIMPIA LA VARIABLE temp
       ldi temp,(1<<ISC01)|(1<<ISC11)           ;SE ASIGNA A temp EL VALOR DE 0x0A PARA QUE LAS INTERRUPCIONES
                                                ;REACCIONEN EN FLANCOS DE BAJADA
       sts EICRA,temp                           ;SE ASIGNA EL VALOR DE temp AL REGISTRO EICRA
      

       sei                                      ;SE HABILITA LA INTERRUPCION GLOBAL

       ldi desplazar0,0x01                      ;SE ASIGNAN VALORES A LAS VARIABLES DE CONTROL


       ldi desplazar1,0x20

main:    rjmp main                               ;BUCLE INFINITO

EXT_INT0:
        push temp                               ;SE GUARDA EL VALOR DE POSICION EN LA PILA
        in   temp, SREG                         ;SE GUARDA EL REGISTRO DE ESTADOS EN LA VARIABLE TEMP
                               
        out PORTB,desplazar0                    ;SE CAMBIA EL ESTADO DEL PUERTO B
        lsl desplazar0                          ;SE DESPLAZA EL VALOR DE LA VARIABLE desplazar0 A LA IZQUIERDA
        sbrc temp2,0                            ;PRUEBA SI SE HA ASIGNADO A LA VARIABLE temp2 UN VALOR EN EL BIT 0 DE SER ASI
LE ASIGNA
        ldi desplazar0,0x01                     ;EL VALOR DE 0x01 A LA VARIABLE despazar0
                        
        sbrc desplazar0,7                       ;PRUEBA SI EL BIT 7 DE LA VARIABLE despalzar0 SE ENCUENTRA SETEADO DE SER ASI
        ldi temp2,0x01                          ;ASIGNA EL VALOR DE 0x01 A LA VARIABLE temp2

        sbrs desplazar0,7                       ;COMPRUEBA SI EL BIT 7 DE LA VARIABLE desplazar0 SE ENCUENTRA SETEADO
        ldi temp2,0x00                          ;DE LO CONTRARIO ASIGNA EL VALOR 0x00 A LA VARIABLE temp2

        out SREG, temp                          ;RETORNA EL VALOR DEL REGISTRO SREG
        pop temp                                ;RETIRA EL REGISTRO DE LA PILA
        reti                                    ;RETORNA A LA POSICION DONDE SE ENCONTRABA ANTES DE REALIZARSE LA
INTERRUPCION

EXT_INT1:
        push temp                                ;SE GUARDA EL VALOR DE POSICION EN LA PILA
        in   temp, SREG                          ;SE GUARDA EL REGISTRO DE ESTADOS EN LA VARIABLE TEMP
        out PORTC,desplazar1                     ;SE CAMBIA EL ESTADO DEL PUERTO C
        lsr desplazar1                           ;SE DESPLAZA EL VALOR DE LA VARIABLE desplazar1 A LA DERECHA
        sbrc temp3,0                             ;PRUEBA SI SE HA ASIGNADO A LA VARIABLE temp3 UN VALOR EN EL BIT 0 DE SER ASI
LE ASIGNA
        ldi desplazar1,0x20                      ;EL VALOR DE 0x20 A LA VARIABLE despazar1

        sbrc desplazar1,0                        ;PRUEBA SI EL BIT 7 DE LA VARIABLE despalzar1 SE ENCUENTRA SETEADO DE SER
ASI
        ldi temp3,0x01                           ;ASIGNA EL VALOR DE 0x01 A LA VARIABLE temp3

        sbrs desplazar1,0                        ;COMPRUEBA SI EL BIT 7 DE LA VARIABLE desplazar1 SE ENCUENTRA SETEADO
        ldi temp3,0x00                           ;DE LO CONTRARIO ASIGNA EL VALOR 0x00 A LA VARIABLE temp3
       
        out SREG, temp                          ;RETORNA EL VALOR DEL REGISTRO SREG
        pop temp                                     ;RETIRA EL REGISTRO DE LA PILA
        reti                                               ;RETORNA A LA POSICION DONDE SE ENCONTRABA   ANTES DE REALIZARSE LA
INTERRUPCION

También podría gustarte