Android usb通信 实现app与arduino通信demo

一、前言

最近在学习android的usb开发,写了一个android手机和arduino开发板通信的Demo。和其他开发板或设备进行usb通信,原理都是一样的。
写篇博客记录一下。

按照惯例,先看效果图:
最后一张图是我用到的arduino设备和用于连接手机的数据线,需要一个usb转tc的转接头才能插到手机上
在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述

二、开始

1、AndroidManifest.xml清单文件

首先,要在清单文件中声明使用 usb host 功能:

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

然后,在主Activity中添加以下内容,用于接收有关连接的 USB 设备的通知。
<intent-filter>标签中添加:

`<action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />`

添加<meta-data>标签:

<meta-data android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" android:resource="@xml/device_filter" />

在res/xml 文件夹下,创建一个名为device_filter的xml文件,
在device_filter中声明一些usb设备信息,当对应的usb设备插入时就会弹出对话框,询问是否打开应用。其作用就是当文件中指定的设备插入时,立即发出通知,打开应用。也可以什么都不写。

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <usb-device vendor-id="6790" product-id="29987" />
</resources>

完整清单文件代码:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.usbdemo">

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

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.USBDemo">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
                <action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
            </intent-filter>

            <meta-data
                android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"
                android:resource="@xml/device_filter" />
        </activity>
    </application>

</manifest>

2、创建权限广播接收者

创建一个监听连接usb权限的广播接收者,并在onCreate方法中进行广播注册。

private static final String ACTION_USB_PERMISSION = "ACTION.USB_PERMISSION";
private final BroadcastReceiver usbPerMissionReceiver = new BroadcastReceiver() {

        public void onReceive(Context context, Intent intent) {
            Log.d(TAG, "onReceive");
            String action = intent.getAction();
            if (ACTION_USB_PERMISSION.equals(action)) {
                synchronized (this) {
                    if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {
                        //获取连接权限成功
                        }
                    } else {
                        Toast.makeText(MainActivity.this,"访问权限被拒绝",Toast.LENGTH_SHORT);
                        //Log.d(TAG, "访问权限被拒绝 " + mDevice);
                    }
                }
            }
        }
    };

注册广播接收者:

permissionIntent = PendingIntent.getBroadcast(this, 0, new Intent(ACTION_USB_PERMISSION), 0);
        IntentFilter filter = new IntentFilter(ACTION_USB_PERMISSION);
        registerReceiver(usbPerMissionReceiver, filter);

3、枚举usb设备

通过 usbManager.getDeviceList() 获取已插入的usb设备列表,对感兴趣的设备发出连接权限请求,在上面的广播接收者中处理请求结果

mUsbManager = (UsbManager) this.getSystemService(Context.USB_SERVICE);
HashMap<String, UsbDevice> deviceList = mUsbManager.getDeviceList();
Iterator<UsbDevice> deviceIterator = deviceList.values().iterator();
while (deviceIterator.hasNext()) {
       mDevice = deviceIterator.next();
       //因为只有一个设备插入,所以直接获取该设备的接口及端点
       getEndpoint();
       //请求访问/连接usb设备的权限
       mUsbManager.requestPermission(mDevice, permissionIntent);
       }

4、获取usb接口以及输入/输出端点

一个usb设备可以有多个接口(UsbInterface),一个接口也有多个端点(UsbEndpoint),我的这个arduino nano板只有一个接口。
你可以通过 usbDevice.getInterfaceCount() 来查看你的usb设备有多少个接口,一般使用第一个接口就行。接口中有多种端点,有控制端点和数据传输端点,我们需要的是数据传输端点。通过endpoint.getType() 来分辨是哪种端点,类型为UsbConstants.USB_ENDPOINT_XFER_BULK 的就是数据传输端点。我们还得分别获取数据端点中的输入和输出端点,用来传输数据。可以通过端点的getDirection()方法获取端点的传输方向: UsbConstants.USB_DIR_INUsbConstants.USB_DIR_OUT
下面我写的这个getEndpoint()方法就包含了上面的内容,在获取设备后,调用该方法:

    private void getEndpoint() {
        inft = mDevice.getInterface(0);
        Log.d(TAG,"Interface Count:" + mDevice.getInterfaceCount());
        endpointCount = inft.getEndpointCount();
        for (int i = 0; i < endpointCount; i++) {
            if (inft.getEndpoint(i).getType() == UsbConstants.USB_ENDPOINT_XFER_BULK) {
                if (inft.getEndpoint(i).getDirection() == UsbConstants.USB_DIR_IN) {
                    mEndpointIN = inft.getEndpoint(i);
                    Log.d(TAG, "获取到mEndpointIN");
                } else if (inft.getEndpoint(i).getDirection() == UsbConstants.USB_DIR_OUT) {
                    mEndpointOUT = inft.getEndpoint(i);
                    Log.d(TAG, "获取到mEndpointOUT");
                }
            }
        }
    }

5、打开设备

在成功获取权限后,在广播接收者中顺便打开设备,打开设备很容易,调用usb设备实例的openDevice(UsbDevice device)方法就可以了。

mUsbConnection = mUsbManager.openDevice(mDevice);

广播接收者的完整代码继续往下看

6、设置波特率

关于如何设置波特率,网上很多文章都没有提到,但在我学习的过程中,发现如果没有设置和arduino端同样的波特率就会收发数据出错。别人有没有遇到这个问题我不知道,但我觉得还是有必要设置的。我在csdn找到了一篇博客关于如何设置波特率的,但没有过多的解释,只有代码。我也看不懂,希望大伙有看得懂的教一下我。代码如下:

 private boolean configUsb(int paramInt) {
        byte[] arrayOfByte = new byte[8];
        mUsbConnection.controlTransfer(192, 95, 0, 0, arrayOfByte, 8, 1000);
        mUsbConnection.controlTransfer(64, 161, 0, 0, null, 0, 1000);
        long l1 = 1532620800 / paramInt;
        for (int i = 3; ; i--) {
            if ((l1 <= 65520L) || (i <= 0)) {
                long l2 = 65536L - l1;
                int j = (short) (int) (0xFF00 & l2 | i);
                int k = (short) (int) (0xFF & l2);
                mUsbConnection.controlTransfer(64, 154, 4882, j, null, 0, 1000);
                mUsbConnection.controlTransfer(64, 154, 3884, k, null, 0, 1000);
                mUsbConnection.controlTransfer(192, 149, 9496, 0, arrayOfByte, 8, 1000);
                mUsbConnection.controlTransfer(64, 154, 1304, 80, null, 0, 1000);
                mUsbConnection.controlTransfer(64, 161, 20511, 55562, null, 0, 1000);
                mUsbConnection.controlTransfer(64, 154, 4882, j, null, 0, 1000);
                mUsbConnection.controlTransfer(64, 154, 3884, k, null, 0, 1000);
                mUsbConnection.controlTransfer(64, 164, 0, 0, null, 0, 1000);
                return true;
            }
            l1 >>= 3;
        }
    }

7、创建接收数据的线程

创建一个线程来持续接收数据,在打开设备之后开启线程就可以。
发送/接收 数据都是调用UsbConnection实例的同一个方法:bulkTransfer

bulkTransfer(UsbEndpoint endpoint, byte[] buffer, int length, int timeout)

该方法的参数分别是:
UsbEndpoint endpoint 数据输入/输出端点(接收数据用输入端点)
byte[] buffer 接收或发送的数据,类型是字节数组
int length 字节数组的长度
int timeout 超时的时间,单位毫秒

该方法的返回值是一个整数,是实际传输的字节数。

public class MyThread extends Thread {
        private boolean isReceive = true;
        String message = null;
        byte[] bytes = new byte[5];
        @Override
        public void run() {
            super.run();
            while (isReceive) {
                int i = mUsbConnection.bulkTransfer(mEndpointIN, bytes, 0, bytes.length, 3000);
                if(i < 0){
                    Log.d(TAG,"没有收到数据。。。");
                }else{
                    try {
                        message = new String(bytes,"UTF-8");
                        Log.d(TAG, "接收到数据:" + message);
                        runOnUiThread(new Runnable() {
                            @Override
                            public void run() {
                                mTextView.setText(message);
                            }
                        });
                        isReceive = false;
                    } catch (UnsupportedEncodingException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

结合上面说到的打开usb设备,设置波特率,开启线程,把广播接收者的代码补充完整:

private final BroadcastReceiver usbPerMissionReceiver = new BroadcastReceiver() {

        public void onReceive(Context context, Intent intent) {
            Log.d(TAG, "onReceive");
            String action = intent.getAction();
            if (ACTION_USB_PERMISSION.equals(action)) {
                synchronized (this) {
                    if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {
                        //获取连接权限成功
                        mDevice = (UsbDevice) intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
                        if (mDevice != null) {
                            //打开设备
                            mUsbConnection = mUsbManager.openDevice(mDevice);
                            mUsbConnection.claimInterface(inft,true);
                            if (mUsbConnection != null) {
                                Log.d(TAG, "连接设备成功");
                                Toast.makeText(MainActivity.this,"连接设备成功",Toast.LENGTH_SHORT);
                            }
                            //设置波特率
                            configUsb(9600);
                            //开启接收数据线程
                            myThread = new MyThread();
                            myThread.start();
                        }
                    } else {
                        Toast.makeText(MainActivity.this,"访问权限被拒绝",Toast.LENGTH_SHORT);
                        //Log.d(TAG, "访问权限被拒绝 " + mDevice);
                    }
                }
            }
        }
    };

8、发送数据

创建一个方法用来发送数据,发送数据也是调用bulkTransfer()方法,第一个参数要传入usb数据输出端点。可以用该方法的返回值来判断是否发送成功。

private void sendMessage(String msg) {
        byte[] bytes = msg.getBytes();
        if (mUsbConnection != null) {
            int result = mUsbConnection.bulkTransfer(mEndpointOUT, bytes, bytes.length, 3000);
            if (result < 0) {
                Log.d(TAG, "发送失败");
            } else {
                Log.d(TAG, "发送成功");
            }
        } else {
            Log.d(TAG, "mUsbConnection-->null");
        }

    }

9、断开连接

当完成通信,断开设备连接后,调用UsbConnection实例的releaseInterface()close()方法来关闭UsbInterface 和 UsbConnection
创建一个广播接收者来监听设备连接断开的广播:

 BroadcastReceiver usbDetachedReceiver = new BroadcastReceiver() {
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();

            if (UsbManager.ACTION_USB_DEVICE_DETACHED.equals(action)) {
                UsbDevice device = (UsbDevice)intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
                if (device != null) {
                    mUsbConnection.releaseInterface(intf);
                    mUsbConnection.close();
                    mDeviceDetail.setText("设备已断开连接");
                }
            }
        }
    };

结束

Android usb通信的简单过程大概就是这样。
你也可以查看官方文档来学习:
https://developer.android.google.cn/guide/topics/connectivity/usb/host?hl=zh_cn

如果这篇文章对你有所帮助,点个赞吧,嘻嘻。

  • 15
    点赞
  • 50
    收藏
    觉得还不错? 一键收藏
  • 20
    评论
在 Proteus 中,你可以使用 Compim 模块来模拟与实体 Arduino 板的串口通信。以下是实现的基本步骤: 1. 准备工作: - 确保你已经安装了 Proteus 软件和 Arduino IDE。 - 在 Proteus 中选择合适的 Arduino 型号,并将其添加到电路图中。 - 添加 Compim 模块,并连接到 Arduino 板的串口引脚(通常是 RX 和 TX)。 2. 设置 Compim 属性: - 右键单击 Compim 模块,选择 "Properties" 打开属性设置窗口。 - 设置串口参数,如波特率、数据位、停止位等,要与实体 Arduino 板上的串口参数一致。 - 注意,Compim 模块的串口号应与实体 Arduino 板上的串口号对应(例如,COM1、COM2)。 3. 编写 Arduino 代码: - 打开 Arduino IDE,编写与串口通信相关的代码。 - 使用 `Serial.begin()` 函数初始化串口通信,并设置与 Compim 模块相同的波特率。 - 使用 `Serial.print()` 和 `Serial.read()` 函数发送和接收数据。 4. 上传程序到实体 Arduino 板: - 将 Arduino 板通过 USB 连接到电脑上。 - 在 Arduino IDE 中,选择正确的串口端口和 Arduino 型号。 - 编译并上传代码到实体 Arduino 板。 5. 运行仿真: - 在 Proteus 中,点击工具栏上的 "Run" 按钮来运行仿真。 - Compim 模块将模拟实体 Arduino 板的串口通信,与 Arduino 代码进行交互。 通过以上步骤,你可以使用 Compim 模块在 Proteus 中模拟与实体 Arduino 板的串口通信。请确保 Compim 模块的串口参数与实体 Arduino 板的参数一致,以确保通信正常。
评论 20
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值