ninjiafan 发表于 2012-10-24 17:23:45

【教程】Android手机通过OTG线连接Arduino,读写串口数据

本帖最后由 ninjiafan 于 2012-10-28 20:30 编辑

因为自己对电子电路是半桶水,复杂的单片机电路把我难倒了,幸好Arduino的出现,让一切都简化了不少。平时和朋友们就喜欢制作一些Arduino的小玩意儿来帮助生活和工作。

因为朋友单位需要采购一些数据采集和云台控制的设备,就定制了些,全是用UNO和Arduino Nano来做的。但是这些设备所在的位置很分散,在城市的周边地区(非常的偏远,甚至是人迹罕见的地方,那是爬山涉水翻山越岭啊)。

而数据采集的调试很麻烦,因为每部设备所在的位置和环境的不同,必须设置不同的参数,值得庆幸的是程序不需要修改了,只修改参数即可。于是我和朋友只需要带手机和外置电池就行了。

言归正传,UNO和Nano的USB串口通信被Android 4.0以上手机支持的,所以通过OTG线连接并使用软件可以直接读取和发送数据。并且Arduino串口波特率必须为9600!
这是我的手机对应的OTG线,有弯头和直头两种,我更喜欢弯头的。对了,提醒一点,OTG线让Android手机为Arduino供电的,所以无需外接电源。



Android手机需要下载谷歌电子市场中的Arduino Uno Communicat:(在谷歌的Play商店下载东西让人放心,不会有木马或者病毒之类的。其他的电子市场就不好说了。)


通过OTG线插入Arduino时会出现提示:


这是插上Arduino后的运行结果:也?回车怎么变成“.”了?


我们现在已经能够查看Arduino串口输出了,但是无法输入,而且回车也变成点了,不好看。不过查看程序工作日志已经够了。

那么我们有没有更好的软件呢?有的!!!
谷歌电子市场下载USB Serial Monitor Lite:


程序主界面


设置菜单,选择Setting


设置界面,有串口和显示设置


串口波特率改为9600


显示可以用默认,也可以自己定制


你需要显示字符、十进制、十六进制?


设置完成后连接好Arduino后,选择Open Device


运行结果:


应坛友要求提取电子市场的USB Serial Monitor Lite,请有条件的坛友测试下。
补充说明:有坛友说无法在电子市场找到,这个问题我觉得是谷歌电子市场分类导致的,可能是手机的系统版本太低无法支持,也可能是手机达不到这个程序的基本硬件需求所以被屏蔽,当然,我说的是谷歌的Play商店,不是第三方电子市场。我的手机是三星的Galaxy Nexus,系统是谷歌原版的Android 4.0.4。

迷你强 发表于 2012-10-24 17:37:30

好贴顶一个。。。等线到了就试试,貌似很好玩

zhangdeyue1 发表于 2012-10-24 17:43:39

这个可以有
呵呵~~~~~~~~~~!:)

Randy 发表于 2012-10-24 17:46:44

{:soso_e142:}

gaoshine 发表于 2012-10-24 18:12:16

以前安卓手机和arduino通过蓝牙通讯,现在看来可以直接用OTG USB线通讯了,赞一个!:)

贴2段 andriod的代码,供各位参考。

/*
* Copyright (C) 2012 Mathias Jeppsson
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*      http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.primavera.arduino.listener;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;

import android.app.ListActivity;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbManager;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.Toast;

public class ArduinoCommunicatorActivity extends ListActivity {

    private static final int ARDUINO_USB_VENDOR_ID = 0x2341;
    private static final int ARDUINO_UNO_USB_PRODUCT_ID = 0x01;
    private static final int ARDUINO_MEGA_2560_USB_PRODUCT_ID = 0x10;
    private static final int ARDUINO_MEGA_2560_R3_USB_PRODUCT_ID = 0x42;
    private static final int ARDUINO_UNO_R3_USB_PRODUCT_ID = 0x43;

    private final static String TAG = "ArduinoCommunicatorActivity";
    private final static boolean DEBUG = false;
   
    private Boolean mIsReceiving;
    private ArrayList<ByteArray> mTransferedDataList = new ArrayList<ByteArray>();
    private ArrayAdapter<ByteArray> mDataAdapter;

    private void findDevice() {
      UsbManager usbManager = (UsbManager) getSystemService(Context.USB_SERVICE);
      UsbDevice usbDevice = null;
      HashMap<String, UsbDevice> usbDeviceList = usbManager.getDeviceList();
      if (DEBUG) Log.d(TAG, "length: " + usbDeviceList.size());
      Iterator<UsbDevice> deviceIterator = usbDeviceList.values().iterator();
      if (deviceIterator.hasNext()) {
            UsbDevice tempUsbDevice = deviceIterator.next();

            // Print device information. If you think your device should be able
            // to communicate with this app, add it to accepted products below.
            if (DEBUG) Log.d(TAG, "VendorId: " + tempUsbDevice.getVendorId());
            if (DEBUG) Log.d(TAG, "ProductId: " + tempUsbDevice.getProductId());
            if (DEBUG) Log.d(TAG, "DeviceName: " + tempUsbDevice.getDeviceName());
            if (DEBUG) Log.d(TAG, "DeviceId: " + tempUsbDevice.getDeviceId());
            if (DEBUG) Log.d(TAG, "DeviceClass: " + tempUsbDevice.getDeviceClass());
            if (DEBUG) Log.d(TAG, "DeviceSubclass: " + tempUsbDevice.getDeviceSubclass());
            if (DEBUG) Log.d(TAG, "InterfaceCount: " + tempUsbDevice.getInterfaceCount());
            if (DEBUG) Log.d(TAG, "DeviceProtocol: " + tempUsbDevice.getDeviceProtocol());

            if (tempUsbDevice.getVendorId() == ARDUINO_USB_VENDOR_ID) {
                if (DEBUG) Log.i(TAG, "Arduino device found!");

                switch (tempUsbDevice.getProductId()) {
                case ARDUINO_UNO_USB_PRODUCT_ID:
                  Toast.makeText(getBaseContext(), "Arduino Uno " + getString(R.string.found), Toast.LENGTH_SHORT).show();
                  usbDevice = tempUsbDevice;
                  break;
                case ARDUINO_MEGA_2560_USB_PRODUCT_ID:
                  Toast.makeText(getBaseContext(), "Arduino Mega 2560 " + getString(R.string.found), Toast.LENGTH_SHORT).show();
                  usbDevice = tempUsbDevice;
                  break;
                case ARDUINO_MEGA_2560_R3_USB_PRODUCT_ID:
                  Toast.makeText(getBaseContext(), "Arduino Mega 2560 R3 " + getString(R.string.found), Toast.LENGTH_SHORT).show();
                  usbDevice = tempUsbDevice;
                  break;
                case ARDUINO_UNO_R3_USB_PRODUCT_ID:
                  Toast.makeText(getBaseContext(), "Arduino Uno R3 " + getString(R.string.found), Toast.LENGTH_SHORT).show();
                  usbDevice = tempUsbDevice;
                  break;
                }
            }
      }

      if (usbDevice == null) {
            if (DEBUG) Log.i(TAG, "No device found!");
            Toast.makeText(getBaseContext(), getString(R.string.no_device_found), Toast.LENGTH_LONG).show();
      } else {
            if (DEBUG) Log.i(TAG, "Device found!");
            Intent startIntent = new Intent(getApplicationContext(), ArduinoCommunicatorService.class);
            PendingIntent pendingIntent = PendingIntent.getService(getApplicationContext(), 0, startIntent, 0);
            usbManager.requestPermission(usbDevice, pendingIntent);
      }
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      if (DEBUG) Log.d(TAG, "onCreate()");

      IntentFilter filter = new IntentFilter();
      filter.addAction(ArduinoCommunicatorService.DATA_RECEIVED_INTENT);
      filter.addAction(ArduinoCommunicatorService.DATA_SENT_INTERNAL_INTENT);
      registerReceiver(mReceiver, filter);

      mDataAdapter = new ArrayAdapter<ByteArray>(this, android.R.layout.simple_list_item_1, mTransferedDataList);
      setListAdapter(mDataAdapter);

      findDevice();
    }

    @Override
    protected void onListItemClick(ListView l, View v, int position, long id) {
      super.onListItemClick(l, v, position, id);

      if (DEBUG) Log.i(TAG, "onListItemClick() " + position + " " + id);
      ByteArray transferedData = mTransferedDataList.get(position);
      transferedData.toggleCoding();
      mTransferedDataList.set(position, transferedData);
      mDataAdapter.notifyDataSetChanged();
    }

    @Override
    protected void onNewIntent(Intent intent) {
      if (DEBUG) Log.d(TAG, "onNewIntent() " + intent);
      super.onNewIntent(intent);

      if (UsbManager.ACTION_USB_DEVICE_ATTACHED.contains(intent.getAction())) {
            if (DEBUG) Log.d(TAG, "onNewIntent() " + intent);
            findDevice();
      }
    }

    @Override
    protected void onDestroy() {
      if (DEBUG) Log.d(TAG, "onDestroy()");
      super.onDestroy();
      unregisterReceiver(mReceiver);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
      getMenuInflater().inflate(R.menu.options, menu);
      return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
      switch (item.getItemId()) {
      case R.id.help:
            startActivity(new Intent(this, Help.class));
            return true;
      default:
            return super.onOptionsItemSelected(item);
      }
    }

    BroadcastReceiver mReceiver = new BroadcastReceiver() {

      private void handleTransferedData(Intent intent, boolean receiving) {
            if (mIsReceiving == null || mIsReceiving != receiving) {
                mIsReceiving = receiving;
                mTransferedDataList.add(new ByteArray());
            }

            final byte[] newTransferedData = intent.getByteArrayExtra(ArduinoCommunicatorService.DATA_EXTRA);
            if (DEBUG) Log.i(TAG, "data: " + newTransferedData.length + " \"" + new String(newTransferedData) + "\"");

            ByteArray transferedData = mTransferedDataList.get(mTransferedDataList.size() - 1);
            transferedData.add(newTransferedData);
            mTransferedDataList.set(mTransferedDataList.size() - 1, transferedData);
            mDataAdapter.notifyDataSetChanged();
      }

      @Override
      public void onReceive(Context context, Intent intent) {
            final String action = intent.getAction();
            if (DEBUG) Log.d(TAG, "onReceive() " + action);

            if (ArduinoCommunicatorService.DATA_RECEIVED_INTENT.equals(action)) {
                handleTransferedData(intent, true);
            } else if (ArduinoCommunicatorService.DATA_SENT_INTERNAL_INTENT.equals(action)) {
                handleTransferedData(intent, false);
            }
      }
    };
}




/*
* Copyright (C) 2012 Mathias Jeppsson
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*      http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.primavera.arduino.listener;

import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
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.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.util.Log;
import android.widget.Toast;

public class ArduinoCommunicatorService extends Service {

    private final static String TAG = "ArduinoCommunicatorService";
    private final static boolean DEBUG = false;

    private boolean mIsRunning = false;
    private SenderThread mSenderThread;

    private volatile UsbDevice mUsbDevice = null;
    private volatile UsbDeviceConnection mUsbConnection = null;
    private volatile UsbEndpoint mInUsbEndpoint = null;
    private volatile UsbEndpoint mOutUsbEndpoint = null;

    final static String DATA_RECEIVED_INTENT = "primavera.arduino.intent.action.DATA_RECEIVED";
    final static String SEND_DATA_INTENT = "primavera.arduino.intent.action.SEND_DATA";
    final static String DATA_SENT_INTERNAL_INTENT = "primavera.arduino.internal.intent.action.DATA_SENT";
    final static String DATA_EXTRA = "primavera.arduino.intent.extra.DATA";

    @Override
    public IBinder onBind(Intent arg0) {
      return null;
    }

    @Override
    public void onCreate() {
      if (DEBUG) Log.d(TAG, "onCreate()");
      super.onCreate();
      IntentFilter filter = new IntentFilter();
      filter.addAction(SEND_DATA_INTENT);
      filter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED);
      registerReceiver(mReceiver, filter);
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
      if (DEBUG) Log.d(TAG, "onStartCommand() " + intent + " " + flags + " " + startId);

      if (mIsRunning) {
            if (DEBUG) Log.i(TAG, "Service already running.");
            return Service.START_REDELIVER_INTENT;
      }

      mIsRunning = true;

      if (!intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {
            if (DEBUG) Log.i(TAG, "Permission denied");
            Toast.makeText(getBaseContext(), getString(R.string.permission_denied), Toast.LENGTH_LONG).show();
            stopSelf();
            return Service.START_REDELIVER_INTENT;
      }

      if (DEBUG) Log.d(TAG, "Permission granted");
      mUsbDevice = (UsbDevice) intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
      if (!initDevice()) {
            if (DEBUG) Log.e(TAG, "Init of device failed!");
            stopSelf();
            return Service.START_REDELIVER_INTENT;
      }

      if (DEBUG) Log.i(TAG, "Receiving!");
      Toast.makeText(getBaseContext(), getString(R.string.receiving), Toast.LENGTH_SHORT).show();
      startReceiverThread();
      startSenderThread();

      return Service.START_REDELIVER_INTENT;
    }

    @Override
    public void onDestroy() {
      if (DEBUG) Log.i(TAG, "onDestroy()");
      super.onDestroy();
      unregisterReceiver(mReceiver);
      mUsbDevice = null;
      if (mUsbConnection != null) {
            mUsbConnection.close();
      }
    }

    private byte[] getLineEncoding(int baudRate) {
      final byte[] lineEncodingRequest = { (byte) 0x80, 0x25, 0x00, 0x00, 0x00, 0x00, 0x08 };
      switch (baudRate) {
      case 14400:
            lineEncodingRequest = 0x40;
            lineEncodingRequest = 0x38;
            break;

      case 19200:
            lineEncodingRequest = 0x00;
            lineEncodingRequest = 0x4B;
            break;
      }

      return lineEncodingRequest;
    }

    private boolean initDevice() {
      UsbManager usbManager = (UsbManager) getSystemService(Context.USB_SERVICE);
      mUsbConnection = usbManager.openDevice(mUsbDevice);
      if (mUsbConnection == null) {
            if (DEBUG) Log.e(TAG, "Opening USB device failed!");
            Toast.makeText(getBaseContext(), getString(R.string.opening_device_failed), Toast.LENGTH_LONG).show();
            return false;
      }
      UsbInterface usbInterface = mUsbDevice.getInterface(1);
      if (!mUsbConnection.claimInterface(usbInterface, true)) {
            if (DEBUG) Log.e(TAG, "Claiming interface failed!");
            Toast.makeText(getBaseContext(), getString(R.string.claimning_interface_failed), Toast.LENGTH_LONG).show();
            mUsbConnection.close();
            return false;
      }

      // Arduino USB serial converter setup
      // Set control line state
      mUsbConnection.controlTransfer(0x21, 0x22, 0, 0, null, 0, 0);
      // Set line encoding.
      mUsbConnection.controlTransfer(0x21, 0x20, 0, 0, getLineEncoding(9600), 7, 0);

      for (int i = 0; i < usbInterface.getEndpointCount(); i++) {
            if (usbInterface.getEndpoint(i).getType() == UsbConstants.USB_ENDPOINT_XFER_BULK) {
                if (usbInterface.getEndpoint(i).getDirection() == UsbConstants.USB_DIR_IN) {
                  mInUsbEndpoint = usbInterface.getEndpoint(i);
                } else if (usbInterface.getEndpoint(i).getDirection() == UsbConstants.USB_DIR_OUT) {
                  mOutUsbEndpoint = usbInterface.getEndpoint(i);
                }
            }
      }

      if (mInUsbEndpoint == null) {
            if (DEBUG) Log.e(TAG, "No in endpoint found!");
            Toast.makeText(getBaseContext(), getString(R.string.no_in_endpoint_found), Toast.LENGTH_LONG).show();
            mUsbConnection.close();
            return false;
      }

      if (mOutUsbEndpoint == null) {
            if (DEBUG) Log.e(TAG, "No out endpoint found!");
            Toast.makeText(getBaseContext(), getString(R.string.no_out_endpoint_found), Toast.LENGTH_LONG).show();
            mUsbConnection.close();
            return false;
      }

      return true;
    }

    BroadcastReceiver mReceiver = new BroadcastReceiver() {
      @Override
      public void onReceive(Context context, Intent intent) {
            final String action = intent.getAction();
            if (DEBUG) Log.d(TAG, "onReceive() " + action);

            if (SEND_DATA_INTENT.equals(action)) {
                final byte[] dataToSend = intent.getByteArrayExtra(DATA_EXTRA);
                if (dataToSend == null) {
                  if (DEBUG) Log.i(TAG, "No " + DATA_EXTRA + " extra in intent!");
                  String text = String.format(getResources().getString(R.string.no_extra_in_intent), DATA_EXTRA);
                  Toast.makeText(context, text, Toast.LENGTH_LONG).show();
                  return;
                }

                mSenderThread.mHandler.obtainMessage(10, dataToSend).sendToTarget();
            } else if (UsbManager.ACTION_USB_DEVICE_DETACHED.equals(action)) {
                Toast.makeText(context, getString(R.string.device_detaches), Toast.LENGTH_LONG).show();
                mSenderThread.mHandler.sendEmptyMessage(11);
                stopSelf();
            }
      }
    };

    private void startReceiverThread() {
      new Thread("arduino_receiver") {
            public void run() {
                byte[] inBuffer = new byte;
                while(mUsbDevice != null ) {
                  if (DEBUG) Log.d(TAG, "calling bulkTransfer() in");
                  final int len = mUsbConnection.bulkTransfer(mInUsbEndpoint, inBuffer, inBuffer.length, 0);
                  if (len > 0) {
                        Intent intent = new Intent(DATA_RECEIVED_INTENT);
                        byte[] buffer = new byte;
                        System.arraycopy(inBuffer, 0, buffer, 0, len);
                        intent.putExtra(DATA_EXTRA, buffer);
                        sendBroadcast(intent);
                  } else {
                        if (DEBUG) Log.i(TAG, "zero data read!");
                  }
                }

                if (DEBUG) Log.d(TAG, "receiver thread stopped.");
            }
      }.start();
    }

    private void startSenderThread() {
      mSenderThread = new SenderThread("arduino_sender");
      mSenderThread.start();
    }

    private class SenderThread extends Thread {
      public Handler mHandler;

      public SenderThread(String string) {
            super(string);
      }

      public void run() {

            Looper.prepare();

            mHandler = new Handler() {
                public void handleMessage(Message msg) {
                  if (DEBUG) Log.i(TAG, "handleMessage() " + msg.what);
                  if (msg.what == 10) {
                        final byte[] dataToSend = (byte[]) msg.obj;

                        if (DEBUG) Log.d(TAG, "calling bulkTransfer() out");
                        final int len = mUsbConnection.bulkTransfer(mOutUsbEndpoint, dataToSend, dataToSend.length, 0);
                        if (DEBUG) Log.d(TAG, len + " of " + dataToSend.length + " sent.");
                        Intent sendIntent = new Intent(DATA_SENT_INTERNAL_INTENT);
                        sendIntent.putExtra(DATA_EXTRA, dataToSend);
                        sendBroadcast(sendIntent);
                  } else if (msg.what == 11) {
                        Looper.myLooper().quit();
                  }
                }
            };

            Looper.loop();
            if (DEBUG) Log.i(TAG, "sender thread stopped");
      }
    }
}

东爱之都 发表于 2012-10-24 18:40:53

小辣椒。。。

沧海笑1122 发表于 2012-10-24 22:00:54

OTG线,请问对安卓手机有限制吗?版本、型号等。。。被ADK弄怕了

heiketiguo 发表于 2012-10-24 22:13:35

2.3版的手机怕刷机有风险。对此只能羡慕!

darkorigin 发表于 2012-10-24 23:59:33

其实可以集思广益,如果OTG支持(ARDUINO这类模块应该是被USB转COM口的),那么用蓝牙模块+arduino 应该也可以被安卓支持吧?毕竟蓝牙也会虚拟出一个COM口貌似。
哇卡卡 ,楼主 你打开了一个巨大的宝藏啊,我等有福了,不需要安卓4了

totson 发表于 2012-10-25 00:05:01

好文章啊 谢谢楼主

hi.t 发表于 2012-10-25 01:20:59

能用手机编译、下载,那就好了:lol

AdmiralSoft 发表于 2012-10-25 06:14:26

能够支持OTG的手机实在太少了,大多数手机应该是取不到电。若可行请给出手机型号版本或刷机办法。

AdmiralSoft 发表于 2012-10-25 06:39:25

gaoshine 发表于 2012-10-24 18:12 static/image/common/back.gif
以前安卓手机和arduino通过蓝牙通讯,现在看来可以直接用OTG USB线通讯了,赞一个!

贴2段 andriod的代 ...

要是使用OTG连接是不是需要在手机上安装驱动啊?!

gaoshine 发表于 2012-10-25 09:58:46

AdmiralSoft 发表于 2012-10-25 06:39 static/image/common/back.gif
要是使用OTG连接是不是需要在手机上安装驱动啊?!

理论上不用,OTG其实就是相当于让手机拥有电脑一样的USB口,可以接键盘、鼠标等等
安卓底层还是linux的,就如同arduino uno 接入 linux无需驱动一样,手机应该也不需要驱动(树莓派就是个例子啊)

darkorigin 发表于 2012-10-25 22:14:18

蓝牙确实很早就有,但是你这个软件可以直接用安卓手机直接连接ARDUINO板。进行通讯和改数据成为可能。(甚至可能直接进行编程,因为LINUX内核的安卓手机貌似应该能支持LINUX版的ARDUINO编辑器。。。太强大了)
页: [1] 2 3 4 5 6
查看完整版本: 【教程】Android手机通过OTG线连接Arduino,读写串口数据