SpringBoot 串口通讯

一、开发环境介绍

  • 系统环境:windows 10 专业版 64位
  • 开发工具:IDEA
  • JDK版本:jdk-8u201-windows-x64 (下载地址
  • SpringBoot版本:2.5.5
  • 构建工具:gradle

一、所需依赖项

  • 需将(rxtxParallel.dll、rxtxSerial.dll) 下载地址 动态链接库文件放入:C:\Program Files\Java\jdk1.8.0_201\bin 中才能进行串口通讯

二、虚拟串口以及串口测试工具

  • 使用 Virtual Serial Port Driver Pro 生成模拟串口;

    在这里插入图片描述

  • 使用 UartAssist 串口调试工具收发消息测试;下载地址

    在这里插入图片描述

二、关键代码

  1. 项目依赖
plugins {
    id 'org.springframework.boot' version '2.5.5'
    id 'io.spring.dependency-management' version '1.0.11.RELEASE'
    id 'java'
}

group = 'com.cn'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '1.8'

repositories {
    mavenCentral()
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'cn.qqhxj.common:spring-boot-starter-rxtx:1.3.1-RELEASE'
    implementation 'org.springframework.boot:spring-boot-starter:2.5.5'

    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

test {
    useJUnitPlatform()
}

  1. 串口通讯配置
    1、相关Bean配置
import cn.qqhxj.common.rxtx.reader.SerialReader;
import gnu.io.SerialPortEventListener;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;

/**
 * @Description: 串口配置类
 * @Author: JC
 * @CreateDate: 2021/10/15 18:17
 * @Version: 1.0
 */
@Component
public class SerialBeanConfig {
    @Bean
    public SerialReader serialReader() {
        return new CustomSerialReader();
    }


    @Bean
    public SerialPortEventListener serialPortEventListener() {
        return new CustomSerialPortEventListener();
    }
}

2、串口数据读取器


import cn.qqhxj.common.rxtx.SerialContext;
import cn.qqhxj.common.rxtx.reader.SerialReader;
import com.magic.serialportdemo.util.CRC16;

import java.nio.ByteBuffer;
import java.util.Arrays;

/**
 * @Description: 串口数据读取器
 * @Author: Juncheng He
 * @CreateDate: 2021/10/18 13:47
 * @Version: 1.0
 */
public class CustomSerialReader implements SerialReader {

    private final byte startChat = 0x48;


    private int dataLengthIndex = 6;

    private int allLength = 0;


    /**
     * 数据长度
     */
    private byte[] lengthArray = new byte[2];
    /**
     * CRC数据
     */
    private byte[] crcArray = new byte[2];


    /**
     * 缓存区
     */
    private ByteBuffer byteBuffer = ByteBuffer.allocate(1024);

    private boolean notOver = false;

    public CustomSerialReader() {
    }


    @Override
    public byte[] readBytes() {
        try {
            byte read = ((byte) SerialContext.getSerialPort().getInputStream().read());
            if (read == -1 && byteBuffer.position() < 8) {
                notOver = false;
                byteBuffer = ByteBuffer.allocate(1024);
                allLength = 0;
                lengthArray = new byte[2];
                crcArray = new byte[2];
            }
            // 读取一个字节 查找起始标记中是否存在
            if (startChat == read) {
                //读取的一个字节中存在起始标记
                byteBuffer.put(read);
                allLength = 1;
                notOver = true;
            } else {
                if (notOver) {

                    //获取数据长度
                    if (allLength == dataLengthIndex) {
                        lengthArray[0] = read;
                    } else if (allLength == (dataLengthIndex + 1)) {
                        lengthArray[1] = read;
                    }


                    allLength += 1;
                    byteBuffer.put(read);
                    //Data长度
                    int l = hex2Int(byteToHex(lengthArray));
                    if (allLength >= 8 + l) {

                        //获取校验码
                        if (allLength == 8 + l + 1) {
                            crcArray[0] = read;
                        } else if (allLength == 8 + l + 2) {
                            crcArray[1] = read;
                        }

                        if (allLength == 8 + l + 2) {
                            notOver = false;
                            byte[] array = Arrays.copyOf(byteBuffer.array(), byteBuffer.position());
                            byteBuffer = ByteBuffer.allocate(1024);
                            allLength = 0;
                            lengthArray = new byte[2];

                            if (CRC16.CRC16_KERMIT(array, 0, 8 + l).equals(byteToHex(crcArray))) {
                                crcArray = new byte[2];
                                System.out.println("CRC Success:" + byteToHex(array));
                                return array;
                            }
                            System.out.println("CRC FAIL:" + byteToHex(array));
                            crcArray = new byte[2];
                        }
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }


    /***
     * 字节转10进制
     * @param b byte
     * @return 十进制
     */
    public static int byte2Int(byte b) {
        int r = (int) b;
        return r;
    }

    public static int toInt(byte[] bytes) {
        int number = 0;
        for (int i = 0; i < 4; i++) {
            number += bytes[i] << i * 8;
        }
        return number;
    }

    /**
     * byte数组转hex
     *
     * @param bytes
     * @return
     */
    public static String byteToHex(byte[] bytes) {
        String strHex = "";
        StringBuilder sb = new StringBuilder("");
        for (int n = 0; n < bytes.length; n++) {
            strHex = Integer.toHexString(bytes[n] & 0xFF);
            // 每个字节由两个字符表示,位数不够,高位补0
            sb.append((strHex.length() == 1) ? "0" + strHex : strHex);
        }
        return sb.toString().trim().toUpperCase();
    }

    /**
     * hex转int
     *
     * @param hex
     * @return
     */
    public static int hex2Int(String hex) {
        return Integer.parseInt(hex, 16);
    }

    /**
     * hex字符串转byte数组
     *
     * @param inHex 待转换的Hex字符串
     * @return 转换后的byte数组结果
     */
    public static byte[] hexToByteArray(String inHex) {
        int hexlen = inHex.length();
        byte[] result;
        if (hexlen % 2 == 1) {
            //奇数
            hexlen++;
            result = new byte[(hexlen / 2)];
            inHex = "0" + inHex;
        } else {
            //偶数
            result = new byte[(hexlen / 2)];
        }
        int j = 0;
        for (int i = 0; i < hexlen; i += 2) {
            result[j] = hexToByte(inHex.substring(i, i + 2));
            j++;
        }
        return result;
    }

    /**
     * Hex字符串转byte
     *
     * @param inHex 待转换的Hex字符串
     * @return 转换后的byte
     */
    public static byte hexToByte(String inHex) {
        return (byte) Integer.parseInt(inHex, 16);
    }
}

3、事件监听器配置


import cn.qqhxj.common.rxtx.SerialContext;
import cn.qqhxj.common.rxtx.parse.SerialDataParser;
import cn.qqhxj.common.rxtx.processor.SerialByteDataProcessor;
import cn.qqhxj.common.rxtx.processor.SerialDataProcessor;
import gnu.io.SerialPortEvent;
import gnu.io.SerialPortEventListener;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Set;

/**
 * @Description: 串口数据监听器
 * @Author: Juncheng He
 * @CreateDate: 2021/10/19 11:50
 * @Version: 1.0
 */
@Slf4j
public class CustomSerialPortEventListener implements SerialPortEventListener {

    private Logger logger= LoggerFactory.getLogger(CustomSerialPortEventListener.class);

    @Override
    public void serialEvent(SerialPortEvent ev) {
        switch (ev.getEventType()) {
            // 通讯中断
            case SerialPortEvent.BI:
                logger.warn("通讯中断");
                break;
            // 溢位错误
            case SerialPortEvent.OE:
                logger.warn("溢位错误");
                break;
            // 帧错误
            case SerialPortEvent.FE:
                logger.warn("帧错误");
                break;
            // 奇偶校验错误
            case SerialPortEvent.PE:
                logger.warn("奇偶校验错误");
                break;
            // 载波检测
            case SerialPortEvent.CD:
                logger.warn("载波检测");
                break;
            // 清除发送
            case SerialPortEvent.CTS:
                logger.warn("清除发送");
                break;
            // 数据设备准备好
            case SerialPortEvent.DSR:
                logger.warn("数据设备准备好");
                break;
            // 响铃侦测
            case SerialPortEvent.RI:
                logger.warn("响铃侦测");
                break;
            // 输出缓冲区已清空
            case SerialPortEvent.OUTPUT_BUFFER_EMPTY:
                logger.warn("输出缓冲区已清空");
                break;
            // 有数据到达
            case SerialPortEvent.DATA_AVAILABLE:
                // 调用读取数据的方法
                Set<SerialDataParser> parserSet = SerialContext.getSerialDataParserSet();
                byte[] bytes = SerialContext.readData();
                if (bytes == null) {
                    return;
                }
                Object parse;
                for (SerialDataParser serialDataParser : parserSet) {
                    parse = serialDataParser.parse(bytes);
                    if (parse != null) {
                        dataProcessors(parse);
                    }
                }
                if (bytes.length > 0) {
                    SerialByteDataProcessor processor = SerialContext.getSerialByteDataProcessor();
                    if (processor != null) {
                        processor.process(bytes);
                    }
                }
            default:
                break;
        }
    }


    private void dataProcessors(Object obj) {
        Set<SerialDataProcessor> dataProcessors = SerialContext.getSerialDataProcessorSet();
        for (SerialDataProcessor serialDataProcessor : dataProcessors) {
            Class cl = serialDataProcessor.getClass();
            Class c2 = cl.getSuperclass();
            while (!c2.equals(Object.class)) {
                cl = cl.getSuperclass();
                c2 = cl.getSuperclass();
            }
            Type[] types = cl.getGenericInterfaces();
            for (Type type : types) {
                if (type instanceof ParameterizedType) {
                    ParameterizedType parameterizedType = (ParameterizedType) type;
                    Type rawType = parameterizedType.getRawType();
                    if (rawType instanceof Class) {
                        boolean equals = rawType.equals(SerialDataProcessor.class);
                        if (equals) {
                            String typeName = ((ParameterizedType) type).getActualTypeArguments()[0].getTypeName();
                            try {
                                Class<?> forName = Class.forName(typeName);
                                if (forName == obj.getClass()) {
                                    serialDataProcessor.process(obj);
                                }
                            } catch (ClassNotFoundException e) {
                                e.printStackTrace();
                            }
                        }
                    }
                }
            }
        }
    }
}

4、数据解析器

import cn.qqhxj.common.rxtx.parse.SerialDataParser;
import com.magic.serialportdemo.entity.DataEntity;
import org.springframework.stereotype.Component;

/**
 * @Description: 串口数据解析器
 * @Author: Juncheng He
 * @CreateDate: 2021/10/15 18:19
 * @Version: 1.0
 */
@Component
public class CustomStringSerialDataParser implements SerialDataParser<String> {

    @Override
    public String parse(byte[] bytes) {
        return new String(bytes);
    }
}

5、数据处理类

import cn.qqhxj.common.rxtx.processor.SerialDataProcessor;
import com.magic.serialportdemo.entity.DataEntity;
import com.magic.serialportdemo.util.Const;
import com.magic.serialportdemo.util.CustomSerialUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;

/**
 * @Description: 数据处理类
 * @Author: Juncheng He
 * @CreateDate: 2021/10/15 18:18
 * @Version: 1.0
 */
@Component
public class CustomDataProcessor implements SerialDataProcessor<String> {


    private Logger logger = LoggerFactory.getLogger(CustomDataProcessor.class);


 

    @Override
    public void process(Strings) {
 
        logger.info("回复消息:" + s);
    }
}

6、application.properties

serialport.baud-rate=115200
serialport.port-name=COM1
serialport.stop-bits=1
serialport.parity=2
serialport.data-bits=8
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 201,681评论 5 474
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,710评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,623评论 0 334
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,202评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,232评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,368评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,795评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,461评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,647评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,476评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,525评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,226评论 3 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,785评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,857评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,090评论 1 258
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,647评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,215评论 2 341

推荐阅读更多精彩内容

  • RXTX简介 RXTX是一个提供串口和并口通信的开源java类库,由该项目发布的文件均遵循LGPL协议。RXTX项...
    小土豆哥哥阅读 18,701评论 4 11
  • 上一篇对串口进行了简单的测试,在实际的应用中串口是硬件和上位机的通讯桥梁,这一篇我们通过编程方式来完成访问和控制串...
    文叉叉阅读 676评论 0 0
  • 前面说了现在的台式机和笔记本几乎都没有串口设计,但我们可以通过两种简单方法来进行串口的测试: 1.通过安装USB转...
    文叉叉阅读 438评论 0 0
  • 给大家分项下用RXTX库实现JAVA串口编程。 一 准备工作 1.1 下载资源文件 首先下载RXTX库...
    tuacy阅读 10,351评论 10 7
  • 串口通信是在工程应用中很常见。在上位机与下位机通讯过程中常通过有线的串口进行通信,在低速传输模式下串口通信得到广泛...
    Caizq阅读 6,195评论 2 9