三大硬核方式揭秘:Java如何与底层硬件和工业设备轻松通信!
大家好,我是V哥,程序员聊天真是三句不到离不开技术啊,这不前两天跟一个哥们吃饭,他是我好多年前的学员了,一直保持着联系,现在都李总了,在做工业互联网相关的项目,真是只要 Java 学得好,能干一辈子,卷死的是那些半吊子。
感谢李总给我分享了工业互联网项目的事情,收获很多,今天的内容来聊一聊 Java如何与底层硬件和工业设备轻松通信的事情。
Java读取寄存器数据通常涉及与硬件设备的通信。这种操作通常是通过以下几种方式来实现的:
使用 Modbus 协议读取设备寄存器数据(使用 jLibModbus
)
Modbus 是一种用于工业自动化设备的通信协议。常见的Modbus通信方式包括:Modbus RTU(基于串行通信)和Modbus TCP(基于网络通信)。在此示例中,我们将使用 Java 和 jLibModbus
库通过 Modbus TCP 协议读取设备的寄存器数据。
实现步骤
- 添加 jLibModbus 依赖。
- 设置 Modbus Master 客户端。
- 通过 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();
}
}
}
来解释一下代码哈
Modbus TCP 参数:
TcpParameters
用于配置 Modbus TCP 的主机和端口。默认的Modbus TCP端口是502
。- 使用
InetAddress.getByName("192.168.1.100")
设置设备的IP地址。
Modbus Master 实例:
ModbusMaster master = ModbusMasterFactory.createModbusMasterTCP(tcpParameters);
创建一个Modbus主机(Master),用于与从设备通信。master.connect();
连接到Modbus设备。
读取保持寄存器:
int[] registerValues = master.readHoldingRegisters(slaveId, startAddress, quantity);
读取从设备的保持寄存器(Holding Registers),从startAddress
开始,读取quantity
个寄存器。- 输出寄存器值。
错误处理:
- 捕获
ModbusProtocolException
、ModbusNumberException
和ModbusIOException
以处理通信过程中可能出现的错误。
- 捕获
断开连接:
- 使用
master.disconnect();
在完成数据读取后断开与Modbus设备的连接。
- 使用
3. 如何使用
- 设备配置:确保你有一个支持 Modbus TCP 的设备,并且设备的IP地址与端口正确配置。通常情况下,Modbus TCP设备监听502端口。
- 运行程序:运行此 Java 程序,程序将连接到设备并读取指定寄存器的数据。
- 结果示例:
寄存器数据:
寄存器[0] = 1234
寄存器[1] = 5678
寄存器[2] = 910
...
使用时要注意的事项
- 设备通信参数:确保设备支持Modbus TCP协议,并正确配置了IP地址、端口和从站地址(Slave ID)。
- 读取不同类型的寄存器:在Modbus中,可以读取不同类型的寄存器,比如输入寄存器(Input Registers)或线圈(Coils)。根据需求调用对应的方法:
readInputRegisters(...)
:读取输入寄存器。readCoils(...)
:读取线圈状态。
- 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基本流程
- 在Java中声明本地方法。
- 使用
javac
编译Java类。 - 使用
javah
生成C/C++头文件。 - 编写C/C++代码实现Java方法。
- 编译生成动态库。
- 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
解释一下
readRegister
方法:在Java中调用时,会通过JNI调用C代码中的Java_RegisterReader_readRegister
函数。- 动态库加载:
System.loadLibrary("register_reader")
加载名为register_reader
的动态库,确保C函数可以被Java程序调用。
优点
- 直接访问硬件:通过JNI可以直接访问寄存器或其他硬件设备,而不受JVM的限制。
- 高性能:C/C++语言可以提供更高效的底层操作。
要注意的事
- JNI涉及原生代码,因此需要注意平台兼容性和安全性问题。
- 处理JNI时,通常需要了解设备的驱动接口和通信机制。
JSerialComm或RXTX等库
使用 JSerialComm
通过串口通信读取设备寄存器数据。
在一些嵌入式或工业设备中,使用串口(如RS232或RS485)进行数据通信是非常常见的。Java提供了多个库来实现串口通信,其中JSerialComm
和RXTX
是两个常用的库。JSerialComm
相对较新且维护良好,兼容性更好,因此我们以它为例介绍如何使用它进行串口通信。
实现步骤
- 添加
JSerialComm
依赖。 - 配置串口连接。
- 通过串口发送和接收数据。
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串口关闭");
}
}
解释一下代码
获取可用串口设备:
SerialPort.getCommPorts()
获取系统上所有可用的串口设备,并打印其名称,方便选择要使用的端口。
串口配置:
- 设置串口的波特率、数据位、停止位和校验位。这些参数必须根据你的设备文档设置。
- 例如,波特率设置为9600,数据位为8,停止位为1,无校验位。
打开串口:
serialPort.openPort()
打开串口,如果成功,程序会继续执行,否则输出错误并终止。
发送命令:
serialPort.writeBytes(commandBytes, commandBytes.length)
向串口设备发送一个命令,这个命令通常由设备的通信协议决定。这里的命令READ_REGISTER
是一个假设的示例,实际命令需要根据设备的手册来确定。
读取响应:
serialPort.readBytes(readBuffer, readBuffer.length)
从串口设备接收响应数据。接收到的数据存储在readBuffer
中,并逐字节打印出来。
关闭串口:
- 在完成操作后,使用
serialPort.closePort()
关闭串口设备。
- 在完成操作后,使用
运行时结果示例
可用串口设备列表:
0: COM3
串口打开成功: COM3
读取到的数据长度: 6
数据内容:
123456
串口关闭
要注意的事项有哪些
串口通信协议:串口设备之间的通信通常遵循某种协议,如Modbus RTU、自定义协议等。你需要根据设备手册实现特定的命令发送和数据解析。
波特率和其他参数设置:确保波特率、数据位、停止位和校验位的设置与设备匹配。错误的设置可能导致通信失败或数据乱码。
错误处理:串口通信可能会遇到各种错误,如通信超时、数据帧丢失等。需要根据具体情况进行错误处理。
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 中实现串口通信。这个库简化了串口的配置和操作,且跨平台兼容性好,非常适合需要与硬件设备通过串口通信的项目。如果你在工业设备、嵌入式系统或物联网应用中需要使用串口通信,它是一个很好的选择。
总结一下三种方式的使用场景
以下是使用 JNI、Modbus协议 和 串口通信库(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 可能是唯一选择。好了,今天的内容就到这里,欢迎关注威哥爱编程,点赞关注加收藏,让我们一起在 Java 路上越走越远。
三大硬核方式揭秘:Java如何与底层硬件和工业设备轻松通信!的更多相关文章
- 阿里P7整理“硬核”面试文档:Java基础+数据库+算法+框架技术等
现在的程序员越来越多,大部分的程序员都想着自己能够进入大厂工作,但每个人的能力都是有差距的,所以并不是人人都能跨进BATJ.即使如此,但身在职场的我们一刻也不能懈怠,既然对BATJ好奇,那么就要朝这个 ...
- 硬核万字长文,深入理解 Java 字节码指令(建议收藏)
Java 字节码指令是 JVM 体系中非常难啃的一块硬骨头,我估计有些读者会有这样的疑惑,"Java 字节码难学吗?我能不能学会啊?" 讲良心话,不是我谦虚,一开始学 Java 字 ...
- 袋鼠云研发手记 | 数栈·开源:Github上400+Star的硬核分布式同步工具FlinkX
作为一家创新驱动的科技公司,袋鼠云每年研发投入达数千万,公司80%员工都是技术人员,袋鼠云产品家族包括企业级一站式数据中台PaaS数栈.交互式数据可视化大屏开发平台Easy[V]等产品也在迅速迭代.在 ...
- 【开源我写的富文本】打造全网最劲富文本技术选型之经典OOP仍是魅力硬核。
套路--先贴图 demo : http://www.vvui.net/editor/index.html gitee : https://gitee.com/kevin-huang/Bui-Edit ...
- Colder框架硬核更新(Sharding+IOC)
目录 引言 控制反转 读写分离分库分表 理论基础 设计目标 现状调研 设计思路 实现之过五关斩六将 动态对象 动态模型缓存 数据源移植 查询表达式树深度移植 数据合并算法 事务支持 实际使用 展望未来 ...
- 程序员需要了解的硬核知识之CPU
大家都是程序员,大家都是和计算机打交道的程序员,大家都是和计算机中软件硬件打交道的程序员,大家都是和CPU打交道的程序员,所以,不管你是玩儿硬件的还是做软件的,你的世界都少不了计算机最核心的 - CP ...
- 硬核讲解 Jetpack 之 LifeCycle 源码篇
前一篇 硬核讲解 Jetpack 之 LifeCycle 使用篇 主要介绍了 LifeCycle 存在的意义,基本和进阶的使用方法.今天话不多说,直接开始撸源码. 本文基于我手里的 android_9 ...
- 全网最硬核 JVM TLAB 分析(单篇版不包含额外加菜)
今天,又是干货满满的一天.这是全网最硬核 JVM 系列的开篇,首先从 TLAB 开始.由于文章很长,每个人阅读习惯不同,所以特此拆成单篇版和多篇版 全网最硬核 JVM TLAB 分析(单篇版不包含额外 ...
- Mybatis系列全解(六):Mybatis最硬核的API你知道几个?
封面:洛小汐 作者:潘潘 2020 年的大疫情,把世界撕成几片. 时至今日,依旧人心惶惶. 很庆幸,身处这安稳国, 兼得一份安稳工. · 东家常讲的一个词:深秋心态 . 大势时,不跟风.起哄, 萧条时 ...
- 新上线!3D单模型轻量化硬核升级,G级数据轻松拿捏!
"3D模型体量过大.面数过多.传输展示困难",用户面对这样的3D数据,一定不由得皱起眉头.更便捷.快速处理三维数据,是每个3D用户对高效工作的向往. 在老子云最新上线的单模型轻量化 ...
随机推荐
- 一文全懂:独立冗余磁盘阵列(RAID)
独立冗余磁盘阵列,也就是大家常说的RAID,英文全称是:Redundant Array of Independent Disks,使用该技术,可以大幅提高硬盘设备的 IO 读写速度,还存在数种数据冗余 ...
- ABC357
A link 循环加每一个数,加到哪个数不能加了输出前一个数,注意如果加到最后还能加,记得输出\(n\). 点击查看代码 #include<bits/stdc++.h> using nam ...
- java引入es使用
引入依赖 <dependency> <groupId>org.elasticsearch.client</groupId> <artifactId>el ...
- 【H5】12 表单 其一 第一个表单
本系列的第一篇文章提供了您第一次创建HTML表单的经验, 包括设计一个简单表单,使用正确的HTML元素实现它, 通过CSS添加一些非常简单的样式,以及如何将数据发送到服务器. HTML表单是什么? H ...
- 【转载】 NetworkManager——nmcli命令连接WIFI和创建热点
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明.本文链接:https://blog.csdn.net/u014695839/article/de ...
- mujoco安装报错:mujoco_py/cymj.pyx:67:5: Exception check on 'c_warning_callback' will always require the GIL to be acquired.
参考: https://blog.csdn.net/weixin_49373427/article/details/131981583 https://blog.csdn.net/CCCDeric/a ...
- 【转载】 Ubuntu 中开机自动执行脚本的两种方法
原文地址: https://www.jianshu.com/p/6366d7070642 作者:貘鸣来源:简书 ============================================ ...
- pyglet.gl.ContextException: Could not create GL context
参考: https://www.saoniuhuo.com/question/detail-2725960.html ========================================= ...
- csv或excel文件通过plsql导入到oracle数据库中
1.背景 实际开发中经常遇到将数据直接导入到数据库中,操作如下 2.操作 第一步: 第二步:选择要导入的csv文件 第三步:选择数据库表字段与csv的列对应,然后点击导入,完成 完美!
- 实习记录day03:尝试写一个接口
前言:今天突然意识到,实习记录很少有技术性的东西,更多的是自己的心里活动和一些感想,其实这类博客更趋向于日记而非技术记录.也许哪天不再充满兴趣了,这个实习记录也就结束了(想下班了同志们) 实习第三天: ...