El microcontrolador ATtiny 2313 posee dos módulo de timer, Timer0 y Timer1. El módulo Timer0 es de 8 bits y el módulo Timer1 es de 16 bits. El funcionamiento de ambos Timers es similar. En este caso utilizaremos interrupciones junto con el Timer0.
El primer paso para utilizar el módulo Timer0 o Timer1 es configurar de manera adecuada el reloj del sistema. Para ello debemos tomar la decisión de si usaremos un cristal externo o el oscilador interno del microcontrolador (que en el caso del ATtiny 2313 corresponde a un oscilador de 4 MHz o un oscilador de 8 Mhz según se configure).
Si vemos el esquemático o la documentación del AVR-MT, veremos que posee un cristal de 10 Mhz. Por lo tanto, se usará ese cristal para dar el reloj del sistema. Los fusibles de configuración (que se llaman fusibles por razones históricas, dado que hace un tiempo atrás verdaderamente eran fusibles, no como ahora que se implemementan en la memoria flash) que determinan qué fuente se usara para el reloj del sistema son CKSEL3..0. Las configuraciones posibles son,
Device Clocking Option | CKSEL3..0 |
External Clock | 0000 |
Calibrated Internal RC Oscillator 4MHz | 0010 |
Calibrated internal RC Oscillator 8MHz | 0100 |
Watchdog Oscillator 128kHz | 0110 |
External Crystal/Ceramic Resonator | 1000 – 1111 |
Reserved | 0001/0011/0101/0111 |
Por lo tanto en nuestro caso, escribiremos una configuración entre 1000 y 1111. Para saber cúal de esas configuraciones es la correcta, leemos en el datasheet que para una frecuencia de 8 Mhz o más, debemos configurar CKSEL3..0 como 111.
Debemos dejar los fusibles de configuración CKSEL2..0 como 1111. Para ello, abrimos PonyProg, seleccionamos como microcontrolador el ATtiny 2313,

Luego accedemos al diálogo de los fusibles de configuración,

Y configuramos los fusibles como en la imagen (notemos que las casillas seleccionadas significan un 0 y las casillas en blanco un 1).

El reloj del sistema posee un prescaler que permite dividir la frecuencia en los momentos en que los requerimientos de procesamiento no sean altos (lo que permite consumir menos energía). Sin embargo, en este ejemplo no dividiremos la frecuencia del sistema. El registro de configuración de este prescaler es CLKPR, y los bits que configuran la división de frecuencia son los cuatro de menor orden, CLKPS3:0. Si tomamos el valor de estos 4 bits como un número, digamos x, entonces el número por el cual se divide el reloj del sistema es 2^x. Si no queremos realizar una división del reloj del sistema, entonces x = 0 (lo que equivale a dividir por 1, o no dividir). Por esta razón, seteamos los bits CLKPS3:0 como 0.
En la cabecera del ATtiny 2313 se define el registro CLKPR, y los números de los bits CLKPS3:0, por lo que en nuestra sección de inicialización agregamos el código,
//Configurarprescalerdelsistemasindivisión //defrecuencia(dividirpor1)dejando CLKPR &= ~(1<<CLKPS0 | 1<<CLKPS1 | 1<<CLKPS2 | 1<<CLKPS3);
Ahora necesitamos configurar el módulo Timer0. Para ello, leemos el datasheet y nos documentamos acerca de los modos de funcionamiento del módulo. El modo más simple de operación es el modo normal, en el que el Timer0 se incrementa hasta el rebalse (donde comienza desde 0 nuevamente). Se puede configurar para que cada vez que ocurra un rebalse del contador (que como es de 8-bits ocurriría cada 256 incrementos), se lance una interrupción. Afortunadamente, el valor de inicial de la configuración del módulo Timer0 corresponde al modo normal. De todas maneras es bueno entender los demás modos de funcionamiento del módulo, y la manera de configurarlo (ver registros TCCR0A y TCCR0B).
El paso siguiente es configurar la fuente del Timer0 (ahora el valor inicial no nos sirve). Se puede escoger como fuente a una externa en el pin T0 o al reloj del sistema. Si se elige el reloj del sistema, además se puede usar un prescaler. Las divisiones posibles son desde 1 (sin división) hasta 1024. Los bits que configuran lo anterior son CS02:0 en el registro TCCR0B (que son los 3 bits menos significativos).
Para configurar el Timer0 sin prescaler (dividir por 1), los bits CS02:0 deben tener el valor 001. El código que se debe escribir es entonces,
//ConfiguraTimer0parautilicecomofuente //elrelojdelsistemasindividirlo TCCR0B |= 1 << CS00; TCCR0B &= ~(1 << CS01 | 1 << CS02);
Además, se quiere activar las interrupciones de manera que cuando el contador se rebalse (pase de 255 a 0), se lance una interrupción. El bit que activa o desactiva dicha interrupción es TOIE0, el bit 1 del registro TIMSK.
Entonces,
//Activarlainterrupción //deTimer0 TIMSK |= 1 << TOIE0;
El registro donde se guarda el valor del Timer0 es TCNT0. Es posible escribir como leer de él. Al finalizar el trozo de código de inicialización, volvemos a dejar su valor en 0 (y que de ahí en adelante siga contando).
//InicializarelTimer0 TCNT0 = 0;
Dado que hemos activado la interrupción del Timer0, debemos proveer una rutina de atención. El vector asociado al rebalse del Timer0 (mirando la cabecera del ATtiny 2313) es,
/*Timer/Counter0Overflow*/ #defineTIMER0_OVF_vect_VECTOR(6)
Por lo que creamos la rutina de atención así,
//Rutinadeatenciónparaelrebalse //delregistroTCTN0(delTimer0) ISR(TIMER0_OVF_vect) { //Atenderlainterrupción }
Ahora es cuando podemos escribir algo interesante. Veamos primero que en un comienzo configuramos el reloj del sistema para que utilizara el cristal del AVR-MT (que posee una frecuencia de 10 MHz). Entonces, en cada segundo el registro TCTN0 se incrementa 10000000, es decir, cada 0.1 microsegundos ocurre un incremento. Entonces, cada 0.1*256 = 25.6 microsegundos ocurre una interrupción. Entonces, en un segundo (1 000 000 de microsegundos) ocurren 1000000/25.6 = 39062.5 interrupciones. Para que el cálculo entregue un número entero, podemos decir entonces que en 2 segundos, ocurren 78125 interrupciones.
Entonces, vamos a realizar alguna tarea cada dos segundos. Por ejemplo, podemos enviar por medio del puerto serial las letras del abecedario cada dos segundos. Con respecto al código de envío y configuración del puerto serial, se puede consultar el tutorial para su configuración y uso en este enlace. Por el momento, suponemos que contamos con una función de inicialización del puerto serial, y una función de envío de datos.
El código del conteo de la rutina de atención es,
//Variablequecuentael //númerodeinterrupciones //delTimer0 unsigned long counter; //Variablequeindicasihan //pasadodossegundos volatile unsigned char two_seconds; ISR(TIMER0_OVF_vect) { //Incrementoelcontador. //Cadaincrementocorrespondea256 //incrementosdelTimer0 counter++; //256*78125=20000000incrementos //Sielrelojcorrea10Mhz,entonces //20000000incrementos=2segundos if(counter == 78125) { //Marcamosquehanpasado2segundos two_seconds = 1; //Reiniciamoselcontadorpara //lospróximos2segundos counter = 0; } }
En main, tenemos
int main(void) { //configuracionesiniciales init_ports(); //contador unsigned char i = 0; for(;;) { //Esperaraquehayan //pasadodossegundos if(two_seconds) { for(;;) { //Siyaenviamoselabecedario if('A' + i > 'Z') { USART_Transmit('\n'); USART_Transmit('\r'); i = 0; //Dejardeenviarcaracteres break; } //Enviarcaracteresdelabecedario USART_Transmit('A' + i); ++i; } //Yapasaronlosdossegundos two_seconds = 0; } } }
De la misma manera, se puede generar eventos periodicos de mayor frecuencia (cada 100 milisegundos, por ejemplo). Lo único necesario es calcular las veces que se debería incrementar el contador para obtener la frecuencia deseada. También hay que tener en cuenta que la rutina de interrupción no debe ser extensa, ni ejecutarse demasiadas veces en un corto periodo de tiempo pues de ser así, la mayor parte del tiempo de procesamiento, estaría siendo usada en atender las interrupciones en vez de ejecutar el programa principal. En esos casos, puede ser conveniente utilizar el prescaler del Timer y con la división adecuada, generar menos interrupciones.
Otra opción en caso de programas más complejos es utilizar el Timer1, el cual posee 16 bits, el doble de lo que tiene el Timer0. De todas formas, el código no debería ser más complejo que el de este ejemplo.