Corso su Android – Lavoriamo con la porta USB del telefono

arduino android usb

In molti, quando si parla di comunicazione tra smartphone ed un dispositivo elettronico, pensano subito ad una comunicazione via Bluetooth o via Ethernet. Android, però, mette a disposizione un altro interessante modo per interfacciarsi con i dispositivi: utilizzando la porta USB. Lo scopo dell’articolo è quello di far vedere come sfruttando le API messe a disposizione dal sistema operativo, sia possibile lo scambio di dati tra telefono ed Arduino, infatti l’idea è di creare uno sketch che tramite un segnale PWM varia l’intensità di luce di un led, dove per controllare la variazione di intensità andremo a realizzare una nostra applicazione Android.

Come prima cosa bisogna sapere che Android supporta una varietà di periferiche USB attraverso 2 modalità:

  • USB host: agisce come USB host e fornisce corrente al bus e fa un check sul numero di dispositivi connessi;
  • USB accessory: Il dispositivo connesso funziona come host e da corrente al bus.

Entrambe le modalità sono state introdotte dalle API 12, quindi serve un telefono o tablet  con sistema operativo maggiore o uguale alla 3.1. Noi ci concentreremo sulla prima modalità.
Come abbiamo detto l’idea è di controllare l’intensità di luce del led, per far questo nella nostra interfaccia utilizzeremo una SeekBar e 2 bottoni per abilitare/disabilitare il controllo della barra.

Cosa ci serve:

  • un telefono Android con SO maggiore o uguale 3.1 (ho usato un Samsung S2);
  • Arduino UNO (o altro tipo);
  • un cavo OTG per collegare Arduino al telefono.

Partiamo da Android creando un progetto: ArduinoUSBCommunication (ho usato API Level 15).
Il nostro layout main.xml risulta essere molto semplice: un LinearLayout con una SeekBar e due Button:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android            = "http://schemas.android.com/apk/res/android"
    android:layout_width    = "fill_parent"
    android:layout_height    = "fill_parent"
    android:orientation        = "vertical" >

    <SeekBar
        android:id                = "@+id/seekBar1"
        android:layout_width    = "match_parent"
        android:layout_height    = "wrap_content"
        android:max                = "255"
        android:progress        = "0"
        android:padding            = "40dp"
        />

    <Button
        android:id                = "@+id/idBtnEnablePWM"
        android:layout_width    = "wrap_content"
        android:layout_height    = "wrap_content"
        android:text            = "@string/enablePWM"
        android:layout_gravity    = "center_horizontal"/>
    
    <Button
        android:id                = "@+id/idBtnDisablePWM"
        android:layout_width    = "wrap_content"
        android:layout_height    = "wrap_content"
        android:text            = "@string/disablePWM"
        android:layout_gravity    = "center_horizontal"/>
    
</LinearLayout>

Prima di analizzare la nostra Activity andiamo a vedere cosa deve essere inserito all’interno del manifest:
prima di tutto dobbiamo garantire che il dispositivo supporti la modalità USB host, quindi inseriamo:

<uses-feature android:name="android.hardware.usb.host" />

Bisogna poi accertarsi che il livello minimo di API sia maggiore od uguale a 12, nel nostro caso risulta essere:

<uses-sdk
        android:minSdkVersion="13"
        android:targetSdkVersion="14" />

Dobbiamo definire un <intent-filter> per fare in modo che la nostra Activity riceva notifica quando un dispositivo viene connesso alla porta USB, in aggiunta va inserito un tag <meta-data> per identificare il dispositivo che viene agganciato:

<intent-filter>
  <action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"/>
</intent-filter>
<meta-data android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" 
   android:resource="@xml/device_filter" />

Dove device_filter.xml deve essere inserito sotto la cartella res/xml/ e contiene il vendor-id che rappresenta Arduino.

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <usb-device vendor-id="9025" />
</resources>

Quindi il nostro manifest risulterà essere:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.test"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="13"
        android:targetSdkVersion="14" />
    
    <uses-feature android:name="android.hardware.usb.host" />
    

    <application
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name" >
        <activity
            android:label="@string/app_name"
            android:name=".ArduinoActivity" android:launchMode="singleTask"
            android:screenOrientation="portrait">
             <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
              
            </intent-filter>
            <intent-filter>
              <action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
            </intent-filter>
            <meta-data android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" 
    android:resource="@xml/device_filter" />
        </activity>
    </application>

</manifest>

Una volta definiti i parametri nel manifest creiamo la nostra Activity: ArduinoActivity. Questa sarà l’Activity che verrà aperta nel momento in cui sarà connesso Arduino al telefono, quindi dovremo implementare un metodo che elenchi i dispositivi collegati:

private void discoverDevice(){
        mUsbManager = (UsbManager)getSystemService(Context.USB_SERVICE);
            
        UsbManager usbManager = (UsbManager)getSystemService(Context.USB_SERVICE);
        HashMap deviceList = usbManager.getDeviceList();
        Iterator deviceIterator = deviceList.values().iterator();

        Toast.makeText(this.getApplicationContext(), "Device Count : "+deviceList.size()
 + "", Toast.LENGTH_SHORT).show();
        UsbDevice usbDevice = null;
        while (deviceIterator.hasNext()) {
            
            usbDevice = (UsbDevice)deviceIterator.next();
            Toast.makeText(this.getApplicationContext(), "Product ID : " + 
usbDevice.getProductId() + "", Toast.LENGTH_SHORT).show();
        }
        if (usbDevice != null) initDevice(usbDevice);

    }

Questo sfrutta il metodo getDeviceList() della classe UsbManager, che mappa tutti i dispositivi connessi. Da questa cicliamo sui dispositivi connessi; nel nostro caso c’è solo Arduino, recuperiamo l’oggetto UsbDevice ed andiamo ad inizializzare il dispositivo:

    private void initDevice(UsbDevice mDevice){
        UsbDeviceConnection conn = mUsbManager.openDevice(mDevice);
                
        
        if (!conn.claimInterface(mDevice.getInterface(1), true)) {
            return;
        }
        
        // Arduino Serial usb Conv
        conn.controlTransfer(0x21, 34, 0, 0, null, 0, 0);
        conn.controlTransfer(0x21, 32, 0, 0, new byte[] { (byte) 0x80,
                0x25, 0x00, 0x00, 0x00, 0x00, 0x08 }, 7, 0);

        

        UsbInterface usbIf = mDevice.getInterface(1);
        for (int i = 0; i < usbIf.getEndpointCount(); i++) {
            if (usbIf.getEndpoint(i).getType() == UsbConstants.USB_ENDPOINT_XFER_BULK) {
                if (usbIf.getEndpoint(i).getDirection() == UsbConstants.USB_DIR_IN){
                     System.out.println(">>> EP_IN[" + i + "]");
                    epIN = usbIf.getEndpoint(i);
                }else{
                    System.out.println(">>> EP_OUT[" + i + "]");
                    epOUT = usbIf.getEndpoint(i);
                }
            }
        }
          

          arduinoController = new ArduinoConnection(epOUT, conn);
          new Thread(arduinoController).start();
          
    }

Per prima cosa apriamo una connessione con questa scheda

UsbDeviceConnection conn = mUsbManager.openDevice(mDevice);
// Arduino Serial usb Conv
        conn.controlTransfer(0x21, 34, 0, 0, null, 0, 0);
        conn.controlTransfer(0x21, 32, 0, 0, new byte[] { (byte) 0x80,
                0x25, 0x00, 0x00, 0x00, 0x00, 0x08 }, 7, 0);

Successivamente recuperiamo l’UsbEndPiont di output per mandare i dati ad Arduino. Lo scambio vero e proprio viene effettuato in un Thread separato, altrimenti la comunicazione dei dati con Arduino renderebbe inutilizzabile l’interfaccia grafica. Vediamo quindi come è fatta la classe ArduinoConnection

public class ArduinoConnection implements Runnable{
    public Handler mHandler;

    private volatile UsbEndpoint mOutUsbEndpoint;
    BroadcastReceiver mReceiver;
    private byte[] data = new byte[]{0x00};
    private volatile UsbDeviceConnection mUsbConnection;
   
    public ArduinoConnection(UsbEndpoint mOutUsbEndpoint,
                               UsbDeviceConnection mUsbConnection){
        this.mOutUsbEndpoint = mOutUsbEndpoint;
        this.mUsbConnection = mUsbConnection;
    }
    
    public void send( byte[] data) {
        this.data = data;
        System.out.println("send data");
    }
    
    @Override
    public void run(){
    System.out.println("Thread started");
        while (true){
            if (data != null){
                System.out.println("sending data");
                mUsbConnection.bulkTransfer(mOutUsbEndpoint, data, data.length, 0);
                System.out.println("data sent");
                data = null;
            }
        }
    }
}

che deve implementare Runnable e all’interno del metodo run invieremo i dati ad Arduino nel momento in cui vengono settati dalla nostra Activity (vedremo di seguito come) grazie al metodo send(byte[] data)
Nel metodo onCreate della nostra Activity, recuperiamo i due Button, che come detto sopra ci serviranno per abilitare o disabilitare l’invio dei dati ad Arduino. Con il Button di attivazione invieremo la lettera P per indicare ad Arduino di iniziare a leggere i dati ed invieremo la lettera N per indicare di smettere di interpretare i dati.
L’implementazione dei due Button risulterà essere:

        Button btnEnablePWM    = (Button)findViewById(R.id.idBtnEnablePWM);
        btnEnablePWM.setOnClickListener(new OnClickListener() {
            
            @Override
            public void onClick(View v) {
                
                String s = "P";
               
                try {
                    byte[] b = s.getBytes("US-ASCII");
                    System.out.println("EnablePWM");
                    

                    if (arduinoController != null) arduinoController.send(b);
                } catch (Exception e) {
                    
                    e.printStackTrace();
                }
            }
        });
        
        
        Button btnDisablePWM    = (Button)findViewById(R.id.idBtnDisablePWM);
        btnDisablePWM.setOnClickListener(new OnClickListener() {
            
            @Override
            public void onClick(View v) {
                
                String s = "N";
               
                try {
                    byte[] b = s.getBytes("US-ASCII");

                

                    if (arduinoController != null) arduinoController.send(b);
                } catch (Exception e) {
                    
                    e.printStackTrace();
                }
            }
        });

Non ci resta che recuperare la SeekBar ed implementare il metodo OnSeekBarChangeListener e inseriremo il codice da inviare ad Arduino nell’implementazione del metodo onProgressChanged cosi da inviare dati ogni volta che c’è un cambiamento nella SeekBar. Per farlo utilizziamo il metodo send illustrato precedentemente della classe ArduinoController. Ma cosa stiamo mandando ad Arduino?
Gli inviamo il valore della SeekBar che varia da un minimo di 0 ad un massimo di 255 seguito dalla lettera “a” che ci servirà nello sketch per capire quando la serie di byte inviati che rappresentano un valore è terminata:

SeekBar seekBar = (SeekBar)findViewById(R.id.seekBar1);
        
        seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
            @Override
            public void onStopTrackingTouch(SeekBar seekBar) {
                
            }
            @Override
            public void onStartTrackingTouch(SeekBar seekBar) {

            }
            
           @Override
           public void onProgressChanged(SeekBar seekBar,int progress,boolean fromUser){
                
                if(fromUser){
                    
                    int i = (int)progress;
                    sValue = i + "a";
                
                    try {
                        byte[] b = sValue.getBytes("US-ASCII");
                        if(arduinoController != null) arduinoController.send(b);
                        
                    } catch (UnsupportedEncodingException e) {
                        e.printStackTrace();
                    }
                    
                }
            }
        });

Passiamo ora al codice da inserire su Arduino dove viene utilizzato il PIN 11 per collegare il led e le due variabili:

const char GAME_NO        = 'N';
const char GAME_PWM       = 'P';

per far in modo alla SeekBar di agire o meno sul led.
Nel loop, viene controllato come prima cosa se quello che arriva dalla seriale è un numero e viene usata la variabile strValue per concatenare i valori numerici che arrivano dal telefono.
Se invece il dato che arriva dalla seriale è un carattere, allora viene controllato se è arrivata una P o una N e viene settata la variabile currentGame per abilitare o meno il led, altrimenti se arriva la lettera “a” indica la fine della seguenza da inviare al PIN 11 quindi viene convertita in intero dutyCycle = atoi(strValue); e viene inviata al PIN 11 analogWrite(LED_PWM_11, dutyCycle);
dopo di che vengono resettati i valori utilizzati.

#define LED_PWM_11 11

const char GAME_NO        = 'N';
const char GAME_PWM       = 'P';
const char STOP_SEQUENCE_OF_BYTE  = 'a';
int   dutyCycle;       //
char strValue[6];      // stringa di valori letti
int index = 0;         // indice dei valori letti
char currentGame;  // indica il gioco di luci corrente, inizialmente nessuno

void setup()
{
 Serial.begin(9600);
 currentGame  = GAME_NO;
 pinMode(LED_PWM_11, OUTPUT);   //imposta il pin come output
}

void loop()
{
  if( Serial.available())
  {
    char ch = Serial.read();
    
    //controlla se è un carattere
    if (!isDigit(ch)){
      
      //controlla quale gioco di luce è selezionato
      if (ch == GAME_PWM){
        currentGame = GAME_PWM;

      }else if (ch == GAME_NO){
        currentGame = GAME_NO;
       
        analogWrite(LED_PWM_11, 0);
          
      }else if (ch == STOP_SEQUENCE_OF_BYTE){
   
        dutyCycle = atoi(strValue);  // use atoi to convert the string to an int
        analogWrite(LED_PWM_11, dutyCycle);   // manda al led il valore letto dalla
 seriale
        
        resetValue();
      }
    }else {
      //è un numero
     
      if (currentGame == GAME_PWM){
        if(index < 5 && isDigit(ch) ){
           strValue[index++] = ch; // add the ASCII character to the string;
          }
          
      } //end if (currentGame == GAME_PWM)
     
      else {
        //non deve fare niente
      }
        
    }//end if( isDigit(ch))
 
  }
 
}  

void resetValue(){
   index = 0;
   for (int i=0; i<5; i++){
     strValue[i] = 0;
   }
}

Notare che quando viene collegato Arduino con il telefono, verrà mostrato un popup del sistema operativo che chiede se aprire la nostra applicazione quando il dispositivo è connesso, acconsentire e se l’applicazione era chiusa verrà aperta.
Non ci resta che provare il tutto!

Conclusioni

Abbiamo quindi visto come è semplice inviare dati ad Arduino connettendo il telefono tramite la porta USB. Come esercizio si potrebbe pensare di aggiungere un altro led e far in modo di abilitare uno o l’altro attraverso l’applicazione sul telefono, per esempio introducendo un altro Button che abiliti il secondo led e disabiliti il primo così da pilotarli con la stessa SeekBar, oppure introducendone una nuova così da averne una per ciascuno. Buon divertimento.

 

Quello che hai appena letto è un Articolo Premium reso disponibile affinché potessi valutare la qualità dei nostri contenuti!

 

Gli Articoli Tecnici Premium sono infatti riservati agli abbonati e vengono raccolti mensilmente nella nostra rivista digitale EOS-Book in PDF, ePub e mobi.
volantino eos-book1
Vorresti accedere a tutti gli altri Articoli Premium e fare il download degli EOS-Book? Allora valuta la possibilità di sottoscrivere un abbonamento a partire da € 2,95!
Scopri di più

3 Comments

  1. Ivan.Tarozzi 22 luglio 2014
  2. FabrizioG 24 luglio 2014
  3. xDuKeN xDuKeN 29 gennaio 2015

Leave a Reply