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

news2024/12/29 0:45:05

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

在这里插入图片描述

查询/应答模式

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

以下是通信过程的步骤:

1. 添加串行通信库依赖

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

<dependency>
    <groupId>com.fazecast</groupId>
    <artifactId>jSerialComm</artifactId>
    <version>2.6.2</version>
</dependency>

2. 串行端口通信代码

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

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();
        }
    }
}

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. 事件监听模式

package com.zxbd.project.task;

import com.fazecast.jSerialComm.SerialPort;
import com.fazecast.jSerialComm.SerialPortDataListener;
import com.fazecast.jSerialComm.SerialPortEvent;
import com.zxbd.project.queue.DataQueue;
import lombok.extern.slf4j.Slf4j;

import java.time.Duration;
import java.time.Instant;
import javax.annotation.PostConstruct;

@Slf4j
public class SerialPortListener {

    private volatile Instant lastDataReceivedTime = Instant.now();
    private final long TIMEOUT_MILLIS = 30000; // 30秒的超时时间

    @PostConstruct
    public void init() {
        Thread serialPortListener = new Thread(this::listenerSerialPort);
        serialPortListener.setName("SerialPortListener");
        serialPortListener.start();
    }

    public void listenerSerialPort() {
        // 无限循环,以便在断开后重新尝试连接
        while (!Thread.currentThread().isInterrupted()) {
            try {
                SerialPort serialPort = SerialPort.getCommPort("COM3");
                serialPort.setComPortParameters(9600, 8, SerialPort.ONE_STOP_BIT, SerialPort.NO_PARITY);
                serialPort.setComPortTimeouts(SerialPort.TIMEOUT_NONBLOCKING, 1000, 0);
                if (serialPort.openPort()) {
                    log.info("Port opened successfully.");
                    lastDataReceivedTime = Instant.now();
                } else {
                    log.info("Unable to open the port. Retrying...");
                    Thread.sleep(5000); // 等待一段时间后重试
                    continue;
                }


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

                    @Override
                    public void serialEvent(SerialPortEvent event) {
                        try {
                            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++) {
                                    	// 加入阻塞队列用于其他线程后续数据处理
                                        // DataQueue.sensorDataQueue.put(String.format("%02X", readBuffer[i]));
                                        data.append(String.format("%02X ", readBuffer[i]));
                                    }
                                    log.info("Received data: " + data.toString());
                                }
                            }
                            // 更新收到数据的时间戳
                            lastDataReceivedTime = Instant.now();
                        } catch (Exception e) {
                            log.error("Error in data processing: " + e.getMessage());
                            throw new RuntimeException("Error in data processing", e);
                        }
                    }
                });
                // 接收数据超时,抛出异常
                while (true) {
                    if (Duration.between(lastDataReceivedTime, Instant.now()).toMillis() > TIMEOUT_MILLIS) {
                        throw new RuntimeException("No data received for over 30 seconds");
                    }
                    Thread.sleep(1000); // 每秒检查一次
                }
            } catch (Exception e) {
                log.warn("Error: " + e.getMessage() + ". Retrying...");
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException ex) {
                    log.error("Error in sleeping: " + ex.getMessage());
                    Thread.currentThread().interrupt();
                }
            }
        }
    }
}

3. 轮询模式

3.1 实现方法

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

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();
    }
}

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

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

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

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

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

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

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

3.3 串口缓冲区

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

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

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

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1362458.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

LabVIEW开发自动读取指针式仪表测试系统

LabVIEW开发自动读取指针式仪表测试系统 在工业领域&#xff0c;尤其是煤矿、变电站和集气站等环境中&#xff0c;指针式仪表因其简单的结构、抗干扰能力强以及能适应高温高压等恶劣环境条件而被广泛应用于设备运行状态监视。然而&#xff0c;传统的人工读表方式不仅成本高昂&…

Pygame和Cocos2d

Pygame和Cocos2d都是 Python 中常用的游戏引擎&#xff0c;但它们的设计目标、特点和使用场景略有不同。 Pygame与Cocos2d&#xff0c;目前是使用人数最多的两个Python游戏库。根据某知名产品点评网站的数据显示&#xff0c;排名前五的Python 2D游戏库如下图所示。其中&#x…

【linux踩雷】Ubuntu中su root密码无法使用

【linux踩雷】Ubuntu中su root密码无法使用 在ubuntu的安装过程中&#xff0c;没有出现设置root密码&#xff0c;以为密码为空&#xff0c;但是却不能使用 解决方法&#xff1a; 先用sudo passwd更改密码&#xff0c;再去su root就可以了。

python数据可视化之折线图案例讲解

学习完python基础知识点&#xff0c;终于来到了新的模块——数据可视化。 我理解的数据可视化是对大量的数据进行分析以更直观的形式展现出来。 今天我们用python数据可视化来实现一个2023年三大购物平台销售额比重的折线图。 准备工作&#xff1a;我们需要下载用于生成图表的第…

机器学习(四) -- 模型评估(3)

系列文章目录 机器学习&#xff08;一&#xff09; -- 概述 机器学习&#xff08;二&#xff09; -- 数据预处理&#xff08;1-3&#xff09; 机器学习&#xff08;三&#xff09; -- 特征工程&#xff08;1-2&#xff09; 机器学习&#xff08;四&#xff09; -- 模型评估…

稿件代写3个不可或缺的步骤让你事半功倍-华媒舍

作为一个需求频繁的作者&#xff0c;你可能会面临大量的稿件代写任务。但是&#xff0c;你是否曾经为提高文章质量而苦恼过&#xff1f;是否希望在有限的时间内完成更多的代写任务&#xff1f;本篇文章将向你介绍三个不可或缺的稿件代写步骤&#xff0c;帮助你事半功倍&#xf…

java预科

文章目录 预科什么是计算机?硬件及冯诺依曼结构Windows常用快捷键常用的DOS命令 预科 什么是计算机? 名称&#xff1a;Computer&#xff0c;全称电子计算机&#xff0c;俗称电脑。 定义&#xff1a;能够按照程序运行&#xff0c;自动、高速处理海量数据的现代化智能电子设备…

C语言程序设计——数学运算

基本运算符 运算符说明例子赋值运算符a b;、-、*、/、()基本四则运算a (a c) * d;%取余运算符a b % 2&、^、~、l位运算a ~b l c>>、<<左移和右移a b >> 2 在c语言的数学运算中&#xff0c;所涉及到的符号如图所示&#xff0c;在使用过程中应该了…

超简单的详细教程:如何为一个GitHub开源项目做出贡献!

仓库&#xff1a;Ai-trainee/GPT-Prompts-Hub 让我们通过一个具体的例子&#xff0c;详细了解如何从克隆一个GitHub仓库开始&#xff0c;一步步地贡献到一个项目。以下是详细步骤&#xff0c;包括所需的代码和说明&#xff1a; 首先我们Fork想要贡献的项目&#xff0c;然后请看…

Vite scss 如何引入本地 字体

Vite scss 如何引入本地 字体 最近在用 Vite 改造一个旧项目 Diary&#xff0c;遇到了好多从 Vue 转到 Vite 的问题。 这次这个问题是&#xff1a; scss 里本地字体引入的问题。 一、问题描述 可以看到下面的卡片字体&#xff0c;本来应该是 impact 的&#xff0c;但现在无法…

KBDSL1.DLL文件丢失,软件或游戏无法启动,快速修复方法

在Windows操作系统中&#xff0c;KBDSL1.DLL是一个动态链接库文件&#xff0c;由Microsoft Corporation提供。通常包含多个函数和程序&#xff0c;可以被多个应用程序共享&#xff0c;以执行一些特定的任务或功能。 如果KBDSL1.DLL文件丢失或损坏&#xff0c;可能会在启动计算机…

金和OA C6 UploadFileEditorSave.aspx 任意文件上传漏洞

产品介绍 金和网络是专业信息化服务商,为城市监管部门提供了互联网监管解决方案,为企事业单位提供组织协同OA系统开发平台,电子政务一体化平台,智慧电商平台等服务。 漏洞概述 金和 OA C6 uploadfileeditorsave接口处存在任意文件上传漏洞&#xff0c;攻击者可以通过构造特殊…

vmware中ubuntu虚拟机不能够用共享文件夹

有时候发现装好虚拟机后&#xff0c;然后 虚拟机-设置-选项-共享文件夹 然后使用快捷键ctrlaltt 打开命令行&#xff0c;cd /mnt下没有看到hgfs文件夹 解决办法是安装vmware tools工具 此时想通过点击 虚拟机-安装vmwaretools工具 按钮 居然发现该按钮是灰色的&#xff0…

Note: A Woman Doctor Lina

A woman doctor Lina 女医生丽娜 Born in a pigs’ nest, Lina led a poor life in her childhood. 出生在猪圈里&#xff0c;丽娜过着贫穷的童年生活。 led nest She was looked down upon by the children of her generation. 她被她同时代的孩子瞧不起。 generation look…

Python爬虫中的协程

协程 基本概念 协程&#xff1a;当程序执行的某一个任务遇到了IO操作时&#xff08;处于阻塞状态&#xff09;&#xff0c;不让CPU切换走&#xff08;就是不让CPU去执行其他程序&#xff09;&#xff0c;而是选择性的切换到其他任务上&#xff0c;让CPU执行新的任务&#xff…

QtApplets-SystemInfo

QtApplets-SystemInfo ​ 今天是2024年1月3日09:18:44&#xff0c;这也是2024年的第一篇博客&#xff0c;今天我们主要两件事&#xff0c;第一件&#xff0c;获取系统CPU使用率&#xff0c;第二件&#xff0c;获取系统内存使用情况。 ​ 这里因为写博客的这个本本的环境配置不…

C++学习day--25 俄罗斯方块游戏图像化开发

项目分析 项目演示、项目分析 启动页面 启动页面&#xff1a; 分析&#xff1a; 开发环境搭建 1&#xff09;安装vc2010, 或其他vs版本 2&#xff09;安装easyX图形库 代码实现: # include <stdio.h> # include <graphics.h> void welcome(void) { initgraph(55…

【MySQL】MySQL如何查询和筛选存储的JSON数据?

MySQL如何查询和筛选存储的JSON数据&#xff1f; 一、背景介绍二、支持的JSON数据类型三、基础数据3.1 创建表3.2 插入 JSON 数据3.3 查询 JSON 数据 四、操作函数4.1 JSON_OBJECT4.2 JSON_ARRAY4.3 JSON_EXTRACT 一、背景介绍 JSON(JavaScript Object Notation)是一种轻量级的…

如何通过内网穿透实现无公网IP远程访问内网的Linux宝塔面板

文章目录 一、使用官网一键安装命令安装宝塔二、简单配置宝塔&#xff0c;内网穿透三、使用固定公网地址访问宝塔 正文开始前给大家推荐个网站&#xff0c;前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。…

dubbo的基础知识

dubbo是什么 Dubbo是一个分布式服务框架&#xff0c;是一种高性能的远程通讯框架。它提供了基于Java的RPC&#xff08;远程过程调用&#xff09;通信机制&#xff0c;使得应用之间可以方便地进行远程调用&#xff0c;实现分布式服务的调用和管理。Dubbo提供了服务注册、发现、负…