SpringBoot使用RXTX连接串口教程及遇到的坑总结

article/2025/10/31 19:47:13

文章目录

  • SpringBoot使用RXTX连接串口教程及遇到的坑总结
    • 一、所用环境及依赖
    • 二、部署流程
      • 2.1 下载RXTXComm包
      • 2.2 部署RXTXComm包
    • 三、编写串口使用程序
      • 3.1 编写RXTXConfig.java
      • 3.2 编写实体类SerialPortEntity
      • 3.3 编写监听器SerialPortListener
      • 3.4 编写工具类SerialPortUtil
      • 3.5 编写枚举类WarningLightEnum
      • 3.6 编写测试类TestController
    • 四、所踩到的坑

SpringBoot使用RXTX连接串口教程及遇到的坑总结

  • 本文主要记录了本人在近期使用串口操作设备时的流程以及踩到的坑,希望能够帮助到有需要运用JAVA去操作串口需求的朋友,避免像我一样踩了很多坑才成功连接上。

一、所用环境及依赖

  • java: jdk11
  • maven: apache-maven-3.5.3
  • sprintboot:2.7.6
  • rxtx: 2.1.7
  • operate system: windows10

二、部署流程

2.1 下载RXTXComm包

  1. 可以选择从官网下载
  2. 也直接下载我网盘的分享文件网盘文件,提取码:sjsf
  3. 下载完成后把压缩包里的RXTXcomm.jar、rxtxParallel.dll、rxtxSerial.dll复制出来
    在这里插入图片描述

2.2 部署RXTXComm包

  1. 由于我的Springboot工程使用的是Maven仓库,因此需要通过命令先把RXTXcomm.jar导入到自己Maven的Repository里面(Maven的具体使用方式本文不做详解)
  2. 打开CMD,输入以下命令,导入RXTXcomm.jar
/** DgroupId: pom.xml中映射的groupId* DartifactId: pom.xml中映射的artifactId* Dversion: pom.xml中映射的version* Dpackaging: 加载的是jar包* Dfile: RXTXcomm.jar包的路径,建议使用绝对路径*/ 
mvn install:install-file-DgroupId=gnu.io -DartifactId=rxtx -Dversion=2.1.7 -Dpackaging=jar -Dfile="E:\RXTXcomm.jar"
  1. 运行后若在Maven的repository中存在下面的文件夹及文件,就代表导入成功了
    在这里插入图片描述
  2. RXTXcomm.jar导入成功后,需要把解压到的rxtxParallel.dll、rxtxSerial.dll文件复制到JAVA_HOME/bin路径下或者C:/Windows/System32/路径下(JAVA_HOME的配置方法很简单,自行百度即可)
  3. 最后在pom.xml中输入以下命令引用依赖即可完成RXTXcomm依赖导入到项目中
<dependency><groupId>gnu.io</groupId><artifactId>rxtx</artifactId><version>2.1.7</version></dependency>

三、编写串口使用程序

  • 为了更有效、更清晰地使用RXTXcomm,我编写了以下几个文件,分享出来供参考
  • 我的工程目录如下
    在这里插入图片描述

3.1 编写RXTXConfig.java

  1. 首先在application.yml中添加下面的配置
rxtx-config:portName: COM13  # 串口名baudRate: 9600  # 波特率parityBit: EVEN  # 检验位dataBits: 8  # 数据位stopBits: 1  # 停止位
  1. config目录下建立RXTXConfig.java,代码如下
package com.mbtxtq.app.config;import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;@Component
@ConfigurationProperties(prefix = "rxtx-config")
public class RXTXConfig {private String portName;private Integer baudRate;private String parityBit;private String dataBits;private String stopBits;public String getPortName() {return portName;}public void setPortName(String portName) {this.portName = portName;}public Integer getBaudRate() {return baudRate;}public void setBaudRate(Integer baudRate) {this.baudRate = baudRate;}public String getParityBit() {return parityBit;}public void setParityBit(String parityBit) {this.parityBit = parityBit;}public String getDataBits() {return dataBits;}public void setDataBits(String dataBits) {this.dataBits = dataBits;}public String getStopBits() {return stopBits;}public void setStopBits(String stopBits) {this.stopBits = stopBits;}}

3.2 编写实体类SerialPortEntity

  • 在entity目录下建立SerialPortEntity.java,代码如下
package com.mbtxtq.app.entity;import com.mbtxtq.app.listener.SerialPortListener;
import com.mbtxtq.app.utils.SerialPortUtil;
import gnu.io.SerialPort;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;import java.util.List;@Slf4j
@Data
public class SerialPortEntity {private String portName;  //端口号private Integer baudRate;  //波特率private int dataBits;  //数据位private String dataBitsStr;  //数据位字符串private int stopBits;  //停止位private String stopBitsStr;  //停止位字符串private int parityBit;  //校验位private String parityBitStr;  //校验位字符串private SerialPort serialPort = null; //串口会话public SerialPortEntity(String portName, Integer baudRate, String parityBit, String dataBits, String stopBits){this.portName = portName;this.baudRate = baudRate;this.dataBitsStr = dataBits;this.stopBitsStr = stopBits;this.parityBitStr = parityBit;//校验位switch (parityBit) {case "NONE":this.parityBit = SerialPort.PARITY_NONE;break;case "ODD":this.parityBit = SerialPort.PARITY_ODD;break;case "EVEN":this.parityBit = SerialPort.PARITY_EVEN;break;case "MARK":this.parityBit = SerialPort.PARITY_MARK;break;case "SPACE":this.parityBit = SerialPort.PARITY_SPACE;break;}//数据位switch (dataBits) {case "5":this.dataBits = SerialPort.DATABITS_5;break;case "6":this.dataBits = SerialPort.DATABITS_6;break;case "7":this.dataBits = SerialPort.DATABITS_7;break;case "8":this.dataBits = SerialPort.DATABITS_8;break;}//停止位switch (stopBits) {case "1":this.stopBits = SerialPort.STOPBITS_1;break;case "1.5":this.stopBits = SerialPort.STOPBITS_1_5;break;case "2":this.stopBits = SerialPort.STOPBITS_2;break;}}// 连接串口public SerialPort connect(){// 查看所有串口SerialPortUtil serialPortUtil = SerialPortUtil.getSerialPortUtil();List<String> portList = serialPortUtil.findPort();log.info("发现全部端口: "+portList);log.info("尝试打开端口:"+portName+" ....");// 打开指定端口SerialPort serialPort = serialPortUtil.openPort(portName,baudRate,dataBits,parityBit,stopBits);SerialPortListener listener = new SerialPortListener();listener.setSerialPort(serialPort);setSerialPort(serialPort);serialPortUtil.addListener(serialPort, listener);return serialPort;}// 关闭串口public void close(){SerialPortUtil serialPortUtil = SerialPortUtil.getSerialPortUtil();if(serialPort != null) serialPortUtil.closePort(serialPort);else log.info("请先调用connect方法!");}// 发送指令public void send(byte[] code){SerialPortUtil serialPortUtil = SerialPortUtil.getSerialPortUtil();if (serialPort != null) serialPortUtil.sendToPort(serialPort,code);else log.info("请先调用connect方法!");}}

3.3 编写监听器SerialPortListener

  • 在listener目录下建立SerialPortEntity.java,代码如下
package com.mbtxtq.app.listener;import com.mbtxtq.app.utils.SerialPortUtil;
import gnu.io.SerialPort;
import gnu.io.SerialPortEvent;
import gnu.io.SerialPortEventListener;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;import java.util.Arrays;
import java.util.Date;@Slf4j
@Data
public class SerialPortListener implements SerialPortEventListener {private SerialPort serialPort = null;@Overridepublic void serialEvent(SerialPortEvent serialPortEvent) {switch (serialPortEvent.getEventType()){// 串口存在有效数据case SerialPortEvent.DATA_AVAILABLE:byte[] bytes = SerialPortUtil.getSerialPortUtil().readFromPort(serialPort);log.info("===========start===========");log.info(new Date() + "【读到的字符】:-----" + Arrays.toString(bytes));
//                log.info(new Date() + "【字节数组转16进制字符串】:-----" + ModBusUtils.bytes2HexString(bytes));log.info("===========end===========");break;// 2.输出缓冲区已清空case SerialPortEvent.OUTPUT_BUFFER_EMPTY:log.error("输出缓冲区已清空");break;// 3.清除待发送数据case SerialPortEvent.CTS:log.error("清除待发送数据");break;// 4.待发送数据准备好了case SerialPortEvent.DSR:log.error("待发送数据准备好了");break;// 10.通讯中断case SerialPortEvent.BI:log.error("与串口设备通讯中断");break;default:break;}}}

3.4 编写工具类SerialPortUtil

  • 在utils目录下建立SerialPortUtil.java,代码如下
package com.mbtxtq.app.utils;import gnu.io.*;
import lombok.extern.slf4j.Slf4j;import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.TooManyListenersException;@Slf4j
public class SerialPortUtil {private static SerialPortUtil serialPortUtil = null;static {serialPortUtil = new SerialPortUtil();}private SerialPortUtil(){}/*** 获取提供服务的SerialTool对象* @return serialPortUtil*/public static SerialPortUtil getSerialPortUtil(){if(serialPortUtil == null){serialPortUtil = new SerialPortUtil();}return serialPortUtil;}/*** 查找所有可用端口* @return 可用端口名称列表*/public ArrayList<String> findPort() {// 获得当前所有可用串口Enumeration<CommPortIdentifier> portList = CommPortIdentifier.getPortIdentifiers();ArrayList<String> portNameList = new ArrayList<>();// 将可用串口名添加到List并返回该Listwhile (portList.hasMoreElements()) {String portName = portList.nextElement().getName();portNameList.add(portName);}return portNameList;}/*** 打开串口* @param portName 端口名称* @param baudrate 波特率  9600* @param databits 数据位  8* @param parity   校验位(奇偶位)  NONE :0* @param stopbits 停止位 1* @return 串口对象*/public SerialPort openPort(String portName, int baudrate, int databits, int parity, int stopbits) {try {// 通过端口名识别端口CommPortIdentifier portIdentifier = CommPortIdentifier.getPortIdentifier(portName);// 打开端口,并给端口名字和一个timeout(打开操作的超时时间)CommPort commPort = portIdentifier.open(portName, 5000);// 判断是不是串口if (commPort instanceof SerialPort) {SerialPort serialPort = (SerialPort) commPort;// 设置一下串口的波特率等参数serialPort.setSerialPortParams(baudrate, databits, stopbits, parity);log.info("打开串口 " + portName + " 成功 !");return serialPort;} else {log.error("不是串口");}} catch (NoSuchPortException e1) {log.error("没有找到端口");e1.printStackTrace();} catch (PortInUseException e2) {log.error("端口被占用");e2.printStackTrace();} catch (UnsupportedCommOperationException e) {e.printStackTrace();}return null;}/*** 关闭串口* @param serialPort 待关闭的串口对象*/public void closePort(SerialPort serialPort) {if (serialPort != null) {serialPort.close();}}/*** 往串口发送数据* @param serialPort 串口对象*/public void sendToPort(SerialPort serialPort, byte[] bytes) {OutputStream out = null;try {out = serialPort.getOutputStream();out.write(bytes);//            如果jdk大于1.8版本的话不要把out.flush()打开,会报错
//            out.flush();} catch (IOException e) {e.printStackTrace();} finally {try {if (out != null) {out.close();}} catch (IOException e) {e.printStackTrace();}}}/*** 从串口读取数据* @param serialPort 当前已建立连接的SerialPort对象* @return 读取到的数据*/public byte[] readFromPort(SerialPort serialPort) {InputStream in = null;byte[] bytes = null;try {Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}try {in = serialPort.getInputStream();// 获取buffer里的数据长度int bufferlength = in.available();while (bufferlength != 0) {// 初始化byte数组为buffer中数据的长度bytes = new byte[bufferlength];in.read(bytes);bufferlength = in.available();}} catch (IOException e) {e.printStackTrace();} finally {try {if (in != null) {in.close();}} catch (IOException e) {e.printStackTrace();}}return bytes;}/*** 添加监听器* @param port     串口对象* @param listener 串口监听器*/public void addListener(SerialPort port, SerialPortEventListener listener) {try {// 给串口添加监听器port.addEventListener(listener);// 设置当有数据到达时唤醒监听接收线程port.notifyOnDataAvailable(true);// 设置当通信中断时唤醒中断线程port.notifyOnBreakInterrupt(true);} catch (TooManyListenersException e) {log.error("太多监听器");e.printStackTrace();}}/*** 删除监听器** @param port     串口对象* @param listener 串口监听器*/public void removeListener(SerialPort port, SerialPortEventListener listener) {// 删除串口监听器port.removeEventListener();}/*** 设置串口的Listener** @param serialPort* @param listener*/public static void setListenerToSerialPort(SerialPort serialPort, SerialPortEventListener listener) {try {// 给串口添加事件监听serialPort.addEventListener(listener);} catch (TooManyListenersException e) {e.printStackTrace();}// 串口有数据监听serialPort.notifyOnDataAvailable(true);// 中断事件监听serialPort.notifyOnBreakInterrupt(true);}}

3.5 编写枚举类WarningLightEnum

  • 在enums目录下建立WarningLightEnum.java,代码如下
package com.mbtxtq.app.enums;public enum WarningLightEnum {ALL_CLOSE("全关", new byte[]{1, 5, 0, 0, 0, 0, (byte) 205, (byte) 202}),RED_LIGHT_OPEN("红灯亮", new byte[]{1, 5, 0, 1, (byte) 255, 0, (byte) 221, (byte) 250});private String codeType;private byte[] codeList;private WarningLightEnum(String codeType, byte[] codeList) {this.codeType = codeType;this.codeList = codeList;}public String getCodeType() {return codeType;}public void setCodeType(String codeType) {this.codeType = codeType;}public byte[] getCodeList() {return codeList;}public void setCodeList(byte[] codeList) {this.codeList = codeList;}}

3.6 编写测试类TestController

  • 在controller目录下建立TestController.java,代码如下
package com.mbtxtq.app.controller.test;import com.mbtxtq.app.config.RXTXConfig;
import com.mbtxtq.app.entity.main_scout_process.SerialPortEntity;
import com.mbtxtq.app.enums.WarningLightEnum;import gnu.io.SerialPort;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;import java.util.Map;@Controller
@CrossOrigin(origins = "*")
@ResponseBody
@RequestMapping("/target/feature/extract")
public class TestController {@Autowiredprivate RXTXConfig rxtxConfig;@PostMapping("test")public void test(@RequestBody Map<String, Object> params) {String useType = (String) params.get("use_type");byte[] codeList1 = warningLightEnum.ALL_CLOSE.getCodeList();byte[] codeList2 = WarningLightEnum.RED_LIGHT_OPEN.getCodeList();// 初始化实体类SerialPortEntity,参数使用RXTXConfig中的参数,可自行调整SerialPortEntity serialPortEntity = new SerialPortEntity(this.rxtxConfig.getPortName(), this.rxtxConfig.getBaudRate(),this.rxtxConfig.getParityBit(), this.rxtxConfig.getDataBits(), this.rxtxConfig.getStopBits());try {SerialPort serialPort = serialPortEntity.connect();System.out.println(serialPort);if (useType.equals("close")) {serialPortEntity.send(codeList1);} else if (useType.equals("start")) {serialPortEntity.send(codeList2);}//            Thread.sleep(10000);serialPortEntity.close();} catch (Exception e){e.printStackTrace();serialPortEntity.close();}}}
  • 通过postman获取前端代码请求test的api即可调用串口的测试接口,成功连接!

四、所踩到的坑

  1. 复制rxtxParallel.dll、rxtxSerial.dll这两个文件很重要!没有复制或者复制错目录会导致报以下的错
java.lang.UnsatisfiedLinkError: no rxtxSerial64 in java.library.path thrown while loading gnu.io.RXTXCommDriver
Exception in thread "AWT-EventQueue-0" java.lang.NoClassDefFoundError: Could not initialize class gnu.io.RXTXVersionat gnu.io.CommPortIdentifier.<clinit>(CommPortIdentifier.java:123)
  1. 如果你使用的是jdk1.8以上的版本的话,SerialPortUtil中sendToPort方法一定不要加out.flush,否则会报错。这个地方卡了我大半天,后来才找出来。
  2. 当使用java调用串口时,不要使用其他串口测试工具,否则会出现报错

http://chatgpt.dhexx.cn/article/iacenEM8.shtml

相关文章

【计算机基础|计算机组成原理】【10】海明校验码

海明校验码 海明校验码思路 偶校验&#xff1a;1010 → 01010&#xff0c;能发现奇数位错误&#xff0c;但无法确定是哪一位出错 → 1个校验位只能携带2种状态信息&#xff1a;对/错 海明码设计思路&#xff1a;将信息位分组进行偶校验 → 多个校验位能携带多种状态信息&#x…

计算机组成原理汉明校验,海明校验码(计算机组成原理11)

海明校验码 视频链接地址&#xff1a; https://www.bilibili.com/video/BV1BE411D7ii?fromsearch&seid6420326887479343502 前言 在本篇中&#xff0c;你将掌握 海明码的基本思想 海明码的求解步骤和全校验码 在计算机内部进行存储、计算的数据都是以二进制形式传送&#…

海明校验码纠错设计原理

本文不阐述海明码计算方式&#xff0c;为了节省您的时间&#xff0c;请先学习海明码/奇偶校验是如何计算的&#xff0c;如果好奇其海明码的纠错设计思路再来看此文章&#xff01; 信息为位数为n&#xff0c;校验位数为k&#xff0c;正确状态占1位&#xff0c;所以总位数&#…

2.21 海明校验码

海明校验码 需要了解海明码的编码规则&#xff0c;要会计算需要多少位校验位。 海明码的编码规则 校验位&#xff1a; 校验位的位置是有规律的。都是位于2n。 比如20(1),21(2),22(4)。。都是校验位。 信息位&#xff1a;不是校验位的其他位置。 举个例子 当信息位有1位&am…

【软考学习7】数据校验——海明校验码、循环校验码、奇偶校验码

一、检错、纠错和码距 1.1 检错 从接收的报文中&#xff0c;检查出错误。 1.2 纠错 从接收的报文中检查出错误&#xff0c;并改正错误。 一般通过加冗余信息&#xff08;增大码距&#xff09;来实现。 1.3 码距 码距是整个编码系统中任意两个码字的最小距离。 也就是说&a…

超详细的海明校验码方法解读

海明校验码原理&#xff1a;在有效的信息为中加入几个校验位形成海明码&#xff0c;使码距[rjazgj1] 比较均匀地拉大&#xff0c;并把海明码的每个二进制位分配到几个奇偶校验组[rjazgj2] 中。当某一位出错后&#xff0c;就会引起有关的几个校验位的值发生变化&#xff0c;这不…

海明校验码原理以及作用机制的介绍

什么是海明校验码&#xff1f; 由Richard Hamming于1950年提出、还被广泛采用的一种很有效的校验方法&#xff0c;是只要增加少数几个校验位&#xff0c;就能检测出二位同时出错、亦能检测出一位出错并能自动恢复该出错位的正确值的有效手段&#xff0c;后者被称为自动纠错。 它…

海明校验码的计算及检验

海明校验码的计算及检验 目录 海明校验码的计算及检验知识背景计算海明校验码步骤一&#xff1a;计算校验码位数步骤二&#xff1a;确定校验组步骤三&#xff1a;计算校验码的值得出海明校验码 利用海明校验码校验数据其他 总结 最近和兄弟探讨一个海明校验码的题目&#xff0c…

海明校验码举例

海明校验码举例&#xff1a; 编制ASCII字符M的海明校验码。 解&#xff1a;M的ASCII码为A6A5A4A3A2A1A01001101 M为7位那么海明码最少需要2i&#xff0c;也就是说需要&#xff0c;才能表示出来&#xff0c;&#xff08;238&#xff09; 用哪些信息位分别被哪些校验位效验如…

计算机底层:海明校验码。

计算机底层&#xff1a;海明校验码。 海明校验码是由奇偶校验码中的偶校验延申出来的&#xff1a; 计算机底层&#xff1a;奇偶校验码_srhqwe的博客-CSDN博客 了解海明校验码之前需要先了解奇偶校验码。 海明校验码设计思路&#xff1a; 需要知道&#xff1a;多个校验位就能携…

海明校验码

1. 海明码的特点&#xff1a; 其中m表示数据位的位数&#xff0c;k表示海明校验码的位数 k位海明校验码一共可以表示种校验信息结果&#xff0c;其中有一种要用来表示没有出错的情况&#xff0c;则其余还剩-1种结果&#xff0c;为了使校验结果可以指出任一位出错的位置&#x…

计算机组成原理学习笔记:海明校验码

概述 海明校验码又可以称为汉明校验码, 这只是一个音译的问题, 作者是 Richard Hamming海明校验码对于信息纠错这个领域的贡献十分巨大&#xff0c;Richard Hamming 获得了1968年的图灵奖内容主要包括&#xff1a;海明校验码的思想、如何构建海明校验码、如何使用海明校验码 …

海明码校验【简单详细】

海明码 1.什么是海明码: 一个名叫Richard Hanming老爷爷在1950年提出的检验纠错方法&#xff0c;它具有一位纠错能力。 2.海明码的计算方法: 设欲检测的二进制代码为n位,K为检测位(提供纠错),总共nk位代码 当中检测位满足的关系: 2 k 2^{k} 2k>(nk1) 此关系也是求不同代码长…

一文看懂海明校验码及其计算方法(详细总结)

网上看了好几篇文章后终于算是捋明白了&#xff0c;但是看到的这些资源要么说得云里雾里&#xff0c;要么干脆说得有问题&#xff08;然后还被点了好多赞。。。&#xff09;&#xff0c;无论如何这些都容易误导小白。作为C站多年老潜水员&#xff0c;我还是把海明校验码的要点总…

ResNets

ResNets 背景&#xff1a; 非常非常深的神经网络是很难训练的&#xff0c;因为存在梯度消失和梯度爆炸问题。 《转载更改》 https://blog.csdn.net/qq_29893385/article/details/81207203 ResNets是由残差块&#xff08;Residual block&#xff09;构建的 首先解释一下什么是…

正确定位混淆后Crash代码行数

Android--定位混淆后Crash代码行数 一、需求背景二、前期准备三、对混淆日志进行还原四、示例 一、需求背景 打包时需要对代码进行混淆&#xff0c;目的是增加安全性&#xff0c;防⽌反编译。但这会导致App崩溃时&#xff0c;抓到的日志堆栈中显示的代码行数对应不上&#xff…

repalce

1、replace基本用法 <script>/*要求将字符串中所有的a全部用A代替*/var str "javascript is great script language!";//只会将第一个匹配到的a替换成Aconsole.log(str.replace("a", "A")); // > jAvascript is great script language…

Android studio 4.2新特性及升级异常

Android studio 版本及特性系列目录 Android 12 终于来了&#xff0c;你准备好了吗&#xff1f;Android studio 4.2新特性Android studio 4.1新特性Android Studio 4.0新特性及升级异常Android Studio3.6. 插件搜索不到终极解决方案 Android studio 4.2新特性 前言升级异常Gra…

强化学习的学习之路(五十一)2021-02-20 Retrace

作为一个新手&#xff0c;写这个强化学习-基础知识专栏是想和大家分享一下自己学习强化学习的学习历程&#xff0c;希望对大家能有所帮助。这个系列后面会不断更新&#xff0c;希望自己在2021年能保证平均每日一更的更新速度&#xff0c;主要是介绍强化学习的基础知识&#xff…

RecId

我记得好像AX最初版本RecId是所有表都唯一的。但是这样有一个坏处就是限制了数据库可存储的数据的条数。D365FO中RecId 不再全局唯一&#xff0c;但是表唯一。 每个表都有一个Sequences生成表的RecId,格式是&#xff1a;SEQ_TableId 右键Sequences可以看下当前RecId的值&#…