Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
0% found this document useful (0 votes)
10 views37 pages

串口编程介绍 (英文版)

Download as pdf or txt
Download as pdf or txt
Download as pdf or txt
You are on page 1/ 37

Introduction

The focus of this article is on application programming interfaces (APIs)


and methods that are compatible with Microsoft?Windows NT?and Windows 95;
therefore, APIs supported on both platforms are the only ones discussed.
Windows 95 supports the Win32?Telephony API (TAPI) and Windows NT 3.x does
not; therefore, this discussion will not include TAPI. TAPI does deserve
mention, however, in that it very nicely implements modem interfacing and
call controlling. A production application that works with modems and
makes telephone calls should implement these features using the TAPI
interface. This will allow seamless integration with the other
TAPI-enabled applications that a user may have. Furthermore, this article
does not discuss some of the configuration functions in Win32, such as
GetCommProperties.

The article is broken into the following sections: Opening a port, reading
and writing (nonoverlapped and overlapped), serial status (events and
errors), and serial settings (DCB, flow control, and communications
time-outs).

The sample included with this article, MTTTY: Multithreaded TTY,


implements many of the features discussed here. It uses three threads in
its implementation: a user interface thread that does memory management,
a writer thread that controls all writing, and a reader/status thread that
reads data and handles status changes on the port. The sample employs a
few different data heaps for memory management. It also makes extensive
use of synchronization methods to facilitate communication between
threads.

Opening a Port

The CreateFile function opens a communications port. There are two ways
to call CreateFile to open the communications port: overlapped and
nonoverlapped. The following is the proper way to open a communications
resource for overlapped operation:

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

Removal of the FILE_FLAG_OVERLAPPED flag from the call to CreateFile


specifies nonoverlapped operation. The next section discusses overlapped
and nonoverlapped operations.

The Platform SDK documentation states that when opening a communications


port, the call to CreateFile has the following requirements:

 fdwShareMode must be zero. Communications ports cannot be shared


in the same manner that files are shared. Applications using TAPI
can use the TAPI functions to facilitate sharing resources between
applications. For Win32 applications not using TAPI, handle
inheritance or duplication is necessary to share the communications
port. Handle duplication is beyond the scope of this article; please
refer to the Platform SDK documentation for more information.

 fdwCreate must specify the OPEN_EXISTING flag.

 hTemplateFile parameter must be NULL.

One thing to note about port names is that traditionally they have been
COM1, COM2, COM3, or COM4. The Win32 API does not provide any mechanism
for determining what ports exist on a system. Windows NT and Windows 95
keep track of installed ports differently from one another, so any one
method would not be portable across all Win32 platforms. Some systems even
have more ports than the traditional maximum of four. Hardware vendors
and serial-device-driver writers are free to name the ports anything they
like. For this reason, it is best that users have the ability to specify
the port name they want to use. If a port does not exist, an error will
occur (ERROR_FILE_NOT_FOUND) after attempting to open the port, and the
user should be notified that the port isn’t available.

Reading and Writing

Reading from and writing to communications ports in Win32 is very similar


to file input/output (I/O) in Win32. In fact, the functions that
accomplish file I/O are the same functions used for serial I/O. I/O in
Win32 can be done either of two ways: overlapped or nonoverlapped. The
Platform SDK documentation uses the terms asynchronous and synchronous
to connote these types of I/O operations. This article, however, uses the
terms overlapped and nonoverlapped.

Nonoverlapped I/O is familiar to most developers because this is the


traditional form of I/O, where an operation is requested and is assumed
to be complete when the function returns. In the case of overlapped I/O,
the system may return to the caller immediately even when an operation
is not finished and will signal the caller when the operation completes.
The program may use the time between the I/O request and its completion
to perform some “background?work.

Reading and writing in Win32 is significantly different from reading and


writing serial communications ports in 16-bit Windows. 16-bit Windows
only has the ReadComm and WriteComm functions. Win32 reading and writing
can involve many more functions and choices. These issues are discussed
below.

Nonoverlapped I/O

Nonoverlapped I/O is very straightforward, though it has limitations. An


operation takes place while the calling thread is blocked. Once the
operation is complete, the function returns and the thread can continue
its work. This type of I/O is useful for multithreaded applications
because while one thread is blocked on an I/O operation, other threads
can still perform work. It is the responsibility of the application to
serialize access to the port correctly. If one thread is blocked waiting
for its I/O operation to complete, all other threads that subsequently
call a communications API will be blocked until the original operation
completes. For instance, if one thread were waiting for a ReadFile
function to return, any other thread that issued a WriteFile function
would be blocked.

One of the many factors to consider when choosing between nonoverlapped


and overlapped operations is portability. Overlapped operation is not a
good choice because most operating systems do not support it. Most
operating systems support some form of multithreading, however, so
multithreaded nonoverlapped I/O may be the best choice for portability
reasons.

Overlapped I/O

Overlapped I/O is not as straightforward as nonoverlapped I/O, but allows


more flexibility and efficiency. A port open for overlapped operations
allows multiple threads to do I/O operations at the same time and perform
other work while the operations are pending. Furthermore, the behavior
of overlapped operations allows a single thread to issue many different
requests and do work in the background while the operations are pending.

In both single-threaded and multithreaded applications, some


synchronization must take place between issuing requests and processing
the results. One thread will have to be blocked until the result of an
operation is available. The advantage is that overlapped I/O allows a
thread to do some work between the time of the request and its completion.
If no work can be done, then the only case for overlapped I/O is that it
allows for better user responsiveness.

Overlapped I/O is the type of operation that the MTTTY sample uses. It
creates a thread that is responsible for reading the port’s data and
reading the port’s status. It also performs periodic background work.
The program creates another thread exclusively for writing data out the
port.

Note Applications sometimes abuse multithreading systems by creating


too many threads. Although using multiple threads can resolve many
difficult problems, creating excessive threads is not the most efficient
use of them in an application. Threads are less a strain on the system
than processes but still require system resources such as CPU time and
memory. An application that creates excessive threads may adversely
affect the performance of the entire system. A better use of threads is
to create a different request queue for each type of job and to have a
worker thread issue an I/O request for each entry in the request queue.
This method is used by the MTTTY sample provided with this article.

An overlapped I/O operation has two parts: the creation of the operation
and the detection of its completion. Creating the operation entails
setting up an OVERLAPPED structure, creating a manual-reset event for
synchronization, and calling the appropriate function (ReadFile or
WriteFile). The I/O operation may or may not be completed immediately.
It is an error for an application to assume that a request for an overlapped
operation always yields an overlapped operation. If an operation is
completed immediately, an application needs to be ready to continue
processing normally. The second part of an overlapped operation is to
detect its completion. Detecting completion of the operation involves
waiting for the event handle, checking the overlapped result, and then
handling the data. The reason that there is more work involved with an
overlapped operation is that there are more points of failure. If a
nonoverlapped operation fails, the function just returns an error-return
result. If an overlapped operation fails, it can fail in the creation of
the operation or while the operation is pending. You may also have a
time-out of the operation or a time-out waiting for the signal that the
operation is complete.

Reading
The ReadFile function issues a read operation. ReadFileEx also issues a
read operation, but since it is not available on Windows 95, it is not
discussed in this article. Here is a code snippet that details how to issue
a read request. Notice that the function calls a function to process the
data if the ReadFile returns TRUE. This is the same function called if
the operation becomes overlapped. Note the fWaitingOnRead flag that is
defined by the code; it indicates whether or not a read operation is
overlapped. It is used to prevent the creation of a new read operation
if one is outstanding.

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);
}
}

The second part of the overlapped operation is the detection of its


completion. The event handle in the OVERLAPPED structure is passed to the
WaitForSingleObject function, which will wait until the object is
signaled. Once the event is signaled, the operation is complete. This does
not mean that it was completed successfully, just that it was completed.
The GetOverlappedResult function reports the result of the operation. If
an error occurred, GetOverlappedResult returns FALSE and GetLastError
returns the error code. If the operation was completed successfully,
GetOverlappedResult will return TRUE.
Note GetOverlappedResult can detect completion of the operation, as
well as return the operation’s failure status. GetOverlappedResult
returns FALSE and GetLastError returns ERROR_IO_INCOMPLETE when the
operation is not completed. In addition, GetOverlappedResult can be made
to block until the operation completes. This effectively turns the
overlapped operation into a nonoverlapped operation and is accomplished
by passing TRUE as the bWait parameter.

Here is a code snippet that shows one way to detect the completion of an
overlapped read operation. Note that the code calls the same function to
process the data that was called when the operation completed immediately.
Also note the use of the fWaitingOnRead flag. Here it controls entry into
the detection code, since it should be called only when an operation is
outstanding.

#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. fWaitingOnRead flag isn't
// changed since I'll loop back around, and I don't want
// to issue another read until the first one finishes.
//
// This is a good time to do some background work.
break;

default:
// Error in the WaitForSingleObject; abort.
// This indicates a problem with the OVERLAPPED structure's
// event handle.
break;
}
}

Writing

Transmitting data out the communications port is very similar to reading


in that it uses a lot of the same APIs. The code snippet below demonstrates
how to issue and wait for a write operation to be completed.

BOOL WriteABuffer(char * lpBuf, DWORD dwToWrite)


{
OVERLAPPED osWrite = {0};
DWORD dwWritten;
DWORD dwRes;
BOOL fRes;

// Create this write operation's OVERLAPPED structure's hEvent.


osWrite.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
if (osWrite.hEvent == NULL)
// error creating overlapped event handle
return FALSE;

// Issue write.
if (!WriteFile(hComm, lpBuf, dwToWrite, &dwWritten, &osWrite)) {
if (GetLastError() != ERROR_IO_PENDING) {
// WriteFile failed, but isn't delayed. Report error and abort.
fRes = FALSE;
}
else
// Write is pending.
dwRes = WaitForSingleObject(osWrite.hEvent, INFINITE);
switch(dwRes)
{
// OVERLAPPED structure's event has been signaled.
case WAIT_OBJECT_0:
if (!GetOverlappedResult(hComm, &osWrite, &dwWritten,
FALSE))
fRes = FALSE;
else
// Write operation completed successfully.
fRes = TRUE;
break;

default:
// An error has occurred in WaitForSingleObject.
// This usually indicates a problem with the
// OVERLAPPED structure's event handle.
fRes = FALSE;
break;
}
}
}
else
// WriteFile completed immediately.
fRes = TRUE;

CloseHandle(osWrite.hEvent);
return fRes;
}

Notice that the code above uses the WaitForSingleObject function with a
time-out value of INFINITE. This causes the WaitForSingleObject function
to wait forever until the operation is completed; this may make the thread
or program appear to be “hung?when, in fact, the write operation is simply
taking a long time to complete or flow control has blocked the transmission.
Status checking, discussed later, can detect this condition, but doesn’
t cause the WaitForSingleObject to return. Three things can alleviate this
condition:

 Place the code in a separate thread. This allows other threads to


execute any functions they desire while our writer thread waits for
the write to be completed. This is what the MTTTY sample does.

 Use COMMTIMEOUTS to cause the write to be completed after a time-out


period has passed. This is discussed more fully in the
“Communications Time-outs?section later in this article. This is
also what the MTTTY sample allows.

 Change the WaitForSingleObject call to include a real time-out


value. This causes more problems because if the program issues
another operation while an older operation is still pending, new
OVERLAPPED structures and overlapped events need to be allocated.
This type of recordkeeping is difficult, particularly when compared
to using a “job queue?design for the operations. The “job
queue?method is used in the MTTTY sample.
Note: The time-out values in synchronization functions are not
communications time-outs. Synchronization time-outs cause
WaitForSingleObject or WaitForMultipleObjects to return
WAIT_TIMEOUT. This is not the same as a read or write operation
timing out. Communications time-outs are described later in this
article.

Because the WaitForSingleObject function in the above code snippet uses


an INFINITE time-out, it is equivalent to using GetOverlappedResult with
TRUE for the fWait parameter. Here is equivalent code in a much simplified
form:

BOOL WriteABuffer(char * lpBuf, DWORD dwToWrite)


{
OVERLAPPED osWrite = {0};
DWORD dwWritten;
BOOL fRes;

// Create this writes OVERLAPPED structure hEvent.


osWrite.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
if (osWrite.hEvent == NULL)
// Error creating overlapped event handle.
return FALSE;

// Issue write.
if (!WriteFile(hComm, lpBuf, dwToWrite, &dwWritten, &osWrite)) {
if (GetLastError() != ERROR_IO_PENDING) {
// WriteFile failed, but it isn't delayed. Report error and
abort.
fRes = FALSE;
}
else {
// Write is pending.
if (!GetOverlappedResult(hComm, &osWrite, &dwWritten, TRUE))
fRes = FALSE;
else
// Write operation completed successfully.
fRes = TRUE;
}
}
else
// WriteFile completed immediately.
fRes = TRUE;
CloseHandle(osWrite.hEvent);
return fRes;
}

GetOverlappedResult is not always the best way to wait for an overlapped


operation to be completed. For example, if an application needs to wait
on another event handle, the first code snippet serves as a better model
than the second. The call to WaitForSingleObject is easy to change to
WaitForMultipleObjects to include the additional handles on which to wait.
This is what the MTTTY sample application does.

A common mistake in overlapped I/O is to reuse an OVERLAPPED structure


before the previous overlapped operation is completed. If a new overlapped
operation is issued before a previous operation is completed, a new
OVERLAPPED structure must be allocated for it. A new manual-reset event
for the hEvent member of the OVERLAPPED structure must also be created.
Once an overlapped operation is complete, the OVERLAPPED structure and
its event are free for reuse.

The only member of the OVERLAPPED structure that needs modifying for
serial communications is the hEvent member. The other members of the
OVERLAPPED structure should be initialized to zero and left alone.
Modifying the other members of the OVERLAPPED structure is not necessary
for serial communications devices. The documentation for ReadFile and
WriteFile state that the Offset and OffsetHigh members of the OVERLAPPED
structure must be updated by the application, or else results are
unpredictable. This guideline should be applied to OVERLAPPED structures
used for other types of resources, such as files.

Serial Status

There are two methods to retrieve the status of a communications port.


The first is to set an event mask that causes notification of the
application when the desired events occur. The SetCommMask function sets
this event mask, and the WaitCommEvent function waits for the desired
events to occur. These functions are similar to the 16-bit functions
SetCommEventMask and EnableCommNotification, except that the Win32
functions do not post WM_COMMNOTIFY messages. In fact, the WM_COMMNOTIFY
message is not even part of the Win32 API. The second method for retrieving
the status of the communications port is to periodically call a few
different status functions. Polling is, of course, neither efficient nor
recommended.

Communications Events
Communications events can occur at any time in the course of using a
communications port. The two steps involved in receiving notification of
communications events are as follows:

 SetCommMask sets the desired events that cause a notification.

 WaitCommEvent issues a status check. The status check can be an


overlapped or nonoverlapped operation, just as the read and write
operations can be.

Note: The word event in this context refers to communications events


only. It does not refer to an event object used for synchronization.

Here is an example of the SetCommMask function:

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

A description of each type of event is in Table 1.

Table 1. Communications Event Flags

Event Flag Description

EV_BREAK A break was detected on input.

EV_CTS The CTS (clear-to-send) signal changed state. To get the


actual state of the CTS line, GetCommModemStatus should be
called.

EV_DSR The DSR (data-set-ready) signal changed state. To get the


actual state of the DSR line, GetCommModemStatus should be
called.

EV_ERR A line-status error occurred. Line-status errors are


CE_FRAME, CE_OVERRUN, and CE_RXPARITY. To find the cause of
the error, ClearCommError should be called.

EV_RING A ring indicator was detected.

EV_RLSD The RLSD (receive-line-signal-detect) signal changed


state. To get the actual state of the RLSD line,
GetCommModemStatus should be called. Note that this is
commonly referred to as the CD (carrier detect) line.

EV_RXCHAR A new character was received and placed in the input buffer.
See the “Caveat?section below for a discussion of this flag.

EV_RXFLAG The event character was received and placed in the input
buffer. The event character is specified in the EvtChar
member of the DCB structure discussed later. The
“Caveat?section below also applies to this flag.

EV_TXEMPTY The last character in the output buffer was sent to the
serial port device. If a hardware buffer is used, this flag
only indicates that all data has been sent to the hardware.
There is no way to detect when the hardware buffer is empty
without talking directly to the hardware with a device
driver.

After specifying the event mask, the WaitCommEvent function detects the
occurrence of the events. If the port is open for nonoverlapped operation,
then the WaitCommEvent function does not contain an OVERLAPPED structure.
The function blocks the calling thread until the occurrence of one of the
events. If an event never occurs, the thread may block indefinitely.

Here is a code snippet that shows how to wait for an EV_RING event when
the port is open for nonoverlapped operation:

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;

Note The Microsoft Win32 SDK Knowledge Base documents a problem with
Windows 95 and the EV_RING flag. The above code never returns in Windows
95 because the EV_RING event is not detected by the system; Windows NT
properly reports the EV_RING event. Please see the Win32 SDK Knowledge
Base for more information on this bug.

As noted, the code above can be blocked forever if an event never occurs.
A better solution would be to open the port for overlapped operation and
wait for a status event in the following manner:

#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);

The code above very closely resembles the code for overlapped reading.
In fact, the MTTTY sample implements its reading and status checking in
the same thread using WaitForMultipleObjects to wait for either the read
event or the status event to become signaled.

There are two interesting side effects of SetCommMask and WaitCommEvent.


First, if the communications port is open for nonoverlapped operation,
WaitCommEvent will be blocked until an event occurs. If another thread
calls SetCommMask to set a new event mask, that thread will be blocked
on the call to SetCommMask. The reason is that the original call to
WaitCommEvent in the first thread is still executing. The call to
SetCommMask blocks the thread until the WaitCommEvent function returns
in the first thread. This side effect is universal for ports open for
nonoverlapped I/O. If a thread is blocked on any communications function
and another thread calls a communications function, the second thread is
blocked until the communications function returns in the first thread.
The second interesting note about these functions is their use on a port
open for overlapped operation. If SetCommMask sets a new event mask, any
pending WaitCommEvent will complete successfully, and the event mask
produced by the operation is NULL.

Caveat

Using the EV_RXCHAR flag will notify the thread that a byte arrived at
the port. This event, used in combination with the ReadFile function,
enables a program to read data only after it is in the receive buffer,
as opposed to issuing a read that waits for the data to arrive. This is
particularly useful when a port is open for nonoverlapped operation
because the program does not need to poll for incoming data; the program
is notified of the incoming data by the occurrence of the EV_RXCHAR event.
Initial attempts to code this solution often produce the following
pseudocode, including one oversight covered later in this section:

DWORD dwCommEvent;
DWORD dwRead;
char chRead;

if (!SetCommMask(hComm, EV_RXCHAR))
// Error setting communications event mask.

for ( ; ; ) {
if (WaitCommEvent(hComm, &dwCommEvent, NULL)) {
if (ReadFile(hComm, &chRead, 1, &dwRead, NULL))
// A byte has been read; process it.
else
// An error occurred in the ReadFile call.
break;
}
else
// Error in WaitCommEvent.
break;
}

The above code waits for an EV_RXCHAR event to occur. When this happens,
the code calls ReadFile to read the one byte received. The loop starts
again, and the code waits for another EV_RXCHAR event. This code works
fine when one or two bytes arrive in quick succession. The byte reception
causes the EV_RXCHAR event to occur. The code reads the byte. If no other
byte arrives before the code calls WaitCommEvent again, then all is fine;
the next byte to arrive will cause the WaitCommEvent function to indicate
the occurrence of the EV_RXCHAR event. If another single byte arrives
before the code has a chance to reach the WaitCommEvent function, then
all is fine, too. The first byte is read as before; the arrival of the
second byte causes the EV_RXCHAR flag to be set internally. When the code
returns to the WaitCommEvent function, it indicates the occurrence of the
EV_RXCHAR event and the second byte is read from the port in the ReadFile
call.

The problem with the above code occurs when three or more bytes arrive
in quick succession. The first byte causes the EV_RXCHAR event to occur.
The second byte causes the EV_RXCHAR flag to be set internally. The next
time the code calls WaitCommEvent, it indicates the EV_RXCHAR event. Now,
a third byte arrives at the communications port. This third byte causes
the system to attempt to set the EV_RXCHAR flag internally. Because this
has already occurred when the second byte arrived, the arrival of the third
byte goes unnoticed. The code eventually will read the first byte without
a problem. After this, the code will call WaitCommEvent, and it indicates
the occurrence of the EV_RXCHAR event (from the arrival of the second byte).
The second byte is read, and the code returns to the WaitCommEvent function.
The third byte waits in the system’s internal receive buffer. The code
and the system are now out of sync. When a fourth byte finally arrives,
the EV_RXCHAR event occurs, and the code reads a single byte. It reads
the third byte. This will continue indefinitely.

The solution to this problem seems as easy as increasing the number of


bytes requested in the read operation. Instead of requesting a single byte,
the code could request two, ten, or some other number of bytes. The problem
with this idea is that it still fails when two or more extra bytes above
the size of the read request arrive at the port in quick succession. So,
if two bytes are read, then four bytes arriving in quick succession would
cause the problem. Ten bytes requested would still fail if twelve bytes
arrived in quick succession.

The real solution to this problem is to read from the port until no bytes
are remaining. The following pseudocode solves the problem by reading in
a loop until zero characters are read. Another possible method would be
to call ClearCommError to determine the number of bytes in the buffer and
read them all in one read operation. This method requires more
sophisticated buffer management, but it reduces the number of reads when
a lot of data arrives at once.

DWORD dwCommEvent;
DWORD dwRead;
char chRead;

if (!SetCommMask(hComm, EV_RXCHAR))
// Error setting communications event mask

for ( ; ; ) {
if (WaitCommEvent(hComm, &dwCommEvent, NULL)) {
do {
if (ReadFile(hComm, &chRead, 1, &dwRead, NULL))
// A byte has been read; process it.
else
// An error occurred in the ReadFile call.
break;
} while (dwRead);
}
else
// Error in WaitCommEvent
break;
}

The above code does not work correctly without setting the proper
time-outs. Communications time-outs, discussed later, affect the
behavior of the ReadFile operation in order to cause it to return without
waiting for bytes to arrive. Discussion of this topic occurs later in the
“Communications Time-outs?section of this article.

The above caveat regarding EV_RXCHAR also applies to EV_RXFLAG. If flag


characters arrive in quick succession, EV_RXFLAG events may not occur for
all of them. Once again, the best solution is to read all bytes until none
remain.

The above caveat also applies to other events not related to character
reception. If other events occur in quick succession some of the
notifications will be lost. For instance, if the CTS line voltage starts
high, then goes low, high, and low again, an EV_CTS event occurs. There
is no guarantee of how many EV_CTS events will actually be detected with
WaitCommEvent if the changes in the CTS line happen quickly. For this
reason, WaitCommEvent cannot be used to keep track of the state of the
line. Line status is covered in the “Modem Status?section later in this
article.

Error Handling and Communications Status

One of the communications event flags specified in the call to SetCommMask


is possibly EV_ERR. The occurrence of the EV_ERR event indicates that an
error condition exists in the communications port. Other errors can occur
in the port that do not cause the EV_ERR event to occur. In either case,
errors associated with the communications port cause all I/O operations
to be suspended until removal of the error condition. ClearCommError is
the function to call to detect errors and clear the error condition.

ClearCommError also provides communications status indicating why


transmission has stopped; it also indicates the number of bytes waiting
in the transmit and receive buffers. The reason why transmission may stop
is because of errors or to flow control. The discussion of flow control
occurs later in this article.

Here is some code that demonstrates how to call ClearCommError:

COMSTAT comStat;
DWORD dwErrors;
BOOL fOOP, fOVERRUN, fPTO, fRXOVER, fRXPARITY, fTXFULL;
BOOL fBREAK, fDNS, fFRAME, fIOE, fMODE;

// Get and clear current errors on the port.


if (!ClearCommError(hComm, &dwErrors, &comStat))
// Report error in ClearCommError.
return;

// Get error flags.


fDNS = dwErrors & CE_DNS;
fIOE = dwErrors & CE_IOE;
fOOP = dwErrors & CE_OOP;
fPTO = dwErrors & CE_PTO;
fMODE = dwErrors & CE_MODE;
fBREAK = dwErrors & CE_BREAK;
fFRAME = dwErrors & CE_FRAME;
fRXOVER = dwErrors & CE_RXOVER;
fTXFULL = dwErrors & CE_TXFULL;
fOVERRUN = dwErrors & CE_OVERRUN;
fRXPARITY = dwErrors & CE_RXPARITY;

// COMSTAT structure contains information regarding


// communications status.
if (comStat.fCtsHold)
// Tx waiting for CTS signal

if (comStat.fDsrHold)
// Tx waiting for DSR signal

if (comStat.fRlsdHold)
// Tx waiting for RLSD signal

if (comStat.fXoffHold)
// Tx waiting, XOFF char rec'd

if (comStat.fXoffSent)
// Tx waiting, XOFF char sent

if (comStat.fEof)
// EOF character received

if (comStat.fTxim)
// Character waiting for Tx; char queued with TransmitCommChar

if (comStat.cbInQue)
// comStat.cbInQue bytes have been received, but not read

if (comStat.cbOutQue)
// comStat.cbOutQue bytes are awaiting transfer

Modem Status (a.k.a. Line Status)

The call to SetCommMask may include the flags EV_CTS, EV_DSR, EV_RING,
and EV_RLSD. These flags indicate changes in the voltage on the lines of
the serial port. There is no indication of the actual status of these lines,
just that a change occurred. The GetCommModemStatus function retrieves
the actual state of these status lines by returning a bit mask indicating
a 0 for low or no voltage and 1 for high voltage for each of the lines.

Please note that the term RLSD (Receive Line Signal Detect) is commonly
referred to as the CD (Carrier Detect) line.

Note The EV_RING flag does not work in Windows 95 as mentioned earlier.
The GetCommModemStatus function, however, does detect the state of the
RING line.

Changes in these lines may also cause a flow-control event. The


ClearCommError function reports whether transmission is suspended
because of flow control. If necessary, a thread may call ClearCommError
to detect whether the event is the cause of a flow-control action. Flow
control is covered in the “Flow Control?section later in this article.

Here is some code that demonstrates how to call GetCommModemStatus:

DWORD dwModemStatus;
BOOL fCTS, fDSR, fRING, fRLSD;

if (!GetCommModemStatus(hComm, &dwModemStatus))
// Error in GetCommModemStatus;
return;

fCTS = MS_CTS_ON & dwModemStatus;


fDSR = MS_DSR_ON & dwModemStatus;
fRING = MS_RING_ON & dwModemStatus;
fRLSD = MS_RLSD_ON & dwModemStatus;

// Do something with the flags.

Extended Functions

The driver will automatically change the state of control lines as


necessary. Generally speaking, changing status lines is under the control
of a driver. If a device uses communications port control lines in a manner
different from RS-232 standards, the standard serial communications
driver will not work to control the device. If the standard serial
communications driver will not control the device, a custom device driver
is necessary.

There are occasions when standard control lines are under the control of
the application instead of the serial communications driver. For instance,
an application may wish to implement its own flow control. The application
would be responsible for changing the status of the RTS and DTR lines.
EscapeCommFunction directs a communications driver to perform such
extended operations. EscapeCommFunction can make the driver perform some
other function, such as setting or clearing a BREAK condition. For more
information on this function, consult the Platform SDK documentation, the
Microsoft Win32 SDK Knowledge Base, or the Microsoft Developer Network
(MSDN) Library.

Serial Settings

DCB Settings

The most crucial aspect of programming serial communications applications


is the settings in the Device-Control Block (DCB) structure. The most
common errors in serial communications programming occur in initializing
the DCB structure improperly. When the serial communications functions
do not behave as expected, a close examination of the DCB structure usually
reveals the problem.

There are three ways to initialize a DCB structure. The first method is
to use the function GetCommState. This function returns the current DCB
in use for the communications port. The following code shows how to use
the GetCommState function:

DCB dcb = {0};

if (!GetCommState(hComm, &dcb))
// Error getting current DCB settings
else
// DCB is ready for use.

The second method to initialize a DCB is to use a function called


BuildCommDCB. This function fills in the baud, parity type, number of stop
bits, and number of data bits members of the DCB. The function also sets
the flow-control members to default values. Consult the documentation of
the BuildCommDCB function for details on which default values it uses for
flow-control members. Other members of the DCB are unaffected by this
function. It is the program's duty to make sure the other members of the
DCB do not cause errors. The simplest thing to do in this regard is to
initialize the DCB structure with zeros and then set the size member to
the size, in bytes, of the structure. If the zero initialization of the
DCB structure does not occur, then there may be nonzero values in the
reserved members; this produces an error when trying to use the DCB later.
The following function shows how to properly use this method:
DCB dcb;

FillMemory(&dcb, sizeof(dcb), 0);


dcb.DCBlength = sizeof(dcb);
if (!BuildCommDCB("9600,n,8,1", &dcb)) {
// Couldn't build the DCB. Usually a problem
// with the communications specification string.
return FALSE;
}
else
// DCB is ready for use.

The third method to initialize a DCB structure is to do it manually. The


program allocates the DCB structure and sets each member with any value
desired. This method does not deal well with changes to the DCB in future
implementations of Win32 and is not recommended.

An application usually needs to set some of the DCB members differently


than the defaults or may need to modify settings in the middle of execution.
Once proper initialization of the DCB occurs, modification of individual
members is possible. The changes to the DCB structure do not have any
effect on the behavior of the port until execution of the SetCommState
function. Here is a section of code that retrieves the current DCB, changes
the baud, and then attempts to set the configuration:

DCB dcb;

FillMemory(&dcb, sizeof(dcb), 0);


if (!GetCommState(hComm, &dcb)) // get current DCB
// Error in GetCommState
return FALSE;

// Update DCB rate.


dcb.BaudRate = CBR_9600 ;

// Set new state.


if (!SetCommState(hComm, &dcb))
// Error in SetCommState. Possibly a problem with the
communications
// port handle or a problem with the DCB structure itself.

Here is an explanation of each of the members of the DCB and how they affect
other parts of the serial communications functions.
Note Most of this information is from the Platform SDK documentation.
Because documentation is the official word in what the members actually
are and what they mean, this table may not be completely accurate if
changes occur in the operating system.

Table 2. The DCB Structure Members

Member Description

DCBlength Size, in bytes, of the structure. Should be set before


calling SetCommState to update the settings.

BaudRate Specifies the baud at which the communications device


operates. This member can be an actual baud value, or
a baud index.

fBinary Specifies whether binary mode is enabled. The Win32


API does not support nonbinary mode transfers, so this
member should be TRUE. Trying to use FALSE will not
work.

fParity Specifies whether parity checking is enabled. If this


member is TRUE, parity checking is performed and
parity errors are reported. This should not be
confused with the Parity member, which controls the
type of parity used in communications.

fOutxCtsFlow Specifies whether the CTS (clear-to-send) signal is


monitored for output flow control. If this member is
TRUE and CTS is low, output is suspended until CTS is
high again. The CTS signal is under control of the DCE
(usually a modem), the DTE (usually the PC) simply
monitors the status of this signal, the DTE does not
change it.

fOutxDsrFlow Specifies whether the DSR (data-set-ready) signal is


monitored for output flow control. If this member is
TRUE and DSR is low, output is suspended until DSR is
high again. Once again, this signal is under the
control of the DCE; the DTE only monitors this signal.

fDtrControl Specifies the DTR (data-terminal-ready) input flow


control. This member can be one of the following
values:

Value Meaning
DTR_CONTROL_DISABLE Lowers the DTR line when the
device is opened. The
application can adjust the
state of the line with
EscapeCommFunction.

DTR_CONTROL_ENABLE Raises the DTR line when the


device is opened. The
application can adjust the
state of the line with
EscapeCommFunction.

DTR_CONTROL_HANDSHAKE Enables DTR flow-control


handshaking. If this value is
used, it is an error for the
application to adjust the
line with
EscapeCommFunction.

fDsrSensitivity Specifies whether the communications driver is


sensitive to the state of the DSR signal. If this
member is TRUE, the driver ignores any bytes received,
unless the DSR modem input line is high.

fTXContinueOnXoff Specifies whether transmission stops when the input


buffer is full and the driver has transmitted the XOFF
character. If this member is TRUE, transmission
continues after the XOFF character has been sent. If
this member is FALSE, transmission does not continue
until the input buffer is within XonLim bytes of being
empty and the driver has transmitted the XON
character.

fOutX Specifies whether XON/XOFF flow control is used


during transmission. If this member is TRUE,
transmission stops when the XOFF character is
received and starts again when the XON character is
received.

fInX Specifies whether XON/XOFF flow control is used


during reception. If this member is TRUE, the XOFF
character is sent when the input buffer comes within
XoffLim bytes of being full, and the XON character is
sent when the input buffer comes within XonLim bytes
of being empty.
fErrorChar Specifies whether bytes received with parity errors
are replaced with the character specified by the
ErrorChar member. If this member is TRUE and the
fParity member is TRUE, replacement occurs.

fNull Specifies whether null bytes are discarded. If this


member is TRUE, null bytes are discarded when
received.

fRtsControl Specifies the RTS (request-to-send) input flow


control. If this value is zero, the default is
RTS_CONTROL_HANDSHAKE. This member can be one of the
following values:

Value Meaning

RTS_CONTROL_DISABLE Lowers the RTS line when the


device is opened. The
application can use
EscapeCommFunction to
change the state of the line.

RTS_CONTROL_ENABLE Raises the RTS line when the


device is opened. The
application can use
EscapeCommFunction to
change the state of the line.

RTS_CONTROL_HANDSHAKE Enables RTS flow-control


handshaking. The driver
raises the RTS line, enabling
the DCE to send, when the
input buffer has enough room
to receive data. The driver
lowers the RTS line,
preventing the DCE to send,
when the input buffer does
not have enough room to
receive data. If this value
is used, it is an error for
the application to adjust the
line with
EscapeCommFunction.

RTS_CONTROL_TOGGLE Specifies that the RTS line


will be high if bytes are
available for transmission.
After all buffered bytes have
been sent, the RTS line will
be low. If this value is set,
it would be an error for an
application to adjust the
line with
EscapeCommFunction. This
value is ignored in Windows
95; it causes the driver to
act as if RTS_CONTROL_ENABLE
were specified.

fAbortOnError Specifies whether read and write operations are


terminated if an error occurs. If this member is TRUE,
the driver terminates all read and write operations
with an error status (ERROR_IO_ABORTED) if an error
occurs. The driver will not accept any further
communications operations until the application has
acknowledged the error by calling the ClearCommError
function.

fDummy2 Reserved; do not use.

wReserved Not used; must be set to zero.

XonLim Specifies the minimum number of bytes allowed in the


input buffer before the XON character is sent.

XoffLim Specifies the maximum number of bytes allowed in the


input buffer before the XOFF character is sent. The
maximum number of bytes allowed is calculated by
subtracting this value from the size, in bytes, of the
input buffer.

Parity Specifies the parity scheme to be used. This member


can be one of the following values:

Value Meaning

EVENPARITY Even

MARKPARITY Mark

NOPARITY No parity

ODDPARITY Odd
StopBits Specifies the number of stop bits to be used. This
member can be one of the following values:

Value Meaning

ONESTOPBIT 1 stop bit

ONE5STOPBITS 1.5 stop bits

TWOSTOPBITS 2 stop bits

XonChar Specifies the value of the XON character for both


transmission and reception.

XoffChar Specifies the value of the XOFF character for both


transmission and reception.

ErrorChar Specifies the value of the character used to replace


bytes received with a parity error.

EofChar Specifies the value of the character used to signal


the end of data.

EvtChar Specifies the value of the character used to cause the


EV_RXFLAG event. This setting does not actually cause
anything to happen without the use of EV_RXFLAG in the
SetCommMask function and the use of WaitCommEvent.

wReserved1 Reserved; do not use.

Flow Control

Flow control in serial communications provides a mechanism for suspending


communications while one of the devices is busy or for some reason cannot
do any communication. There are traditionally two types of flow control:
hardware and software.

A common problem with serial communications is write operations that


actually do not write the data to the device. Often, the problem lies in
flow control being used when the program did not specify it. A close
examination of the DCB structure reveals that one or more of the following
members may be TRUE: fOutxCtsFlow, fOutxDsrFlow, or fOutX. Another
mechanism to reveal that flow control is enabled is to call ClearCommError
and examine the COMSTAT structure. It will reveal when transmission is
suspended because of flow control.
Before discussing the types of flow control, a good understanding of some
terms is in order. Serial communications takes place between two devices.
Traditionally, there is a PC and a modem or printer. The PC is labeled
the Data Terminal Equipment (DTE). The DTE is sometimes called the host.
The modem, printer, or other peripheral equipment is identified as the
Data Communications Equipment (DCE). The DCE is sometimes referred to as
the device.

Hardware flow control

Hardware flow control uses voltage signals on control lines of the serial
cable to control whether sending or receiving is enabled. The DTE and the
DCE must agree on the types of flow control used for a communications
session. Setting the DCB structure to enable flow control just configures
the DTE. The DCE also needs configuration to make certain the DTE and DCE
use the same type of flow control. There is no mechanism provided by Win32
to set the flow control of the DCE. DIP switches on the device, or commands
sent to it typically establish its configuration. The following table
describes the control lines, the direction of the flow control, and the
line's effect on the DTE and DCE.

Table 3. Hardware Flow-control Lines

Line and Effect on DTE/DCE


Direction

CTS DCE sets the line high to indicate that it can receive
(Clear To Send) data. DCE sets the line low to indicate that it cannot
Output flow receive data.
control
If the fOutxCtsFlow member of the DCB is TRUE, then the
DTE will not send data if this line is low. It will resume
sending if the line is high.

If the fOutxCtsFlow member of the DCB is FALSE, then the


state of the line does not affect transmission.

DSR DCE sets the line high to indicate that it can receive
(Data Set data. DCE sets the line low to indicate that it cannot
Ready) receive data.
Output flow
control If the fOutxDsrFlow member of the DCB is TRUE, then the
DTE will not send data if this line is low. It will resume
sending if the line is high.
If the fOutxDsrFlow member of the DCB is FALSE, then the
state of the line does not affect transmission.

DSR If the DSR line is low, then data that arrives at the
(Data Set port is ignored. If the DSR line is high, data that
Ready) arrives at the port is received.
Input flow
control This behavior occurs if the fDsrSensitivity member of
the DCB is set to TRUE. If it is FALSE, then the state
of the line does not affect reception.

RTS The RTS line is controlled by the DTE.


(Ready To Send)
Input flow If the fRtsControl member of the DCB is set to
control RTS_CONTROL_HANDSHAKE, the following flow control is
used: If the input buffer has enough room to receive data
(at least half the buffer is empty), the driver sets the
RTS line high. If the input buffer has little room for
incoming data (less than a quarter of the buffer is
empty), the driver sets the RTS line low.

If the fRtsControl member of the DCB is set to


RTS_CONTROL_TOGGLE, the driver sets the RTS line high
when data is available for sending. The driver sets the
line low when no data is available for sending. Windows
95 ignores this value and treats it the same as
RTS_CONTROL_ENABLE.

If the fRtsControl member of the DCB is set to


RTS_CONTROL_ENABLE or RTS_CONTROL_DISABLE, the
application is free to change the state of the line as
it needs. Note that in this case, the state of the line
does not affect reception.

The DCE will suspend transmission when the line goes low.
The DCE will resume transmission when the line goes high.

DTR The DTR line is controlled by the DTE.


(Data Terminal
Ready) If the fDtrControl member of the DCB is set to
Input flow DTR_CONTROL_HANDSHAKE, the following flow control is
control used: If the input buffer has enough room to receive data
(at least half the buffer is empty), the driver sets the
DTR line high. If the input buffer has little room for
incoming data (less than a quarter of the buffer is
empty), the driver sets the DTR line low.

If the fDtrControl member of the DCB is set to


DTR_CONTROL_ENABLE or DTR_CONTROL_DISABLE, the
application is free to change the state of the line as
it needs. In this case, the state of the line does not
affect reception.

The DCE will suspend transmission when the line goes low.
The DCE will resume transmission when the line goes high.

The need for flow control is easy to recognize when the CE_RXOVER error
occurs. This error indicates an overflow of the receive buffer and data
loss. If data arrives at the port faster than it is read, CE_RXOVER can
occur. Increasing the input buffer size may cause the error to occur less
frequently, but it does not completely solve the problem. Input flow
control is necessary to completely alleviate this problem. When the driver
detects that the input buffer is nearly full, it will lower the input
flow-control lines. This should cause the DCE to stop transmitting, which
gives the DTE enough time to read the data from the input buffer. When
the input buffer has more room available, the voltage on flow-control
lines is set high, and the DCE resumes sending data.

A similar error is CE_OVERRUN. This error occurs when new data arrives
before the communications hardware and serial communications driver
completely receives old data. This can occur when the transmission speed
is too high for the type of communications hardware or CPU. This can also
occur when the operating system is not free to service the communications
hardware. The only way to alleviate this problem is to apply some
combination of decreasing the transmission speed, replacing the
communications hardware, and increasing the CPU speed. Sometimes
third-party hardware drivers that are not very efficient with CPU
resources cause this error. Flow control cannot completely solve the
problems that cause the CE_OVERRUN error, although it may help to reduce
the frequency of the error.

Software flow control

Software flow control uses data in the communications stream to control


the transmission and reception of data. Because software flow control uses
two special characters, XOFF and XON, binary transfers cannot use software
flow control; the XON or XOFF character may appear in the binary data and
would interfere with data transfer. Software flow control befits
text-based communications or data being transferred that does not contain
the XON and XOFF characters.

In order to enable software flow control, the fOutX and fInX members of
the DCB must be set to TRUE. The fOutX member controls output flow control.
The fInX member controls input flow control.

One thing to note is that the DCB allows the program to dynamically assign
the values the system recognizes as flow-control characters. The XoffChar
member of the DCB dictates the XOFF character for both input and output
flow control. The XonChar member of the DCB similarly dictates the XON
character.

For input flow control, the XoffLim member of the DCB specifies the minimum
amount of free space allowed in the input buffer before the XOFF character
is sent. If the amount of free space in the input buffer drops below this
amount, then the XOFF character is sent. For input flow control, the XonLim
member of the DCB specifies the minimum number of bytes allowed in the
input buffer before the XON character is sent. If the amount of data in
the input buffer drops below this value, then the XON character is sent.

Table 4 lists the behavior of the DTE when using XOFF/XON flow control.

Table 4. Software flow-control behavior

Flow-control Behavior
character

XOFF received by DTE transmission is suspended until XON is received.


DTE DTE reception continues. The fOutX member of the DCB
controls this behavior.

XON received by DTE If DTE transmission is suspended because of a


previous XOFF character being received, DTE
transmission is resumed. The fOutX member of the DCB
controls this behavior.

XOFF sent from DTE XOFF is automatically sent by the DTE when the
receive buffer approaches full. The actual limit is
dictated by the XoffLim member of the DCB. The fInX
member of the DCB controls this behavior. DTE
transmission is controlled by the fTXContinueOnXoff
member of the DCB as described below.

XON sent from the XON is automatically sent by the DTE when the receive
DTE buffer approaches empty. The actual limit is
dictated by the XonLim member of the DCB. The fInX
member of the DCB controls this behavior.

If software flow control is enabled for input control, then the


fTXContinueOnXoff member of the DCB takes effect. The fTXContinueOnXoff
member controls whether transmission is suspended after the XOFF
character is automatically sent by the system. If fTXContinueOnXoff is
TRUE, then transmission continues after the XOFF is sent when the receive
buffer is full. If fTXContinueOnXoff is FALSE, then transmission is
suspended until the system automatically sends the XON character. DCE
devices using software flow control will suspend their sending after the
XOFF character is received. Some equipment will resume sending when the
XON character is sent by the DTE. On the other hand, some DCE devices will
resume sending after any character arrives. The fTXContinueOnXoff member
should be set to FALSE when communicating with a DCE device that resumes
sending after any character arrives. If the DTE continued transmission
after it automatically sent the XOFF, the resumption of transmission would
cause the DCE to continue sending, defeating the XOFF.

There is no mechanism available in the Win32 API to cause the DTE to behave
the same way as these devices. The DCB structure contains no members for
specifying suspended transmission to resume when any character is
received. The XON character is the only character that causes transmission
to resume.

One other interesting note about software flow control is that reception
of XON and XOFF characters causes pending read operations to complete with
zero bytes read. The XON and XOFF characters cannot be read by the
application, since they are not placed in the input buffer.

A lot of programs on the market, including the Terminal program that comes
with Windows, give the user three choices for flow control: Hardware,
Software, or None. The Windows operating system itself does not limit an
application in this way. The settings of the DCB allow for Software and
Hardware flow control simultaneously. In fact, it is possible to
separately configure each member of the DCB that affects flow control,
which allows for several different flow-control configurations. The
limits placed on flow-control choices are there for ease-of-use reasons
to reduce confusion for end users. The limits placed on flow-control
choices may also be because devices used for communications may not
support all types of flow control.
Communications Time-outs

Another major topic affecting the behavior of read and write operations
is time-outs. Time-outs affect read and write operations in the following
way. If an operation takes longer than the computed time-out period, the
operation is completed. There is no error code that is returned by ReadFile,
WriteFile, GetOverlappedResult, or WaitForSingleObject. All indicators
used to monitor the operation indicate that it completed successfully.
The only way to tell that the operation timed out is that the number of
bytes actually transferred are fewer than the number of bytes requested.
So, if ReadFile returns TRUE, but fewer bytes were read than were requested,
the operation timed out. If an overlapped write operation times out, the
overlapped event handle is signaled and WaitForSingleObject returns
WAIT_OBJECT_O. GetOverlappedResult returns TRUE, but dwBytesTransferred
contains the number of bytes that were transferred before the time-out.
The following code demonstrates how to handle this in an overlapped write
operation:

BOOL WriteABuffer(char * lpBuf, DWORD dwToWrite)


{
OVERLAPPED osWrite = {0};
DWORD dwWritten;
DWORD dwRes;
BOOL fRes;

// Create this write operation's OVERLAPPED structure hEvent.


osWrite.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
if (osWrite.hEvent == NULL)
// Error creating overlapped event handle.
return FALSE;

// Issue write
if (!WriteFile(hComm, lpBuf, dwToWrite, &dwWritten, &osWrite)) {
if (GetLastError() != ERROR_IO_PENDING) {
// WriteFile failed, but it isn't delayed. Report error.
fRes = FALSE;
}
else
// Write is pending.
dwRes = WaitForSingleObject(osWrite.hEvent, INFINITE);
switch(dwRes)
{
// Overlapped event has been signaled.
case WAIT_OBJECT_0:
if (!GetOverlappedResult(hComm, &osWrite, &dwWritten,
FALSE))
fRes = FALSE;
else {
if (dwWritten != dwToWrite) {
// The write operation timed out. I now need to
// decide if I want to abort or retry. If I retry,
// I need to send only the bytes that weren't sent.
// If I want to abort, I would just set fRes to
// FALSE and return.
fRes = FALSE;
}
else
// Write operation completed successfully.
fRes = TRUE;
}
break;

default:
// An error has occurred in WaitForSingleObject. This
usually
// indicates a problem with the overlapped event handle.
fRes = FALSE;
break;
}
}
}
else {
// WriteFile completed immediately.

if (dwWritten != dwToWrite) {
// The write operation timed out. I now need to
// decide if I want to abort or retry. If I retry,
// I need to send only the bytes that weren't sent.
// If I want to abort, then I would just set fRes to
// FALSE and return.
fRes = FALSE;
}
else
fRes = TRUE;
}

CloseHandle(osWrite.hEvent);
return fRes;
}

The SetCommTimeouts function specifies the communications time-outs for


a port. To retrieve the current time-outs for a port, a program calls the
GetCommTimeouts function. An applications should retrieve the
communications time-outs before modifying them. This allows the
application to set time-outs back to their original settings when it
finishes with the port. Following is an example of setting new time-outs
using SetCommTimeouts:

COMMTIMEOUTS timeouts;

timeouts.ReadIntervalTimeout = 20;
timeouts.ReadTotalTimeoutMultiplier = 10;
timeouts.ReadTotalTimeoutConstant = 100;
timeouts.WriteTotalTimeoutMultiplier = 10;
timeouts.WriteTotalTimeoutConstant = 100;

if (!SetCommTimeouts(hComm, &timeouts))
// Error setting time-outs.

Note Once again, communications time-outs are not the same as time-out
values supplied in synchronization functions. WaitForSingleObject, for
instance, uses a time-out value to wait for an object to become signaled;
this is not the same as a communications time-out.

Setting the members of the COMMTIMEOUTS structure to all zeros causes no


time-outs to occur. Nonoverlapped operations will block until all the
requested bytes are transferred. The ReadFile function is blocked until
all the requested characters arrive at the port. The WriteFile function
is blocked until all requested characters are sent out. On the other hand,
an overlapped operation will not finish until all the characters are
transferred or the operation is aborted. The following conditions occur
until the operation is completed:

 WaitForSingleObject always returns WAIT_TIMEOUT if a


synchronization time-out is supplied. WaitForSingleObject will
block forever if an INFINITE synchronization time-out is used.

 GetOverlappedResult always returns FALSE and GetLastError returns


ERROR_IO_INCOMPLETE if called directly after the call to
GetOverlappedResult.
Setting the members of the COMMTIMEOUTS structure in the following manner
causes read operations to complete immediately without waiting for any
new data to arrive:

COMMTIMEOUTS timeouts;

timeouts.ReadIntervalTimeout = MAXDWORD;
timeouts.ReadTotalTimeoutMultiplier = 0;
timeouts.ReadTotalTimeoutConstant = 0;
timeouts.WriteTotalTimeoutMultiplier = 0;
timeouts.WriteTotalTimeoutConstant = 0;

if (!SetCommTimeouts(hComm, &timeouts))
// Error setting time-outs.

These settings are necessary when used with an event-based read described
in the “Caveat?section earlier. In order for ReadFile to return 0 bytes
read, the ReadIntervalTimeout member of the COMMTIMEOUTS structure is
set to MAXDWORD, and the ReadTimeoutMultiplier and ReadTimeoutConstant
are both set to zero.

An application must always specifically set communications time-outs when


it uses a communications port. The behavior of read and write operations
is affected by communications time-outs. When a port is initially open,
it uses default time-outs supplied by the driver or time-outs left over
from a previous communications application. If an application assumes
that time-outs are set a certain way, while the time-outs are actually
different, then read and write operations may never complete or may
complete too often.

Conclusion

This article serves as a discussion of some of the common pitfalls and


questions that arise when developing a serial communications application.
The Multithreaded TTY sample that comes with this article is designed
using many of the techniques discussed here. Download it and try it out.
Learning how it works will provide a thorough understanding of the Win32
serial communications functions.

Bibliography

Brain, Marshall. Win32 System Services: The Heart of Windows NT. Englewood
Cliffs, NJ: Prentice Hall, 1994.
Campbell, Joe. C Programmer’s Guide to Serial Communications. 2d ed.
Indianapolis, IN: Howard W. Sams & Company, 1994.

Mirho, Charles, and Andy Terrice. “Create Communications Programs for


Windows 95 with the Win32 Comm API.?Microsoft Systems Journal 12 (December
1994). (MSDN Library, Books and Periodicals)

You might also like