Comenzando con la programación.

Una vez que la electrónica ha sido realizada lo primero es verificar su correcto funcionamiento, para ello se debe empezar a programar el microcontrolador, por lo que necesitaremos un programador serie para pasar el programa que realicemos en el pc al microcontrolador, un entorno de programación  y en este caso un compilador ya que se va a programar en C. Menos el programador que está entre $10-$40 lo demás es gratis. 

El entorno de programación lo podemos obtener de manera gratuita de la página de Atmel, fabricante del microcontrolador que vamos a usar, se puede descargar de: http://www.atmel.com/avrstudio/ donde en software seleccionamos el AVR Studio de algo más de 100 Megas (la versión a día de hoy que está en la página :
AVR Studio 4.18 (build 684)  (116 MB, updated 11/09)), para ello antes debemos de registrarnos rellenando el formulario que sale y se pasa a la descarga e instalación.

AVR Studio.


A la hora de elegir un compilador hay varias opciones, de pago y gratuitas, para el micro que tenemos de Atmel hay un compilador gratuito y bueno, con una comunidad bastante grande que lo usa donde resolver dudas y de la que leer documentación, por lo que no hay necesidad de irse a uno de pago. Además el precio de los compiladores suele ser alto y sería varias veces el coste del robot que vamos a hacer, por lo que no tiene sentido usar una alternativa que no sea libre. El compilador es el WinAVR y se puede obtener en:  http://winavr.sourceforge.net/download.html. Una vez descargado e instalado vemos la siguiente pantalla cuando se crea un proyecto nuevo.



Donde se puede seleccionar entre un proyecto en ensamblador y un proyecto en lenguaje c (AVR GCC). Para este robot en principio sólo se va  a usar c.

Como programador serie podemos elegir la opción de pololu: http://www.pololu.com/catalog/product/1302 al comprar la baby orangutan elegimos la opción con programador por $12 más, y nos quitamos el problema de tener que comprarlo por otro lado. Yo en su día compre el oficial de Atmel, que es mucho más caro que los $12 de la opción anterior. Este programador lo usaremos para conectar el robot (mediante el conector de la baby orangutan para tal fin) con el pc y descargar en el micro el programa que hemos realizado y que gobernará el funcionamiento del robot.

Programador conectado al robot. El cable ha de conectarse en ese sentido.


Una alternativa a AVR Studio sería el entorno de programación de los famosos Arduinos que creo haber leído que son compatibles con estas placas o algunas versiones de ellas. Puestos a aprender, la alternativa del AVR Studio desde mi punto de vista es mejor que arduino, ya que tendremos total libertad sobre el micro y no tenemos que adaptarnos a lo que haya hecho en Arduino. Programar en el entorno de Arduino es demasiado sencillo, el grado de abstracción es muy alto y está pensado para los que buscan una forma sencilla y rápida de hacer las cosas, y para aprender a usar un microcontrolador sin meternos en ensamblador la mejor opción desde mi punto de vista es AVR Studio + WinAVR.

Lo primero es probar la placa Baby Orangutan cargando un programa y viendo su funcionamiento, para ello podemos descargar un ejemplo de la página de pololu que hace parpadear el led rojo de la baby orangutan una vez por segundo: http://www.pololu.com/file/0J190/BlinkLED_m328.zip . Lo descargamos y sacamos la carpeta y encontramos los siguiente:



Si pinchamos dos veces sobre BlinkLED se nos abre el entorno del AVR Studio con el proyecto que hemos bajado.


Si nos vamos a Project --> Configuration Options, se nos abre la siguiente ventana.


En ella seleccionamos el microcontrolador que tenemos en nuestra placa, en el caso de la baby orangutan el atmega328p y en Optimization una opción distinta de la -O0, ya que algunas de las librerias que vamos a utilizar requieren de las opciones de optimización habilitadas, la frecuencia del reloj no es necesario indicarla en esta ventana, ya está puesto en el porgrama. Una vez hecho lo anterior aceptamos volviendo a la pantalla principal, pulsamos F7 para compilar el programa, si todo ha ido bien en el cuadro inferior de message podemos leer algo como:

AVR Memory Usage
----------------
Device: atmega328p
Program:     242 bytes (0.7% Full)
(.text + .data + .bootloader)
Data:          0 bytes (0.0% Full)
(.data + .bss + .noinit)
Build succeeded with 0 Warnings...

Donde podemos ver la cantidad de memoria de programa y datos utilizada, y también si hay algún Warning. Los warning lo mejor es siempre solucionarlos (excepto que sea una variable sin usar) porque siempre acaban dando problemas y luego en el circuito real van a provocar un funcionamiento incorrecto.

Enchufamos el programador al PC y al robot, en la conexión cable robot debemos fijarnos en la polaridad del conector (ya que lo podemos conectar al revés) y damos al botón de conectar con el programador.

Arriba el botón de un circuito integrado que pone Con, nos abre la ventana para seleccionar el programdor que vamos a utilizar, en mi caso el AVRISP mkII, el de pololu creo que también es este ya que será un clónico del AVRISP, y le damos a Connect (si ya lo tenemos seleccionados damos al botón de AVR al lado de Con y nos saltamos este paso). Se nos abre la siguiente ventana.


En esta pestaña seleccionamos el ATmega328P y pasamos a la pestaña de Program.


En Program podemos programar, leer y verificar las distintas memorias del microcontrolador. La memoria donde hay que grabar el programa que hemos hecho en el PC es en la Flash, y el fichero a grabar es un .hex que encontraremos en la carpeta inicial que se descargó, en la subcarpeta default, fichero BlinkLED.hex, cada vez que le damos a F7 se crea este fichero. Por lo que seleccionamos el fichero .hex y le damos a Program.

Si todo ha ido bien nos lo indica en la parte inferior de la ventana y el led rojo de la baby orangutan se encenderá una vez por segundo.


En la pantalla de Fuses tenemos la opción para seleccionar la fuente de reloj externa o interna, en este caso se está usando un cristal externo.

Aunque aquí no hay que tocar nada, no debemos confundir external crystal con externar clock, un cristal no es un oscilador que proporciona una señal de reloj al microcontrolador, con el cristal junto a una electrónica dentro del microcontrolador se crea la señal de reloj. Si seleccionamos external clock por error pues no podremos deshacer lo hecho y volver a programar la placa, para poder recuperarla habría que generar una señal de reloj con un dispositivo externo en el pin del microcontrolador correspondiente, y si no se sabe hacer ésto o no se tienen los medios pues ya estamos buscando otra baby orangutan.

Hay muchas más pestañas y opciones, sólo comento los pasos principales para hacer funcionar el robot ya que comentar todo sería demasiado largo y ya hay mucha información en internet, además que también llevo unos pocos meses con estos micros y no conozco demasiado de ellos.

El programa que hemos grabado es el siguiente:

// F_CPU tells util/delay.h our clock frequency
//#define F_CPU 8000000UL    // Orangutan frequency (8MHz)
#define F_CPU 20000000UL    // Baby Orangutan frequency (20MHz)
#include <avr/io.h>
#include <util/delay.h>

void delayms( uint16_t millis ) {
    while ( millis ) {
        _delay_ms( 1 );
        millis--;
    }
}

int main( void ) {
    DDRD |= 1 << DDD1;                // set LED pin PD1 to output
    while ( 1 ) {
        PORTD &= ~( 1 << PORTD1 );    // LED off
        delayms( 900 );                // delay 900 ms
        PORTD |= 1 << PORTD1;         // LED on
        delayms( 100 );                // delay 100 ms
    }
    return 0;
}

Se comienza con los defines e includes, en #define F_CPU 20000000UL indicamos la frecuencia de reloj, debe ir antes de los #include que necesitan conocer la frecuencia de reloj para funcionar, por ejemplo si lo ponemos después del #include <util/delay.h> pues nos dará un error.

Se declara la función que vamos a utilizar para generar los retrasos y se entra en la función principal, donde se comienza configurando el pin PD1 como salida en su registro correspondiente, y se entra en el bucle principal donde se enciende el led durante 100 ms cada segundo.

Hace unos meses me puse con los AVR a través de un Arduino Mega, para montar luego un AVR en los velocistas MiniZ, y todos los avances con estos micros los voy documentando aquí, aún me falta meterme con la UART, el SPI y PWM (tengo que hacer los 3 en breve) para tener más o menos documentados los principales periféricos que se usan en robótica. El código es bastante mejorable ya que sólo voy leyendo del datasheet, probando y programando lo que leo para verificar que funciona, además programar no es lo mio para que nos vamos a engañar XD.

Una vez que hemos comprobado que la baby orangutan que nos han mandado se puede programar y funciona, lo siguiente es verificar el funcionamiento de la electrónica que se ha creado para el robot mediante un programa que lea las entradas, muevas los motores, encienda los leds, etc.. Sobre el proyecto que tenemos abierto en el AVR Studio escribimos nuestro "hola mundo" o BlinkLED propio, para ellos borramos todo menos las siguientes líneas:

#define F_CPU 20000000UL    // Baby Orangutan frequency (20MHz)
#include <avr/io.h>
#include <util/delay.h>

int main( void ) {

    while ( 1 ) {

    }
    return 0;
}

Estas van en todos los programas que hagamos, el include con los valores de los registros del micro, la función principal y el bucle principal y a partir de aquí comenzamos con el "hola mundo". Un programa sencillo para probar el hardware es el siguiente:


#define F_CPU 20000000UL    // Baby Orangutan frequency (20MHz)
#include <avr/io.h>
#include <util/delay.h>

//Control de motores.Salidas.
#define M1A PORTD5
#define M2A PORTD3
#define M1B PORTD6
#define M2B PORTB3
//Leds. Salidas.
#define LEDP PORTD1
#define    LEDV PORTB1
#define    LEDR PORTD0
//Interruptores. Entradas.
#define SW1    PORTB0
#define SW2 PORTB2

void inicializar_puertos(void);
void reset(void);

int main( void )
{
    char leer_pin=0;

    inicializar_puertos();
    reset();

    PORTD |= ((1<<M1A)|(1<<M2A));

    while ( 1 )
    {
        leer_pin = PINB & (1<<SW1);
        if(leer_pin)
        {
            PORTD &= ~(1<<LEDR);
            PORTD |= (1<<M1B);
        }
        else
        {
            PORTD |= (1<<LEDR);
            PORTD &= ~(1<<M1B);
        }

        leer_pin = PINB & (1<<SW2);
        if(leer_pin)
        {
            PORTB &= ~(1<<LEDV);
            PORTB |= (1<<M2B);
        }
        else
        {
            PORTB |= (1<<LEDV);
            PORTB &= ~(1<<M2B);
        }           
    }
    return 0;
}

void inicializar_puertos(void)
{
   DDRD=0x6A;     //0110 1011  0,1,3,5,6 Salidas
   PORTD=0x00;  
   DDRB=0x0A;     //0000 1010  1,3 Salidas
   PORTB=0x00; 
}

void reset(void)
{
    PORTD |= (1<<LEDP); //Encendemos Led en Baby Orangutan.
    _delay_ms(300);
    PORTD &= ~(1<<LEDP);//Apagamos Led en Baby Orangutan.
    _delay_ms(300);
    PORTD |= (1<<LEDP);
    _delay_ms(300);
    PORTD &= ~(1<<LEDP);
    _delay_ms(300);
    PORTD |= (1<<LEDP);
    _delay_ms(300);
    PORTD &= ~(1<<LEDP);
}


Se comienza dando a los pines del pic un nombre más fácil de identificar que PORTD5, PORTB3, etc..
//Control de motores.Salidas.
#define M1A PORTD5
#define M2A PORTD3
#define M1B PORTD6
#define M2B PORTB3
//Leds. Salidas.
#define LEDP PORTD1
#define    LEDV PORTB1
#define    LEDR PORTD0
//Interruptores. Entradas.
#define SW1    PORTB0
#define SW2 PORTB2


Tenemos 4 salidas del microcontrolador que van al puente en h, con los que controlaremos el sentido de giro y la velocidad de los motores. Tres salidas más que se encargan de enceder diodos leds para indicar diferentes estados del robot, los que resultan muy útiles a la hora de programar para detectar errores, y 2 interruptores que son entradas para comunicarnos con el robot. Faltan los 8 sensores y el puerto I2C pero esos para más adelante.

void inicializar_puertos(void)
{
   DDRD=0x6A;     //0110 1011  0,1,3,5,6 Salidas
   PORTD=0x00;  
   DDRB=0x0A;     //0000 1010  1,3 Salidas
   PORTB=0x00; 
}

void reset(void)
{
    PORTD |= (1<<LEDP); //Encendemos Led en Baby Orangutan.
    _delay_ms(300);
    PORTD &= ~(1<<LEDP);//Apagamos Led en Baby Orangutan.
    _delay_ms(300);
    PORTD |= (1<<LEDP);
    _delay_ms(300);
    PORTD &= ~(1<<LEDP);
    _delay_ms(300);
    PORTD |= (1<<LEDP);
    _delay_ms(300);
    PORTD &= ~(1<<LEDP);
}

Declaramos y definimos las funciones que se van a utilizar. La primera sirve para inicializar los pines del microcontrolador como entradas o salidas, si no tocamos nada por defecto los registros que controlar los pines están a 0 y son entradas, por lo que habrá que poner un uno en el bit correspondiente del registro donde tengamos un pin que es salida, más sobre este tema aquí. La otra función sirve para apagar y encender un led 3 veces, se usará para detectar posibles reseteos en el robot causado por diferentes causas, al principio del programa el led rojo de la placa de la baby orangutan se enciende tres veces, indicando que se ha iniciado el programa. Si por cualquier motivo no deseado el microcontrolador se resetea lo detectaremos mediante este led.

int main( void )
{
    char leer_pin=0;

    inicializar_puertos();
    reset();

    PORTD |= ((1<<M1A)|(1<<M2A));

    while ( 1 )
    {
            !!Bucle principal.
    }
    return 0;
}

Entramos en la función principal, se declara una variable local que se usará en la función, se llaman a las funciones de configuración de los puertos y de reseteo, se establecen un par de salidas a 1 y se entra en el bucle principal, un bucle infinito del que el microcontrolador no saldrá, de tal forma que siempre esté ejecutando el código que le indiquemos en el búcle. Dentro del bucle lo que haremos para comprobar el funcionamiento de una primera parte de la electrónica, es leer los dos interruptores, y en función de sus valores encender y apagar los motores y los leds.

Para saber como debemos controlar los motores nos fijamos en la siguiente tabla que se encuentra en la documentación de la baby orangutan.


Cuando los pines PD5 y PD6 (M1A y M1B) del microcontrolador, configurados anteriormente como salida, se encuentren a nivel alto el motor está parado. Cuando tengan valores distintos el motor girará en un sentido o en el inverso, dependiendo de cuál de los dos pines sea el 1 ó el 0. Cuando los dos están a cero el motor se encuentra libre, es decir puede girar sin nada que lo retenga, no tenemos freno. Comutando entre la opción de brake y el giro en un sentido, se controla la velocidad del motor, teniendo la máxima velocidad cuando el motor está en "foward" o "reverse" de manera constante. M2A y M2B de la misma forma controlarán el otro motor.

    PORTD |= ((1<<M1A)|(1<<M2A));

    while ( 1 )
    {
        leer_pin = PINB & (1<<SW1);
        if(leer_pin)
        {
            PORTD &= ~(1<<LEDR);
            PORTD |= (1<<M1B);
        }
        else
        {
            PORTD |= (1<<LEDR);
            PORTD &= ~(1<<M1B);
        }

        leer_pin = PINB & (1<<SW2);
        if(leer_pin)
        {
            PORTB &= ~(1<<LEDV);
            PORTB |= (1<<M2B);
        }
        else
        {
            PORTB |= (1<<LEDV);
            PORTB &= ~(1<<M2B);
        } 

Según la tabla anterior comenzamos fijando un pin de ambos motores a 1 y así  va a permanecer durante todo el programa, cuando el otro pin que va al motor en su salida tenga un cero el motor correspondiente se mueve y cuando su salida sea 1 se para. Se comienza leyendo un interruptor, para ello se hace una AND con la posición del bit del registro que nos interesa con el registro donde se guardan los valores de la entrada del puerto. Si el interruptor está apagado la AND nos devuelve un uno (estamos usando una resistencia de pull-up) por lo que pasamos a poner el pin que controla el motor a 1, para pararlo, y al mismo tiempo apagamos un led que sólo estará encendido cuando el motor esté en marcha. Cuando el interruptor esté encendido asignamos a leer_pin un valor cero, lo que hace que pasemos a encender el led y poner un cero en el pin del motor, para que éste se mueva.

Video del funcionamiento.


El código es muy sencillo y sólo se ponen a uno o a cero las salidas en función de las entradas para comprobar el funcionamiento de la placa. A continuación se comprobarán los sensores, para lo que probablemente haya que implementar un interfaz por el bus I2C de tal forma que podamos ver los valores que se obtienen en la lectura, no forma parte del proyecto este interfaz pero ya está hecho y será útil para compronder como leen los sensores. Lo siguiente sería controlar en velocidad los motores mediante PWM, y una vez que se tenga implementada la lectura de los sensores y el control de velocidad de los motores, lo siguiente y último es poner el robot sobre la línea e implementar un algoritmo que permita seguir la línea de la mejor forma posible, aquí se verá si el robot se acerca al metro por segundo... o se queda lejos.

Hasta ahora todo ha sido directo y muy sencillo, las tres últimas partes son más complejas, aunque se pueden mirar las librerias del robot 3pi y probablemente se pueda reutilizar la mayoría.

Para cualquier duda de lo anterior, idea, comentario: Foro.