Java程序与RSR232串口通讯小练手

一直以来都是在学习J2EE方面的应用系统开发,从未想过用JAVA来编写硬件交互程序,不过自己就是喜欢尝试一些未曾接触的新东西。在网上搜索了些资源,了解到JAVA写串口通讯的还是蛮多的,那么便着手准备开发调试环境。软件程序开发环境搭建不成问题,可这硬件环境就有点犯难啦。更何况自己用的是笔记本哪来的串口呀,再说要是真拿这串口硬件来自己也不会弄,随即想到了虚拟机,觉得这东西应该也有虚拟的吧,果真跟自己的猜测一样还真有这东西,顺便也下载了个串口小助手做为调试之用。

下面就先看看软件环境的搭建:

  • 下载comm.jarwin32com.dlljavax.comm.properties。 (附件提供下载)
  • 介绍:comm.jar提供了通讯用的java API,win32com.dll提供了供comm.jar调用的本地驱动接口,javax.comm.properties是这个驱动的类配置文件
  • 拷贝javacomm.jarX:\jre\lib\ext目录下面;
  • 拷贝javax.comm.propertiesX:\jre\lib目录下面;
  • 拷贝win32com.dllX:\jre\bin目录下面;
  • 更新下IDE里面的JDK环境,如下图:

java-hard-rsr232-1

接着是硬件虚拟环境安装虚拟串口,这里我用的是VSPD6.0(附件提供下载),安装好后启动VSPD添加我们所需要的端口,注意这里是按组的方式添加的,例如COM1和COM2是一组同时添加,以此类推。

所有环境都准备好后,先来简单认识下comm.jar的内容。单从comm API的javadoc来看,SUM提供给我们的只有区区以下13个类或接口,具体如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10

javax.comm.CommDriver
javax.comm.CommPort javax.comm.ParallelPort
javax.comm.SerialPort javax.comm.CommPortIdentifier
javax.comm.CommPortOwnershipListener
javax.comm.ParallelPortEvent javax.comm.SerialPortEvent
javax.comm.ParallelPortEventListener (extends java.util.EventListener)
javax.comm.SerialPortEventListener (extends java.util.EventListener)
javax.comm.NoSuchPortException javax.comm.PortInUseException
javax.comm.UnsupportedCommOperationException

这些类和接口命名一看便知其意,就不做一一介绍啦,可以到官网或网上找到更详细的信息。下面先测试下所搭建的环境是否可用,主要代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10

Enumeration<?> en = CommPortIdentifier.getPortIdentifiers();
CommPortIdentifier portId;
while (en.hasMoreElements()) {
	portId = (CommPortIdentifier) en.nextElement();
	// 如果端口类型是串口,则打印出其端口信息
	if (portId.getPortType() == CommPortIdentifier.PORT_SERIAL) {
		System.out.println(portId.getName());
	}
}

运行代码后,控制台有输出正确的端口(如下图),说明所有环境正常可进行下步工作,否则请检查。

java-hard-rsr232-2

最后要解决的就是与串口数据交互的问题。在这个问题上,最主要的难点就是数据读取,因为我们不知道端口什么时候会有数据到来,也不知数据长度如何。通常,串口通信应用程序有两种模式,一种是实现SerialPortEventListener接口,监听各种串口事件并作相应处理;另一种就是建立一个独立的接收线程专门负责数据的接收。参考众多老前辈的代码后,下面就采用第一种方式写了个简单的助手程序,具体的实现请看详细代码,如下:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433

package com.elkan1788.view;

import java.awt.BorderLayout;
import java.awt.Button;
import java.awt.Color;
import java.awt.Font;
import java.awt.GridLayout;
import java.awt.Image;
import java.awt.TextArea;
import java.awt.TextField;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.TooManyListenersException;

import javax.comm.CommPortIdentifier;
import javax.comm.NoSuchPortException;
import javax.comm.PortInUseException;
import javax.comm.SerialPort;
import javax.comm.SerialPortEvent;
import javax.comm.SerialPortEventListener;
import javax.comm.UnsupportedCommOperationException;
import javax.imageio.ImageIO;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.SwingConstants;
import javax.swing.border.EmptyBorder;

public class JavaRs232 extends JFrame implements ActionListener, SerialPortEventListener {

	/**
	 * JDK Serial Version UID
	 */
	private static final long serialVersionUID = -7270865686330790103L;

	protected int WIN_WIDTH = 380;

	protected int WIN_HEIGHT = 300;

	private JComboBox<?> portCombox, rateCombox, dataCombox, stopCombox, parityCombox;

	private Button openPortBtn, closePortBtn, sendMsgBtn;

	private TextField sendTf;

	private TextArea readTa;

	private JLabel statusLb;

	private String portname, rate, data, stop, parity;

	protected CommPortIdentifier portId;

	protected Enumeration<?> ports;

	protected List<String> portList;

	protected SerialPort serialPort;

	protected OutputStream outputStream = null;

	protected InputStream inputStream = null;

	protected String mesg;

	protected int sendCount, reciveCount;

    /**
     * 默认构造函数
     */
	public JavaRs232() {
		super("Java RS-232串口通信测试程序   凡梦星尘");
		setSize(WIN_WIDTH, WIN_HEIGHT);
		setLocationRelativeTo(null);
		Image icon = null;
		try {
			icon = ImageIO.read(JavaRs232.class.getResourceAsStream("/res/rs232.png"));
		} catch (IOException e) {
			showErrMesgbox(e.getMessage());
		}
		setIconImage(icon);
		setResizable(false);
		scanPorts();
		initComponents();
		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		setVisible(true);
	}

	/**
	 * 初始化各UI组件
	 * @since 2012-3-22 下午11:56:39
	 */
	public void initComponents() {
		// 共用常量
		Font lbFont = new Font("微软雅黑", Font.TRUETYPE_FONT, 14);

		// 创建左边面板
		JPanel northPane = new JPanel();
		northPane.setLayout(new GridLayout(1, 1));
		// 设置左边面板各组件
		JPanel leftPane = new JPanel();
		leftPane.setOpaque(false);
		leftPane.setLayout(new GridLayout(3,2));
		JLabel portnameLb = new JLabel("串口号:");
		portnameLb.setFont(lbFont);
		portnameLb.setHorizontalAlignment(SwingConstants.RIGHT);
		portCombox = new JComboBox<String>((String [])portList.toArray(new String[0]));
		portCombox.addActionListener(this);
		JLabel databitsLb = new JLabel("数据位:");
		databitsLb.setFont(lbFont);
		databitsLb.setHorizontalAlignment(SwingConstants.RIGHT);
		dataCombox = new JComboBox<Integer>(new Integer[]{5, 6, 7, 8});
		dataCombox.setSelectedIndex(3);
		dataCombox.addActionListener(this);
		JLabel parityLb = new JLabel("校验位:");
		parityLb.setFont(lbFont);
		parityLb.setHorizontalAlignment(SwingConstants.RIGHT);
		parityCombox = new JComboBox<String>(new String[]{"NONE","ODD","EVEN","MARK","SPACE"});
		parityCombox.addActionListener(this);
		// 添加组件至面板
		leftPane.add(portnameLb);
		leftPane.add(portCombox);
		leftPane.add(databitsLb);
		leftPane.add(dataCombox);
		leftPane.add(parityLb);
		leftPane.add(parityCombox);

		//创建右边面板
		JPanel rightPane = new JPanel();
		rightPane.setLayout(new GridLayout(3,2));
		// 设置右边面板各组件
		JLabel baudrateLb = new JLabel("波特率:");
		baudrateLb.setFont(lbFont);
		baudrateLb.setHorizontalAlignment(SwingConstants.RIGHT);
		rateCombox = new JComboBox<Integer>(new Integer[]{2400,4800,9600,14400,19200,38400,56000});
		rateCombox.setSelectedIndex(2);
		rateCombox.addActionListener(this);
		JLabel stopbitsLb = new JLabel("停止位:");
		stopbitsLb.setFont(lbFont);
		stopbitsLb.setHorizontalAlignment(SwingConstants.RIGHT);
		stopCombox = new JComboBox<String>(new String[]{"1","2","1.5"});
		stopCombox.addActionListener(this);
		openPortBtn = new Button("打开端口");
		openPortBtn.addActionListener(this);
		closePortBtn = new Button("关闭端口");
		closePortBtn.addActionListener(this);
		// 添加组件至面板
		rightPane.add(baudrateLb);
		rightPane.add(rateCombox);
		rightPane.add(stopbitsLb);
		rightPane.add(stopCombox);
		rightPane.add(openPortBtn);
		rightPane.add(closePortBtn);
		// 将左右面板组合添加到北边的面板
		northPane.add(leftPane);
		northPane.add(rightPane);

		// 创建中间面板
		JPanel centerPane = new JPanel();
		// 设置中间面板各组件
		sendTf = new TextField(42);
		readTa = new TextArea(8,50);
		readTa.setEditable(false);
		readTa.setBackground(new Color(225,242,250));
		centerPane.add(sendTf);
		sendMsgBtn = new Button(" 发送 ");
		sendMsgBtn.addActionListener(this);
		// 添加组件至面板
		centerPane.add(sendTf);
		centerPane.add(sendMsgBtn);
		centerPane.add(readTa);

		// 设置南边组件
		statusLb = new JLabel();
		statusLb.setText(initStatus());
		statusLb.setOpaque(true);

		// 获取主窗体的容器,并将以上三面板以北、中、南的布局整合
		JPanel contentPane = (JPanel)getContentPane();
		contentPane.setLayout(new BorderLayout());
		contentPane.setBorder(new EmptyBorder(0, 0, 0, 0));
		contentPane.setOpaque(false);
		contentPane.add(northPane, BorderLayout.NORTH);
		contentPane.add(centerPane, BorderLayout.CENTER);
		contentPane.add(statusLb, BorderLayout.SOUTH);
	}

	/**
	 * 初始化状态标签显示文本
	 * @return String
	 * @since 2012-3-23 上午12:01:53
	 */
	public String initStatus() {
		portname = portCombox.getSelectedItem().toString();
		rate = rateCombox.getSelectedItem().toString();
		data = dataCombox.getSelectedItem().toString();
		stop = stopCombox.getSelectedItem().toString();
		parity = parityCombox.getSelectedItem().toString();

		StringBuffer str = new StringBuffer("当前串口号:");
		str.append(portname).append(" 波特率:");
		str.append(rate).append(" 数据位:");
		str.append(data).append(" 停止位:");
		str.append(stop).append(" 校验位:");
		str.append(parity);
		return str.toString();
	}

	/**
	 * 扫描本机的所有COM端口
	 * @since 2012-3-23 上午12:02:42
	 */
	public void scanPorts() {
		portList = new ArrayList<String>();
		Enumeration<?> en = CommPortIdentifier.getPortIdentifiers();
		CommPortIdentifier portId;
		while(en.hasMoreElements()){
			portId = (CommPortIdentifier) en.nextElement();
			if(portId.getPortType() == CommPortIdentifier.PORT_SERIAL){
				String name = portId.getName();
				if(!portList.contains(name)) {
					portList.add(name);
				}
			}
		}
		if(null == portList
				|| portList.isEmpty()) {
			showErrMesgbox("未找到可用的串行端口号,程序无法启动!");
			System.exit(0);
		}
	}

	/**
	 * 打开串行端口
	 * @since 2012-3-23 上午12:03:07
	 */
	public void openSerialPort() {
		// 获取要打开的端口
		try {
			portId = CommPortIdentifier.getPortIdentifier(portname);
		} catch (NoSuchPortException e) {
			showErrMesgbox("抱歉,没有找到"+portname+"串行端口号!");
			setComponentsEnabled(true);
			return ;
		}
		// 打开端口
		try {
			serialPort = (SerialPort) portId.open("JavaRs232", 2000);
			statusLb.setText(portname+"串口已经打开!");
		} catch (PortInUseException e) {
			showErrMesgbox(portname+"端口已被占用,请检查!");
			setComponentsEnabled(true);
			return ;
		}

		// 设置端口参数
		try {
			int rate = Integer.parseInt(this.rate);
			int data = Integer.parseInt(this.data);
			int stop = stopCombox.getSelectedIndex()+1;
			int parity = parityCombox.getSelectedIndex();
			serialPort.setSerialPortParams(rate,data,stop,parity);
		} catch (UnsupportedCommOperationException e) {
			showErrMesgbox(e.getMessage());
		}

		// 打开端口的IO流管道
		try {
			outputStream = serialPort.getOutputStream();
			inputStream = serialPort.getInputStream();
		} catch (IOException e) {
			showErrMesgbox(e.getMessage());
		}

		// 给端口添加监听器
		try {
			serialPort.addEventListener(this);
		} catch (TooManyListenersException e) {
			showErrMesgbox(e.getMessage());
		}

		serialPort.notifyOnDataAvailable(true);
	}

	/**
	 * 给串行端口发送数据
	 * @since 2012-3-23 上午12:05:00
	 */
	public void sendDataToSeriaPort() {
		try {
			sendCount++;
			outputStream.write(mesg.getBytes());
			outputStream.flush();

		} catch (IOException e) {
			showErrMesgbox(e.getMessage());
		}

		statusLb.setText("  发送: "+sendCount+"                                      接收: "+reciveCount);
	}

	/**
	 * 关闭串行端口
	 * @since 2012-3-23 上午12:05:28
	 */
	public void closeSerialPort() {
		try {
			if(outputStream != null)
				outputStream.close();
			if(serialPort != null)
				serialPort.close();
			serialPort = null;
			statusLb.setText(portname+"串口已经关闭!");
			sendCount = 0;
			reciveCount = 0;
			sendTf.setText("");
			readTa.setText("");
		} catch (Exception e) {
			showErrMesgbox(e.getMessage());
		}
	}

	/**
	 * 显示错误或警告信息
	 * @param msg 信息
	 * @since 2012-3-23 上午12:05:47
	 */
	public void showErrMesgbox(String msg) {
		JOptionPane.showMessageDialog(this, msg);
	}

	/**
	 * 各组件行为事件监听
	 */
	public void actionPerformed(ActionEvent e) {
		if(e.getSource() == portCombox
				|| e.getSource() == rateCombox
				|| e.getSource() == dataCombox
				|| e.getSource() == stopCombox
				|| e.getSource() == parityCombox){
			statusLb.setText(initStatus());
		}
		if(e.getSource() == openPortBtn){
			setComponentsEnabled(false);
			openSerialPort();
		}
		if(e.getSource() == closePortBtn){
			if(serialPort != null){
				closeSerialPort();
			}
			setComponentsEnabled(true);
		}

		if(e.getSource() == sendMsgBtn){
			if(serialPort == null){
				showErrMesgbox("请先打开串行端口!");
				return ;
			}
			mesg = sendTf.getText();
			if(null == mesg || mesg.isEmpty()){
				showErrMesgbox("请输入你要发送的内容!");
				return ;
			}
			sendDataToSeriaPort();
		}
	}

	/**
	 * 端口事件监听
	 */
	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:
				byte[] readBuffer = new byte[50];

			try {
				while (inputStream.available() > 0) {
					inputStream.read(readBuffer);
				}
				StringBuilder receivedMsg = new StringBuilder("/-- ");
				receivedMsg.append(new String(readBuffer).trim()).append(" --/\n");
				readTa.append(receivedMsg.toString());
				reciveCount++;
				statusLb.setText("  发送: "+sendCount+"  接收: "+reciveCount);
			} catch (IOException e) {
				showErrMesgbox(e.getMessage());
			}
		}
	}

	/**
	 * 设置各组件的开关状态
	 * @param enabled 状态
	 * @since 2012-3-23 上午12:06:24
	 */
	public void setComponentsEnabled(boolean enabled) {
		openPortBtn.setEnabled(enabled);
		openPortBtn.setEnabled(enabled);
		portCombox.setEnabled(enabled);
		rateCombox.setEnabled(enabled);
		dataCombox.setEnabled(enabled);
		stopCombox.setEnabled(enabled);
		parityCombox.setEnabled(enabled);
	}

	/**
	 * 运行主函数
	 * @param args
	 * @since 2012-3-23 上午12:06:45
	 */
	public static void main(String[] args) {
		new JavaRs232();
	}
}

代码编写完成,按下F11键进入调试状态,一切运行正常良好,请看图:

  • 启动界面

java-hard-rsr232-3

  • 端口检测

java-hard-rsr232-4

  • 通讯测试

java-hard-rsr232-5

  • 最后再抽空来美化程序下,效果更漂亮

java-hard-rsr232-6

java-hard-rsr232-7

PS: 示例源下载