串口通信学习(GPS模块)2021.5.10

Posted jing_zhong

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了串口通信学习(GPS模块)2021.5.10相关的知识,希望对你有一定的参考价值。

1、串口通信简介

        串口通信(Serial Communication)是利用串口按位(bit)发送和接收字节。尽管比按字节(byte)的并行通信慢,但由于串口通信是异步的,端口能够在一根线上发送数据同时在另一根线上接收数据。**串口通信最重要的参数是波特率、数据位、停止位和奇偶校验**。

1.1 波特率

        波特率指的是信号被调制以后在单位时间内的变化,即单位时间内载波参数变化的次数,如每秒钟传送240个字符,而每个字符格式包含10位(1个起始位,1个停止位,8个数据位),这时的波特率为240Bd,比特率为10位*240个/秒=2400bps。

1.2 数据位

        当计算机发送一个信息包,实际的数据往往不会是8位的,标准的值是6、7和8位。标准的ASCII码是0~127(7位),而扩展的ASCII码是0~255(8位),如果数据使用简单的文本(标准 ASCII码),那么每个数据包使用7位数据。每个包是指一个字节,包括开始/停止位,数据位和奇偶校验位。

1.3 停止位

        停止位即用于表示单个包的最后一位。典型的值为1,1.5和2位。

1.4 奇偶校验位

        串口通信有四种检错方式:偶、奇、高和低。当然没有校验位也是可以的。对于偶和奇校验的情况,假设传输的数据位为01001100,如果是奇校验,则奇校验位为0(要确保总共有奇数个1),如果是偶校验,则偶校验位为1(要确保总共有偶数个1)。

2、GPS模块串口通信配置

GPS 模块
正面
反面

2.1 驱动安装

        在GPS模块上的USB插入电脑之前,首先向客服人员索要驱动,得到PL2303_Prolific_GPS_AllInOne_1013.exe驱动安装包后,双击运行即可,安装完成后可在控制面板中查看已安装的程序中有PL-2303 USB-to-Serial,表示安装成功。



在这里插入图片描述

        当然也可以自己从网上下载相关的驱动安装包后安装,也没有问题,如下面的PL2303_Prolific_DriveInstaller_v130.exe和PL2303_64bit_Installer.exe均可。

2.2 插入GPS模块

        驱动安装成功后,可以将GPS模块的USB接口插入到电脑的USB接口上,这时电脑右下方会提示成功安装了设备驱动程序,同时在电脑->设备管理器中也可进行查看

        在设备管理器的端口(COM和LPT)下可以看到Prolific USB-to-Serial Comm Port(COM3),右键->属性可以查看该GPS模块对应端口的具体信息,其中最重要的就是端口设置中的波特率、数据位、停止位和奇偶校验这四个参数
在这里插入图片描述

常规
端口设置
驱动程序
详细信息

2.3 GPS模块串口通信数据简介

在这里插入图片描述

        由于GPS模块接收到的数据遵循NMEA-0183协议该协议采用ASCII码,其串行通信默认参数为:波特率=9600bps,数据位=8bit,开始位=1bit,停止位=1bit,无奇偶校验。由于数据中包含GPRMC、GPVTG、GPGNS、GPGGA、GPGSA、GPGLL等,但我只需要RMC中一条数据,所以打开串口助手SSCOM3.2,打开COM3串口后通过向GPS模块发送新行$PMTK314,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0*29使得模块只输出 GPRMC(这里是客服人员告诉我的指令)

串口调试助手

在这里插入图片描述

3、Java实现GPS模块串口通信

3.1 所需环境(Eclipse+Java)

Eclipse
Java

3.2 新建Java工程COMM

        打开Eclipse,文件->新建Java Project,输入项目名称为COMM,同时选择对应的JAVA JDK 1.8后确定即可,然后在src文件夹下新建三个Package包(bean、serialport和test),并在bean包下新建GPSRMCMessage类,在serialport包下新建CostomException类、ParamConfig类和SerialPortUtil类,在test包下新建hellodouble类和Operate类。

3.3 各个包下的java类代码

3.3.1 SerialPortUtils.java

package serialport;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.util.Enumeration;
import java.util.TooManyListenersException;

import bean.GPSRMCMessage;
import gnu.io.CommPortIdentifier;
import gnu.io.PortInUseException;
import gnu.io.SerialPort;
import gnu.io.SerialPortEvent;
import gnu.io.SerialPortEventListener;
import gnu.io.UnsupportedCommOperationException;

/**
 * 串口参数的配置 串口一般有如下参数可以在该串口打开以前进行配置: 包括串口号,波特率,输入/输出流控制,数据位数,停止位和奇偶校验。
 */
// 注:串口操作类一定要继承SerialPortEventListener
public class SerialPortUtils implements SerialPortEventListener {
	// 检测系统中可用的通讯端口类
	private CommPortIdentifier commPortId;
	// 枚举类型
	private Enumeration<CommPortIdentifier> portList;
	// RS232串口
	private SerialPort serialPort;
	// 输入流
	private InputStream inputStream;
	// 输出流
	private OutputStream outputStream;
	// 保存串口返回信息
	private String data;
	// 保存串口返回信息十六进制
	private String dataHex;
	// 解析后的数据
	private GPSRMCMessage gpsrmc_msg;
    public int resulti=1;//写txt的个数

	@SuppressWarnings("unchecked")
	public void init(ParamConfig paramConfig) throws CostomException {
		// 获取系统中所有的通讯端口
		portList = CommPortIdentifier.getPortIdentifiers();
		// 记录是否含有指定串口
		boolean isExsist = false;
		// 循环通讯端口
		while (portList.hasMoreElements()) {
			commPortId = portList.nextElement();
			// 判断是否是串口
			if (commPortId.getPortType() == CommPortIdentifier.PORT_SERIAL) {
				// 比较串口名称是否是指定串口
				if (paramConfig.getSerialNumber().equals(commPortId.getName())) {
					// 串口存在
					isExsist = true;
					// 打开串口
					try {
						// open:(应用程序名【随意命名】,阻塞时等待的毫秒数)
						serialPort = (SerialPort) commPortId.open(Object.class.getSimpleName(), 2000);
						// 设置串口监听
						serialPort.addEventListener(this);
						// 设置串口数据时间有效(可监听)
						serialPort.notifyOnDataAvailable(true);
						// 设置串口通讯参数:波特率,数据位,停止位,校验方式
						serialPort.setSerialPortParams(paramConfig.getBaudRate(), paramConfig.getDataBit(),
								paramConfig.getStopBit(), paramConfig.getCheckoutBit());
					} catch (PortInUseException e) {
						throw new CostomException("端口被占用");
					} catch (TooManyListenersException e) {
						throw new CostomException("监听器过多");
					} catch (UnsupportedCommOperationException e) {
						throw new CostomException("不支持的COMM端口操作异常");
					}
					// 结束循环
					break;
				}
			}
		}
		// 若不存在该串口则抛出异常
		if (!isExsist) {
			throw new CostomException("不存在该串口!");
		}
	}

	/**
	 * 实现接口SerialPortEventListener中的方法 读取从串口中接收的数据
	 */
	@Override
	public void serialEvent(SerialPortEvent event) {
		switch (event.getEventType()) {
		case SerialPortEvent.BI: // 通讯中断
		case SerialPortEvent.OE: // 溢位错误
		case SerialPortEvent.FE: // 帧错误
		case SerialPortEvent.PE: // 奇偶校验错误
		case SerialPortEvent.CD: // 载波检测
		case SerialPortEvent.CTS: // 清除发送
		case SerialPortEvent.DSR: // 数据设备准备好
		case SerialPortEvent.RI: // 响铃侦测
		case SerialPortEvent.OUTPUT_BUFFER_EMPTY: // 输出缓冲区已清空
			break;
		case SerialPortEvent.DATA_AVAILABLE: // 有数据到达
			// 调用读取数据的方法
			try {
				myReadComm();
			} catch (CostomException e) {
				e.printStackTrace();
			}
			break;
		default:
			break;
		}
	}

	public void myReadComm() throws CostomException {
		try {
			inputStream = serialPort.getInputStream();
			// 通过输入流对象的available方法获取数组字节长度
			byte[] readBuffer = new byte[inputStream.available()];
			// 从线路上读取数据流
			int len = 0;
			data = "";
			// 从输入流读取数据,直到遇到"\\r\\n"或数据为空,即读取一行数据
			while (true) {
				len = inputStream.read(readBuffer);
				if (len == -1 || len == 0)
					break;
				data = data.concat(new String(readBuffer, 0, len));
				if (data.endsWith("\\r\\n"))
					break;
			}
			System.out.print(data);
			// 转为十六进制数据
			//dataHex = bytesToHexString(data.getBytes());
			//System.out.println(dataHex);
			 //解析数据
			gpsrmc_msg = GPSRMCMessage.parse(data);
			System.out.print(gpsrmc_msg.toMyString());
			inputStream.close();
			inputStream = null;
//			//2021.4.11 By jzbg
//			FileOutputStream fos = null;
//			OutputStreamWriter writer = null;
//			String writetxtfilepath = "D:\\\\guotuziyuan\\\\zhongjiban\\\\system\\\\ActiveDemoEarth\\\\WindowsFormsApplication1\\\\bin\\\\x64\\\\Release\\\\tag\\\\result"+resulti+".txt";
//			File file = new File(writetxtfilepath);
//			fos = new FileOutputStream(file);
//			writer = new OutputStreamWriter(fos,"utf-8");
//			writer.write(msg.toJJGString());
//			writer.close();
//			fos.close();
			this.resulti++;
		} catch (IOException e) {
			throw new CostomException("读取串口数据时发生IO异常");
		}
	}

	public void readComm() throws CostomException {
		try {
			inputStream = serialPort.getInputStream();
			// 通过输入流对象的available方法获取数组字节长度
			byte[] readBuffer = new byte[inputStream.available()];
			// 从线路上读取数据流
			int len = 0;
			while ((len = inputStream.read(readBuffer)) != -1) {
				// 直接获取到的数据
				data = new String(readBuffer, 0, len).trim();
				// 转为十六进制数据
				dataHex = bytesToHexString(readBuffer);
				System.out.println("data:" + data);
				System.out.println("dataHex:" + dataHex);// 读取后置空流对象
				inputStream.close();
				inputStream = null;
				break;
			}
		} catch (IOException e) {
			throw new CostomException("读取串口数据时发生IO异常");
		}
	}

	public void sendComm(String data) throws CostomException {
		byte[] writerBuffer = null;
		try {
			writerBuffer = hexToByteArray(data);
		} catch (NumberFormatException e) {
			throw new CostomException("命令格式错误!");
		}
		try {
			outputStream = serialPort.getOutputStream();
			outputStream.write(writerBuffer);
			outputStream.flush();
		} catch (NullPointerException e) {
			throw new CostomException("找不到串口。");
		} catch (IOException e) {
			throw new CostomException("发送信息到串口时发生IO异常");
		}
	}

	public void closeSerialPort() throws CostomException {
		if (serialPort != null) {
			serialPort.notifyOnDataAvailable(false);
			serialPort.removeEventListener();
			if (inputStream != null) {
				try {
					inputStream.close();
					inputStream = null;
				} catch (IOException e) {
					throw new CostomException("关闭输入流时发生IO异常");
				}
			}
			if (outputStream != null) {
				try {
					outputStream.close();
					outputStream = null;
				} catch (IOException e) {
					throw new CostomException("关闭输出流时发生IO异常");
				}
			}
			serialPort.close();
			serialPort = null;
		}
	}

	/**
	 * 十六进制串口返回值获取
	 */
	public String getDataHex() {
		String result = dataHex;
		// 置空执行结果
		dataHex = null;
		// 返回执行结果
		return result;
	}

	/**
	 * 串口返回值获取
	 */
	public String getData() {
		String result = data;
		// 置空执行结果
		data = null;
		// 返回执行结果
		return result;
	}

	/**
	 * Hex字符串转byte
	 * 
	 * @param inHex
	 *            待转换的Hex字符串
	 * @return 转换后的byte
	 */
	public static byte hexToByte(String inHex) {
		return (byte) Integer.parseInt(inHex, 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;
	}

	/**
	 * 数组转换成十六进制字符串
	 * 
	 * @param byte[]
	 * @return HexString
	 */
	public static final String bytesToHexString(byte[] bArray) {
		StringBuffer sb = new StringBuffer(bArray.length);
		String sTemp;
		for (int i = 0; i < bArray.length; i++) {
			sTemp = Integer.toHexString(0xFF & bArray[i]);
			if (sTemp.length() < 2)
				sb.append(0);
			sb.append(sTemp.toUpperCase());
		}
		return sb.toString();
	}
}

3.3.2 CostomException.java

package serialport;

public class CostomException extends Exception{

	private static final long serialVersionUID = 4087486187456785575L;

	public CostomException() {
        super();
    }
	
	public CostomException(String message) {
        super(message);
    }
}

3.3.3 ParamConfig.java

package serialport;

public class ParamConfig {

    private String serialNumber;// 串口号
    private int baudRate;        // 波特率
    private int checkoutBit;    // 校验位
    private int dataBit;        // 数据位
    private int stopBit;        // 停止位
    
    public ParamConfig() {}
        
    /**
     * 构造方法
     * @param serialNumber    串口号
     * @param baudRate        波特率
     * @param checkoutBit    校验位
     * @param dataBit        数据位
     * @param stopBit        停止位
     */
    public ParamConfig(String serialNumber, int baudRate, int checkoutBit, int dataBit, int stopBit) {
        this.serialNumber = serialNumber;
        this.baudRate = baudRate;
        this.checkoutBit = checkoutBit;
        this.dataBit = dataBit;
        this.stopBit = stopBit;
    }

	public String 以上是关于串口通信学习(GPS模块)2021.5.10的主要内容,如果未能解决你的问题,请参考以下文章

Perl 串口访问 [Windows 10]

BC20 NB+GPS二合一通信模块 带GPS 北斗定位 多频段无线通信模块(一看就会,一学就懂)

满意给高分,现有一gps模块,一51单片机。想做成能显示所在地经纬度的东西只需要知道经纬度数据即可求辅导

两片单片机通过串口一发一收的C语言例程

GPS定位函数

STM32 GPS+BD ATGM332D定位