本文介绍: 公司属于北斗通信行业,项目大多都需要和各式各样的硬件设备结合来满足项目需求,因此所涉及到的各种技术也相对比较冷门。前段时间有个项目用到了一款定制 Android 设备,其中有多个接口,包括两个 USB 接口一个 RS232 串口,需要用到其中一个 USB 连接北斗设备实现指令互通,经过摸索现在也大致了解了 Android USB(host通信流程后续还有另一个项目则用到了 USB(accessory)有空再写一篇。在这里记录分享一下,有错漏或可优化之处欢迎大家留言

前言公司属于北斗通信行业,项目大多都需要和各式各样的硬件设备相结合来满足项目需求,因此所涉及到的各种技术也相对比较冷门。前段时间有个项目用到了一款定制 Android 设备,其中有多个接口,包括两个 USB 接口一个 RS232 串口,需要用到其中一个 USB 连接北斗设备实现指令互通,经过摸索现在也大致了解了 Android USB(host通信流程后续还有另一个项目则用到了 USB(accessory)有空再写一篇。在这里记录分享一下,有错漏或可优化之处欢迎大家留言

一、导入模块

工具基于 github 上的串口工具库:usb-serial-for-android

1. 在项目级 build.gradle 文件添加 jitpack.io

maven { url 'https://jitpack.io' }

2. 添加依赖

implementation 'com.github.mik3y:usb-serial-for-android:3.4.6'
可能有更新版本自行修改

3. 本地

2023年11月30日更新:最近把代码到家里的电脑准备在家办公时恰好停网了导致第三方导入不了,于是就到工具源码上把代码都拉到了本地,如果你也恰好遇到了这种情况也可以这样处理这里顺手打包了一份,自己重新导入一下即可https://download.csdn.net/download/lxt1292352578/88565529

二、上代码

DEMO:https://github.com/LXTTTTTT/USBtoSerialPortDemo
复制再把报红的部分直接去掉或者换成自己的就能直接使用

package com.example.SecondProject.Utils.Transfer.USB;

import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.hardware.usb.UsbDeviceConnection;
import android.hardware.usb.UsbManager;
import android.os.Build;
import android.util.Log;
import android.widget.Toast;

import com.example.SecondProject.Base.MainApplication;
import com.example.SecondProject.BuildConfig;
import com.example.SecondProject.Global.Constant;
import com.example.SecondProject.Global.Variable;
import com.example.SecondProject.Utils.DataUtil;
import com.example.SecondProject.Utils.NotificationCenter;
import com.example.SecondProject.Utils.ProtocolUtil;
import com.hoho.android.usbserial.driver.UsbSerialDriver;
import com.hoho.android.usbserial.driver.UsbSerialPort;
import com.hoho.android.usbserial.driver.UsbSerialProber;
import com.hoho.android.usbserial.util.SerialInputOutputManager;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

// usb 数据传输工具
public class USBTransferUtil {

    private String TAG = "USBTransferUtil";
    private MainApplication APP = MainApplication.getInstance();  // 主程序替换为你自己private UsbManager manager = (UsbManager) APP.getSystemService(Context.USB_SERVICE);  // usb管理器

    private BroadcastReceiver usbReceiver;  // 广播监听判断usb设备授权操作
    private static final String INTENT_ACTION_GRANT_USB = BuildConfig.APPLICATION_ID + ".INTENT_ACTION_GRANT_USB";  // usb权限请求标识
    private final String IDENTIFICATION = " USB-Serial Controller D";  // 目标设备标识

    private List<UsbSerialDriver> availableDrivers = new ArrayList<>();  // 所有可用设备
    private UsbSerialDriver usbSerialDriver;  // 当前连接的设备
    private UsbDeviceConnection usbDeviceConnection;  // 连接对象
    private UsbSerialPort usbSerialPort;  // 设备端口对象通过这个读写数据
    private SerialInputOutputManager inputOutputManager;  // 数据输入输出管理器

// 连接参数,按需求自行修改 ---------
    private int baudRate = 115200;  // 波特率
    private int dataBits = 8;  // 数据位
    private int stopBits = UsbSerialPort.STOPBITS_1;  // 停止位
    private int parity = UsbSerialPort.PARITY_NONE;// 奇偶校验

// 单例 -------------------------
    private static USBTransferUtil usbTransferUtil;
    public static USBTransferUtil getInstance() {
        if(usbTransferUtil == null){
            usbTransferUtil = new USBTransferUtil();
        }
        return usbTransferUtil;
    }


    public void connect(){
        // “Variable.isConnectUSB” 我的变量标识,自行删除修改
        if(!Variable.isConnectUSB){
            registerReceiver();  // 注册广播监听
            refreshDevice();  // 拿到已连接的usb设备列表
            connectDevice();  // 建立连接
        }
    }


    // 注册usb授权监听广播
    public void registerReceiver(){
        usbReceiver = new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                if(INTENT_ACTION_GRANT_USB.equals(intent.getAction())) {
                    // 授权操作完成,连接
//                    boolean granted = intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false);  // 不知为何获取到的永远都是 false 因此无法判断授权还是拒绝
                    connectDevice();
                }
            }
        };
        APP.registerReceiver(usbReceiver,new IntentFilter(INTENT_ACTION_GRANT_USB));
    }

    // 刷新当前可用 usb设备
    public void refreshDevice(){
        availableDrivers.clear();
        availableDrivers = UsbSerialProber.getDefaultProber().findAllDrivers(manager);
        Log.e(TAG, "当前可用 usb 设备数量: " + availableDrivers.size() );


        // 有设备可以连接
        if(availableDrivers.size() != 0){

            // 当时开发用的是定制平板电脑有 2 个usb口,所以搜索两个
            if(availableDrivers.size()>1){
                for (int i = 0; i < availableDrivers.size(); i++) {
                    UsbSerialDriver availableDriver = availableDrivers.get(i);
                    // 我是通过 ProductName 这个参数识别我要连接的设备
                    if(availableDriver.getDevice().getProductName().equals(IDENTIFICATION)){
                        usbSerialDriver = availableDriver;
                    }
                }
            }
            // 通常手机只有充电口 1 个
            else {
                usbSerialDriver = availableDrivers.get(0);
            }
            usbSerialPort = usbSerialDriver.getPorts().get(0);  // 一般设备的端口都只有一个,具体要参考设备的说明文档
            // 同时申请设备权限
            if(!manager.hasPermission(usbSerialDriver.getDevice())){
                int flags = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M ? PendingIntent.FLAG_IMMUTABLE : 0;
                PendingIntent usbPermissionIntent = PendingIntent.getBroadcast(APP, 0, new Intent(INTENT_ACTION_GRANT_USB), flags);
                manager.requestPermission(usbSerialDriver.getDevice(), usbPermissionIntent);
            }
        }
        // 没有设备
        else {
            APP.showToast("请先接入设备",0);
        }


    }

    // 连接设备
    public void connectDevice(){
        if(usbSerialDriver == null || inputOutputManager != null){return;}
        // 判断是否拥有权限
        boolean hasPermission = manager.hasPermission(usbSerialDriver.getDevice());
        if(hasPermission){
            usbDeviceConnection = manager.openDevice(usbSerialDriver.getDevice());  // 拿到连接对象
            if(usbSerialPort == null){return;}
            try {
                usbSerialPort.open(usbDeviceConnection);  // 打开串口
                usbSerialPort.setParameters(baudRate, dataBits, stopBits, parity);  // 设置串口参数波特率 - 115200 , 数据位 - 8 , 停止位 - 1 , 奇偶校验 - 无
                startReceiveData();  // 开启数据线程
            } catch (IOException e) {
                e.printStackTrace();
            }
        }else {
            APP.showToast("请先授予权限再连接",0);
        }

    }

    // 开启数据接收监听
    public void startReceiveData(){
        if(usbSerialPort == null || !usbSerialPort.isOpen()){return;}
        inputOutputManager = new SerialInputOutputManager(usbSerialPort, new SerialInputOutputManager.Listener() {
            @Override
            public void onNewData(byte[] data) {
                // 在这里处理收到的 usb 数据
                String data_str = DataUtil.bytes2string(data).toUpperCase();
                Log.e(TAG, "收到 usb 数据: " + data_str);
            }
            @Override
            public void onRunError(Exception e) {
                Log.e(TAG, "usb 断开了" );
                disconnect();
                e.printStackTrace();
            }
        });
        inputOutputManager.start();
        Variable.isConnectUSB = true;  // 修改连接标识
        NotificationCenter.standard().postNotification(Constant.CONNECT_USB);  // 发送全局广播
        APP.showToast("连接成功" ,Toast.LENGTH_SHORT);
    }

    // 下发数据
    public void write(String data_hex){
        if(usbSerialPort != null){
            Log.e(TAG, "当前usb状态: isOpen-" + usbSerialPort.isOpen() );
            // 当串口打开时再下发
            if(usbSerialPort.isOpen()){
                byte[] data_bytes = DataUtil.hex2bytes(data_hex);  // 将字符数据转化为 byte[]
                if (data_bytes == null || data_bytes.length == 0) return;
                try {
                    usbSerialPort.write(data_bytes,100);  // 写入数据
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }else {
                APP.showToast(" usb 未连接" ,Toast.LENGTH_SHORT);
            }
        }
    }


    // 断开连接
    public void disconnect(){
        try{
            // 停止数据接收监听
            if(inputOutputManager != null){
                inputOutputManager.stop();
                inputOutputManager = null;
            }
            // 关闭端口
            if(usbSerialPort != null){
                usbSerialPort.close();
                usbSerialPort = null;
            }
            // 关闭连接
            if(usbDeviceConnection != null){
                usbDeviceConnection.close();
                usbDeviceConnection = null;
            }
            // 清除设备
            if(usbSerialDriver != null){
                usbSerialDriver = null;
            }
            // 清空设备列表
            availableDrivers.clear();
            // 注销广播监听
            APP.unregisterReceiver(usbReceiver);
            Variable.isConnectUSB = false;  // 修改标识
            NotificationCenter.standard().postNotification(Constant.DISCONNECT_USB);  // 发送广播
            APP.showToast("断开连接",0);

        }catch (Exception e){
            e.printStackTrace();
        }
    }


    // 下发初始化指令
    public void init_device(){
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        write(ProtocolUtil.CCICR(0,"00"));  // 查询 IC 信息
    }



}

三、使用例

1. 使用场景

原本项目设备使用的是左边这种标准的 USB 转串口线连接北斗设备,但是现在手头没有当时的设备,恰好有一条右图这种 type-c 转串口线,那就用手机电脑连接模拟使用场景

连接之后是这个样子的

手机接口占用了,这时可以使用无线调试,附个无线调试教程https://blog.csdn.net/lxt1292352578/article/details/131954052

2. 使用案例

电脑需要准备一个串口调试工具,注意需要把串口参数设置为相同

直接调用连接设备方法:USBTransferUtil.getInstance().connect();


注意首次连接需要手动授权

连接成功后在电脑上使用串口调试助手发送数据模拟通过
下发数据方法:USBTransferUtil.getInstance().write(“363636”);
由于我下发北斗指令时使用的是16进制字符,有其他需求自行在方法里面修改

接收数据的处理:直接在工具类内部处理,不知道是因为设备的不同还是转接线的不同,这里收到的数据是碎片化的而不像我在开发收到的是完整的数据,恰好之前开发串口连接北斗设备时也是这种碎片化数据,之后有空的时候顺便做一个北斗协议拼接/解析教程

互通成功

3. 设备接入监听

Android系统在每次拔插 USB 设备时都会广播一个意图,这样如果我们需要在 USB 设备连接时进行某种操作只需要在 manifest 文件里面对应的 activty 添加一声明指定过滤规则即可
<intentfilter>
        <action android:name=”android.hardware.usb.action.USB_DEVICE_ATTACHED” />
</intentfilter>
<meta-data android:name=”android.hardware.usb.action.USB_DEVICE_ATTACHED”         android:resource=”@xml/device_filter” />

xml源文件夹中添加 device_filter 文件

附上过滤规则文件代码:这里包含了大部分USB设备的厂商厂商ID和产品ID,如有别的需求自行增加

<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools"
    tools:ignore="MissingDefaultResource">
    <!-- 0x0403 / 0x60??: FTDI -->
    <usb-device vendor-id="1027" product-id="24577" /> <!-- 0x6001: FT232R -->
    <usb-device vendor-id="1027" product-id="24592" /> <!-- 0x6010: FT2232H -->
    <usb-device vendor-id="1027" product-id="24593" /> <!-- 0x6011: FT4232H -->
    <usb-device vendor-id="1027" product-id="24596" /> <!-- 0x6014: FT232H -->
    <usb-device vendor-id="1027" product-id="24597" /> <!-- 0x6015: FT230X, FT231X, FT234XD -->

    <!-- 0x10C4 / 0xEA??: Silabs CP210x -->
    <usb-device vendor-id="4292" product-id="60000" /> <!-- 0xea60: CP2102 and other CP210x single port devices -->
    <usb-device vendor-id="4292" product-id="60016" /> <!-- 0xea70: CP2105 -->
    <usb-device vendor-id="4292" product-id="60017" /> <!-- 0xea71: CP2108 -->

    <!-- 0x067B / 0x23?3: Prolific PL2303x -->
    <usb-device vendor-id="1659" product-id="8963" /> <!-- 0x2303: PL2303HX, HXD, TA, ... -->
    <usb-device vendor-id="1659" product-id="9123" /> <!-- 0x23a3: PL2303GC -->
    <usb-device vendor-id="1659" product-id="9139" /> <!-- 0x23b3: PL2303GB -->
    <usb-device vendor-id="1659" product-id="9155" /> <!-- 0x23c3: PL2303GT -->
    <usb-device vendor-id="1659" product-id="9171" /> <!-- 0x23d3: PL2303GL -->
    <usb-device vendor-id="1659" product-id="9187" /> <!-- 0x23e3: PL2303GE -->
    <usb-device vendor-id="1659" product-id="9203" /> <!-- 0x23f3: PL2303GS -->

    <!-- 0x1a86 / 0x?523: Qinheng CH34x -->
    <usb-device vendor-id="6790" product-id="21795" /> <!-- 0x5523: CH341A -->
    <usb-device vendor-id="6790" product-id="29987" /> <!-- 0x7523: CH340 -->

    <!-- CDC driver -->
    <usb-device vendor-id="9025" />                   <!-- 0x2341 / ......: Arduino -->
    <usb-device vendor-id="5824" product-id="1155" /> <!-- 0x16C0 / 0x0483: Teensyduino  -->
    <usb-device vendor-id="1003" product-id="8260" /> <!-- 0x03EB / 0x2044: Atmel Lufa -->
    <usb-device vendor-id="7855" product-id="4"    /> <!-- 0x1eaf / 0x0004: Leaflabs Maple -->
    <usb-device vendor-id="3368" product-id="516"  /> <!-- 0x0d28 / 0x0204: ARM mbed -->
    <usb-device vendor-id="1155" product-id="22336" /><!-- 0x0483 / 0x5740: ST CDC -->
    <usb-device vendor-id="11914" product-id="5"   /> <!-- 0x2E8A / 0x0005: Raspberry Pi Pico Micropython -->
    <usb-device vendor-id="11914" product-id="10"  /> <!-- 0x2E8A / 0x000A: Raspberry Pi Pico SDK -->
    <usb-device vendor-id="6790" product-id="21972" /><!-- 0x1A86 / 0x55D4: Qinheng CH9102F -->
</resources>

通过声明以上的intent-filter和meta-data,表明它是一个能够处理USB设备连接事件的Activity,并且根据res/xml/device_filter.xml中的规则对连接的USB设备进行过滤和处理。这样,在Android设备连接USB设备时,系统就会将相应的事件发送给Activity,从而实现对USB设备的交互和数据处理。比如我在这个 activity 的 onResume 生命周期添加连接USB设备设备操作,这样当系统发送了USB设备接入通知授权成功后就能直接跳转到这个 activity 并进行后续操作简单粗暴。
系统监测到 USB 设备接入并发通知授权跳转至对应 activity

四、小结

整个连接流程大致是这样的:
获取当前系统可用的 USB 设备列表 → 选中对应的USB设备并申请权限(首次)→ 获取设备端口(通常只有一个)→ 按照特定参数打开端口
具体操作参考代码,参考的项目是上文提到的工具库官方demo
https://github.com/mik3y/usb-serial-for-android/tree/master
我的demohttps://github.com/LXTTTTTT/USBtoSerialPortDemo
原工具库官方demo代码比较复杂,我只是按照自己的需求把对应的部分整理出来所以比较简单。由于这个功能用的不多所以也没有做过多的封装几乎所有的连接/通信操作都直接写在工具类里面,如果有需求自行改造即可。

原文地址:https://blog.csdn.net/lxt1292352578/article/details/131976810

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任

如若转载,请注明出处:http://www.7code.cn/show_13913.html

如若内容造成侵权/违法违规/事实不符,请联系代码007邮箱suwngjj01@126.com进行投诉反馈,一经查实,立即删除

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注