Androidのメモとか

ポキオの日記です。今日も遅延してない。

AndroidのUSBホスト(OTG)機能を使って、接続されたArduinoとシリアル通信してみる話

今回はシリアル通信します

relativelayout.hatenablog.com

前回は、なんとなくAndroidのUSBホスト(OTG)機能をつかって、接続されているデバイスの情報を引っこ抜いてみました。今回は、もう少し踏み込んで、接続されたArduinoとシリアル通信してみます。

ArduinoAndroid のUSBホスト機能を利用した通信にはオープンなライブラリが多数あります。

github.com

github.com

今回は、このようなライブラリを利用せずに、1からAndroidの公開APIだけで実装していきます。

まずはAndroid

Manifestですが、前回同様以下の1文のみ変更します。

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

いわゆるIntentFilterの追加はしません。必ずしも必要というわけではなく、USB機器が接続されたら自動でActivityを立ち上げる必要がある時だけです。

Activityではこんな実装をしました。 ポイントは3つ。

  • 接続されたUSBデバイスと通信する際はPermissionが必要。Android MのRuntimePermissionとはことなり、UsbManager#requestPermission()を使用する。デバイスごとにPermissionが必要で、デバイスを切断するとPermissionはクリアされ、再度接続した際はもう一度Permissionを取得する必要がある。
  • UsbManager#requestPermission()の結果は、引数のPendingIntentで記載された呼び出し先で取得できるIntentに詰まっている。この引数をnullにすると、Permission取得後にAndroidのネイティブアプリが落ちる(笑)ので、なにかしらPendingIntentを詰める必要がある。
  • シリアル通信を開始する前に、UsbDeviceConnection#controlTransfer() でコントロール転送を行いシリアル通信を開始できるように準備をする必要がある。ここで転送する値はUSBデバイス(つまりUSB-シリアル変換を行うチップ)によってことなる。今回はArduino決め打ちでUsbDevice#getVendorId()でベンダーIDを見て、Arduino(=9025)であれば接続を行うようにしている。

取り敢えず、テストということで、全部Activityクラスに書いています。本当はちゃんとクラス分けして下さい。また、Buttonが連打された時の排他処理は入っていません(笑)

package com.android.usbhost;

import android.app.PendingIntent;
import android.content.Intent;
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.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

import java.util.HashMap;

public class MainActivity extends AppCompatActivity {

    private TextView mTextView;

    private Button mButton;

    private UsbManager mUsbManager;

    private UsbDevice mUsbDevice;

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

    public void onResume() {
        super.onResume();

        mTextView = (TextView)findViewById(R.id.textview);
        mButton = (Button)findViewById(R.id.button);
        mUsbManager = (UsbManager)getSystemService(USB_SERVICE);

        updateList();
        mButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (mUsbDevice == null) {
                    return;
                }

                if (!mUsbManager.hasPermission(mUsbDevice)) {
                    mUsbManager.requestPermission(mUsbDevice,
                            PendingIntent.getBroadcast(MainActivity.this, 0, new Intent("なにか"), 0));
                    return;
                }

                connectDevice();
            }
        });
    }

    public void onPause() {
        super.onPause();
        mUsbDevice = null;
        mButton.setOnClickListener(null);
    }

    private void updateList() {
        HashMap<String, UsbDevice> deviceList = mUsbManager.getDeviceList();

        if (deviceList == null || deviceList.isEmpty()) {
            mTextView.setText("no device found");
        } else {
            String string = "";

            for (String name : deviceList.keySet()) {
                string += name;

                if (deviceList.get(name).getVendorId() == 9025) {
                    string += " (Arduino)\n";
                    mUsbDevice = deviceList.get(name);
                } else {
                    string += "\n";
                }
            }

            mTextView.setText(string);
        }
    }

    private void connectDevice() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                UsbDeviceConnection connection = mUsbManager.openDevice(mUsbDevice);

                if (!connection.claimInterface(mUsbDevice.getInterface(1), true)) {
                    connection.close();
                    return;
                }

                connection.controlTransfer(0x21, 34, 0, 0, null, 0, 0);
                connection.controlTransfer(0x21, 32, 0, 0, new byte[] {
                        (byte)0x80, 0x25, 0x00, 0x00, 0x00, 0x00, 0x08
                }, 7, 0);

                UsbEndpoint epIN = null;
                UsbEndpoint epOUT = null;

                UsbInterface usbIf = mUsbDevice.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)
                            epIN = usbIf.getEndpoint(i);
                        else
                            epOUT = usbIf.getEndpoint(i);
                    }
                }

                connection.bulkTransfer(epOUT, "1".getBytes(), 1, 0);
                connection.close();
            }
        }).start();
    }
}

Arduino側の実装

こっちは本当に単純。Serialで何かしらのInputがあったら、LEDをチカチカさせます。

void setup() {
  pinMode(2, OUTPUT);
  Serial.begin(9600);
}

void loop() {
  if (Serial.read() == -1) {
    digitalWrite(2, LOW);
    delay(1000);
  } else {
    digitalWrite(2, HIGH);
    delay(250);
    digitalWrite(2, LOW);
    delay(250);
    digitalWrite(2, HIGH);
    delay(250);
    digitalWrite(2, LOW);
    delay(250);
  }

  digitalWrite(2, LOW);
}

いざ動作させん

Arduino Android Usb Host Otg

Arduino互換機のFreaduinoを使用。LEDは抵抗挟まず直挿し・・・本当はダメです(笑)

Arduino Android Usb Host Otg

OTGアダプターを介して、Android端末とArduinoを接続させます。

Arduino Android Usb Host Otg

ボタンを押すと、チカチカします。成功です!

「Androidのメモとか」は、Amazon.co.jpを宣伝しリンクすることによってサイトが紹介料を獲得できる手段を提供することを目的に設定されたアフィリエイト宣伝プログラムである、Amazonアソシエイト・プログラムの参加者です。

このブログは個人的なメモ書きであったり、考えを書く場所であります。執筆者の所属する団体や企業のコメントや意向とは無関係であります。また、このブログは必ずしも正しいことが書かれているとは限らず、誤字脱字や意図せず誤った情報を載せる場合がありえます。それが原因で読者が不利益を被ったとしても、執筆者はいかなる責任も負いません。ありがとうございます。