最近一门课要求编写一个上位机串口通信工具,我基于Java编写了一个带有图形界面的简单串口通信工具,下面详述一下过程,供大家参考 ^_^
一:
首先,你需要下载一个额外的支持Java串口通信操作的jar包,由于java.comm比较老了,而且不支持64位系统,这里推荐Rxtx这个jar包(32位/64位均支持)。
官方下载地址:http://fizzed.com/oss/rxtx-for-java (注:可能需要FQ才能下载)
不能FQ的童鞋,可以在这里下载:
http://xiazai.jb51.net/201612/yuanma/javamfzrxtx(jb51.net).rar(32位)
http://xiazai.jb51.net/201612/yuanma/javamfzrxtx(jb51.net).rar(64位)
二:
下载解压jar包并在 Java Build Path 下引入:
捕获
注:如果运行过程中抛出 java.lang.UnsatisfiedlinkError 错误,请将rxtx解压包中的 rxtxParallel.dll,rxtxSerial.dll 这两个文件复制到 C:WindowsSystem32 目录下即可解决该错误。
三:
关于该jar包的使用,我写了一个SerialTool.java类,该类提供关于串口通信的各简单服务,代码如下(注意该类位于 serialPort 包里):
package serialPort;
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 gnu.io.CommPort;
import gnu.io.CommPortIdentifier;
import gnu.io.NoSuchPortException;
import gnu.io.PortInUseException;
import gnu.io.SerialPort;
import gnu.io.SerialPortEventListener;
import gnu.io.UnsupportedCommOperationException;
import serialException.*;
public class SerialTool {
private static SerialTool serialTool = null;
static {
//在该类被ClassLoader加载时就初始化一个SerialTool对象
if (serialTool == null) {
serialTool = new SerialTool();
}
}
//私有化SerialTool类的构造方法,不允许其他类生成SerialTool对象
private SerialTool() {}
public static SerialTool getSerialTool() {
if (serialTool == null) {
serialTool = new SerialTool();
}
return serialTool;
}
public static final ArrayList findPort() {
//获得当前所有可用串口
Enumeration portList = CommPortIdentifier.getPortIdentifiers();
ArrayList portNameList = new ArrayList<>();
//将可用串口名添加到List并返回该List
while (portList.hasMoreElements()) {
String portName = portList.nextElement().getName();
portNameList.add(portName);
}
return portNameList;
}
public static final SerialPort openPort(String portName, int baudrate) throws SerialPortParameterFailure, NotASerialPort, NoSuchPort, PortInUse {
try {
//通过端口名识别端口
CommPortIdentifier portIdentifier = CommPortIdentifier.getPortIdentifier(portName);
//打开端口,并给端口名字和一个timeout(打开操作的超时时间)
CommPort commPort = portIdentifier.open(portName, 2000);
//判断是不是串口
if (commPort instanceof SerialPort) {
SerialPort serialPort = (SerialPort) commPort;
try {
//设置一下串口的波特率等参数
serialPort.setSerialPortParams(baudrate, SerialPort.DATABITS_8, SerialPort.STOPBITS_1, SerialPort.PARITY_NONE);
} catch (UnsupportedCommOperationException e) {
throw new SerialPortParameterFailure();
}
//System.out.println("Open " + portName + " sucessfully !");
return serialPort;
}
else {
//不是串口
throw new NotASerialPort();
}
} catch (NoSuchPortException e1) {
throw new NoSuchPort();
} catch (PortInUseException e2) {
throw new PortInUse();
}
}
public static void closePort(SerialPort serialPort) {
if (serialPort != null) {
serialPort.close();
serialPort = null;
}
}
public static void sendToPort(SerialPort serialPort, byte[] order) throws SendDataToSerialPortFailure, SerialPortOutputStreamCloseFailure {
OutputStream out = null;
try {
out = serialPort.getOutputStream();
out.write(order);
out.flush();
} catch (IOException e) {
throw new SendDataToSerialPortFailure();
} finally {
try {
if (out != null) {
out.close();
out = null;
}
} catch (IOException e) {
throw new SerialPortOutputStreamCloseFailure();
}
}
}
public static byte[] readFromPort(SerialPort serialPort) throws ReadDataFromSerialPortFailure, SerialPortInputStreamCloseFailure {
InputStream in = null;
byte[] bytes = null;
try {
in = serialPort.getInputStream();
int bufflenth = in.available(); //获取buffer里的数据长度
while (bufflenth != 0) {
bytes = new byte[bufflenth]; //初始化byte数组为buffer中数据的长度
in.read(bytes);
bufflenth = in.available();
}
} catch (IOException e) {
throw new ReadDataFromSerialPortFailure();
} finally {
try {
if (in != null) {
in.close();
in = null;
}
} catch(IOException e) {
throw new SerialPortInputStreamCloseFailure();
}
}
return bytes;
}
public static void addListener(SerialPort port, SerialPortEventListener listener) throws TooManyListeners {
try {
//给串口添加监听器
port.addEventListener(listener);
//设置当有数据到达时唤醒监听接收线程
port.notifyonDataAvailable(true);
//设置当通信中断时唤醒中断线程
port.notifyonBreakInterrupt(true);
} catch (TooManyListenersException e) {
throw new TooManyListeners();
}
}
}
注:该类方法中 throw 的 Exception 都是我自定义的 Exception,之所以这么做是为了方便在主程序中进行相应处理,下面贴其中一个Exception出来给大家做下说明:
(注意我所有自定义的 Exception 都放在 serialException 包里)
package serialException;
public class SerialPortParameterFailure extends Exception {
private static final long serialVersionUID = 1L;
public SerialPortParameterFailure() {}
@Override
public String toString() {
return "设置串口参数失败!打开串口操作未完成!";
}
}
每个自定义的Exception类我都重写了它的 toString() 方法,便于主程序捕捉到该Exception后打印对应的错误信息
其中在serialException包里还有一个专门将接收到的Exception对象内的错误信息提取出来转换成字符串并返回的类,代码如下:
package serialException;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
public class ExceptionWriter {
public static String getErrorInfoFromException(Exception e) {
StringWriter sw = null;
PrintWriter pw = null;
try {
sw = new StringWriter();
pw = new PrintWriter(sw);
e.printStackTrace(pw);
return "rn" + sw.toString() + "rn";
} catch (Exception e2) {
return "出错啦!未获取到错误信息,请检查后重试!";
} finally {
try {
if (pw != null) {
pw.close();
}
if (sw != null) {
sw.close();
}
} catch (IOException e1) {
e1.printStackTrace();
}
}
}
}
四:
主程序类的使用,Client.java里含有程序的入口地址(main方法),它的作用是显示一个欢迎界面并调用DataView.java这个类进行实际的串口数据显示。
Client.java代码如下:
package serialPort;
import java.awt.Color;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.frame;
import java.awt.Graphics;
import java.awt.GridLayout;
import java.awt.Image;
import java.awt.Label;
import java.awt.Panel;
import java.awt.Toolkit;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import javax.swing.JOptionPane;
import serialException.ExceptionWriter;
public class Client extends frame{
private static final long serialVersionUID = 1L;
public static final int WIDTH = 800;
public static final int HEIGHT = 620;
public static final int LOC_X = 200;
public static final int LOC_Y = 70;
Color color = Color.WHITE;
Image offScreen = null; //用于双缓冲
//设置window的icon(这里我自定义了一下Windows窗口的icon图标,因为实在觉得哪个小咖啡图标不好看 = =)
Toolkit toolKit = getToolkit();
Image icon = toolKit.getImage(Client.class.getResource("computer.png"));
//持有其他类
DataView dataview = new DataView(this); //主界面类(显示监控数据主面板)
public static void main(String[] args) {
new Client().launchframe();
}
public void launchframe() {
this.setBounds(LOC_X, LOC_Y, WIDTH, HEIGHT); //设定程序在桌面出现的位置
this.setTitle("CDIO工程项目"); //设置程序标题
this.setIconImage(icon);
this.setBackground(Color.white); //设置背景色
this.addWindowListener(new WindowAdapter() {
//添加对窗口状态的监听
public void windowClosing(WindowEvent arg0) {
//当窗口关闭时
System.exit(0); //退出程序
}
});
this.addKeyListener(new KeyMonitor()); //添加键盘监听器
this.setResizable(false); //窗口大小不可更改
this.setVisible(true); //显示窗口
new Thread(new RepaintThread()).start(); //开启重画线程
}
public void paint(Graphics g) {
Color c = g.getColor();
g.setFont(new Font("微软雅黑", Font.BOLD, 40));
g.setColor(Color.black);
g.drawString("欢迎使用上位机实时监控系统", 45, 190);
g.setFont(new Font("微软雅黑", Font.ITALIC, 26));
g.setColor(Color.BLACK);
g.drawString("Version:1.0 Powered By:ZhongLei", 280, 260);
g.setFont(new Font("微软雅黑", Font.BOLD, 30));
g.setColor(color);
g.drawString("————点击Enter键进入主界面————", 100, 480);
//使文字 "————点击Enter键进入主界面————" 黑白闪烁
if (color == Color.WHITE) color = Color.black;
else if (color == color.BLACK) color = Color.white;
}
public void update(Graphics g) {
if (offScreen == null) offScreen = this.createImage(WIDTH, HEIGHT);
Graphics gOffScreen = offScreen.getGraphics();
Color c = gOffScreen.getColor();
gOffScreen.setColor(Color.white);
gOffScreen.fillRect(0, 0, WIDTH, HEIGHT); //重画背景画布
this.paint(gOffScreen); //重画界面元素
gOffScreen.setColor(c);
g.drawImage(offScreen, 0, 0, null); //将新画好的画布“贴”在原画布上
}
private class KeyMonitor extends KeyAdapter {
public void keyReleased(KeyEvent e) {
int keyCode = e.getKeyCode();
if (keyCode == KeyEvent.VK_ENTER) { //当监听到用户敲击键盘enter键后执行下面的操作
setVisible(false); //隐去欢迎界面
dataview.setVisible(true); //显示监测界面
dataview.dataframe(); //初始化监测界面
}
}
}
private class RepaintThread implements Runnable {
public void run() {
while(true) {
repaint();
try {
Thread.sleep(250);
} catch (InterruptedException e) {
//重画线程出错抛出异常时创建一个Dialog并显示异常详细信息
String err = ExceptionWriter.getErrorInfoFromException(e);
JOptionPane.showMessageDialog(null, err, "错误", JOptionPane.INFORMATION_MESSAGE);
System.exit(0);
}
}
}
}
}
运行截图:
注:实际运行过程中最下面的“点击Enter键进入主界面”有一个一闪一闪的效果(是通过每隔一段时间重画一次界面,让这句话以白黑两色反复交替出现实现的),双缓冲方式利于解决重画时界面闪烁的问题(如果不使用双缓冲方式的话相当于每次重画时是在旧界面上一点一点画上新东西,而双缓冲实质上是通过先在内存中直接画好一张新界面图,然后一次性直接用新界面覆盖掉旧界面)
DataView.java代码如下:(该类用于实时显示串口数据)
简单说明:
硬件设备每隔一段时间通过串口发送一次数据到计算机,该串口工具成功连接至硬件设备并添加监听后,会在每次接收到数据时解析数据并更新界面;
你在使用时很可能需求跟我不一样,该类仅供参考,实际使用中你很可能需要重新制作数据显示界面以及数据解析方式
package serialPort;
import java.awt.Button;
import java.awt.Choice;
import java.awt.Color;
import java.awt.Font;
import java.awt.frame;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Label;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.util.List;
import java.util.TooManyListenersException;
import javax.swing.JOptionPane;
import gnu.io.SerialPort;
import gnu.io.SerialPortEvent;
import gnu.io.SerialPortEventListener;
import serialException.*;
public class DataView extends frame {
private static final long serialVersionUID = 1L;
Client client = null;
private List commList = null; //保存可用端口号
private SerialPort serialPort = null; //保存串口对象
private Font font = new Font("微软雅黑", Font.BOLD, 25);
private Label tem = new Label("暂无数据", Label.CENTER); //温度
private Label hum = new Label("暂无数据", Label.CENTER); //湿度
private Label pa = new Label("暂无数据", Label.CENTER); //压强
private Label rain = new Label("暂无数据", Label.CENTER); //雨量
private Label win_sp = new Label("暂无数据", Label.CENTER); //风速
private Label win_dir = new Label("暂无数据", Label.CENTER); //风向
private Choice commChoice = new Choice(); //串口选择(下拉框)
private Choice bpsChoice = new Choice(); //波特率选择
private Button openSerialButton = new Button("打开串口");
Image offScreen = null; //重画时的画布
//设置window的icon
Toolkit toolKit = getToolkit();
Image icon = toolKit.getImage(DataView.class.getResource("computer.png"));
public DataView(Client client) {
this.client = client;
commList = SerialTool.findPort(); //程序初始化时就扫描一次有效串口
}
public void dataframe() {
this.setBounds(client.LOC_X, client.LOC_Y, client.WIDTH, client.HEIGHT);
this.setTitle("CDIO工程项目");
this.setIconImage(icon);
this.setBackground(Color.white);
this.setLayout(null);
this.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent arg0) {
if (serialPort != null) {
//程序退出时关闭串口释放资源
SerialTool.closePort(serialPort);
}
System.exit(0);
}
});
tem.setBounds(140, 103, 225, 50);
tem.setBackground(Color.black);
tem.setFont(font);
tem.setForeground(Color.white);
add(tem);
hum.setBounds(520, 103, 225, 50);
hum.setBackground(Color.black);
hum.setFont(font);
hum.setForeground(Color.white);
add(hum);
pa.setBounds(140, 193, 225, 50);
pa.setBackground(Color.black);
pa.setFont(font);
pa.setForeground(Color.white);
add(pa);
rain.setBounds(520, 193, 225, 50);
rain.setBackground(Color.black);
rain.setFont(font);
rain.setForeground(Color.white);
add(rain);
win_sp.setBounds(140, 283, 225, 50);
win_sp.setBackground(Color.black);
win_sp.setFont(font);
win_sp.setForeground(Color.white);
add(win_sp);
win_dir.setBounds(520, 283, 225, 50);
win_dir.setBackground(Color.black);
win_dir.setFont(font);
win_dir.setForeground(Color.white);
add(win_dir);
//添加串口选择选项
commChoice.setBounds(160, 397, 200, 200);
//检查是否有可用串口,有则加入选项中
if (commList == null || commList.size()<1) {
JOptionPane.showMessageDialog(null, "没有搜索到有效串口!", "错误", JOptionPane.INFORMATION_MESSAGE);
}
else {
for (String s : commList) {
commChoice.add(s);
}
}
add(commChoice);
//添加波特率选项
bpsChoice.setBounds(526, 396, 200, 200);
bpsChoice.add("1200");
bpsChoice.add("2400");
bpsChoice.add("4800");
bpsChoice.add("9600");
bpsChoice.add("14400");
bpsChoice.add("19200");
bpsChoice.add("115200");
add(bpsChoice);
//添加打开串口按钮
openSerialButton.setBounds(250, 490, 300, 50);
openSerialButton.setBackground(Color.lightGray);
openSerialButton.setFont(new Font("微软雅黑", Font.BOLD, 20));
openSerialButton.setForeground(Color.darkGray);
add(openSerialButton);
//添加打开串口按钮的事件监听
openSerialButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
//获取串口名称
String commName = commChoice.getSelectedItem();
//获取波特率
String bpsStr = bpsChoice.getSelectedItem();
//检查串口名称是否获取正确
if (commName == null || commName.equals("")) {
JOptionPane.showMessageDialog(null, "没有搜索到有效串口!", "错误", JOptionPane.INFORMATION_MESSAGE);
}
else {
//检查波特率是否获取正确
if (bpsStr == null || bpsStr.equals("")) {
JOptionPane.showMessageDialog(null, "波特率获取错误!", "错误", JOptionPane.INFORMATION_MESSAGE);
}
else {
//串口名、波特率均获取正确时
int bps = Integer.parseInt(bpsStr);
try {
//获取指定端口名及波特率的串口对象
serialPort = SerialTool.openPort(commName, bps);
//在该串口对象上添加监听器
SerialTool.addListener(serialPort, new SerialListener());
//监听成功进行提示
JOptionPane.showMessageDialog(null, "监听成功,稍后将显示监测数据!", "提示", JOptionPane.INFORMATION_MESSAGE);
} catch (SerialPortParameterFailure | NotASerialPort | NoSuchPort | PortInUse | TooManyListeners e1) {
//发生错误时使用一个Dialog提示具体的错误信息
JOptionPane.showMessageDialog(null, e1, "错误", JOptionPane.INFORMATION_MESSAGE);
}
}
}
}
});
this.setResizable(false);
new Thread(new RepaintThread()).start(); //启动重画线程
}
public void paint(Graphics g) {
Color c = g.getColor();
g.setColor(Color.black);
g.setFont(new Font("微软雅黑", Font.BOLD, 25));
g.drawString(" 温度: ", 45, 130);
g.setColor(Color.black);
g.setFont(new Font("微软雅黑", Font.BOLD, 25));
g.drawString(" 湿度: ", 425, 130);
g.setColor(Color.black);
g.setFont(new Font("微软雅黑", Font.BOLD, 25));
g.drawString(" 压强: ", 45, 220);
g.setColor(Color.black);
g.setFont(new Font("微软雅黑", Font.BOLD, 25));
g.drawString(" 雨量: ", 425, 220);
g.setColor(Color.black);
g.setFont(new Font("微软雅黑", Font.BOLD, 25));
g.drawString(" 风速: ", 45, 310);
g.setColor(Color.black);
g.setFont(new Font("微软雅黑", Font.BOLD, 25));
g.drawString(" 风向: ", 425, 310);
g.setColor(Color.gray);
g.setFont(new Font("微软雅黑", Font.BOLD, 20));
g.drawString(" 串口选择: ", 45, 410);
g.setColor(Color.gray);
g.setFont(new Font("微软雅黑", Font.BOLD, 20));
g.drawString(" 波特率: ", 425, 410);
}
public void update(Graphics g) {
if (offScreen == null) offScreen = this.createImage(Client.WIDTH, Client.HEIGHT);
Graphics gOffScreen = offScreen.getGraphics();
Color c = gOffScreen.getColor();
gOffScreen.setColor(Color.white);
gOffScreen.fillRect(0, 0, Client.WIDTH, Client.HEIGHT); //重画背景画布
this.paint(gOffScreen); //重画界面元素
gOffScreen.setColor(c);
g.drawImage(offScreen, 0, 0, null); //将新画好的画布“贴”在原画布上
}
private class RepaintThread implements Runnable {
public void run() {
while(true) {
//调用重画方法
repaint();
//扫描可用串口
commList = SerialTool.findPort();
if (commList != null && commList.size()>0) {
//添加新扫描到的可用串口
for (String s : commList) {
//该串口名是否已存在,初始默认为不存在(在commList里存在但在commChoice里不存在,则新添加)
boolean commExist = false;
for (int i=0; i
运行截图:
整个项目源码打包下载:http://xiazai.jb51.net/201612/yuanma/javaserialMonitor(jb51.net).rar
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持考高分网。



