Android 使用 Usb Accessory 模式与 linux 下位机进行通信

这段时间研究了下Usb Accessory模式, 这个模式正常的使用方法是用来和专为Android设备设计的USB主机硬件通讯的.
这种设备需要使用Accessory Development Kit (ADK)来开发, 遵循Android Open Accessory (AOA)协议.

但是对于我们安卓开发者来说想尝试下这个模式总不可能自己去用ADK来编写一个驱动吧, 所以本文会在linux里使用libusb库来模拟aoa协议来作为外设端.

Android端:

USB Accessory有两种API可供选择, 第一种是android.hardware.usb包, 只能用在Android 3.1及之后的系统上, 不需要额外添加library, 第二种是com.android.future.usb包, 需要额外添加 Google APIs add-on library , 也就是谷歌服务, 但是可以运行在Android 2.3.4以及之后的系统上, 这里我们使用第一种来演示, 第二种可以去官方文档进行了解 .

首先在AndroidManifest文件内添加如下配置

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="com.gavinandre.usbaccessory">
    <uses-feature android:name="android.hardware.usb.accessory"/>
    <application
        ...
        <activity
            ...
            <intent-filter>
                <action android:name="android.hardware.usb.action.USB_ACCESSORY_ATTACHED"/>
            </intent-filter>
            <meta-data
                android:name="android.hardware.usb.action.USB_ACCESSORY_ATTACHED"
                android:resource="@xml/accessory_filter"/>
        </activity>
    </application>
</manifest>

在res文件夹下建立xml文件夹, 添加accessory_filter.xml文件

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <usb-accessory
        manufacturer="Lutixia"
        model="Demo"
        version="1.0"
        />
</resources>

获取Usb Accessory设备后打开Accessory模式

public AccessoryCommunicator(final Context context) {
        this.context = context;

        usbManager = (UsbManager) this.context.getSystemService(Context.USB_SERVICE);
        final UsbAccessory[] accessoryList = usbManager.getAccessoryList();

        if (accessoryList == null || accessoryList.length == 0) {
            onError("no accessory found");
        } else {
            openAccessory(accessoryList[0]);
        }
    }

注意: 官方文档内提供了两种方法获取UsbAccessory设备, 第一种就是我使用的方法(UsbAccessory[] accessoryList = usbManager.getAccessoryList();)可以一次性获取多个Accessory设备, 但是目前Android系统还不支持同时与多个设备进行通信, 第二种方法是在广播内获取Accessory设备, 但是需要取得权限, 具体可以去官方文档查看.

Accessory的开启和关闭, 传输数据使用FileInputStream和FileOutputStream来进行读写

private void openAccessory(UsbAccessory accessory) {
        fileDescriptor = usbManager.openAccessory(accessory);
        if (fileDescriptor != null) {

            FileDescriptor fd = fileDescriptor.getFileDescriptor();
            inStream = new FileInputStream(fd);
            outStream = new FileOutputStream(fd);

            //接收消息的线程
            new CommunicationThread().start();

            //发送消息的线程
            sendHandler = new Handler() {
                public void handleMessage(Message msg) {
                    try {
                        outStream.write((byte[]) msg.obj);
                    } catch (final Exception e) {
                        onError("USB Send Failed " + e.toString() + "\n");
                    }
                }
            };
        } else {
            onError("could not connect");
        }
    }

public void closeAccessory() {
        running = false;
        try {
            if (fileDescriptor != null) {
                fileDescriptor.close();
            }
        } catch (IOException e) {
        } finally {
            fileDescriptor = null;
        }
}

读取消息线程实现, 通过循环来不断读取不同的消息

private class CommunicationThread extends Thread {
        @Override
        public void run() {
            running = true;

            while (running) {
                byte[] msg = new byte[Constants.BUFFER_SIZE_IN_BYTES];
                try {
                    //Handle incoming messages
                    int len = inStream.read(msg); //等待消息时会阻塞在这里
                    while (inStream != null && len > 0 && running) {
                        receive(msg, len);
                        Thread.sleep(10);
                        len = inStream.read(msg);
                    }
                } catch (final Exception e) {
                    onError("USB Receive Failed " + e.toString() + "\n");
                    closeAccessory();
                }
            }
        }
    }

界面部分具体看demo

Linux端:

首先需要安装libusb和usb的头文件

sudo apt-get install libusb-dev
sudo apt-get install libusb-1.0-0-dev

安装好后用usb连接android和linux, 在linux下使用lsusb命令查看usb设备
这里写图片描述
我的设备是0e8d:2008, 记录下自己设备的VID和PID号

然后添加规则, 在ATTR{idVendor}==”0e8d”内替换你自己的VID

sudo vim /etc/udev/rules.d/51-android.rules
//在51-android.rules内添加两条规则
SUBSYSTEM=="usb", ATTR{idVendor}=="0e8d", MODE="0666", GROUP="plugdev"
SUBSYSTEM=="usb", ATTR{idVendor}=="18d1", MODE="0666", GROUP="plugdev"
//保存后要添加权限
sudo chmod a+r /etc/udev/rules.d/51-android.rules

打开demo里的usbacc.c文件, 更改你自己的VID和PID

/* Android device vendor/product */
#define AD_VID 0x0e8d
#define PID 0x2008

将makefile和usbacc.c放在同一目录后使用make编译并运行

make
...
./usbacc

这时如果出现Error setting up accessory的话, 使用lsusb查看一下usb信息
这里写图片描述
用你accessory模式的vid和pid替换usbacc.c里相应的数值

#define GOOGLE_VID 0x18d1
#define ACCESSORY_PID 0x2d01 /* accessory with adb */
#define ACCESSORY_PID_ALT 0x2d00 /* accessory without adb */

Android设备如果打开了调试模式的话,在如下位置使用ACCESSORY_PID_ALT来代替ACCESSORY_PID

if ((handle = libusb_open_device_with_vid_pid(NULL,
              GOOGLE_VID, ACCESSORY_PID_ALT)) == NULL) {

如果出现LIBUSB_ERROR_IO错误如下:

Error: LIBUSB_ERROR_IO
Input/output error.
Done, no errors

就使用如下命令可以看到bEndpointAddress值, 18d1:2d01是你进入accessory后的vid和pid

lsusb -v -d 18d1:2d00 | grep bEndpointAddress
        bEndpointAddress     0x81  EP 1 IN
        bEndpointAddress     0x02  EP 2 OUT

在这里进行相应更改

#define EP_IN 0x81
#define EP_OUT 0x02

如果一切正常的话, 第一次进入accessory应该会出现如下提示
这里写图片描述
如果没有出现该提示但是却进入accessory模式了, 可能会出现linux端能接收但是不能发送的情况, 这时试试换台手机测试, 我在这里卡了两天……

最后看一下通讯的效果
这里写图片描述
这里写图片描述

demo下载: https://github.com/GavinAndre/UsbAccessoryDemo

参考:
https://github.com/LucianZala/linuxUsbAccessory
https://github.com/quandoo/android2android-accessory

  • 7
    点赞
  • 43
    收藏
    觉得还不错? 一键收藏
  • 18
    评论
Android Open Accessory (AOA) 是 Android 3.1 及以上版本中引入的一种协议,用于让 Android 设备与 USB 外设通信。其中,AOA 分为两种模式Accessory 模式和 Host 模式。 在 Accessory 模式下,Android 设备作为一个 USB 主机,外接一个 USB 外设,如 Arduino 或者 PIC 单片机等,通过 USB 接口进行通信。在 Host 模式下,Android 设备作为一个 USB 从设备,连接到 PC 上,与 PC 进行通信。 对于 51 单片机的 AOA 开发,需要实现 USB 设备的相关硬件电路,并编写相应的固件程序。具体步骤如下: 1. 首先,需要了解 Android 设备与外设的通信协议,了解 AOA 协议的数据格式、命令以及响应的数据包格式。 2. 在单片机中实现 USB 设备的硬件电路,包括 USB 转串口、USB 控制器等。 3. 在单片机中编写 AOA 协议的固件程序,根据协议格式,实现设备的握手、命令响应、数据传输等功能。 4. 在 Android 应用程序中,使用 USB Host API 进行设备的检测和数据传输,根据协议格式,封装命令和数据,发送给单片机。 下面是一个简单的 Android 应用程序,演示如何使用 USB Host API 实现与 51 单片机的通信: ``` public class MainActivity extends Activity implements UsbManager.OnUsbDeviceConnectionListener { private UsbManager mUsbManager; private PendingIntent mPermissionIntent; private UsbDevice mDevice; private UsbEndpoint mEndpoint; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // 获取 UsbManager 实例 mUsbManager = (UsbManager) getSystemService(Context.USB_SERVICE); // 注册 USB 设备权限 mPermissionIntent = PendingIntent.getBroadcast(this, 0, new Intent(ACTION_USB_PERMISSION), 0); IntentFilter filter = new IntentFilter(ACTION_USB_PERMISSION); registerReceiver(mUsbReceiver, filter); } @Override public void onResume() { super.onResume(); // 枚举所有连接的 USB 设备 for (UsbDevice device : mUsbManager.getDeviceList().values()) { // 判断是否为目标设备 if (device.getVendorId() == VENDOR_ID && device.getProductId() == PRODUCT_ID) { // 请求设备权限 mUsbManager.requestPermission(device, mPermissionIntent); break; } } } private final BroadcastReceiver mUsbReceiver = new BroadcastReceiver() { public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if (ACTION_USB_PERMISSION.equals(action)) { synchronized (this) { mDevice = (UsbDevice) intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) { // 获取 USB 设备接口 UsbInterface intf = mDevice.getInterface(0); // 获取 USB 设备端点 mEndpoint = intf.getEndpoint(0); // 打开 USB 设备连接 UsbDeviceConnection connection = mUsbManager.openDevice(mDevice); if (connection != null) { connection.claimInterface(intf, true); // 发送命令给单片机 byte[] command = new byte[]{0x01, 0x02, 0x03}; connection.bulkTransfer(mEndpoint, command, command.length, TIMEOUT); } } else { Log.d(TAG, "permission denied for device " + mDevice); } } } } }; @Override public void onUsbDeviceConnection(UsbDevice device, UsbDeviceConnection connection) { // USB 设备连接成功回调 } @Override public void onUsbDeviceDisconnection(UsbDevice device, UsbDeviceConnection connection) { // USB 设备断开连接回调 } } ``` 51 单片机的固件程序实现比较复杂,需要涉及到 USB 设备的控制、数据传输、命令响应等方面,需要根据 AOA 协议的具体要求实现。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 18
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值