Este blog está dedicado a mis experiencias, proyectos, dificultades y demás en todo lo relacionado a la electrónica y la programación en general sobre GNU/Linux

AVR, Digital, Electrónica, Nivel: Intermedio

Funciones útiles para la UART en un AVR ATmega8 y/o ATmega328P

Nivel: Intermiedio

¿Qué tengo que saber para este post?

  • Hexadecimal (no excluyente).
  • Conocimientos sólidos de lenguaje C de programación: punteros y operadores de bit, colas y pilas circulares.
  • Conocimientos básicos de electrónica digital: qué es un 1 y un 0, compuertas lógicas.
  • Conocimientos intermedios de microcontroladores: qué son los SFR’s, qué es el Transmisor Receptor Asincrónico Universal (UART). Qué es una máscara y operadores de bit. Mínimos conocimientos de la familia AVR de ATMEL, en especial la linea ATmega.

NOTA: Si te interesa saber como fueron pensadas las funciones y la explicación de como es su funcionamiento básico, seguí leyendo. Sino, anda directo al ejemplo, donde dice «a los bifes» al final del post.

NOTA2: Este post fue editado el 16/01/13, fue agregada la posibilidad de compilarlo con el ATmega328P. Los archivos main m8.c tienen el archivo original que se muestra en este post y el main m328p.c el mismo ejemplo pero inicializando los registros de la USART del ATmega328P, que son muy similares al del ATmega8. Saludos.

————————————–

Introducción

Buenas y santas, estimados lectores y curiosos. Este es el primer post técnico oficia en el que les voy a transmitir mis pobres conocimientos a Uds. en la espera de que les sea de utilidad y lo puedan utilizar en algún proyecto personal que tengan. Antes que nada, pido encarecidamente que si alguien nota algo mal en el post, que me avise, ya que empecé a experimentar con los AVR hace un par de semanas nada más y capaz que le pifio en algo. Mi mayor experiencia con MCUs la hice con micros basados en la familia Intel 8051, y tengo mucho código basado en ellos, pero siempre en vista de que sean lo más portable dentro de las posibilidades. Además tengo apuntes hechos que subiré en algún momento, porque es la materia que dicto en el colegio donde doy clases. Por ello, me vi interesado en usar algunas funciones que ya tenía implementadas para MCS51 en los ATmega de AVR. Busqué en las hojas de datos y los ejemplos que encontré allí no me parecieron muy útiles, en Internet tampoco encontré lo que buscaba. Claro, Uds. deben estar preguntándose ¿qué estaba buscando este tipo? Muy sencillo, buscaba un código que utilizando las interrupciones asociadas a la transmisión y recepción de la USART, enviara y recibiera información sin desperdiciar tiempo en ese proceso y leerlo solamente cuando me sea necesario de un buffer de entrada y otro de salida. Es decir, que a mí me pueden llegar muchos datos, almacenarlo en un buffer de recepción y luego en algún punto x del programa, sacar los datos. En todos los ejemplos que encontré en Internet la comunicación serie estaba asociada a un while en el que se esperaba a que el registro de datos del micro se liberara para enviar el próximo dato. Si quisiera enviar un string, cuando hay otras cosas que atender dentro de nuestro programa, esta demora involuntaria nos estaría molestando (al menos desde mi punto de visa).

¿Cómo funciona?

La idea es la siguiente, tener dos buffer circulares del tipo FIFO (First Input First Output – el primero en entrar es el primero en salir) en el que nosotros almacenemos los datos que queremos que salgan y otro para los datos que entran de la UART. En un buffer circuilar tenemos 2 punteros, uno es el puntero de extracción y el otro de inserción de datos. Cuando entra un dato por la UART este entra a la dirección de memoria a la que apunta el puntero de inserción (push) y este se aumenta en 1, y así sucesivamente con todos los datos que vayan entrando. Luego, lo único que tenemos que hacer es ir recuperando los datos del buffer (pop), cuando nosotros lo necesitemos, empezando por la dirección de memoria del puntero de extracción.

Buffer Circular

Ocurre exactamente lo contrario (y de cierta manera, lo mismo), cuando nosotros queremos transmitir por la UART. Simplemente debemos insertar (push) datos en el buffer de transmisión y luego, por medio de la interrupción se irán sacando los datos del buffer (pop) a medida que se van transmitiendo. Esto nos da la facilidad de escribir todos los datos que queremos enviar sin tener que esperar a que el registro de transmisión se vacíe ganando tiempo para ejecutar otras cosas.

En cualquiera de los dos casos, cuando el puntero de extracción alcanza al de inserción, esto indica que ya no hay datos que sacar del buffer y retorna un (-1). Pero CUIDADO, también podría pasar que estemos escribiendo datos más rápido de los que podemos transmitir y eso nos llevaría a dar toda la vuelta al buffer y a perder información. En el caso de la recepción es igual, si nosotros no leemos el buffer y la información supera la longitud del mismo, perderemos información, por lo cual deberemos aumentar el tamaño de los buffers. De todas formas, siempre podemos crear algún mecanismo o algoritmo para pedir que nos envíen la información nuevamente, como con un checksum. Pero bueno, tampoco les voy a solucionar la vida xD algo tienen que laburar Uds. también.

Implementación

  • Preparativos:

Antes que nada, debemos configurar la USART del micro, para ello, debemos atenernos a la hoja de datos del ATmega8. Claro que no les voy a hacer leerse las 400 hojas que tiene, vayan al indice del pdf y a «USART Register description». Allí procederemos a los 3 pasos básicos para usar una UART cualquiera.

  1. Establecer un Baudrate para la transmisión y la recepción.
  2. Configurar el modo de la USART (sincrónica/asincrónica, 5/6/7/8/9 bits de datos, paridad, etc.)
  3. Y no menos importante, habilitar la transmisión y/o recepción + las interrupciones pertinentes  (si se desean utilizar).

Lo primero lo hacemos con los registros UBRRH y UBRRL y el bit-1  del registro UCSRA (U2X) que nos permite multiplicar x2 el baudrate. Gracias a él, podemos (y lo haremos) generar 9600 baudios (dentro de un error más que tolerable) con el oscilador RC interno de 1 Mhz =) Ahora les copipasteo copio y pego una tabla que está en la hoja de datos, para mostrarles la configuración que utilicé para el ejemplo que les voy a pasar:

Baudrates posibles a 1Mhz de Fosc

Lo segundo se hace con el bit-2 de UCSRB (UCSZ2) y el registro UCSRC, el cual los muy putos de los fabricantes pusieron en el mismo espacio de memoria que el registro UBRRH. Por lo tanto, para acceder a él debemos poner su bit más significativo (el bit-7), a.k.a. URSEL, en 1. y allí hay varias tablas para configurar la cantidad de datos, la paridad, y demás cosas (miren la hoja de datos).

Por último, para habilitar la recepción, transmisión e interrupciones anexadas a la USART, debemos dirigirnos al registro UCSRB, salvo los bit-0, bit-1 y bit-2. El bit-2 lo vimos en el párrafo anterior, y los otros 2 se utilizan como el bit que falta en las transmisiones de 9 bits de datos. Así que, sabiendo todas estas cosas, les paso a comentar qué necesitamos para poder utilizar las funciones que les voy a pasar:

  • Baudrate: cualquiera.
  • Cantidad de bits de datos: 8 o menos.
  • Paridad: Cualquiera.
  • Control de flujo: si quieren…
  • Habilitar recepción y/o transmisión de datos bit-4 y bit-3 de UCSRB.
  • Habilitar interrupciones de recepción y transmisión completadas bit-7 y bit-6 del registro UCSRB.
  • Las funciones que disponemos:

Las funciones son las siguientes:

int USART_PopRx(void);

void USART_PushTx(unsigned char nDato);

void USART_SendStr(const char* pszStr);

  • PopRx(): devuelve el valor leído del buffer de recepción, si no hay datos, entonces devuelve -1.
  • PushTx(): entra nDato al buffer de transmisión, si no hay datos que se estén enviando, entonces arranca la transmisión.
  • SendStr(): pone un string en el buffer de transmisión, son llamadas múltiples a la función PushTx().
  • A los bifes:

El ejemplo que les presento utiliza las 3 funciones para que quede claro como trabajar con ellas:

#include <avr/io.h>
#include <avr/interrupt.h>
#include "Serial.h"

#define ULDIFF      ('A'-'a')
#define TOUPPER(X)  (X+ULDIFF)
#define TOLOWER(X)  (X-ULDIFF)

void USART_Init(void)
{
/* Configuración de 9600 con error del 0.2% a 1 Mhz de F_CPU */
UBRRL=12;   // Dato sacado de la hoja de datos
UCSRA=0x02; // Doblar el Baudrate - U2X = 1

/* UART con 8-bits de datos y 1 de STOP sin paridad */
UCSRC=0x86; // URSEL = 1 : acceder al registro UCSRC
// UCSZ1 = UCSZ0 = 1 : 8-bits de dato

/* Habilitación de la Recepcion y la Transmisión e interrupciones
de recepción y transmisión terminada */
UCSRB=0xD8; // RXCIE = TXCIE = RXEN = TXEN = 1

/* Habilitación de las interrupciones globales, sino no funcionan
las funciones de la USART */
sei();
}

int main(void)
{
int data;

// Inicializo el puerto serie
USART_Init();

// Envio un dato para verificar que funcione la comunicación
USART_SendStr("Hola Mundo!!!");

while(1)
{
// Saco el dato del buffer y lo almaceno en data
data=USART_PopRx();

// Verifico si había datos en el buffer
if(data!=-1)
{
if(data>='a'&&data<='z')                    // Si el dato es una minúscula
USART_PushTx((uint8_t)TOUPPER(data));   // envio su mayúscula
else if(data>='A'&&data<='Z')               // sino, si el dato es una mayúscula
USART_PushTx((uint8_t)TOLOWER(data));   // envio su minúscula
else                                        // sino,
USART_PushTx((uint8_t)data);            // devuelvo el dato tal cual entró
}

}

return 0;
}

Por último les dejo el link a una carpeta de GoogleDrive para que se bajen el header y la implementación de las funciones junto con el mismo ejemplo que ven acá, para que les echen un ojo y tal vez las mejoren o las adapten para que les sean de mayor utilidad. Ah, casi me olvido de mencionar que el código está bajo licencia GNU/GPLv3, así que estamos «tudo bem, tudo legal».

Código fuente ACÁ

Es todo, espero que les sea útil y como dice un colega y amigo, sean felices.

15 Comentarios

  1. david

    Buen dia.

    Buen post!
    Depronto sabes como es posible programar un avr por usart?

  2. Andres Galvez

    Excelente post, muchas gracias 😀

  3. Fabricio

    Saludos, la verdad me encanta la electrónica básica, pero tengo una curiosidad sobre hasta donde puedo llegar con la tecnología bluetooth, quiero construir un dispositivo que al captar una señal bluetooth active un rele y al perder la señal se desactive el rele, talvez tienes una sugerencia por donde pueda empezar?

    • Comentario del autor

      Buenas, gracias por tu comentario. Nunca utilicé bluetooth,lamento no poder ser de más ayuda.

      Lo que yo haría es conseguir un módulo bluetooth, que seguramente tendrá un chip para manejarlo, no creo que sea muy distinto a un módulo GSM o similares. Estudiar mucho la hoja de datos.

      Si conseguís algo de eso, me encantaría que lo compartas.

      Saludos.

  4. david

    Buena tarde msavalos.

    Depronto conoces el protocolo RFID Wiegand de 26bit 125Khz?
    Es para tarjetas de control de acceso.
    La idea es leer la informacion suministrada por un control de acceso de protocolo Wiegand con un microcontrolador ATmega para visualizarla por USART en el PC.
    La necesidad se debe a que el control de acceso no suministra el codigo de usuario.

    • Comentario del autor

      Sí, creo que sé cuales son, he trabajado con ellas alguna vez. Si son las que yo pienso, hay unos lectores muy baratos que funcionan por USAT, que se consiguen por eBay.com que funcionan realmente bien. Te dejo el link.

      Si te comprás un módulo USB->TTL ni siquiera necesitas un micro. Te tira la info en forma de caracteres a una hyperterminal/terminal serie de la PC.

      Espero que te sirva, Saludos.

  5. Lewop

    Hola, excelente post, …
    Quisiera saber si es posible programar funciones usart en la interfaz de arduino o si puedo compilar el codigo en atmel studio y luego usar arduino uno para grabar el .hex en un atmega 328p…

    • Comentario del autor

      Podes tomar las librerías de arduino, que vienen incluidas con el ide de arduino. Podés tomar esos archivos e incluirlos en tu proyecto de atmel studio. Arduino implementa algo muy parecido a esto que postee (eso supongo). Espero haberte sido útil.

      Saludos.

  6. como incluyo la librería serial.h la he intentado usar y atmel studio no me la acepta

    • Comentario del autor

      No tengo mucha experiencia con el Atmel Studio, sinceramente. Lo utilicé pocas veces, ya que soy un usuario Linux la mayor parte del tiempo. Pero, he de suponer que no debe ser muy distinto a cualquier otro IDE.

      Verifica que el archivo «serial.c» esté agregado a tu proyecto actual y que tu archivo fuente donde esté la función main() tenga incluida el archivo con el «#include «serial.h», siempre y cuando tus archivos estén todos juntos.

      Saludos, éxitos.

  7. David

    Excelente Post,

    Soy novato en esto de los micros, actualmente he ralizado un codigo para recibir un caracter y dependiendo de que caracter sea puedo encender un led, ahora quiero encender un led pero al recibir una cadena en especifico y utilizando la interrupción rx , digamos «encender_led1», un ejemplo que me pudieras pasar.

    De antemano gracias y excelente trabajo el que haces al subir información como esta.

    Saludos,

    • Comentario del autor

      Hola, gracias por tu comentario. Si recién estás aprendiendo a programar te sugiero que busques en internet las funciones de manejo de strings de la biblioteca estándar string.h, sobre todo la función strcmp() que es la que necesitarías en esta caso.

      Para esta práctica te recomiendo declarar un vector de char (string) e ir almacenando lo que llegue hasta encontrar el carácter ‘\n’ que te indicaría el final de la frase o string. Y luego compararlo con tu string hardcodeado con la función strcmp.

      De todas formas, no es una muy buena idea utilizar un string para ejecutar un pedazo de código dentro del micro, no pasa de una simple práctica por diversión o desafiarte para lograr que funcione. Si se trata de una implementación un poco más seria, mi recomendación sería que te armes un mini protocolo y lo implementes a nivel de bytes. Por ejemplo: Bstart-Bdata0-Bdata1-…-BdataN-chksum-Bstop o algo así, dependiendo de tus necesidades.

      Saludos.

      PD: disculpá que no te ponga ningún ejemplo en código, ando medio saturado con laburos.

  8. teran

    hola disculpa me podras mandar el .hexe de tu programa porfavor

Dejar una respuesta