Serial Communications

The device drivers that have been developed on the Pocket PC platform used for serial communications are known as streaming interfaces. A streaming interface is simply defined as any device that is attached to the Pocket PC that can provide or use a stream (or flow) of data. It can also be thought of as a "virtual" data source. This concept is similar to how a file is viewed inside the Windows CE object store, which is why talking to a device has been designed in a similar fashion to working with a file.

Just as you need to develop an application in order to open a file for reading or writing, you first need to open a connection with the attached device before you can perform any serial communications. Once you have opened the connection, you are returned a system resource handle to perform any other communication over the serial interface. This handle is similar to other system handles, as it needs to be properly closed when you are finished using it; otherwise, other applications will not be able to access the device.

In addition to reading and writing data, you can also use the device's resource handle to query or modify the serial connection settings, to control the state of the serial line, or even to talk directly to the serial device driver.

Opening a Connection

The first step in working with any serial device is to open the serial port using the CreateFile() function, which is defined as follows:

HANDLE CreateFile(LPCWSTR lpFileName, DWORD dwDesiredAccess,
   DWORD dwShareMode, LPSECURITY_ATTRIBUTES lpSecurityAttributes,
   DWORD dwCreationDisposition, DWORD dwFlagsAndAttributes,
   HANDLE hTemplateFile);

Any serial device you attempt to open uses the same naming scheme for the lpFileName parameter: the word COM followed by the specific communications port number to open, and ending with a colon (for example, to open the first serial port, you would use COM1:). The dwDesiredAccess parameter defines how the serial device should be accessed, and can be a combination of 0 (for query access), GENERIC_READ (for read access), or GENERIC_WRITE (for write access). The dwShareMode parameter must be set to 0, because a serial device has to be opened with exclusive access rights; and lpSecurityAttributes should be set to NULL.

Because you are accessing a device, rather than a file, dwCreationDisposition is always set to OPEN_EXISTING; and dwFlagsAndAttributes is set to FILE_ATTRIBUTE_NORMAL. Finally, hTemplateFile should be set to NULL.

If the call to CreateFile() succeeds, a valid open handle is returned, which you can use for further operations on the serial port. Otherwise, you will receive an INVALID_HANDLE_VALUE return error code.

For example, if you wanted to open the USB serial port (typically, COM9 on Pocket PC), you would do the following:

HANDLE hSerialPort = CreateFile(TEXT("COM9:"),
   GENERIC_READ|GENERIC_WRITE, 0, NULL, OPEN_EXISTING,
   FILE_ATTRIBUTE_NORMAL, NULL);
if(hSerialPort == INVALID_HANDLE_VALUE)
   return FALSE;

Configuring the Serial Port

Now that you have an open handle to a serial communications device, you can use a few functions to query and configure the opened port.

To set the recommended values that the device driver will reserve for the internal buffers used for input and output, call the SetupComm() function. Setting the internal buffer size can be useful when a particular protocol that uses the serial port has packets that are larger than the system default of 1,024 bytes.

Be aware, however, that streaming serial device drivers are not required to use your recommended size, and possibly could ignore these values. The SetupComm() function is defined as follows:

BOOL SetupComm(HANDLE hFile, DWORD dwInQueue, DWORD dwOutQueue);

The first parameter is the device handle that you previously opened with the call to CreateFile(). This is followed by dwInQueue and dwOutQueue, which are DWORD values for recommended input and output buffer sizes, respectively. The function returns TRUE if it successful; otherwise, it returns FALSE.

A somewhat more useful function is the GetCommProperties() API. This function enables you to examine a serial device to see what features and capabilities are supported on the device. The GetCommProperties() function is defined as follows:

BOOL GetCommProperties(HANDLE hFile, LPCOMMPROP lpCommProp);

The function takes two parameters: the open handle to the device and a pointer to a COMMPROP structure containing the device configuration settings when the function returns. If the call is successful, the function returns a TRUE value.

The COMMPROP structure looks like the following:

typedef struct _COMMPROP {
   WORD wPacketLength;
   WORD wPacketVersion;
   DWORD dwServiceMask;
   DWORD dwReserved1;
   DWORD dwMaxTxQueue;
   DWORD dwMaxRxQueue;
   DWORD dwMaxBaud;
   DWORD dwProvSubType;
   DWORD dwProvCapabilities;
   DWORD dwSettableParams;
   DWORD dwSettableBaud;
   WORD wSettableData;
   WORD wSettableStopParity;
   DWORD dwCurrentTxQueue;
   DWORD dwCurrentRxQueue;
   DWORD dwProvSpec1;
   DWORD dwProvSpec2;
   WCHAR wcProvChar[1];
} COMMPROP,*LPCOMMPROP;

The COMMPROP structure contains a substantial amount of information about the capabilities of the serial device you are querying:

  • wPacketLength is the size of the data packet requested, in bytes.

  • wPacketVersion is the version of the COMMPROP structure.

  • dwServiceMask is the driver type supported by the device. This will always be SP_SERIALCOMM.

  • dwReserved1 is not used.

  • dwMaxTxQueue is the maximum size of the internal output buffer. This will be 0 if there is no maximum size.

  • dwMaxRxQueue is the maximum size of the internal input buffer. This will be 0 if there is no maximum size.

  • dwMaxBaud is the maximum possible baud rate of the serial port. It can be any of the values shown in Table 5.1.

  • dwProvSubType is the communications provider type. It can be any of the values shown in Table 5.2.

  • dwProvCapabilities specifies the capabilities available on the specific provider type. This parameter can be one of the values shown in Table 5.3.

  • dwSettableParams are the communications device parameters that can be changed. This parameter can be one of the values shown in Table 5.4.

Table 5.1. Baud Rates

Value

Description

BAUD_075

75 bits per second

BAUD_110

110 bits per second

BAUD_134_5

134.5 bits per second

BAUD_150

150 bits per second

BAUD_300

300 bits per second

BAUD_600

600 bits per second

BAUD_1200

1,200 bits per second

BAUD_1800

1,800 bits per second

BAUD_2400

2,400 bits per second

BAUD_4800

4,800 bits per second

BAUD_7200

7,200 bits per second

BAUD_9600

9,600 bits per second

BAUD_14400

14,400 bits per second

BAUD_19200

19,200 bits per second

BAUD_38400

38,400 bits per second

BAUD_56K

56,000 bits per second

BAUD_57600

57,600 bits per second

BAUD_115200

115,200 bits per second

BAUD_128K

128,000 bits per second

BAUD_USER

Programmable baud rates are available

Table 5.2. Communication Provider Types

Value

Description

PST_RS232

RS-232C serial port

PST_PARALLELPORT

Parallel port

PST_RS422

RS-422 port

PST_RS423

RS-423 port

PST_RS449

RS-449 port

PST_MODEM

Modem device

PST_FAX

Fax device

PST_SCANNER

Scanner device

PST_NETWORK_BRIDGE

Network bridge device

PST_LAT

LAT device

PST_TCPIP_TELNET

TCP/IP Telnet protocol

PST_X25

X.25 standards

PST_UNSPECIFIED

Unspecified device

Table 5.3. Provider Capabilities

Value

Description

PCF_DTRDSR

Data Terminal Ready (DTR) and Data Set Ready (DSR) are supported.

PCF_RTSCTS

Request to Send (RTS) and Clear To Send (CTS) are supported.

PCF_RLSD

Receive Line Signal Detect (RLSD) is supported.

PCF_PARITY_CHECK

Parity checking is supported.

PCF_XONXOFF

Flow control is supported.

PCF_SETXCHAR

Settable XON and XOFF flow control are supported.

PCF_TOTALTIMEOUTS

Total (elapsed) timeouts are supported.

PCF_INTTIMEOUTS

Interval timeouts are supported.

PCF_SPECIALCHARS

Special character support is available.

PCF_16BITMODE

16-bit mode is available.

Table 5.4. Port Parameters

Value

Description

SP_PARITY

Parity can be set on the port.

SP_BAUD

Baud rate can be set on the port.

SP_DATABITS

The number of data bits can be set on the port.

SP_STOPBITS

The number of stop bits can be selected on the port.

SP_HANDSHAKING

Flow control can be set on the port.

SP_PARITY_CHECK

Parity checking can be set on the port.

SP_RLSD

Receive Line Signal Detect can be controlled on the port.

  • dwSettableBaud indicates the baud rates that can be used on the device. This will be one of the values specified by dwMaxBaud.

  • wSettableData indicates the number of data bits that can be set. This can be set to one of the values shown in Table 5.5.

  • wSettableStopParity is the number of stop bits and parity settings that can be selected. This can be set to one of the values shown in Table 5.6.

  • dwCurrentTxQueue is the current size of the internal output buffer.

  • dwCurrentRxQueue is the current size of the internal input buffer.

  • dwProvSpec1 is used for provider-specific information.

  • dwProvSpec2 is used for provider-specific information.

  • wcProvChar is used for provider-specific information.

Table 5.5. Data Bits

Value

Description

DATABITS_5

5 data bits

DATABITS_6

6 data bits

DATABITS_7

7 data bits

DATABITS_8

8 data bits

DATABITS_16

16 data bits

DATABITS_16X

Wide path data bit

Table 5.6. Stop Bits and Parity

Value

Description

STOPBITS_10

1 stop bit

STOPBITS_15

1.5 stop bits

STOPBITS_20

2 stop bits

PARITY_NONE

No parity

PARITY_ODD

Odd parity

PARITY_EVEN

Even parity

PARITY_MARK

Mark parity

PARITY_SPACE

Space parity

Therefore, to get the properties of the communications port you previously opened, you could do the following:

COMMPROP commProp;
memset(&commProp, 0, sizeof(COMMPROP));
GetCommProperties(hSerialPort, &commProp);

Finally, to actually configure the communications device that is attached to the serial port, you can use the GetCommState() and SetCommState() functions. The configuration of a serial device is determined by the current settings, which are located in a device-control-block (DCB) structure. This structure contains all of the relevant settings for the connected device.

To retrieve the current DCB for the port, you can call as follows:

BOOL GetCommState(HANDLE hFile, LPDCB lpDCB);

Here, the first parameter is the handle to the device that was opened with the initial call to CreateFile(). The lpDCB parameter is a pointer to a DCB structure. If the function succeeds, it will return a TRUE value.

To set the configuration of a communications device, you can call the SetCommState() API, which is defined as follows:

BOOL SetCommState(HANDLE hFile, LPDCB lpDCB);

The SetCommState() function takes the same parameters as GetCommState(), and will return TRUE if it successfully configures the serial device.

Be aware of a few things when SetCommState() is called. First, using the function will reinitialize all serial hardware and control settings, but it does not actually empty any incoming or outgoing data that is in the device driver's queue. Second, you should ensure that the XonChar and XoffChar members are not set to the same character; otherwise, SetCommState() will fail. Finally, when changing settings, it is usually easiest to first call GetCommState() to populate the structure for the current port values, modify the DCB structure with any changes you want to make, and then call SetCommState() with the same structure.

The device-control-block (DCB) structure looks like the following:

typedef struct _DCB {
   DWORD DCBlength;
   DWORD BaudRate;
   DWORD fBinary;
   DWORD fParity;
   DWORD fOutxCtsFlow;
   DWORD fOutxDsrFlow;
   DWORD fDtrControl;
   DWORD fDsrSensitivity;
   DWORD fTXContinueOnXoff;
   DWORD fOutX;
   DWORD fInX;
   DWORD fErrorChar;
   DWORD fNull;
   DWORD fRtsControl;
   DWORD fAbortOnError;
   DWORD fDummy2;
   WORD wReserved;
   WORD XonLim;
   WORD XoffLim;
   BYTE ByteSize;
   BYTE Parity;
   BYTE StopBits;
   char XonChar;
   char XoffChar;
   char ErrorChar;
   char EofChar;
   char EvtChar;
   WORD wReserved1;
} DCB, *LPDCB;

Table 5.7 describes the fields of the DCB structure.

Table 5.7. Device-Control-Block (DCB) Field Descriptions

Value

Description

DCBlength

Size of the DCB structure.

BaudRate

The baud rate at which the device is communicating, which can be one of the following values: CBR_110, CBR_300, CBR_600, CBR_1200, CBR_2400, CBR_4800, CBR_9600, CBR_14400, CBR_19200, CBR_38400, CBR_56000, CBR_57600, CBR_115200, CBR_128000, CBR_256000.

fBinary

Must be set to TRUE.

fParity

TRUE if parity checking is enabled.

fOutxCtsFlow

TRUE if the Clear To Send (CTS) signal is controlled by the serial line for output.

fOutxDsrFlow

TRUE if the Data Set Ready (DSR) signal is controlled by the serial line for output.

fDtrControl

Specifies how the Data Terminal Ready (DTR) signal should be handled. It can be set as follows:

DTR_CONTROL_DISABLE to disable it;

DTR_CONTROL_ENABLE to enable DTR; or

DTR_CONTROL_HANDSHAKE to enable DTR handshaking.

fDsrSensitivity

TRUE if the serial line is aware of any Data Set Ready (DSR) state changes. If set to FALSE, DSR changes will be ignored.

fTXContinueOnXoff

If set to TRUE, the transmission will stop when the input buffer is full (XoffLim bytes) and the serial driver has sent the XoffChar value to stop receiving bytes.

If set to FALSE, the transmission does not continue until the input buffer is empty (XonLim bytes) and the serial driver has sent the XonChar value to resume receiving bytes.

fOutX

TRUE if Xon/Xoff flow control is used when sending data.

fInX

TRUE if Xon/Xoff flow control is used when receiving data.

fErrorChar

Not used.

fNull

TRUE if NULL bytes are thrown away when received.

fRtsControl

Specifies how the Request To Send (RTS) flow control signal should be handled. It can be set as follows:

RTS_CONTROL_DISABLE to set the RTS line to a disabled state;

RTS_CONTROL_ENABLE to set the RTS line to an enabled state;

RTS_CONTROL_HANDSHAKE to specify that the driver should handle the RTS line;

RTS_CONTROL_TOGGLE to enable the RTS line if there is data in the output buffer; otherwise, disable it.

fAbortOnError

Not used.

fDummy2

Not used.

wReserved

Reserved.

XonLim

The minimum number of bytes required in the input buffer before the XON signal is sent.

XoffLim

The maximum number of bytes required in the input buffer before the XOFF signal is sent.

ByteSize

The number of bits in the bytes sent or received.

Parity

Specifies the parity scheme to use. It can be set to one of the following: NOPARITY, ODDPARITY, EVENPARITY, MARKPARITY, or SPACEPARITY.

StopBits

Specifies the number of stop bits per byte to be used. It can be set to one of the following: ONESTOPBIT, ONE5STOPBITS, or TWOSTOPBITS.

XonChar

The character to be used for sending and receiving XON.

XoffChar

The character to be used for sending and receiving XOFF.

ErrrorChar

Not used.

EofChar

The character to be used for sending and receiving the end-of-data marker.

EvtChar

The character to be used to signal that an event has occurred.

wReserved1

Not used.

For example, the following changes the baud rate on the port previously opened:

DCB commDCB;
BOOL fSuccess = FALSE;
memset(&commDCB, 0, sizeof(DCB));

// The current communications port settings
if(GetCommState(hSerialPort, &commDCB)) {
   // Can we change the baud rate? Let's check the commProp
   if(commProp.dwSettableParams && SP_BAUD) {
         commDCB.BaudRate = BAUD_57600;
         fSuccess = SetCommState(hSerialPort, &commDCB);
      }
   }

// Were we able to change the baud rate?
if(!fSuccess)
   MessageBox(NULL, TEXT("Could not modify the port's baud
      rate"), TEXT("Error!"), MB_OK);
Serial Timeouts

Before we look at how to read and write data from and to a serial device, it is important that your application set the communication timeout values after it opens the port. The timeout values are used to specify the amount of time that the serial driver will wait for data to be transmitted or received from the device. If it is not configured properly, your application could experience serial operations that never complete or that possibly finish before all the data in the queue is transferred.

To get the current timeout values for a serial device, you can call the GetCommTimeouts() function, which is defined as follows:

BOOL GetCommTimeouts(HANDLE hFile, LPCOMMTIMEOUTS
  lpCommTimeouts);

The first parameter, hFile, is the handle to the serial port that you originally obtained from calling the CreateFile() function. This is followed by a pointer to a COMMTIMEOUTS structure. If the function succeeds, it will return TRUE.

To set the timeout parameters for a specific serial port, you can use the following function:

BOOL SetCommTimeouts(HANDLE hFile, LPCOMMTIMEOUTS
  lpCommTimeouts);

The SetCommTimeouts() function takes the same parameters as GetCommTimeouts().

Both functions make use of the COMMTIMEOUTS structure, which specifies the values that should be used for both the reading and writing timeouts on a serial port. COMMTIMEOUTS is defined as follows:

typedef struct _COMMTIMEOUTS {
   DWORD ReadIntervalTimeout;
   DWORD ReadTotalTimeoutMultiplier;
   DWORD ReadTotalTimeoutConstant;
   DWORD WriteTotalTimeoutMultiplier;
   DWORD WriteTotalTimeoutConstant;
} COMMTIMEOUTS, *LPCOMMTIMEOUTS;

Using the COMMTIMEOUTS structure, there are basically three different ways that serial communication timeouts are used:

  • Read Interval Timeouts. When reading data from the serial line, you can set a timeout to occur when a specific amount of time has elapsed between receiving characters. By setting the ReadIntervalTimeout value (in milliseconds), a read operation will time out if the amount of time between receiving characters exceeds this value. Setting ReadIntervalTimeout to 0 indicates that interval timeouts are not used.

  • Read Byte Timeouts. To cause a serial read operation timeout after a certain number of bytes have been read, you can use the ReadTotalTimeoutMultipler and ReadTotalTimeoutConstant values. The timeout value is calculated by multiplying the value that you specify in ReadTotalTimeoutMultipler (in milliseconds) by the number of bytes you request in your read operation. This total is then added to ReadTotalTimeoutConstant (also in milliseconds) to determine the total read timeout time. Set both of these values to 0 if you do not want to use byte timeouts for read operations.

  • Write Byte Timeouts. Calculating the timeout value for serial write operations is similar to read byte timeouts. The value (in milliseconds) specified in WriteTotalTimeoutMultipler is multiplied by the number of bytes you request in your write operation. The total is then added to the WriteTotalTimeoutConstant value (in milliseconds) to determine the total write timeout time. Set both of these values to 0 if you do not want to use byte timeouts for write operations.

Note that if you want a read operation to automatically return any data in the input buffer, you can set the ReadIntervalTimeout value to MAXDWORD, and set ReadTotalTimeoutConstant and ReadTotalTimeoutMultiplier to 0.

To properly ensure that a read or write operation has timed out, you should look at the number of bytes that you have sent or received after the function returns. If a timeout has occurred and the function returned successfully, then the number of bytes transferred or received will be less than what was requested in the original function call.

For example, if you wanted to set the serial port to return immediately after a read operation (whether or not there was any data in the input queue), you would do the following:

COMMTIMEOUTS commTimeOut;
memset(&commTimeOut, 0, sizeof(COMMTIMEOUTS));

// Set the timeouts so that read operations will return
// immediately
commTimeOut.ReadIntervalTimeout = MAXDWORD;
commTimeOut.ReadTotalTimeoutConstant = 0;
commTimeOut.ReadTotalTimeoutMultiplier = 0;

SetCommTimeouts(hSerialPort, &commTimeOut);

Controlling Data Flow

In addition to setting the timeout values when reading and writing data over the serial line, you can also control the flow of data, starting and stopping data transmission.

To suspend the transmission of data on a serial port and place it in a break state, you can call the following:

BOOL SetCommBreak(HANDLE hFile);

The function takes a single parameter, the handle to the serial device that was opened with the call to the CreateFile() function. To continue transmitting data, you can call the ClearCommBreak() function, which is defined as follows:

BOOL ClearCommBreak(HANDLE hFile);

As with SetCommBreak(), the only parameter that ClearCommBreak() needs is the handle to the open serial port.

To remove any data from the internal read or write buffers of a serial device, call the following:

BOOL PurgeComm(HANDLE hFile, DWORD dwFlags);

The first parameter is the handle to the open serial device, and is followed by dwFlags, which specifies which queue to purge. You can set this to PURGE_RXCLEAR to clear the receive buffer, or to PURGE_TXCLEAR to purge the transfer buffer. In addition, if you want to ensure that the contents of the transfer buffer are sent before you purge them, you can also call the FlushFileBuffers() function (which takes only the handle to the serial device) before using PurgeComm().

To set the specific state of the serial device, you can use the EscapeCommFunction() API call. Using this function will send a direct command to the device so it can perform an extended function, such as setting the current break state or a specific signal.

BOOL EscapeCommFunction(HANDLE hFile, DWORD dwFunc);

The first parameter is a handle to the open serial device, and is followed by the function you want to perform. This can be any of the values shown in Table 5.8.

Reading and Writing Serial Data

Now that you have looked at all of the options for both configuring and using a serial device, it's time to actually send and receive some data. Because Pocket PC does not support overlapped I/O, remember that both reading and writing to the serial port will block until a timeout occurs, an event fires, or the amount of data requested is returned. A good practice to maintain when using the serial port is to perform the data transmission in a separate thread so that the blocking functions do not affect the performance of your application's user interface.

Sending data over a serial port is done in the same manner as writing data to a file using the WriteFile() function:

Table 5.8. Serial Port States

Value

Description

SETIR

Set the port to infrared mode.

CLRIR

Set the port to serial mode.

CLRDTR

Clear the Data Terminal Ready (DTR) signal.

CLRRTS

Clear the Request to Send (RTS) signal.

SETDTR

Send the Data Terminal Ready (DTR) signal.

SETRTS

Send the Request to Send (RTS) signal.

SETXOFF

Act as if an XOFF character has been received.

SETXON

Act as if an XON character has been received.

SETBREAK

Identical to the SetCommBreak function, as it will set the line into a break state until ClearCommBreak or EscapeCommFunction is called with the CLRBREAK flag.

CLRBREAK

Identical to the ClearCommBreak function, as it will clear the break state on the serial line.

BOOL WriteFile(HANDLE hPort, LPCVOID lpBuffer, DWORD
   nNumberOfBytesToWrite, LPDWORD lpNumberOfBytesWritten,
   LPOVERLAPPED lpOverlapped);

The first parameter is a handle to the open serial port. This is followed by lpBuffer, which is a pointer to a buffer containing the data that you want to send, and is followed by nNumberOfBytesToWrite, a DWORD value specifying how many bytes are in the buffer pointed to in the lpBuffer parameter. The next parameter, lpNumberOfBytesWritten, is a pointer to a DWORD value that specifies the total number of bytes that were actually sent with this operation. Finally, lpOverlapped should be set to NULL.

For example, if you wanted to send a small buffer over the serial port to a connected device at 9,600 baud, you could use the following:

COMMPROP commProp;
memset(&commProp, 0, sizeof(COMMPROP));
GetCommProperties(hSerialPort, &commProp);

DCB commDCB;
BOOL fSuccess = FALSE;
memset(&commDCB, 0, sizeof(DCB));

// The the current communication port settings
if(GetCommState(hSerialPort, &commDCB)) {
   // Can we change the settings? Let's check the commProp
   if(commProp.dwSettableParams && SP_BAUD) {
      commDCB.BaudRate = BAUD_9600;
      commDCB.fParity = PARITY_NONE;
      commDCB.StopBits = ONESTOPBIT;
      commDCB.ByteSize = 8;
      fSuccess = SetCommState(hSerialPort, &commDCB);
   }
}

if(!fSuccess)
   MessageBox(NULL, TEXT("Could not modify the port's settings"),
      TEXT("Error!"), MB_OK);

char cBuffer[256] = "\0";
DWORD dwSize = 0, dwWritten = 0;

sprintf(cBuffer, "Hello there");
dwSize = strlen(cBuffer);

WriteFile(hSerialPort, &cBuffer, dwSize, &dwWritten, NULL);

Sometimes it is necessary to send a single value, such as an interrupt character (Control-C), at the front of the send queue to alert the device you are talking with. To do so, you should call the TransmitCommChar() API function. Once a character has been placed at the front of the transmit queue, the function cannot be called again until the original character has been sent.

The TransmitCommChar() function is defined as follows:

BOOL TransmitCommChar(HANDLE hFile, char cChar);

The function simply takes a handle to the open serial device, followed by the character that you wish to send.

Reading data from the serial port is also comparable to reading data from a file; you use the ReadFile() function:

BOOL ReadFile(HANDLE hPort, LPVOID lpBuffer, DWORD
   nNumberOfBytesToRead, LPDWORD lpNumberOfBytesRead, LPOVERLAPPED
   lpOverlapped);

The first parameter, as with all serial API calls, is the handle to the port. This is followed by the lpBuffer parameter, which contains a buffer that receives the incoming data. The nNumberOfBytesToRead parameter should specify how many bytes are to be read, and is followed by lpNumberOfBytesRead, which contains the actual number of bytes retrieved from the port when the function returns (either when completed or by timeout). Finally, the lpOverlapped parameter is not supported, and should be set to NULL.

The following example reads 10 characters from the opened serial port:

char cBuffer[10] = "\0";
DWORD dwSize = 10, dwRead = 0;

COMMTIMEOUTS commTimeOut;
memset(&commTimeOut, 0, sizeof(COMMTIMEOUTS));
memset(&cBuffer, 0, 10);

// Set the timeouts so that read operations will only
// after our buffer is full (10 characters)
commTimeOut.ReadIntervalTimeout = 0;
commTimeOut.ReadTotalTimeoutConstant = 0;
commTimeOut.ReadTotalTimeoutMultiplier = 0;

SetCommTimeouts(hSerialPort, &commTimeOut);

ReadFile(hSerialPort, &cBuffer, dwSize, &dwRead, NULL);

NOTE:

To prevent the Pocket PC device from going into suspend mode while a serial operation is in effect, you can call the SystemIdleTimerReset() function while transmitting or receiving data. This function resets the Windows CE internal idle time timer, and will prevent the device from suspending. You should, however, use this function sparingly and with caution, as you can run down your device's battery by continuously calling it.


Retrieving Error and/or Status Information

To get information about an error that has occurred or to get the status of a serial device, you can call the ClearCommError() function. This function also clears any error flag that has been set, allowing input and output operations to continue after an error has occurred. The ClearCommError() function is defined as follows:

BOOL ClearCommError(HANDLE hFile, LPDWORD lpErrors,
  LPCOMSTAT lpStat);

As usual, the first parameter is the handle to the open communications port. This is followed by a pointer to a DWORD variable, lpErrors, which will be filled in with the type of error that has occurred. This can be one or more of the conditions described in Table 5.9.

Table 5.9. Communication Errors

Value

Description

CE_BREAK

A break condition has occurred.

CE_FRAME

There was a hardware framing error.

CE_IOE

An I/O error has occurred.

CE_MODE

The communication port requested is invalid.

CE_OVERRUN

A buffer overrun has occurred and data has been lost.

CE_RXOVER

A buffer overrun has occurred on the input buffer.

CE_RXPARITY

A parity error has occurred.

CE_TXFULL

The output buffer is full and cannot transmit any more characters.

The final parameter, lpStat, is a pointer to a COMSTAT structure, which contains the status information for the port. This parameter can be set to NULL if you do not need to get the status information. COMSTAT is defined as follows:

typedef struct _COMSTAT {
   DWORD fCtsHold;
   DWORD fDsrHold;
   DWORD fRlsdHold;
   DWORD fXoffHold;
   DWORD fXoffSent;
   DWORD fEof;
   DWORD fTxim;
   DWORD fReserved;
   DWORD cbInQue;
   DWORD cbOutQue;
} COMSTAT, *LPCOMSTAT;

Table 5.10 describes the fields of the COMSTAT structure.

Table 5.10. COMSTAT Structure Field Descriptions

Field

Description

fCtsHold

TRUE if the serial device is waiting for a Clear To Send (CTS) signal

fDsrHold

TRUE if the serial device is waiting for a Data Set Ready (DSR) signal

fRlsdHold

TRUE if the serial device is waiting for a Receive Line Signal Detect (RLSD) signal

fXoffHold

TRUE if the serial device is holding since it received a XOFF character

fXoffSent

TRUE if the serial device is waiting for the other serial device to send an XON in response to a transmitted XOFF character

fEof

TRUE if the end of file (EOF) character has been received

fTxim

TRUE if a character has been placed in the front of the transfer queue by the TransmitCommChar() function

fReserved

Reserved

cbInQue

The number of characters currently in the input queue

cbOutQue

The number of characters currently in the output queue

In addition to getting error and status information about the serial line, you can also query the status of any of the modem control signals using the GetCommModemStatus() function:

BOOL GetCommModemStatus(HANDLE hFile, LPDWORD lpModemStat);

The first parameter is the handle to the serial port you want to query, and is followed by a pointer to a DWORD variable that contains the current state of the modem control-registers. The lpModemStat value can return one or more of the values shown in Table 5.11.

Table 5.11. Modem Status Values

Value

Description

MS_CTS_ON

The Clear To Send (CTS) signal is on.

MS_DSR_ON

The Data Set Ready (DSR) signal is on.

MS_RING_ON

The Ring Indicate (RI) signal is on.

MS_RLSD_ON

The Receive Line Signal Detect (RLSD) signal is on.

Serial Event Notification

A somewhat simple mechanism is in place to determine when specific events occur on a serial connection. Monitoring serial event masks can be useful when you need to perform specific actions if certain conditions exist in a serial data transfer.

To set which events you want to monitor, you use the SetCommMask() API function:

BOOL SetCommMask(HANDLE hFile, DWORD dwEvtMask);

The first parameter is the handle to the open serial device, and is followed by one or more event masks for which you want to monitor events. Once you have turned on those masks, you can wait for a specific event to occur by calling the WaitCommEvent() function. This function will block (so you might want to consider calling it from within a helper thread) until that event occurs. The function is defined as follows:

BOOL WaitCommEvent(HANDLE hFile, LPDWORD lpEvtMask,
   LPOVERLAPPED lpOverlapped);

WaitCommEvent() takes three parameters. The first is the handle to the open serial device, followed by a pointer to a DWORD value that receives the event mask when an event occurs. The final parameter, lpOverlapped, is not used and should be set to NULL.

To query the current event mask for a serial device handle, you can also use the following function:

BOOL GetCommMask(HANDLE hFile, LPDWORD lpEvtMask);

The GetCommMask(), SetCommMask(), and WaitCommEvent() functions each use the same values for the event mask, which are defined in Table 5.12.

For example, you could use the following to monitor (usually in a separate thread) the serial line for any communication errors that occur:

DWORD dwCommMaskRecvd = 0;

// Set the port to watch for errors
SetCommMask(hSerialPort, EV_ERR);

// Wait for the error
if(WaitCommEvent(hSerialPort, &dwCommMaskRecvd, NULL)) {
   if(dwCommMaskRecvd == EV_ERR) {
      MessageBox(NULL, TEXT("An error has occurred"),
         TEXT("Error!"), MB_OK);

      // Handle the error with some code here
      // ...
   }
}

Table 5.12. Serial Event Masks

Value

Description

EV_BREAK

A break was detected.

EV_CTS

The Clear To Send (CTS) signal has changed state.

EV_DSR

The Data Set Ready (DSR) signal has changed state.

EV_ERR

An error has occurred. See the ClearCommError() function for more information.

EV_RING

A Ring Indicator (RI) was detected.

EV_RLSD

The Receive Line Signal Detect (RLSD) signal has changed state.

EV_RXCHAR

A character was received and is in the input queue.

EV_RXFLAG

An event character (specified by the DCB structure and the SetCommState() function) was received and is in the input queue.

EV_TXEMPTY

The output buffer is now empty.

Closing the Connection

Finally, to close an open device handle and free the resource for other processes to use the serial port, you need to call the CloseHandle() function:

BOOL CloseHandle(HANDLE hObject);

The only parameter you need to pass in is the handle to the previously opened port:

// Close the serial port
if(hSerialPort)
   CloseHandle(hSerialPort);

Differences between Windows and Pocket PC Serial APIs

Be aware of two minor differences between the Windows and Pocket PC implementations of the serial APIs:

  1. Pocket PC does not support overlapped I/O. Because Pocket PC devices do not support any method for overlapped I/O, you should consider creating a worker thread to handle your serial data transfer. This is necessary because the reading and writing of data operations will block any message processing until they are completed. Creating a helper thread enables your application's primary thread to continue processing messages from your user interface and the operating system while it waits for the serial transfer to complete.

  2. Pocket PC does not support security attributes on serial devices or files.