Java串口操作

概述

这篇文章讲解了使用 rxtx 工具包在 java 程序中连接串口,并使用串口收发数据。

认识串口

串行接口(Serial Interface)简称串口,也称串行通信接口或串行通讯接口(通常是指COM接口),是采用串行通信方式的扩展接口,串口传输时数据一位一位地顺序传送。串行通信具有通信线路简单的特点,只需要一对传输线就可以实现双向通信,降低了成本,适合远距离通信,缺点是传输速度较慢。

串口标准分类:

  • RS-232: 也称标准串口,最常用的串行通信接口。
  • RS-422: 全称“平衡电压数字接口电路的电气特性”,它定义了接口电路的特性。
  • RS-485: 在 RS-422 基础上发展而来。

驱动下载

这里使用的连接工具是 JavaRXTX,用于在java中实现串口和并行通信,它能够在64位/32位的Windows和Linux系统上使用。

RXTX for Java: http://fizzed.com/oss/rxtx-for-java

注意:下载时要区分平台,网站提供了Windows版和Linux版,此外还需要区分系统架构,即区分64位和32位的 JVM,特别注意是 JVM 的版本,而不是系统的版本。

下载解压后,需要将包和驱动拷贝到Java目录下:

Windows:

  • RXTXcomm.jar 拷贝到 <JAVA_HOME>\jre\lib\ext
  • rxtxSerial.dll 拷贝到 <JAVA_HOME>\jre\bin
  • rxtxParallel.dll 拷贝到 <JAVA_HOME>\jre\bin

Linux:

  • RXTXcomm.jar 拷贝到 <JAVA_HOME>/jre/lib/ext
  • librxtxSerial.so 拷贝到 <JAVA_HOME>/jre/lib/i386/
  • librxtxParallel.so 拷贝到 <JAVA_HOME>/jre/lib/i386/

代码示例

导入串口通信jar包

下载 rxtx 的 jar 包导入项目,或在 pom.xml 中添加依赖:

1
2
3
4
5
<dependency>
<groupId>org.rxtx</groupId>
<artifactId>rxtx</artifactId>
<version>2.1.7</version>
</dependency>

查看可用串口

使用 CommPortIdentifier 类的静态方法 getPortIdentifiers() 获取当前主机中可有的串口列表,返回结果是一个 Enumeration (列举),使用 while 循环遍历其中元素,即为串口信息。通过 getName() 方法获取串口名称,以此判断是否是我们想要开启的串口。

1
2
3
4
5
Enumeration<CommPortIdentifier> em = CommPortIdentifier.getPortIdentifiers();
while (em.hasMoreElements()) {
String name = em.nextElement().getName();
System.out.println(name);
}

使用串口

  • 获取所有可用的串口信息列表,遍历列表,通过串口名找到想要开启的串口,串口信息对象的类型为 CommPortIdentifier
  • 使用 CommPortIdentifieropen(String owner, int timeOut) 方法开启串口通信,方法参数分别是:owner 串口使用者(名称随意),timeOut 开启串口的超时时间,单位毫秒。
  • 开启串口后将对象类型强转成 SerialPort 类型,即为我们可用于传输数据的对象,然后需要使用 setSerialPortParams(int baudRate, int dataBit, int stopBit, int checkType) 方法为串口对象设定参数,包括波特率、数据位、停止位、校验方式。
  • 使用串口对象的 addEventListener(SerialPortEventListener listener) 方法添加串口事件监听器,SerialPortEventListener 是一个接口,需要自己实现它。
  • 添加串口事件监听器后,还需要使用 notifyOnDataAvailable(boolean b) 开启数据可用时的通知,开启后,收到数据时才会触发事件,监听器才会收到通知。
  • 收数据:在串口事件监听器中,需要实现 serialEvent(SerialPortEvent serialPortEvent) 方法,通过 SerialPortEvent 类的 getEventType() 方法获取事件类型,当事件类型为 SerialPortEvent.DATA_AVAILABLE 时,说明有可读数据,通过串口对象的 getInputStream() 方法获取输入流,使用输入流即可从流中读取数据。
  • 发数据:使用串口对象的 getOutputStream() 方法获取输出流,通过输出流即可发送数据。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
import gnu.io.*;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Enumeration;
import java.util.TooManyListenersException;

public class Test {
private static SerialPort mySerialPort = null;

public static void main(String[] args) {
String myCommName = "COM2"; // 要开启的端口名
int baudRate = 9600; // 通信波特率
int dataBit = SerialPort.DATABITS_8; // 数据位,8
int stopBit = SerialPort.STOPBITS_1; // 停止位,1
int checkBit = SerialPort.PARITY_NONE; // 校验方式,无

// 获取所有可用的串口信息
Enumeration<CommPortIdentifier> commPorts = CommPortIdentifier.getPortIdentifiers();
while (commPorts.hasMoreElements()) {
CommPortIdentifier port = commPorts.nextElement();
System.out.println("Port: " + port.getName());
if (CommPortIdentifier.PORT_SERIAL == port.getPortType() && myCommName.equals(port.getName())) {
try {
// 开启串口,参数: 程序名称; 开启端口超时时间:ms
mySerialPort = (SerialPort) port.open("TestProgram", 2000);

// 设置串口通讯参数: 波特率,数据位,停止位, 校验方式
mySerialPort.setSerialPortParams(baudRate, dataBit, stopBit, checkBit);

// 添加串口事件监听器
mySerialPort.addEventListener(new SerialPortEventListener() {
public void serialEvent(SerialPortEvent serialPortEvent) {
// 休眠 25ms,解决数据断行问题
try {
Thread.sleep(25);
} catch (InterruptedException e) {
e.printStackTrace();
}
switch (serialPortEvent.getEventType()) {
case SerialPortEvent.BI: // 通讯中断
case SerialPortEvent.OE: // 溢位错误
case SerialPortEvent.FE: // 帧错误
case SerialPortEvent.PE: // 奇偶校验错误
case SerialPortEvent.CD: // 载波检测
case SerialPortEvent.CTS: // 清除发送
case SerialPortEvent.DSR: // 数据设备准备好
case SerialPortEvent.RI: // 响铃侦测
case SerialPortEvent.OUTPUT_BUFFER_EMPTY: // 输出缓冲区已清空
break;
case SerialPortEvent.DATA_AVAILABLE: // 有数据到达
// 读取数据
InputStream inputStream = null;
try {
// 获取输入流
inputStream = mySerialPort.getInputStream();
while (inputStream.available() > 0) {
int len = inputStream.available();
byte[] readBuffer = new byte[len];
inputStream.read(readBuffer);
String data = new String(readBuffer, 0, len).trim();
System.out.println("Receive data: " + data);
// 打印收到的字节
System.out.print("Hex data: ");
for (byte b : readBuffer) {
String s = Integer.toHexString(0xFF & b);
System.out.print(b < 16 ? "0" + s : s);
}
System.out.println();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
break;
default:
break;
}
}
});
// 开启当有数据时的通知
mySerialPort.hnotifyOnDataAvailable(true);

// 发送数据
// 获取串口输出流
OutputStream outputStream = mySerialPort.getOutputStream();
outputStream.write("hello".getBytes());
outputStream.flush();
if (outputStream != null) outputStream.close();

} catch (PortInUseException e) {
e.printStackTrace();
} catch (TooManyListenersException e) {
e.printStackTrace();
} catch (UnsupportedCommOperationException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
break;
}
}
}
}

测试

如果没有硬件用来测试串口,可以使用模拟串口软件来模拟,这里使用到的是 Virtual Serial Port Driver 工具,收费软件,可免费使用 14 天。用这个软件可以创建虚拟串口组,我们可以开启两个串口程序分别连接,即可进行通信测试。

软件操作很简单,创建一个 Pair 即可,我创建的是 COM2COM3 ,是两个虚拟串口,通过程序连上这两个串口后,软件上会显示串口连接信息,包括连接的程序、配置、收发字节数。

将上述的 java 代码再复制一份,并将代码中的连接串口名分别改成创建的虚拟串口 COM2COM3 ,运行代码后,查看 Virtual Serial Port Driver ,发现串口已经被连接上了。查看java程序控制台,发现其中一个程序控制台输出收到的数据,说明程序运行成功。

1
2
3
Data len: 5
Receive data: hello
Hex data: 68656c6c6f

模拟串口工具

Virtual Serial Port Driver (免费试用14天)

Download Virtual Serial Port Driver: https://www.eltima.com/vspd-post-download.html

为开发、测试、排错人员设计的串口工具,能够创建大量虚拟串口,解决系统没有串口的难题。Virtual Serial Port Driver 不能能单独使用,还能将高级特性直接集成到用户的产品中去。

Virtual Serial Ports Kit (免费试用14天)

Download Virtual Serial Port Kit: https://www.virtual-serial-port.com/virtual-serial-port-kit-download.html

能够创建使用虚拟零调制解调器线缆(virtual null-modem cable)连接的虚拟串口,让软件像连接真实的零调制解调器线缆一样工作。

Free Virtual Serial Ports (免费)

FREE Virtual Serial Ports driver, Rs-232 null modem emulator: https://freevirtualserialports.com/

Windows 平台下的串口模拟工具,能够创建使用虚拟零调制解调器线缆(virtual null-modem cable)连接的虚拟串口。支持 Win 10 32位和64位,支持Windows Server 2019。

完整版可免费试用14天,免费版可免费使用,但功能有限制,限制包括:

  • 没有永久设备
  • 不能创建远程网桥
  • 不能用户命名设备
  • 不支持多个设备使用相同模式
  • 不支持高级设备配置
  • 不能用于商业、政府和军事用途

注意事项

驱动版本

RXTX 驱动在使用时需要区分架构版本,32位或64位,需要注意的是这个版本是指 JAVA 的版本,而不是操作系统的架构版本。

驱动配置

使用 RXTX for Java 操作串口时,需要先配置驱动,将驱动文件拷贝到运行环境中,操作方式参考驱动下载一节,否则会报下面的错:

1
java.lang.UnsatisfiedLinkError: no rxtxSerial in java.library.path

串口数据读取断行问题

在实际情况中,读取串口数据时,可能会出现数据读取断行的问题,导致程序收到的数据被分成几段,可以在串口事件监听器中添加 Thread.sleep(time) 来短暂休眠,以此解决。其中 time 是休眠时长,单位毫秒,这个值需要小于数据发送方的数据发送间隔。如传感器每 50ms 上传一次数据,则可选择休眠 25ms 。

1
2
3
4
5
try {
Thread.sleep(25);
} catch (InterruptedException e) {
e.printStackTrace();
}

参考资料

总结

串口通信在硬件开发时经常用到,学会用 java 操作串口,对于想要接触或配合硬件开发的 java 程序员来说,还是挺重要的。需要注意的是,对于硬件的操作需要区分平台,所以需要根据运行环境下载安装不同版本的工具包。另外, java 操作串口的库已经很久没有更新过了,随着硬件的发展,不知道以后这些库会不会有问题。