1. 简介
jSerialComm 是一个用于 Java 串行通信的库,相比 RXTX 和 Java Communications API,它具有以下优点:
-
无需额外安装本地库(自动提取)
-
跨平台支持(Windows、Linux、macOS、Solaris)
-
易于使用的 API
-
支持 USB 串口热插拔检测
2. 安装
Maven
html
<dependency>
<groupId>com.fazecast</groupId>
<artifactId>jSerialComm</artifactId>
<version>2.10.2</version>
</dependency>
3. 基础用法
3.1 列出可用串口
java
import com.fazecast.jSerialComm.SerialPort;
public class ListPorts {
public static void main(String[] args) {
SerialPort[] ports = SerialPort.getCommPorts();
System.out.println("可用串口:");
for (SerialPort port : ports) {
System.out.println(port.getSystemPortName() + " - "
+ port.getDescriptivePortName() + " - "
+ port.getPortDescription());
}
}
}
3.2 打开和配置串口
java
SerialPort port = SerialPort.getCommPort("COM3"); // Windows
// SerialPort port = SerialPort.getCommPort("/dev/ttyUSB0"); // Linux/Mac
// 配置串口参数
port.setBaudRate(9600);
port.setNumDataBits(8);
port.setNumStopBits(1);
port.setParity(SerialPort.NO_PARITY);
// 打开串口
if (port.openPort()) {
System.out.println("串口打开成功");
} else {
System.out.println("串口打开失败");
return;
}
// 设置读写超时
port.setComPortTimeouts(SerialPort.TIMEOUT_READ_SEMI_BLOCKING, 100, 0);
3.3 发送数据
java
// 发送字符串
String data = "Hello Serial\n";
port.writeBytes(data.getBytes(), data.length());
// 发送字节数组
byte[] buffer = {0x01, 0x02, 0x03, 0x04};
port.writeBytes(buffer, buffer.length);
3.4 接收数据
java
// 读取数据
byte[] readBuffer = new byte[1024];
int numRead = port.readBytes(readBuffer, readBuffer.length);
String received = new String(readBuffer, 0, numRead);
System.out.println("接收: " + received);
4. 完整示例
简单收发示例
java
import com.fazecast.jSerialComm.SerialPort;
import java.util.Scanner;
public class SerialCommExample {
public static void main(String[] args) {
// 选择串口
SerialPort[] ports = SerialPort.getCommPorts();
if (ports.length == 0) {
System.out.println("未找到串口");
return;
}
SerialPort port = ports[0];
System.out.println("使用串口: " + port.getSystemPortName());
// 配置串口
port.setBaudRate(9600);
port.setNumDataBits(8);
port.setNumStopBits(1);
port.setParity(SerialPort.NO_PARITY);
port.setComPortTimeouts(SerialPort.TIMEOUT_READ_SEMI_BLOCKING, 100, 0);
// 打开串口
if (!port.openPort()) {
System.out.println("无法打开串口");
return;
}
// 创建读取线程
Thread readThread = new Thread(() -> {
byte[] buffer = new byte[1024];
while (true) {
int bytesRead = port.readBytes(buffer, buffer.length);
if (bytesRead > 0) {
String data = new String(buffer, 0, bytesRead);
System.out.print("接收: " + data);
}
}
});
readThread.start();
// 主线程发送数据
Scanner scanner = new Scanner(System.in);
System.out.println("输入要发送的数据(输入 'exit' 退出):");
while (true) {
String input = scanner.nextLine();
if (input.equalsIgnoreCase("exit")) {
break;
}
input += "\n";
port.writeBytes(input.getBytes(), input.length());
}
// 关闭串口
port.closePort();
System.exit(0);
}
}
5. 事件监听方式
java
import com.fazecast.jSerialComm.SerialPort;
import com.fazecast.jSerialComm.SerialPortDataListener;
import com.fazecast.jSerialComm.SerialPortEvent;
public class EventListenerExample {
public static void main(String[] args) {
SerialPort port = SerialPort.getCommPort("COM3");
port.setBaudRate(9600);
// 添加数据监听器
port.addDataListener(new SerialPortDataListener() {
@Override
public int getListeningEvents() {
return SerialPort.LISTENING_EVENT_DATA_AVAILABLE;
}
@Override
public void serialEvent(SerialPortEvent event) {
if (event.getEventType() != SerialPort.LISTENING_EVENT_DATA_AVAILABLE) {
return;
}
byte[] buffer = new byte[port.bytesAvailable()];
int bytesRead = port.readBytes(buffer, buffer.length);
if (bytesRead > 0) {
String data = new String(buffer);
System.out.println("收到数据: " + data);
}
}
});
port.openPort();
// 保持程序运行
try {
Thread.sleep(60000); // 运行60秒
} catch (InterruptedException e) {
e.printStackTrace();
}
port.closePort();
}
}
6. 高级功能
6.1 检测串口热插拔
java
import com.fazecast.jSerialComm.*;
public class HotPlugDetection {
public static void main(String[] args) {
SerialPort.addCommPortDiscoveryListener(new SerialPortDiscoveryListener() {
@Override
public void portAdded(SerialPort port) {
System.out.println("串口已连接: " + port.getSystemPortName());
}
@Override
public void portRemoved(SerialPort port) {
System.out.println("串口已移除: " + port.getSystemPortName());
}
});
// 保持程序运行
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
6.2 获取串口详细信息
java
SerialPort port = SerialPort.getCommPort("COM3");
System.out.println("系统名称: " + port.getSystemPortName());
System.out.println("描述性名称: " + port.getDescriptivePortName());
System.out.println("端口描述: " + port.getPortDescription());
System.out.println("制造商: " + port.getManufacturer());
System.out.println("产品ID: " + port.getProductID());
System.out.println("供应商ID: " + port.getVendorID());
System.out.println("串口号: " + port.getPortLocation());
6.3 配置流控制
java
// 禁用所有流控制
port.setFlowControl(SerialPort.FLOW_CONTROL_DISABLED);
// 启用硬件流控制 (RTS/CTS)
port.setFlowControl(SerialPort.FLOW_CONTROL_RTS_ENABLED |
SerialPort.FLOW_CONTROL_CTS_ENABLED);
// 启用软件流控制 (XON/XOFF)
port.setFlowControl(SerialPort.FLOW_CONTROL_XONXOFF_IN_ENABLED |
SerialPort.FLOW_CONTROL_XONXOFF_OUT_ENABLED);
7. 常见问题解决
7.1 权限问题 (Linux)
bash
# 将用户添加到 dialout 组
sudo usermod -a -G dialout $USER
# 或者临时修改权限
sudo chmod 666 /dev/ttyUSB0
7.2 读取阻塞问题
java
// TIMEOUT_READ_BLOCKING - 无限期等待
// TIMEOUT_READ_SEMI_BLOCKING - 等待直到有数据或超时
// TIMEOUT_NONBLOCKING - 立即返回,无数据则返回0
port.setComPortTimeouts(SerialPort.TIMEOUT_READ_SEMI_BLOCKING, 500, 0);
8. 完整应用示例:串口调试助手
java
import com.fazecast.jSerialComm.SerialPort;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.io.IOException;
public class SerialDebugger extends JFrame {
private SerialPort selectedPort;
private JTextArea textArea;
private JTextField inputField;
private JComboBox<String> portCombo;
private JComboBox<Integer> baudRateCombo;
private JButton openButton;
private JButton sendButton;
private boolean isOpen = false;
public SerialDebugger() {
setTitle("串口调试助手");
setSize(600, 400);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
initUI();
refreshPorts();
}
private void initUI() {
// 顶部控制面板
JPanel controlPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
controlPanel.add(new JLabel("串口:"));
portCombo = new JComboBox<>();
controlPanel.add(portCombo);
controlPanel.add(new JLabel("波特率:"));
Integer[] baudRates = {9600, 19200, 38400, 57600, 115200};
baudRateCombo = new JComboBox<>(baudRates);
baudRateCombo.setSelectedIndex(0);
controlPanel.add(baudRateCombo);
JButton refreshButton = new JButton("刷新");
refreshButton.addActionListener(e -> refreshPorts());
controlPanel.add(refreshButton);
openButton = new JButton("打开串口");
openButton.addActionListener(e -> togglePort());
controlPanel.add(openButton);
add(controlPanel, BorderLayout.NORTH);
// 数据显示区域
textArea = new JTextArea();
textArea.setEditable(false);
JScrollPane scrollPane = new JScrollPane(textArea);
add(scrollPane, BorderLayout.CENTER);
// 底部发送区域
JPanel sendPanel = new JPanel(new BorderLayout());
inputField = new JTextField();
sendButton = new JButton("发送");
sendButton.setEnabled(false);
sendButton.addActionListener(e -> sendData());
sendPanel.add(inputField, BorderLayout.CENTER);
sendPanel.add(sendButton, BorderLayout.EAST);
add(sendPanel, BorderLayout.SOUTH);
}
private void refreshPorts() {
portCombo.removeAllItems();
SerialPort[] ports = SerialPort.getCommPorts();
for (SerialPort port : ports) {
portCombo.addItem(port.getSystemPortName());
}
}
private void togglePort() {
if (!isOpen) {
// 打开串口
String portName = (String) portCombo.getSelectedItem();
selectedPort = SerialPort.getCommPort(portName);
selectedPort.setBaudRate((Integer) baudRateCombo.getSelectedItem());
selectedPort.setNumDataBits(8);
selectedPort.setNumStopBits(1);
selectedPort.setParity(SerialPort.NO_PARITY);
selectedPort.setComPortTimeouts(SerialPort.TIMEOUT_READ_SEMI_BLOCKING, 100, 0);
if (selectedPort.openPort()) {
isOpen = true;
openButton.setText("关闭串口");
sendButton.setEnabled(true);
portCombo.setEnabled(false);
baudRateCombo.setEnabled(false);
// 启动读取线程
new Thread(this::readData).start();
} else {
JOptionPane.showMessageDialog(this, "无法打开串口");
}
} else {
// 关闭串口
selectedPort.closePort();
isOpen = false;
openButton.setText("打开串口");
sendButton.setEnabled(false);
portCombo.setEnabled(true);
baudRateCombo.setEnabled(true);
}
}
private void readData() {
byte[] buffer = new byte[1024];
while (isOpen) {
int bytesRead = selectedPort.readBytes(buffer, buffer.length);
if (bytesRead > 0) {
String data = new String(buffer, 0, bytesRead);
SwingUtilities.invokeLater(() -> {
textArea.append("接收: " + data + "\n");
textArea.setCaretPosition(textArea.getDocument().getLength());
});
}
}
}
private void sendData() {
String data = inputField.getText();
if (!data.isEmpty() && isOpen) {
data += "\n";
selectedPort.writeBytes(data.getBytes(), data.length());
textArea.append("发送: " + data);
inputField.setText("");
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
new SerialDebugger().setVisible(true);
});
}
}
9. 总结
jSerialComm 提供了简单而强大的串口通信 API,主要特点:
-
简单直观的 API 设计
-
跨平台兼容性好
-
支持事件驱动编程
-
提供热插拔检测
-
性能稳定可靠
建议在实际项目中使用时,根据具体需求选择合适的通信模式(轮询或事件监听)。