大家好,我是V哥,程序員聊天真是三句不到離不開技術(shù)啊,這不前兩天跟一個(gè)哥們吃飯,他是我好多年前的學(xué)員了,一直保持著聯(lián)系,現(xiàn)在都李總了,在做工業(yè)互聯(lián)網(wǎng)相關(guān)的項(xiàng)目,真是只要 Java 學(xué)得好,能干一輩子,卷死的是那些半吊子。
感謝李總給我分享了工業(yè)互聯(lián)網(wǎng)項(xiàng)目的事情,收獲很多,今天的內(nèi)容來(lái)聊一聊 Java如何與底層硬件和工業(yè)設(shè)備輕松通信的事情。
Java讀取寄存器數(shù)據(jù)通常涉及與硬件設(shè)備的通信。這種操作通常是通過以下幾種方式來(lái)實(shí)現(xiàn)的:
jLibModbus
)
Modbus 是一種用于工業(yè)自動(dòng)化設(shè)備的通信協(xié)議。常見的Modbus通信方式包括:Modbus RTU(基于串行通信)和Modbus TCP(基于網(wǎng)絡(luò)通信)。在此示例中,我們將使用 Java 和 jLibModbus
庫(kù)通過 Modbus TCP 協(xié)議讀取設(shè)備的寄存器數(shù)據(jù)。
使用 Maven 管理項(xiàng)目時(shí),可以在 pom.xml
中添加 jLibModbus
依賴:
<dependency>
<groupId>com.intelligt.modbus</groupId>
<artifactId>jlibmodbus</artifactId>
<version>1.2.8.1</version> <!-- 根據(jù)具體需求設(shè)置版本 -->
</dependency>
或者直接下載 jar 包并將其添加到項(xiàng)目的 classpath 中。
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 參數(shù)
TcpParameters tcpParameters = new TcpParameters();
InetAddress address = InetAddress.getByName("192.168.1.100"); // 設(shè)備的IP地址
tcpParameters.setHost(address);
tcpParameters.setPort(Modbus.TCP_PORT); // Modbus 默認(rèn)TCP端口 502
// 創(chuàng)建 ModbusMaster 實(shí)例
ModbusMaster master = ModbusMasterFactory.createModbusMasterTCP(tcpParameters);
master.connect();
// 設(shè)備的 Slave ID(通常是從站地址)
int slaveId = 1;
// 讀取保持寄存器(Holding Registers),從地址 0 開始,讀取 10 個(gè)寄存器
int startAddress = 0;
int quantity = 10;
try {
// 讀取保持寄存器數(shù)據(jù)
int[] registerValues = master.readHoldingRegisters(slaveId, startAddress, quantity);
System.out.println("寄存器數(shù)據(jù):");
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();
}
}
}
TcpParameters
用于配置 Modbus TCP 的主機(jī)和端口。默認(rèn)的Modbus TCP端口是 502
。InetAddress.getByName("192.168.1.100")
設(shè)置設(shè)備的IP地址。ModbusMaster master = ModbusMasterFactory.createModbusMasterTCP(tcpParameters);
創(chuàng)建一個(gè)Modbus主機(jī)(Master),用于與從設(shè)備通信。master.connect();
連接到Modbus設(shè)備。int[] registerValues = master.readHoldingRegisters(slaveId, startAddress, quantity);
讀取從設(shè)備的保持寄存器(Holding Registers),從 startAddress
開始,讀取 quantity
個(gè)寄存器。ModbusProtocolException
、ModbusNumberException
和 ModbusIOException
以處理通信過程中可能出現(xiàn)的錯(cuò)誤。master.disconnect();
在完成數(shù)據(jù)讀取后斷開與Modbus設(shè)備的連接。寄存器數(shù)據(jù):
寄存器[0] = 1234
寄存器[1] = 5678
寄存器[2] = 910
...
readInputRegisters(...)
:讀取輸入寄存器。readCoils(...)
:讀取線圈狀態(tài)。我們通過 jLibModbus
庫(kù)使用 Java 讀取支持 Modbus TCP 協(xié)議的設(shè)備的寄存器數(shù)據(jù)。Modbus是工業(yè)控制領(lǐng)域中廣泛應(yīng)用的通信協(xié)議,利用Java實(shí)現(xiàn)設(shè)備通信可以用于各種自動(dòng)化系統(tǒng)中。如果你的設(shè)備使用Modbus RTU協(xié)議,可以通過配置串口通信來(lái)實(shí)現(xiàn)類似的操作。
Java Native Interface (JNI) 允許Java代碼與C/C++等本地語(yǔ)言編寫的代碼交互,可以用于實(shí)現(xiàn)高性能、直接的硬件訪問,如寄存器讀取。
javac
編譯Java類。javah
生成C/C++頭文件。首先,定義一個(gè)Java類,該類聲明一個(gè)本地方法用于讀取寄存器數(shù)據(jù)。
public class RegisterReader {
// 聲明本地方法,該方法將在C/C++代碼中實(shí)現(xiàn)
public native int readRegister(int address);
static {
// 加載本地庫(kù),假設(shè)庫(kù)名為 "register_reader"
System.loadLibrary("register_reader");
}
public static void main(String[] args) {
RegisterReader reader = new RegisterReader();
int registerAddress = 0x1000; // 假設(shè)寄存器地址
int value = reader.readRegister(registerAddress);
System.out.println("Register Value: " + value);
}
}
編譯Java文件:
javac RegisterReader.java
生成C/C++頭文件:
javah -jni RegisterReader
此命令將生成一個(gè)RegisterReader.h
文件,包含C/C++中需要實(shí)現(xiàn)的方法聲明。
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
接下來(lái),在C語(yǔ)言中實(shí)現(xiàn)readRegister
方法。在這里,我們假設(shè)寄存器通過內(nèi)存映射的方式訪問。
#include "RegisterReader.h"
#include <stdio.h>
#include <stdlib.h>
// 模擬寄存器的內(nèi)存映射地址
#define REGISTER_BASE_ADDRESS 0x1000
JNIEXPORT jint JNICALL Java_RegisterReader_readRegister(JNIEnv *env, jobject obj, jint address) {
// 模擬讀取寄存器,實(shí)際實(shí)現(xiàn)應(yīng)訪問真實(shí)硬件哈
int registerValue = (address - REGISTER_BASE_ADDRESS) * 2; // 偽代碼,用來(lái)模擬一下
printf("Reading register at address: 0x%x\n", address);
return registerValue;
}
在Linux或macOS上,編譯C代碼并生成動(dòng)態(tài)庫(kù):
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
確保編譯生成的動(dòng)態(tài)庫(kù)位于Java的庫(kù)路徑中,然后運(yùn)行Java程序:
java -Djava.library.path=. RegisterReader
Java程序?qū)⒄{(diào)用本地C代碼來(lái)讀取寄存器的值,輸出類似如下的結(jié)果:
Reading register at address: 0x1000
Register Value: 0
readRegister
方法:在Java中調(diào)用時(shí),會(huì)通過JNI調(diào)用C代碼中的Java_RegisterReader_readRegister
函數(shù)。System.loadLibrary("register_reader")
加載名為register_reader
的動(dòng)態(tài)庫(kù),確保C函數(shù)可以被Java程序調(diào)用。
使用 JSerialComm
通過串口通信讀取設(shè)備寄存器數(shù)據(jù)。
在一些嵌入式或工業(yè)設(shè)備中,使用串口(如RS232或RS485)進(jìn)行數(shù)據(jù)通信是非常常見的。Java提供了多個(gè)庫(kù)來(lái)實(shí)現(xiàn)串口通信,其中JSerialComm
和RXTX
是兩個(gè)常用的庫(kù)。JSerialComm
相對(duì)較新且維護(hù)良好,兼容性更好,因此我們以它為例介紹如何使用它進(jìn)行串口通信。
JSerialComm
依賴。JSerialComm
依賴
使用Maven管理項(xiàng)目時(shí),可以在pom.xml
中添加JSerialComm
的依賴:
<dependency>
<groupId>com.fazecast</groupId>
<artifactId>jSerialComm</artifactId>
<version>2.9.2</version> <!-- 根據(jù)需要選擇版本 -->
</dependency>
或者,下載 jar 包并將其添加到項(xiàng)目的 classpath 中。
JSerialComm
讀取寄存器數(shù)據(jù)import com.fazecast.jSerialComm.SerialPort;
public class SerialCommExample {
public static void main(String[] args) {
// 獲取系統(tǒng)上的所有串口設(shè)備
SerialPort[] ports = SerialPort.getCommPorts();
System.out.println("可用串口設(shè)備列表:");
for (int i = 0; i < ports.length; i++) {
System.out.println(i + ": " + ports[i].getSystemPortName());
}
// 選擇第一個(gè)串口設(shè)備并打開
SerialPort serialPort = ports[0];
serialPort.setBaudRate(9600); // 設(shè)置波特率
serialPort.setNumDataBits(8); // 數(shù)據(jù)位
serialPort.setNumStopBits(SerialPort.ONE_STOP_BIT); // 停止位
serialPort.setParity(SerialPort.NO_PARITY); // 校驗(yàn)位
if (serialPort.openPort()) {
System.out.println("串口打開成功: " + serialPort.getSystemPortName());
} else {
System.out.println("無(wú)法打開串口");
return;
}
// 等待串口設(shè)備準(zhǔn)備好
try {
Thread.sleep(2000); // 等待2秒
} catch (InterruptedException e) {
e.printStackTrace();
}
// 發(fā)送命令給設(shè)備讀取寄存器
String command = "READ_REGISTER"; // 根據(jù)設(shè)備協(xié)議構(gòu)建讀取命令
byte[] commandBytes = command.getBytes();
serialPort.writeBytes(commandBytes, commandBytes.length);
// 接收設(shè)備響應(yīng)
byte[] readBuffer = new byte[1024];
int numRead = serialPort.readBytes(readBuffer, readBuffer.length);
System.out.println("讀取到的數(shù)據(jù)長(zhǎng)度: " + numRead);
System.out.println("數(shù)據(jù)內(nèi)容:");
for (int i = 0; i < numRead; i++) {
System.out.print((char) readBuffer[i]);
}
// 關(guān)閉串口
serialPort.closePort();
System.out.println("\n串口關(guān)閉");
}
}
獲取可用串口設(shè)備:
SerialPort.getCommPorts()
獲取系統(tǒng)上所有可用的串口設(shè)備,并打印其名稱,方便選擇要使用的端口。serialPort.openPort()
打開串口,如果成功,程序會(huì)繼續(xù)執(zhí)行,否則輸出錯(cuò)誤并終止。serialPort.writeBytes(commandBytes, commandBytes.length)
向串口設(shè)備發(fā)送一個(gè)命令,這個(gè)命令通常由設(shè)備的通信協(xié)議決定。這里的命令 READ_REGISTER
是一個(gè)假設(shè)的示例,實(shí)際命令需要根據(jù)設(shè)備的手冊(cè)來(lái)確定。serialPort.readBytes(readBuffer, readBuffer.length)
從串口設(shè)備接收響應(yīng)數(shù)據(jù)。接收到的數(shù)據(jù)存儲(chǔ)在 readBuffer
中,并逐字節(jié)打印出來(lái)。serialPort.closePort()
關(guān)閉串口設(shè)備。可用串口設(shè)備列表:
0: COM3
串口打開成功: COM3
讀取到的數(shù)據(jù)長(zhǎng)度: 6
數(shù)據(jù)內(nèi)容:
123456
串口關(guān)閉
串口通信協(xié)議:串口設(shè)備之間的通信通常遵循某種協(xié)議,如Modbus RTU、自定義協(xié)議等。你需要根據(jù)設(shè)備手冊(cè)實(shí)現(xiàn)特定的命令發(fā)送和數(shù)據(jù)解析。
RXTX
是另一種用于串口通信的庫(kù),但由于維護(hù)不如JSerialComm
積極,V哥建議使用JSerialComm
。如果你還是要使用RXTX
咋辦?那 V 哥只能...上案例了,一個(gè)簡(jiǎn)單的串口通信示例:
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);
// 設(shè)置串口參數(shù)
serialPort.setSerialPortParams(9600, SerialPort.DATABITS_8, SerialPort.STOPBITS_1, SerialPort.PARITY_NONE);
// 獲取輸入輸出流
InputStream inputStream = serialPort.getInputStream();
OutputStream outputStream = serialPort.getOutputStream();
// 發(fā)送命令
String command = "READ_REGISTER";
outputStream.write(command.getBytes());
// 接收響應(yīng)
byte[] buffer = new byte[1024];
int length = inputStream.read(buffer);
System.out.println("讀取到的數(shù)據(jù)長(zhǎng)度: " + length);
// 打印接收到的數(shù)據(jù)
for (int i = 0; i < length; i++) {
System.out.print((char) buffer[i]);
}
// 關(guān)閉串口
serialPort.close();
}
}
通過 JSerialComm
庫(kù),咱們可以方便地在 Java 中實(shí)現(xiàn)串口通信。這個(gè)庫(kù)簡(jiǎn)化了串口的配置和操作,且跨平臺(tái)兼容性好,非常適合需要與硬件設(shè)備通過串口通信的項(xiàng)目。如果你在工業(yè)設(shè)備、嵌入式系統(tǒng)或物聯(lián)網(wǎng)應(yīng)用中需要使用串口通信,它是一個(gè)很好的選擇。
以下是使用 JNI、Modbus協(xié)議 和 串口通信庫(kù)(JSerialComm或RXTX) 三種方式的場(chǎng)景總結(jié):
選擇哪種方式取決于設(shè)備的通信協(xié)議和項(xiàng)目的復(fù)雜性需求,如果是標(biāo)準(zhǔn)工業(yè)設(shè)備,Modbus協(xié)議 是首選。如果是自定義設(shè)備或嵌入式設(shè)備,使用 JSerialComm 或 RXTX。如果需要高效底層硬件訪問:JNI 可能是唯一選擇。好了,今天的內(nèi)容就到這里,歡迎關(guān)注威哥愛編程,點(diǎn)贊關(guān)注加收藏,讓我們一起在 Java 路上越走越遠(yuǎn)。Java與硬件通信:Modbus、JNI和串口庫(kù)實(shí)戰(zhàn)應(yīng)用Java與硬件通信:Modbus、JNI和串口庫(kù)實(shí)戰(zhàn)應(yīng)用
更多建議: