首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 程式設計入門

Java 串行通訊開發指南:基於 RS-232 與 javax.comm / jSerialComm 實戰

     編輯:Java 程式設計入門

前言:串口雖老,仍是工控/醫療/物聯網鐵律

USB、藍牙、Wi-Fi、LoRa、5G 層出不窮,但 **RS-232/RS-485/RS-422 串行通訊** 依然統治著:PLC/變頻器/儀表/條碼掃描器/醫療設備/智能電表/工業機器人/舊系統改造。Java 標準庫無串口 API,**javax.comm** (Sun 1998, 早已停維) 、**RXTX** (開源移植, 2010 後停更) 、**jSerialComm** (Fazecast, 純 Java + JNA, 現役主力)、**PureJavaComm** (純 Java, 輕量) 成為四大選擇。本文以 **jSerialComm** 為主,系統講解串口基礎、參數配置、讀寫模式、协议解析、多埠口管理。

一、RS-232 物理層與協議基礎 (必懂硬體知識)

1. 連接器與腳位 (DB9 公/母座最常見)

腳位縮寫全稱方向 (DTE 視角: PC)功能
1DCDData Carrier Detect輸入數據載波檢測 (Modem 就緒)
2RXDReceive Data輸入**接收數據** (關鍵)
3TXDTransmit Data輸出**發送數據** (關鍵)
4DTRData Terminal Ready輸出數據終端就緒
5GNDSignal Ground—**信號地** (關鍵)
6DSRData Set Ready輸入數據設備就緒
7RTSRequest To Send輸出請求發送 (硬體流控)
8CTSClear To Send輸入允許發送 (硬體流控)
9RIRing Indicator輸入振鈴指示

最小三線制:TXD ↔ RXD (交叉)、GND ↔ GND。**必須交叉連接** (PC TXD → 設備 RXD)。

2. 關鍵通訊參數 (雙方必須完全一致)

  • 波特率:9600, 19200, 38400, 57600, 115200 (位元/秒)。
  • 資料位:5/6/7/8 (通常 8)。
  • 校驗位:None (N), Even (E), Odd (O), Mark, Space (通常 N)。
  • 停止位:1, 1.5, 2 (通常 1)。
  • 流控:None / RTS/CTS (硬體) / XON/XOFF (軟體)。
  • 標準寫法:9600-8-N-1 (9600 baud, 8 data, No parity, 1 stop)。

二、jSerialComm 核心 API 速覽 (v2.10.0+)

類別/介面核心方法說明
SerialPortgetCommPorts() (靜態列舉所有埠口)、getCommPort(String name)、openPort()/closePort()、setBaudRate(int)、setNumDataBits(int)、setNumStopBits(int)、setParity(int)、setFlowControl(int)、setComPortTimeouts(int mode, int readTimeout, int writeTimeout)、getInputStream()/getOutputStream()、addDataListener(SerialPortDataListener)、removeDataListener()埠口控制核心,非執行緒安全,單執行緒操作
SerialPortDataListenerint getListeningEvents() (返回 LISTENING_EVENT_DATA_RECEIVED / LISTENING_EVENT_DATA_WRITTEN)、void serialEvent(SerialPortEvent event)**非阻塞事件驅動模式** 推薦介面,避免輪詢執行緒
SerialPortEventgetReceivedData() (byte[])、getEventType()、getSerialPort()事件載體

三、三種讀寫模式對比與代碼實戰

模式 1:阻塞 I/O (傳統 InputStream/OutputStream)


SerialPort port = SerialPort.getCommPort("COM3"); // Windows
// Linux: "/dev/ttyUSB0", "/dev/ttyS0"
// Mac: "/dev/tty.usbserial-XXXX"
port.setBaudRate(9600);
port.setNumDataBits(8);
port.setNumStopBits(1);
port.setParity(SerialPort.NO_PARITY);
port.setFlowControl(SerialPort.FLOW_CONTROL_DISABLED);
port.setComPortTimeouts(SerialPort.TIMEOUT_READ_BLOCKING, 1000, 1000); // 阻塞模式

if (!port.openPort()) throw new IllegalStateException("Open port failed");

try (OutputStream out = port.getOutputStream();
     InputStream in = port.getInputStream()) {

    // 發送指令 (例: Modbus RTU 讀保持寄存器)
    byte[] cmd = {0x01, 0x03, 0x00, 0x00, 0x00, 0x02, 0xC4, 0x0B}; // CRC16
    out.write(cmd);
    out.flush();

    // 讀取回應 (阻塞直到滿足超時或資料到達)
    byte[] buffer = new byte[256];
    int len = in.read(buffer); // 可能不足預期長度,需協議層組裝
    System.out.println("Received: " + bytesToHex(buffer, len));
} finally {
    port.closePort();
}

缺點:阻塞執行緒、難處理變長幀、需自行管理讀取執行緒。

模式 2:非阻塞輪詢 (Timeout 模式)


port.setComPortTimeouts(SerialPort.TIMEOUT_NONBLOCKING, 0, 0); // 非阻塞
// 或 TIMEOUT_READ_SEMI_BLOCKING: 有資料即返回,無資料等待 timeout

// 需自建讀取執行緒輪詢
ExecutorService executor = Executors.newSingleThreadExecutor();
executor.submit(() -> {
    byte[] buffer = new byte[1024];
    while (port.isOpen()) {
        int len = port.getInputStream().read(buffer);
        if (len > 0) {
            // 送入協議解碼器 (如 ByteToMessageDecoder)
            protocolDecoder.decode(buffer, len);
        } else {
            Thread.sleep(10); // 避免空轉佔 CPU
        }
    }
});

模式 3:事件驅動 (推薦 — SerialPortDataListener)


port.setComPortTimeouts(SerialPort.TIMEOUT_READ_SEMI_BLOCKING, 100, 0); // 配合事件模式

port.addDataListener(new SerialPortDataListener() {
    @Override
    public int getListeningEvents() {
        return SerialPort.LISTENING_EVENT_DATA_RECEIVED;
    }

    @Override
    public void serialEvent(SerialPortEvent event) {
        if (event.getEventType() != SerialPort.LISTENING_EVENT_DATA_RECEIVED) return;
        byte[] newData = event.getReceivedData();
        // 直接送入協議解碼器 (需執行緒安全)
        protocolDecoder.decode(newData);
    }
});

// 發送數據 (非阻塞)
port.getOutputStream().write(requestFrame);
port.getOutputStream().flush();

優勢:無輪詢執行緒、資料到達即回調、延遲最低、CPU 佔用極低。**生產環境首選。**

四、協議解析與幀組裝 (關鍵難點)

串口是**位元組流**,無訊息邊界。應用層必須實現**幀同步**:

  • 定長幀:固定位元組數,直接切片。
  • 分隔符幀:換行符 (\n/\r\n)、特定字元 (Modbus RTU 靜默間隔 3.5 字元時間)。
  • 長度前綴:首 1-2 位元組標明荷載長度。
  • 特定起始/結束標記:如 0x7E (HDLC)、0x68 (DL/T 645)。

推薦架構:ByteBuf (Netty) / RingBuffer 累積位元組 → **狀態機解碼器** 解析完整幀 → 交付業務層。參考 Netty ByteToMessageDecoder 設計模式。

五、多埠口管理與生產級封裝建議


public class SerialPortManager {
    private final Map ports = new ConcurrentHashMap<>();
    private final ProtocolDecoder decoder;

    public SerialPortManager(ProtocolDecoder decoder) { this.decoder = decoder; }

    public synchronized SerialPort openPort(String portName, SerialPortConfig config) {
        SerialPort port = SerialPort.getCommPort(portName);
        config.applyTo(port); // 封裝參數設定
        port.setComPortTimeouts(SerialPort.TIMEOUT_READ_SEMI_BLOCKING, 100, 0);
        port.addDataListener(new SerialPortDataListener() {
            @Override public int getListeningEvents() { return SerialPort.LISTENING_EVENT_DATA_RECEIVED; }
            @Override public void serialEvent(SerialPortEvent event) {
                if (event.getEventType() == SerialPort.LISTENING_EVENT_DATA_RECEIVED) {
                    decoder.decode(event.getReceivedData(), portName); // 攜帶埠口標識
                }
            }
        });
        if (!port.openPort()) throw new IllegalStateException("Failed to open " + portName);
        ports.put(portName, port);
        return port;
    }

    public void send(String portName, byte[] data) {
        SerialPort port = ports.get(portName);
        if (port != null && port.isOpen()) {
            try { port.getOutputStream().write(data); port.getOutputStream().flush(); }
            catch (IOException e) { handleError(portName, e); }
        }
    }

    public void closeAll() { ports.values().forEach(SerialPort::closePort); ports.clear(); }
}

六、避坑指南

  1. 埠口名稱跨平台差異:Windows `COM1`~`COM256`、Linux `/dev/ttyUSB*` `/dev/ttyS*` `/dev/ttyACM*`、Mac `/dev/tty.usbserial-*` `/dev/tty.usbmodem*`。配置外部化。
  2. 資源洩漏:closePort() 必須在 `finally` / `try-with-resources` / 關閉鉤子中調用,否則埠口被佔用需重啟機器/拔插設備。
  3. 編碼問題:串口傳輸**位元組**,非字元。勿直接 `new String(bytes)` 除非確定是 ASCII/UTF-8 文本協議。十六進制/二進制協議用 `byte[]`/`ByteBuf` 處理。
  4. 並發存取:`SerialPort` **非執行緒安全**。單埠口單執行緒操作,或自行加鎖。事件回調執行緒屬於庫內部,**不可在回調中執行耗時操作/呼叫阻塞 API**,快速切片入隊即可。
  5. USB 轉串口晶片驅動:CH340/CH341 (Win/Mac 需裝驅動)、FTDI (內建)、CP210x (Silabs 驅動)、PL2303 (Prolific 驅動)。Linux 通常內建。

結語

Java 串口開發,**選 jSerialComm、用事件驅動模式、建立協議解碼器、封裝埠口管理器、外部化配置、嚴格資源釋放**。掌握 RS-232/485 物理層知識、波特率/校驗/流控參數、幀同步機制、Modbus RTU/DL-T645/自定義協議解析,即可從容應對工控、醫療、電力、物聯網邊緣端的串口整合挑戰。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved