Java串口通信编程教程hao.docx
《Java串口通信编程教程hao.docx》由会员分享,可在线阅读,更多相关《Java串口通信编程教程hao.docx(27页珍藏版)》请在冰豆网上搜索。
Java串口通信编程教程hao
Win32 串口编程
(一)(2009-08-1616:
28:
13)
标签:
串口 重叠i/o
分类:
Windows
翻译自:
ms-help:
//MS.MSDNQTR.v80.chs/MS.MSDN.v80/MS.WIN32COM.v10.en/dnfiles/html/msdn_serial.htm
老外写的文章,虽比较全面,但很啰嗦,不如看各个函数的文档来得快。
为方便以后查阅,列出本文涉及的主要函数如下:
CreateFile、ReadFile、WriteFile、GetOverlappedResult、WaitForSingleObject
SetCommMask、WaitCommEvent
ClearCommError、GetCommModemStatus、EscapeCommFunction
GetCommState、BuildCommDCB、SetCommState、SetCommTimeouts
此外,新浪的博客系统限制文章最大长度为40000字节,只好把这篇文章分成几部分了。
0简介
本文仅关注在WindowsNT和95间兼容的API。
Windows95支持TelephonyAPI(TAPI),但WindowsNT3.x不支持TAPI,所以本文不讨论它。
本文的示例程序MTTTY(MultithreadedTTY)使用了三个线程:
一个进行内存管理的界面线程;控制所有写入操作的写入者线程;读取数据和处理端口状态改变的读取/状态线程。
示例采用了一些不同的堆来进行内存管理;还大量使用了同步方法来进行线程间通信。
1打开端口
使用CreateFile函数打开端口。
打开端口时有两种方法:
重叠的和非重叠的。
下列代码片段以重叠方式打开端口:
HANDLE hComm;
hComm = CreateFile( gszPort,
GENERIC_READ | GENERIC_WRITE,
0,
0,
OPEN_EXISTING,
FILE_FLAG_OVERLAPPED,
0);
if (hComm == INVALID_HANDLE_VALUE)
// error opening port; abort
Win32 串口编程
(二)(2009-08-1616:
39:
32)
标签:
串口 重叠i/o it
分类:
Windows
3串口状态
有两种获取通信端口状态的方法。
第一种方法是设置事件掩码,当指定事件发生时应用程序会收到通知。
SetCommMask函数用于设置事件掩码,WaitCommEvent用于等待指定的事件发生。
它们与16位Windows中的SetCommEventMask和EnableCommNotification类似,只是它们不发送WM_COMMNOTIFY消息。
第二种方法是不时地调用另一些状态函数来获取通信端口的状态。
当然,轮询是低效的,不建议使用。
3.1通信事件
通信事件在使用通信端口时可能随时发生。
接收通信事件需要两个步骤:
用SetCommMask设定需要接收通知的事件
用WaitCommEvent提交状态检查请求,请求可以是重叠的或者非重叠的,与读写操作一样。
下面是使用SetCommMask的示例:
DWORD dwStoredFlags;
dwStoredFlags = EV_BREAK | EV_CTS | EV_DSR | EV_ERR | EV_RING |
EV_RLSD | EV_RXCHAR | EV_RXFLAG | EV_TXEMPTY ;
if (!
SetCommMask(hComm, dwStoredFlags))
// error setting communications mask
下表描述了每种事件类型。
事件标志
描述
EV_BREAK
检测到输入中的break
EV_CTS
CTS(ClearToSend)信号状态改变。
要取得CTS线路状态,应使用GetCommModemStatus函数。
EV_DSR
DSR(DataSetReady)信号状态改变。
要取得DSR线路状态,应使用GetCommModemStatus函数。
EV_ERR
某线路状态错误发生。
线路状态错误包括CE_FRAME、CE_OVERRUN和CE_RXPARITY。
要取得具体错误种类,需调用ClearCommError函数。
EV_RING
检测到振铃指示
EV_RLSD
RLSD(ReceiveLineSignalDetect)信号状态改变。
要取得RLSD线路状态,需调用GetCommModemStatus函数。
注意,RLSD通常被称作CD(carrierdetect)。
EV_RXCHAR
接收到一个字符并且已放入输入缓冲区。
请参考下面的“警告”节对此标志的详细讨论。
EV_RXFLAG
接收到一个事件字符并且已放入输入缓冲区。
事件字符由下文讨论的DCB结构EvtChar字段指定。
下面的“警告”节也讨论了这个标志。
EV_TXEMPTY
输出缓冲区中最后一个字符被发送到串口设备了。
如果使用硬件缓冲区,此标志仅表示所有数据已经发送到硬件了。
如果不与设备驱动交互,是无法确定硬件缓冲区空的。
指定事件掩码后,使用WaitCommEvent函数检测事件发生。
如果以非重叠方式打开端口,则WaitCommEvent不需要OVERLAPPED结构体,函数阻塞调用线程直到某事件发生。
如果没有事件发生,调用线程将无限阻塞。
下面的代码片段展示了如何在以非重叠方式打开的端口上等待EV_RING事件。
DWORD dwCommEvent;
if (!
SetCommMask(hComm, EV_RING))
// Error setting communications mask
return FALSE;
if (!
WaitCommEvent(hComm, &dwCommEvent, NULL))
// An error occurred waiting for the event.
return FALSE;
else
// Event has occurred.
return TRUE;
如果没有事件发生,上面的代码将无限阻塞调用线程。
解决方法是以重叠方式打开端口,用下面的方式等待状态事件:
#define STATUS_CHECK_TIMEOUT 500 // Milliseconds
DWORD dwRes;
DWORD dwCommEvent;
DWORD dwStoredFlags;
BOOL fWaitingOnStat = FALSE;
OVERLAPPED osStatus = {0};
dwStoredFlags = EV_BREAK | EV_CTS | EV_DSR | EV_ERR | EV_RING |\
EV_RLSD | EV_RXCHAR | EV_RXFLAG | EV_TXEMPTY ;
if (!
SetCommMask(comHandle, dwStoredFlags))
// error setting communications mask; abort
return 0;
osStatus.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
if (osStatus.hEvent == NULL)
// error creating event; abort
return 0;
for ( ; ; ) {
// Issue a status event check if one hasn't been issued already.
if (!
fWaitingOnStat) {
if (!
WaitCommEvent(hComm, &dwCommEvent, &osStatus)) {
if (GetLastError() == ERROR_IO_PENDING)
bWaitingOnStatusHandle = TRUE;
else
// error in WaitCommEvent; abort
break;
}
else
// WaitCommEvent returned immediately.
// Deal with status event as appropriate.
ReportStatusEvent(dwCommEvent);
}
// Check on overlapped operation.
if (fWaitingOnStat) {
// Wait a little while for an event to occur.
dwRes = WaitForSingleObject(osStatus.hEvent, STATUS_CHECK_TIMEOUT);
switch(dwRes)
{
// Event occurred.
case WAIT_OBJECT_0:
if (!
GetOverlappedResult(hComm, &osStatus, &dwOvRes, FALSE))
// An error occurred in the overlapped operation;
// call GetLastError to find out what it was
// and abort if it is fatal.
else
// Status event is stored in the event flag
// specified in the original WaitCommEvent call.
// Deal with the status event as appropriate.
ReportStatusEvent(dwCommEvent);
// Set fWaitingOnStat flag to indicate that a new
// WaitCommEvent is to be issued.
fWaitingOnStat = FALSE;
break;
case WAIT_TIMEOUT:
// Operation isn't complete yet. fWaitingOnStatusHandle flag
// isn't changed since I'll loop back around and I don't want
// to issue another WaitCommEvent until the first one finishes.
//
// This is a good time to do some background work.
DoBackgroundWork();
break;
default:
// Error in the WaitForSingleObject; abort
// This indicates a problem with the OVERLAPPED structure's
// event handle.
CloseHandle(osStatus.hEvent);
return 0;
}
}
}
CloseHandle(osStatus.hEvent);
上面的代码片段与重叠读取操作的代码非常相似。
实际上,MTTTY使用WaitForMultipleObjects在同一个线程中等待读取完成或者状态改变事件发生。
SetCommMask和WaitCommEvent有两种很有意思的边际效应。
第一,如果以非重叠方式打开通信端口,WaitCommEvent将阻塞直到某事件发生。
如果其他线程调用SetCommMask设置新的事件掩码,则线程将阻塞在SetCommMask调用上,原因是第一个线程的WaitCommEvent调用仍在执行中。
SetCommMask将一直阻塞调用线程,直到第一个线程的WaitCommEvent调用返回。
这种边际效应对于以非重叠方式打开的端口是通用的。
如果某线程阻塞在任何通信函数上,则第二个线程对任何通信函数的调用都将阻塞,直到第一个线程的函数调用返回。
第二种边际效应是关于以重叠方式打开的端口的。
如果使用SetCommMask设置新的事件掩码,则未决的WaitCommEvent调用将成功完成,导致调用完成的事件掩码将是NULL。
去掉参数中的FILE_FLAG_OVERLAPPED就是非重叠操作方式了。
用CreateFile打开通信端口时,有下列限制:
fdwShareMode必须是0。
通信端口不能像文件那样被共享。
要共享通信端口,需要使用句柄继承或者复制操作。
fdwCreate必须指定OPEN_EXISTING标志。
hTemplateFile参数必须是NULL。
端口名通常是COM1、COM2、COM3和COM4。
Win32API不提供确定系统中有哪些端口可用的机制。
WindowsNT和Windows95跟踪系统已安装端口的方法是不同的,所以不太可能提供兼容的确定可用端口的方法。
某些系统可能有多于4个端口,而传统的通信端口最大个数是4。
硬件厂商和串口驱动编写者可以自由地为端口命名。
所以,程序最好可以让用户指定要使用的端口名字。
如果端口不存在,则试图打开端口时会返回ERROR_FILE_NOT_FOUND错误,这时应该提示用户端口不可用。
2读写操作
通信端口的读写操作与文件I/O操作非常相似,它们使用同样的函数。
Win32的I/O操作可分为两种:
重叠(overlapped)的和非重叠的(nonoverlapped)。
平台SDK文档分别使用异步(asynchronous)和同步(synchronous)来表示这两种I/O方式。
很多开发者都熟悉非重叠I/O,因为它就是传统的I/O方式:
函数返回时,所请求的操作已经完成。
然而在重叠I/O的情况下,系统则可能在操作还没有完成的情形下立即返回,随后才通知调用者操作完成。
程序可以在发起I/O请求和请求被完成之间进行一些后台工作。
2.1非重叠I/O
非重叠I/O的工作方式很简单:
I/O操作进行时,调用线程被阻塞;操作完成后,函数返回,调用线程可以继续执行。
在多线程应用中,这种I/O方式很有用:
一个线程阻塞在某I/O操作上时,其他线程可以继续工作。
应用程序应该保证对端口的串行访问。
某个线程阻塞在等待某I/O操作上时,其他线程后续的通信API调用也都将阻塞。
比如说,一个线程在等待ReadFile调用返回时,另一个线程的WriteFile函数调用将阻塞。
在选择使用非重叠还是重叠方式时,可移植性是要考虑的因素之一。
有时候重叠操作并不是好的选择,因为很多操作系统不支持它;然而很多操作系统都支持某种形式的多线程。
所以从兼容性方面考虑,多线程非重叠I/O可能是最好的选择。
2.2重叠I/O
重叠I/O不像非重叠I/O那样简单易懂,但却灵活高效。
使用重叠方式打开的端口允许多个线程同时进行I/O操作,并且在操作进行期间可以进行其他的工作。
此外,重叠操作的行为方式还允许单个线程提交多个不同的请求,然后在操作进行期间进行其他后台工作。
在单线程和多线程应用中,都必须在提交I/O请求和处理操作结果间进行一些同步操作。
线程可能需要在操作结果可用前阻塞;当然也可以进行其他工作。
如果没有其他需要进行的工作,则重叠I/O的优点是更好的用户响应性能。
MTTTY使用了重叠I/O。
它创建用于读取数据和监测端口状态的线程,并且还定时进行一些后台工作;此外它还另外创建一个线程用于写入数据。
重叠I/O操作分为两个部分:
创建I/O操作和检测操作完成。
创建I/O操作涉及到建立OVERLAPPED结构体、创建用于同步的手动复位事件、调用恰当的函数(ReadFile或者WriteFile)。
I/O操作可能立即完成,也可能不能立即完成,不能认为一个重叠I/O操作请求总是生成一个重叠操作。
如果操作立即完成,程序应该可以继续进行通常的处理。
检测操作完成涉及到等待事件句柄、检查操作完成结果、处理数据。
与重叠I/O相关的工作更多的原因是有更多的失败点。
非重叠操作中,简单地通过函数返回值判断操作是否失败;而重叠操作中,则可能在创建操作请求时失败,或者操作阻塞期间失败,也可能是操作超时,或者是等待操作完成信号超时。
2.2.1读操作
下面的代码片段展示了提交重叠的读操作请求的方法。
注意,如果ReadFile返回TRUE,调用了一个函数处理数据。
代码定义了fWaitingOnRead标志,它表示是否有重叠的读取操作存在,用于阻止在一个操作进行中时提交另一个读取操作请求。
DWORD dwRead;
BOOL fWaitingOnRead = FALSE;
OVERLAPPED osReader = {0};
// Create the overlapped event. Must be closed before exiting
// to avoid a handle leak.
osReader.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
if (osReader.hEvent == NULL)
// Error creating overlapped event; abort.
if (!
fWaitingOnRead) {
// Issue read operation.
if (!
ReadFile(hComm, lpBuf, READ_BUF_SIZE, &dwRead, &osReader)) {
if (GetLastError() !
= ERROR_IO_PENDING) // read not delayed?
// Error in communications; report it.
else
fWaitingOnRead = TRUE;
}
else {
// read completed immediately
HandleASuccessfulRead(lpBuf, dwRead);
}
}
OVERLAPPED结构体的事件句柄被传递给WaitForSingleObject以等待事件授信,操作完成。
注意,事件受信表示操作完成,而不是操作成功完成。
应该用GetOverlappedResult来取得操作结果,它返回TRUE表示操作成功完成;FALSE表示有错误发生,用GetLastError可以取得具体的错误码。
也可以用GetOverlappedResult来检测操作完成:
GetOverlappedResult返回FALSE,GetLastError返回ERROR_IO_INCOMPLETE表示操作进行中。
如果对bWait参数传入TRUE,则效果就是重叠操作变成了非重叠的,直到操作完成,函数才返回。
下面的代码片段展示了一种检测重叠读取操作完成的方法。
注意fWaitingOnRead标志的使用,它是检测代码的控制入口,只有在某操作进行中时,才应该调用检测代码。
#define READ_TIMEOUT 500 // milliseconds
DWORD dwRes;
if (fWaitingOnRead) {
dwRes = WaitForSingleObject(osReader.hEvent, READ_TIMEOUT);
switch(dwRes)
{
// Read completed.
case WAIT_OBJECT_0:
if (!
GetOverlappedResult(hComm, &osReader, &dwRead, FALSE))
// Error in communications; report it.
else
// Read completed successfully.
HandleASuccessfulRead(lpBuf, dwRead);
// Reset flag so that another opertion can be issued.
fWaitingOnRead = FALSE;
break;
case WAIT_TIMEOUT:
// Operation isn't complete yet. fWaiti