Fuente: http://www.jalcdeveloper.com/projects/chapters/arduino-android-usb-cap1/

Buenas a tod@s, en este tutorial veremos como conectar un Arduino Leonardo con un móvil Android por USB.

Montaremos un circuito muy simple con un sensor de temperatura y humedad, que utilizaremos de ejemplo. Veremos un poco de conceptos sobre el USB OTG las diferentes clases de comunicación y crearemos una aplicación móvil, en la que podremos ver los valores de temperatura y humedad.

Para seguir este tutorial necesitaremos:

  1. Un Arduino Leonardo ( chip Atmega32u4 ) no sirve un Arduino Uno
  2. Un cable USB OTG
  3. Un cable MicroUSB a USB
  4. Un móvil Android versión 3.2 o superior.
  5. Un sensor de temperatura y humedad DHT22
  6. Una resistencia de 10k

Porque un Arduino Leonardo

Para los que se preguntan por que usamos un Arduino Leonardo y no un Arduino UNO, se debe a que el Arduino Leonardo, tiene soporte nativo de USB con las clases de protocolo CDC y HID, encontra el Arduino UNO utiliza un chip separado que convierte de serie a USB. La programación seria distinta a la que mostramos en este tutorial.

¿ Que es USB CDC ?

El USB dispone de diferentes clases de protocolos de comunicación, según el dispositivo que conectemos. Tenemos básicamente cinco clases.

  • ADC : Audio Device Class
  • CDC: Comminication Device Class
  • HID: Human Interface Device
  • MSC: Mass Storage Class
  • Custom Class

Sus nombres son bastante desciptivos, para nuestro proyecto nos interesa la clase CDC, esta clase emula un puerto serie y nos permite comunicar con la placa Arduino Leonardo de una manera muy sencilla. El Arduino Leonardo también soporta la clase HID, así que lo podríamos utilizar como si de un mouse o teclado se tratara. ¿ Alguien se anima a probarlo ? ( más info )

El USB OTG

Para aquellos que desconozcan que es el USB OTG, se trata de una extensión del USB 2.0 la cual tiene una mayor flexibilidad permitiendo que un dispositivo funcione como esclavo o maestro (host). Con otras palabras nos permite conectar cualquier dispositivo (pendrive, camaras, discos, arduino’s, … ) a nuestro móvil.

Para ello necesitaremos un cable USB OTG, el cual en un extremo tiene un conector microUSB que conectamos a nuestro móvil y en el otro extremo tiene un conector USB hembra, al que podremos conectar el cable USB del Arduino Leonardo. ( el mismo cable que utilizamos para programarlo).

Cable_usb_otg

Este tipo de cable lo podemos conseguir en cualquier tienda de electronica, centros comerciales o tiendas de móviles.

El sensor DHT22

Hemos elegido montar un sensor ambiental para dar un giro practico al tutorial, no entraremos en el detalle de su funcionamiento. Si cabe mencionar para aquellos que no lo conozcan, que se trata de un sensor de humedad y temperatura, muy fácil de utilizar ya que el mismo se encarga de convertir los valores en una señal digital, así que no necesitaremos utilizar pins analógicos, ni electrónica adicional pa

Para los que queráis profundizar en este sensor os dejo un par de enlaces, con muchísimas mas información

Datasheet – http://www.adafruit.com/datasheets/DHT22.pdf

Tutoriales sobre el sensor (Ingles) – https://learn.adafruit.com/dht

El circuito

El montaje del circuito es muy simple como podemos ver en el esquema, solo tenemos que alimentar el sensor y conectar el pin de datos al Arduino, junto a la resistencia pull-up de 10k.

circuito

Sketch de Arduino

El sketch del Arduino es muy simple ya que para enviar datos por el USB solo tenemos que utilizar la función Serial.print() o Serial.println().

La parte que tenemos que hacer algo mas complicada es la lectura del sensor de temperatura, lo primero es descargar la librería que nuestros amigos de Adafruit han preparado ( aquí ), esta nos permite leer los datos del sensor sin complicaciones. Una vez descargada descomprimiremos el zip, renombrados la carpeta que acabamos de descomprimir, con el nombre DHT y por ultimo la copiamos a la carpeta /libraries/

En el entono de programación de Arduino copiaremos el siguiente código, y lo cargaremos en nuestro Arduino Leonardo.

#include "DHT.h"
#define DHTPIN 2     
#define DHTTYPE DHT22   
DHT dht(DHTPIN, DHTTYPE);
 
void setup() {
  Serial.begin(9600);  
  dht.begin();
}
 
void loop() {
 
  // Reading temperature or humidity takes about 250 milliseconds!
  // Sensor readings may also be up to 2 seconds 'old' (its a very slow sensor)
  float h = dht.readHumidity();
  float t = dht.readTemperature();
 
  // check if returns are valid, if they are NaN (not a number) then something went wrong!
  if (isnan(t) || isnan(h)) {
    Serial.println("Failed to read from DHT");
  } else {
    Serial.print("H:"); 
    Serial.print(h);
    Serial.print(",");
    Serial.print("T:"); 
    Serial.print(t);
    Serial.println(";");
  }
}

Antes de seguir comprobamos con el «monitor serie» que leemos correctamente el sensor, deberíamos de tener una lectura parecida a la de esta imagen:

Captura_de_pantalla_2014-06-18_a_las_10.35.36

Donde «H» indica la humedad y T indica la temperatura, prestar mucha atención a las «comas», «dos puntos» y «punto y como» ya que los utilizaremos para separar los valores.

Aplicación Android

Llega el momento que tod@s esperábamos, crear una aplicación Android que nos permite leer datos por USB desde nuestro Arduino.

Para poder realizarlo tendremos que seguir unos cuantos pasos:

  • Obtener la lista de dispositivos conectados
  • Obtener el dispositivo con el que queremos comunicar
  • Solicitar permiso al usuario, para poder utilizarlo
  • Obtener el Interface según la clase de comunicación que queramos usar. Para nuestro ejemplo la clase es la CDC.
  • Obtener los Endpoints de entrada y salida para esta clase.
  • Enviar los mensajes de configuración a nuestro Arduino Leonardo
  • Por fin! Leer y escribir los datos que queramos.

Empezando por descargar el código fuente de ejemplo, completamente funcional, desde GitHub e importarlo a vuestro Eclipse:

https://github.com/jalucenyo/USB-Arduino-Android

NOTA: En las explicaciones omitiremos todas las partes no fundamentales, a la programación del USB.

Diagrama de flujo App

Diagrama_USB_Arduino2Android

Variables

Para manejar la conexión USB necesitaremos las siguientes Variables.

// TODO: Variables USB
UsbManager mUsbManager;
UsbDevice mUsbDevice;
PendingIntent mPermissionIntent;
UsbDeviceConnection mUsbDeviceConnection;
UsbEndpoint epIN = null;
UsbEndpoint epOUT = null;

UsbManager

Esta variable nos permite manejar todo lo referente al USB de nuestro móvil asi como obtener una lista de dispositivos conectados.

UsbDevice

Con esta variables manejaremos un dispositivo concreto, de todos los que podamos tener conectados. Obtener su nombre, fabricante, versión, los protocolos e interfases que soporta, etc.

PendingIntent

La necesitaremos para llamar al Intent que solicita al usuario el permiso de uso del USB.

UsbDeviceConnection

Necesaria para conectar con el dispositivo USB que tengamos conectado.

UsbEndPoint

Por ultimo necesitamos este tipo de variable para poder comunicar con el dispositivo USB. Vienen a ser los caneles que disponen, pueden ser tanto de entrada como de salida de datos.

PASO 1: Declarar BroadcastReveiver

Declaramos un BroadCastReceiver, que se encarga de comprobar que el usuario acepta el permiso de uso del USB y tambien detecta cuando desconectamos el dispositivo USB para limpiar las variables.

// TODO: Al conectar a un dispositvo USB se solicita un permiso al usuario
// este broadcast se encarga de recoger la respuesta del usuario.
private static final String ACTION_USB_PERMISSION = "com.android.example.USB_PERMISSION";
 
private final BroadcastReceiver mUsbReceiver = new BroadcastReceiver(){
    public void onReceive(Context context, android.content.Intent intent) {
        String action = intent.getAction();
 
        // TODO: Al aceptar el permiso del usuario.
        if (ACTION_USB_PERMISSION.equals(action)){
            synchronized (this) {
                //UsbDevice device = (UsbDevice) intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
                if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)){
                    Log.d(TAG, "Permiso aceptado");
                    processComunicationUSB();
                }else{
                    Log.e(TAG, "Permiso denegado");
                }
            }
        }
 
        // TODO: Al desconectar el dispositivo USB cerramos las conexiones y liberamos la variables.
        if (UsbManager.ACTION_USB_DEVICE_DETACHED.equals(action)) {
            UsbDevice device = (UsbDevice)intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
            if (device != null) {
                // call your method that cleans up and closes communication with the device
            }
        }
    }
};

Como todos los BroadcastReceiver tenemos que registrarlos, por norma suelo hacerlo en el método onResume(), para asegurar que estos siempre se registran, ( no olvides hacer el unregisterReceiver en el método onPause())

@Override
protected void onResume() {
 
    super.onResume();
    //TODO: Solicitamos permiso al usuario si este esta pendiente
    mPermissionIntent = PendingIntent.getBroadcast(this, 0, new Intent(ACTION_USB_PERMISSION), 0);
 
    //TODO: Registro del Broadcast
    registerReceiver(mUsbReceiver, new IntentFilter(ACTION_USB_PERMISSION));
    registerReceiver(mUsbReceiver, new IntentFilter(UsbManager.ACTION_USB_DEVICE_DETACHED));
 
}

PASO 2: Detectar y solicitar permiso

El único botón que dispone nuestra app, programaremos el evento OnClick, para que realice la búsqueda de los dispositivos USB conectados. Una vez encontrado conectara con el primero y solicitara permiso al usuario para utilizarlo. Todo esto lo podemos hacer con este simple código:

// TODO: Boton Conectar.
btn = (Button) findViewById(R.id.button1);
btn.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        //TODO: Obtemos el Manager USB del sistema Android 
        mUsbManager = (UsbManager) getSystemService(Context.USB_SERVICE);
 
        // TODO: Recuperamos todos los dispositvos USB detectados
        HashMap<String, UsbDevice> deviceList = mUsbManager.getDeviceList();
 
        //TODO: en nuestro ejemplo solo conectamos un disposito asi que sera 
        // el unico que encontraremos.
        Iterator<UsbDevice> deviceIterator = deviceList.values().iterator();
        if(deviceIterator.hasNext()){
            mUsbDevice = deviceIterator.next();
            Log.d(TAG, "Name: " + mUsbDevice.getDeviceName());
            Log.d(TAG, "Protocol: " + mUsbDevice.getDeviceProtocol());
            //TODO: Solicitamos el permiso al usuario.
            mUsbManager.requestPermission(mUsbDevice, mPermissionIntent);
        }else{
            Log.e(TAG, "Dispositvo USB no detectado.");
        }
}
});

Si el usuario acepta el permiso se ejecuta la función processComunicationUSB(), veamos que pasos cumple esta función.

PASO 3: Conectar al Arduino

El primer paso que realiza nuestra función processComunicationUSB() es conectar al Arduino

// TODO: Conectar al dispositivo (Arduino)
mUsbDeviceConnection = mUsbManager.openDevice(mUsbDevice);
if(mUsbDeviceConnection == null){
    Log.e(TAG, "No se ha podido conectar con el dispositivo USB.");
    finish();
}

PASO 4: Obtener Interface y Endpoints

Anteriormente vimos que el usb tiene diferentes clases de comunicación ( CDC, HID, etc. ) en el framework de Android esto son los llamadas interfases. Destras de las interfases y dependiendo de la clase estan los enpoints que vendrian a ser los caneles de comunicación que dispone la clase. Como comunicaremos con un Arduino Leonardo obtenemos el interfase de clase CDC y los endpoints necesitaremos, con el siguiente código.

// TODO: getInterfase(1) Obtiene el tipo de comunicacion CDC (USB_CLASS_CDC_DATA)
UsbInterface mUsbInterface = mUsbDevice.getInterface(1);
// TODO: Obtenemos los Endpoints de entrada y salida para el interface que hemos elegido.
for (int i = 0; i < mUsbInterface.getEndpointCount(); i++) {
    if (mUsbInterface.getEndpoint(i).getType() == UsbConstants.USB_ENDPOINT_XFER_BULK) {
        if (mUsbInterface.getEndpoint(i).getDirection() == UsbConstants.USB_DIR_IN)
            epIN = mUsbInterface.getEndpoint(i);
        else
            epOUT = mUsbInterface.getEndpoint(i);
    }
}

NOTA: En caso de que queráis investigar con otros dispositivo USB que no sea un Arduino Leonardo, este punto un poco confuso. Os recomiendo utilizar las funciones getClass() del UsbInterface para saber con que clases admite trabajar el dispositivo que estéis trasteando.

PASO 5: Enviar los mensajes de configuración

Para los que estamos acostumbrados a trabajar con comunicación serie, esta parte puede ser un poco rara. El protocolo USB dispone de unos comandos que podemos enviar del Host al Device para indicarle como nos vamos a comunicar. En este caso le enviamos dos mensajes al Arduino, uno indicando la configuración de velocidad, bits, pariedad etc. y otro mensaje mas para que active la linea DTR.

mUsbDeviceConnection.claimInterface(mUsbInterface, true);
// TODO: Mensaje de configuración para el Device.
int baudRate = 115200;
byte stopBitsByte = 1;
byte parityBitesByte = 0;
byte dataBits = 8;
byte[] msg = {
    (byte) (baudRate & 0xff),
    (byte) ((baudRate >> 8) & 0xff),
    (byte) ((baudRate >> 16) & 0xff),
    (byte) ((baudRate >> 24) & 0xff),
    stopBitsByte,
    parityBitesByte,
    (byte) dataBits
};
 
mUsbDeviceConnection.controlTransfer(UsbConstants.USB_TYPE_CLASS | 0x01, 0x20, 0, 0, msg, msg.length, 5000);
// (UsbConstants.USB_TYPE_CLASS | 0x01) 0x21 -> Indica que se envia un parametro/mensaje del Host al Device (movil a la placa leonardo)
// 0x20 -> paramtro/mensaje SetLineCoding
 
mUsbDeviceConnection.controlTransfer(UsbConstants.USB_TYPE_CLASS | 0x01, 0x22, 0x1, 0, null, 0, 0);
// (UsbConstants.USB_TYPE_CLASS | 0x01) 0x21 -> Indica que se envia un parametro/mensaje del Host al Device (movil a la placa leonardo)
// 0x22 -> paramtro/mensaje SET_CONTROL_LINE_STATE (DTR) 
// 0x1  -> Activado.

Si tenéis curiosidad o quereis profundizar más en estos comandos del USB os dejo este enlace: http://www.usb.org/developers/devclass_docs/usbcdc11.pdf

PASO 6: Obtener datos

Siguiendo todos los pasos anteriores solo nos queda crear un clase AsynTask, que nosotros hemos llamado UpdateHumidityTemperature, que se encargara de obtener y tratar los datos que recibimos por el USB. La parte fundamental del código son estas lineas:

int bufferMaxLength=epIN.getMaxPacketSize();
ByteBuffer mBuffer = ByteBuffer.allocate(bufferMaxLength);
UsbRequest inRequest = new UsbRequest();
inRequest.initialize(mUsbDeviceConnection, epIN);
 
while(inRequest.queue(mBuffer, bufferMaxLength) == true){
    mUsbDeviceConnection.requestWait();
.....
.....
.....

Creamos e inicializamos una petición con UsbRequest en el enpoint de entrada de datos ( Arduino -> móvil ) , la siguiente instrucción «queque» indicaremos las variables de buffer donde se guardaran los datos que recibimos y por ultimo con requestWait esperaremos a rebir datos desde el USB.

Probando la App

Llego el momento de la verdad y si no hemos cometido fallos conectaremos la placa Arduino Leonardo al móvil con un cable USB OTG, arrancamos la aplicación «TestUSB2Android» y veremos la siguiente pantalla.

usbArduinoAndroid01

Pulsamos el botón conectar y nos el sistema nos solicitara que demos permiso para usar el USB

usbArduinoAndroid02

Pulsamos sobre aceptar y eureka!!!! ya tenemos en la pantalla del móvil la temperatura y humedad.

usbArduinoAndroid03

Si abrimos el LogCat, podemos ver como recibimos desde el Arduino la actualización de la temperatura y humedad.

Captura_de_pantalla_2014-06-21_a_las_19.09.25

Conclusión

Parece un proceso muy complicado, pero una vez lo hemos ello una vez se adopta muy bien la mecánica de su funcionamiento. Espero que os guste este microproyecto y que os aventureis ha hacer vuestros proyectos con conexión USB. Cualquier duda que tengais dejar un comentario, será bienvenido.

Fuente: http://www.jalcdeveloper.com/projects/chapters/arduino-android-usb-cap1/