三大硬核方式揭秘:Java如何與底層硬體和工業裝置輕鬆通訊!

威哥爱编程發表於2024-09-26

大家好,我是V哥,程式設計師聊天真是三句不到離不開技術啊,這不前兩天跟一個哥們吃飯,他是我好多年前的學員了,一直保持著聯絡,現在都李總了,在做工業網際網路相關的專案,真是隻要 Java 學得好,能幹一輩子,卷死的是那些半吊子。

感謝李總給我分享了工業網際網路專案的事情,收穫很多,今天的內容來聊一聊 Java如何與底層硬體和工業裝置輕鬆通訊的事情。

Java讀取暫存器資料通常涉及與硬體裝置的通訊。這種操作通常是透過以下幾種方式來實現的:

使用 Modbus 協議讀取裝置暫存器資料(使用 jLibModbus

Modbus 是一種用於工業自動化裝置的通訊協議。常見的Modbus通訊方式包括:Modbus RTU(基於序列通訊)和Modbus TCP(基於網路通訊)。在此示例中,我們將使用 Java 和 jLibModbus 庫透過 Modbus TCP 協議讀取裝置的暫存器資料。

實現步驟

  1. 新增 jLibModbus 依賴。
  2. 設定 Modbus Master 客戶端。
  3. 透過 Modbus Master 讀取裝置的暫存器資料。

1. 新增 jLibModbus 依賴

使用 Maven 管理專案時,可以在 pom.xml 中新增 jLibModbus 依賴:

<dependency>
    <groupId>com.intelligt.modbus</groupId>
    <artifactId>jlibmodbus</artifactId>
    <version>1.2.8.1</version> <!-- 根據具體需求設定版本 -->
</dependency>

或者直接下載 jar 包並將其新增到專案的 classpath 中。

2. 示例程式碼:透過 Modbus TCP 讀取暫存器

import com.intelligt.modbus.jlibmodbus.Modbus;
import com.intelligt.modbus.jlibmodbus.ModbusMaster;
import com.intelligt.modbus.jlibmodbus.ModbusMasterFactory;
import com.intelligt.modbus.jlibmodbus.exception.ModbusIOException;
import com.intelligt.modbus.jlibmodbus.exception.ModbusNumberException;
import com.intelligt.modbus.jlibmodbus.exception.ModbusProtocolException;
import com.intelligt.modbus.jlibmodbus.modbus.ModbusFunctionCode;
import com.intelligt.modbus.jlibmodbus.tcp.TcpParameters;

import java.net.InetAddress;

public class ModbusTcpClient {
    public static void main(String[] args) {
        try {
            // 配置 Modbus TCP 引數
            TcpParameters tcpParameters = new TcpParameters();
            InetAddress address = InetAddress.getByName("192.168.1.100"); // 裝置的IP地址
            tcpParameters.setHost(address);
            tcpParameters.setPort(Modbus.TCP_PORT); // Modbus 預設TCP埠 502

            // 建立 ModbusMaster 例項
            ModbusMaster master = ModbusMasterFactory.createModbusMasterTCP(tcpParameters);
            master.connect();

            // 裝置的 Slave ID(通常是從站地址)
            int slaveId = 1;
            // 讀取保持暫存器(Holding Registers),從地址 0 開始,讀取 10 個暫存器
            int startAddress = 0;
            int quantity = 10;

            try {
                // 讀取保持暫存器資料
                int[] registerValues = master.readHoldingRegisters(slaveId, startAddress, quantity);
                System.out.println("暫存器資料:");
                for (int i = 0; i < registerValues.length; i++) {
                    System.out.println("暫存器[" + (startAddress + i) + "] = " + registerValues[i]);
                }
            } catch (ModbusProtocolException | ModbusNumberException | ModbusIOException e) {
                System.err.println("Modbus 讀取失敗: " + e.getMessage());
            }

            // 斷開連線
            master.disconnect();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

來解釋一下程式碼哈

  1. Modbus TCP 引數

    • TcpParameters 用於配置 Modbus TCP 的主機和埠。預設的Modbus TCP埠是 502
    • 使用 InetAddress.getByName("192.168.1.100") 設定裝置的IP地址。
  2. Modbus Master 例項

    • ModbusMaster master = ModbusMasterFactory.createModbusMasterTCP(tcpParameters); 建立一個Modbus主機(Master),用於與從裝置通訊。
    • master.connect(); 連線到Modbus裝置。
  3. 讀取保持暫存器

    • int[] registerValues = master.readHoldingRegisters(slaveId, startAddress, quantity); 讀取從裝置的保持暫存器(Holding Registers),從 startAddress 開始,讀取 quantity 個暫存器。
    • 輸出暫存器值。
  4. 錯誤處理

    • 捕獲 ModbusProtocolExceptionModbusNumberExceptionModbusIOException 以處理通訊過程中可能出現的錯誤。
  5. 斷開連線

    • 使用 master.disconnect(); 在完成資料讀取後斷開與Modbus裝置的連線。

3. 如何使用

  1. 裝置配置:確保你有一個支援 Modbus TCP 的裝置,並且裝置的IP地址與埠正確配置。通常情況下,Modbus TCP裝置監聽502埠。
  2. 執行程式:執行此 Java 程式,程式將連線到裝置並讀取指定暫存器的資料。
  3. 結果示例
   暫存器資料:
   暫存器[0] = 1234
   暫存器[1] = 5678
   暫存器[2] = 910
   ...

使用時要注意的事項

  1. 裝置通訊引數:確保裝置支援Modbus TCP協議,並正確配置了IP地址、埠和從站地址(Slave ID)。
  2. 讀取不同型別的暫存器:在Modbus中,可以讀取不同型別的暫存器,比如輸入暫存器(Input Registers)或線圈(Coils)。根據需求呼叫對應的方法:
    • readInputRegisters(...):讀取輸入暫存器。
    • readCoils(...):讀取線圈狀態。
  3. Modbus地址偏移:一些Modbus裝置使用1-based地址系統,而程式中可能使用0-based地址,注意這點以防讀取錯誤地址。

小結一下

我們透過 jLibModbus 庫使用 Java 讀取支援 Modbus TCP 協議的裝置的暫存器資料。Modbus是工業控制領域中廣泛應用的通訊協議,利用Java實現裝置通訊可以用於各種自動化系統中。如果你的裝置使用Modbus RTU協議,可以透過配置串列埠通訊來實現類似的操作。

JNI(Java Native Interface)

Java Native Interface (JNI) 允許Java程式碼與C/C++等本地語言編寫的程式碼互動,可以用於實現高效能、直接的硬體訪問,如暫存器讀取。

JNI基本流程

  1. 在Java中宣告本地方法。
  2. 使用javac編譯Java類。
  3. 使用javah生成C/C++標頭檔案。
  4. 編寫C/C++程式碼實現Java方法。
  5. 編譯生成動態庫。
  6. Java程式碼載入動態庫並呼叫本地方法。

來看案例:使用JNI讀取暫存器資料

1. Java程式碼

首先,定義一個Java類,該類宣告一個本地方法用於讀取暫存器資料。

public class RegisterReader {
    // 宣告本地方法,該方法將在C/C++程式碼中實現
    public native int readRegister(int address);

    static {
        // 載入本地庫,假設庫名為 "register_reader"
        System.loadLibrary("register_reader");
    }

    public static void main(String[] args) {
        RegisterReader reader = new RegisterReader();
        int registerAddress = 0x1000; // 假設暫存器地址
        int value = reader.readRegister(registerAddress);
        System.out.println("Register Value: " + value);
    }
}

編譯Java檔案:

javac RegisterReader.java

生成C/C++標頭檔案:

javah -jni RegisterReader

此命令將生成一個RegisterReader.h檔案,包含C/C++中需要實現的方法宣告。

2. 生成的標頭檔案(RegisterReader.h

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class RegisterReader */

#ifndef _Included_RegisterReader
#define _Included_RegisterReader
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     RegisterReader
 * Method:    readRegister
 * Signature: (I)I
 */
JNIEXPORT jint JNICALL Java_RegisterReader_readRegister
  (JNIEnv *, jobject, jint);

#ifdef __cplusplus
}
#endif
#endif

3. C程式碼實現

接下來,在C語言中實現readRegister方法。在這裡,我們假設暫存器透過記憶體對映的方式訪問。

#include "RegisterReader.h"
#include <stdio.h>
#include <stdlib.h>

// 模擬暫存器的記憶體對映地址
#define REGISTER_BASE_ADDRESS 0x1000

JNIEXPORT jint JNICALL Java_RegisterReader_readRegister(JNIEnv *env, jobject obj, jint address) {
    // 模擬讀取暫存器,實際實現應訪問真實硬體哈
    int registerValue = (address - REGISTER_BASE_ADDRESS) * 2;  // 虛擬碼,用來模擬一下
    printf("Reading register at address: 0x%x\n", address);
    return registerValue;
}

4. 編譯生成動態庫

在Linux或macOS上,編譯C程式碼並生成動態庫:

gcc -shared -o libregister_reader.so -I${JAVA_HOME}/include -I${JAVA_HOME}/include/linux RegisterReader.c

在Windows上:

gcc -shared -o register_reader.dll -I"%JAVA_HOME%\include" -I"%JAVA_HOME%\include\win32" RegisterReader.c

5. 執行Java程式

確保編譯生成的動態庫位於Java的庫路徑中,然後執行Java程式:

java -Djava.library.path=. RegisterReader

結果

Java程式將呼叫本地C程式碼來讀取暫存器的值,輸出類似如下的結果:

Reading register at address: 0x1000
Register Value: 0

解釋一下

  1. readRegister方法:在Java中呼叫時,會透過JNI呼叫C程式碼中的Java_RegisterReader_readRegister函式。
  2. 動態庫載入System.loadLibrary("register_reader")載入名為register_reader的動態庫,確保C函式可以被Java程式呼叫。

優點

  • 直接訪問硬體:透過JNI可以直接訪問暫存器或其他硬體裝置,而不受JVM的限制。
  • 高效能:C/C++語言可以提供更高效的底層操作。

要注意的事

  • JNI涉及原生程式碼,因此需要注意平臺相容性和安全性問題。
  • 處理JNI時,通常需要了解裝置的驅動介面和通訊機制。

JSerialComm或RXTX等庫

使用 JSerialComm 透過串列埠通訊讀取裝置暫存器資料。
在一些嵌入式或工業裝置中,使用串列埠(如RS232或RS485)進行資料通訊是非常常見的。Java提供了多個庫來實現串列埠通訊,其中JSerialCommRXTX是兩個常用的庫。JSerialComm相對較新且維護良好,相容性更好,因此我們以它為例介紹如何使用它進行串列埠通訊。

實現步驟

  1. 新增 JSerialComm 依賴。
  2. 配置串列埠連線。
  3. 透過串列埠傳送和接收資料。

1. 新增 JSerialComm 依賴

使用Maven管理專案時,可以在pom.xml中新增JSerialComm的依賴:

<dependency>
    <groupId>com.fazecast</groupId>
    <artifactId>jSerialComm</artifactId>
    <version>2.9.2</version> <!-- 根據需要選擇版本 -->
</dependency>

或者,下載 jar 包並將其新增到專案的 classpath 中。

2. 示例程式碼:使用 JSerialComm 讀取暫存器資料

import com.fazecast.jSerialComm.SerialPort;

public class SerialCommExample {
    public static void main(String[] args) {
        // 獲取系統上的所有串列埠裝置
        SerialPort[] ports = SerialPort.getCommPorts();
        System.out.println("可用串列埠裝置列表:");
        for (int i = 0; i < ports.length; i++) {
            System.out.println(i + ": " + ports[i].getSystemPortName());
        }

        // 選擇第一個串列埠裝置並開啟
        SerialPort serialPort = ports[0];
        serialPort.setBaudRate(9600); // 設定波特率
        serialPort.setNumDataBits(8); // 資料位
        serialPort.setNumStopBits(SerialPort.ONE_STOP_BIT); // 停止位
        serialPort.setParity(SerialPort.NO_PARITY); // 校驗位

        if (serialPort.openPort()) {
            System.out.println("串列埠開啟成功: " + serialPort.getSystemPortName());
        } else {
            System.out.println("無法開啟串列埠");
            return;
        }

        // 等待串列埠裝置準備好
        try {
            Thread.sleep(2000); // 等待2秒
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 傳送命令給裝置讀取暫存器
        String command = "READ_REGISTER";  // 根據裝置協議構建讀取命令
        byte[] commandBytes = command.getBytes();
        serialPort.writeBytes(commandBytes, commandBytes.length);

        // 接收裝置響應
        byte[] readBuffer = new byte[1024];
        int numRead = serialPort.readBytes(readBuffer, readBuffer.length);

        System.out.println("讀取到的資料長度: " + numRead);
        System.out.println("資料內容:");
        for (int i = 0; i < numRead; i++) {
            System.out.print((char) readBuffer[i]);
        }

        // 關閉串列埠
        serialPort.closePort();
        System.out.println("\n串列埠關閉");
    }
}

解釋一下程式碼

  1. 獲取可用串列埠裝置

    • SerialPort.getCommPorts() 獲取系統上所有可用的串列埠裝置,並列印其名稱,方便選擇要使用的埠。
  2. 串列埠配置

    • 設定串列埠的波特率資料位停止位校驗位。這些引數必須根據你的裝置文件設定。
    • 例如,波特率設定為9600,資料位為8,停止位為1,無校驗位。
  3. 開啟串列埠

    • serialPort.openPort() 開啟串列埠,如果成功,程式會繼續執行,否則輸出錯誤並終止。
  4. 傳送命令

    • serialPort.writeBytes(commandBytes, commandBytes.length) 向串列埠裝置傳送一個命令,這個命令通常由裝置的通訊協議決定。這裡的命令 READ_REGISTER 是一個假設的示例,實際命令需要根據裝置的手冊來確定。
  5. 讀取響應

    • serialPort.readBytes(readBuffer, readBuffer.length) 從串列埠裝置接收響應資料。接收到的資料儲存在 readBuffer 中,並逐位元組列印出來。
  6. 關閉串列埠

    • 在完成操作後,使用 serialPort.closePort() 關閉串列埠裝置。

執行時結果示例

可用串列埠裝置列表:
0: COM3
串列埠開啟成功: COM3
讀取到的資料長度: 6
資料內容:
123456
串列埠關閉

要注意的事項有哪些

  1. 串列埠通訊協議:串列埠裝置之間的通訊通常遵循某種協議,如Modbus RTU、自定義協議等。你需要根據裝置手冊實現特定的命令傳送和資料解析。

  2. 波特率和其他引數設定:確保波特率、資料位、停止位和校驗位的設定與裝置匹配。錯誤的設定可能導致通訊失敗或資料亂碼。

  3. 錯誤處理:串列埠通訊可能會遇到各種錯誤,如通訊超時、資料幀丟失等。需要根據具體情況進行錯誤處理。

RXTX 示例

RXTX是另一種用於串列埠通訊的庫,但由於維護不如JSerialComm積極,V哥建議使用JSerialComm。如果你還是要使用RXTX咋辦?那 V 哥只能...上案例了,一個簡單的串列埠通訊示例:

import gnu.io.CommPortIdentifier;
import gnu.io.SerialPort;
import java.io.InputStream;
import java.io.OutputStream;

public class RXTXExample {
    public static void main(String[] args) throws Exception {
        // 獲取串列埠
        CommPortIdentifier portIdentifier = CommPortIdentifier.getPortIdentifier("COM3");
        SerialPort serialPort = (SerialPort) portIdentifier.open("SerialComm", 2000);

        // 設定串列埠引數
        serialPort.setSerialPortParams(9600, SerialPort.DATABITS_8, SerialPort.STOPBITS_1, SerialPort.PARITY_NONE);

        // 獲取輸入輸出流
        InputStream inputStream = serialPort.getInputStream();
        OutputStream outputStream = serialPort.getOutputStream();

        // 傳送命令
        String command = "READ_REGISTER";
        outputStream.write(command.getBytes());

        // 接收響應
        byte[] buffer = new byte[1024];
        int length = inputStream.read(buffer);
        System.out.println("讀取到的資料長度: " + length);

        // 列印接收到的資料
        for (int i = 0; i < length; i++) {
            System.out.print((char) buffer[i]);
        }

        // 關閉串列埠
        serialPort.close();
    }
}

小結一下

透過 JSerialComm 庫,咱們可以方便地在 Java 中實現串列埠通訊。這個庫簡化了串列埠的配置和操作,且跨平臺相容性好,非常適合需要與硬體裝置透過串列埠通訊的專案。如果你在工業裝置、嵌入式系統或物聯網應用中需要使用串列埠通訊,它是一個很好的選擇。

總結一下三種方式的使用場景

以下是使用 JNIModbus協議串列埠通訊庫(JSerialComm或RXTX) 三種方式的場景總結:

1. JNI(Java Native Interface)

  • 場景

    • 當你需要直接訪問硬體層或底層系統API時,使用JNI非常合適。
    • 適用於 Java 無法直接處理的硬體操作,例如與裝置的暫存器、記憶體對映、驅動程式直接互動。
    • 適合需要極高效能或需要使用C/C++庫與裝置進行復雜通訊的場景。
    • 如果裝置的驅動程式只提供C/C++ API,你可以透過JNI將其整合到Java專案中。
  • 典型應用

    • 裝置驅動程式的開發和使用。
    • 高效能、低延遲的硬體通訊。
    • 作業系統特定的API呼叫,訪問系統資源(例如暫存器、硬體介面)。
  • 優缺點

    • 優點:允許Java與本地系統程式碼通訊;適合複雜的硬體控制。
    • 缺點:開發複雜,涉及C/C++程式碼,增加了跨平臺複雜性。

2. Modbus協議

  • 場景

    • Modbus協議是用於工業裝置之間通訊的常見標準,適用於透過RS485/RS232串列埠乙太網TCP與支援Modbus協議的裝置進行通訊。
    • 主要用於自動化控制系統,如PLC、感測器、變頻器、HMI等工業裝置的資料交換。
    • 適合需要透過標準工業協議與多個裝置進行監控和資料採集的場景。
  • 典型應用

    • 工業自動化:讀取裝置狀態、控制輸出、獲取感測器資料。
    • 物聯網裝置的監控和管理。
    • 遠端控制和裝置管理:如透過Modbus TCP讀取遠端裝置資料。
  • 優缺點

    • 優點:標準化協議,相容大量工業裝置,簡單易用。
    • 缺點:相對較慢的通訊速率,適用於監控和控制而非實時複雜計算。

3. 串列埠通訊庫(JSerialComm或RXTX)

  • 場景

    • 當裝置透過串列埠(RS232、RS485)進行通訊時,可以使用串列埠通訊庫直接讀取裝置資料。
    • 適合與工業裝置、嵌入式系統、感測器、儀表等需要基於串列埠進行通訊的場景。
    • 如果裝置使用的是自定義協議,且不支援標準的Modbus協議,可以透過這種方式實現與裝置的通訊。
    • 適合需要簡單、直接的裝置通訊,尤其是在傳統的嵌入式裝置和工業場景中。
  • 典型應用

    • 透過串列埠與嵌入式裝置通訊,獲取感測器資料。
    • 與PLC、工業控制系統、微控制器等裝置進行通訊。
    • 物聯網裝置資料傳輸,尤其是需要透過串列埠傳輸的低速裝置。
  • 優缺點

    • 優點:輕量、跨平臺支援廣泛、配置簡單,適合與串列埠裝置進行直接通訊。
    • 缺點:僅適用於串列埠通訊,缺乏複雜的協議支援。

總結對比:

  • JNI 適用於底層硬體訪問高效能應用,如與作業系統或驅動程式直接互動。
  • Modbus協議工業標準協議,適用於需要透過串列埠或乙太網與工業裝置通訊的場景。
  • JSerialComm/RXTX 適用於與串列埠裝置通訊,尤其是在嵌入式物聯網裝置中進行簡單的裝置互動。

選擇哪種方式取決於裝置的通訊協議和專案的複雜性需求,如果是標準工業裝置,Modbus協議 是首選。如果是自定義裝置或嵌入式裝置,使用 JSerialCommRXTX。如果需要高效底層硬體訪問:JNI 可能是唯一選擇。好了,今天的內容就到這裡,歡迎關注威哥愛程式設計,點贊關注加收藏,讓我們一起在 Java 路上越走越遠。

相關文章