本程序是基于下面博客修改而来,添加了更多的代码注释和一些功能,具体环境安装看下面的微博
https://blog.csdn.net/kong_gu_you_lan/article/details/80589859#commentBox
本文的源码也请前 先去上面博客里面下载原作者的源码,然后 把文章最后列出的两个文件的内容覆盖一下就行了。
运行图如下:
-
整体架构
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发送数据两个函数了,没什么特殊的了。
-
用到的其他类
SerialPortManager 这个类是对RXTX提供的对串口操作函数的再一次封装,让我们在MainFrame类中的操作更加简单点。
ArrayUtils 这个类是对数组操作的类,在接收byte[] 数组时候需要用到里面的函数进行拼接
ByteUtils 这个类是16进制和byte数组的转换 -
关于收发数据的处理
如果选择了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
调试收到的数据就是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();//被外面调用时候 重载的
}
}