写点什么

三大硬核方式揭秘:Java 如何与底层硬件和工业设备轻松通信!

  • 2024-09-27
    福建
  • 本文字数:7679 字

    阅读完需:约 25 分钟

今天的内容来聊一聊 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 个寄存器。

  • 输出寄存器值。


3、错误处理

  • 捕获 ModbusProtocolExceptionModbusNumberException 和 ModbusIOException 以处理通信过程中可能出现的错误。


4、断开连接

  • 使用 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 __cplusplusextern "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: 0x1000Register 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咋办?那只能...上案例了,一个简单的串口通信示例:


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 协议 是首选。如果是自定义设备或嵌入式设备,使用 JSerialComm 或 RXTX。如果需要高效底层硬件访问:JNI 可能是唯一选择。好了,今天的内容就到这里,欢迎关注!


文章转载自:威哥爱编程

原文链接:https://www.cnblogs.com/wgjava/p/18433152

体验地址:http://www.jnpfsoft.com/?from=infoq

用户头像

还未添加个人签名 2023-06-19 加入

还未添加个人简介

评论

发布
暂无评论
三大硬核方式揭秘:Java如何与底层硬件和工业设备轻松通信!_Java_不在线第一只蜗牛_InfoQ写作社区