コンテンツへスキップ

[Arduino] USB Host Library2.0をADB通信に対応させる(Android1.6端末と通信)

Android端末とArduino等のマイコンをUSB接続する手段として一般的にはADKを使用しますが、ADK未対応な古い端末(OSバージョン1.6とか)は代わりにADBを使います。ADB通信に対応したarduino側ライブラリもありますが1:1の通信しかサポートしていません。そこで既存ライブラリを少し改造してUSB-HUBに対応させてみました。これを使えば1:他の構成が実現出来るので用途が広がります。

 

環境

Android端末は毎度おなじみIS01です。通信にADBを利用することで、ふるーいIS01(Android 1.6)に新たな利用価値を見出したいと思います。これが上手くいけばインジェクションコントローラ用のロガーとしても使えるハズ。。。

  • SHARP IS01(Android1.6)
  • Arduono uno互換品(Atmega328p)
  • USB Host Sheild2.0
  • USB2.0ハブ
IS01とArduino/USB Host Sheild2.0
USB-HUBを使って複数機器と通信出来れば、もうまともに使えないような古いAndroid端末で色々と楽しそうなことが出来そうです

 

Arduino uno R3互換品
Arduino uno R3の互換品

 

Arduino USB Host sheild2.0互換品
Arduino USB Host Shield2.0の互換品

 

8ポートUSB-HUB
安かった8ポートUSB-HUB、外部電源が使えますがちょっと癖があります

 

USB機器の構成

実現したいUSB機器の構成を以下の図に示します。Arduino uno+USB Host ShieldがUSBホストとなり、USB-HUBを介してAndroid端末やマイコンが接続されます。マイコンから吸い上げたデータをAndroid端末に送る、Android端末から各マイコンに指示を出すといった使い方を想定しています(個人的には簡易インジェクションコントローラと組み合わせてエンジン状態のログを取るのに使いたい)。

USB-HUBを使ってADB接続する構成
USB機器の構成図

この構成を実現するには、USBホストとなるArduinoに以下の機能を持たせる必要があります。

  • USB-HUBサポート
  • ADB通信サポート(android1.6端末と通信用)
  • USBシリアル通信サポート(他のマイコンとの通信用、本記事では使いません)

 

ライブラリに機能追加する

既存ライブラリを探す

まず初めにADBに対応したArduino向けライブラリをゼロから作成するのは私の技術力+根性的に無理ですので、既存のライブラリに機能追加する形をとりたいと思います。私が探した限りでは使えそうなライブラリがふたつありました。

  • USB Host Library 2.0
  • microbridge

USB Host libraryはADKをサポートしていて最近のAndroid端末とは問題なく接続可能です。USB-HUB、USB-BluetoothやPS3コントローラなど多くのデバイスをサポートしておりとても便利なライブラリです。ただしADBは未対応なためandroid1.6端末とは通信出来ません。

microbridgeはアプリケーションデバッグ等で利用するADBを使ってAndroid端末と通信します。これはAndroidのバージョンに依存性しません。ただし他のデバイスサポートは無くUSB-HUBも未サポートです。

Arduino+USB Host Shield2.0
Arduino+USB Host Shield2.0の構成です。USB Host Library2.0、microbridgeともにこの構成で使用可能です

 

USB Host Library2.0をベースに機能追加する

欲しいのはUSB-HUBとADB(+色々なデバイス)に対応しているライブラリですが、残念ながら都合の良いものはありません。そこで元々機能豊富なUSB Host Library2.0にmicrobridgeのADB機能を移植することにしました。

具体的には以下の2ファイルをUSB Host Library2.0に追加します。元からあるライブラリのコードには変更を加えていません。

  • adb.cpp
  • adb.h

"adb.h"に接続するandroidデバイスのVID,PIDが定義してありますので、接続するデバイスのものに書き換えてください(ディスクリプタテーブルでADBプロトコルのチェックしているのでVID/PIDは確認しなくてもいいような気がするのですが、ほかの処理見るとみんなやっているので一応確認しています)。

 

"adb.cpp"


#include "adb.h"

const uint8_t ADB::epDataInIndex = 1;
const uint8_t ADB::epDataOutIndex = 2;

ADB::ADB(USB *p, const char* str) :
pUsb(p),
connectionString(str),
lastConnectionAttempt(0),
status(ADB_CLOSED),
bAddress(0),
bConfNum(0),
bNumEP(1),
ready(false),
connected(false),
reconnect(true){
// initialize endpoint data structures
for(uint8_t i = 0; i < ADB_MAX_ENDPOINTS; i++) {
epInfo[i].epAddr = 0;
epInfo[i].maxPktSize = (i) ? 0 : 8;
epInfo[i].bmSndToggle = 0;
epInfo[i].bmRcvToggle = 0;
// epInfo[i].bmNakPower = (i) ? USB_NAK_NOWAIT : USB_NAK_MAX_POWER;
epInfo[i].bmNakPower = USB_NAK_MAX_POWER;
}//for(uint8_t i=0; i<ADK_MAX_ENDPOINTS; i++...

localID = connectionLocalId++;

// register in USB subsystem
if(pUsb) {
pUsb->RegisterDeviceClass(this); //set devConfig[] entry
}
}

uint8_t ADB::ConfigureDevice(uint8_t parent, uint8_t port, bool lowspeed) {
return Init(parent, port, lowspeed); // Just call Init. Yes, really!
}

/* Connection initialization of an Android phone */
uint8_t ADB::Init(uint8_t parent, uint8_t port, bool lowspeed) {
uint8_t buf[sizeof (USB_DEVICE_DESCRIPTOR)];
USB_DEVICE_DESCRIPTOR * udd = reinterpret_cast<USB_DEVICE_DESCRIPTOR*>(buf);
uint8_t rcode;
uint8_t num_of_conf; // number of configurations
UsbDevice *p = NULL;
EpInfo *oldep_ptr = NULL;

// get memory address of USB device address pool
AddressPool &addrPool = pUsb->GetAddressPool();

USBTRACE("\r\nADB Init");

// check if address has already been assigned to an instance
if(bAddress) {
USBTRACE("\r\nAddress in use");
return USB_ERROR_CLASS_INSTANCE_ALREADY_IN_USE;
}

// Get pointer to pseudo device with address 0 assigned
p = addrPool.GetUsbDevicePtr(0);

if(!p) {
USBTRACE("\r\nAddress not found");
return USB_ERROR_ADDRESS_NOT_FOUND_IN_POOL;
}

if(!p->epinfo) {
USBTRACE("epinfo is null\r\n");
return USB_ERROR_EPINFO_IS_NULL;
}

// Save old pointer to EP_RECORD of address 0
oldep_ptr = p->epinfo;

// Temporary assign new pointer to epInfo to p->epinfo in order to avoid toggle inconsistence
p->epinfo = epInfo;

p->lowspeed = lowspeed;

// Get device descriptor
rcode = pUsb->getDevDescr(0, 0, sizeof (USB_DEVICE_DESCRIPTOR), (uint8_t*)buf);

// Restore p->epinfo
p->epinfo = oldep_ptr;

if(rcode) {
goto FailGetDevDescr;
}

// Allocate new address according to device class
bAddress = addrPool.AllocAddress(parent, false, port);

// Extract Max Packet Size from device descriptor
epInfo[0].maxPktSize = udd->bMaxPacketSize0;

// Assign new address to the device
rcode = pUsb->setAddr(0, 0, bAddress);
if(rcode) {
p->lowspeed = false;
addrPool.FreeAddress(bAddress);
bAddress = 0;
//USBTRACE2("setAddr:",rcode);
return rcode;
}//if (rcode...

//USBTRACE2("\r\nAddr:", bAddress);
// Spec says you should wait at least 200ms.
delay(300);

p->lowspeed = false;

//get pointer to assigned address record
p = addrPool.GetUsbDevicePtr(bAddress);
if(!p) {
return USB_ERROR_ADDRESS_NOT_FOUND_IN_POOL;
}

p->lowspeed = lowspeed;

// Assign epInfo to epinfo pointer - only EP0 is known
rcode = pUsb->setEpInfoEntry(bAddress, 1, epInfo);
if(rcode) {
goto FailSetDevTblEntry;
}

if(udd->idVendor == ADB_VID && udd->idProduct == ADB_PID){
USBTRACE("\r\nVID,PID matched.");
num_of_conf = udd->bNumConfigurations;

for(uint8_t i = 0; i < num_of_conf; i++) {
// ConfigDescParser < 0, 0, 0, 0 > confDescrParser(this);
ConfigDescParser < ADB_CLASS, ADB_SUBCLASS, ADB_PROTOCOL, CP_MASK_COMPARE_ALL > confDescrParser(this);

delay(1);
rcode = pUsb->getConfDescr(bAddress, 0, i, &confDescrParser);
#if defined(XOOM)
//added by Jaylen Scott Vanorden
if(rcode) {
USBTRACE2("\r\nGot 1st bad code for config: ", rcode);
// Try once more
rcode = pUsb->getConfDescr(bAddress, 0, i, &confDescrParser);
}
#endif
if(rcode) {
goto FailGetConfDescr;
}
if(bNumEP > 2) {
break;
}
} // for (uint8_t i=0; i<num_of_conf; i++...
}else{
USBTRACE("VID,PID not matched.");
rcode = -1;
goto Fail;
}

if(bNumEP == 3) {
rcode = pUsb->setEpInfoEntry(bAddress, 3, epInfo);
if(rcode) {
goto FailSetDevTblEntry;
}
}

// Set Configuration Value
rcode = pUsb->setConf(bAddress, 0, bConfNum);
if(rcode) {
goto FailSetConfDescr;
}

USBTRACE("\r\nConfiguration successful");
ready = true;
return 0;
/* diagnostic messages */
FailGetDevDescr:
#ifdef DEBUG_USB_HOST
NotifyFailGetDevDescr(rcode);
goto Fail;
#endif

FailSetDevTblEntry:
#ifdef DEBUG_USB_HOST
NotifyFailSetDevTblEntry(rcode);
goto Fail;
#endif

FailGetConfDescr:
#ifdef DEBUG_USB_HOST
NotifyFailGetConfDescr(rcode);
goto Fail;
#endif

FailSetConfDescr:
#ifdef DEBUG_USB_HOST
NotifyFailSetConfDescr(rcode);
goto Fail;
#endif

Fail:
Release();
return rcode;
}
uint8_t ADB::Release()
{
pUsb->GetAddressPool().FreeAddress(bAddress);

bNumEP = 1;

bAddress = 0;
ready = false;
connected = false;
connecting = false;

status = ADB_CLOSED;

return 0;
}
uint8_t ADB::Poll()
{
adb_message message;

if(ready){
showStatus();
}else{
return 0;
}

// if(!connected){
if(!connected && !connecting){
delay(1000);
ADB::writeStringMessage(A_CNXN, 0x01000000, 4096, (char*)"host::microbridge");
connecting=true;
delay(500);
}else if(connected && !connecting){
ADB::openClosedConnections();
}

// Check for an incoming ADB message.
if (!ADB::pollMessage(&message, true))
return 0;

// Handle a response from the ADB device to our CONNECT message.
if (message.command == A_CNXN)
ADB::handleConnect(&message);

if (status!=ADB_UNUSED && localID==message.arg1)
{
switch(message.command)
{
case A_OKAY:
ADB::handleOkay(&message);
break;
case A_CLSE:
ADB::handleClose();
break;
case A_WRTE:
ADB::handleWrite(&message);
break;
default:
USBTRACE2("undefined command:", message.command);
break;
}
}

return 0;
}
/* Extracts bulk-IN and bulk-OUT endpoint information from config descriptor */
void ADB::EndpointXtract(uint8_t conf, uint8_t iface, uint8_t alt, uint8_t proto, const USB_ENDPOINT_DESCRIPTOR *pep)
{
//added by Yuuichi Akagawa
if(bNumEP == 3) {
return;
}

bConfNum = conf;

if((pep->bmAttributes & 0x02) == 2) {
USBTRACE2("\r\nbEndpointAddress:", pep->bEndpointAddress);
uint8_t index = ((pep->bEndpointAddress & 0x80) == 0x80) ? epDataInIndex : epDataOutIndex;
// Fill in the endpoint info structure
epInfo[index].epAddr = (pep->bEndpointAddress & 0x0F);
epInfo[index].maxPktSize = (uint8_t)pep->wMaxPacketSize;

bNumEP++;
}
}

/**
* Poll an ADB message.
* @param message on success, the ADB message will be returned in this struct.
* @param poll true to poll for a packet on the input endpoint, false to wait for a packet. Use false here when a packet is expected (i.e. OKAY in response to WRTE)
* @return true iff a packet was successfully received, false otherwise.
*/
bool ADB::pollMessage(adb_message * message, bool poll)
{
uint8_t buf[ADB_USB_PACKETSIZE];
uint16_t bytesRead = sizeof(buf);

USBTRACE("\r\nADB::pollMessage()");

// Poll a packet from the USB
ADB::RcvData((uint16_t *)&bytesRead, buf);
USBTRACE2("byteRead:",bytesRead);
// Check if the USB in transfer was successful.
if (bytesRead<0){
return false;
}

// Check if the buffer contains a valid message
memcpy((void*)message, (void*)buf, sizeof(adb_message));

// If the message is corrupt, return.
if (message->magic != (message->command ^ 0xffffffff))
{
#ifdef DEBUG
serialPrintf("Broken message, magic mismatch, %d bytes\n", bytesRead);
return false;
#endif
}

// Check if the received number of bytes matches our expected 24 bytes of ADB message header.
if (bytesRead != sizeof(adb_message)) return false;

return true;
}

/**
* Sends an ADB OPEN message for any connections that are currently in the CLOSED state.
*/
void ADB::openClosedConnections()
{
uint32_t timeSinceLastConnect;

timeSinceLastConnect = millis() - lastConnectionAttempt;
if (status==ADB_CLOSED && timeSinceLastConnect>ADB_CONNECTION_RETRY_TIME)
{
// Issue open command.
ADB::writeStringMessage(A_OPEN, localID, 0, connectionString);

// Record the last attempt time
lastConnectionAttempt = millis();
status = ADB_OPENING;
}
}

/**
* Handles and ADB OKAY message, which represents a transition in the connection state machine.
*
* @param message ADB message struct.
*/
void ADB::handleOkay(adb_message * message)
{
// Check if the OKAY message was a response to a CONNECT message.
if (status==ADB_OPENING)
{
status = ADB_OPEN;
remoteID = message->arg0;

// ADB::fireEvent(connection, ADB_CONNECTION_OPEN, 0, NULL);
}

// Check if the OKAY message was a response to a WRITE message.
if (status == ADB_WRITING)
status = ADB_OPEN;
}

/**
* Handles an ADB CLOSE message, and fires an ADB event accordingly.
*/
void ADB::handleClose()
{
// Check if the CLOSE message was a response to a CONNECT message.
// if (status==ADB_OPENING)
// ADB::fireEvent(connection, ADB_CONNECTION_FAILED, 0, NULL);
// else
// ADB::fireEvent(connection, ADB_CONNECTION_CLOSE, 0, NULL);

// Connection failed
if (reconnect)
status = ADB_CLOSED;
else
status = ADB_UNUSED;
}

/**
* Handles an ADB WRITE message.
*
* @param message ADB message struct.
*/
void ADB::handleWrite(adb_message * message)
{
uint32_t bytesLeft = message->data_length;
uint8_t buf[ADB_USB_PACKETSIZE];
ConnectionStatus previousStatus;
int bytesRead;

previousStatus = status;

status = ADB_RECEIVING;
dataRead = 0;
dataSize = message->data_length;

while (bytesLeft>0)
{
int len = bytesLeft < ADB_USB_PACKETSIZE ? bytesLeft : ADB_USB_PACKETSIZE;

// Read payload
ADB::RcvData(&len, buf);

// Break out of the read loop if there's no data to read :(
if(len <= 0) break;

dataRead += len;
// ADB::fireEvent(connection, ADB_CONNECTION_RECEIVE, len, buf);

bytesLeft -= bytesRead;
}

// Send OKAY message in reply.
bytesRead = writeEmptyMessage(A_OKAY, message->arg1, message->arg0);

status = previousStatus;
}
/**
* Handles an ADB connect message. This is a response to a connect message sent from our side.
* @param message ADB message.
*/
void ADB::handleConnect(adb_message * message)
{
uint8_t buf[MAX_BUF_SIZE];
uint16_t len;

// Read payload (remote ADB device ID)
len = message->data_length < MAX_BUF_SIZE ? message->data_length : MAX_BUF_SIZE;
ADB::RcvData(&len, buf);

// Signal that we are now connected to an Android device (yay!)
connected = true;
connecting = false;

// Fire event.
// ADB::fireEvent(NULL, ADB_CONNECT, len, buf);
}

/**
* Writes an ADB command with a string as payload.
*
* @param command ADB command.
* @param arg0 first ADB argument (command dependent).
* @param arg0 second ADB argument (command dependent).
* @param str payload string.
* @return error code or 0 for success.
*/
int ADB::writeStringMessage(uint32_t command, uint32_t arg0, uint32_t arg1, char * str)
{
USBTRACE("\r\nADB::writeStringMessage()");
return ADB::writeMessage(command, arg0, arg1, strlen(str) + 1, (uint8_t*)str);
}

/**
* Writes an empty message (without payload) to the ADB device.
*
* @param command ADB command.
* @param arg0 first ADB argument (command dependent).
* @param arg0 second ADB argument (command dependent).
* @return error code or 0 for success.
*/
int ADB::writeEmptyMessage(uint32_t command, uint32_t arg0, uint32_t arg1)
{
adb_message message;

message.command = command;
message.arg0 = arg0;
message.arg1 = arg1;
message.data_length = 0;
message.data_check = 0;
message.magic = command ^ 0xffffffff;

#ifdef DEBUG
serialPrint("OUT << "); adb_printMessage(&message);
#endif

// return USB::bulkWrite(device, sizeof(adb_message), (uint8_t*)&message);
return ADB::SndData(sizeof(message), (uint8_t*)&message);
}
/**
* Writes an ADB message with payload to the ADB device.
*
* @param command ADB command.
* @param arg0 first ADB argument (command dependent).
* @param arg0 second ADB argument (command dependent).
* @param length payload length.
* @param data command payload.
* @return error code or 0 for success.
*/
int ADB::writeMessage(uint32_t command, uint32_t arg0, uint32_t arg1, uint32_t length, uint8_t * data)
{
adb_message message;
uint32_t count, sum = 0;
uint8_t * x;
uint8_t rcode;

// Calculate data checksum
count = length;
x = data;
while(count-- > 0) sum += *x++;

// Fill out the message record.
message.command = command;
message.arg0 = arg0;
message.arg1 = arg1;
message.data_length = length;
message.data_check = sum;
message.magic = command ^ 0xffffffff;

#ifdef DEBUG
serialPrint("OUT << "); adb_printMessage(&message);
#endif

rcode = ADB::SndData(sizeof(adb_message), (uint8_t*)&message);
if (rcode) return rcode;

rcode = ADB::SndData(length, data);
return rcode;
}
/**
* Write a set of bytes to an open ADB connection.
*
* @param length number of bytes to transmit.
* @param data data to send.
* @return number of transmitted bytes, or -1 on failure.
*/
int ADB::write(uint16_t length, uint8_t * data)
{
int ret;

// First check if we have a working ADB connection
if (!connected) return -1;

// Check if the connection is open for writing.
if (status != ADB_OPEN) return -2;

// Write payload
ret = ADB::writeMessage(A_WRTE, localID, remoteID, length, data);
if (ret==0)
status = ADB_WRITING;

return ret;
}
uint8_t ADB::RcvData(uint16_t *bytes_rcvd, uint8_t *dataptr) {
uint8_t rcode = 0;
USBTRACE("\r\nADB::RcvData()");
USBTRACE2("Addr: ", bAddress );
USBTRACE2("EP: ",epInfo[epDataInIndex].epAddr);
rcode = pUsb->inTransfer(bAddress, epInfo[epDataInIndex].epAddr, bytes_rcvd, dataptr);
USBTRACE2("rcode: ",rcode);
return rcode;
}

uint8_t ADB::SndData(uint16_t nbytes, uint8_t *dataptr) {
uint8_t rcode = 0;
USBTRACE("\r\nADB::SndData()");
USBTRACE2("Addr: ", bAddress );
USBTRACE2("EP: ",epInfo[epDataOutIndex].epAddr);
USBTRACE2("nbytes: ", nbytes);
rcode = pUsb->outTransfer(bAddress, epInfo[epDataOutIndex].epAddr, nbytes, dataptr);
USBTRACE2("rcode: ",rcode);
return rcode;
}
void ADB::showStatus()
{
USBTRACE("\r\n-------[ADB Status]------------");
USBTRACE2("\r\nlocalID:", localID);
USBTRACE2("remoteID:", remoteID);
USBTRACE2("connected:", connected);
USBTRACE2("connecting:", connecting);
USBTRACE2("status:", status);
}

 

"adb.h"


#ifndef __adb_h__
#define __adb_h__

#include "Usb.h"

#define ADB_VID 0x04DD //SHARP
#define ADB_PID 0x9337 //IS01

#define XOOM //enables repeating getProto() and getConf() attempts
//necessary for slow devices such as Motorola XOOM
//defined by default, can be commented out to save memory

#define MAX_PAYLOAD 4096
#define MAX_BUF_SIZE 256

#define A_SYNC 0x434e5953
#define A_CNXN 0x4e584e43
#define A_OPEN 0x4e45504f
#define A_OKAY 0x59414b4f
#define A_CLSE 0x45534c43
#define A_WRTE 0x45545257

#define ADB_CLASS 0xff
#define ADB_SUBCLASS 0x42
#define ADB_PROTOCOL 0x1

#define ADB_USB_PACKETSIZE 0x40
#define ADB_CONNECTION_RETRY_TIME 1000

#define ADB_MAX_ENDPOINTS 3 //endpoint 0, bulk_IN, bulk_OUT

typedef struct
{
// Command identifier constant
uint32_t command;

// First argument
uint32_t arg0;

// Second argument
uint32_t arg1;

// Payload length (0 is allowed)
uint32_t data_length;

// Checksum of data payload
uint32_t data_check;

// Command ^ 0xffffffff
uint32_t magic;

} adb_message;

typedef enum
{
ADB_UNUSED = 0,
ADB_CLOSED,
ADB_OPEN,
ADB_OPENING,
ADB_RECEIVING,
ADB_WRITING
} ConnectionStatus;

static int connectionLocalId = 1;

class ADB;
class ADB : public USBDeviceConfig, public UsbConfigXtracter{
private:
const char* connectionString;
uint32_t localID, remoteID;
uint32_t lastConnectionAttempt;
uint16_t dataSize, dataRead;
ConnectionStatus status;
bool reconnect;
bool connected;
bool connecting;

void openClosedConnections();
bool pollMessage(adb_message * message, bool poll);
void handleOkay(adb_message * message);
void handleClose();
void handleWrite(adb_message * message);
void handleConnect(adb_message * message);

protected:
static const uint8_t epDataInIndex; // DataIn endpoint index
static const uint8_t epDataOutIndex; // DataOUT endpoint index

/* mandatory members */
USB *pUsb;
uint8_t bAddress;
uint8_t bConfNum; // configuration number

uint8_t bNumEP; // total number of EP in the configuration
bool ready;

/* Endpoint data structure */
EpInfo epInfo[ADB_MAX_ENDPOINTS];

void PrintEndpointDescriptor(const USB_ENDPOINT_DESCRIPTOR* ep_ptr);
public:
ADB(USB *p, const char* str);

// Methods for receiving and sending data
uint8_t RcvData(uint16_t *nbytesptr, uint8_t *dataptr);
uint8_t SndData(uint16_t nbytes, uint8_t *dataptr);
int writeStringMessage(uint32_t command, uint32_t arg0, uint32_t arg1, char * str);
int writeEmptyMessage(uint32_t command, uint32_t arg0, uint32_t arg1);
int writeMessage(uint32_t command, uint32_t arg0, uint32_t arg1, uint32_t length, uint8_t * data);
int write(uint16_t length, uint8_t * data);
bool isConnected(){
return connected;
}

// USBDeviceConfig implementation
uint8_t ConfigureDevice(uint8_t parent, uint8_t port, bool lowspeed);
uint8_t Init(uint8_t parent, uint8_t port, bool lowspeed);
uint8_t Release();
uint8_t Poll();
void showStatus();

virtual uint8_t GetAddress() {
return bAddress;
};

virtual bool isReady() {
return ready;
};

virtual bool VIDPIDOK(uint16_t vid, uint16_t pid) {
// return (vid == ADK_VID && (pid == ADK_PID || pid == ADB_PID));
return (vid == ADB_VID && pid == ADB_PID);
};

//UsbConfigXtracter implementation
void EndpointXtract(uint8_t conf, uint8_t iface, uint8_t alt, uint8_t proto, const USB_ENDPOINT_DESCRIPTOR *ep);
};
#endif

 

動作確認してみる

動作確認用プログラム

動作確認用のスケッチとAndroidアプリを作成しました。Arduino側は適当なメッセージを一定間隔でAndroid端末に送信して、端末側はそれを画面に表示しています。

Arduinoスケッチ

USB-HUBをカスケードして使う場合には、スケッチの最初にあるUSBHubインスタンスを増やしてください。私の使ったUSB-HUBは中身にハブ用ICが2個入っていたためふたつ宣言しています(Hub1, Hub2)。


#include <usbhub.h>
#include <adb.h>

// Satisfy the IDE, which needs to see the include statment in the ino too.
#ifdef dobogusinclude
#include <spi4teensy3.h>
#include <SPI.h>
#endif

// #define DEBUG
// #define DEBUG_USB_HOST

USB Usb;
USBHub Hub1(&Usb);
USBHub Hub2(&Usb);
ADB adb(&Usb, "tcp:4567");

uint32_t next_time;
uint32_t count;

// forDebug
static FILE uartout;
static int uart_putchar(char c, FILE *stream) {
if(Serial.write(c) > 0) {
return 0;
} else {
return -1;
}
}
void setup()
{
//forDebug
fdev_setup_stream(&uartout, uart_putchar, NULL, _FDEV_SETUP_WRITE);
stdout = &uartout;

Serial.begin( 115200 );
#if !defined(__MIPSEL__)
while (!Serial); // Wait for serial port to connect - used on Leonardo, Teensy and other boards with built-in USB CDC serial connection
#endif
Serial.println("Start");

if (Usb.Init() == -1)
Serial.println("OSC did not start.");
delay( 200 );

next_time = millis() + 10000;
count = 0;
}
void loop()
{
uint32_t data[] = {0,1,2,3,4,5,6,7};

Usb.Task();
delay(1000);

if(adb.isConnected()){
for(int i=0; i<8; i++){
data[i] = count++;
}

adb.write(sizeof(data), (uint8_t*)data);
}
}

 

Androidプログラム

microbridgeで通信するのと同じプログラムが使えます。私は以下のプログラムを使わせて頂きました。

以下に表示部分の処理を記載します。

MainActivity.java


public class MainActivity extends FragmentActivity {
private Server server;
private Handler handler;
private EditText textDegug;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

textDegug = (EditText)findViewById(R.id.textDebug);

handler = new Handler();

// Create TCP server
server = null;
try {
server = new Server(4567);
server.start();
} catch (IOException e) {
Log.e("microbridge", "Unable to start TCP server", e);
System.exit(-1);
}

server.addListener(new AbstractServerListener(){
@Override
public void onReceive(Client client,final byte[] data) {
parser.parse(data);
handler.post(new Runnable() {
@Override
public void run() {
updateText(data);
}
});

}
});

}

@Override
protected void onStart() {
super.onStart();
}
private void updateText(byte[] data){
try{
if(data == null){
textDegug.setText("null");
}else{
StringBuffer sb = new StringBuffer();
sb.append("size:" + data.length + "\n");
sb.append("data:");
for(byte b:data){
sb.append(String.format("%02X", b));
}

sb.append("\n------\n");
for(int i=0; i<7; i++) {
sb.append(String.format("%08X\n", ByteBuffer.wrap(data, 4*i, 4).order(ByteOrder.LITTLE_ENDIAN).getInt()));
}

textDegug.setText(sb.toString());
}
}catch (Exception e){

}

}
}

 

結果

端末と繋いで動作させてみると、Arduinoから送ったメッセージがAndroid端末の画面に表示されているのが分かります。意外と簡単にいきました、と言いたいところですが・・・移植するにあたってライブラリの仕組みを追いかけたりADB仕様を読んだりするのにかなり時間を要してしまいました(こりゃ、もうエンジニアとしては食っていけないなぁ)。

AndroidアプリでADB通信を確認
Androidアプリで受信できているかをチェックしています。右側のテキストボックスにArduino側から送ったデータが表示されていればOKです(左側の項目はインジェクションコントローラのロガー用に作ったやつなので無視してください)
R0012693.JPG
無事に受信出来ているようです

ちなみにデバッグ中に嵌ったのですが、VBUSに十分な電力が供給されないと認識に失敗するのでUSB-HUBは外部電源の使えるものを選んだ方が無難だと思います。バスパワーで動作させた場合、Android端末がUSB充電する設定になっていると電力不足になりやすいです。上手く動作しない場合はVBUSの電圧を確認してみてください。

R0012685.JPG
セルフパワーなUSB-HUBのはずが、えらい電圧が下がっていました・・・通信は成功するものの充電は上手くいきません。

また、このライブラリは切った張ったで作ったので怪しい箇所が多々あります(一旦切断すると再接続するのにリセットが必要だったり)。汚いコードが気になってたまらない、という方が居られましたらコメント頂けますとありがたいです。

 

参考

この記事は以下の内容を参考に記載させていただきました。

 

 

3 thoughts on “[Arduino] USB Host Library2.0をADB通信に対応させる(Android1.6端末と通信)

  1. むしけら。

    早速のレスありがとうございます。

    先ほどのコメント、「2丁少ない」と書きましたが、
    「2丁多い」の間違いですね。
    失礼しました。
    15-42、100リンクのままで問題ないのかが気になってました。
    参考にさせていただきます。

    返信
  2. むしけら。

    こんにちは。
    初コメントです。

    同じ年式のプレスカブに乗っている者で、
    参考になる記事を多く読ませていただきました。

    一つ質問よろしいでしょうか?
    Fスプロケットの交換を2度されていますが、
    チェーンは変更されなかったのでしょうか?
    一般的には純正プレスカブは13-42で100リンク、
    Rを40に交換時には98などと言われていますが、
    Fを2丁少ない物に変えられた時も
    純正そのままのチェーンで問題無かったのでしょうか?

    以上、お暇な時にでもお答えいただければありがたく存じます。
    よろしくお願いします。

    返信
    1. suke

      こんばんは。

      私の場合、スプロケット交換の際にチェーン変更はしませんでした。チェーンアジャスターで弛みを調整したくらいですが、以下のスプロケット構成では問題なく走れました。チェーンはプレスカブを中古購入した後に交換していて、確か"D.I.D 420N 100リンク"だったはずです。

      ・F13T / R42T
      ・F14T / R42T
      ・F15T / R42T
      ・F17T / R40T

      実際に試していないのではっきりとは言えませんが、F15T/R40Tならアジャスターで調整出来そうな感じです。F13T/R40Tとかになってくると、うーん、どうでなんでしょう。この場合はコマをつめて98リンクにした方がいいかもしれません(チェーンが伸びたときに調整出来なくなるかも)。

      ご参考になれば幸いです。

      返信

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください