AVR programación en C – 09 Timer/Counter0 del ATmega16 – Parte 1 (Modo
Normal)
Posted by alfreedom on August 17, 2014
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. Opcional mente 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.
Configuració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.
Registro 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órmula 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 timer0 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 #include <avr/io.h>
3
4 int main(void) {
5
6 // Timer0 modo normal, sin usar el pin OC0, prescaler de 1024
7 TCCR0 = _BV(CS00) | _BV(CS02);
8
9 // Puerto PB0 como salida.
10 DDRB = _BV(PB0);
11 PORTB = 0;
12
13 while(1) {
14
15 // si ya hubo desbordamiento del timer0:
16 if( (TIFR & _BV(TOV0)) ){
17
18 PORTB ^= _BV(PB0); // conmuta el led
19 TIFR |= _BV(TOV0); // limpia la bandera
20 }
21 }
22 return 0;
23 }
24 //
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
#include <avr/io.h>
3 #include <avr/interrupt.h>
4
5 int main(void) {
6
7 // Timer0 modo normal, sin usar el pin OC0, prescaler de 1024
8 TCCR0 = _BV(CS00) | _BV(CS02);
9
10 // habilta interrupción por desbordamiento de timer0
11 TIMSK = _BV(TOIE0);
12
13 // habilita interrupciones globales
14 sei();
15
16 // Puerto PB0 como salida.
17 DDRB = _BV(PB0);;
18 PORTB = 0;
19
20 while(1) {} // CPU libre
21
22 return 0;
23 }
24 // función para atender la interrupción
25 ISR(TIMER0_OVF_vect){
26 // se conmuta el estado del led
27 PORTB ^= _BV(PB0);
28 }
29 //
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
3 #include <avr/io.h>
4 #define RETARDO 1000 // 1000 milisegundos - 1 segundo
5 int main(void) {
6 int milis=0;
7 // Timer en modo normal, sin salida por OC0 con prescaler de 1024
8 TCCR0 = _BV(CS00) | _BV(CS02);
9
10 // Puerto PB0 como salida.
11 DDRB = _BV(PB0);
12 PORTB = 0;
13
14 while(1) {
15
16 if(TCNT0 >= 79) // si ya pasaron 10 milisegundos
17 {
18 TCNT0=0;
19 if((milis+=10) >= RETARDO) // si ya pasaron 1000
20 milisegundos
21 {
22 // cambia el estado del led
23 PORTB ^= _BV(PB0);
24 // reinicia el contador de milisegundos
25 milis=0;
26 }
27 }
28
29 }
30 return 0;
31 }
32 //
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 #define RETARDO 1000 // 1000 milisegundos - 1 segundo
3 #define F_CPU 8000000
4 #define LED PB0
5
6 #include <avr/io.h>
7 #include <avr/interrupt.h>
8
9 volatile int milis=0;
10
11 int main(void) {
12
13 // Timer en modo normal, sin salida por OC0 con preescaler de 1024
14 TCCR0 = _BV(CS00) | _BV(CS02);
15
16 // Activa interrupción por debordamiento del timer0
17 TIMSK = _BV(TOV0);
18
19 // puerto PB0 como salida
20 DDRB = _BV(LED);
21 PORTB = 0;
22
23 // carga el timer con 177 para que se desborde a los 10ms
24 TCNT0 = 177;
25
26 // Avtiva interrupciones globales
27 sei();
28
29 while(1) {
30 }
31
32 return 0;
33 }
34
35 ISR(TIMER0_OVF_vect){
36
37 // El timer se desborda cada 10ms
38 // recarga el timer con 177 de nuevo
39 TCNT0 = 177;
40 if((milis += 10) >= RETARDO){
41 milis=0;
42 PORTB ^= _BV(LED);
43 }
44 }
45 //
Programa 2: Interrupción por Coincidencia en comparación.
1 //
2 #define RETARDO 1000 // 1000 milisegundos - 1 segundo
3 #define F_CPU 8000000
4 #define LED PB0
5
6 #include <avr/io.h>
7 #include <avr/interrupt.h>
8
9 volatile int milis=0;
10
11 int main(void) {
12 // Timer en modo normal, sin salida por OC0 con preescaler de 1024
13 TCCR0 = _BV(CS00) | _BV(CS02);
14
15 // Activa interrupción por comparación del timer0
16 TIMSK = _BV(OCIE0);
17
18 // puerto PB0 como salida
19 DDRB = _BV(LED);
20 PORTB = 0;
21
22 // carga registro comparador con 79
23 OCR0 = 79;
24
25 // Avtiva interrupciones globales
26 sei();
27
28 while(1) {
29 }
30 return 0;
31 }
32
33 ISR(TIMER0_COMP_vect){
34
35 // reinicia el timer
36 TCNT0 = 0;
37 if((milis += 10) >= RETARDO){
38 milis=0;
39 PORTB ^= 0xFF;
40 }
41 }
43 //
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
AVR programación en C – 10 Timer/Counter0 del ATmega16 – Parte 2
(Modo CTC)
Hola a todos, después de ausentarme un buen rato (por ocupaciones de la escuela y cosas por el estilo)
y no andar subiendo nada por acá, regreso con la serie de turoriales para programar el timer0 de los
AVR, para este caso el ATmega16/32. Como se vió en la parte 1, el timer/counter0 tiene distintos
modos de trabajo, en esa parte se vio el modo normal, que solo cuenta de 0 a 255 de manera ascendente
y puede generar interrupción al desbordarse o al coincidir con el registro OCR0. En esta parte veremos
el modo CTC (Clear Timer on Compare) que reinicia el timer0 cuando los valores del registro OCR0 y
el registro TMR0 coinciden, y este evento puede generar una interrupción o cambiar el estado de un
pin del microcontrolador. Este modo del timer es útil para generar una salida de onda cuadrada por el
pin OC0 (Output Compare 0), que es el pin asociado al módulo comparador del timer0, ya sea dejando
el periodo definido en el registro OCR0 o cambiando este periodo al atender la interrupción que se
genera al coincidir la comparación del registro OCR0 con el TMR0. Estos registros se describen más
detalladamente en la primera parte de los tutoriasl del Timer/Counter0. La mejor manera de aprender es
con el ejemplo, y mas si el ejemplo tiene aplicación, y como se menciono antes, este modo sirve para
generar una salida de onda cuadrada de frecuencia mas o menos variable, y que además de todo puede
controlar el estado de el pin OC0, haremos un programa que simule un control remoto emisor. La gran
mayoría de los controles remotos funcionan transmitiendo señales de luz infrarroja en forma de pulsos
a una frecuencia de 38Khz, aunque también se utilizan frecuencias de 30, 36 y 56 KHz. Los receptores
infrarrojos reciben la luz modulada a esta frecuencia y la convierten en niveles lógicos de 1’s y 0’s, un
0 para la presencia de luz y un 1 para la ausencia de luz, por lo que se podría decir que los receptores
trabajan con lógica negada. Los modelos más comunes de receptores son el TSOP1738 y el VS1838,
que funcionan a una frecuencia de 38kHz, y es el que usaremos. Entonces, lo que tendremos que hacer,
es un circuito que emita pulsos infrarrojos a 38khz para que el receptor los pueda recibir y decodificar.
En este ejemplo lo que se quiere es ejemplificar el uso del timer0 como generador de frecuencia, por lo
que solo veremos el transmisor, ya en otro post se verá como hacer el receptor y así poder dotar
fácilmente a nuestros proyectos de comunicación infrarroja con un protocolo propio. El circuito no es
mas que un led infrarrojo conectado al pin OC0 y un push-button conectado a cualquier otro pin. El
funcionamiento seria que cuando se presione el push-button el Led infrarrojo se encienda a una
frecuencia de 38khz y cuando se suelte se apague. Para generar la frecuencia de 38 kHz usaremos el
timer0 en modo CTC, que conmuta el estado del pin OC0 cada que la cuenta del timer coincide con el
registro OCR0, generando una señal de onda cuadrada. Para encender y apagar el Led, se usa un push
button conectado a pin INT0, pata atender al botón en una interrupción. La manera de encender el Led
es cambiando la dirección del pin, si el pin es de salida encenderá el led con la señal enviada por el
modulo CTC, si el pin es de entrada esta señal solo activará y desactivará la resistencia pull-up interna
del pin, manteniendo al led apagado. Para establecer al timer0 en modo CTC, se deben programar los
bits WMG00 y WMG01 del registro TCCR0 con 0 y 1 respectivamente.
Para que cambie el estado del pin OC0 en cada comparación se programan los bits COM00 y COM01
con 1 y 0 respectivamente.
Para establecer el prescaler de 8 para el timer se programan los bits CS0, CS1 y CS2 con 0, 1 y 0
respectivamente. Para calcular la frecuencia de la onda que genera el timer en modo CTC, el datasheet
tiene la siguiente fórmula:
Donde Focn es la frecuencia a calcular, Fclk es la frecuencia del microcontrolador, N es el prescaler del
timer ( 1, 8, 64, 256 o 1024) y OCRn es el valor del registro OCR0. Como lo que se desea saber es el
valor del registro OCR0, de la fórmula anterior solo hay que despejar el valor OCRn, quedando:
Ahora sólo se sustituye la Focn en la fórmula con la frecuencia deseada y obtendremos el valor que hay
que cargar en el registro OCR0, que para este caso lo deseado es 38kh (38000 Hz). Con un
microcontrolador funcionando a 8Mhz y con un prescaler de 8 para el timer, el valor del registro OCR0
seria:
El valor que se tiene que escribir en el registro OCR0 es 12 para generar una frecuencia de 38khz. El
programa propuesto para realizar esta tarea sería el siguiente:
Código:
1
2 //
3 #define F_CPU 8000000
4 #define BUTTON PORTD2 // Push Button conectado a PD2
5
6 #include <avr/io.h>
#include <avr/interrupt.h>
17 #include <util/delay.h>
18 void InitiSystem(){
19 DDRD &= ~_BV(BUTTON); //Puerto PD2 como entrada.
20 PORTD |= _BV(BUTTON); // Activa Pull-Up de PD2
// Timer0 en modo CTC, prescaler de 8 y cambio de estado de pin
21
OC0
22
// cada coincidencia del registro OCR0 y TCNT0
23
TCCR0 = _BV(WGM01) | _BV(COM00) | _BV(CS01);
24
// Registro OCR0 con 12 para generar una señal de 38khz
25
OCR0 = 12;
26
// Habilita interrupcion en INT0
27
GICR = _BV(INT0);
28
// Configura interrupcion por flanco de bajada en pin INT0
29 MCUCR = _BV(ISC01);
30 // Habilita interrupciones globales.
31 sei();
32 }
33 int main(void) {
34 InitiSystem();
35 while(1) {
36 }
37 return 0;
38 }
39 // Función para atender la interrupción INT0 del push button
40 ISR(INT0_vect){
41 // Cambia la dirección del puerto OC0, si es de entrada
42 // lo pone como salida (enciende led) si es de salida
43 // lo pone como entrada (apaga el led)
44 DDRB ^= _BV(PB3);
45
46 // pausa para eliminar rebotes.
47 _delay_ms(30);
48 }
49 //
50