Java 串口通信(RS232/485)
Java 串口通信(RS232/485)
- 一.串口通信页面
- 二.串口服务实现
- 1.Java 串口通信配置
- 1.扩展包和依赖库
- 2.Pom配置
- 2.启动类
- 3.工具包类
- 1.Common
- 2.Crc16Modbus
- 3.SerialUtil
- 4.WebSocket 配置
- 1.启动配置
- 2.监听配置
- 5.UI交互类
- 1.串口配置对象
- 2.串口信息获取接口
- 3.RS232接口
- 4.RS485接口
- 6.串口配置类
- 1.串口配置
- 2.RS232串口配置
- 3.RS232串口监听
- 4.RS485串口配置
- 5.RS485串口监听
- 三.UI代码
- 四.测试效果
- 1.串口通信
- 2.CRC16通信
一.串口通信页面
Java 实现串口通信,同时通过 WebSocket 与 UI 实时交互传递通信数据
准备工作:
虚拟串口工具:Launch Virtual Serial Port Driver
串口调试助手:SSCOM
RS485
根据 Modbus 协议,常规485通讯的信息发送形式如下:
地址 功能码 数据信息 校验码
1byte 1byte nbyte 2byte
在线 CRC检验码计算:CRC 测试链接
二.串口服务实现
1.Java 串口通信配置
1.扩展包和依赖库
RXTXcomm.jar 放入 {JAVA_HOME}/jre/lib/ext
rxtxserial.dll 放入 {JAVA_HOME}/jre/bin
以上两个包可以直接网上下载,注意和JDK版本搭配即可
2.Pom配置
串口通信包:rxtx
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>org.example</groupId><artifactId>SerialPort</artifactId><version>1.0-SNAPSHOT</version><properties><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId><version>2.7.4</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.24</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-websocket</artifactId><version>5.3.27</version></dependency><dependency><groupId>org.rxtx</groupId><artifactId>rxtx</artifactId><version>2.1.7</version></dependency></dependencies><repositories><repository><id>nexus-aliyun</id><name>nexus-aliyun</name><url>http://maven.aliyun.com/nexus/content/groups/public/</url><releases><enabled>true</enabled></releases><snapshots><enabled>false</enabled></snapshots></repository></repositories><pluginRepositories><pluginRepository><id>public</id><name>aliyun nexus</name><url>http://maven.aliyun.com/nexus/content/groups/public/</url><releases><enabled>true</enabled></releases><snapshots><enabled>false</enabled></snapshots></pluginRepository></pluginRepositories></project>
2.启动类
package com.serial.demo;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;/*** @author* @date 2023-07-01 12:41* @since 1.8*/
@SpringBootApplication
public class SerialApplication {public static void main(String[] args) {SpringApplication.run(SerialApplication.class,args);}}
3.工具包类
1.Common
package com.serial.demo.util;/*** @author* @date 2023-07-03 22:17* @since 1.8*/
public class Common {public static String HEX_STRING = "0123456789ABCDEF";public static final String NONE = "无";public static final String ODD = "奇";public static final String EVEN = "偶";public static final String FORMAT_HEX="HEX";
}
2.Crc16Modbus
CRC16 Modbus Java 实现:计算数据的校验码
package com.serial.demo.util;/*** @author* @date 2023-07-04 20:37* @since 1.8*/
public class Crc16Modbus {/*** CRC 循环冗余校验 即通过生成多项式对原始数据进行计算,将计算结果拼接到数据上一起发送* 接收方计算接收到的数据校验接收结果是否准确* CRC 即对生成多项式的模二运算** 1.预置1个16位的寄存器为十六进制 FFFF(即全为1),称此寄存器为CRC寄存器* 2.把第1个8位二进制数据(帧头字节)与 CRC 寄存器的低8位相异或并写回寄存器 高8位数据不变* 3.把 CRC 循环右移 高位补 0 取得移出位* 4.如果移出位为 0 继续右移 如果移出位为 1 则 CRC 寄存器与多项式 A001(1010 0000 0000 0001)进行异或运算* 5.重复步骤 3 和 4 直到右移 8 次* 6.重复步骤 2 到 5 进行数据帧下一个字节的处理 直到将数据帧所有字节按上述步骤计算* 7.根据需要将寄存器的高、低字节进行交换 得到最终 CRC码**//*** 初始值 CRC-16 寄存器*/private static final int INITIAL_VALUE = 0xFFFF;private static final boolean IS_OUT_PUT_OVER_TURN = true;/*** 原始数据 + CRC码** @param hexes 16 进制字符串* @return*/public static byte[] getData(String... hexes) {byte[] data = new byte[hexes.length];int i = 0;for (String hex:hexes){//先转为数字在转为 bytedata[i++] = (byte) Integer.parseInt(hex, 16);}return merge(data);}/*** 原始数据 + CRC码** @param data* @return*/public static byte[] merge(byte[] data) {byte[] crc = getCrc16(data);int dLen = data.length;int cLen = crc.length;byte[] result = new byte[dLen + cLen];System.arraycopy(data,0,result,0,dLen);System.arraycopy(crc,0,result,dLen,cLen);return result;}/*** 基于 CRC16 Modbus 计算校验码* CRC 16 Modbus 默认多项式为 x16+x15+x2+1 => 8005 反转即 A001** @param data* @return*/private static byte[] getCrc16(byte[] data) {int len = data.length;int crc = INITIAL_VALUE;int i, j;for (i = 0; i < len; i++) {// 把第一个 8 位二进制数据 与 16 位的 CRC寄存器的低 8 位相异或, 把结果放于 CRC寄存器crc = ((crc & 0xFF00) | (crc & 0x00FF) ^ (data[i] & 0xFF));for (j = 0; j < 8; j++) {// 把 CRC 寄存器的内容右移一位(朝低位)用 0 填补最高位, 并检查右移后的移出位if ((crc & 0x0001) > 0) {// 如果移出位为 1, CRC寄存器与多项式A001进行异或crc = crc >> 1;crc = crc ^ 0xA001;} else {// 如果移出位为 0,再次右移一位crc = crc >> 1;}}}return intToBytes(crc);}/*** 将 int 转换成 byte 数组 低位在前 高位在后*/private static byte[] intToBytes(int value) {byte[] src = new byte[2];byte hig = (byte) ((value>>8) & 0xFF);byte low = (byte) (value & 0xFF);if (IS_OUT_PUT_OVER_TURN){src[0] = low;src[1] = hig;} else {src[0] = hig;src[1] = low;}return src;}/*** 将字节数组转换成十六进制字符串*/public static String byteTo16String(byte[] data) {StringBuffer buffer = new StringBuffer();for (byte b : data) {byteToHex(buffer,b);}return buffer.toString().toUpperCase();}/*** 将字节转换成十六进制字符串** int 转 byte 对照表* [128,255],0,[1,128)* [-128,-1],0,[1,128)*/public static void byteToHex(StringBuffer buffer ,byte b) {if (b < 0) {buffer.append(Integer.toString(b + 256, 16));} else if (b == 0) {buffer.append("00 ");} else if (b > 0 && b <= 15) {buffer.append("0" + Integer.toString(b, 16));} else if (b > 15) {buffer.append(Integer.toString(b, 16));}buffer.append(" ");}
}
3.SerialUtil
package com.serial.demo.util;import gnu.io.SerialPort;import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;/*** @author* @date 2023-07-03 21:52* @since 1.8*/
public class SerialUtil {/*** 转为 HEX* @param str* @return*/public static String toHex(String str){StringBuffer sbf = new StringBuffer();byte[] b = str.getBytes(StandardCharsets.UTF_8);for (int i = 0; i < b.length; i++) {String hex = Integer.toHexString(b[i] & 0xFF);if (hex.length() == 1) {hex = '0' + hex;}sbf.append(hex.toUpperCase() + " ");}return sbf.toString().trim();}/**** @param hex* @return*/public static String toStr(String hex) {return new String(hexToByte(hex));}/*** 转 HEX 字节* @param hex* @return*/public static byte[] hexToByte(String hex){hex = hex.toUpperCase().replace(" ","");ByteArrayOutputStream bao = new ByteArrayOutputStream(hex.length() / 2);// 将每2位16进制整数组装成一个字节for (int i = 0; i < hex.length(); i += 2) {bao.write((Common.HEX_STRING.indexOf(hex.charAt(i)) << 4 | Common.HEX_STRING.indexOf(hex.charAt(i + 1))));}return bao.toByteArray();}/*** 获取校验位配置* @param checkBit* @return*/public static int getParity(String checkBit){if (Common.NONE.equals(checkBit)){return SerialPort.PARITY_NONE;} else if (Common.ODD.equals(checkBit)){return SerialPort.PARITY_ODD;} else if (Common.EVEN.equals(checkBit)){return SerialPort.PARITY_EVEN;} else {return SerialPort.PARITY_NONE;}}/*** 读取数据* @param in* @return*/public static byte[] readFromPort(InputStream in) {byte[] bytes = {};try {// 缓冲区大小为一个字节byte[] readBuffer = new byte[1];int bytesNum = in.read(readBuffer);while (bytesNum > 0) {bytes = concat(bytes, readBuffer);bytesNum = in.read(readBuffer);}} catch (IOException e) {e.printStackTrace();} finally {try {if (in != null) {in.close();in = null;}} catch (IOException e) {e.printStackTrace();}}return bytes;}/*** 字节转换* @param format* @param b* @return*/public static String printHexString(String format, byte[] b) {String result = new String(b);if (Common.FORMAT_HEX.equals(format)){return SerialUtil.toHex(result);}return result;}/*** 合并数组** @param firstArray 第一个数组* @param secondArray 第二个数组* @return 合并后的数组*/public static byte[] concat(byte[] firstArray, byte[] secondArray) {if (firstArray == null || secondArray == null) {if (firstArray != null) {return firstArray;}if (secondArray != null) {return secondArray;}return null;}byte[] bytes = new byte[firstArray.length + secondArray.length];System.arraycopy(firstArray, 0, bytes, 0, firstArray.length);System.arraycopy(secondArray, 0, bytes, firstArray.length, secondArray.length);return bytes;}
}
4.WebSocket 配置
1.启动配置
package com.serial.demo.socket;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;/*** @author* @date 2023-07-02 21:05* @since 1.8*/
@Configuration
public class WebSocketConfig {/*** 开启 websocket 配置* @return*/@Beanpublic ServerEndpointExporter serverEndpointExporter(){return new ServerEndpointExporter();}
}
2.监听配置
package com.serial.demo.socket;import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;import javax.websocket.OnClose;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;/*** @author* @date 2023-07-02 21:07* @since 1.8*/
@Slf4j
@Component
@ServerEndpoint("/websocket/{sid}")
public class SerialWebSocket {/*** 缓存通信实例*/private static Map<String