串口通信,在 Android 手机端 APP 开发中用的很少,因为绝大多数 APP 都是使用 HTTP 与后台通信获取数据的,但是在智能家居,人脸识别门禁,自动售货机,和单片机通信等场景,基本上都离不开串口通信,就比如自动售货机,Android 应用收到付款成功后,发送串口指令,控制货道进行出货。
什么是串口
串口叫做串行接口,是指数据一位一位地顺序传送,通信线路简单,只要一对传输线就可以实现双向通信,相当于一个管道,在硬件层面有三根跳线,Tx 线,Rx 线和地线,这个管道传输的数据是串行的,有顺序的。串口通信就是通信双方按位进行,遵守时序的一种通信方式。
串口的通信方式
- 单工模式:只支持数据在一个方向上传输。
- 半双工模式:可以发送和接收数据,但不能同时进行发送和接收,也就是同一时间只有一个动作发生。
- 全双工模式:数据可以同时往两个方向传输,在同一时间可以同时进行发送和接收数据,实现双向通信。
编码
引入依赖
implementation 'com.licheedev:android-serialport:2.1.3'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4'
implementation 'androidx.activity:activity-ktx:1.5.1'
这里以测温为例,有些场所的人脸识别 Android 终端设备是带有测温功能的,能实时显示屏幕前人的体温,这个是怎么做到的呢?其实就是利用串口通信,简单地说,就是测温相关的硬件模块把测得的温度发送给 Android 设备,Android 接收后解析数据进行显示。
private var serialPort: SerialPort? = null
private var outputStream: OutputStream? = null
private var inputStream: InputStream? = null
private var isStopRead = false
开启串口,SerialPort 接收两个参数 - 串口地址和波特率,串口地址就是串口文件所在地,波特率表示每秒钟传送的 bit 的个数,这俩参数都会在硬件说明上标注,我们根据说明书填写即可。
try {
serialPort = SerialPort(File("/dev/ttyS3"), 115200) //串口地址,波特率
outputStream = serialPort?.outputStream
inputStream = serialPort?.inputStream
} catch (e: Exception) {
e.printStackTrace()
}
你也可以配置数据位,停止位和校验位,不过一般都用默认的,无需配置。
serialPort = SerialPort.newBuilder(File("/dev/ttyS3"), 115200) //串口地址,波特率
.dataBits(dataBits) // 数据位:通信中数据位的参数,默认8,可选值为5-8
.stopBits(stopBits) // 停止位:单个包的最后一位,表示传输的结束,默认1位
.parity(parity) //一种简单的检错方式,0指无校验,1指奇校验,2指偶校验
.build()
打开之后,我们就可以开始读取温度数据了
private suspend fun readTemperature() {
isStopRead = false
try {
inputStream?.let {
if (it.available() > 0) { //清除缓存
it.skip(it.available().toLong())
}
//开启循环,不断读取数据,直到协程喊停。
while (!isStopRead) {
if (it.available() <= 0) { //说明没有可用字节
delay(200)
continue
}
val tempBuffer = ByteArray(231)
val tempSize: Int = it.read(tempBuffer)
Log.i(tag, "Read Length: $tempSize")
//对数据进行解析,这个根据交互的实际情况出发。
val tempCollect = String(tempBuffer) //此数据长这样:{36.35}{36.35}{36.35}.......
val temperature = tempCollect.substring(1, 6) //拿到第一个温度,设置上去即可。
withContext(Dispatchers.Main) {
findViewById<TextView>(R.id.result).text = temperature
}
}
}
} catch (e: IOException) {
e.printStackTrace()
}
}
开启协程,执行读取任务
lifecycleScope.launch(Dispatchers.IO) {
readTemperature()
}
记得不用的时候关一下就行了
private fun closePort() {
try {
isStopRead = true
inputStream?.close()
outputStream?.close()
serialPort?.close()
serialPort = null
} catch (e: Exception) {
e.printStackTrace()
}
}
简单的测温模块这样就完成了。当然,如果你要给串口发送命令的话,看下面代码
//给串口发送命令
private suspend fun sendCommand(hexStr: String) {
try {
outputStream?.also {
it.write(hexStr2bytes(hexStr))
}
} catch (e: IOException) {
e.printStackTrace()
}
}
//把十六进制表示的字节数组字符串,转换成十六进制字节数组
private fun hexStr2bytes(hex: String): ByteArray {
val len = (hex.length / 2)
val result = ByteArray(len)
val achar = hex.uppercase(Locale.ROOT).toCharArray()
for (i in 0 until len) {
val pos = i * 2
result[i] = (hexChar2byte(achar[pos]) shl 4 or hexChar2byte(achar[pos + 1])).toByte()
}
return result
}
// 把十六进制字符转成字节
private fun hexChar2byte(c: Char) = when (c) {
'0' -> 0
'1' -> 1
'2' -> 2
'3' -> 3
'4' -> 4
'5' -> 5
'6' -> 6
'7' -> 7
'8' -> 8
'9' -> 9
'a', 'A' -> 10
'b', 'B' -> 11
'c', 'C' -> 12
'd', 'D' -> 13
'e', 'E' -> 14
'f', 'F' -> 15
else -> -1
}