工業控制、儀表采集、POS 設備等場景仍大量依賴 RS232/RS485 串口通訊。本文補全原文代碼結構,系統講解 Java 串口開發全棧:驅動選型、通訊參數配置、異步事件監聽、Modbus RTU 幀構造、CRC16 校驗算法、IEEE754 浮點數解析、線程安全與異常恢復機制。
| 庫 | 狀態 | 特點 | 推薦場景 |
|---|---|---|---|
| RXTX | 停止維護 | 標準 API、跨平台、需原生庫 | 維護老項目 |
| jSerialComm | 活躍維護 | 無原生依賴、API 簡潔、自動掃描 | 新項目首選 |
| NRJavaSerial | 活躍 | RXTX 兼容、Maven Central 發布 | 遷移 RXTX 項目 |
| PureJavaComm | 活躍 | 純 Java、支援藍牙、USB CDC | 嵌入式/無原生環境 |
<dependency>
<groupId>com.fazecast</groupId>
<artifactId>jSerialComm</artifactId>
<version>2.10.4</version>
</dependency>
public class SerialConfig {
private String portName; // COM1 / /dev/ttyUSB0
private int baudRate = 9600; // 2400/4800/9600/19200/38400/57600/115200
private int dataBits = 8; // 5/6/7/8
private int stopBits = 1; // 1/1.5/2
private int parity = 0; // 0=NONE, 1=ODD, 2=EVEN, 3=MARK, 4=SPACE
private int timeoutMs = 2000; // 讀超時
// getter/setter + builder
}
public class SerialManager {
private final SerialConfig config;
private SerialPort serialPort;
private final Object lock = new Object();
private final List listeners = new CopyOnWriteArrayList<>();
public SerialManager(SerialConfig config) { this.config = config; }
public boolean open() {
synchronized (lock) {
if (serialPort != null && serialPort.isOpen()) return true;
serialPort = SerialPort.getCommPort(config.getPortName());
serialPort.setBaudRate(config.getBaudRate());
serialPort.setNumDataBits(config.getDataBits());
serialPort.setNumStopBits(config.getStopBits());
serialPort.setParity(config.getParity());
serialPort.setComPortTimeouts(SerialPort.TIMEOUT_READ_SEMI_BLOCKING,
config.getTimeoutMs(), 0);
if (!serialPort.openPort()) return false;
// 註冊異步監聽
serialPort.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) {
byte[] buffer = new byte[serialPort.bytesAvailable()];
int numRead = serialPort.readBytes(buffer, buffer.length);
if (numRead > 0) {
byte[] data = Arrays.copyOf(buffer, numRead);
notifyListeners(data);
}
}
}
});
return true;
}
}
public void send(byte[] data) {
synchronized (lock) {
if (serialPort != null && serialPort.isOpen()) {
serialPort.writeBytes(data, data.length);
}
}
}
public void addListener(SerialDataListener listener) { listeners.add(listener); }
public void removeListener(SerialDataListener listener) { listeners.remove(listener); }
private void notifyListeners(byte[] data) {
for (SerialDataListener l : listeners) l.onDataReceived(data);
}
public void close() {
synchronized (lock) {
if (serialPort != null) {
serialPort.removeDataListener();
serialPort.closePort();
serialPort = null;
}
}
}
@FunctionalInterface
public interface SerialDataListener {
void onDataReceived(byte[] data);
}
}
[從站地址:1B] [功能碼:1B] [數據域:N B] [CRC16:2B 低位在前]
例:01 03 00 00 00 02 C4 0B (讀保持寄存器)
public class ModbusRtuFrame {
public static byte[] buildRequest(int slaveId, int functionCode, int startAddr, int quantity) {
ByteBuffer buf = ByteBuffer.allocate(6).order(ByteOrder.BIG_ENDIAN);
buf.put((byte) slaveId);
buf.put((byte) functionCode);
buf.putShort((short) startAddr);
buf.putShort((short) quantity);
byte[] pdu = buf.array();
return appendCrc16(pdu);
}
public static byte[] buildWriteSingleRegister(int slaveId, int addr, int value) {
ByteBuffer buf = ByteBuffer.allocate(6).order(ByteOrder.BIG_ENDIAN);
buf.put((byte) slaveId);
buf.put((byte) 0x06); // 功能碼 06
buf.putShort((short) addr);
buf.putShort((short) value);
return appendCrc16(buf.array());
}
private static byte[] appendCrc16(byte[] data) {
int crc = CRC16.compute(data);
byte[] result = Arrays.copyOf(data, data.length + 2);
result[data.length] = (byte) (crc & 0xFF); // 低位
result[data.length + 1] = (byte) ((crc >> 8) & 0xFF); // 高位
return result;
}
public static boolean validateCrc(byte[] frame) {
if (frame.length < 3) return false;
byte[] data = Arrays.copyOf(frame, frame.length - 2);
int expected = CRC16.compute(data);
int actual = (frame[frame.length - 1] << 8) | (frame[frame.length - 2] & 0xFF);
return expected == actual;
}
}
public class CRC16 {
private static final int POLYNOMIAL = 0xA001; // x^16 + x^15 + x^2 + 1
// 查表法(高性能)
private static final int[] TABLE = new int[256];
static {
for (int i = 0; i < 256; i++) {
int crc = i;
for (int j = 0; j < 8; j++) {
crc = (crc & 1) != 0 ? (crc >>> 1) ^ POLYNOMIAL : (crc >>> 1);
}
TABLE[i] = crc;
}
}
public static int compute(byte[] data) {
int crc = 0xFFFF;
for (byte b : data) {
crc = (crc >>> 8) ^ TABLE[(crc ^ b) & 0xFF];
}
return crc;
}
// 逐位法(教學用)
public static int computeBitwise(byte[] data) {
int crc = 0xFFFF;
for (byte b : data) {
crc ^= b & 0xFF;
for (int i = 0; i < 8; i++) {
if ((crc & 1) != 0) crc = (crc >>> 1) ^ POLYNOMIAL;
else crc >>>= 1;
}
}
return crc;
}
}
Modbus 寄存器為 16 位,浮點數佔兩個寄存器(32 位),常見兩種字節序:
public class ModbusFloat {
// CDAB 格式:低寄存器在前
public static float parseCDAB(int lowReg, int highReg) {
int bits = (highReg << 16) | (lowReg & 0xFFFF);
return Float.intBitsToFloat(bits);
}
// ABCD 格式:高寄存器在前
public static float parseABCD(int highReg, int lowReg) {
int bits = (highReg << 16) | (lowReg & 0xFFFF);
return Float.intBitsToFloat(bits);
}
// 字節序交換(BADC/DCBA 等變體)
public static float parseSwapped(byte[] fourBytes, ByteOrder order) {
return ByteBuffer.wrap(fourBytes).order(order).getFloat();
}
// 反向轉換
public static int[] toRegistersCDAB(float value) {
int bits = Float.floatToIntBits(value);
return new int[] { bits & 0xFFFF, (bits >>> 16) & 0xFFFF };
}
}
public class ModbusClient {
private final SerialManager serial;
private final int slaveId;
private final CountDownLatch latch = new CountDownLatch(1);
private byte[] response;
private volatile boolean timeout = false;
public ModbusClient(SerialManager serial, int slaveId) {
this.serial = serial;
this.slaveId = slaveId;
serial.addListener(this::onData);
}
public float readFloatRegister(int address) throws Exception {
byte[] request = ModbusRtuFrame.buildRequest(slaveId, 0x03, address, 2);
response = null;
timeout = false;
serial.send(request);
if (!latch.await(2, TimeUnit.SECONDS)) {
timeout = true;
throw new SocketTimeoutException("響應超時");
}
if (response == null || response.length < 5) {
throw new IOException("響應數據無效");
}
if (!ModbusRtuFrame.validateCrc(response)) {
throw new IOException("CRC 校驗失敗");
}
// 響應格式:[地址][功能碼][字節數][數據...][CRC]
int byteCount = response[2] & 0xFF;
if (byteCount != 4) throw new IOException("數據長度異常");
int lowReg = ((response[3] & 0xFF) << 8) | (response[4] & 0xFF);
int highReg = ((response[5] & 0xFF) << 8) | (response[6] & 0xFF);
return ModbusFloat.parseCDAB(lowReg, highReg);
}
private void onData(byte[] data) {
if (timeout) return;
this.response = data;
latch.countDown();
}
}
synchronized 保證幀完整發送,避免交錯SerialPortEvent.PORT_CLOSED 觸發指數退避重連