Archivo del sitio

Programación de microcontroladores de la familia AVR en GNU/Linux (Debian/Ubuntu) Parte II

Nivel: Básico

¿Qué tengo que saber para este post?

—————————————

Como les prometí, en esta segunda parte les mostraré cómo hacer un primer programa. Voy a arrancar con el que tiene menos vueltas.

Programando con Code::Blocks

La interfaz

Bueno, teniendo todo instalado, lo único que necesitamos hacer es ejecutar el Code::Blocks, sí es la primera vez que lo utilizamos, aparecerá una venta notificando los compiladores que detectó automaticamente y el AVR GCC debe estar en esa lista como detectado.

Compiladores detectados

Compiladores detectados

Luego entramos a la pantalla principal, donde tendremos nuestra área de administración de proyectos a la izquierda, el editor a la derecha (ocupando la mayor parte del espacio), abajo el área de mensajes y las barras de tareas arriba (que son muchas, si lo pueden notar…).

Interfaz Code::Blocks

Interfaz Code::Blocks

Creando un proyecto nuevo

Para crear un proyecto nuevo, debemos hacer click en la carpeta azul que tenemos al centro de la pantalla y dice “Create a new project” (elemental, mi querdio Watson). Y en la ventana emergente debemos seleccionar “AVR project” y hacer clic en “Go”.

AVR Project

AVR Project

Ahora nos saltará la ventana emergente del wizard para hacer un proyecto, la primer ventana es siempre la que explica que nos va a guiar para generar un nuevo proyecto, yo siempre la tildo para que no aparezca la próxima vez e ir directamente a la selección de una carpeta para nuestro proyecto. Cuando decidamos un path, recuerden que por cada proyecto se genera una carpeta, es decir, conviene tener una carpeta llamada “~/proyectos” o “~/proyectos avr” y utilizar siempre ese mismo path. Porque cuando pongamos un nombre al proyecto, por ejemplo “ejemplo1” se creará una carpeta llamada “~/proyecto/ejemplo1/” donde estarán todos los archivos relacionados a nuestro proyecto. Si no entendieron, vean la imagen, que dice más que mil palabras:

Skip

Skip

ejemplo1

ejemplo1

Algo que es importante es que el nombre de su proyecto no puede contener espacios, porque al momento de compilar no funciona, así que si tienen ganas de poner espacios utilicen el guión bajo. Y recuerden de poner el path completo, yo puse “~/”, pero deberían poner “/home/usuario/”.

La siguiente ventana nos pregunta los “targets” a compilar. Yo nunca use, y nunca encontré la necesidad de compilar, la configuración de “Debug” y sólo tildo la de “Release”, pero si la quieren dejar, no hay problema. Pero tengan en cuenta que la que deben grabar en el micro es la versión “Release”, sino las cosas no van a funcionar del todo bien (sobre todo los delays). Y por último, la selección del micro que van a utilizar, el cristal y otras opciones. En general, salvo que ustedes deseen hacer algo especifico, pueden solamente seleccionar el micro y velocidad del oscilador. Yo voy a elegir el ATmega328P y como lo voy a usar con el oscilador interno que viene de fábrica fijado en 1Mhz, el valor de frecuencia de oscilador que voy a poner es “1000000UL”. Y para finalizar, hacer clic en el botón de “Finish”.

Target select

Target select

Chip select

Chip select

Escribiendo el programa

El Code::Blocks nos incluye dos archivos a nuestro proyecto, el “main.c” y el fuse.c. Ambos son templates, que tienen lo básico de cualquier programa, el header (cabecera necesaria) que agrega los registros necesarios para utilizar el micro seleccionado. El “fuse.c” tiene la configuración por defecto de los fuses del micro que estemos utilizando, para mi ejemplo, los voy a dejar como vienen. En caso de que este archivo no fuera generado automáticamente, les recomiendo que lo creen. Van a “File->New->File…” y eligen archivo “C/C++ source” y siguen los pasos para crear el archivo “fuse.c”, que por defecto contiene esto:

#include <avr/io.h>

FUSES = {
    .low = LFUSE_DEFAULT,
    .high = HFUSE_DEFAULT,
    .extended = EFUSE_DEFAULT
};

Como pueden ver, tiene todos los parámetros de fábrica. Oscilador interno de 8Mhz con un divisor por 8 (en el caso del ATmega328p, es decir 1Mhz de frecuencia de CPU (1000000UL). Esto es un define, que se utiliza, sobre todo, para la librería que nos provee los delays (“util/delay.h”). Si quieren saber sobre los defines para modificar los fuses, busquen acá, está en inglés pero bueno, tampoco es tan imposible de entender.

El template del “main.c” es muy básico:

/*
 */

#include <avr/io.h>

int main(void)
{

    // Insert code

    while(1)
    ;

    return 0;
}

Noten que, a pesar de que no hay un sistema operativo (supongo que es por alguna cuestión del gcc), la función main retorna un valor. Esto no tiene ningún sentido en la programación de microcontroladores, pero si no lo ponemos el gcc nos dará un warning. Y tampoco influye en mucho. Recuerden que todo programa con micros debe tener un loop infinito, para frenar al PC (o IP), y el dispositivo no tenga un comportamiento errático. Escribamos un código simple donde hagamos titilar un led, es decir cambiar el estado de algún pin de un puerto. Para ello, debemos conocer un poco de la familia AVR. En algún momento haré algún post sobre eso, pero ahora voy a asumir que tienen algún conocimiento básico y simplemente escribir el código. Noten que a la izquierda tienen  los archivos que pertenecen a su proyecto.

/*
 * Ejemplo1 AVR en GNU/Linux
 */

#include <avr/io.h>
#include <util/delay.h>

/*
    Inicialización del dispositivo:
*/
void InitDevice(void)
{
    DDRB=0x01;  // Declaro el PIN0 del PORTB como Salida
}

int main(void)
{
    // Inicializo el dispositivo
    InitDevice();

    // Bucle infinito
    while(1)
    {
        PORTB^=0x01;    // Alterno el valor del PIN0 del PORTB con una XOR
        _delay_ms(500); // Hago una demora de medio segundo
    }

    return 0;
}

Una vez que tenemos nuestro código, lo que resta es compilarlo, que lo haremos con el icono de la tuerca dorada que está en la barra de “compilación” y dice “Build”:

Compilando

Compilando

Si les salta el siguiente error de “/usr/include/features.h 323 fatal error: bits/predefs.h: No existe el fichero o el directorio”, no se asusten, es que hubo un pequeño cambio en Debian y es fácil de solucionar. Tienen que ir a “Settings->Compiler…”, allí seleccionar “GNU AVR GCC Compiler” e ir a la solapa que dice “Search directories” y más abajo a la solapa “Compiler” y borrar el ítem “/usr/include” haciendo clic y luego en el botón “Delete”. Lugo ir a la solapa que está al lado, la que dice “Linker” y borrar de la misma manera la que dice “/usr/lib” y confirmen las dos veces:

Corregir editando opciones del compilador

Corregir editando opciones del compilador

Borrar /usr/include

Borrar /usr/include

Borrar /usr/lib

Borrar /usr/lib

Clicqueamos Le damos clic al botón “Aceptar” más abajo y listo. Intentamos compilar de nuevo y si tuvimos éxito, debería verse algo así:

Éxito al compilar =)

Éxito al compilar =)

Simulación y debugging ( no hay… =( )

Ahora, debemos abarcar una pequeña cuestión, que desde mi punto de vista, es crucial. No encontré, ni encuentro aún, una herramienta que funcione bien y sea amigable para simular los microcontroladores de la familia AVR que sea GPL y/o gratuita. Así que, debemos volver a la edad de piedra, donde teníamos que grabar el firmware y verificar si todo funciona según lo planeado y crear diferentes estrategias, como prender un led cuando pasó por cierto punto del programa. Para eso diseñé una placa de desarrollo, pero sólo para micros de 28 pines de la familia AVR. Claro que pueden probarlo sobre un protoboard también, no hay que hacer tanto escándalo para prender un led.

Grabando el firmware al microcontrolador

Llego el momento de la verdad, como el Code::Blocks, a diferencia del plug-in del eclipse, no tiene un botón que invoque al avrdude, ni una configuración gráfica para éste, debemos hacerlo a mano. Hay dando vueltas por ahí algunas interfaces gráficas para el avrdude, pero ninguna que me convenza realmente, ni tan cómoda de instalar en GNU/Linux. Por eso iremos a configurar nuestra herramienta personalizada, para subir el firmware al microcontrolador. Yo les voy a dejar dos configuraciones muy simples (una con el dasa y la otro con el USBasp), que graban el firmware y los fuses high y low del micro, y evité a proposito el de lock y el extendido. El extendido porque no todos lo tienen y el de lock para evitar que dejen su micro inservible, porque asumo que recién están empezando con estos micros (como yo).

Vayamos al menú “Tools->Configure tools…” y le hacemos clic en “Add”. En mi caso le puse el nombre “Update w/dasa” y “Update w/USBasp” para diferenciar el programador que estoy utilizando. pero básicamente es igual, es copiar y pegar los códigos que aparecen abajo de los screenshots:

Añadir el llamado al avrdude

Añadir el llamado al avrdude

Llenamos los campos con los comandos que aparecen abajo

Llenamos los campos con los comandos que aparecen abajo

Para el programador dasa:

Update w/dasa
/usr/bin/avrdude
-c dasa -P /dev/ttyS0 -p $(MCU) -u -U flash:w:${TARGET_OUTPUT_BASENAME}.hex -U eeprom:w:${TARGET_OUTPUT_BASENAME}.eep -U lfuse:w:${TARGET_OUTPUT_BASENAME}.lfs -U hfuse:w:${TARGET_OUTPUT_BASENAME}.hfs
${PROJECT_DIR}/${TARGET_OUTPUT_DIR}

Para el programador USBasp:

Update w/USBasp
/usr/bin/avrdude
-c usbasp -P usb -p $(MCU) -u -U flash:w:${TARGET_OUTPUT_BASENAME}.hex -U eeprom:w:${TARGET_OUTPUT_BASENAME}.eep -U lfuse:w:${TARGET_OUTPUT_BASENAME}.lfs -U hfuse:w:${TARGET_OUTPUT_BASENAME}.hfs
${PROJECT_DIR}/${TARGET_OUTPUT_DIR}
Debería quedar así

Debería quedar así

Por último, llamamos al comando desde el menú “Tools->Update w/dasa” o el programador que tengan y grabamos el firmware al microcontrolador. Si todo salió bien deberíamos tener un retorno exitoso :).

Tools->Update w/dasa

Tools->Update w/dasa

¡Éxito! =)

¡Éxito! =)

Si quieren saber más sobre los parámetros que puede recibir el avrdude, hay muchos tutoriales dando vueltas por ahí. Recuerden que también pueden hacer desde una terminal:

man avrdude

Y así ver toda la documentación disponible y mucho más. Bueno, ya está todo configurado como para arrancar a programar y probar cosas. Y les dejo un vídeo del programa funcionando, de pésima calidad, tomado con la webcam (perdón, me da vergüenza mostrarlo), para que vean que sí funciona todo. Utilicé mi placa de desarrollo y el módulo de E/S, que consta de 8 leds y 8 pulsadores. Luego subiré ese diseño y los otros módulos también.

Cualquier duda que surja, o consejo que me quieran dar, será bienvenido. En el próximo post les mostraré como configurar y usar el plug-in de eclipse, para que elijan cual es su mejor opción. Saludos y hasta la próxima.

Programación de microcontroladores de la familia AVR en GNU/Linux (Debian/Ubuntu) Parte I

Nivel: Intermedio

¿Qué tengo que saber para este post?

  • Instalar software desde tu distribución de linux, acá sólo voy a hacer referencia sobre Debian/Ubuntu utilizando apt.
  • Copiar, crear y mover archivos como administrador del sistema (root) o con el comando sudo.
  • Programación de microcontroladores (no necesariamente de la familia AVR de Atmel).

—————————————

Bueno, sé que este es motivo de búsqueda para muchos. Yo mismo me he encontrado buscando ésto en google alguna vez, pero nadie lo concentraba en una forma amigable y bien detallada, como para un newbie en el asunto. Así que fui de a poco, leyendo en varios lados hasta que más o menos llegué a una forma amigable de programar AVRs en GNU/Linux y con todo bien organizado. Sin el ánimo de alardear, espero expresar a continuación, de la mejor manera posible, una sencilla guía como para dar los primeros pasos en este asunto.

Instalar el compilador

Para instalar el compilador, librerías y demás cosas esenciales para programar microcontroladores de la familia AVR, lo que debemos hacer es sencillamente poner lo siguiente en una terminal como root o si estamos en un sistema sin root (como Ubuntu) poner el “sudo” delante:

apt-get install gcc-avr avr-libc avrdude binutils-avr

Con eso instalamos lo necesario, el avrdude es el programa que nos permitirá, luego, bajar nuestro archivos *.hex al microcontrolador. Para esto debemos darle permisos de usuario a los puertos (USB, serie o paralelo) de cada dispositivo, pero eso lo haremos cuando llegue el momento.

Eligiendo un IDE (o no…)

Lo siguiente sería elegir un entorno de desarrollo que nos facilite la vida al momento de programar. En lo personal, he probado con 2 de ellos, el Code::Blocks que recientemente ha sacado una nueva versión estable, y el plug-in de AVR para la plataforma eclipse. Claro que también podemos editar nuestro código en el vim o nuestro editor favorito y compilarlo a manopla mano desde la consola, pero la realidad es que no vamos a ser más machos por ello y, sinceramente, no es muy amigable para  los poco conocedores. Además, tener una herramienta que nos agiliza el trabajo es más que tentador y nadie nos va a llamar put@s poco hombre o poco mujer (ejem) por usar un entorno de desarrollo.

Volvamos a los IDEs, tanto el Code::Blocks como el plug-in para el eclipse tienen sus pros y sus contras. El plug-in del eclipse tiene un par de bugs feos, como definir y “desdefinir” cosas en la linea del comando del compilador y a veces no toma los cambios hasta después de compilar, dando warnings y otras cuestiones como generar mal el makefile… en fin, tiene sus quilombos problemas, pero se deja usar a pesar de todo con un par de fixeos y parcheos trucos que se pueden usar para solventar estos problemas. Lo bueno de este plug-in son 2 cosas:

  • Nos agrega una solapa en la parte de abajo llamada “AVR device explorer” que nos muestra el nombre de los registros de los micros soportados por el compilador y los defines de los vectores de interrupción (lo cual está bueno si ya conocemos lo que hacen y cómo se utilizan).
  • Nos agrega un botón que llama al avrdude y baja el firmware directo al micro, y además podemos configurar los parámetros y fuses de forma gráfica.

La desventaja del Code::Blocks es que no tiene esa dos ventajas ya mencionadas que tiene el eclipse, aunque el botón para bajar el firmware lo podemos configurar. Y sus ventajas son:

  • El editor es excelente.
  • Tiene varios plug-ins que están muy buenos, incluyendo uno que invoca a doxygen, que es una herramienta para generar documentación, muy útil y utilizada.

Ambos son multiplataforma, es decir que en Windows funcionan de la misma manera, nada más que es necesario instalar el WinAVR, que es lo que sería el primer paso de esta guía, “Instalar el compilador”, instala también las librerías y el avrdude (pero como acá nos concentramos en GNU/Linux, dejemos a Ventanas a un lado).

Instalar Code::Blocks en Debian/Ubuntu

Primero debemos tener permisos de administrador, y luego editar el archivo “/etc/apt/sources.list” y agregar al final del archivo lo siguiente (para la versión estable):

# Code::Blocks
deb http://apt.jenslody.de/stable stable main
deb-src http://apt.jenslody.de/stable stable main

Para la versión testing solo deben reemplazar “testing” donde dice “stable” y listo y luego instalar el key-ring para que no les salte el warning de que hay paquetes que no están firmados, que lo hacen instalando el siguiente paquete como root:

apt-get update
apt-get install jens-lody-debian-keyring

Y ahora a instalar el Code::Blocks y otros paquetes que necesitamos para que no salten errores al compilar:

apt-get install codeblocks codeblocks-additional srecord

Y listo, eso sería lo necesario para instalar todo lo que necesitamos, la primera vez que lo ejecutemos nos mostrará una lista de los compiladores que detectó. El AVR GCC debería estar en esa lista. Para más detalles vean la página oficial.

Instalar eclipse y el plug-in avr

Bueno, básicamente es parecido. primero debemos instalar eclipse:

apt-get install eclipse-cdt

Una vez que lo tenemos instalado, debemos seguir las instrucciones de la página oficial del plug-in que tienen screenshots y es muy amigable, a pesar de estar en inglés 😉 y con eso ya estamos.

Configurando el acceso a los puertos a nivel usuario para el avrdude

Antes que nada, debemos hacernos (de) un programador. Muchos tienen la loca idea de arrancar armándose un programador USB, pero lamentablemente, ese nunca es el primer paso. Un programador USB tiene como “defecto” que necesita de un microcontrolador con un firmware que capture los datos del puerto y los traduzca a las señales de programación de los AVR (que es un protocolo muy sencillo: SPI). Así que, si tenías en mente hacerte un programador USB, olvídate, salvo que tengas un amigo que tenga un programador para prestarte. Mi consejo es:

  • Comprar un USBasp, que es un programador muy barato que vas a encontrar en ebay por pocos dolares.
  • Hacete el programador DASA, que se hace con una ficha DB9 hembra, 3 resistencias, 3 zeners, un cable plano de 6 vías y un IDC6 hembra. Y luego, con un ATmega8 hacete el USBasp.

El USBasp es el que le hago armar a mis estudiantes del colegio donde doy clase, porque programa también la familia 8051 de Atmel, luego subiré el circuito. Una vez que tengamos nuestro programador, debemos darle permisos a nuestro usuario para manejar el puerto deseado. Para eso, lo único que hay que hacer es agregar a nuestro usuario al grupo “dialout”. Verifiquen primero si su usuario ya pertenece a tal grupo escribiendo en su terminal el comando:

groups

Si dialout aparece en la lista, no deben hacer nada, sino como root deben escribir el siguiente comando:

addgroup <tu_usuario_aqui> dialout

Una vez que formemos parte de este grupo, podremos usar el programador DASA sin ningún problema a nivel usuario (si tienen algún problema, sólo reinicien su equipo). Pero si quieren usar el USBasp, hagan el paso anterior de todas formas. Ya que les voy a pasar un archivo que enumera los programadores USB más utilizados (por no decir todos ellos) y que deben agregar a la carpeta “/etc/udev/rules.d/”. También pueden crear el archivo como root, yo lo llamé “99-usb-avr-programmers.rules”, puede tener el nombre que quieran, pero el contenido debe ser el siguiente:

# Programmers for avrdude

ATTR{idVendor}=="03eb", ATTR{idProduct}=="2104", GROUP="dialout", MODE="0660" # AVRISP mkII
ATTR{idVendor}=="03eb", ATTR{idProduct}=="2107", GROUP="dialout", MODE="0660" # AVR-Dragon
ATTR{idVendor}=="03eb", ATTR{idProduct}=="2103", GROUP="dialout", MODE="0660" # JTAG ICE mkII
ATTR{idVendor}=="03eb", ATTR{idProduct}=="2106", GROUP="dialout", MODE="0660" # STK600
ATTR{idVendor}=="16c0", ATTR{idProduct}=="05dc", GROUP="dialout", MODE="0660" # USBASP von www.fischl.de
ATTR{idVendor}=="03eb", ATTR{idProduct}=="2ffa", GROUP="dialout", MODE="0660" # AT90USB
ATTR{idVendor}=="10c4", ATTR{idProduct}=="ea60", GROUP="dialout", MODE="0660" # AVR910
ATTR{idVendor}=="03eb", ATTR{idProduct}=="2105", GROUP="dialout", MODE="0660" # AVR ONE
ATTR{idVendor}=="03eb", ATTR{idProduct}=="210d", GROUP="dialout", MODE="0660" # Atmel XPLAIN CDC Gateway
ATTR{idVendor}=="03eb", ATTR{idProduct}=="2ffb", GROUP="dialout", MODE="0660" # AT90USB AVR DFU bootloader
ATTR{idVendor}=="1781", ATTR{idProduct}=="0c9f", GROUP="dialout", MODE="0660" # adafruit usbtiny

En algún lado lo verán diferente, pero así es como funciona en Debian y supongo que así también debería de funcionar en Ubuntu, si alguien me lo confirma, sería buenísimo.

NOTA: Recuerden que todas las PCs de escritorio, o mejor dicho, todas las motherboards de escritorio, siguen trayendo el puerto serie, históricamente conocido como COM1. Este suele estar en un conector del tipo IDC10. Donde el PIN1 del IDC10 es el que va soldado al PIN1 del DB9 y así el 2 con el 2, el 3 con el 3 y el 10 del IDC10 no se conecta. En general es así, pero verifiquen con el manual de su motherboard. Recuerden que la ficha DB9 debería ser MACHO cuando la compren ;).

Ya tenemos todas las herramientas básicas para empezar nuestro proyectos AVR en GNU/Linux. En la siguiente entrega les mostraré un primer programa (ahora estoy medio cansado para seguir). Les prometo screenshots, fotos y tal vez algún vídeo, tanto con Code::Blocks como con eclipse, enseñándoles un par de arreglos para que la experiencia con eclipse no sea tan decepcionante.

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.