一、开发环境介绍
- 系统环境: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 串口调试工具收发消息测试;下载地址
二、关键代码
- 项目依赖
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、相关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