El microcontrolador ATtiny 2313 posee un módulo para comunicación serial, USART. Para utilizar este módulo, necesitamos configurar varios registros, para ver si se usará un bit de paridad, el número de bits de los datos, el número de stop bits entre otros.
El módulo USART posee un generador de tasa de transferencia (baud rate), controlado por el registro UBRR. La fórmula para obtener el valor de UBRR depende de la frecuencia de operación del microcontrolador y de los baudios que se quiera obtener.
En este ejemplo se utilizará transmisión asíncrona de los datos. El microcontrolador posee también dos modos de transmisión de datos, uno rápido y otro normal. La diferencia principal es que el modo de alta velocidad se usan menos muestras que en el modo de transmisión normal.
En este ejemplo utilizaremos el modo normal de transmisión de datos. La fórmula para el registro UBRR es (para modo de velocidad normal y transmisión asíncrona),
UBRR = FOSC/(16*BAUD) – 1
Sabemos que el cristal conectado al ATtiny 2313 provee una frecuencia de 10 MHz. Además si se desea transmitir a 9600 baudios, entonces
UBRR = 10^7/(16*9600) – 1 UBRR = 64.104
Dado que no es un número entero, deberemos elegir el entero más cercano al valor ideal para UBRR. Elegimos 64, lo que genera un error de -0.16% el cual en la mayoría de las aplicaciones no debería causar problemas.
El registro UBRR es en verdad dos registros, UBRRH y UBRRL, ambos de 8 bits. UBRRH guarda los 4 bits más significativos de UBRR, y UBRRL guarda los 8 bits menos significativos de UBRR. Los 4 bits más significativos de UBRRH no se utilizan y en el datasheet se recomienda escribirlos como 0 para compatibilidad con dispositivos futuros.
Por lo tanto, si necesitamos escribir 64 en UBRR (por el cálculo de más arriba), en la rutina de inicialización, podemos escribir,
//64cabeenlos8bitsdel //registroUBRRL UBRRH = 0; //9600baudiosconel //cristalde10MHz //delAVR-MT UBRRL = 64;
De todas maneras, en el datasheet del microcontrolador, se sugieren las instrucciones para un valor variable de UBRR,
UBRRH = (unsigned char) (baud >> 8); UBRRL = (unsigned char) baud;
Bastaría hacer baud = 64, antes de usarlas en este ejemplo. Para activar la transmisión y la recepción se configura los bits TXEN y RCEN del registro UCSRB (bit 3 y 4, respectivamente). Activamos tanto transmisión como recepción de datos,
//Activarlatransmisiónyrecepcióndedatos UCSRB = (1<<RXEN)|(1<<TXEN);
El siguiente paso en la configuración es determinar la cantidad de bits que se considerarán un caracter. El microcontrolador inicialmente viene configurado para enviar caracteres de 5 bits. Los bits de configuración que determinan el tamaño del caracter son los bits UCSZ2:0 del registro UCSRC (UCSZ1:0) y UCSRB (UCSZ2). Utilizaremos un tamaño de caracter de 8 bits, el cual corresponde al valor 011 de UCSZ2:0.
//Eltamañodeuncaracteres8bits. UCSRC = (1 << UCSZ0 | 1 << UCSZ1);
Con respecto a stop bits, la configuración inicial es de 1 stop bit y no la cambiaremos. De todas formas, el bit que configura si es 1 stop bit o 2 stop bits es el bit USBS, del registro UCSRC. Si este bit vale 0 entonces se usará 1 stop bit. Si el bit vale 1, entonces se usará 2 stop bits.
Una vez realizadas las configuraciones necesarias para usar el puerto serial, escribiremos un programa de prueba que haga eco de los caracteres que recibe. Para enviar y recibir datos, existe un único registro, UDR. Cuando se escribe en el registro UDR, se da a entender que se transmitirá lo escrito. De la misma forma, cuando se lee del registro UDR, se toma como si se estuviese leyendo del puerto serial. Es por esta razón que no hay inconveniente alguno con que sólo exista un registro para la transmisión y recepción de datos a través del puerto serial.
Para escribir, es necesario verificar que el registro UDR se encuentre vacío y listo para recibir un nuevo dato para transmitir. El bit que indica si UDR se encuentra listo para recibir datos es UDRE, el bit 5 del registro UCSRA. Si UDRE es 1, quiere decir que el registro UDR se encuentra listo para recibir datos. Por lo tanto, debemos esperar que UDRE sea 1 antes de escribir en UDR,
//EsperarmientraselbitUDRE //sea0(siesasilacondicióndelwhileesverdadera) while(!(UCSRA & (1 << UDRE))) ;
Una vez terminado el busy waiting, podemos escribir en el registro UDR,
//Enviardatosporelpuertoserial //Porejemplo,podemosenviar42 //TheAnswertoLife,theUniverse,andEverything UDR = 42;
Para leer desde el puerto serial, el procedimiento es similar. Esta vez, se debe verificar el bit 7 del registro UCSRA, RXC. Este bit indica que hay datos en el registro UDR que aún no han sido leídos. Cuando el bit vale 1, quiere decir que hay datos para ser leídos. Cuando RXC vale 0, no hay datos que leer en el registro UDR. Dado lo anterior, podemos escribir las siguientes instrucciones que leen un dato desde el puerto serial,
unsigned char data; //Esperarquehaydatosqueleer while(!(UCSRA & (1 << RXC))) ; //Leerlos data = UDR;
Entonces, el programa que hace eco de los caracteres que llegan por el puerto serial, sería (el trozo relevante del código),
unsigned char data; for(;;) { while(!(UCSRA & (1 << RXC))) ; data = UDR; while(!(UCSRA & (1 << UDRE))) ; UDR = data; }
Por supuesto que no es un programa eficiente, pero es un buen ejemplo. Una mejora posible sería utilizar interrupciones, de manera que cada vez que hubiese un caracter para leer se interrumpiera el flujo normal del programa.
Para utilizar la interrupción del puerto serial, es necesario configurar el bit RXCIE (bit 7 del registro UCSRB) como 1, lo que activa la interrupción. También es necesario activar las interrupciones globalmente.
El vector correspondiente a la interrupción de recepción de datos a través del puerto serial es USART_RX_vect.
El código de importancia para utilizar la interrución de recepción de datos del puerto serial es,
Al comienzo del archivo,
#include<avr/interrupt.h>
Como variable global,
volatile unsigned char read;
En la la función de inicialización,
//Noleemosdatosaún read = 0; //Activarinterrupciones sei(); //Activarinterrupción //delpuertoserial UCSRB |= 1 << RXCIE;
La rutina de atención,
ISR(USART_RX_vect) { read = 1; }
Y el programa principal,
for(;;) { while(!read) ; data = UDR; read = 0; while(!(UCSRA & (1 << UDRE))) ; UDR = data; }
Es claro que en el código anterior se tiene condiciones de carrera (por ejemplo, luego de leer el caracter, pero antes de escribir un 0 en read, podría llegar otro caracter, que sería ignorado pues a continuación se pondría read en 0), pero la intención es mostrar el funcionamiento de la rutina de interrupción, donde se puede implementar alguna técnica más avanzada como un buffer circular, por ejemplo.
El espíritu de usar interrupciones es que se pueda realizar una tarea principal sin perder de vista lo que ocurre con el módulo que lanzará interrupciones cuando un evento importante ocurra (en este caso, la llegada de un caracter).
El código completo de este ejemplo se encuentra en el archivo Serial.c serial_int.c.