¿Qué es este blog?

La idea de este blog nace para compartir los avances que se vayan realizando a lo largo de un estudio sobre cómo interconectar los distintos sensores que se pueden encontrar en el mercado, o fabricar de forma casera, con la plataforma Mindstorms de LEGO. Para ello se hará uso ARDUINO, un entorno de desarrollo abierto basado en microcontrolador.

jueves, 24 de marzo de 2011

Primera comunicación NXT <--> Arduino <--> Sensor analógico


         En esta entrada se va a describir cómo conectar sensores analógicos al NXT usando Arduino como intermediario.
Aunque la conexión de sensores analógicos se puede hacer de manera directa a los puertos del NXT, cada sensor presenta unas resistencias de salida distintas, unos rangos de tensión y corriente distintos, y además, para poder conectar varios sensores a la misma entrada sería necesario un multiplexor y una circuitería que permitiera al robot elegir de qué sensor quiere leer. Todas estas funciones van a ser desempeñadas por Arduino, que será el encargado de leer los valores analógicos desde los sensores, hacer la conversión a digital, y mandar los datos al robot que éste necesite.


Sensor analógico: Ardupilot 4 DOF


El Ardupilot 4 DOF es un IMU que proviene de un proyecto original de Chris Anderson y Jordi Muñoz para crear un AHRS (Attitude Heading Reference System) para control UAV y captura general de movimiento y orientación.
Ésta es la placa principal de sensores que se usa en la placa ArduPilot. Por si sola proporciona 4 grados de libertad (4 DOF) y puede ser conectada directamente a sus pines analógicos o utilizado en cualquier otro proyecto que necesite de captura de movimiento sobre varios ejes.
El sensor viene equipado con un giroscopio LISY300AL de ±300°/s de un sólo eje y un acelerómetro ADXL335 de 3 ejes ±3g. Todo los componentes de filtrado están incorporados en la placa. No dispone de regulador de tensión, por lo que hay que aplicar una tensión de alimentación limpia de 3.3V.

Las principales características de esta placa son:
  • 4 grados de libertad:
    • ADXL335 - acelerómetro de 3 ejes, ±3g
    • LISY300AL - giroscopio de un eje para velocidad angular, ±300°/s
  • Compatible con ArduPilot 6DOF daughter board
  • Los valores analógicos de los seis ejes son accesibles
  • Espaciado de pines: 0.1"
  • Dimensiones: 2.3x1.5cm
En las figura siguiente se puede ver tanto el tamaño de la placa, como las salidas que ésta nos ofrece:










Conexiones entre Arduino Mega 2560 y el IMU

El primer paso es realizar la conexión entre la placa de Arduino que se ha utilizado (Arduino Mega 2560) y el sensor descrito anteriormente. Las conexiones que se han hecho han sido:
  • Salidas y,z,x del acelerómetro y x, y z del giróscopo a las entradas A0-A5 analógicas de la placa (en ese orden, aunque eso da igual siempre y cuando lo tengamos en cuenta en el código que se use).
  • La toma de alimentación de 3,3 V del sensor necesita una alimentación estable. Se conecta a la salida de 3,3 V de Arduino y al pin AREF. Esto último se hace para escalar correctamente el conversor A/D, ya que la referencia analógica por defecto para Arduino es de 5 V.
  • Las tierras se conectan juntas

Conexiones entre Arduino Mega 2560 y el NXT

En cuanto a las conexiones entre el sistema de desarrollo de Arduino y el NXT, éstas ya se comentaron en otra entrada del blog. Al igual que entonces, esta comunicación se va a realizar siguiendo el protocolo de comunicación I2C. Estas conexiones serían las siguientes:
  • Pines 2 y 3 del NXT (colores rojo y negro) se conectan a las entradas de tierra de Arduino (GND).
  • Pin 4 del NXT (color verde) que corresponde a la alimentación, se conecta a la entrada de 5 V de Arduino.
  •  Pin 5 del NXT (color amarillo) que corresponde a la línea de reloj del protocolo I2C se conecta al pin marcado como SCL en la placa de Arduino.
  • Pin 6 del NXT (color azul) que corresponde a la línea de datos del protocolo I2C se conecta al pin marcado como SDA en la placa de Arduino.

 Código cargado en el NXT

En cuanto a las conexiones entre el sistema de desarrollo de Arduino y el NXT, éstas ya se comentaron el apartado 4.3 de esta memoria. Al igual que entonces, esta comunicación se va a realizar siguiendo el protocolo de comunicación I2C. Estas conexiones serían las siguientes:
-       Pines 2 y 3 del NXT (colores rojo y negro) se conectan a las entradas de tierra de Arduino (GND).
-       Pin 4 del NXT (color verde) que corresponde a la alimentación, se conecta a la entrada de 5 V de Arduino.
-       Pin 5 del NXT (color amarillo) que corresponde a la línea de reloj del protocolo I2C se conecta al pin marcado como SCL en la placa de Arduino.
-       Pin 6 del NXT (color azul) que corresponde a la línea de datos del protocolo I2C se conecta al pin marcado como SDA en la placa de Arduino.


import lejos.nxt.*;




public class ArduCom {




    public static void main(String[] args) throws Exception{
        byte [] dato = new byte[3];
        String Tipo = "";
        String ID ="";
        String Ver="";
        int err = 0;
       
        for (int i=0;i<dato.length;i++){
            dato[i] = 0;
        }
       
        I2CSensor Arduino = new I2CSensor(SensorPort.S1);
        Arduino.setAddress(1);
        Ver = Arduino.getVersion();
        System.out.println("Ver: "+Ver);
        Tipo = Arduino.getSensorType();
        System.out.println("Tipo: "+Tipo);
        ID = Arduino.getProductID();
        System.out.println("ID: "+ID);
        Thread.sleep(1000);
        while (!Button.ESCAPE.isPressed()) {
            err = Arduino.getData(3, dato, 3);
            System.out.println("Acel: "+dato[0]+" "+dato[1]+" "+dato[2]);;
            err = Arduino.getData(4, dato, 3);
            System.out.println("Giros: "+dato[0]+" "+dato[1]+" "+dato[2]);
        } //Fin del while
    }
}

Código cargado en Arduino


A continuación se va a presentar el código que se ha cargado en Arduino. Este código se ha dejado comentado para facilitar la comprensión del mismo. Las tareas que realiza son las siguientes:
-       En el apartado de configuración (setup) se configura:
o   Referencia externa de 3,3 V
o   Se marcan los pines analógicos como salidas
o   Nos unimos al bus I2C como esclavos usando la dirección 1. A través de él se realizará la comunicación con el NXT.
o   Se configura la velocidad de escritura por el puerto serie, lo que nos va a permitir ver a través de la herramienta de monitorización del propio entorno de desarrollo de Arduino los datos que vayamos leyendo y convirtiendo desde el sensor analógico.
o   Se configuran las rutinas que se ejecutarán cuando se reciba una petición de datos desde el NXT
-       El bucle principal realiza la lectura de los pines analógicos comprendidos entre el 0 y el 5, que será donde estén conectados los pines del sensor. Se les resta el valor medio del sensor en reposo para que den un valor 0 estando en reposo. Estos datos se almacenan y se dejan listos para enviarlos al NXT cuando sea necesario.
-       En las rutinas de interrupción se identifica  el tipo de dato que está pidiendo el NXT (codificado en el campo registro) y se procede al envío de dichos datos.

El código usado es el siguiente:



#include <Wire.h> //Para gestionar las comunicaciones


//Definiciones y variables globales


uint8_t SensorVersion[9] = " V0.1   ";
uint8_t SensorName[9] =    "Arduino ";
uint8_t SensorType[9] =    "Casero  ";
byte buffersalida[] = { 0, 0, 0, 0, 0, 0}; //Para enviar al RCX
byte buffgiros[] = { 0, 0, 0}; //Ejes x, y, z del giroscopo
byte buffacel[] = { 0, 0, 0}; // Ejes x, y, z del acelerometro


int val = 0;                    //Datos que se leeran del acelerometro o giroscopo
unsigned long timer=0;          //timer
unsigned long  delta_t;         //delta o tiempo entre muestras


byte rec;


void setup(){
 
  analogReference(EXTERNAL);     //Usamos referencia externa de 3,3 V para calibrar el ADC
  Serial.begin(115200);
  DDRC = B00000000; //Marcamos los puertos analogicos como salidas
 
  Serial.println("t[ms] \t az \t ay \t ax \t gx \t gy \t gz "); //Cabeceras
   
  Wire.begin(1); //Nos unimos como esclavos con dir. 1
  Wire.onRequest(requestEvent); //Rutinas para cuando llegue algo
  Wire.onReceive(receiveEvent);
 
  timer=millis();
}




void loop(){
  delta_t = millis() - timer; // Calculamos el tiempo de adquisicion
  timer=millis();          // Actualizamos el timer
  Serial.print(delta_t);
  Serial.print ("\t");
    
  for (long i=0; i<6; i++) //Vamos a recorrer los pines analogicos
 {
  val = analogRead(i);    // Leemos el pin de entrada
 
  if(i==0) { //Le quitamos el offset al valor leido
    val = val - 601;
    buffacel[2] = val; //Eje z del acelerometro
  } else if (i==1) {
    val = val - 506;
    buffacel[1] =  val; // Eje y del acelerometro
  } else if (i==2) {
    val = val - 504;
    buffacel[0] =  val; // Eje x del acelerometro
  } else if (i==3) {
    val = val - 504;
    buffgiros[0] = val; //Eje x del giroscopo
  } else if (i==4) {
    val = val - 502; //Valor medio       
    buffgiros[1] = val; //Eje y del giroscopo
  } else if (i==5) {
    val = val - 483; //Valor medio
    buffgiros[2] = val; //Eje z del giroscopo
  }
 
  Serial.print(val);      //Imrpimimos por puerto serie
  Serial.print ("\t");
 } 


  Serial.println("");
  delay(16);             //Bucle ejecutado a ~ 50Hz o 20ms
}


void receiveEvent(int len){ //Devuelve el nº de bytes recibidos
  while (0 < Wire.available() ) {
    rec = Wire.receive();
  } //While
}


void requestEvent(){


  Serial.print(rec, BYTE);
  if (rec == 0x00){ //Version del sensor
    Wire.send(SensorVersion, 8);


  } else if (rec == 0x08){ //Nombre del sensor
    Wire.send(SensorName, 8);
  } else if (rec == 0x10){ //Tipo del sensor
    Wire.send(SensorType, 8);
  } else if (rec == 0x03) { // Lectura del acelerometro
    Wire.send(buffacel, 3);
  } else if (rec = 0x04) { //Lectura del giroscopo
    Wire.send(buffgiros, 3);
  } else {
    buffersalida[0] = 0;
    buffersalida[1] = 0;
    Wire.send(buffersalida, 2);
  }
}


Conclusiones

Se ha realizado con éxito la comunicación entre un sensor analógico y el NXT usando como intermediario el Sistema de Desarrollo Arduino, que se comunica con el robot haciendo uso del protocolo I2C. Los datos que se reciben desde el sensor analógico son correctos, y sólo se aprecia una oscilación de números algo mayor en dos ejes del giróscopo. Esta oscilación debida al ruido de medida, puede estar relacionada con el hecho de que las pruebas se hayan realizado en una placa de prototipos estándar y con hilos de conexión normales. A continuación se muestra una fotografía tomada del sistema bajo pruebas.
----------------------------------------------------------------------------------------------------------------
Referencias:


- Razor IMU and Arduino: http://voidbot.net/razor-6dof.html

martes, 8 de marzo de 2011

Arduno y la comunicación I2C: Librería Wire()


En publicaciones anteriores del blog se comentaron las distintas herramientas de las que disponía el NXT para establecer la comunicación mediante I2C con dispositivos esclavos, e hicimos especial hincapié en el Sistema Operativo LejOs, que es con el que más estamos trabajando.

En el otro lado de la comunicación, y funcionando como esclavo, se va a encontrar nuestra placa de Arduino. En este epígrafe se va a describir el funcionamiento de la librería Wire(), que es aquella que nos va a permitir realizar esta comunicación, y se van a describir brevemente las funciones más interesantes de las que ésta dispone.

Principales funciones

Como se acaba de comentar, esta librería permite la comunicación de una placa Arduino con dispositivos I2C y TWI. En la mayoría de las placas Arduino, la línea de datos (SDA) se encuentra en el pin analógico 4, y la línea de reloj (SCL) se encuentra en el pin analógico 5. En el caso de la placa que nosotros usamos, Arduino Mega 2560, estas líneas están en los pines digitales 20 y 21 respectivamente.

Las principales funciones que nos ofrece esta librería son las siguientes:

- Wire.begin() y Wire.begin(dirección). Esta función inicializa la librería Wire y conecta Arduino al bus. Si no se especifica la dirección, Arduino se conectará al bus como maestro, mientras que si ésta se indica lo hará como esclavo y asumiendo la dirección que se la ha proporcionado. En ambos casos, esta función no devuelve ningún valor.

- Wire.requestFrom(dirección, cantidad). Solicita bytes desde otros dispositivos. Los bytes pueden ser recibidos con las funciones available() (que nos indica si hay datos disponibles para ser leídos en el bus) y receive() (que realiza la lectura de los datos). Esta función no devuelve nada.

  • El parámetro “dirección” es la dirección de 7 bits del dispositivo al que le queremos pedir los datos
  • El parámetro “cantidad” es el número de bytes a pedir.
- Wire.beginTransmission(dirección). Comienza una transmisión a un dispositivo I2esclavo con la dirección que se especifique en el parámetro “dirección”. Posteriormente, prepara los bytes a transmitir con la función send() y los transmite llamando a la función endTransmision(). Esta función no devuelve nada.

  • El parámetro “dirección” es la dirección de 7 bits del dispositivo al que le queremos pedir los datos.
- Wire.endTransmission(). Finaliza una transmisión a un esclavo que fue empezada por beginTransmission(), y realmente lo que hace es transmitir los bytes que fueron preparados por send(). Esta función tampoco devuelve nada.

- Wire.Send(valor), Wire.Send(string) o Wire.Send(dato, cantidad). Envía datos desde un esclavo como respuesta a una petición de un maestro, o prepara los bytes para transmitir desde un maestro a un esclavo (entre llamadas a beginTransmission() y endTransmission()).

  • El parámetro “valor” es un byte que se desee enviar.
  • El parámetro “string” es una cadena de tipo (char *) que se desee enviar.
  • El parámetro “dato” es un vector de datos para enviar (byte *).
  • El parámetro “cantidad” es el número de bytes que se desea transmitir

- Wire.available(). Devuelve el número de bytes disponibles para recuperar con receive(). Debería ser llamada por un maestro después de llamar a requestFrom() o por un esclavo dentro de la función a ejecutar por onReceive(), que será lo que nosotros hagamos al recibir datos del NXT. Esta función devuelve el número de bytes disponibles para leer.

- Wire.receive(). Recupera un byte que fue transmitido desde un dispositivo esclavo a un maestro después de una llamada a requestFrom() o que fue transmitido desde un maestro a un esclavo. Devuelve el byte recibido.

- Wire.onReceive(handler). Registra una función que será llamada cuando un dispositivo esclavo reciba una transmisión desde un maestro. Esta función no devuelve nada.

  • El parámetro “handler” corresponde a la función que será llamada cuando el esclavo recibe datos. Esta función debería coger un único parámetro entero (el número de bytes recibidos desde un maestro) y no devolver nada, por ejemplo: void myManejador(int numBytes).
- Wire.onRequest(handler). Registra una función que será llamada por el dispositivo esclavo cuando un maestro solicite datos. No devuelve nada.

  • El parámetro “handler” es la función que tiene que ser llamada.
A modo de ejemplo se muestra a continuación el código de un maestro y de un esclavo que hacen uso de esta librería. El esclavo envía datos, y el maestro los recibe y los imprime por puerto serie, con lo que se pueden monitorizar desde el PC.


En primer lugar el código del maestro:





Y a continuación el del esclavo:





--------------------------------------------------------------------
REFERENCIAS:
- Ejemplo de comunicación I2C entre dos Arduinos: http://arduino.cc/en/Tutorial/MasterReader
- Arduino Wire: http://arduino.cc/es/Reference/Wire
- I2C bus. TWI bus: http://www.i2c-bus.org/twi-bus/

lunes, 7 de marzo de 2011

Solución a los problemas entre Arduino Mega 2560 y el Mindstorm

En primer lugar, hacer referencia a una publicación que hice hace unos días, donde explicaba el problema y aún no le había encontrado solución: enlace

En ella, básicamente comentaba los problemas que tenía para comunicar el NXT con la placa Arduino Mega 2560 mediante I2C. Voy a resumir el problema con datos y medidas para que todo quede bien claro, y finalmente pondré la solución a la que hemos llegado.
  • En primer lugar, se hicieron pruebas de comunicación I2C entre Arduino Duemilanove y el NXT. La conexión entre ambos era la siguiente:
    • Pines 2 y 3 del NXT (Rojo y negro) (tierra) --> Arduino GND.
    • Pin 4 del NXT (alimentacion) (verde) --> Arduino 5V
    • Pin 5 del NXT (Amarillo) (SCL) --> Arduino A5
    • Pin 6 del NXT (Azul) (SDA) --> Arduino A4
    Con estas conexiones, y un programa sencillo que simplemente recogiera algunos datos del Arduino y los mostrara en la pantalla del NXT todo funcionó perfectamente. El programa usado también lo comenté en otra publicación y lo podéis encontrar aquí.
  • El siguiente paso era hacer el mismo intercambio de datos usando una placa algo más potente y con más prestaciones: la Arduino Mega 2560. Las conexiones que se tienen que hacer para ellos son estas:
    • Pines 2 y 3 del NXT (Rojo y negro) (tierra) --> Arduino GND
    • Pin 4 del NXT (alimentacion) (verde) --> Arduino 5V
    • Pin 5 del NXT (Amarillo) (SCL) --> Arduino SCL
    • Pin 6 del NXT (Azul) (SDA) --> Arduino SDA
Bien, haciendo esto y lanzando exactamente el mismo programa que en el caso del Arduino Duemilanove tanto para el Mindstorm como para el Arduino... no pasó absolutamente nada. Es decir, no hubo intercambio de datos. ¿Por qué? Bueno, voy a ir explicando los pasos que he dado hasta dar con la solución...
    Diferencias entre Arduino Duemilanove y Arduino Mega 2560
    Si nos fijamos en los esquemáticos de ambos (disponibles en la página oficial de Arduino), hay una diferencia en el camino que recorren tanto la señal de datos (SDA) como la de reloj (SCL) del protocolo I2C desde el conector hasta el microcontrolador. Se trata de dos resistencias de pull-up, de un valor de 10K que sirven para mantener el valor lógico "uno" si la tensión en la línea no es suficiente. Estas resistencias son las que nos están originando el problema, como entenderemos enseguida. Estas resistencias las vemos en la siguiente captura (extraída del esquemático):

    ¿Por qué pueden dar problemas?
    En principio, una resistencia de pull-up no es malo, sino todo lo contrario. Pero en este caso nos está perjudicando. Vamos a analizar el recorrido de las señales de reloj (SCL) y de datos (SDA) en el Mindstorm en el caso del puerto A (es idéntico para todos los puertos). 

    En la figura que tenemos aquí vemos como del procesador (AT91SAM7S256) salen las señales DIG1A0 y DIG1A1. Estas son las señales SCL y SDA respectivamente. 



    Estas señales, van a parar al circuito siguiente:


    En él vemos el recorrido que hacen hasta obtener las señales DIGIAI0 y DIGIAI1 que son las que tendremos a la salida (podemos ver el conector en la figura inferior). Si nos fijamos, estas señales pasan por una resistencia en serie de 4,7 K y a continuación tienen conectadas unas resistencias de pull-up de 82 K que van alimentadas a 5V (ver nota 1). 


    Esta resistencia de pull-up de 82 K, al conectar nuestro Mindstorm con la placa Arduino Mega 2560 se asocia en paralelo con la de 10 K de dicha placa, formando una resistencia equivalente de 8,91 K (es decir, casi diez veces inferior a la original). 
    El procesador nos dará como nivel alto un valor de 3,3 V, y como nivel bajo 0 V. Si hacemos algunos cálculos (teniendo en cuenta el circuito equivalente) para calcular qué valores vamos a tener a la salida en cada caso, obtendremos lo siguiente:


    • Circuito equivalente conectando el NXT con Arduino Duemilanove: se obtiene un valor a la salida de 3,40 V (nivel alto) y de 0,28 V (nivel bajo).
    • Circuito equivalente conectando el NXT con Arduino Mega 2560 (y por tanto con el paralelo de las dos resistencias de pull-up): se obtiene un valor a la salida de unos 3,89 V (nivel alto) y 1,70 V (nivel bajo).

    Lo que parece, es que el valor de tensión a la salida para el nivel alto será detectado sin problemas, pero para el nivel bajo, en el caso del Arduino Mega tenemos 1,70 V. Es un valor demasiado alto, y probablemente pueda confundirse con el '1' lógico haciendo que la comunicación falle. Esto se debe a que la resistencia de 4,7 K que vemos en serie a la salida del Mindstorm, está alterando el valor de tensión que hay a la salida y hace que las resistencias de pull-up entren en acción cuando no deberían, es decir, cuando la línea está a nivel bajo.

    Primeras medidas
    Todas las medidas que se describen a continuación han sido realizadas con un osciloscopio digital de la marca Tektronix, modelo TDS 210, del departamento de Ingeniería de Sistemas y Automática de la Universidad de Málaga.

    Lo primero que se hizo fue, para descartar que hubiera cualquier problema en los puertos I2C de la placa Arduino Mega 2560, interconectar dicha placa con la de Arduino Duemilanove y ponerlas a intercambiar datos. Hay muchos ejemplos de esto por internet, pero el que seguimos fue este. Al hacer esto, vimos que la comunicación era correcta, y que las señales de datos y de reloj mantenían un valor medio de señal próximo a los 5 v (de aproximadamente 4,90 V y cercano a 0 para el '0' lógico.

    A continuación, para comprobar los cálculos teóricos que comentamos en el apartado anterior, se hicieron las medidas conectando en primer lugar el NXT con la placa Duemilanove, y a continuación con la Mega. Lo que se vio confirmó lo que pensábamos, ya que, aunque el valor medio de las señales era similar, en el caso de la conexión con Arduino Mega la señal tenía la mitad de amplitud (unos 2 V pico a pico) y su extremo inferior estaba cercana a los 2 V, mientras que con Duemilanove el pico inferior rozaba los 0 V y tenía una amplitud de 4 V pico a pico.

    Por lo tanto, efectivamente el nivel bajo de la señal se está confundiendo con el nivel alto. 

    La solución
    Tras todo este proceso, la única solución viable era la de eliminar las resistencias de la placa de Arduino Mega 2560. Para aquellos que tengan el modelo Arduino Mega (versión anterior) lo tendrán más fácil, ya que lo que tienen es simplemente dos resistencias separadas que se pueden desoldar y eliminar fácilmente. En nuestro caso, es algo más complejo ya que las resistencias están dentro de un encapsulado DIL, que contiene además la resistencia que mantiene el reset a nivel alto hasta que lo pulsemos. Si se eliminase este encapsulado (que vemos en la figura inferior con un 103 escrito), se estaría dejando el reset permanentemente a nivel bajo, y por lo tanto la placa no llegaría a encenderse. 


    (Gracias por la captura a Chiva, del foro oficial de Arduino :) )

    Aunque había otra alternativa, como desoldar el encapsulado, y colocar después una resistencia de 10k para el reset, optamos por "cortar" las líneas de datos que unían las pistas de SDA y SCL con las resistencias. Para ello, nos fijamos en el esquema de PCB de Arduino (también disponible en la página oficial) y elegimos el mejor sitio para el corte. En las dos figuras de abajo se han resaltado las líneas SDA y SCL, y se ha marcado en verde el sitio por el que se ha hecho el corte:




    Y tras hacer estos cortes (con cuidado, arañando con un cúter hasta comprobar con un multímetro que la vía por la que pasa la señal y la patilla de la resistencia ya no hacían contacto) se ha vuelto a probar la comunicación entre el NXT y el Arduino Mega 2560 y... todo funciona correctamente :)

    Nota1: Sobre la tensión VCC_RS485


    En los cálculos se ha asumido que la tensión VCC_RS485 es de 5 V. Para ello, se ha recurrido una vez más al esquemático. El diagrama de bloques del sistema de alimentación se puede ver a continuación.



    Si nos fijamos, la señal VCC_RS485 tiene a su entrada un conmutador que alterna su valor entre 5 V y un circuito abierto. La señal que controla esto es POWER_EN que procede del ATMEGA48, y que corresponde al pin PB7. Se trata de una señal periódica generada por el oscilador interno del microcontrolador y que hace que el conmutador vaya abriéndose y cerrándose continuamente.