前语

在上一篇文章中咱们讲解了关于串口的基础知识,没有看过的同学推荐先看一下,否则你或许会不太了解这篇文章所述的某些内容。

这篇文章咱们将讲解安卓端的串口通讯实践,即怎样运用串口通讯完结安卓设备与其他设备例如PLC主板之间数据交互。

需求留意的是正如上一篇文章所说的,我现在的条件只允许我运用 ESP32 开发版烧录 Arduino 程序与安卓真机(小米10U)进行串口通讯演示。

准备作业

由于咱们需求运用 ESP32 烧录 Arduino 程序演示安卓端的串口通讯,所以在开端之前咱们应该先把程序烧录好。

那么烧录一个怎样的程序呢?

很简略,我这儿直接烧了一个 ESP32 运用 9600 的波特率进行串口通讯,程序内容便是 ESP32 不断的向串口发送数据 “e” ,而且监听串口数据,假如接纳到数据 “o” 则翻开开发版上自带的 LED 灯,假如接纳到数据 “c” 则封闭这个 LED 灯。

代码如下:

#define LED 12
void setup() {
  Serial.begin(9600);
  pinMode(LED, OUTPUT);
}
void loop() {
  if (Serial.available()) {
    char c = Serial.read();
    if (c == 'o') {
      digitalWrite(LED, HIGH);
    }
    if (c == 'c') {
      digitalWrite(LED, LOW);
    }
  }
  Serial.write('e');
  delay(100);
}

上面的 12 号 Pin 是这块开发版的 LED。

运用 Arduino自带串口监视器测试成果:

安卓与串口通信-实践篇

能够看到,确实如咱们想象的经过串口不断的发送字符 “e”,而且在接纳到字符 “o” 后点亮了 LED。

安卓完结串口通讯

原理概述

众所周知,安卓其实是依据 Linux 的操作体系,所以在安卓中对于串口的处理与 Linux 共同。

在 Linux 中串口会被视为一个“设备”,并体现为 /dev/ttys 文件。

/dev/ttys 又被称为字符终端,例如 ttys0 对应的是 DOS/Windows 体系中的 COM1 串口文件。

一般,咱们能够简略了解,假如咱们插入了某个串口设备,则这个设备与 Linux 的通讯会由 /dev/ttys 文件进行 “中转”。

即,假如 Linux 想要发送数据给串口设备,则能够经过往 /dev/ttys 文件中直接写入要发送的数据来完结,如:

echo test > /dev/ttyS1 这个指令会将 “test” 这串字符发送给串口设备。

假如想读取串口发送的数据也是相同的,能够经过读取 /dev/ttys 文件内容完结。

所以,假如咱们在安卓中想要完结串口通讯,大约率也会想到直接读取/写入这个特殊文件。

android-serialport-api

在上文中咱们提到,在安卓中也能够经过与 Linux 相同的办法–直接读写 /dev/ttys 完结串口通讯。

可是其实并不需求咱们自己去处理读写和数据的解析,由于谷歌官方给出了一个解决方案:android-serialport-api

为了便于了解,咱们会大致说一下这个解决方案的源码,可是就不上示例了,至于为什么,同学们往下看就知道了。别的,虽然这个方案前史比较悠久,也很长时间没有人维护了,可是并不意味着不能运用了,只是运用条件比较严苛,当然,我司现在运用的仍是这套方案(哈哈哈哈)。

不过这儿咱们不直接看 android-serialport-api 的源码,而是经过其他大佬二次封装的库来看: Android-SerialPort-API

在这个库中,经过

// 默许直接初始化,运用8N1(8数据位、无校验位、1中止位),path为串口途径(如 /dev/ttys1),baudrate 为波特率
SerialPort serialPort = new SerialPort(path, baudrate);
// 运用可选参数装备初始化,可装备数据位、校验位、中止位 - 7E2(7数据位、偶校验、2中止位)
SerialPort serialPort = SerialPort 
    .newBuilder(path, baudrate)
// 校验位;0:无校验位(NONE,默许);1:奇校验位(ODD);2:偶校验位(EVEN)
//    .parity(2) 
// 数据位,默许8;可选值为5~8
//    .dataBits(7) 
// 中止位,默许1;1:1位中止位;2:2位中止位
//    .stopBits(2) 
    .build();

初始化串口,然后经过:

InputStream in = serialPort.getInputStream();
OutputStream out = serialPort.getOutputStream();

获取到输入/输出流,经过读取/写入这两个流来完结与串口设备的数据通讯。

咱们首要来看看初始化串口是怎样做的。

安卓与串口通信-实践篇

首要查看了当时是否具有串口文件的读写权限,假如没有则经过 shell 指令更改权限为 666 ,更改后再次查看是否有权限,假如仍是没有就抛出异常。

留意这儿的履行 shell 时运用的 runtime 是 Runtime.getRuntime().exec(sSuPath); 也便是说,它是经过 root 权限来履行这段指令的!

换句话说,假如想要经过这种办法完结串口通讯,有必要要有 ROOT 权限!这便是我说我不会给出示例的原因,由于我手头的设备无法 ROOT 啊。至于为啥我司还能持续运用这种方案的原因也很简略,由于咱们工控机的安卓设备都是定制版的啊,拥有 ROOT 权限不是基本操作?

确认权限可用后经过 open 办法拿到一个类型为 FileDescriptor 的变量 mFd ,最终经过这个 mFd 拿到输入输出流。

所以中心在于 open 办法,而 open 办法是一个 native 办法,即 C 代码:

private native FileDescriptor open(String absolutePath, int baudrate, int dataBits, int parity,
    int stopBits, int flags);

C 的源码这儿就不放了,只需求知道它做的作业便是翻开了 /dev/ttys 文件(精确的说是“终端”),然后经过传递进去的这些参数去按串口规矩解析数据,最终回来一个 java 的 FileDescriptor 目标。

在 java 中咱们再经过这个 FileDescriptor 目标能够拿到输入/输出流。

原理说起来是非常的简略。

看完通讯部分的原理后,咱们再来看看咱们怎样查找可用的串口呢?

其实和 Linux 上也相同:

public Vector<File> getDevices() {
    if (mDevices == null) {
        mDevices = new Vector<File>();
        File dev = new File("/dev");
        File[] files = dev.listFiles();
        if (files != null) {
            int i;
            for (i = 0; i < files.length; i++) {
                if (files[i].getAbsolutePath().startsWith(mDeviceRoot)) {
                    Log.d(TAG, "Found new device: " + files[i]);
                    mDevices.add(files[i]);
                }
            }
        }
    }
    return mDevices;
}

也是经过直接遍历 /dev 下的文件,只不过这儿做了一些额定的过滤。

或许也能够经过读取 /proc/tty/drivers 装备文件后过滤:

Vector<Driver> getDrivers() throws IOException {
    if (mDrivers == null) {
        mDrivers = new Vector<Driver>();
        LineNumberReader r = new LineNumberReader(new FileReader("/proc/tty/drivers"));
        String l;
        while ((l = r.readLine()) != null) {
            // Issue 3:
            // Since driver name may contain spaces, we do not extract driver name with split()
            String drivername = l.substring(0, 0x15).trim();
            String[] w = l.split(" +");
            if ((w.length >= 5) && (w[w.length - 1].equals("serial"))) {
                Log.d(TAG, "Found new driver " + drivername + " on " + w[w.length - 4]);
                mDrivers.add(new Driver(drivername, w[w.length - 4]));
            }
        }
        r.close();
    }
    return mDrivers;
}

关于读取可用串口设备,其实从这儿的途径也能够看出,都是体系途径,也便是说,假如没有权限,大约率也是读取不到东西的。

这便是运用与 Linux 相同的办法去读取串口数据的基本原理,那么问题来了,已然我说这个办法运用条件比较严苛,那么更易用的替代方案是什么呢?

咱们下面就会介绍,那便是运用安卓的 USB host (USB主机)的功用。

USB host

Android 3.1(API 等级 12)或更高版别的渠道直接支撑 USB 配件和主机形式。USB 配件形式还作为插件库向后移植到 Android 2.3.4(API 等级 10)中,以支撑更广泛的设备。设备制造商能够选择是否在设备的体系映像中增加该插件库。

在安卓 3.1 版别开端,支撑将USB作为主机形式(USB host)运用,而咱们假如想要经过 USB 读取串口数据则需求依靠于这个主机形式。

在正式开端介绍USB主机形式前,咱们先简要介绍一下安卓上支撑的USB形式。

安卓上的USB支撑三种形式:设备形式、主机形式、配件形式。

设备形式即咱们常用的直接将安卓设备衔接至电脑上,此刻电脑上显示为 USB 外设,即能够当成 “U盘” 运用拷贝数据,不过现在安卓遍及还支撑 MTP形式(作为摄像头)、文件传输形式(即当U盘用)、网卡形式等。

主机形式行将咱们的安卓设备作为主机,衔接其他外设,此刻安卓设备就相当于上面设备形式中的电脑。此刻安卓设备能够衔接键盘、鼠标、U盘以及嵌入式应用USB转串口、转I2C等设备。可是假如想要将安卓设备作为主机形式或许需求一条支撑 OTG 的数据线或转接头。(Micro-USB 或 USB type-c 转 USB-A 口)

而在 USB 配件形式下,外部 USB 硬件充当 USB 主机。配件示例或许包含机器人控制器、扩展坞、确诊和音乐设备、自助服务终端、读卡器等等。这样,不具备主机功用的 Android 设备就能够与 USB 硬件互动。Android USB 配件有必要设计为与 Android 设备兼容,而且有必要恪守 Android 配件通讯协议。

设备形式与配件形式的差异在于在配件形式下,除了 adb 之外,主机还能够看到其他 USB 功用。

安卓与串口通信-实践篇

运用USB主机形式与外设交互数据

在介绍完安卓中的三种USB形式后,下面咱们开端介绍怎样运用USB主机形式。当然,这儿只是大约介绍原生APi的运用办法,咱们在实践运用中一般都都是直接运用大佬编写的第三方库。

准备作业

在开端正式运用USB主机形式时咱们需求先做一些准备作业。

首要咱们需求在清单文件(AndroidManifest.xml)中增加:

<!-- 声明需求USB主机形式支撑,防止不支撑的设备安装了该应用 -->
<uses-feature android:name="android.hardware.usb.host" />
<!-- …… -->
<!-- 声明需求接纳USB衔接事情 -->
<action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />

一个完好的清单文件示例如下:

<manifest ...>
    <uses-feature android:name="android.hardware.usb.host" />
    <uses-sdk android:minSdkVersion="12" />
    ...
    <application>
        <activity ...>
            ...
            <intent-filter>
                <action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
            </intent-filter>
        </activity>
    </application>
</manifest>

声明好清单文件后,咱们就能够查找当时可用的设备信息了:

private fun scanDevice(context: Context) {
    val manager = context.getSystemService(Context.USB_SERVICE) as UsbManager
    val deviceList: HashMap<String, UsbDevice> = manager.deviceList
    Log.i(TAG, "scanDevice: $deviceList")
}

将 ESP32 开发版插上手机,运转程序,输出如下:

安卓与串口通信-实践篇

能够看到,正确的查找到了咱们的 ESP32 开发版。

这儿提一下,由于咱们的手机只要一个 USB 口,此刻现已插上了 ESP32 开发版,所以无法再经过数据线直接衔接电脑的 ADB 了,此刻咱们需求运用无线 ADB,详细怎样运用无线 ADB,请自行查找。

别的,假如咱们想要经过查找到设备后恳求衔接的办法衔接到串口设备的话,还需求额定请求权限。(同理,假如咱们直接在清单文件中提前声明需求衔接的设备则不需求额定请求权限,详细能够看看参考资料5,这儿不再赘述)

首要声明一个广播接纳器,用于接纳授权成果:

private lateinit var permissionIntent: PendingIntent
private const val ACTION_USB_PERMISSION = "com.android.example.USB_PERMISSION"
private val usbReceiver = object : BroadcastReceiver() {
    override fun onReceive(context: Context, intent: Intent) {
        if (ACTION_USB_PERMISSION == intent.action) {
            synchronized(this) {
                val device: UsbDevice? = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE)
                if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {
                    device?.apply {
                    	// 已授权,能够在这儿开端恳求衔接
                        connectDevice(context, device)
                    }
                } else {
                    Log.d(TAG, "permission denied for device $device")
                }
            }
        }
    }
}

声明好之后在 Acticity 的 OnCreate 中注册这个广播接纳器:

permissionIntent = PendingIntent.getBroadcast(this, 0, Intent(ACTION_USB_PERMISSION), FLAG_MUTABLE)
val filter = IntentFilter(ACTION_USB_PERMISSION)
registerReceiver(usbReceiver, filter)

最终,在查找到设备后,调用 manager.requestPermission(deviceList.values.first(), permissionIntent) 弹出对话框请求权限。

衔接到设备并收发数据

完结上述的准备作业后,咱们总算能够衔接查找到的设备并进行数据交互了:

private fun connectDevice(context: Context, device: UsbDevice) {
    val usbManager = context.getSystemService(Context.USB_SERVICE) as UsbManager
    CoroutineScope(Dispatchers.IO).launch {
        device.getInterface(0).also { intf ->
            intf.getEndpoint(0).also { endpoint ->
                usbManager.openDevice(device)?.apply {
                    claimInterface(intf, forceClaim)
                    while (true) {
                        val validLength = bulkTransfer(endpoint, bytes, bytes.size, TIMEOUT)
                        if (validLength > 0) {
                            val result = bytes.copyOfRange(0, validLength)
                            Log.i(TAG, "connectDevice: length = $validLength")
                            Log.i(TAG, "connectDevice: byte = ${result.contentToString()}")
                        }
                        else {
                            Log.i(TAG, "connectDevice: Not recv data!")
                        }
                    }
                }
            }
        }
    }
}

在上面的代码中,咱们运用 usbManager.openDevice 翻开了指定的设备,即衔接到设备。

然后经过 bulkTransfer 接纳数据,它会将接纳到的数据写入缓冲数组 bytes 中,并回来成功接纳到的数据长度。

运转程序,衔接设备,日志打印如下:

安卓与串口通信-实践篇

能够看到,输出的数据并不是咱们预料中的数据。

这是由于这是非常原始的数据,假如咱们想要读取数据,还需求针对不同的串口转USB芯片或协议编写驱动程序才能获取到正确的数据。

顺道一提,假如想要将数据写入串口数据的话能够运用 controlTransfer()

所以,咱们在实践生产环境中运用的都是依据此封装好的第三方库。

这儿推荐运用 usb-serial-for-android

usb-serial-for-android

运用这个库的第一步当然是导入依靠:

// 增加库房
allprojects {
    repositories {
        ...
        maven { url 'https://jitpack.io' }
    }
}
// 增加依靠
dependencies {
    implementation 'com.github.mik3y:usb-serial-for-android:3.4.6'
}

增加完依靠同样需求在清单文件中增加相应字段以及处理权限,由于和上述运用原生API共同,所以这儿不再赘述。

和原生 API 不同的是,由于咱们此刻现已知道了咱们的 ESP32 主板的设备信息,以及运用的驱动(CDC),所以咱们就不运用原生的查找可用设备的办法了,咱们这儿直接指定咱们已知的这个设备(当然,你也能够持续运用原生API的查找和衔接办法):

private fun scanDevice(context: Context) {
    val manager = context.getSystemService(Context.USB_SERVICE) as UsbManager
    val customTable = ProbeTable()
    // 增加咱们的设备信息,三个参数分别为 vendroId、productId、驱动程序
    customTable.addProduct(0x1a86, 0x55d3, CdcAcmSerialDriver::class.java)
    val prober = UsbSerialProber(customTable)
    // 查找指定的设备是否存在
    val drivers: List<UsbSerialDriver> = prober.findAllDrivers(manager)
    if (drivers.isNotEmpty()) {
        val driver = drivers[0]
        // 这个设备存在,衔接到这个设备
        val connection = manager.openDevice(driver.device)
    }
    else {
        Log.i(TAG, "scanDevice: 无设备!")
    }
}

衔接到设备后,下一步便是和数据交互,这儿封装的非常便利,只需求获取到 UsbSerialPort 后,直接调用它的 read()write() 即可读写数据:

port = driver.ports[0] // 大多数设备都只要一个 port,所以大多数状况下直接取第一个就行
port.open(connection)
// 设置衔接参数,波特率9600,以及 “8N1”
port.setParameters(9600, 8, UsbSerialPort.STOPBITS_1, UsbSerialPort.PARITY_NONE)
// 读取数据
val responseBuffer = ByteArray(1024)
port.read(responseBuffer, 0)
// 写入数据
val sendData = byteArrayOf(0x6F)
port.write(sendData, 0)

此刻,一个完好的,用于测试咱们上述 ESP32 程序的代码如下:

@Composable
fun SerialScreen() {
    val context = LocalContext.current
    Column(
        Modifier.fillMaxSize(),
        verticalArrangement = Arrangement.Center,
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Button(onClick = { scanDevice(context) }) {
            Text(text = "查找并衔接设备")
        }
        Button(onClick = { switchLight(true) }) {
            Text(text = "开灯")
        }
        Button(onClick = { switchLight(false) }) {
            Text(text = "关灯")
        }
    }
}
private fun scanDevice(context: Context) {
    val manager = context.getSystemService(Context.USB_SERVICE) as UsbManager
    val customTable = ProbeTable()
    customTable.addProduct(0x1a86, 0x55d3, CdcAcmSerialDriver::class.java)
    val prober = UsbSerialProber(customTable)
    val drivers: List<UsbSerialDriver> = prober.findAllDrivers(manager)
    if (drivers.isNotEmpty()) {
        val driver = drivers[0]
        val connection = manager.openDevice(driver.device)
        if (connection == null) {
            Log.i(TAG, "scanDevice: 衔接失利")
            return
        }
        port = driver.ports[0]
        port.open(connection)
        port.setParameters(9600, 8, UsbSerialPort.STOPBITS_1, UsbSerialPort.PARITY_NONE)
        Log.i(TAG, "scanDevice: Connect success!")
        CoroutineScope(Dispatchers.IO).launch {
            while (true) {
                val responseBuffer = ByteArray(1024)
                val len = port.read(responseBuffer, 0)
                Log.i(TAG, "scanDevice: recv data = ${responseBuffer.copyOfRange(0, len).contentToString()}")
            }
        }
    }
    else {
        Log.i(TAG, "scanDevice: 无设备!")
    }
}
private fun switchLight(isON: Boolean) {
    val sendData = if (isON) byteArrayOf(0x6F) else byteArrayOf(0x63)
    port.write(sendData, 0)
}

运转这个程序,而且衔接设备,输出如下:

安卓与串口通信-实践篇

能够看到输出的是 byte 的 101,转换为 ASCII 即为 “e”。

然后咱们点击 “开灯”、“关灯” 作用如下:

安卓与串口通信-实践篇

对了,这儿发送的数据 “0x6F” 即 ASCII “o” 的十六进制,同理,“0x63” 即 “c”。

能够看到,能够完美的和咱们的 ESP32 开发版进行通讯。

实例

无论运用什么办法与串口通讯,咱们在安卓APP的代码层面能够拿到的数据现已是处理好了的数据。

即,在上一篇文章中咱们说过串口通讯的一帧数据包含开始位、数据位、校验位、中止位。可是咱们在安卓中运用时一般拿到的都只要 数据位 的数据,其他数据现已在底层被解析好了,无需咱们去关怀怎样解析,或许运用。

咱们能够直接拿到的便是可用数据。

这儿举一个我之前用过的某类型驱动版的例子。

这块驱动版关于通讯的信息如图:

安卓与串口通信-实践篇

能够看到,它采用了 RS485 的通讯办法,波特率支撑 9600 或 38400,8位数据位,无校验,1位中止位。

而且,它还规则了一个数据协议。

在它界说的协议中,第一位为地址;第二位为指令;第三位到第N位为数据内容;最终两位为CRC校验。

需求留意的是,这儿界说的协议是依据串口通讯的,不要把这个协议和串口通讯搞混了,简略来说便是在串口通讯协议的数据位中又界说了一个自己的协议。

而且能够看到,虽然界说串口参数时没有指定校验,可是在它自己的协议中指定了运用 CRC 校验。

别的,弱弱的吐槽一句,这个驱动版的协议真的不好使。

在实践运用过程中,主机与驱动版的通讯数据无法保证一定会在同一个数据帧中发送完结,所以或许会造成“粘包”、“分包”现象,也便是说,数据或许会分几回发过来,而且你不好判断这数据是前次没发送完的数据仍是新的数据。

我运用过的别的一款驱动版就便利的多,由于它会在帧头加上开端符号和数据长度,帧尾加上结束符号。

这样一来,即便呈现“粘包”、“分包”咱们也能很好的给它解析出来。

当然,它这样设计协议肯定是有它的道理的,无非便是减少通讯价值之类的。

我还遇到过一款非常简练的驱动版,直接发送一个整数曩昔表示履行对应的指令。

驱动版回传的数据同样非常简略,便是一个数字,然后事前约好各个数字表示什么意思……

说归说,咱们仍是持续来看这款驱动版的通讯协议:

安卓与串口通信-实践篇

这是它的其间一个指令内容,咱们发送指令 “1” 曩昔后,它会回来当时驱动版的类型和版别信息给咱们。

由于咱们的主板是定制工控主板,所以运用的通讯办法是直接用 android-serialport-api。

最终发送与接纳回复也很简略:

/**
 * 将十六进制字符串转成 ByteArray
 * */
private fun hexStrToBytes(hexString: String): ByteArray {
    check(hexString.length % 2 == 0) { return ByteArray(0) }
    return hexString.chunked(2)
        .map { it.toInt(16).toByte() }
        .toByteArray()
}
private fun isReceivedLegalData(receiveBuffer: ByteArray): Boolean {
    val rcvData = receiveBuffer.copyOf()  //从头拷贝一个运用,防止原数据被清零
    if (cmd.cmdId.checkDataFormat(rcvData)) {  //查看回复数据格式
        isPkgLost = false
        if (cmd.cmdId.isResponseBelong(rcvData)) {  //查看回复指令来历
            if (!AdhShareData.instance.getIsUsingCrc()) {  //假如不开启CRC查验则直接回来 true
                resolveRcvData(cmdRcvDataCallback, rcvData, cmd.cmdId)
                coroutineScope.launch(Dispatchers.Main) {
                    cmdResponseCallback?.onCmdResponse(ResponseStatus.Success, rcvData, 0, rcvData.size, cmd.cmdId)
                }
                return true
            }
            if (cmd.cmdId.checkCrc(rcvData)) {  //查验CRC
                 resolveRcvData(cmdRcvDataCallback, rcvData, cmd.cmdId)
                coroutineScope.launch(Dispatchers.Main) {
                    cmdResponseCallback?.onCmdResponse(ResponseStatus.Success, rcvData, 0, rcvData.size, cmd.cmdId)
                }
                return true
            }
            else {
                coroutineScope.launch(Dispatchers.Main) {
                    cmdResponseCallback?.onCmdResponse(ResponseStatus.FailCauseCrcError, ByteArray(0), -1, -1, cmd.cmdId)
                }
                return false
            }
        }
        else {
            coroutineScope.launch(Dispatchers.Main) {
                cmdResponseCallback?.onCmdResponse(ResponseStatus.FailCauseNotFromThisCmd, ByteArray(0), -1, -1, cmd.cmdId)
            }
            return false
        }
    }
    else {  //数据不符合,或许是遇到了分包,持续等待下一个数据,然后合并
        isPkgLost = true
        return isReceivedLegalData(cmd)
        /*coroutineScope.launch(Dispatchers.Main) {
            cmdResponseCallback?.onCmdResponse(ResponseStatus.FailCauseWrongFormat, ByteArray(0), -1, -1, cmd.cmdId)
        }
        return false  */
    }
}
// ……省掉初始化和衔接代码
// 发送数据
val bytes = hexStrToBytes("0201C110")
outputStream.write(bytes, 0, bytes.size)
// 解析数据
val recvBuffer = ByteArray(0)
inputStream.read(recvBuffer)
while (receiveBuffer.isEmpty()) {
   delay(10)
}
isReceivedLegalData()

原本方案直接发我封装好的这个驱动版的协议库的,想了想,如同不太适宜,所以就大约抽出了这些不完好的代码,懂这个意思就行了,哈哈。

总结

从上面介绍的两种办法能够看出,两种办法运用各有优缺点。

运用 android-serialport-api 能够直接读取串口数据内容,不需求转USB接口,不需求驱动支撑,可是需求 ROOT,适合于定制安卓主板上现已预留了 RS232 或 RS485 接口且设备已 ROOT 的状况下运用。

而运用 USB host ,能够直接读取USB接口转接的串口数据,不需求ROOT,可是只支撑有驱动的串口转USB芯片,且只支撑运用USB接口,不支撑直接衔接串口设备。

各位能够依据自己的实践状况灵敏选择运用什么办法来完结串口通讯。

当然,除了现在介绍的这些串口通讯,其实还有一个通讯协议在实践运用中用的非常多,那便是 MODBUS 协议。

下一篇文章,咱们将介绍 MODBUS。

参考资料

  1. android-serialport-api
  2. What is tty?
  3. Text-Terminal-HOWTO
  4. Terminal Special Files
  5. USB host
  6. Android开启OTG功用/USB Host API功用

本文正在参加「金石方案 . 瓜分6万现金大奖」