2013年12月23日月曜日

USB Host に挑戦

アンドロイドのドキュメント:USB Host

USBディバイスはMbed を使いました。

参考サイト:kshoji:自作のUSBデバイスを、Androidで動かす

参考サイトに詳細に解説されています。

参考サイトの一部必要な所だけ取り出してみました。

とりあえず、Mbed に送信してLED4を点灯させてみる。
点灯と消灯の二つのボタンを置いて操作する。
USBディバイスを接続した時に使うアプリを聞いてくる。

回路図:アンドロイドとUSBホストケーブルで接続する
Mbed のソースコード:参考サイトのソースコードをそのまま使いました。
#include "mbed.h"
#include "USBHID.h"

// device
USBHID hid;

// read
HID_REPORT recv;
BusOut leds(LED1, LED2, LED3, LED4);

// write
HID_REPORT send;
AnalogIn input(p15);

int main(void)
{
    int lastData = 0;
    int currentData;

    send.length = 64;
    while (1) {
        // read
        hid.readNB(&recv);
        if (recv.length > 0) {
            leds = recv.data[0];
            
        }

        currentData = input.read_u16();
        if (currentData != lastData) {
            lastData = currentData;

            // write
            send.data[0] = (currentData >> 8) & 0xff;
            send.data[1] = currentData & 0xff;
            //hid.send(&send);
        }
        
    }
}

アンドロイドアプリのソースコード
エラー処理や例外・終了処理は考慮していない。
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.hidtest002"
    android:installLocation="preferExternal"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="12"
        android:targetSdkVersion="17" />

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name="com.example.hidtest002.MainActivity"
            android:label="@string/app_name" >

            <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>

res/xml/device_filter.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <!-- Interface class: USB HID -->
    <usb-device class="3" subclass="0" protocol="0" />
</resources>

MainActivity.java
package com.example.hidtest002;

import android.hardware.usb.UsbConstants;
import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbDeviceConnection;
import android.hardware.usb.UsbEndpoint;
import android.hardware.usb.UsbInterface;
import android.hardware.usb.UsbManager;
import android.os.Bundle;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;

public class MainActivity extends Activity implements OnClickListener {

 private static final String TAG = "HidTest";
 private TextView text;
 private UsbDeviceConnection deviceConnection = null;
 private UsbEndpoint outputEndpoint2;

 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);

  text = (TextView) findViewById(R.id.textView1);

  //  ボタンがクリックされた時の処理
  Button btn1 = (Button) findViewById(R.id.button1);
  btn1.setOnClickListener(this);
  Button btn2 = (Button) findViewById(R.id.button2);
  btn2.setOnClickListener(this);

  //  USB ディバイスが接続されていない時は終了
  if (!UsbManager.ACTION_USB_DEVICE_ATTACHED.equals(getIntent().getAction())) {
   finish();
  } else {
   // USB ディバイスを接続
   Intent intent = getIntent();
   UsbDevice device = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
   if (device != null) {
    Log.v(TAG, "UsbDevice がある");
    UsbInterface usbInterface = findUsbInterface(device);
    if (usbInterface != null) {
     // アタッチ
     UsbManager usbManager = (UsbManager) getSystemService(Context.USB_SERVICE);
     deviceConnection = usbManager.openDevice(device);
     deviceConnection.claimInterface(usbInterface, true);
    }
   }
  }

 }

 /*
  * UsbInterface の確認 
  */
 private UsbInterface findUsbInterface(UsbDevice device) {
  // TODO 自動生成されたメソッド・スタブ
  text.setText("UsbDevice.getDeviceName():" + device.getDeviceName() + "\n");
  int count = device.getInterfaceCount();// 4
  text.append("UsbDevice.getInterfaceCount():" + count + "\n");
  for (int i = 0; i < count; i++) {
   UsbInterface usbInterface = device.getInterface(i);

   UsbEndpoint inputEndpoint = null;
   UsbEndpoint outputEndpoint = null;

   text.append("**************************" + "\n");
   text.append(" UsbInterface.getEndpointCount():" + usbInterface.getEndpointCount() + "\n");
   if (usbInterface.getEndpointCount() >= 1) {
    for (int endpointIndex = 0; endpointIndex < usbInterface.getEndpointCount(); endpointIndex++) {
     UsbEndpoint endpoint = usbInterface.getEndpoint(endpointIndex);
     text.append(endpointIndex + ":");

     if ((endpoint.getType() == UsbConstants.USB_ENDPOINT_XFER_BULK || endpoint.getType() == UsbConstants.USB_ENDPOINT_XFER_INT)) {
      if (endpoint.getDirection() == UsbConstants.USB_DIR_OUT) {
       outputEndpoint = outputEndpoint == null ? endpoint : outputEndpoint;
       outputEndpoint2 = endpoint;
       text.append("  UsbEndpoint:getDirection()" + endpoint.getDirection() + "\n");// USB_DIR_OUT
      }
     }
    }

    if (inputEndpoint != null || outputEndpoint != null) {
     return usbInterface;
    }
   }
  }

  return null;
 }

 /*
  * ボタンが押された時の処理
  */
 @Override
 public void onClick(View v) {
  // TODO 自動生成されたメソッド・スタブ
  byte data1 = 0;
  byte data2 = 0;
  byte data3 = 0;
  byte data4 = 0;

  switch (v.getId()) {
  case R.id.button1:
   Log.i("buttonTest", "点灯ボタンが押された");
   data1 = 0;
   data2 = 0;
   data3 = 0;
   data4 = (byte) 8;
   break;
  case R.id.button2:
   Log.i("buttonTest", "消灯ボタンが押された");
   data1 = 0;
   data2 = 0;
   data3 = 0;
   data4 = 0;
   break;

  }
  outPutMbed(new byte[] { (byte) (data1 | data2 | data3 | data4) });

 }

 // Mbed に送信
 private void outPutMbed(byte[] bytes) {
  // TODO 自動生成されたメソッド・スタブ
  final int maxPacketSize = outputEndpoint2.getMaxPacketSize();
  for (int i = 0; i < bytes.length; i += maxPacketSize) {
   byte[] writeBuffer = new byte[maxPacketSize];
   int length = (bytes.length - i) < writeBuffer.length ? bytes.length - i : writeBuffer.length;
   System.arraycopy(bytes, i, writeBuffer, 0, length);
   deviceConnection.bulkTransfer(outputEndpoint2, writeBuffer, writeBuffer.length, 0);
  }
 }

 /*
  * メニューの処理
  */
 @Override
 public boolean onCreateOptionsMenu(Menu menu) {
  // Inflate the menu; this adds items to the action bar if it is present.
  getMenuInflater().inflate(R.menu.main, menu);
  return true;
 }

 @Override
 public boolean onOptionsItemSelected(MenuItem item) {
  switch (item.getItemId()) {
  case R.id.itemFinish:
   Log.v(TAG, "finish");
   finish();
   return true;
  }

  return false;
 }
}

activity_main.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context=".MainActivity" >

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical" >

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content" >

            <Button
                android:id="@+id/button1"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="点灯" />

            <Button
                android:id="@+id/button2"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="消灯" />

        </LinearLayout>

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical" >

            <TextView
                android:id="@+id/textView1"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="@string/hello_world" />

        </LinearLayout>

    </LinearLayout>

</RelativeLayout>