bigywater.tech
Java · 255

Java 串口通信:把设备字节流变成可靠协议

串口读到的是字节流,不是消息。设备分几次发、中间夹杂噪声、响应迟到时怎么保证协议可靠?这篇用一个 pty 伪终端模拟的「设备」配合 jSerialComm,真实运行帧的累积、CRC 重同步、读超时模式与带序号的重试。

12, 12, 12, 9
一个 45 字节的帧每次 readBytes 返回的字节数
21 字节
解析器重同步时丢弃的噪声字节数
seq
用序号丢弃迟到的旧响应

字节流与帧

实测 · 本机执行输出
  设备把一个 45 字节的帧分 4 次发出(每次间隔约 30 ms):每次 readBytes 返回的字节数 = [12, 12, 12, 9],分别在 [0, 39, 79, 119] ms 返回;总共 45 字节
  按帧解析:读到完整帧 1 个,payload 长度 40,第一次读到的 12 字节不足以构成一个帧(帧头 + 长度字段 + 数据 + CRC 共 45 字节)

噪声与重同步

实测 · 本机执行输出
  设备依次发出:7 字节垃圾 + 一个 CRC 错误的帧 + 一个正确的帧(seq=1)+ 3 字节垃圾 + 一个正确的帧(seq=2),共读到 43 字节
  解析结果:接受 2 个帧 seq=[1, 2],payload [first, second];丢弃的垃圾字节数 21;CRC 不符而重新找帧头的次数 1

读超时模式

实测 · 本机执行输出
  TIMEOUT_NONBLOCKING                  设备不发任何数据时 readBytes(8) → 返回 0,耗时 0 ms
  TIMEOUT_READ_SEMI_BLOCKING(300 ms)   设备不发任何数据时 readBytes(8) → 返回 0,耗时 302 ms
  TIMEOUT_READ_BLOCKING(300 ms)        设备不发任何数据时 readBytes(8) → 返回 0,耗时 301 ms
  TIMEOUT_READ_BLOCKING(2000 ms):设备 100 ms 后只发来 3 字节(要求读 8 个):readBytes → 3,耗时 2102 ms(要读满 8 个字节或超时)
  TIMEOUT_READ_SEMI_BLOCKING(2000 ms):同样的 3 字节 → readBytes → 3,耗时 103 ms(有数据就返回)
  bytesAvailable() = 0

超时重试与序号

实测 · 本机执行输出
  t=0ms 发出请求 seq=1
  t=209ms seq=1 超过 200 ms 没有响应,重试
  t=212ms 发出请求 seq=2
  t=213ms 收到 seq=1 的响应「late-reply-for-seq1」→ 不是当前请求(seq=2),丢弃
  t=244ms 收到 seq=2 的响应「reply-for-seq2」→ 接受
  结果:reply-for-seq2;总耗时 250 ms

分步演示

串口通信
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / OpenJDK(Temurin 17.0.20、Homebrew 21.0.12 与 27,每篇注明实际用的版本);用 javac 编译、java 直接运行,需要第三方库时用 Maven 解析依赖;计时用 System.nanoTime,数字每次运行会有小幅抖动。jSerialComm 2.11.0;「设备」是 Python 通过 pty.openpty() 创建的伪终端的一端(macOS)。没有接真实串口硬件:波特率、校验位、流控没有物理意义;拆包的节奏来自设备脚本的 sleep,不是波特率
    • 没有验证:真实 USB 转串口设备(/dev/cu.*)的行为、波特率误差与线路噪声、硬件流控(RTS/CTS)、串口被其它进程占用时的错误、热插拔、RS-485 半双工方向控制、RXTX 与 jSerialComm 的差别、Windows 的 COM 口命名
    京ICP备2026067502号-1京公网安备11010502063487号