【教程】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。
好贴顶一个。。。等线到了就试试,貌似很好玩 这个可以有
呵呵~~~~~~~~~~!:) {:soso_e142:} 以前安卓手机和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");
}
}
}
小辣椒。。。 OTG线,请问对安卓手机有限制吗?版本、型号等。。。被ADK弄怕了 2.3版的手机怕刷机有风险。对此只能羡慕! 其实可以集思广益,如果OTG支持(ARDUINO这类模块应该是被USB转COM口的),那么用蓝牙模块+arduino 应该也可以被安卓支持吧?毕竟蓝牙也会虚拟出一个COM口貌似。
哇卡卡 ,楼主 你打开了一个巨大的宝藏啊,我等有福了,不需要安卓4了 好文章啊 谢谢楼主 能用手机编译、下载,那就好了:lol 能够支持OTG的手机实在太少了,大多数手机应该是取不到电。若可行请给出手机型号版本或刷机办法。 gaoshine 发表于 2012-10-24 18:12 static/image/common/back.gif
以前安卓手机和arduino通过蓝牙通讯,现在看来可以直接用OTG USB线通讯了,赞一个!
贴2段 andriod的代 ...
要是使用OTG连接是不是需要在手机上安装驱动啊?! AdmiralSoft 发表于 2012-10-25 06:39 static/image/common/back.gif
要是使用OTG连接是不是需要在手机上安装驱动啊?!
理论上不用,OTG其实就是相当于让手机拥有电脑一样的USB口,可以接键盘、鼠标等等
安卓底层还是linux的,就如同arduino uno 接入 linux无需驱动一样,手机应该也不需要驱动(树莓派就是个例子啊) 蓝牙确实很早就有,但是你这个软件可以直接用安卓手机直接连接ARDUINO板。进行通讯和改数据成为可能。(甚至可能直接进行编程,因为LINUX内核的安卓手机貌似应该能支持LINUX版的ARDUINO编辑器。。。太强大了)