Archivos Mensuales: octubre 2012

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.

Anuncios

Hello world!

¡Bienvenidos a este blog!

WordPress pone como primer entrada de ejemplo un post llamado “Hello world!” y teniendo en cuenta la naturaleza de este pequeño espacio en la red, me pareció divertido dejarlo así. Para que nos vayamos conociendo un poco, y además para no dejarlos con las manos vacías. Me parece prudente que hablemos un poco del gcc y como hacer un “¡Hola, mundo!”, que es el primer programa de ejemplo cuando uno aprende un lenguaje de programación desde el principio de los tiempos (no estoy seguro de que sea lo más didáctico).

Un lenguaje de programación es una forma de asentar un grupo de instrucciones para que ejecute algún dispositivo. Estas instrucciones deben ser compiladas (traducidas) al lenguaje de máquina que son los famosos unos y ceros que tienen que interpretar el dispositivo que queramos que las ejecute (Una PC, un sistema embebido, un robot, etc.). Para esto necesitamos un traductor, un programa llamado compilador. El gcc fue históricamente la copia del ya, en ese entonces, histórico cc (“C compiller”), de ahí su nombre: GNU C Compiller (para los que quieran saber que es GNU, su significado e historia pueden echarle un vistazo acá). Actualmente el gcc es una “Colección” de compiladores (GNU Compiller Collection), porque no sólo nos aporta programas que compilan lenguaje C de programación, sino que también otros lenguajes varios. No voy ahondar mucho en estos temas ahora, ya que en futuros post bajo algún tag (etiqueta/categoría) distintiva como “ABC de la programación”, será objeto de estudio de estos conceptos básicos.

¿Dónde y cómo genero mi programa? Las serie de instrucciones que le dictamos al dispositivo a ejecutar son escritas en un archivo plano de texto. Un archivo plano es un “.txt” que desde hace año podemos escribir en el clásico NotePad de Ventanas (a.k.a. “Bloc de Notas”), es decir un texto sin formato (sin tipo de letra, negrita, y ese tipo de yerbas). Entonces, programar, podemos hacerlo en cualquier computadora y en cualquier lugar, incluso en papel. Más adelante haré públicos algunos apuntes que he confeccionado para mis estudiantes del colegio, que ahora están violando algunos derechos de autor (más que nada por las gráficas y dibujos que utilizo sin autorización). En ellos se explica qué son y como se utilizan los diagramas de flujo y como es la mecánica y lógica de la programación en general. Por ahora, salteemos todo eso y vamos a los bifes: si tenés Ubuntu o Debian podés intalar gcc utilizanco el programa de consola/terminal apt:

apt-get install gcc vim

Si tenés Ubuntu tenés que escribir “sudo” al principio y en Debian tenés que ser root. Si ejecutan ese código se instalará el gcc y el vim, que es un editor de texto plano para consola. No necesitan usar ese, pueden usar gedit (si tienen gnome), kate(si tienen kde), mousepad(si tenés xfce) o cualquier otro, como no sé cual es la interfaz gráfica que tendrá cada uno, decidí usar la consola (también existe nano, pero no me llevo bien con él). Entonces ejecutamos el vim:

vim holamundo.c

Al hacer esto, creamos el archivo “holamundo.c” y lo podremos editar apretando la letra Insert del teclado y verifiquen que diga que están en modo de inserción abajo:

holamundo.c en vim

Ahora, simplemente escribiremos el siguiente código:

#include<stdio.h> // Incluye la librería de Entrada/Salida Estandar (STandarD Input Output), es decir, el teclado y la pantalla

// Función principal del programa, arranca por acá:

int main(void)
{
// La función puts() escribe el texto entre comillas en la pantalla:

    puts("¡Hola, mundo!\n"); // '\n' <- eso indica un ENTER, para que el siguiente texto aparezca debajo

    return 0; // Se termina el programa devolviendo un 0 al Sistema Operativo para indicar que el programa terminó con éxito.
}

¡Presto! Ahora apretamos ESC y luego “:x” (dos puntos x) y damos ENTER para guardar las modificaciones y cerrar el vim.

Ahora, llega el momento de la magia, de compilar el programa, ejecutaremos el gcc de la siguiente manera:

gcc holamundo.c -o holamundo

Lo primero que tenemos que poner luego de gcc es el archivo.c que queremos compilar y luego con “-o” especificaremos el nombre de la salida (de no hacerlo el ejecutable se llamará “a.out”). En este caso se llama igual pero sin ninguna extensión. En Linux no es necesario indicar la extensión de los archivos, en Ventanas sería un “.exe”. Para ejecutarlo, primero debemos darle permisos de ejecución con chmod y luego simplemente ejecutarlo utilizando el path completo:

chmod +x+r holamundo

./holamundo

Si todo salió bien, deberíamos ver el texto en pantalla. El “./” significa la carpeta en donde estamos parados en la linea de comando. Imaginen que estaban en la carpeta “/home/usuario”, entonces el ‘.’ (punto) reemplaza eso, porque es más cómodo escribir “./programa” a “/home/usuario/programa”, a qué sí. Otro día hablamos de chmod y demás cosas básicas de Linux.

Les mando saludos, y sean felices.