Archivo de la categoría: Electrónica

Todos los temas relacionados con la electrónica.

Algunos tips útiles para programar un micro AVR con GCC

Nivel: Intermedio

¿Qué tengo que saber para este post?

  • Estructuras de microprocesadores/microcontroladores.
  • Mapeo de memoria.
  • Programación en C/C++, programación orientada a objetos (POO), uso de punteros.

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

Soy culpable, no he escrito en mucho tiempo en este pequeño espacio de la red, pero les traigo varios tips que creo podrían ser de utilidad. Como siempre, espero poder explicar cada una de estas pequeñas soluciones.

A medida que avanza este post voy a ir describiendo un pequeño programa que hice en C++ para un ATmega8 (simplemente por jugar, no tiene ningún fin práctico demasiado útil). En este programa utilizo una tabla que contiene los valores necesarios para mostrar los números del 0 (cero) al 9 (nueve) en un display 7 segmentos, incluyendo un onceavo valor que es un código de error (enciende el punto). Para esto, hice una clase “SevenSeg” para crear un objeto display al cual por medio de un método que utiliza una tabla almacenada en memoria de programa.

Veamos qué pasa:

Pasar un registro como parámetro

Para este primer tip no hace falta mucho, sólo saber que los registros de funciones especiales (SFRs) son un espacio en memoria RAM que se utiliza como interfaz entre el procesador y el hardware que conforma nuestro µC. Por ejemplo, para poder poner un pin del µC en 1 ó 0 debemos escribir en una dirección de memoria especial (en los AVR, se llama PORTx), claro que para eso hay otra dirección en memoria que sirve para configurar si ese pin va a ser entrada o salida (DDRx).

Ahora, si nos ponemos a explorar, veremos que en la librería del avr-libc para declarar un registro de los SFRs hay una serie de macros (una atrás de la otra), pero al fin y al cabo no es nada más que una dirección de memoria, manejada como un puntero a un “volatile uint8_t”. Recuerden que un uint8_t es un typedef de un unsigned char y volatile es para que el compilador no la optimice, es decir, que le cambie la dirección al registro.

Por ende, si necesitamos pasar como parámetro a una función (o a este caso al constructor de mi clase), nos conviene utilizar un puntero o (cómo nos permite hacer en C++) una referencia a la dirección de un volatile uint8_t.

class SevenSeg
{
    // (...)
    public:
    SevenSeg(volatile uint8_t &ddrPort,volatile uint8_t &port,uint8_t type = COM_CAT);
    // (...)
}

Como habrán notado, no me voy a detener mucho en la implementación de la clase que cree, de todas formas se las voy a dejar en un link a mi GoogleDrive, como siempre. Observen que en C++ podemos utilizar el & en el parámetro, directamente, cosa que en C no es permitido. En C debemos declarar un puntero (volatile uint8_t *) y cuando llamamos a nuestra función agregar el &. A continuación les mostraré el main para ponerlos en tema de lo que hace el código.

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

int main(void)
{
    // Creo el objeto display, ubicado en el Puerto D del micro
    SevenSeg display(DDRD,PORTD);

    while(1)
    {
        // Muestro los números del 0 al 9
        for(uint8_t i=0;i<10;i++)
        {
            display.put(i);
            _delay_ms(500);
        }
    }
    return 0;

Como verán, el código es bastante sencillo (es la idea), utilizo el método put() del objeto display para mostrar el número que quiero. En definitiva, no hay mucho más que pueda aportar. Recuerden que si el código está en C++, los fuentes son “.cpp” (C Plus Plus), y lo demás es igual que siempre. Pasemos al siguiente tópico.

Guardar tablas en la memoria de programa

Este es un recurso que, para los que hace rato que venimos programando microcontroladores (µC), nos es muy útil para almacenar tablas de valores constantes medianamente largas (o no tan largas). ¿Por qué almacenar datos en la memoria de programa? Bueno, muy simple, agarre la hoja da datos (datasheet) de su µC favorito y compare la memoria de programa (usualmente llamada FLASH) y la memoria de datos (RAM/SRAM). Por lo general la memoria de programa suele ser mucho mas grande que la RAM. Está bien, ya sé que en los AVR hay KBs de memoria de datos y muy pocas veces usamos tanto, sin embargo hay parte de la RAM que usamos sin saberlo, por ejemplo para el STACK. El STACK es una memoria tipo LIFO que almacena las direcciones de los saltos que pegamos (llamadas a función), para luego retornar al lugar donde estábamos. Claro que los que programamos en C/C++, ésto no nos preocupa demasiado, pero es bueno saber que existe, porque para aplicaciones grandes la optimización optimizar el uso de la memoria se va tornando cada vez más importante.

Analicemos un poco las dificultades de programar un µC en C/C++. Originalmente el lenguaje C nació como un lenguaje de programación para sistemas operativos (UNIX), y no hace falta decir que las PCs son en su mayoría de arquitectura Von Neumann. ¿Y qué tiene que ver eso? Mucho. Resulta que la mayoría de los µCs son de arquitectura Harvard o Harvard modificado y los AVR no difieren. La principal diferencia está en que en las arquitecturas Von Neumann la memoria de programa y la memoria de datos (vulgarmente ROM y RAM) comparten los buses de datos, direcciones y control. Mientras que en las arquitecturas del tipo Harvard las memorias tienen buses diferentes, es decir que no son direccionadas por el mismo bus. La ventaja de esto es que se pueden acceder a las dos memorias al mismo tiempo ganando en tiempo de proceso (lo cual es muy deseable en un sistema embebido).

Ahora pensemos que son los datos (o variables) en nuestros programas. Bueno, un profesor de la facultad nos comentaba que todo son datos y direcciones, ¿y saben qué? Es cierto. Sin ir más lejos, una variable no es más que una dirección en memoria en donde encontrar los datos. ¿Ahora ven el problema? Claro, como los buses de direcciones en una arquitectura Harvard son distintos no podemos direccionar esa memoria de la misma manera. ¿Cómo solucionan estas cosas los compiladores de C/C++ para µCs? Bueno, haciendo artilugios. En general, los programadores de compiladores nos proveen bytes adicionales para indicarle que queremos direccionar la memoria de programa y no la de datos. El avr-gcc no se salva de esto.

Basta de preámbulos, ¿cómo lo hacemos? Bien, para almacenar una variable en la memoria de programa hay un atributo especial en avr-gcc, pero para evitar al usuario final recordar difíciles palabras hay macros y defines que nos provee la librería. Éstas están en el header <avr/pgmspace.h>. Para declarar este atributo se hace con el define PROGMEM:

#include <avr/pgmspace.h>

/*
    Ubicación de los pines en el puerto seleccionado
    (puede variar, según la implementación)
*/
               // Pins: 7654|3210
                     // .gfe|dcba
#define CERO    0x3F // 0011|1111b
#define UNO     0x06 // 0000|0110b
#define DOS     0x5B // 0101|1011b
#define TRES    0x4F // 0100|1111b
#define CUATRO  0x66 // 0110|0110b
#define CINCO   0x6D // 0110|1101b
#define SEIS    0x7D // 0111|1101b
#define SIETE   0x07 // 0000|0111b
#define OCHO    0x7F // 0111|1111b
#define NUEVE   0x6F // 0110|1111b
#define ERROR   0x80 // 1000|0000b

static const uint8_t tabla[11] PROGMEM = {
    CERO,UNO,DOS,TRES,CUATRO,CINCO,SEIS,SIETE,OCHO,NUEVE,ERROR
};

Hagamos una pequeña aclaración, la memoria de programa de los AVR es de 16-bits, por ende, por más que pongamos uint8_t o unsigned char, vamos a estar desperdiciando 8-bits. Pero esto a modo informativo.

Bien, con eso nuestra tabla está en memoria de programa. Ahora nos queda acceder a ella. Si estabas pensando que era tan fácil como usar el array así nomás, te equivocaste. Veamos:

Acceso a una tabla almacenada en memoria

Para acceder a los datos que guardamos en memoria vamos a utilizar una macro que está en el header <avr/pgmspace.h>, esta macro es una pequeña rutina de assembler, la cual nos ocupará más de lo que esperábamos en la memoria de programa, pero aún así, se supone que debería ser mejor que sacrificar la RAM, hasta cierto punto. Lo ideal sería hacernos los machos y estudiar el assembler generado por el compilador, para entender mejor lo que está pasando y verificar que nuestro sacrificio de memoria de programa no sea muy excesivo.

Esta macro es “pgm_read_byte()” la cual recibe como parámetro la dirección del byte que queremos leer, por ende, en código se vería algo así:

variable = pgm_read_byte(&(tabla[<número>]))

Así se debería ver, estamos pasando la dirección del “número” de elemento a la macro para poder almacenarla en RAM en alguna “variable”.

No se termina acá… apenas empieza

En el header hay muchas funciones para manejar strings guardados en memoria de programa, agregando un “_P” a las funciones “estándar” para, por ejemplo, comparar strings y demás conocidas. Todo lo que aquí vieron lo saqué de esta página. Es muy útil, échenle varios vistazos, guárdenla en favoritos. Les voy a dejar la declaración completa de la clase y su implementación ACÁ. También está el main.cpp para que lo puedan compilar y probar Uds. mismos.

Espero poder aportar mucho más que esto en los próximos meses, estoy con algunos proyectos en el colegio donde doy clases y espero que rindan frutos.

Sean felices y hasta la próxima.

Introducción a los microcontroladores (uCs)

Nivel: Básico

¿Qué tengo que saber para este post?

  • Conocimientos básico de electricidad y electrónica, por ejemplo, cómo encender un LED.
  • Algo de computadoras, una noción básica de qué y cómo es una memoria.

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

Sí, ya sé que hace rato que no escribo nada, pero es porque estoy ocupado con el trabajo y sobre todo generando material. Muchas cosas pasan por mi vida, pero de eso no se trata este blog. Les traigo hoy una presentación hecha en LibreOffice Impress (algo así como una presentación de diapositivas en “PuntoDePoder“), el cual me ocupé y demoré mi tiempo en animar para dar clases de una manera más copada didáctica. No voy a hacer mucho preámbulo al respecto, que mi trabajo hable por sí mismo.

Lo que sí, también subí una versión en video a youtube, para que tengan una idea de que decir a medida que pasan las diapositivas.

Descargá la versión original de la presentación ACÁ.

Licencia Creative Commons
Introducción a μCs por Matías Sebastián Ávalos se encuentra bajo una Licencia Creative Commons Atribución-CompartirIgual 3.0 Unported.

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.