JAVA使用RXTX编写串口调试工具-代码分析

本程序是基于下面博客修改而来,添加了更多的代码注释和一些功能,具体环境安装看下面的微博
https://blog.csdn.net/kong_gu_you_lan/article/details/80589859#commentBox
本文的源码也请前 先去上面博客里面下载原作者的源码,然后 把文章最后列出的两个文件的内容覆盖一下就行了。
项目所有文件-主窗口
运行图如下:
在这里插入图片描述

  1. 整体架构
    MainFrame.java既是主窗口的类,也包含主函数,主函数如下,就是绘制主窗口,调用MainFrame的初始化函数,对于java.awt.EventQueue.invokeLater,各位可以查一查,我也没完全理解,就不献丑了

    public static void main(String args[]) {
    		java.awt.EventQueue.invokeLater(new Runnable() {
    			public void run() {
    				new MainFrame().setVisible(true);
    			}
    		});
    	}
    

    而主窗口类的初始化函数如下

    public MainFrame() {
    		initView();//初始化窗口
    		initComponents();//初始化组件
    		actionListener();//监控事件
    		initData();
    	}
    

    接下来看看分别看看这四个函数的作用
    initView()函数如下,主要设置和窗口的一些属性

    private void initView() {
    		// 关闭程序
    		setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
    		// 禁止窗口最大化
    		setResizable(true);
    
    		// 设置程序窗口居中显示
    		Point p = GraphicsEnvironment.getLocalGraphicsEnvironment().getCenterPoint();
    		setBounds(p.x - WIDTH / 2, p.y - HEIGHT / 2, WIDTH, HEIGHT);
    		this.setLayout(null);
    		
    		setTitle("串口通信");
    	}
    

    private void initComponents() 函数主要完成了窗口里面各个组件的位置大小和其他的属性

    private void initComponents() {
    		// 数据显示
    		mDataView.setFocusable(false); 
    		    //将此组件的可聚焦状态设置为指定的值,这里false就是鼠标不可指向,点击没用
    		mScrollDataView.setBounds(10, 10, 505, 200);
    		add(mScrollDataView);
    
    	//总串口设置
    		mSerialPortPanel.setBorder(BorderFactory.createTitledBorder("串口设置"));
    		mSerialPortPanel.setBounds(10, 220, 170, 225);
    		mSerialPortPanel.setLayout(null);//清空布局管理器
    		add(mSerialPortPanel);
    		//串口设置
    		mSerialPortLabel.setForeground(Color.gray);//串口 两个字的标签 字体是灰色的
    		mSerialPortLabel.setBounds(10, 25, 40, 20);
    		mSerialPortPanel.add(mSerialPortLabel);//容器添加标签
    
    		mCommChoice.setFocusable(false);//串口选择框,不可聚焦状态,确保数据点击后就提交了
    		mCommChoice.setBounds(60, 25, 100, 20);
    		mSerialPortPanel.add(mCommChoice);//容器添加 串口选择框
    		
    		//波特率设置
    		mBaudrateLabel.setForeground(Color.gray);//波特率字体是灰色的
    		mBaudrateLabel.setBounds(10, 60, 40, 20);
    		mSerialPortPanel.add(mBaudrateLabel);
    
    		mBaudrateChoice.setFocusable(false);
    		mBaudrateChoice.setBounds(60, 60, 100, 20);
    		mSerialPortPanel.add(mBaudrateChoice);//容器添加 波特率选择框
    
    		//数据位
    		mDataBitLabel.setForeground(Color.gray);//波特率字体是灰色的
    		mDataBitLabel.setBounds(10, 95, 40, 20);
    		mSerialPortPanel.add(mDataBitLabel);
    
    		mDataBitChoice.setFocusable(false);
    		mDataBitChoice.setBounds(60, 95, 100, 20);
    		mSerialPortPanel.add(mDataBitChoice);//容器添加 波特率选择框
    		//停止位
    		mStopBitLabel.setForeground(Color.gray);//波特率字体是灰色的
    		mStopBitLabel.setBounds(10, 130, 40, 20);
    		mSerialPortPanel.add(mStopBitLabel);
    
    		mStopBitChoice.setFocusable(false);
    		mStopBitChoice.setBounds(60, 130, 100, 20);
    		mSerialPortPanel.add(mStopBitChoice);//容器添加 波特率选择框
    		//校验选择 
    		mParityLabel.setForeground(Color.gray);//波特率字体是灰色的
    		mParityLabel.setBounds(10, 165, 40, 20);
    		mSerialPortPanel.add(mParityLabel);
    
    		mParityChoice.setFocusable(false);
    		mParityChoice.setBounds(60, 165, 100, 20);
    		mSerialPortPanel.add(mParityChoice);//容器添加 波特率选择框
    		
    		
    		
    		mDataASCIIChoice.setBounds(20, 195, 55, 20);
    		mDataHexChoice.setBounds(95, 195, 55, 20);
    		mDataChoice.add(mDataASCIIChoice);//组按钮添加单选按钮
    		mDataChoice.add(mDataHexChoice);//组按钮添加单选按钮
    		mSerialPortPanel.add(mDataASCIIChoice);//容器添加两个单元按钮
    		mSerialPortPanel.add(mDataHexChoice);
    
    	// 操作面板 
    		mOperatePanel.setBorder(BorderFactory.createTitledBorder("操作"));
    		mOperatePanel.setBounds(200, 220, 315, 200);
    		mOperatePanel.setLayout(null);
    		add(mOperatePanel);//主窗口添加 操作容器
    
    		mDataInput.setBounds(25, 25, 265, 120);
    		mDataInput.setLineWrap(true);
    		mDataInput.setWrapStyleWord(true);
    		mOperatePanel.add(mDataInput);
    
    		mSerialPortOperate.setFocusable(false);//打开关闭串口 按钮
    		mSerialPortOperate.setBounds(45, 160, 90, 20);
    		mOperatePanel.add(mSerialPortOperate);
    
    		mSendData.setFocusable(false);//
    		mSendData.setBounds(180, 160, 90, 20);
    		mOperatePanel.add(mSendData);
    	}
    
    

    actionListener()代码如下,主要完成的任务有三个,第一:窗口的选择框,需要更新,因为如果电脑的串口有变化的时候,就需要更新串口列表。第二是:打开/关闭 串口的按钮需要一个监听事件,对应执行打开串口的函数和关闭串口的函数。第三是:发送数据按钮的监听事件,执行发送数据的任务

    private void actionListener() {
    		// 串口的复选框添加一个监听事件,这个事件主要就是当串口信息变化的时候更新复选框
    		mCommChoice.addPopupMenuListener(new PopupMenuListener() {
    
    			@Override
    			public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
    				mCommList = SerialPortManager.findPorts();
    				// 检查是否有可用串口,有则加入选项中
    				if (mCommList == null || mCommList.size() < 1) {
    					ShowUtils.warningMessage("没有搜索到有效串口!");
    				} else {
    					int index = mCommChoice.getSelectedIndex();
    					mCommChoice.removeAllItems();
    					for (String s : mCommList) {
    						mCommChoice.addItem(s);
    					}
    					mCommChoice.setSelectedIndex(index);
    					//指定要选择的列表项的整数,其中0指定列表中的第一个项,-1表示不选择
    					//如果不这样的话,比如你这一次点击了com4,但是下一次点击这个下拉框还是com3 com4,
    				}
    			}
    
    			@Override
    			public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
    				// NO OP
    			}
    
    			@Override
    			public void popupMenuCanceled(PopupMenuEvent e) {
    				// NO OP
    			}
    		});
    		
    		
    
    		// 打开|关闭串口 按钮添加监控事件 
    		mSerialPortOperate.addActionListener(new ActionListener() {
    
    			@Override
    			public void actionPerformed(ActionEvent e) {
    				if ("打开串口".equals(mSerialPortOperate.getText()) && mSerialport == null) {
    					openSerialPort(e);
    				} else {
    					closeSerialPort(e);
    				}
    				mDataView.setCaretPosition(mDataView.getDocument().getLength());
    			}
    		});
    
    		// 发送数据   按钮添加监控事件 
    		mSendData.addActionListener(new ActionListener() {
    
    			@Override
    			public void actionPerformed(ActionEvent e) {
    				sendData(e);
    				mDataView.setCaretPosition(mDataView.getDocument().getLength());
    
    			}
    		});
    	}
    

    接下来是主窗口类里面的三个辅助函数,会被上面所提到的三个监听事件调用
    第一:打开串口函数openSerialPort,其中里面组重要的是打开串口后,给串口添加了一个串口的监听事件,这个事件就是用来接收串口收到的数据的

    private void openSerialPort(java.awt.event.ActionEvent evt) {
    		// 获取串口名称
    		String commName = (String) mCommChoice.getSelectedItem();
    		// 获取波特率,默认为9600
    		int baudrate = 9600;
    		String bps = (String) mBaudrateChoice.getSelectedItem();//返回按钮的text
    		baudrate = Integer.parseInt(bps);
    		
    		int dataBit = mDataBitChoice.getSelectedIndex();//返回按钮的text
    		int stopBit =  mStopBitChoice.getSelectedIndex();//返回按钮的text
    		int pairity =  mParityChoice.getSelectedIndex();//返回按钮的text
    		
    		
    		// 检查串口名称是否获取正确
    		if (commName == null || commName.equals("")) {
    			ShowUtils.warningMessage("没有搜索到有效串口!");
    		} else {
    			try {
    				mSerialport = SerialPortManager.openPort(commName, baudrate,dataBit, stopBit, pairity);
    				if (mSerialport != null) {
    					mDataView.append(commName+"串口已打开" + "\r\n");//数据显示区域
    					mSerialPortOperate.setText("关闭串口");//按钮
    				}
    			} catch (PortInUseException e) {
    				ShowUtils.warningMessage("串口已被占用!");
    			}
    		}
    
    		// 添加串口监听
    		SerialPortManager.addListener(mSerialport, new SerialPortManager.DataAvailableListener() {
    
    			@Override  //重载 最初初始化函数里面什么都没有
    			public void dataAvailable() {
    				byte[] data = null;
    				try {
    					if (mSerialport == null) {
    						ShowUtils.errorMessage("串口对象为空,监听失败!");
    					} else {
    						// 读取串口数据
    						data = SerialPortManager.readFromPort(mSerialport);
    
    						// 以字符串的形式接收数据
    						if (mDataASCIIChoice.isSelected()) {
    							mDataView.append(new String(data) + "\r\n");
    						}
    
    						// 以十六进制的形式接收数据,使用ByteUtils类的方法,把byte[] 转换为string
    						if (mDataHexChoice.isSelected()) {
    							mDataView.append(ByteUtils.byteArrayToHexString(data) + "\r\n");
    						}
    						mDataView.setCaretPosition(mDataView.getDocument().getLength());
    
    					}
    				} catch (Exception e) {
    					ShowUtils.errorMessage(e.toString());
    					// 发生读取错误时显示错误信息后退出系统
    					System.exit(0);
    				}
    			}
    		});
    	}
    
    

    后面就是关闭串口closeSerialPort 和sendData发送数据两个函数了,没什么特殊的了。

  2. 用到的其他类
    在这里插入图片描述
    SerialPortManager 这个类是对RXTX提供的对串口操作函数的再一次封装,让我们在MainFrame类中的操作更加简单点。
    ArrayUtils 这个类是对数组操作的类,在接收byte[] 数组时候需要用到里面的函数进行拼接
    ByteUtils 这个类是16进制和byte数组的转换

  3. 关于收发数据的处理
    如果选择了ASCII码,那么你键入的每个字符都会转化为ASCII对应的数字,比如发送的单个数字1 对应就是49,可以调试一下
    在这里插入图片描述
    调试如下图,ssss这个byte数组就一个数字,数字是49
    在这里插入图片描述
    如果发送的的 1234 四个数字,那么ssss就保存了4个数字,分别是49 50 51 52。
    那么如果你选择的发送数据方式是十六进制:比如键入11,那么你键入的这个数字对应就是十进制的 17 对应byte[0]=11 接收端收到为 11 ;发送111(但这个大多调试助手不会让这样发送,111 这个数其实是0x0111 对应一个字节,所以可以改为0111),对应byte[0]=1,byte[1]=17 接受端收到为 01 11;发送a(也就是十进制的 10) 对应byte[0]=10,接收端是 0A ;
    那么这个byte[]数组是怎么发送出去的哪? 我们可以进入SerialPortManager.sendToPort();里面看看,如下图
    在这里插入图片描述
    相应的,现在选择ASCII,接受到了一个字节 byte 的值是49,则对应就是字母1,就是把每个字节转化为对应的ASCII码。那么如果是十六进制的话,使用另一个调试助手发送16进制 01
    使用16进制向我们编写的表示助手发送01数据
    调试收到的数据就是1,但是我们显示时候还是 需要显示01的在这里插入图片描述在这里插入图片描述
    如果发送的是 十六进制的a,接受到的数据是10,显示就是0a,如果发送的是十六进制的aa,接受到的一个byte字节的数据是-86 显示就是aa。(这里顺便说一下为什么是-86的问题,0xaa对应二进制的10101010,而二进制的101010对应-86的补码。关于计算可以参考这个https://blog.csdn.net/craftsman1970/article/details/60466539)
    主要计算就是byte字节转换为十六进制显示
    public static String byteToHex(byte b) 函数里面的
    String hex = Integer.toHexString(b & 0xFF); b就是-86的值,运算后hex=“aa”
    下面关于这行代码的介绍摘自
    https://blog.csdn.net/Jamie_Jiang/article/details/78343549博客
    0x表示是十六进制。
    ff是两个十六进制的数,每个f用二进制表示是1111,所以占四位(bit 对二进制而言的 一个十六进制的数字就对应二进制的四),两个f(ff)占八位(bit),八位(bit)也就是一个字节(byte).
    最后,上面的这些都理解了,那么具体是怎么个流程呢?
    首先toHexString传的参数应该是int类型32位,此处传的是byte类型8位,所以前面需要补24个0。然后& 0xff 就是把前面24个0去掉只要后8位。
    (经过百度)int本身就是由4组byte组成,并且Java中本身就以byte读取。所以此处传参没有问题。
    toHexString(b & 0xff)相当于做了一次位的与运算,将前24位字符省略,将后8位保留。即只转了后8位。而一个8位(bit) 用16进制表示刚好两位 即可得到两个十六进制的值。

    ————————————————
    版权声明:本文为CSDN博主「姜姜好呢」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
    原文链接:https://blog.csdn.net/Jamie_Jiang/article/details/78343549


在这里记载一下修改程序所用到的文档https://wenku.baidu.com/view/3f1dde8b4128915f804d2b160b4e767f5acf8020.html 一部分常用的API解释很详细,如果想了解RXTX很值得读一读
还有我找到的RXTX 的API 上传到GITHUB上了
https://github.com/WunaiDczh/RXTX-API-doc


源码:先去之前那个博客的GITHUB项目上下载下来后,复制我下面贴的两个文件的源码到下载后的对应文件里面就行,帮原作者多涨点下载 QAQ
一个是MainFrame.java

package com.yang.serialport.ui;

import java.awt.Color;
import java.awt.GraphicsEnvironment;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.List;

import javax.swing.BorderFactory;
import javax.swing.ButtonGroup;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.event.PopupMenuEvent;
import javax.swing.event.PopupMenuListener;

import com.yang.serialport.manager.SerialPortManager;
import com.yang.serialport.utils.ByteUtils;
import com.yang.serialport.utils.ShowUtils;

import gnu.io.PortInUseException;
import gnu.io.SerialPort;

/**
 * 主界面
 * 
 * @author yangle
 */
@SuppressWarnings("all")
public class MainFrame extends JFrame {//JFrame是java封装的一个GUI类
//下一步改进,发送 接受数据不同颜色
	// 设置文本框的 文本、字体 和 字体颜色
//	void setText(String text)
//	void setFont(Font font)
//	void setForeground(Color fg)  

	
	
	// 程序界面宽度
	public final int WIDTH = 550;
	// 程序界面高度
	public final int HEIGHT = 500;

	// 数据显示区
	private JTextArea mDataView = new JTextArea();
	private JScrollPane mScrollDataView = new JScrollPane(mDataView);
			//提供轻量级组件的可滚动视图

	// 串口设置面板
	private JPanel mSerialPortPanel = new JPanel();//容器
	private JLabel mSerialPortLabel = new JLabel("串口");//标签
	private JLabel mBaudrateLabel = new JLabel("波特率");
	private JComboBox mCommChoice = new JComboBox();//串口选择框
	
	private JLabel mDataBitLabel = new JLabel("数据位");//标签
	private JLabel mStopBitLabel = new JLabel("停止位");
	private JLabel mParityLabel = new JLabel("校验");//标签

	private JComboBox mBaudrateChoice = new JComboBox();//波特率选择框
	private JComboBox mDataBitChoice = new JComboBox();//数据位选择框
	private JComboBox mStopBitChoice = new JComboBox();//停止位选择框
	private JComboBox mParityChoice = new JComboBox();//校验选择框
	
	//用于为一组按钮创建多重排除范围 ,包含下面两个单选选项,打开一个关闭一个
	private ButtonGroup mDataChoice = new ButtonGroup();
	private JRadioButton mDataASCIIChoice = new JRadioButton("ASCII", true);
	private JRadioButton mDataHexChoice = new JRadioButton("Hex");

	// 操作面板
	private JPanel mOperatePanel = new JPanel();
	private JTextArea mDataInput = new JTextArea();
	private JButton mSerialPortOperate = new JButton("打开串口");
	private JButton mSendData = new JButton("发送数据");

	// 串口列表
	private List<String> mCommList = null;
	// 串口对象
	private SerialPort mSerialport;

	public MainFrame() {
		initView();//初始化窗口
		initComponents();//初始化组件
		actionListener();//监控事件
		initData();
	}

	/**
	 * 初始化主窗口   MainFrame类初始化时运行的第一个函数
	 */
	private void initView() {
		// 关闭程序
		setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
		// 禁止窗口最大化
		setResizable(true);

		// 设置程序窗口居中显示
		Point p = GraphicsEnvironment.getLocalGraphicsEnvironment().getCenterPoint();
		setBounds(p.x - WIDTH / 2, p.y - HEIGHT / 2, WIDTH, HEIGHT);
		this.setLayout(null);
		
		setTitle("串口通信");
	}

	/**
	 * 初始化主窗口里面的控件    MainFrame类初始化时运行的第二个函数
	 */
	private void initComponents() {
		// 数据显示
		mDataView.setFocusable(false); 
		    //将此组件的可聚焦状态设置为指定的值,这里false就是鼠标不可指向,点击没用
		mScrollDataView.setBounds(10, 10, 505, 200);
		add(mScrollDataView);

	//总串口设置
		mSerialPortPanel.setBorder(BorderFactory.createTitledBorder("串口设置"));
		mSerialPortPanel.setBounds(10, 220, 170, 225);
		mSerialPortPanel.setLayout(null);//清空布局管理器
		add(mSerialPortPanel);
		//串口设置
		mSerialPortLabel.setForeground(Color.gray);//串口 两个字的标签 字体是灰色的
		mSerialPortLabel.setBounds(10, 25, 40, 20);
		mSerialPortPanel.add(mSerialPortLabel);//容器添加标签

		mCommChoice.setFocusable(false);//串口选择框,不可聚焦状态,确保数据点击后就提交了
		mCommChoice.setBounds(60, 25, 100, 20);
		mSerialPortPanel.add(mCommChoice);//容器添加 串口选择框
		
		//波特率设置
		mBaudrateLabel.setForeground(Color.gray);//波特率字体是灰色的
		mBaudrateLabel.setBounds(10, 60, 40, 20);
		mSerialPortPanel.add(mBaudrateLabel);

		mBaudrateChoice.setFocusable(false);
		mBaudrateChoice.setBounds(60, 60, 100, 20);
		mSerialPortPanel.add(mBaudrateChoice);//容器添加 波特率选择框

		//数据位
		mDataBitLabel.setForeground(Color.gray);//波特率字体是灰色的
		mDataBitLabel.setBounds(10, 95, 40, 20);
		mSerialPortPanel.add(mDataBitLabel);

		mDataBitChoice.setFocusable(false);
		mDataBitChoice.setBounds(60, 95, 100, 20);
		mSerialPortPanel.add(mDataBitChoice);//容器添加 波特率选择框
		//停止位
		mStopBitLabel.setForeground(Color.gray);//波特率字体是灰色的
		mStopBitLabel.setBounds(10, 130, 40, 20);
		mSerialPortPanel.add(mStopBitLabel);

		mStopBitChoice.setFocusable(false);
		mStopBitChoice.setBounds(60, 130, 100, 20);
		mSerialPortPanel.add(mStopBitChoice);//容器添加 波特率选择框
		//校验选择 
		mParityLabel.setForeground(Color.gray);//波特率字体是灰色的
		mParityLabel.setBounds(10, 165, 40, 20);
		mSerialPortPanel.add(mParityLabel);

		mParityChoice.setFocusable(false);
		mParityChoice.setBounds(60, 165, 100, 20);
		mSerialPortPanel.add(mParityChoice);//容器添加 波特率选择框
		
		
		
		mDataASCIIChoice.setBounds(20, 195, 55, 20);
		mDataHexChoice.setBounds(95, 195, 55, 20);
		mDataChoice.add(mDataASCIIChoice);//组按钮添加单选按钮
		mDataChoice.add(mDataHexChoice);//组按钮添加单选按钮
		mSerialPortPanel.add(mDataASCIIChoice);//容器添加两个单元按钮
		mSerialPortPanel.add(mDataHexChoice);

	// 操作面板 
		mOperatePanel.setBorder(BorderFactory.createTitledBorder("操作"));
		mOperatePanel.setBounds(200, 220, 315, 200);
		mOperatePanel.setLayout(null);
		add(mOperatePanel);//主窗口添加 操作容器

		mDataInput.setBounds(25, 25, 265, 120);
		mDataInput.setLineWrap(true);
		mDataInput.setWrapStyleWord(true);
		mOperatePanel.add(mDataInput);

		mSerialPortOperate.setFocusable(false);//打开关闭串口 按钮
		mSerialPortOperate.setBounds(45, 160, 90, 20);
		mOperatePanel.add(mSerialPortOperate);

		mSendData.setFocusable(false);//
		mSendData.setBounds(180, 160, 90, 20);
		mOperatePanel.add(mSendData);
	}

	/**
	 * 初始化数据       MainFrame类初始化时运行的第四个函数
	 */
	private void initData() {
		mCommList = SerialPortManager.findPorts();
		// 检查是否有可用串口,有则加入选项中
		if (mCommList == null || mCommList.size() < 1) {
			ShowUtils.warningMessage("没有搜索到有效串口!");
		} else {
			for (String s : mCommList) {
				mCommChoice.addItem(s);
			}
		}

		mBaudrateChoice.addItem("9600");
		mBaudrateChoice.addItem("19200");
		mBaudrateChoice.addItem("38400");
		mBaudrateChoice.addItem("57600");
		mBaudrateChoice.addItem("115200");
		
		mDataBitChoice.addItem("5");
		mDataBitChoice.addItem("6");
		mDataBitChoice.addItem("7");
		mDataBitChoice.addItem("8");
		mDataBitChoice.setSelectedIndex(3);// 数据位 8是默认选项 
		
		mStopBitChoice.addItem("1");
		mStopBitChoice.addItem("1.5");
		mStopBitChoice.addItem("2");
		
		mParityChoice.addItem("None");
		mParityChoice.addItem("Odd");
		mParityChoice.addItem("Even");
		mParityChoice.addItem("Mark");
		mParityChoice.addItem("Space");
		
	}

	/**
	 * 按钮监听事件       MainFrame类初始化时运行的第三个函数
	 */
	private void actionListener() {
		// 串口的复选框添加一个监听事件,这个事件主要就是当串口信息变化的时候更新复选框
		mCommChoice.addPopupMenuListener(new PopupMenuListener() {

			@Override
			public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
				mCommList = SerialPortManager.findPorts();
				// 检查是否有可用串口,有则加入选项中
				if (mCommList == null || mCommList.size() < 1) {
					ShowUtils.warningMessage("没有搜索到有效串口!");
				} else {
					int index = mCommChoice.getSelectedIndex();
					mCommChoice.removeAllItems();
					for (String s : mCommList) {
						mCommChoice.addItem(s);
					}
					mCommChoice.setSelectedIndex(index);
					//指定要选择的列表项的整数,其中0指定列表中的第一个项,-1表示不选择
					//如果不这样的话,比如你这一次点击了com4,但是下一次点击这个下拉框还是com3 com4,
				}
			}

			@Override
			public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
				// NO OP
			}

			@Override
			public void popupMenuCanceled(PopupMenuEvent e) {
				// NO OP
			}
		});
		
		

		// 打开|关闭串口 按钮添加监控事件 
		mSerialPortOperate.addActionListener(new ActionListener() {

			@Override
			public void actionPerformed(ActionEvent e) {
				if ("打开串口".equals(mSerialPortOperate.getText()) && mSerialport == null) {
					openSerialPort(e);
				} else {
					closeSerialPort(e);
				}
				mDataView.setCaretPosition(mDataView.getDocument().getLength());
			}
		});

		// 发送数据   按钮添加监控事件 
		mSendData.addActionListener(new ActionListener() {

			@Override
			public void actionPerformed(ActionEvent e) {
				sendData(e);
				mDataView.setCaretPosition(mDataView.getDocument().getLength());

			}
		});
	}

	/**
	 * 打开串口
	 * 
	 * @param evt
	 *            点击事件
	 */
	private void openSerialPort(java.awt.event.ActionEvent evt) {
		// 获取串口名称
		String commName = (String) mCommChoice.getSelectedItem();
		// 获取波特率,默认为9600
		int baudrate = 9600;
		String bps = (String) mBaudrateChoice.getSelectedItem();//返回按钮的text
		baudrate = Integer.parseInt(bps);
		
		int dataBit = mDataBitChoice.getSelectedIndex();//返回按钮的text
		int stopBit =  mStopBitChoice.getSelectedIndex();//返回按钮的text
		int pairity =  mParityChoice.getSelectedIndex();//返回按钮的text
		
		
		// 检查串口名称是否获取正确
		if (commName == null || commName.equals("")) {
			ShowUtils.warningMessage("没有搜索到有效串口!");
		} else {
			try {
				mSerialport = SerialPortManager.openPort(commName, baudrate,dataBit, stopBit, pairity);
				if (mSerialport != null) {
					mDataView.append(commName+"串口已打开" + "\r\n");//数据显示区域
					mSerialPortOperate.setText("关闭串口");//按钮
				}
			} catch (PortInUseException e) {
				ShowUtils.warningMessage("串口已被占用!");
			}
		}

		// 添加串口监听
		SerialPortManager.addListener(mSerialport, new SerialPortManager.DataAvailableListener() {

			@Override  //重载 最初初始化函数里面什么都没有
			public void dataAvailable() {
				byte[] data = null;
				try {
					if (mSerialport == null) {
						ShowUtils.errorMessage("串口对象为空,监听失败!");
					} else {
						// 读取串口数据
						data = SerialPortManager.readFromPort(mSerialport);

						// 以字符串的形式接收数据
						if (mDataASCIIChoice.isSelected()) {
							mDataView.append(new String(data) + "\r\n");
						}

						// 以十六进制的形式接收数据,使用ByteUtils类的方法,把byte[] 转换为string
						if (mDataHexChoice.isSelected()) {
							mDataView.append(ByteUtils.byteArrayToHexString(data) + "\r\n");
						}
						mDataView.setCaretPosition(mDataView.getDocument().getLength());

					}
				} catch (Exception e) {
					ShowUtils.errorMessage(e.toString());
					// 发生读取错误时显示错误信息后退出系统
					System.exit(0);
				}
			}
		});
	}

	/**
	 * 关闭串口
	 * 
	 * @param evt
	 *            点击事件
	 */
	private void closeSerialPort(java.awt.event.ActionEvent evt) {
		SerialPortManager.closePort(mSerialport);
		mDataView.append("串口已关闭" + "\r\n");
		mSerialPortOperate.setText("打开串口");
		mSerialport = null;
	}

	/**
	 * 发送数据
	 * 
	 * @param evt
	 *            点击事件
	 */
	private void sendData(java.awt.event.ActionEvent evt) {
		// 待发送数据
		String data = mDataInput.getText().toString();

		if (mSerialport == null) {
			ShowUtils.warningMessage("请先打开串口!");
			return;
		}

		if ("".equals(data) || data == null) {
			ShowUtils.warningMessage("请输入要发送的数据!");
			return;
		}

		// 以字符串的形式发送数据
		if (mDataASCIIChoice.isSelected()) {
			SerialPortManager.sendToPort(mSerialport, data.getBytes());
			// 以字符串的形式发送数据
			mDataView.append("发送:"+data + "\r\n");
			
		}

		// 以十六进制的形式发送数据
		if (mDataHexChoice.isSelected()) {
			SerialPortManager.sendToPort(mSerialport, ByteUtils.hexStr2Byte(data));
			mDataView.append("发送:"+data + "\r\n");
		}
		mDataView.setCaretPosition(mDataView.getDocument().getLength());

	}

	public static void main(String args[]) {
		java.awt.EventQueue.invokeLater(new Runnable() {
			public void run() {
				new MainFrame().setVisible(true);
			}
		});
	}
}

一个是SerialPortManager.java

package com.yang.serialport.manager;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.TooManyListenersException;

import com.yang.serialport.utils.ArrayUtils;
import com.yang.serialport.utils.ShowUtils;

import gnu.io.CommPort;
import gnu.io.CommPortIdentifier;
import gnu.io.NoSuchPortException;
import gnu.io.PortInUseException;
import gnu.io.RS485PortEvent;
import gnu.io.SerialPort;
import gnu.io.SerialPortEvent;
import gnu.io.SerialPortEventListener;
import gnu.io.UnsupportedCommOperationException;

/**
 * 串口管理
 * 
 * @author yangle
 */
@SuppressWarnings("all")
public class SerialPortManager {

	/**
	 * 查找所有可用端口
	 * 
	 * @return 可用端口名称列表
	 */
	public static final ArrayList<String> findPorts() {
		// 获得当前所有可用串口, CommPortIdentifier 通讯端口管理类  是RXTXcom.jar里面的
		Enumeration<CommPortIdentifier> portList = CommPortIdentifier
				.getPortIdentifiers();
		// 不带参数的getPortIdentifiers方法获得一个CommPortIdentifier对象 枚举对象,

		ArrayList<String> portNameList = new ArrayList<String>();
		// 将可用串口名添加到List并返回该List
		while (portList.hasMoreElements()) {
			CommPortIdentifier portIdentifier = portList.nextElement();
			String portName = portIdentifier.getName();
			portNameList.add(portName);
			System.out.println(portIdentifier.getName()+'-'+getPortTypeName(portIdentifier.getPortType()));
		}
		return portNameList;
	}

	public static String getPortTypeName(int portType) {
		switch (portType) {
		case CommPortIdentifier.PORT_I2C:
			return "I2C";
		case CommPortIdentifier.PORT_PARALLEL:// 井口
			return "Parallel";
		case CommPortIdentifier.PORT_RAW:
			return "Raw";
		case CommPortIdentifier.PORT_RS485:// RS485端口
			return "RS485";
		case CommPortIdentifier.PORT_SERIAL:// 串口
			return "Serial";
		default:
			return "unknown type";
		}
	}

	/**
	 * 打开串口
	 * 
	 * @param portName
	 *            端口名称
	 * @param baudrate
	 *            波特率
	 * @return 串口对象
	 * @throws PortInUseException
	 *             串口已被占用
	 */
	public static final SerialPort openPort(String portName, int baudrate,int dataBit,int stopBit,int pairity)
			throws PortInUseException {
		try {
	// getPortIdentifier(string name) 通过端口名 识别通讯端口,返回CommPortIdentifier通讯端口管理对象
			CommPortIdentifier portIdentifier = CommPortIdentifier
					.getPortIdentifier(portName);
			// 打开端口,并给端口名字和一个timeout(打开操作的超时时间)
			CommPort commPort = portIdentifier.open(portName, 2000);
			
			// 判断是不是串口
			if (commPort instanceof SerialPort) {
				SerialPort serialPort = (SerialPort) commPort;// 通讯端口转化为串行通讯端口
				
				try {
					// 设置一下串口的波特率等参数
					// 数据位:8
					// 停止位:1
					// 校验位:None
					int DATABITS=SerialPort.DATABITS_8,STOPBITS=SerialPort.STOPBITS_1,PARITY=SerialPort.PARITY_NONE;
					switch (dataBit) {
					case 0:
						DATABITS=SerialPort.DATABITS_5;
						break;
					case 1:
						DATABITS=SerialPort.DATABITS_6;
						break;
					case 2:
						DATABITS=SerialPort.DATABITS_7;
						break;
					case 3:
						DATABITS=SerialPort.DATABITS_8;
						break;
					default:
						break;
					}
					switch (stopBit) {
					case 0:
						STOPBITS=SerialPort.STOPBITS_1;
						break;
					case 1:
						STOPBITS=SerialPort.STOPBITS_1_5;
						break;
					case 2:
						STOPBITS=SerialPort.STOPBITS_2;
						break;
					default:
						break;
					}
					switch (pairity) {
					case 0:
						PARITY=SerialPort.PARITY_NONE;
						break;
					case 1:
						PARITY=SerialPort.PARITY_ODD;
						break;
					case 2:
						PARITY=SerialPort.PARITY_EVEN;
						break;
					case 3:
						PARITY=SerialPort.PARITY_MARK;
						break;
					case 4:
						PARITY=SerialPort.PARITY_SPACE;
						break;
					default:
						break;
					}
					serialPort.setSerialPortParams(baudrate,
							DATABITS, STOPBITS,
							PARITY);
				} catch (UnsupportedCommOperationException e) {
					e.printStackTrace();
				}
				return serialPort;
			}
		} catch (NoSuchPortException e1) {
			e1.printStackTrace();
		}
		return null;
	}

	/**
	 * 关闭串口
	 * 
	 * @param serialport
	 *            待关闭的串口对象
	 */
	public static void closePort(SerialPort serialPort) {
		if (serialPort != null) {
			serialPort.close();
		}
	}

	/**
	 * 往串口发送数据
	 * 
	 * @param serialPort
	 *            串口对象
	 * @param order
	 *            待发送数据
	 */
	public static void sendToPort(SerialPort serialPort, byte[] order) {
		OutputStream out = null;
		try {
			out = serialPort.getOutputStream();
			//OutputStream 这个抽象类是表示字节输出流的所有类的超类。 输出流接收输出字节并将其发送到某个接收器。
			out.write(order);
			//将 order.length字节从指定的字节数组写入此输出流。
			out.flush();
			//刷新此输出流并强制任何缓冲的输出字节被写出。 flush的一般合同是,
			//呼叫它表明,如果先前写入的任何字节已经通过输出流的实现进行缓冲,则这些字节应该立即被写入到它们的预定目的地。
		} catch (IOException e) {
			e.printStackTrace();
		} finally {//无论 try / catch 结果如何都会执行的代码块
			try {
				//执行到这里了,out所需得到工作已经完成了,out保存了串口字节输出流的对象,非空才能关闭
				if (out != null) {
					out.close();
					out = null;
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

	/**
	 * 从串口读取数据
	 * 
	 * @param serialPort
	 *            当前已建立连接的SerialPort对象
	 * @return 读取到的数据
	 */
	public static byte[] readFromPort(SerialPort serialPort) {
		InputStream in = null;
		byte[] bytes = {};//保存所有的 字节 
		try {
			in = serialPort.getInputStream();
			// 缓冲区大小为一个字节
			byte[] readBuffer = new byte[1];
			//从输入流in中 读取一些(这里是1个)字节数,并将它们存储到缓冲区 readBuffer 。
			int bytesNum = in.read(readBuffer);
			while (bytesNum > 0) {
				//将读到的readBuffer中的 1字节  连接到bytes  字节数组后 
				bytes = ArrayUtils.concat(bytes, readBuffer);
				bytesNum = in.read(readBuffer);
			}
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			try {
				if (in != null) {
					in.close();
					in = null;
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		return bytes;
	}

	/**
	 * 添加监听器
	 * 
	 * @param port
	 *            串口对象
	 * @param listener
	 *            串口存在有效数据监听
	 */
	public static void addListener(SerialPort serialPort,
			DataAvailableListener listener) {
		try {
			// 给串口添加监听器
			serialPort.addEventListener(new SerialPortListener(listener));
			// 设置当有数据到达时唤醒监听接收线程
			serialPort.notifyOnDataAvailable(true);
			// 设置当通信中断时唤醒中断线程
			serialPort.notifyOnBreakInterrupt(true);
		} catch (TooManyListenersException e) {
			e.printStackTrace();
		}
	}

	/**
	 * 串口监听
	 */
	//串口操作类,一定要继承  SerialPortEventListener
	public static class SerialPortListener implements SerialPortEventListener {

		private DataAvailableListener mDataAvailableListener;

		//SerialPortListener这个自定义的类,初始化要有DataAvailableListener 这个自定义类的参数
		public SerialPortListener(DataAvailableListener mDataAvailableListener) {
			this.mDataAvailableListener = mDataAvailableListener;
		}
          
		//实现接口SerialPortEventListener中的方法读取从串口中接收的数据
		//SerialPortEvent 串行端口事件 
		public void serialEvent(SerialPortEvent serialPortEvent) {
			switch (serialPortEvent.getEventType()) {
			case SerialPortEvent.DATA_AVAILABLE: // 1.串口存在有效数据
				if (mDataAvailableListener != null) {
					// 调用自定义类的函数处理 数据 
					mDataAvailableListener.dataAvailable();
				}
				break;

			case SerialPortEvent.OUTPUT_BUFFER_EMPTY: // 2.输出缓冲区已清空
				break;

			case SerialPortEvent.CTS: // 3.清除待发送数据
				break;

			case SerialPortEvent.DSR: // 4.待发送数据准备好了
				break;

			case SerialPortEvent.RI: // 5.振铃指示
				break;

			case SerialPortEvent.CD: // 6.载波检测
				break;

			case SerialPortEvent.OE: // 7.溢位(溢出)错误
				break;

			case SerialPortEvent.PE: // 8.奇偶校验错误
				break;

			case SerialPortEvent.FE: // 9.帧错误
				break;

			case SerialPortEvent.BI: // 10.通讯中断
				ShowUtils.errorMessage("与串口设备通讯中断");
				break;

			default:
				break;
			}
		}
	}

	/**
	 * 串口存在有效数据监听
	 */
	public interface DataAvailableListener {
		/**
		 * 串口存在有效数据
		 */
		void dataAvailable();//被外面调用时候 重载的
	}
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值