首页 > 编程语言 >Java jSerialComm库串口通信(USB RS-485/232) 查询/应答、主动上报模式

Java jSerialComm库串口通信(USB RS-485/232) 查询/应答、主动上报模式

时间:2024-08-14 09:55:08浏览次数:11  
标签:Java USB SerialPort 串口 缓冲区 serialPort 数据 读取

Java jSerialComm库串口通信(USB RS-485/232) 查询/应答、主动上报模式

 

查询/应答模式

要在Java中通过USB RS-485接口发送和接收特定的数据帧,你需要利用适当的串行通信库。在Java中,一个常见的选择是使用RXTX或jSerialComm库。这些库允许Java应用程序与串行端口进行通信。

以下是实现你描述的特定通信过程的步骤:

1. 添加串行通信库依赖

如果你选择使用jSerialComm库,可以在你的Maven pom.xml 文件中添加以下依赖:

<pre class="custom" data-tool="mdnice编辑器" style="margin-top: 10px; margin-bottom: 10px; border-radius: 5px; box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px; text-align: left;"><dependency> <groupId>com.fazecast</groupId> <artifactId>jSerialComm</artifactId> <version>2.6.2</version> </dependency> </pre>

2. 串行端口通信代码

以下是一个基本的示例代码,展示如何发送和接收数据:

<pre class="custom" data-tool="mdnice编辑器" style="margin-top: 10px; margin-bottom: 10px; border-radius: 5px; box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px; text-align: left;">`import com.fazecast.jSerialComm.SerialPort;

public class RS485Communication {

public static void main(String[] args) {
    SerialPort serialPort = SerialPort.getCommPort("COM3"); // 替换为你的端口名
    serialPort.setComPortParameters(9600, 8, SerialPort.ONE_STOP_BIT, SerialPort.NO_PARITY); // 设置端口参数
    serialPort.setComPortTimeouts(SerialPort.TIMEOUT_READ_BLOCKING, 1000, 0);

    if (serialPort.openPort()) {
        System.out.println("Port opened successfully.");
    } else {
        System.out.println("Unable to open the port.");
        return;
    }

    try {
        // 发送数据
        byte[] writeBuffer = new byte[]{(byte) 0xFA, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, (byte) 0xCE, (byte) 0xFA};
        serialPort.writeBytes(writeBuffer, writeBuffer.length);

        // 接收数据
        byte[] readBuffer = new byte[1024]; // 调整数组大小以适应预期的响应长度
        int numRead = serialPort.readBytes(readBuffer, readBuffer.length);
        System.out.println("Read " + numRead + " bytes.");

        // 将读取的字节转换为十六进制字符串
        StringBuilder data = new StringBuilder();
        for (int i = 0; i < numRead; i++) {
            data.append(String.format("%02X ", readBuffer[i]));
        }
        System.out.println("Received data: " + data.toString());

    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        serialPort.closePort();
    }
}

}` </pre>

3. 数据解析

在接收到数据后,你可能需要根据你的协议解析这些数据。例如,你可能需要检查帧头、SN码、命令字等,并从数据内容中提取所需的信息。

4. 注意事项

  • 确保你的USB RS-485适配器已正确安装,并且你知道它在你的系统中的端口名称(如COM3、COM4等)。
  • 适当设置串行通信参数(如波特率、数据位、停止位和奇偶校验位)以匹配你的设备要求。
  • 如果你的系统不是基于Windows,串行端口名称可能会有所不同(如在Linux上通常是 /dev/ttyUSB0)。
  • 异常处理对于处理通信错误和意外情况非常重要。
  • 根据实际情况调整代码中缓冲区大小。

主动上报模式(监听)

1. 实现方法

一种是轮询模式(Polling),另一种是事件监听模式(Event Listener)。以下是关于这两种方法的说明:

  1. 轮询模式(Polling)

    • 在轮询模式下,程序会周期性地(通常使用循环)检查串口是否有可用数据。
    • 使用一个循环来检查串口COM3是否有可用数据,如果有数据,则读取并处理数据。
    • 这种方式比较简单,但可能会造成CPU的浪费,因为程序会不断地检查串口,即使没有数据到达。
  2. 事件监听模式(Event Listener)

    • 在事件监听模式下,程序注册了一个事件监听器(SerialPortEventListener),当串口有数据到达时,事件监听器会触发相应的事件。
    • 使用事件监听器来监听串口CO3,当有数据到达时,事件监听器会调用serialEvent方法来处理数据。
    • 这种方式相对更高效,因为程序只有在有数据到达时才会执行相应的处理代码,而不需要不断地轮询串口。

根据你的应用需求,选择轮询模式还是事件监听模式都是可以的。事件监听模式通常更加高效,特别是在需要实时处理数据或需要减少CPU占用的情况下。但需要注意的是,使用事件监听模式需要注册事件监听器,并确保程序不会在数据到达前退出。

无论哪种模式,都需要确保串口保持打开状态,以便能够接收数据。串口被打开后,程序进入一个循环或事件监听状态,以便随时接收数据。如果在数据到达之前关闭串口,数据将会丢失。

2. 事件监听模式

<pre class="custom" data-tool="mdnice编辑器" style="margin-top: 10px; margin-bottom: 10px; border-radius: 5px; box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px; text-align: left;">`import com.fazecast.jSerialComm.SerialPort;
import com.fazecast.jSerialComm.SerialPortDataListener;
import com.fazecast.jSerialComm.SerialPortEvent;

public class RS485CommunicationEventListener {

public static void main(String[] args) {
    SerialPort serialPort = SerialPort.getCommPort("COM3"); // 替换为你的端口名
    serialPort.setComPortParameters(9600, 8, SerialPort.ONE_STOP_BIT, SerialPort.NO_PARITY); // 设置端口参数
    /**
     你可以根据具体的需求选择适当的超时模式来控制串口通信的行为。在你的代码中,如果希望立即输出接收到的数据,可以选择 TIMEOUT_NONBLOCKING 模式。如果希望等待数据并确保完整读取,可以选择 TIMEOUT_READ_BLOCKING 模式。
     **/
    serialPort.setComPortTimeouts(SerialPort.TIMEOUT_NONBLOCKING, 0, 0);
    if (serialPort.openPort()) {
        System.out.println("Port opened successfully.");
    } else {
        System.out.println("Unable to open the port.");
        return;
    }

    // 创建数据监听器
    serialPort.addDataListener(new SerialPortDataListener() {
        @Override
        public int getListeningEvents() {
            return SerialPort.LISTENING_EVENT_DATA_AVAILABLE;
        }

        @Override
        public void serialEvent(SerialPortEvent event) {
            if (event.getEventType() == SerialPort.LISTENING_EVENT_DATA_AVAILABLE) {
                // 接收数据
                byte[] readBuffer = new byte[1024]; // 调整数组大小以适应预期的响应长度
                int numRead = serialPort.readBytes(readBuffer, readBuffer.length);
                if (numRead > 0) {
                    // 将读取的字节转换为十六进制字符串
                    StringBuilder data = new StringBuilder();
                    for (int i = 0; i < numRead; i++) {
                        data.append(String.format("%02X ", readBuffer[i]));
                    }
                    System.out.println("Received data: " + data.toString());
                    // 在这里进行收到数据的处理操作,例如加入阻塞队列,另一个模块消费队列解析收到的数据
                }
            }
        }
    });

    // 程序会持续监听串口数据,无需手动创建新线程
}

}` </pre>

3. 轮询模式

3.1 实现方法

为了让程序持续监听串口并输出收到的数据,可以在一个单独的线程中运行一个循环来读取串行端口。

<pre class="custom" data-tool="mdnice编辑器" style="margin-top: 10px; margin-bottom: 10px; border-radius: 5px; box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px; text-align: left;">`import com.fazecast.jSerialComm.SerialPort;

public class RS485CommunicationPolling {

public static void main(String[] args) {
    SerialPort serialPort = SerialPort.getCommPort("COM3"); // 替换为你的端口名
    serialPort.setComPortParameters(9600, 8, SerialPort.ONE_STOP_BIT, SerialPort.NO_PARITY); // 设置端口参数
    serialPort.setComPortTimeouts(SerialPort.TIMEOUT_NONBLOCKING, 0, 0);

    if (serialPort.openPort()) {
        System.out.println("Port opened successfully.");
    } else {
        System.out.println("Unable to open the port.");
        return;
    }

    // 创建一个新线程来处理输入
    Thread thread = new Thread(() -> {
        while (true) {
            try {
                // 接收数据
                byte[] readBuffer = new byte[1024]; // 调整数组大小以适应预期的响应长度
                int numRead = serialPort.readBytes(readBuffer, readBuffer.length);
                if (numRead > 0) {
                    // 将读取的字节转换为十六进制字符串
                    StringBuilder data = new StringBuilder();
                    for (int i = 0; i < numRead; i++) {
                        data.append(String.format("%02X ", readBuffer[i]));
                    }
                    System.out.println("Received data: " + data.toString());
                    // 收到数据的处理操作,例如加入阻塞队列,另一个模块消费队列解析收到的数据

                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    });

    // 启动线程
    thread.start();
}

}` </pre>

上面创建了一个无限循环的线程来读取串行端口。它会持续检查串行端口,当有数据到达时,它会读取数据,将其转换成十六进制格式的字符串,然后输出。

请注意,这样的代码会创建一个永远不会停止的线程,除非你在代码中添加了一种方法来停止它,比如检测到特定的输入或者程序关闭时。在实际应用中,你通常需要一种机制来安全地停止线程,并在不需要它时关闭串行端口。

3.2 轮询模式是否会丢失数据?

在轮询模式下,程序会周期性地使用循环来检查串口是否有可用数据。如果在轮询的间隙内有数据到达串口,这些数据通常会被串口驱动程序缓存起来,等待程序读取。

串口驱动程序通常会提供一个输入缓冲区,用于存储从串口接收到的数据。当数据到达串口时,它们会被放入这个缓冲区中,直到程序来读取它们。如果数据到达速度比程序读取速度快,那么这些数据会在缓冲区中等待。

因此,在轮询模式下,如果程序在轮询间隙内没有及时读取串口数据,已到达但尚未读取的数据会保留在串口的输入缓冲区中,等待程序的读取。程序可以随时读取这些数据,只要它们仍然存在于缓冲区中。

需要注意的是,串口的输入缓冲区大小是有限的,如果数据到达速度非常快,缓冲区可能会被填满,导致后续到达的数据丢失。因此,程序应该以足够快的速度读取串口数据,以避免数据丢失。如果需要处理大量数据或数据到达速度非常快,可以考虑使用事件监听模式,以便在数据到达时立即处理,而不是周期性地轮询。这可以提高数据的实时性。

3.3 串口缓冲区

串口缓冲区通常由串口设备的驱动程序和操作系统共同管理,它们在计算机系统中的位置是软件实现的。

具体来说,串口缓冲区通常包括两个部分:

  1. 硬件缓冲区:这部分是串口硬件上的缓冲区,用于存储从外部串口接收到的数据和将要发送的数据。串口硬件上的缓冲区大小是有限的,通常是几个字节到数十个字节不等,具体取决于串口设备的规格和型号。硬件缓冲区的大小是固定的,不可更改。
  2. 操作系统缓冲区:这部分缓冲区位于操作系统内核中,用于管理串口数据的传输。当数据从串口硬件传输到计算机时,操作系统会将数据从硬件缓冲区复制到操作系统缓冲区中,然后提供给应用程序进行读取。同样,当应用程序要发送数据时,数据首先被写入操作系统缓冲区,然后由操作系统传输到串口硬件。

应用程序通过串口API(如Java中的javax.comm或其他串口库)与操作系统交互,操作系统负责管理硬件缓冲区和数据传输。

因此,串口缓冲区的管理是由操作系统和串口驱动程序协同工作的结果,它们确保数据能够以可靠的方式在计算机和串口设备之间传输。



作者:摘星喵Pro
链接:https://www.jianshu.com/p/a562a5a80803
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

标签:Java,USB,SerialPort,串口,缓冲区,serialPort,数据,读取
From: https://www.cnblogs.com/gzhbk/p/18358272

相关文章

  • 【开端】Java中Log级别和解析
    一、绪论Java系统中需要对日志进行输出,方便定位系统访问信息,系统报错信息,用于排查系统问题等。我们常常使用的日志有一下一些级别publicinterfaceLog{ booleanisDebugEnabled(); booleanisTraceEnabled(); voiderror(Strings,Throwablee); voiderror(......
  • Java计算机毕业设计农村土地资源管理系统(开题报告+源码+论文)
    本系统(程序+源码)带文档lw万字以上 文末可获取一份本项目的java源码和数据库参考。系统程序文件列表开题报告内容研究背景随着农村经济的快速发展与城镇化进程的加速,农村土地资源的有效管理与合理利用成为亟待解决的问题。传统的手工记录与管理方式已难以满足当前农村土地......
  • Java计算机毕业设计教师档案管理系统(开题报告+源码+论文)
    本系统(程序+源码)带文档lw万字以上 文末可获取一份本项目的java源码和数据库参考。系统程序文件列表开题报告内容研究背景随着教育事业的蓬勃发展,教师作为教育教学的核心力量,其管理效率与质量直接关系到学校的整体运行与教学质量。传统的教师档案管理方式往往依赖于纸质文......
  • Java计算机毕业设计家装公司项目管理系统(开题报告+源码+论文)
    本系统(程序+源码)带文档lw万字以上 文末可获取一份本项目的java源码和数据库参考。系统程序文件列表开题报告内容研究背景随着城市化进程的加速与居民生活品质的提升,家装行业迎来了前所未有的发展机遇与挑战。传统家装公司依赖人工管理项目的方式已难以满足日益增长的客户......
  • java继承与多态
    继承与多态一、概念继承继承是面向对象编程中的一个基本概念,它允许我们定义一个类(称为子类或派生类)来继承另一个类(称为父类或基类)的属性和方法。通过继承,子类可以复用父类的代码,同时也可以添加自己的特定属性和方法。在Java中,继承是通过extends关键字来实现的。一个类只......
  • Java小白一文视图教废CSDN大佬们局部内部类和匿名内部类
    内部类一个类的内部又完整地嵌套了另一个类结构,被嵌套的类称为内部类,嵌套其他类的类称为外部类,是我们类的第五大成员,内部类最大的特点就是可以直接访问私有属性,并且可以体现类与类之间的包含关系。类的五大成员:属性、方法、构造器、代码块、内部类内部类快速入门//外部其......
  • 日撸Java三百行(day22:二叉树的存储)
    目录前言一、压缩存储二、层次遍历三、代码实现1.顺序表创建及初始化2.方法创建3.数据测试4.完整的程序代码总结前言关于二叉树的存储,昨天我们提到有顺序存储和链式存储这两种方式,不过非完全二叉树顺序存储的话会造成很大的空间浪费,所以我们昨天使用的是链式存储......
  • 【Java手写RPC框架系列-1】—— 基础知识准备:RPC+Netty
    代码随想录知识星球介绍https://articles.zsxq.com/id_m76jd72243bi.html基于Netty手写实现RPChttps://www.cnblogs.com/mic112/p/15565795.html项目背景与介绍RPC:远程过程调用协议:客户端在不知道调用细节的情况下,调用存在于远程计算机上的某个对象,就像调用本地应用程序......
  • Java 运算符(详解)
    文章目录一、简介二、算术运算符三、自增自减运算符四、关系运算符五、逻辑运算符六、位运算符六、赋值运算符七、条件运算符八、字符串连接符九、运算符优先级一、简介在Java中,运算符是用来对数据进行操作和处理的符号,这些符号能使得Java程序进行各种数学计算、......
  • Leetcode JAVA刷刷站(20)有效的括号
    一、题目概述二、思路方向     在Java中,要判断一个仅包含括号('(',')','{','}','[',']')的字符串是否有效,你可以使用栈(Stack)数据结构来实现。栈是一种后进先出(LIFO,LastInFirstOut)的数据结构,非常适合用来处理这类问题。以下是具体的实现步骤和代码示例:创......