RS232 - overview of RS-232 standard - HW serever

 

 
  History RS-232-C
 
 
   
  In 1969 the Standards Committee, today known as Electronic Industries Association, worked out a general standard for data transmission devices. Those days data transmission meant digital data exchange between the main computer and a remote terminal or between two terminals without using a computer. The connection was established in such a way that terminals were connected via simple telephone lines and hence installed modems were needed on each end of the line to carry out data transmission. Without using modems digital data transmission via an analog channel had its shortcomings, mainly possible transmission errors. To get rid of these shortcomings the project had to be rather complicated.

Thus there emerged a task of producing a new standard which could guarantee reliable connection and simplicity of decisions, at the same time this standard could be used by various communication equipment manufacturers without problems of compatibility with devices by other manufacturers.Thus usage of such a solution could turn out profitable at repetition work.

The RS-232 standard was created according to these ideas. The demands to this standard defined signal levels, signal timing, signal function, mechanical plugs and information exchange protocols.

For more than 30 years from creation of EIA interface three standard modifications have been worked out:

  • 1969. The third review (RS-232-C) was to be accepted as a standard for computer manufacturers.
  • 1987. EIA association elaborated a new version of the standard which was called EIA-232-D.
  • 1991. EIA together with Telecommunications Industry association (TIA) elaborated a new version of the standard which was called EIA/TIA-232-E. Still, many keep calling this standard RS-232-C or simply RS-232.

Most equipment using RS-232 serial port has DB-25 plug, though standard documents didn't define a specific plug, nowadays most computers use the DB-9 plug for asynchronous data exchange.

Further development of the standard was oriented to elimination of defects of initial specifications of RS–232-C interface. Also some new standards have been worked out, amongst them we should distinguish RS–422 (balance system allowing line impedance up to 50 ohm), RS–423 (imbalance system with min line impedance of 450 ohm) and RS–449 (a standard with high baud rates having changed cirquit functions and using 37 pin D-type plug).

 

 
  About programming
 
 
   
  Unlike DOS, Windows 9x,NT,2000,XP work with hardware in a different way. While a DOS driver could be created with asm and have direct acsess to ports, it is a bit more complicated in Windows.It is connected with the fact that Windows, unlike DOS, is a multitasking system which makes it impossible to allow every application to directly change the hardware settings, as one application may fail to 'know' about the changes made to the hardware settings by some other application. Actually one can use asm in and out 378h functions under Windows 9x, but it is undesirable due to said above. To create programmes working with hardware under Windows one should use API (application programming interface). This interface allows to use Windows system services from application programmes. API realization is at that entrusted to the drivers. Windows Driver Developer Kit (DDK)is used to create drivers (there is a separate DDK for every Windows OS). Besides API one can use IOCTL codes (this method was widely used in DOS),but we shall deal with API functions only.

Work with hardware under Windows.

API standartizes work with hardware. To get access to hardware the following steps are used:

  • Get Handler of the device by calling CreateFile with the device name. For more information refer to. Windows SDK Help.
  • To control the device, call an API fuction for this device or send IOCTL(input - output control), the latter via DeviceIOCtl (for more information refer to Windows SDK Help).

In Windows all input/output ports are presented as files, so work with ports is mainly carried out via i/o functions of the file (CreateFile, CloseHandle, ReadFile, ReadFileEx, WriteFile and WriteFileEx). These functions organize the main interface for opening and closing the connection resource descriptor and carrying out read/write operations. API also includes a set of connection functions which provide access to connection resourses.

The usage of the I/O file and connection functions allows the application to perform the following tasks:

  • Getting the serial port descriptor.
  • Serial port configuration set and request.
  • Reading from or writing into the serial port.
  • Control of the given events set, which could occur for this serial port.

     

  • Sending the executive instructions to the driver of the device connected with the specified serial port; driver call-in is required for extended functions execution

 

 
  Open and Close Port
 
 
   
  Opening a port is actually getting the descriptor of the serial port. Due to API it can be done by using CreateFile function. This function results in the creation of a file with a reserved name.It is important when getting access to the corresponding port or device.After the descriptor has been obtained the work with the port is carried out the same way it is with files.

Let's examine CreateFile function. Its parameters are listed below.

Function syntax:

HANDLE CreateFile(LPCTSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode, LPSECURITY_ATTRIBUTES lpSecurityAttributes, DWORD dwCreationDistribution, DWORD dwFlagsAndAttributes, HANDLE hTemplateFile);

Parameters specifications:

lpFileName - pointer to a null-terminated string which defines the object name (in our case it is "COMx", where x is the system port number, or "\\\\.\\COM1" - if there's a need to open the port with its number above 9);

dwDesiredAccess - defines the type of the access to the object. The application can get 'read'-access, 'write'-access, 'read/write'-access or device request access. This parameter can use any combination of the following values:

  • 0 - specifies that the device request refers to an object. The application can make a request of the device attributes without referring to the device.

GENERIC_READ - specifies the 'read' access from the object. The data can be read from the file and the file pointer can be relocated.

GENERIC_WRITE - specifies the 'write' access to the object. The data can be written to the file and the pointer can be relocated.

  • Combination of GENERIC_WRITE and GENERIC_READ grants 'read/write' access to the object.

dwShareMode - a set of bit flags which specifies shared access to the object. If dwShareMode - 0, then access to the object cannot be shared. Further object opening operations will be rejected until the descriptor is closed. To use the object in the shared mode use the combination of the following values:

  • FILE_SHARE_DELETE - Windows NT only: Subsequent open operations on the object will succeed only if delete access is requested.
  • FILE_SHARE_READ - Subsequent open operations on the object will succeed only if read access is requested.
  • FILE_SHARE_WRITE - Subsequent open operations on the object will succeed only if write access is requested. Add _READ to allow read-write access.

lpSecurityAttributes - pointer to the SECURITY_ATTRIBUTES structure which specifies if the descriptor returned can be inherited by the child procedures. If lpSecurityAttributes is NULL, the descriptor cannot be inherited.

Windows NT: lpSecurityDescriptor member of the SECURITY_ATTRIBUTES structure defines the protection descriptor for the object. If lpSecurityAttributes is NULL the object gets the default protection descriptor. The system of the end file must support the protection on files and folders for this parameter to apply this effect to the files.

Windows 95: lpSecurityDescriptor member of the SECURITY_ATTRIBUTES structure is ignored.

DwCreationDistribution - defines the ways of opening existing files and the actions taken if the file doesn't exist. This parameter must contain one of the following values:

  • CREATE_NEW - creates a new file. The function fails if the specified file already exists.
  • CREATE_ALWAYS - creates a new file. If the file already exists the function replaces it with a new one.
  • OPEN_EXISTING - opens a file. Fails if the file doesn't exist.
  • OPEN_ALWAYS - opens the file if it exists. If the file doesn't exist the function creates a file just like CREATE_NEW.
  • TRUNCATE_EXISTING - opens the file. After the file is opened it is reduced until it's of zero size. The request process must open the file with at least GENERIC_WRITE access. Fails if the file doesn't exist.

dwFlagsAndAttributes - defines the file attributes and flags. Any combination of the following attributes except the FILE_ATTRIBUTE_NORMAL attribute is possible:

  • FILE_ATTRIBUTE_ARCHIVE - the file must be archived. The applications use this attribute to mark the file for reserve copying or deletion.
  • FILE_ATTRIBUTE_COMPRESSED - file or catalogue is compressed . If it's a file, it means that all data in the file are compressed. If it's a catalogue it means that compression is the default value for recently created files and subdirectories.
  • FILE_ATTRIBUTE_HIDDEN - the file is hidden. It shouldn't be included into the usual directory list.
  • FILE_ATTRIBUTE_NORMAL - the file has no attributes.
  • FILE_ATTRIBUTE_OFFLINE - the data of the file are not available at the moment. It shows that the data of the file have been moved to an off-line archive.
  • FILE_ATTRIBUTE_READONLY - the file is read-only. The applications can read the file but cannot write or delete the data.
  • FILE_ATTRIBUTE_SYSTEM - a part of the file is used by the operation system only.
  • FILE_ATTRIBUTE_TEMPORARY - the file is used for temporary data storing. File systems try to keep all data in memory for quicker access rather than flushing the data back to mass archive. A temporary file should be deleted by the application as soon as it is no longer needed.

Also possible is any combination of the following flags:

  • FILE_FLAG_WRITE_THROUGH
  • FILE_FLAG_OVERLAPPED
  • FILE_FLAG_NO_BUFFERING
  • FILE_FLAG_RANDOM_ACCESS
  • FILE_FLAG_SEQUENTIAL_SCAN
  • FILE_FLAG_DELETE_ON_CLOSE
  • FILE_FLAG_BACKUP_SEMANTICS
  • FILE_FLAG_POSIX_SEMANTICS

hTemplateFile - specifies that the descriptor with GENERIC_READ refers to a temporary file. The temporary file supports the file attributes and enhanced attributes for the created file.

To open the communication ports the CreateFile function can create a descriptor for the port like COM1serial port. For the ports dwCreationDistribution parameter should be OPEN_EXISTING, hTemplate parameter should be NULL. Read access, write access or read/write access can be specified; the descriptor should also be open for overlapped I/O.

If the port was opened successfully the function returns the handle descriptor to work with further on. If the port wasn't opened successfully the function wil return INVALID_HANDLE_VALUE.

CloseHandle function is used to close the port.

Function syntax:

BOOL CloseHandle(HANDLE hFile};

Parameters specifications:

hFile — the descriptor of the open file of communication port

Example of getting a descriptor of the serial port COM1:

HANDLE hCOM=CreateFile("\\\\.\\COM1",GENERIC_WRITE,0,NULL,OPEN_EXISTING,

FILE_FLAG_OVERLAPPED,NULL);

if (hCOM!=INVALID_HANDLE_VALUE)

{

...

CloseHandle(hCOM);

}

else cout << "Error Open" << endl;

 

 
  SetupComm
 
 
   
  As COM-ports are asynchronous connection devices, buffers for incoming and outgoing data are provided to make the work with the ports more effective. It is connected with the fact that the data bus baud rates greatly varies from the line baud rates, that's why to optimize the work of the system it is advisable to read data from/write data into the port by batches regardles of when they were received. One can also write data into the buffer and only then start the transmission - it is useful when the batch transmission is needed regardless of whether the system is busy. To set the size of the receiving and transmitting buffers SetupComm function is used.

Function syntax:

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

Parameters specifications:

hFile — the descriptor of the open communication port file;

dwInQueue - receiving buffer size, in bytes;

dwOutQueue - transmitting buffer size, in bytes

Note

Let's assume you exchange 1024-byte-batches of information with en external device. In this case the reasonable size of buffers will be 1200. SetupComm function is also interesting as it can just take the size you specify into consideration and correct it, or it can reject the specified size of buffers - in this case its work will end in an error.

Function usage example:

if (!SetupComm(hCOM,2048,2048)) cout << "Error Open" << endl;

 

 
  Communications Time-outs
 
 
   
  Another major thing affecting the work of read and write operations is time-outs. Time-outs have the following effect on read and write operations. If an operation takes longer than the calculated time-out period, the operation is finished. No error code is returned by ReadFile, WriteFile, GetOverlappedResult, or WaitForSingleObject. All indicators used to monitor the operation show that it finished successfully. The only way to tell that the operation has timed out is that the number of bytes actually transferred are lower than the number of bytes requested. So, if ReadFile returns TRUE, but fewer bytes were read than requested, the operation has 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 transferred before the time-out. The following code sample shows how to handle this in an overlapped write operation.

Function example:

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.

// Decide if you want to abort or retry. If you retry,

// you need to send only the bytes that weren't sent.

// If you want to abort, you 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.

// Decide if you want to abort or retry. If you retry,

// you need to send only the bytes that weren't sent.

// If you want to abort, then you would just set fRes to

// FALSE and return.

fRes = FALSE;

}

else

fRes = TRUE;

}

CloseHandle(osWrite.hEvent);

return fRes;

}

The SetCommTimeouts function specifies the connection time-outs for a port. To get the current time-outs for a port, a programme calls the GetCommTimeouts function. An applications should get the communications time-outs before changing them. This allows the application to set time-outs back to their original settings when it finishes with the port. Below 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 differ from time-out values contained in synchronization functions.E.g. WaitForSingleObject uses a time-out value to wait until an object is signaled; this differs from a communications time-out.

Setting the members of the COMMTIMEOUTS structure to ZERO causes no time-outs to occur. Nonoverlapped operations will suspend until all the requested bytes are transmitted. The ReadFile function is suspended until all the requested characters are received at the port. The WriteFile function is suspended until all requested characters are sent out. On the other hand, an overlapped operation won't end 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 suspend 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.

If the members of the COMMTIMEOUTS structure are set in the following manner the read operations complete immediately without waiting for 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 specify communications time-outs when using a communications port. The behavior of read and write operations is affected by communications time-outs. When a port is opened for the first time, it uses default time-outs supplied by the driver or time-outs remaining from a previous connection 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.

 

  PurgeComm
 
 
Before starting your work with the port it is desirable to clear the buffers; sometimes there's also need to clear the buffers when working with ports. For these purposes PurgeComm function can be used. This function can also stop read and write operations.

Function syntax:

BOOL PurgeComm(HANDLE hFile, DWORD dwFlags);

Parameters specifications:

hFile - descriptor of the open communication port file;

dwFlags - task to be performed.

This function can perform two tasks: clear the I/O queue in the driver or finish all the I/O operations. The tasks to be performed are specified by the following parameters:

  • PURGE_TXABORT — immediately stops all write operations even if they are not finished;
  • PURGE_RXABORT — immediately stops all read operations even if they are not finished;
  • PURGE_TXCLEAR — clears the out -queue in the driver;
  • PURGE_RXCLEAR — clears the in -queue in the driver.

Note

These values can be combined with the help of bitwise OR operation. It is recommended to clear the buffers when I/O errors occur and after the work with the port is finished.

 

  Work with DCB
 
 
   
The port setting is carried out with the help of the DCB (Device-Control Block) structure.By filling this structure with needed values you can change the connection parameters to those needed at the moment.

To initially create the DCB structure with necessary general settings (baud rates, patity, number of bits, number of stop bits and flow control) is carried out by the BuildCommDCB function.

Function syntax:

BOOL BuildCommDCB (LPCTSTR lpDef, LPDCB lpDCB);

This function is very useful as it sets the parameters of DCB structure which can be transmitted to the port just like *mode* command. If you remember MS DOS, null modem cable and connection between two computers: sending a string returns the DCB structure.

String example: baud=1200 parity=N data=8 stop=1

Function example:

DCB dcb;

ZeroMemory(&dcb,sizeof(DCB));

char buffer[100];

strcpy(buffer,"baud=1200 parity=N data=8 stop=1");

if (BuildCommDCB((char*)&buffer,&dcb))

{

if (dcb.BaudRate == CBR_1200) cout << "Yes " << endl;

}

else cout << " error config DCB";

In case of successful termination BuildCommDCB() returns any value but zero, otherwize zero. This function replaces only explicit members with some exceptions.

For 9600,n,8,1strings (not ending in x or p characters):

  • fInX, fOutX,fOutXDsrFlow, fOutXCtsFlow are set to FALSE
  • fDtrControl is set to DTR_CONTROL_ENABLE
  • fRtsControl is set to RTS_CONTROL_ENABLE

For 9600,n,8,1,x strings (ending in õ character):

  • fInX, fOutX are set to TRUE
  • fOutXDsrFlow,fOutXCtsFlow are set to FALSE
  • fDtrControl is set to DTR_CONTROL_ENABLE
  • fRtsControl is set to RTS_CONTROL_ENABLE

For 9600,n,8,1,x strings (ending in p character):

  • fInX, fOutX are set to FALSE
  • fOutXDsrFlow,fOutXCtsFlow are set to TRUE
  • fDtrControl is set to DTR_CONTROL_HANDSHAKE
  • fRtsControl is set to RTS_CONTROL_HANDSHAKE

After creating the DCB structure we must write it into the open port, it is done with the help of GetCommState function. When the structure is written into the port it may be needed to change the port parameters. The SetCommState function may be used to evade creating the structure anew .

Function syntax:

BOOL GetCommState (HANDLE hFile, LPDCB lpDCB);

BOOL SetCommState (HANDLE hFile, LPDCB lpDCB);

Parameters specifications:

HANDLE hFile, // descriptor of the communications device

LPDCB lpDCB // initial address of the structure

Functions example:

if(GetCommState(hCom, &dcb))

{

dcb.BaudRate = CBR_9600;

dcb.ByteSize = 7;

dcb.Parity = 2;

dcb.StopBits = 0;

SetCommState(hCom, &dcb);

}

Note

The SetCommState function configures the device according to its specification in the device-control block (a DCB structure). This function initializes the hardware and managing settings parameters again but doesn't clear in and out buffers.

The SetCommState function returns an error if XonChar = XoffChar in the DCB structure.

When using SetCommState to configure a 8250 chip port there are the following limits of ByteSize and StopBits values:

  • number of bits can be from 5 to 8 bits;
  • it's forbidden to use 5 bits together with 2 stop bits;
  • it's forbidden to use 6, 7, or 8 bits together with 1.5 stop bits.

 

  Read and Write Port
 
 
   
As work with the ports in Windows is carried out in the same way as work with files, reading from and writing into the port are carried out with the help of ReadFile and WriteFile functions correspondingly.

ReadFile function is used to read the information from the port

Function syntax:

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

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

Parameters specifications:

hFile — descriptor of the open communication port file;

lpBuffer — buffer address. In case of Write operation the data from this buffer will be transmitted to the port. In case of Read operation the data received from the line will be placed into this buffer;

nNumOfBytesToRead, nNumOfBytesToWrite — number of bytes to be received or or intended for transmission;

nNumOfBytesRead, nNumOfBytesWritten — number of actually received or transmitted bytes. If the number of received or transmitted bytes is lower than requested it's an error for a disc file but not necessarily for a communication port. The cause is to be found in timeouts.

LpOverlapped — the address of OVERLAPPED structure used for asynchronous operations.

In case of abnormal temination the functions return NULL, in case of normal termination - any other value.

Example of read-write operation:

DWORD numbytes, numbytes_ok, temp;

COMSTAT ComState;

OVERLAPPED Overlap;

char buf_in[6] = "Hello!";

numbytes = 6;

ClearCommError(handle, &temp, &ComState); // if temp is not null, the port is in the error state

if (!temp) WriteFile(handle, buf_in, numbytes, &numbytes_ok, &Overlap);

ClearCommError(handle, &temp, &ComState);

if(!temp) ReadFile(handle, buf_in, numbytes, &numbytes_ok, &Overlap); // the numbytes_ok variable contains the actual number of I/O bytes

Note

This example uses two structures COMSTAT and OVERLAPPED which we haven't yet discussed, and also ClearCommError function. In case of “three wires” connection we needn't consider OVERLAPPED structure (just use it as shown in the example).

 

  Event
 
 
   
Win32 API provides WaitCommEvent function used to wait for events which can occur for the specified communications device. At that the set of events checked by this function is contained in the events mask connected with the given device.

Function syntax:

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

Parameters specification:

hFile — the descriptor of the open file of the communication port;

lpEvtMask - pointer to a 32-bit variable which receives the mask specifying the type of event occurred. If an error occurs, null-value is returned, if the result is successful, one of the following values is returned:

  • EV_BREAK – inform when a send break is detected.
  • EV_CTS - inform about changes of CTS (clear-to-send) signal.
  • EV_DSR - inform about changes of DSR (data-set-ready)signal.
  • EV_ERR – inform about line-status errors. Line-status errors are CE_FRAME, CE_OVERRUN, and CE_RXPARITY.
  • EV_EVENT1 - An event of the first provider-specific type occured.
  • EV_EVENT2 - An event of the second provider-specific type occured.
  • EV_PERR – inform about printer errors.
  • EV_RING - A ring indicator was detected.
  • EV_RLSD - inform about changes of RLSD (receive-line-signal-detect) signal.
  • EV_RX80FULL - inform when the buffer is 80 percent full.
  • EV_RXCHAR - inform about the character reception and place the character into the front-end buffer.
  • EV_RXFLAG – inform about the receiving of a random character and its placing into the front-end buffer. The random character is specified in the DCB structure for the given device, which is recorded into the device by SetCommState function.
  • EV_TXEMPTY – inform about the transmission of the last character from the back-end buffer.

lpOverlapped - pointer to the OVERLAPPED structure. This structure is needed if hFile was opened with the help of FILE_FLAG_OVERLAPPED.

If hFile was opened with the help of FILE_FLAG_OVERLAPPED, lpOverlapped parameter shouldn't be NULL. This pointer should point at the active OVERLAPPED structure. If hFile was opened with the help of FILE_FLAG_OVERLAPPED and lpOverlapped is NULL, WaitCommEvent function is executed as an overlapped operation. In this case the OVERLAPPED structure should contain a descriptor to a manual-reset event object (created by using the CreateEvent function).

If hFile descriptor wasn't opened with the help of FILE_FLAG_OVERLAPPED, WaitCommEvent is not returned until one of the specified events (or an error) occurs.

Note

WaitCommEvent controls the events set for the specified connection resource. Use SetCommMask and GetCommMask functions to set and get a mask of the current eventfor the connection device.

If an overlapped operation cannot be stopped immediately the function returns FALSE and GetLastError function returns ERROR_IO_PENDING, by that indicating that an operation is being executed in the background. When it happens the system sets the hEvent member of the OVERLAPPED structure into not-signaled state until WaitCommEvent is returned, and then it sets it to the signaled state when one of the specified events (or an error) occurs. The request process can use one of the wait functions to detect the state of the event objects and then use GetOverlappedResult function to detect the WaitCommEvent operation result. GetOverlappedResult reports of a sucsessful or unsuccessful operation result, at the same time the transient pointer at lpEvtMask parameter is set to indicate the event occurred.

If the process is trying to change the event mask of the device descriptor by using the SetCommMask function while the overlapped WaitCommEvent operation is being executed, WaitCommEvent is immediately returned. The pointer of the lpEvtMask parameter variable is set to zero.

 

  EnumPorts
 
 
   
Besides usual read/write and port setting functions API has the EnumPorts function which allows to detect the quantity of ports present in the system.The convenience of this function is that at launch one can check the list of ports and use only those installed. Function syntax:
BOOL EnumPorts(LPTSTR pName, DWORD Level, LPBYTE pPorts, DWORD cbBuf, LPDWORD pcbNeeded , LPDWORD pcReturned);

Parameters specification:

LPTSTR pName - pointer to a null-terminated string, which contains the name of the server having the ports to be enumerated. If it is a NULL string, the information about the ports is taken from the local machine.

DWORD Level - detects the type of data structures stated in pPorts. This value can equal to 1 or 2.

LPBYTE pPorts - reference to the buffer which receives PORT_INFO_1 or PORT_INFO_2 structures array. Each structure contains data describing the available port. Level value specifies the structure type. Level 1 value specifies PORT_INFO_1 structures, and Level 2 value specifies PORT_INFO_2 structures.

DWORD cbBuf - defines the size (in bytes) of the buffer specified in pPorts.

LPDWORD pcbNeeded - reference to the variable which specifies the data size needed to record the enumerated ports. If cbBuf is smaller than this value EnumPorts isn't executed and GetLastError returns ERROR_INSUFFICIENT_BUFFER and the variable specified by pcbNeeded returns the needed size of the buffer. If cbBuf is equal to or greater than this value the variable specified by pcbNeeded contains the number of bytes saved to the buffer

LPDWORD pcReturned - reference to the variable that returns the number of PORT_INFO_* structures specified in pPorts. In other words this variable contains the number of ports available on the specified server.

Note

EnumPorts function can be executed successfully even if the server specified in pName doesn't detect a printer.

Example:

DWORD cbNeeded = 0,cReturned = 0;

EnumPorts(0,1,0,0,&cbNeeded,&cReturned); // Detection of the necessary memory amount

if (cbNeeded)

{

unsigned char* info = new unsigned char[cbNeeded];

memset(info,0,cbNeeded);

if(EnumPorts(0,1,info,cbNeeded,&cbNeeded,&cReturned ) ) // Reading the information about ports

{

PORT_INFO_1* pi = (PORT_INFO_1*)info; // filling the structure

while(((DWORD)pi-(DWORD)info)/sizeof(PORT_INFO_1)

{

AnsiString str = pi->pName; // Port name

if(str.Pos("COM"))

{

Strings->Add(str.Delete(str.Length(),1)); //adding the port to the list

}

pi ++;

}

delete []info;

}

}

 

  DCB
 
 
   
DCB sructure detects the management settings for the serial port of the connection device.

DCB structure contains the following members:

DWORD DCBlength - sizeof(DCB)

DWORD BaudRate - baud rate. There is a standard set of : all the rate constants look like CBR_

.

E.g. CBR_9600, CBR_115200.

Flags

DWORD fBinary - Eof character check mode – Windows doesn't support this mode (at least now). Mask $01

DWORD fParity - Parity control Mask $02 – enabling Parity control

DWORD fOutxCtsFlow - Mask $04 – enabling CTS signal control at bytes output.

DWORD fOutxDsrFlow - Mask $08 – enabling DSR signal control at bytes output.

DWORD fDtrControl - Mask $30 – DTR signal control type: values

  • DTR_CONTROL_DISABLE - signal deactivation.
  • DTR_CONTROL_ENABLE - the particular signal value can be specified via calling EscapeCommFunction.
  • DTR_CONTROL_HANDSHAKE - automatic sygnal management.

DWORD fDsrSensitivity - Mask $40 - enabling DSR signal control.

DWORD fTXContinueOnXoff - XOFF continues Tx

DWORD fOutX - Mask $100. Enabling XON XOFF out flow control

DWORD fInX - Mask $200 -//- when receiving

DWORD fErrorChar - Mask $400. Allowing to replace the received byte with a ErrorChar structure member if the reception was erratic (parity mismatch occurred).

DWORD fNull - Mask $800 enable null stripping – skip NULL characters when receiving

DWORD fRtsControl - Mask $3000. Control type:

  • RTS_CONTROL_DISABLE - signal deactivation.
  • RTS_CONTROL_ENABLE - the peculiar signal value can be specified via calling EscapeCommFunction.
  • RTS_CONTROL_HANDSHAKE - automatic signal management
  • RTS_CONTROL_TOGGLE – high level while there's data to be transmitted .

DWORD fAbortOnError - Mask $4000. Abortion of read/write operations if errors occur

DWORD fDummy2 - Not used

Other structure data

WORD wReserved - Not used

WORD XonLim - min number of bytes in the receiving buffer before the XON character is sent

WORD XoffLim - min number of bytes in the receiving buffer before the XOFFcharacter is sent

BYTE ByteSize - number of bits in a byte is from 4 to 8

BYTE Parity - 0-4=no,odd,even,mark,space parity bit,

BYTE StopBits - 0,1,2 = 1, 1.5, 2 – stop bits, 1,5 used only when 5 bits are sent to 8250 chip.

  • ONESTOPBIT - 1 stop bit
  • ONE5STOPBITS - 1.5 stop bits
  • TWOSTOPBITS - 2 stop bits

char XonChar - Tx and Rx XON symbol

char XoffChar - Tx and Rx XOFF symbol

char ErrorChar - Symbol replacing the byte received by mistake

char EofChar - end of input character

char EvtChar - received event character

WORD wReserved1 - Not used

 

  COMSTAT
 
 
   
COMSTAT structure contains information about the communications device. This structure is meant to be filled by ClearCommError function.

COMSTAT structure contains the following members:

DWORD fCtsHold - wait of the CTS (clear-to-send) signal transmitter to start transmitting data. TRUE - the transmitter is idle.

DWORD fDsrHold - wait of the DSR (data-set-ready) signal transmitter to start transmitting data. TRUE - the transmitter is idle.

DWORD fRlsdHold - wait of the RLSD (receive-line-signal-detect) signal transmitter to start transmitting data. TRUE - the transmitter is idle.

DWORD fXoffHold - wait of the transmitter after the XOFF character is received. TRUE - the transmitter is idle.

DWORD fXoffSent - wait of the transmitter after the XOFF character is transmitted. TRUE - the transmitter is idle. The transmission is stopped when the XOFF character is transmitted to the system which considers the next character as XON regardless of the actual character.

DWORD fEof - wait for reception of EOF (end-of-file) character. TRUE - the character is received.

DWORD fTxim - detection of the character queueing up for the transmission received for the connection device with the help of TransmitCommChar function . The connection device transmits such a character before other characters in the connection device out buffer. TRUE - this character exists.

DWORD fReserved - reserved, not used

DWORD cbInQue - detection of the number of bytes received from the line and not read by the ReadFile function.

DWORD cbOutQue - detection of the number of data bytes transmitted to the communications device by the user for further transmission in the line. It will be a NULL value for nonoverlapped write.

 

  OVERLAPPED
 
 
   
The OVERLAPPED structure contains the information used in compelled (asynchronous) I/O mode.

The OVERLAPPED structure contains the following members:

DWORD Internal - reserved to be used by the operation system. This member defines the state dependent of the system and is valid when the GetOverlappedResult function is returned without the ERROR_IO_PENDING enhanced error parameter.

DWORD InternalHigh - reserved to be used by the operation system. Ths member defines the length of the transmitted data and is valid when the GetOverlappedResult function returns TRUE.

DWORD Offset - detection of the file position before the transmittion is started. File position is byte shift from the file beginning. The request process places this member before calling ReadFile or WriteFile functions. when reading from or writing into communication devices.

DWORD OffsetHigh - detection of the beginning position of the byte shift to start transmission. This member is ignored when reading from or writing into communication devices.

HANDLE hEvent - identification of the events set for the status signal when the transmittion ends. The request process sets this member before calling ReadFile, WriteFile, ConnectNamedPipe or TransactNamedPipe functions.

Note

You can use the HasOverlappedIoCompleted macro to detect whether the asynchronous I/O operation is over. The CancelIo function can be used to cancel the asynchronous I/O operation.

 

 
  COMMTIMEOUTS
 
 
   
  The COMMTIMEOUTS structure is the second binding structure for the port setting. It detects the parameters of timeouts when receiving/transmitting data.

The members of the COMMTIMEOUTS structure have the following meaning:

DWORD ReadIntervalTimeout - sets max period of time (in milliseconds) allowed between two sequential characters being read from the line of commutation. During the 'read' operation the time period countdown takes its start when the first character is received. When the interval between two sequential characters exceeds the given value the 'read' operation finishes and all the data accumulated in the buffer are transmitted to the programme. Zero value of this member indicates that this timeout isn't used.

DWORD ReadTotalTimeoutMultiplier- sets the multiplier (in milliseconds) used to calculate general timeout of the 'read' operation. In every case this value is multiplied by the quantity of the characters requested for reading.

DWORD ReadTotalTimeoutConstant - sets a constant (in milliseconds) used to calculate the general timeout of the operation. In case of every 'read' operation this value is added to the result of multiplying ReadTotalTimeoutMultiplier by the quantity of the characters requested for the reading. ReadTotalTimeoutMultiplier and ReadTotalTimeoutConstant mean that general timeout for 'read' operation isn't used.

DWORD WriteTotalTimeoutMultiplier - sets the multiplier (in milliseconds) used to calculate general timeout of the 'write' operation. In every case this value is multiplied by the quantity of the characters being written.

DWORD WriteTotalTimeoutConstant - sets a constant (in milliseconds) used to calculate the general timeout of the 'write' operation. In case of every 'write' operation this value is added to the result of multiplying WriteTotalTimeoutMultiplier by the quantity of the characters being written. Zero value of the WriteTotalTimeoutMultiplier and WriteTotalTimeoutConstant members indicates that general timeout for the 'write' operation isn't used.

A bit more about timeouts. Imagine that we read 50 characters at a rate of 9 600 bps. If 8 bits per character, parity complement and one stop bit are used, then there'll be 11 bits (including the start bit) per character in the physical line. So 50 characters at a rate 9 600 bit/s will be received as

50x11x9600=0,0572916 sec

or approximately 57,3 milliseconds, if there's a zero interval between the reception of sequential characters. If the interval between the characters is about a half time of one character transmission, i.e. 0,5 milliseconds, the reception time is calculated as

50x11x9600+49x0,0005=0,0817916 sec

or approximately 82 milliseconds. If the reading process has amounted to 82 milliseconds, we can suppose there's been an external device error and stop the 'read' operation to evade the programme buzz. This is the general timeout of the 'read' operation. The general timeout of the 'write' operation is quite similar to it.

Below is the formula to be used when calculating, for example, the general timeout of the 'read' operation:

NumOfChar x ReadTotalTimeoutMultiplier + ReadTotalTimeoutConstant,

where NumOfChar — is the number of characters requested for the 'read' operation.

 

 
  PORT_INFO_*
 
 
   
  The information about the ports is available with the help of PORT_INFO_*structures, where PORT_INFO_1 structure is supported by all Windows versions and PORT_INFO_2 structure is supported starting with Windows NT. Let's consider these structures:

PORT_INFO_1structure serves as a descriptor of the ports supported by the system, it contains only the name of the port.

PORT_INFO_1 structure contains only one member:

LPTSTR pName - pointer to null-terminated string, which identifies the supported port (e.g., "COM1:", "LPT1:", "FILE", "\\server").

PORT_INFO_2 structure is used to identify the ports supported by the system,in comparison with PORT_INFO_1 this structure supports more parameters.

Members of PORT_INFO_2 structure:

LPSTR pPortName;

LPSTR pMonitorName;

LPSTR pDescription;

DWORD fPortType;

DWORD Reserved;

We won't consider PORT_INFO_2 structure in detail here, as it is used for the printer port settings detection.

 

 
  Ejemplos de Programación del Puerto SERIE  (RS232)
 
 
   
 

Serial I/O (RS232)

All Intel PCs have one or two serial ports usually referred to as Com1 and Com2. These comm ports provide voltages and pin outs consistent with RS-232C. Normally, these ports are used for mice, modems, printers, and the like.

Under DOS, it was possible to directly access these ports. However, Windows 95 and above forbid direct hardware access. Therefore, this page exists to help get around windows.

Generic References | Visual Basic | MS Access | Delphi | C++ Builder | Windows API


Generic References

Most of this was gathered by searching Altavista for rs232 (notice that I used lower case).

  • I suggest starting with this good Tutorial.
  • Here is the complete standard including the pin out, timing, and signal descriptions.
  • Voltage Specs
  • Free Application Software
  • Did you know that RS means Recommended Standard? This site also compares Simplex/Duplex and DTE/DCE. Free software shows how to write Windows 95 programs which access the serial port - comm32.zip/comm32.cpp (4kb)
  • The Visual Basic help file explains how to use Comm.drv to access serial ports. (Search on serial.)
        ' Open the serial port
      MSComm1.CommPort = 2              ' Set the port number
      MSComm1.Settings = "56000,N,8,1"  ' Set UART parameters
      MSComm1.PortOpen = True           ' Required, might lock port
      MsComm1.Output = "Text string"    ' Send data
      Buffer$ = Buffer$ & MSComm1.Input ' Read data
         
  • Another tutorial and links to various rs232 related sites.
  • Books and lots of very good links.
  • While Windows does not normally allow you to directly access the hardware, WIN95IO.dll privides a simple work around. Unfortunately, the distribution package does not provide any examples, just 4 function prototypes. The following 2 examples may help.

      vbOut &H378, MyData%
      temp% = vbInp &H378

    See www.softcircuits.com and www.planet-source-code.com/vb for VB tips.

  • Many hints are available from comp.lang.basic.visual.misc.
  • Hardware kits and book are available from Peter Anderson.
  • The comp.arch.hobbyist FAQ contains many links on how to interface to the Serial Port, Parallel Printer, and Joystick, as well as links to information on IR Standards.
  • Boondog Automation provides a number of related tutorials describing how to build your own hardware interfaces and how to write interface software. They also sell a parallel I/O, 8255-based ISA card - either as a kit or assembled.
  • Information on various UARTs, includes links, pin-outs, and an overview.
  • MarshallSoft provides a good FAQ explaining the UART with links to vendor specs. They also provide shareware libraries for a number of programming languages.
  • This system.ini patch from Newsgroups: microsoft.public.win95.msdosapps may help you run DOS applications under Windows 95 et al.
  • Portmon is a free GUI/device driver combination that monitors and displays all serial and parallel port activity on a system.
  • Reynolds Electronics appears to be a basic PC hobby store. It has all sorts of Basic Stamp, PIC, and data aquisition info.
  • B&B Electronics provides a variety of serial I/O devices (including a USB Data Acquisition Module - 8s/4d 12-bit ADC, 4 10-bit DAC, 8 digital I/Os, and software), books, and a pretty good free Technical Library.
  • ePanorama.net provides a large number of links to all kinds of useful information - serial, parallel, IR, usb, firewire, keyboard, mouse, joystick.
  • Isolated Full Duplex RS232C Interface provides schematics and a PC board for a self powered interface circuit the optically isolates the TxD and RxD lines from the PC serial port.

Visual Basic 6.0

You will use Comm.drv, MSComm.ocx, or MSComm32.ocx, depending on which compiler you are using. For example, in VisualBasic 6.0 Professional or Enterprise editions, first add the MSComm32.ocx to the Toolbox (add the Microsoft Comm Control 6.0 component), then add the control to your form and press F1 (Help).

Visual Basic Programmers Guide to Serial Communications by Richard Grier. Richard's page provides free ActiveX serial components for those that don't have MSComm32.ocx.

VB 6.0 provides VBTerm, a sample terminal emulation application under ..\samples\VB98\MSCOMM.

You can open a port as a file. This example, from comp.lang.basic.visual.misc, opens the printer

        Open "LPT1" For Output As #1
        Print #1, "Text" & chr$(ascii char)
        Close #1
    

MS Access 97 SR-2

I developed the following code to read barcodes from a reader connected to the serial port. This first section is an attempt to open the port as a file (this works with most languages - but not with MS Access); the second section is based on MSCOMM32.ocx (which mostly works). This discussion is included here because MS Access uses VBA - Visual Basic for Applictions.


MS Access 97 SR-2 - Fails

On my system, the following code causes Access to hang.

    Private Sub ReadPort_UIButton_Click()
    Dim temp As String
     Open "com1:" For Input As #1 Len = 3
     Input #1, temp                       ' This line hangs
     Close #1
    End Sub
    

I tested several variations with the same result

  • Without Len = 3
  • With temp as a variant
  • Both "com1" and "com1:"
  • Both Input and Binary

MS Access 97 SR-2 - Works

On the other hand, this works ... sort of.

    Private Sub OpenPort_UIButton_Click()
      ComPort_ActiveXCtl.CommPort = 1
        ' 9600 baud, no parity, 8 data, and 1 stop bit.
      ComPort_ActiveXCtl.Settings = "9600,N,8,1"
      ComPort_ActiveXCtl.InputLen = 0         ' Read entire buffer
      ComPort_ActiveXCtl.PortOpen = True      ' Opens the port
      ComPort_ActiveXCtl.RThreshold = 12
    End Sub
    
    Private Sub Form_Unload(Cancel As Integer)
      If ComPort_ActiveXCtl.PortOpen = True Then
        ComPort_ActiveXCtl.PortOpen = False
      End If
    End Sub
    
    Private Sub ComPort_ActiveXCtl_OnComm()
      Select Case ComPort_ActiveXCtl.CommEvent
    
        Case comEvReceive   ' Received RThreshold # of chars.
                  Test_UIEdit = ComPort_ActiveXCtl.Input
      End Select
    End Sub
    

The trick is to know that ControlName_OnComm() is called when RThreshold is not zero. Unfortunately, this doesn't work so well because barcodes came in various length and RThreshold=12 only works with 12-character barcodes. With these modification, any length barcode will work.

  • Set the threshold to one (1)
  • When the first character is read, set a timer and add the character to the barcode
  • As each additional character is read, re-start the timer
  • When the timer expires, call the function of your choice and pass the completed barcode. (The example below just writes it to a component.)
  • Finally, stop the timer and clear the string Barcode
    ' Global Variables for the Barcode Scanner
    Dim Global_Barcode As String
    Dim Start_Time As Date
    
    '********************************************
    
    '  Start of Barcode reader code
    
    Private Sub Form_Load()
      Start_Time = 0
      Global_Barcode = ""
      ComPort_ActiveXCtl.CommPort = 1
        ' 9600 baud, no parity, 8 data, and 1 stop bit.
      ComPort_ActiveXCtl.Settings = "9600,N,8,1"
      ComPort_ActiveXCtl.InputLen = 0         ' Read entire buffer
      ComPort_ActiveXCtl.PortOpen = True      ' Opens the port
      ComPort_ActiveXCtl.RThreshold = 1       ' Call **_OnComm
                                              '   for each character
    End Sub
    
    Private Sub ComPort_ActiveXCtl_OnComm()
      Select Case ComPort_ActiveXCtl.CommEvent
    
        Case comEvReceive   ' Received RThreshold # of chars.
            If Start_Time = 0 Then
              Start_Time = Timer
            Else
              If Timer - Start_Time > 200 Then ' in case of failure
                Form_Timer
                Exit Sub
              End If
            End If
            Global_Barcode = Global_Barcode & ComPort_ActiveXCtl.Input
            TimerInterval = 80   ' call Form_Timer 80 ms
                                 '   after last character
      End Select
    End Sub
    
    Private Sub Form_Timer()
      TimerInterval = 0    ' Disable the timer
      Start_Time = 0
      Barcode_UIEdit = Global_Barcode
      Global_Barcode = ""
      
      Barcode_UIEdit_Change
    End Sub
    
    Private Sub Form_Unload(Cancel As Integer)
      If ComPort_ActiveXCtl.PortOpen = True Then
        ComPort_ActiveXCtl.PortOpen = False
      End If
    End Sub
    
    '  End of Barcode reader code
    
    '********************************************
    

Notes:

  • In the help, the case statement has many options. To simplify this example, I removed all but the one that reads the data.
  • The only available timer is attached to the form. Therefore, it is hard to encapsulate this in a library.
  • MS Access 97 SR-2 will not display the MSComm help when you click on a command and press F1 ... unless you FIRST locate and open "C:\WIN98\HELP\COMCTL2.HLP". (Your path may be different.)
  • This code requires that MSCOMM32.ocx is installed on every machine that uses this feature. Simply copying the file is NOT sufficient. There are no instructions on how to do it correctly. Copying it installs the TypeLib resistry key, but not the several CLSID's or the License.
  • I had everything working perfect. When I tried to demonstrate the code to my boss, NONE of the case constants were defined ... Well they were 2 hours before! (And they work fine now.)

Delphi

Due to their size, the Delphi notes are here.


C++ Builder

C++ Builder uses the same API calls as Delphi. In addition, the it uses the came components.


_bios_serialcom

With Borland C, you can use _bios_serialcom() in bios.h to perform serial I/O.

    #include <bios.h>
      unsigned temp;
        // Open serial port at 1200 baud, 8 data bits, 
        //  No parity, 1 stop bit 
      temp = _bios_serialcom(_COM_INIT, 0,
                  _COM_CHR8 | _COM_NOPARITY | _COM_STOP1 | _COM_1200);
      temp = _bios_serialcom(_COM_RECEIVE, 0,  0);   // Read a character
      temp = _bios_serialcom(_COM_SEND   , 0, '*');  // Write a character
    

It is unclear which compilers provide bios.h. On 9-24-01, I found references that both Microsoft Visual C++ and Watcom C++ also claim to support _bios_serialcom(). However, a search of my Visual C++ 6 system found neither bios.h nor _bios_serialcom(). Therefore, I assume that Microsoft has dropped the support.

At any rate, I will NOT provide copies of bios.h to anyone.

Virtual Integrated Design provides various RS-232 circuits and free software examples.

Accessing the RS232 Port in DOS using BIOS.H functions provides a summary of the allowed options/contants. There is also a program showing how to use bioscom to access ports.

Microsoft provides a Simple Example Using _bios_serialcom(). The article states that this interface tends to loose data. In order to improve the reliability, the comport needs to call an interrupt routine which moves the data to a buffer. This was easy under DOS, but generally not allowed under Windows.


Windows API

Search the SDK help for Communications (I used the SDK that came with Delphi 5).

Use CreateFile to open a handle to a communications resource, such as com1 or lpt1.

Test the returned handle to verify that the port is not locked by another process.

Use GetCommState to determine the current settings, SetCommState to change the settings.

You can use BuildCommDCB to pass common parameters (baud, parity, etc.) to the DCB as a string. But you'll still need SetCommState to actually change the settings.

See the DCB help for the supported constants.

Some additional commands - TransmitCommChar, PurgeComm, FlushFileBuffers

Microsoft Platform SDK - Communication Overview, Communication Functions

The only property which is remembered between disconnects is the Baud Rate. Parity, StopBits and the like are reset each time the connection is opened. Unfortunately, both SetCommState and SetCommConfig are extremely slow. As a result, it is not practical to disconnect the comport between uses.


Warning

Some versions of MSComm32.ocx leak memory.

 

 

 

 
  Ejemplos de Programación del Puerto SERIE  (RS232) con Borland C++ Builder
 
 
   
 

 


Serial Communication with Borland C++ Builder
 


Contents...

Introduction
Example
Notes


Introduction...

I wish this site had been around when I was trying to figure out how to make serial communications work in Windows95. I, like many programmers, was hit with the double-whammy of having to learn Windows programming and Win95 serial comm programming at the same time. I found both tasks confusing at best. It was particularly frustrating because I had, over the years, written so much stuff (including lots of serial comm software) for the DOS environment and numerous embedded applications. Interrupt driven serial comm, DMA transfer serial comm, TSR serial comm, C, assembler, various processors... you name it, it had written it. Yet, everything I knew seemed upside-down in the message-driven-callback world of Windows.

After spending lots of money on books and seemingly endless effort, I have finally gotten enough of a handle on Win95 and serial comm programming to write something usable in this environment. Borland's C++ Builder has done a lot to help make Win95 programming easier and, once you know the tricks, the serial communications stuff is pretty easy, too.

The purpose of this site is to spare you hardship of my early efforts and get you up and running with your Win9x/NT serial comm programming as quickly as possible. If you're already familiar with using BCB to develop Windows programs, the example code should be plenty to get you going. You can also download the source code in BCBComm.zip. Good luck.


The Example...

In the example that follows we're going to write a bare-bones program to do serial communication. It will consist of a Form with a Memo object (for text I/O) and a Thread object that handles incoming serial data. There are no menus or other features to distract us from focusing on the serial comm aspect of the program. Obviously, you'll want to add these and other elements to a fully functioning program.

Fire up BCB and start a New Project. Place a Memo object on Form1. Using the Object Inspector, set Memo1 properties as follows:
 

  • Alignment = alClient
  • MaxLength = 0
  • ScrollBars = ssVertical
  • WantReturns = true
  • WantTabs = false
  • WordWrap = true

    Next, under the File | New menu, add a Thread Object. Use TRead for the class name when asked.

    You should now have two Unit files: Unit1.cpp for Form1 activity and Unit2.cpp for the thread.

    Using the Object Inspector again, create event handlers for the following events. The easiest way to create events handlers is as follows:
     

  • Go to the event tab sheet in Object Inspector.
  • Find the event of interest.
  • Double-click the blank space next to the event name.

    If you follow this scheme, Object Inspector will create and automatically name the event handlers to the same name used in our examples. OK, here are the objects and the events we need to handle:
     

  • Form1    OnCreate
  • Form1    OnClose
  • Memo1  OnKeyPress

    The framework for Unit1.cpp is now in place. Using the following listing as a guide, fill in Unit1.cpp with the following code. Be sure to note the #includes and global variables. If the framework for event handlers is missing in your program, DO NOT put it there by typing in the framework code! Go back and figure out what you missed. BCB MUST CREATE THE FRAMEWORK FOR YOU.

    The Main Form...

    //---------------------------------------------------------------------------
    #include <vcl\vcl.h>
    #pragma hdrstop
    
    #include "Unit1.h"
    
    // YOU MUST INCLUDE THE HEADER FOR UNIT2 (THE THREAD UNIT)
    
    #include "Unit2.h"
    
    // GLOBAL VARIABLES
    
    HANDLE hComm = NULL;
    TRead *ReadThread;
    COMMTIMEOUTS ctmoNew = {0}, ctmoOld;
    
    //---------------------------------------------------------------------------
    #pragma resource "*.dfm"
    TForm1 *Form1;
    //---------------------------------------------------------------------------
    __fastcall TForm1::TForm1(TComponent* Owner)
      : TForm(Owner)
    {
    }
    //---------------------------------------------------------------------------
    void __fastcall TForm1::FormCreate(TObject *Sender)
    {
      DCB dcbCommPort;
    
      // OPEN THE COMM PORT.
      // REPLACE "COM2" WITH A STRING OR "COM1", "COM3", ETC. TO OPEN
      // ANOTHER PORT.
    
      hComm = CreateFile("COM2",
                          GENERIC_READ | GENERIC_WRITE,
                          0,
                          0,
                          OPEN_EXISTING,
                          0,
                          0);
    
      // IF THE PORT CANNOT BE OPENED, BAIL OUT.
    
      if(hComm == INVALID_HANDLE_VALUE) Application->Terminate();
    
      // SET THE COMM TIMEOUTS IN OUR EXAMPLE.
    
      GetCommTimeouts(hComm,&ctmoOld);
      ctmoNew.ReadTotalTimeoutConstant = 100;
      ctmoNew.ReadTotalTimeoutMultiplier = 0;
      ctmoNew.WriteTotalTimeoutMultiplier = 0;
      ctmoNew.WriteTotalTimeoutConstant = 0;  
      SetCommTimeouts(hComm, &ctmoNew);
    
      // SET BAUD RATE, PARITY, WORD SIZE, AND STOP BITS.
      // THERE ARE OTHER WAYS OF DOING SETTING THESE BUT THIS IS THE EASIEST.
      // IF YOU WANT TO LATER ADD CODE FOR OTHER BAUD RATES, REMEMBER
      // THAT THE ARGUMENT FOR BuildCommDCB MUST BE A POINTER TO A STRING.
      // ALSO NOTE THAT BuildCommDCB() DEFAULTS TO NO HANDSHAKING.
    
      dcbCommPort.DCBlength = sizeof(DCB);
      GetCommState(hComm, &dcbCommPort);
      BuildCommDCB("9600,N,8,1", &dcbCommPort);
      SetCommState(hComm, &dcbCommPort);
    
      // ACTIVATE THE THREAD. THE FALSE ARGUMENT SIMPLY MEANS IT HITS THE
      // GROUND RUNNING RATHER THAN SUSPENDED.
    
      ReadThread = new TRead(false);
    }
    //---------------------------------------------------------------------------
    void __fastcall TForm1::FormClose(TObject *Sender, TCloseAction &Action)
    {
      // TERMINATE THE THREAD.
    
      ReadThread->Terminate();
    
      // WAIT FOR THREAD TO TERMINATE,
      // PURGE THE INTERNAL COMM BUFFER,
      // RESTORE THE PREVIOUS TIMEOUT SETTINGS,
      // AND CLOSE THE COMM PORT.
    
      Sleep(250);
      PurgeComm(hComm, PURGE_RXABORT);
      SetCommTimeouts(hComm, &ctmoOld);
      CloseHandle(hComm);
    
    }
    //---------------------------------------------------------------------------
    void __fastcall TForm1::Memo1KeyPress(TObject *Sender, char &Key)
    {
      // TRANSMITS ANYTHING TYPED INTO THE MEMO AREA.
    
      TransmitCommChar(hComm, Key);
    
      // THIS PREVENTS TYPED TEXT FROM DISPLAYING GARBAGE ON THE SCREEN.
      // IF YOU ARE CONNECTED TO A DEVICE THAT ECHOES CHARACTERS, SET 
      // Key = 0 WITHOUT THE OTHER STUFF.
    
      if(Key != 13 && (Key < ' ' || Key > 'z')) Key = 0;
    }
    //---------------------------------------------------------------------------

    Now we turn our attention to the thread code in Unit2.cpp. The framework should already be in place. Use this listing as a guide and fill in Unit2.cpp with the following code.

    The Thread...

    //---------------------------------------------------------------------------
    #include <vcl\vcl.h>
    #pragma hdrstop
    
    // YOU MUST INCLUDE THE HEADER FOR UNIT1
    
    #include "Unit1.h"
    #include "Unit2.h"
    
    extern HANDLE hComm;
    char InBuff[100];
    
    //---------------------------------------------------------------------------
    //   Important: Methods and properties of objects in VCL can only be
    //   used in a method called using Synchronize, for example:
    //
    //      Synchronize(UpdateCaption);
    //
    //   where UpdateCaption could look like:
    //
    //      void __fastcall TRead::UpdateCaption()
    //      {
    //        Form1->Caption = "Updated in a thread";
    //      }
    //---------------------------------------------------------------------------
    __fastcall TRead::TRead(bool CreateSuspended)
      : TThread(CreateSuspended)
    {
    }
    //---------------------------------------------------------------------------
    
    void __fastcall TRead::DisplayIt()
    {
    
      // NOTE THAT IN THIS EXAMPLE, THERE IS NO EFFORT TO MONITOR
      // HOW MUCH TEXT HAS GONE INTO Memo1. IT CAN ONLY HOLD ABOUT 32K.
      // ALSO, NOTHING IS BEING DONE ABOUT NON-PRINTABLE CHARACTERS
      // OR CR-LF'S EMBEDDED IN THE STRING.
    
      // DISPLAY THE RECEIVED TEXT.
    
      Form1->Memo1->SetSelTextBuf(InBuff);
    
    }
    
    //---------------------------------------------------------------------------
    void __fastcall TRead::Execute()
    {
      //---- Place thread code here ----
    
    
      DWORD dwBytesRead;
    
      // MAKE THE THREAD OBJECT AUTOMATICALLY DESTROYED WHEN THE THREAD
      // TERMINATES.
    
      FreeOnTerminate = true;
    
      while(1)
      {
    
       // TRY TO READ CHARACTERS FROM THE SERIAL PORT.
       // IF THERE ARE NONE, IT WILL TIME OUT AND TRY AGAIN.
       // IF THERE ARE, IT WILL DISPLAY THEM.
    
    
         ReadFile(hComm, InBuff, 50, &dwBytesRead, NULL);        
    
         if(dwBytesRead)
         {
           InBuff[dwBytesRead] = 0; // NULL TERMINATE THE STRING
           Synchronize(DisplayIt);
         }
    
       }
    
    }
    
    //---------------------------------------------------------------------------
    

    One last thing... To do a synchronized call to DisplayIt() from within the thread's Execute() function, DisplayIt() it must be declared as a __fastcall type in the header file. Here's how to do it.
     

    Open the header file "unit2.h" and add the DisplayIt() line as shown below:
     

    //---------------------------------------------------------------------------
    class TRead : public TThread
    {
    private:
    protected:
      void __fastcall DisplayIt(void); // ADD THIS LINE
      void __fastcall Execute();
    public:
      __fastcall TRead(bool CreateSuspended);
    };
    //---------------------------------------------------------------------------
    
    
    

    Notes...

    As mentioned earlier this example focuses strictly on the core elements that make the serial communication functions work. In its present form it's unlikely to be particularly useful or acceptable in an actual application. In other words, you need to add what's missing. If you've followed along this far, that should not be too difficult. To minimize any confusion on what's missing, I'll highlight some of the areas that should be addressed:
     

  • There is little or no provision for error handling
  • The 32K display limit of the Memo object is not handled
  • For proper text display in Memo, ignore linefeeds and replace carriage returns with a CR-LF pair
  • Menus
  • Storing incoming serial data to disk
  • Sending disk contents out serial port
  • Handshaking
  • Protocol (Xmodem, Zmodem, etc.)

    There are several ways to test your work. One method is to perform a loop-back test by jumping pins 2 and 3 on your computer's RS-232 connector. With the loop-back connection anything you type into the Memo area will be echoed back.

    Here are some online references that you might find useful:

  • Serial Communications in Win32 . This is a comprehensive reference.
  • www.ontrak.net . Excellent example of simple serial port access.
  • www.temporaldoorway.com . Good example of threaded serial program with overlapped I/O.
  • www.codeguru.com . Yet another example (more for VC++).

    Good luck.

  •  


    Windows API Hook SDK Manual

    Validtec Software, Inc.
    http://www.validtec.com

    API Hook SDK is a Software Development Kit for hooking Windows 32bit API functions,it will call your own function instead of Windows call some API. API Hook SDK also can hookthe functions in 3rd Appliction's DLL.


    RTC Client API v1.2 SDK

    The Real-time Communications (RTC) Client API is a set of COM interfaces and methods designed to create PC-PC, PC-phone, phone-phone audio/video calls, or text-only Instant Messaging (IM) sessions over the Internet.  The API also allows to:

    ·       Add application and whiteboard sharing to a  PC-to-PC session.

    ·       Use presence information to track the location of contacts for communication purposes.


    Virtual Serial Port ActiveX Control 3.0


    Virtual Serial Port is a powerful advanced ActiveX Control that allows your application to create custom additional virtual serial port in system and fully control it. Created virtual serial port looks like real serial port for other Windows applications. From your application you can control data sent to virtual serial port by other applications and respond to them by sending own data to virtual serial port.


    MarshallSoft GPS Component for C/C++ 1.2


    GPS client library component reads and decodes standard GPS NMEA 183 sentences from the RS232 serial port as well as compute great circle distances and bearings. Several C/C++ example programs with full source code are included. Requires Windows C/C++ compiler. Registration is $105 for email delivery.


    MarshallSoft GPS Component for eVC 1.2


    Win/CE GPS client library component reads and decodes standard GPS NMEA 183 sentences from the RS232 serial port as well as compute great circle distances and bearings. Several eVC example programs with full source code are included. Requires Windows Embedded Tools eVC compiler. Registration is $105 ($195 with source) for email delivery.


    MarshallSoft GPS Component for Visual Basic 1.2


    GPS client library component reads and decodes standard GPS NMEA 183 sentences from the RS232 serial port as well as compute great circle distances and bearings. Several Visual Basic example programs with full source code are included. Requires 32-bit Visual Basic or VB.Net. Registration is $105 for email delivery.


    Add GPS support to your desktop
     

    Sample Image - GPS_support.jpg

    Introduction

    GPS stand for Global Positioning System and is a device for finding useful information about geographical location (in other hand, Longitude and Latitude). But these information are not limited to Longitude and Latitude. Some other information like Time, Date, Compass information, Speed and etc. can be obtained from GPS.

    GPS devices use satellites (minimum of 3 and maximum of 14) to find your location on the earth. Most modern GPS devices now support a protocol called NMEA0183. This protocol used to transfer geographical location information from GPS to your desktop computer or PDA.

    Connecting GPS to your PC

    To transfer data from GPS to your computer, you need a serial cable that can connect to your GPS. After this, you must configure your serial port to communication go right. In this project, I configure my serial port as follow:

    COM Port: COM2
    Baud Rate: 4800
    Data Bits: 8
    Parity: No Parity
    Stop Bits: 2

    You must refer to your GPS manual to configure your serial port properly.

    NMEA0183

    As I mentioned before, NMEA0183 is a standard protocol to transfer location information from GPS to your PC. I use a free library that can be added to your project without any modification.

    you can find this library in above file. Thanks Sam Blackburn for his nice library. This protocol consists of several sentences that every sentences start by $. For example the sentence

    "$GPGGA,104435.12,3337.19,N,11158.43,W,1,06,4.5,,,,,,"

    has these informations (in other hand, translated to):

    Time: 10:44:35 AM UTC
    Latitude: 33 37.19 North
    Longitude: 111 58.43 West
    Number of satellites: 6

    Using Library

    First of all, you must add nmea0183.lib to your project. For this reason, Select Project, Settings and then Link Tab. In Object/Library Modules type path of nmea0183.lib library (for example: .\NMEA0183\Debug\NMEA0183.lib).

    Then press OK to return to project.

    If you use a dialog in your project, Add "NMEA0183.h" to your dialog header file. In other cases add "NMEA0183.h" to main header file.
    Now, Add a member variable named nmea0183 with variable type NMEA0183, like this:
    NMEA0183 nmea0183;

    After this, you must add header and implementation files for serial communication. I use CSerialCom from Shibu K.V. Find his article at this URL: A simple Class for Implementing Serial Communication in Win-9X/2000

    Add a member variable named Serial with variable type CSerialCom, like this:
    CSerialCom Serial;

    Now configure your serial port by:
     

    //Open Port: COM2
    Serial.OpenPort("COM2");
    
    //Configure COM2
    Serial.ConfigurePort(4800,          //Baud Rate
                         8,             //Data Bits
                         FALSE,         //Has Parity
                         NOPARITY,      //Parity Bits
                         TWOSTOPBITS    //Stop Bits
                        );
    


    If anything goes right, you can obtain information from GPS by Read member function, as follow: Note: any NMEA0183 sentences will finished by "\r\n" characters.
     

    //Read data from GPS 
       
    char Data[100]="\0";
    
    BYTE DataByte='\0';
    int nIndex=0;
    
    //Obtaining information from GPS character by character
    //Note: NMEA0183 sentences will finished by "\r\n" characters!
    BOOL Return=Serial.ReadByte(DataByte);
    while (DataByte!='\r' && DataByte!='\n' && Return==TRUE)
    {
       Data[nIndex]=DataByte;
       nIndex++;
       Return=Serial.ReadByte(DataByte);
    }
       
    Data[nIndex++]='\r';
    Data[nIndex++]='\n';
    Data[nIndex++]='\0';
    
    //Remove garbage characters if any exists!
    nIndex=0;
    while (Data[nIndex]!='$' && nIndex<100)
       nIndex++;
    

     

    Now, it's time to work with nmea0183 member variable. NMEA0183 class has two very important member functions and one member variable:

    1. AddTail() to add strings retreived from GPS.
    2. Parse() to parse retreived strings. And
    3. PlainText a member variable that stores plain text of translated string.

    Solution

    I use a thread in my dialog based project that it's duty is to communicate to GPS and show location information in a plain text. My thread function is as below:
     

    UINT MyThread(LPVOID pParam)
    {
       CSerialCom Serial;
       NMEA0183 nmea0183;
       CStringList StrList;
    
       if (!Serial.OpenPort("COM2"))
       {   
          AfxMessageBox("Can't Open Port!");
          return 0;
       }
       
       Serial.ConfigurePort(4800, 8, FALSE, NOPARITY, TWOSTOPBITS);
       
       //Read data from GPS
       char Data[100]="\0";
       
       BYTE DataByte='\0';
       int nIndex=0;
       
       BOOL Return=Serial.ReadByte(DataByte);
       while (DataByte!='\r' && DataByte!='\n' && Return==TRUE)
       {
          Data[nIndex]=DataByte;
          nIndex++;
          Return=Serial.ReadByte(DataByte);
       }
       
       Data[nIndex++]='\r';
       Data[nIndex++]='\n';
       Data[nIndex++]='\0';
       
       //Remove garbage characters
       nIndex=0;
       while (Data[nIndex]!='$' && nIndex<100)
          nIndex++;
       
       StrList.RemoveAll();
       StrList.AddTail(Data+nIndex);
       
       //Parse strings   
       POSITION position = StrList.GetHeadPosition();
       
       while(position!=NULL)
       {
          nmea0183 << StrList.GetNext(position);
          
          if (!nmea0183.Parse())
             AfxMessageBox("Can't parse!");
          else
          {   
             if (nmea0183.PlainText.GetLength()!= 0)
             {
                CString sMsg;
                sMsg.Format("%s",(const char *) nmea0183.PlainText);
                AfxMessageBox(sMsg);
             }
          }
       }
       Serial.ClosePort();
       
       return 0;
    }
    
    

    When you want to show GPS Information, use this thread as below:

    AfxBeginThread(MyThread,NULL);

     

    Linking NMEA0183 Library

    We can link NMEA0183 library in two ways: Statically Linking and Dynamically Linking. For both of these situations, you must choose proper switches for compiler to compile NMEA0183 library. If you want to compile your application and link it statically with MFC, NMEA0183 should be compiled and linked statically. And if you want to compile your application and link it dynamically with MFC, you should compile NMEA0183 library dynamically.

    Further Information

    For further information about NMEA0183 protocol and new specifications, visit NMEA.org


    x10 Firecracker message format and a C++ class to use it
     

    Sample Image - x10Demo.jpg

    Introduction

    The Firecracker(C) is a product by X10 industries. It is a matchbox sized unit that plugs into a serial port and transmits commands wirelessly one-way to a receiving unit that's plugged into an AC outlet, which then sends the same signal through the home's AC wiring to all other x10 control modules. There are several types of modules, the two main ones being a light controller and an appliance controller. This system can accommodate 16 house codes, with 16 controllers in each house code, for a total of 256 devices. The individual controllers can be set to a particular house and unit code by rotary switches. There are several different commands that can be sent out; On, Off, Dim, Brighten, All lights on, All lights off and All controllers off. The On and Off signals are sent to a house and unit code. This causes that controller to go into a listen mode after which it will respond to dim and brighten commands which aren't provided with a target. Each dim or brighten command causes about a 5% difference. The All lights on and All lights off are targeted to only a house code which would cause all light controllers (up to the max of 16) with that house code to respond appropriately. Finally, the All units off will turn off all light and appliance modules on that house code.

    Background

    The Firecracker does not rely on normal serial communications, instead it is driven by the RTS & DTR signal lines which also provide its power. The command frame is pretty simple, consisting of only 5 bytes. Refer to table 4 for the message layout. A couple of tricky areas are initialization of the device and clocking out the signals to the RTS/DTR lines with appropriate delays between bits. Initializing the device is achieved by setting the RTS/DTR lines low for a short time and then bringing them both high with another delay before commencing a message. Sending out bits consists starting out with both lines high and setting DTR low for an on bit and setting RTS low for an off bit. In both cases, there must be a delay of at least 500 microseconds before setting that same line high again. This process continues until the entire 5 bytes are sent out. Table 1 lists the house codes. Notice that the house code is a bit different depending on which bank the target controller is on. Table 2 lists the byte to send to turn a controller either on or off. Note that controllers 9-16 have the same code as 1-8. Bit 2 of the house code is what ultimately decides which bank the command is for. Table 3 lists the all-on-off commands. These are sent in place of a code from table 2. Finally, table 4 shows the format of each message. The 2 header bytes and 1 footer byte are always the same.

    Table 1 - House codes

    Code Units 1 - 8 Units 9 - 16
    Hex Binary Hex Binary
    A 0x60 01100000 0x64 01100100
    B 0x70 01110000 0x74 01110100
    C 0x40 01000000 0x44 01000100
    D 0x50 01010000 0x54 01010100
    E 0x80 10000000 0x84 10000100
    F 0x90 10010000 0x94 10010100
    G 0xA0 10100000 0xA4 10100100
    H 0xB0 10110000 0xB4 10110100
    I 0xE0 11100000 0xE4 11100100
    J 0xF0 11110000 0xF4 11110100
    K 0xC0 11000000 0xC4 11000100
    L 0xD0 11010000 0xD4 11010100
    M 0x00 00000000 0x04 00000100
    N 0x10 00010000 0x14 00010100
    O 0x20 00100000 0x24 00100100
    P 0x30 00110000 0x34 00110100

    Note that the house code has bit 2 turned on when addressing units 9 - 16.

    Table 2 - Unit codes and function

    Unit On Off  
    Hex Binary Hex Binary
    1 0x00 00000000 0x02 00000010
    2 0x10 00010000 0x12 00010010
    3 0x08 00001000 0x0A 00001010
    4 0x18 00011000 0x1A 00011010
    5 0x40 01000000 0x42 01000010
    6 0x50 01010000 0x52 01010010
    7 0x48 01001000 0x4A 01001010
    8 0x58 01011000 0x5A 01011010
    9 0x00 00000000 0x02 00000010
    10 0x10 00010000 0x12 00010010
    11 0x08 00001000 0x0A 00001010
    12 0x18 00011000 0x1A 00011010
    13 0x40 01000000 0x42 01000010
    14 0x50 01010000 0x52 01010010
    15 0x48 01001000 0x4A 01001010
    16 0x58 01011000 0x5A 01011010

    Note that the code for units 8 - 16 are the same. As explained in table 1, the house code indicates if we're addressing units 9 - 16.

    Table 3 - Commands

    Command Hex Binary
    Dim 0x98 10011000
    Brighten 0x88 10001000
    All lights on 0x90 10010000
    All lights off 0xA0 10100000
    All units off 0x80 10000000

    Note that commands have bit 7 turned on.

    Table 4 - Message format

    Header House code Unit code, Function / Command Footer
    0xD5,0xAA / 11010101,10101010 Refer to table 1 Refer to tables 2 and 3 0xAD / 10101101

    Using the code

    Just a handful of functions are needed to surface the functionality of the FireCracker. Open the COM port by calling Open() with desired COM port as a string as in "COM1". Demo projects for both VC6 and VS.NET are included.

    History


    Displays system serial ports & IRQ assignments


    For detecting installed serial ports, type of mouse, IRQs used by ports and mouse, and potential IRQ conflicts.


    Shared Serial Ports 1.0


    Shared Serial Ports - advanced utility to share real serial ports between applications so that all applications will receive same data from real serial port simultaneously. This virtual serial port emulator can create virtual serial ports, which are same copies as real ones. All virtual COM ports will be able to send data to real serial port. Allows to set permissions to read, write or change control lines state for every application separately.


    Serial Monitor 3.21


    Serial Monitor is a monitoring application, which allows you to view, log and analyze serial port activities. Program attaches itself to the serial port driver under Windows NT, 2000, XP and monitors all activity that any software performs via serial ports. Utility is equipped with built-in Protocol Analyzer. Useful for: Developing or Implementing Serial Protocol, Reverse-engineering the Serial Protocol, Testing Software


    Serial Port and Modem Toolkit by ActivXperts 2.1


    Serial port toolkit to enable applications or scripts to communicate with a modem or other serial port device. Features full com port control, binary and ASCII data transfer, RS 232 and RS 485 standards and ISDN modem support. Use it with ASP, ASP.NET, Visual Basic, Visual Basic .NET, Visual C++, Visual C# .NET or VBScript. Samples for all platforms and development tools are provided. ActivComport can control any device that has a serial interface (com port): a printer, a modem, an ISDN modem, a USB serial device, a network device, a computer, a weight indicator, etc., and can be used on Windows 98, Windows NT, Windows 2000, Windows XP and Windows .NET platforms.


    Communicating with serial ports the .NET way. 1.0


    The "Serial.sln" project contains a complete set of tools for the configuration and management of legacy COM ports. The Settings form provides a tool for setting up the COM port and saving the setup parameters to a user defined configuration file. The Terminal form provides a model for how the base class can be instantiated and how the delegates are defined and used to get access to the base overrides. The terminal is a very useful tool for implementing and testing the serial classes as well as testing any general serial communications.


    Creating a Serial communication on Win32

    Sample Image

    Introduction

    The purpose of this article is to describe how to interface to serial port on Win32. The serial port can be implemented by several techniques such as ActiveX, access I/O and file operation. This article explains the use of serial port on Win32 platform by file operation technique. The programmer can use kernel32.lib library that is provided with the Microsoft Visual C++ Version 6.0. In Microsoft Windows (2000, Me, XP and 95/98), serial port can be treated as a file. Therefore it's possible to open a serial port by using Windows file-creating function.

    This article explains not only about serial port communication but also how to implement multi-tasking that can apply with our project "serial port" application. The reason why the software (serial communication) will be implemented with multi-tasking method is that the serial communication application has to handle work with more than one task at the same time. For example data-reading task, data-sending task, GUI task etc.

    These topics describe the basic operation of interfacing a serial port on Win32:

    Initial/Open serial port communication.

    Receive/Send data

    Design approach

    Initial/Open serial port

    The first step in opening a serial port is initiation or setting a serial port's configuration. The purpose of this is to create the serial port agent. All throughout the article we are going to use a file handle as serial port agent.

    Creating a port handle

    The serial port's handle is a handle that can be used to access the object of serial port. The function that is used to create the serial port handle is the CreateFile function. The following code shows the function that is used to create a handle:

    handlePort_ = CreateFile(portName,  // Specify port device: default "COM1"
    GENERIC_READ | GENERIC_WRITE,       // Specify mode that open device.
    0,                                  // the devide isn't shared.
    NULL,                               // the object gets a default security.
    OPEN_EXISTING,                      // Specify which action to take on file. 
    0,                                  // default.
    NULL);                              // default.

    As figure 2 shows, portName = "COM1": the portName is a variable that is declared by const char*. It is used to specify port name that wants to create a serial port handle.

    Figure 2: CreateFile function

    Restoring a configuration

    The restoration of serial port configuration is getting current configuration at control device. The configuration of serial port includes parameters that are used for setting a serial communications device.

    The GetCommState function is used to get the current device-control and then fills to a device-control block (a DBC structure) with the current control settings for a specified communications device. The following code shows the function that is used to get the current control device:

        // Get current configuration of serial communication port.
        if (GetCommState(handlePort_,&config_) == 0)
        {
          AfxMessageBox("Get configuration port has problem.");
          return FALSE;
        }

    Modifying a configuration

    When you already have serial port configuration in the DBC format, you have to modify parameters a bit. Following code shows the parameters modified:

    // Assign user parameter.
    config_.BaudRate = dcb.BaudRate;  // Specify buad rate of communicaiton.
    config_.StopBits = dcb.StopBits;  // Specify stopbit of communication.
    config_.Parity = dcb.Parity;      // Specify parity of communication.
    config_.ByteSize = dcb.ByteSize;  // Specify  byte of size of communication.
    • DWORD BaudRate:

      Current baud rate (default = 9600)

    • BYTE StopBits:

      0,1,2 = 1, 1.5, 2 (default = 0)

    • BYTE Parity:

      0-4= no, odd, even, mark, space (default = 0)

    • BYTE ByteSize:

      Number of bits/byte, 4-8 (default = 8)

    Note: Recommend that programmers use default value for typical communication. As shown in figure 3, Watch Dialog Box shows the default values that are used for typical communication.

    Figure 3: Serial port configuration

    Storing a configuration

    The next step is the storage of new configuration that is modified already into device control. Call SetCommState API function to store the configuration. The SetCommState function configures a communications device according to the specifications in a device-control block (a DBC structure). The function reinitializes all hardware and control settings, but it does not empty output or input queues. Following code shows storage of a new configuration:

    if (SetCommState(handlePort_,&config_) == 0)
    {
      AfxMessageBox("Set configuration port has problem.");
      return FALSE;
    }

    Setting a Time-Out communication

    The final step in serial port opening is setting communication Time-out by using the COMMTIMEOUTS data-structure and calling SetCommTimeouts function. The code below shows setting time-out of communication:

        // instance an object of COMMTIMEOUTS.
        COMMTIMEOUTS comTimeOut;                   
        // Specify time-out between charactor for receiving.
        comTimeOut.ReadIntervalTimeout = 3;
        // Specify value that is multiplied 
        // by the requested number of bytes to be read. 
        comTimeOut.ReadTotalTimeoutMultiplier = 3;
        // Specify value is added to the product of the 
        // ReadTotalTimeoutMultiplier member
        comTimeOut.ReadTotalTimeoutConstant = 2;
        // Specify value that is multiplied 
        // by the requested number of bytes to be sent. 
        comTimeOut.WriteTotalTimeoutMultiplier = 3;
        // Specify value is added to the product of the 
        // WriteTotalTimeoutMultiplier member
        comTimeOut.WriteTotalTimeoutConstant = 2;
        // set the time-out parameter into device control.
        SetCommTimeouts(handlePort_,&comTimeOut);
    ReadIntervalTimeout

    Specifies the maximum time, in milliseconds, allowed to elapse between the arrival of two characters on the communications line. During a ReadFile operation, the time period begins when the first character is received. If the interval between the arrival of any two characters exceeds this amount, the ReadFile operation is completed and any buffered data is returned. A value of zero indicates that interval time-outs are not used.

    A value of MAXDWORD, combined with zero values for both the ReadTotalTimeoutConstant and ReadTotalTimeoutMultiplier members, specifies that the read operation is to return immediately with the characters that have already been received, even if no characters have been received.

    ReadTotalTimeoutMultiplier

    Specifies the multiplier, in milliseconds, used to calculate the total time-out period for read operations. For each read operation, this value is multiplied by the requested number of bytes to be read.

    ReadTotalTimeoutConstant

    Specifies the constant, in milliseconds, used to calculate the total time-out period for read operations. For each read operation, this value is added to the product of the ReadTotalTimeoutMultiplier member and the requested number of bytes.

    A value of zero for both the ReadTotalTimeoutMultiplier and ReadTotalTimeoutConstant members indicates that total time-outs are not used for read operations.

    WriteTotalTimeoutMultiplier

    Specifies the multiplier, in milliseconds, used to calculate the total time-out period for write operations. For each write operation, this value is multiplied by the number of bytes to be written.

    WriteTotalTimeoutConstant

    Specifies the constant, in milliseconds, used to calculate the total time-out period for write operations. For each write operation, this value is added to the product of the WriteTotalTimeoutMultiplier member and the number of bytes to be written.

    A value of zero for both the WriteTotalTimeoutMultiplier and WriteTotalTimeoutConstant members indicates that total time-outs are not used for write operations.

    Note: After the user has set the time-out of communication without any error, the serial port has opened already.

    Sending data

    Most of data transmission of serial port is done as writing a file. Programmer can apply file operation functions for sending data to serial port. The WriteFile function is a function used to send data in serial port communication.

        if (WriteFile(handlePort_, // handle to file to write to
          outputData,              // pointer to data to write to file
          sizeBuffer,              // number of bytes to write
          &length,NULL) == 0)      // pointer to number of bytes written
        {
          AfxMessageBox("Reading of serial communication has problem.");
          return FALSE;
        }

    Note: If the function succeeds, the return value is nonzero.

    Receiving data

    Most of data reception of serial communication is done as reading a file. Programmer can apply file operation functions for receiving data from serial port. The ReadFile function is the function that handles reading data in serial port communication.

     if (ReadFile(handlePort_,  // handle of file to read
        inputData,               // handle of file to read
        sizeBuffer,              // number of bytes to read
        &length,                 // pointer to number of bytes read
        NULL) == 0)              // pointer to structure for data
      {
        AfxMessageBox("Reading of serial communication has problem.");
        return FALSE;
      }

    Note: If the function succeeds, the return value is nonzero.

    Closing a serial port

    The serial port closing calls the CloseHandle API function to close handle of device control.

        if(CloseHandle(handlePort_) == 0)    // Call this function to close port.
        {
          AfxMessageBox("Port Closeing isn't successed.");
          return FALSE;
        }

    Note: If the function succeeds, the return value is nonzero.

     


    Serial communication library for C++

    Introduction

    Serial communications is needed in several types of applications, but the Win32 API isn't a very easy to use API to implement it. Things get even more complicated when you want to use serial communication in an MFC based program. The classes provided in the library try to make life a little easier. Its documentation is extensive, because I want to give you a good background. Serial communication is hard and good knowledge of its implementation saves you a lot of work, both now and in the future...

    First I'll briefly discuss why serial communications is hard. After reading that chapter you'll probably be convinced as well that you need a class, which deals with serial communication. The classes provided in the library are not the only classes, which handle the serial communication. Many other programmers wrote their own classes, but I found many of them too inefficient or they weren't robust, scalable or suitable for non-MFC programs. I tried to make these classes as efficient, reliable and robust as possible, without sacrificing ease of use too much.

    The library has been developed as a public domain library some time ago, but it has been used in several commercial applications. I think most bugs have been solved, but unfortunately I cannot guarantee that there are no bugs left. If you find one (or correct a bug), please inform me so I can update the library.

    Why is serial communication that hard?

    Serial communication in Win32 uses the standard ReadFile/WriteFile functions to receive and transmit data, so why should serial communication be any harder then just plain file I/O? There are several reasons, which I'll try to explain. Some problems are solved in this library, but some others cannot be solved by a library.

    Baudrates, parity, databits, handshaking, etc...

    Serial communication uses different formats to transmit data on the wire. If both endpoints doesn't use the same setting you get garbled data. Unfortunately, no class can help you with these problems. The only way to cope with this is that you understand what these settings are all about. Baudrate, parity, databits and stopbits are often quite easy to find out, because when they match with the other endpoint, you won't have any problems (if your computer is fast enough to handle the amount of data at higher baudrates).

    Handshaking is much more difficult, because it's more difficult to detect problems in this area. Handshaking is being used to control the amount of data that can be transmitted. If the sending machine can send data more quickly then the receiving machine can process we get more and more data in the receiver's buffer, which will overflow at a certain time. It would be nice when the receiving machine could tell the sending machine to stop sending data for a while, so it won't overflow the receiver's buffers. This process of controlling the transmission of data is called handshaking and there are basically three forms of handshaking:

    1. No handshaking, so data is always send even if the receiver cannot handle the data anymore. This can lead to data loss, when the sender is able to transmit data faster then the receiver can handle. Of course this option isn't recommended, but it can be used for situations where only a few bytes are transmitted once in a while.
    2. Hardware handshaking, where the RTS/CTS lines are used to indicate if data can be sent. This mode requires that both ports and the cable support hardware handshaking. Hardware handshaking is the most reliable and efficient form of handshaking available, but is hardware dependant. Make sure you have a proper cable, which is fully wired. There are a lot of wrong cables around, so make sure you use the right one.
    3. Software handshaking, where the XOFF/XON characters are used to throttle the data. A major drawback of this method is that these characters cannot be used for data anymore. The XOFF/XON characters are the CTRL-S/CTRL-Q characters, which cannot be used in the data stream anymore. This makes software handshaking pretty useless, when you want to send binary data. For ASCII data it's pretty useful. It's being used on the old UNIX terminals as well. Scrolling starts and stops with CTRL-S/CTRL-Q on these, so the user provides its own handshaking there (without even knowing it perhaps).

    Problems with handshaking are pretty hard to find, because it will often only fail in cases where buffers overflow. These situations are hard to reproduce so make sure that you did setup handshaking correctly and that the used cable is working correct (if you're using hardware handshaking) before you continue.

    The Win32 API provides more handshaking options, which aren't directly supported by this library. These types of handshaking are rarely used, so it would probably only complicate the classes. If you do need these handshaking options, then you can use the Win32 API to do that and still use the classes provided by the library.

    Asynchronous I/O makes things more complex

    File I/O is relatively fast so if the call blocks for a while, this will probably only be a few milliseconds, which is acceptable for most programs. Serial I/O is much slower, which causes unacceptable delays in your program. Another problem is that you don't know when the data arrives and often you don't even know how much data will arrive.

    Win32 provides asynchronous function calls (also known as overlapped operations) to circumvent these problems. Asynchronous programming is often an excellent way to increase performance, but it certainly increases complexity as well. This complexity is the reason that a lot of programs have bugs in their serial communication routines. This library solves some asynchronous I/O problems by allowing the programmer to use overlapped and non-overlapped operations mixed throughout the code, which is often quite convenient.

    The event driven programming model doesn't fit

    Things get even more complex in GUI applications, which uses the event driven model that they're used to. This programming model is a heritage of the old 16-bit days and it isn't even that bad. The basic rule is simple... All events are send using a windows message, so you need at least one window to receive the events. Most GUI applications are single-threaded (which is often the best solution to avoid a lot of complexity) and they use the following piece of code in the WinMain function to process all messages:

    // Start the message-pump until a WM_QUIT is received
    MSG msg;
    while (::GetMessage(&msg,0,0,0))
    {
        ::TranslateMessage(&msg);
        ::DispatchMessage(&msg);
    }
    

    Because the GetMessage function blocks until there is a message in the message queue, there's no way to wake up when a serial event occurs. Of course you can set a timer and check the ports there, but this kind of polling is bad design and certainly doesn't scale well. Unfortunately the Win32 serial communication API doesn't fit in this event driven model. It would be easier for GUI applications that the Win32 API posted a message to a window when a communication event occurred (this is exactly what the 16-bit implementation looked like).

    If you implement your own message-pump, you can use the MsgWaitForMultipleObjects to wait for a windows message or a windows object to become signaled. The following piece of code demonstrates how to do this (it assumes that the event handle that is being used for asynchronous events is stored in the variable hevtCommEvent):

    bool fQuit = false;
    while (!fQuit)
    {
        // Wait for a communication event or windows message
        switch (::MsgWaitForMultipleObjects(1,&hevtCommEvent,FALSE,INFINITE,QS_ALLEVENTS))
        {
        case WAIT_OBJECT_0:
            {
                // There is a serial communication event, handle it...
                HandleSerialEvent();
            }
            break;
    
        case WAIT_OBJECT_0+1:
            {
                // There is a windows message, handle it...
                MSG msg;
                while (::PeekMessage(&msg,0,0,0,PM_REMOVE))
                {
                    // Abort on a WM_QUIT message
                    if (msg.message == WM_QUIT) { fQuit = true; break; }
    
                    // Translate and dispatch the message
                    ::TranslateMessage(&msg);
                    ::DispatchMessage(&msg);
                }
            }
            break;
    
        default:
            {
                // Error handling...
            }
            break;
        }
    }
    

    This code is much more complex then the simple message pump displayed above. This isn't that bad, but there is another problem with this code, which is much more serious. The message pump is normally in one of the main modules of your program. You don't want to pollute that piece of code with serial communication from a completely different module. The handle is probably not even valid at all times, which can cause problems of its own. This solution is therefore not recommended. MFC and OWL programmers cannot implement this at all, because these frameworks already their own message pumps. You might be able to override that message pump, but it probably requires a lot of tricky code and undocumented tricks.

    Using serial communications in a single-threaded event-driven program is difficult as I've just explained, but you probably found that out yourself. How can we solve this problem for these types of applications? The answer is in the CSerialWnd class, which posts a message to a window (both the message and window can be specified by the programmer) whenever a serial event occurs. This makes using a serial port in GUI based applications much easier. There is also a very thin MFC wrapper class, which is called CSerialMFC but it's that thin, that it's hardly worth mentioning.

    This library cannot perform magic, so how can it send messages without blocking the message pump? The answer is pretty simple. It uses a separate thread, which waits on communication events. If such an event occurs, it will notify the appropriate window. This is a very common approach, which is used by a lot of other (serial) libraries. It's not the best solution (in terms of performance), but it is suitable for 99% of the GUI based communication applications. The communication thread is entirely hidden for the programmer and doesn't need to affect your architecture in any way.

    Which class you should use in your code

    The current implementation contains four different classes, which all have their own purpose. The following three classes are available.

    If you're not using a message pump in the thread that performs the serial communication, then you should use the CSerial or CSerialEx classes. You can use blocking calls (the easiest solution) or one of the synchronization functions (i.e. WaitForMultipleObjects) to wait for communication events. This approach is also used in most Unix programs, which has a similar function as WaitForMultipleObjects called 'select'. This approach is often the best solution in non-GUI applications, such as NT services.

    The CSerialEx adds another thread to the serial object. This frees the main thread from blocking, when waiting for serial events. These events are received in the context of this worker thread, so the programmer needs to know the impact of multi-threading. If all processing can be done in this thread, then this is a pretty efficient solution. You need some kind of thread synchronization, when you need to communicate with the main GUI thread (i.e. for progress indication). If you need to communicate a lot with the main GUI thread, then it is probably better to use the CSerialWnd class. However, if you don't communicate a lot with the main thread, then this class can be a good alternative.

    GUI applications, which want to use the event-driven programming model for serial communications should use CSerialWnd. It is a little less efficient, but the performance degradation is minimal if you read the port efficiently. Because it fits perfectly in the event-driven paradigm the slight performance degradation is a minimal sacrifice. Note that you can use CSerial in GUI based applications (even MFC/WTL based), but then you might block the message pump. This is, of course, bad practice in in a commercial application (blocking the message pump hangs the application from the user's point of view for a certain time). As long as you know what the impact is of blocking the message pump, you can decide for yourself if it is acceptable in your case (could be fine for testing).

    MFC application should use the CSerialMFC wrapper if they want to pass CWnd pointers instead of handles. Because this wrapper is very thin you can also choose to use CSerialWnd directly.

    Using the serial classes in your program

    Using the serial classes can be divided into several parts. First you need to open the serial port, then you set the appropriate baudrate, databits, handshaking, etc... This is pretty straightforward. The tricky part is actually transmitting and receiving the data, which will probably cause the most time to implement. At last you need to close the serial port and as a bonus if you don't then the library will do it for you.

    Sending data

    Let's start with a classic example from K&R and be polite and say hello. The implementation is very straightforward and looks like this (there is no error checking here for simplicity, it is there in the actual project):

    #define STRICT
    #include <tchar.h>
    #include <windows.h>
    #include "Serial.h"
    
    int WINAPI _tWinMain
              (
               HINSTANCE /*hInst*/, 
               HINSTANCE /*hInstPrev*/, 
               LPTSTR    /*lptszCmdLine*/, 
               int       /*nCmdShow*/
              )
    {
        CSerial serial;
    
        // Attempt to open the serial port (COM1)
        serial.Open(_T("COM1"));
    
        // Setup the serial port (9600,N81) using hardware handshaking
        serial.Setup(CSerial::EBaud9600,CSerial::EData8,CSerial::EParNone,CSerial::EStop1);
        serial.SetupHandshaking(CSerial::EHandshakeHardware);
    
        // The serial port is now ready and we can send/receive data. If
        // the following call blocks, then the other side doesn't support
        // hardware handshaking.
        serial.Write("Hello world");
    
        // Close the port again
        serial.Close();
        return 0;
    }
    

    Of course you need to include the serial class' header-file. Make sure that the header-files of this library are in your compiler's include path. All classes depend on the Win32 API, so make sure that you have included them as well. I try to make all of my programs ANSI and Unicode compatible, so that's why the tchar stuff is in there. So far about the header-files.

    The interesting part is inside the main routine. At the top we declare the serial variable, which represents exactly one COM port. Before you can use it, you need to open the port. Of course there should be some error handling in the code, but that's left as an exercise for the reader. Besides specifying the COM port, you can also specify the input and output buffer sizes. If you don't specify anything, then the default OS buffer sizes are being used (older versions of the library used 2KB as the default buffer size, but this has been changed). If you need larger buffers, then specify them yourself.

    Setting up the serial port is also pretty straightforward. The settings from the control panel (or Device Manager) are being used as the port's default settings. Call Setup if these settings do not apply for your application. If you prefer to use integers instead of the enumerated types then just cast the integer to the required type. So the following two initializations are equivalent:

    Setup(CSerial::EBaud9600,CSerial::EData8,CSerial::EParNone,CSerial::EStop1);
      
    Setup(CSerial::EBaudrate(9600),
          CSerial::EDataBits(8),
          CSerial::EParity(NOPARITY),
          CSerial::EStopBits(ONESTOPBIT));
    

    In the latter case, the types are not validated. So make sure that you specify the appropriate values. Once you know which type of handshaking you need, then just call SetupHandshaking with one of the appropriate handshaking.

    Writing data is also very easy. Just call the Write method and supply a string. The Write routine will detect how long the string is and send these bytes across the cable. If you have written Unicode applications (like this one) then you might have noticed that I didn't send a Unicode string. I think that it's pretty useless to send Unicode strings, so you need to send them as binary or convert them back to ANSI yourself. Because we are using hardware handshaking and the operation is non-overlapped, the Write method won't return until all bytes have been sent to the receiver. If there is no other side, then you might block forever at this point.

    Finally, the port is closed and the program exits. This program is nice to display how easy it is to open and setup the serial communication, but it's not really useful. The more interesting programs will be discussed later.

    Receiving data

    Like in real life it's easier to tell something what to do then listening to another and take appropriate actions. The same holds for serial communication. As we saw in the Hello world example writing to the port is just as straightforward as writing to a file. Receiving data is a little more difficult. Reading the data is not that hard, but knowing that there is data and how much makes it more difficult. You'll have to wait until data arrives and when you're waiting you cannot do something else. That is exactly what causes problems in single-threaded applications. There are three common approaches to solve this.

    The first solution is easy. Just block until some data arrives on the serial port. Just call WaitEvent without specifying the overlapped structure. This function blocks until a communication event occurs (or an optional time-out expires). Easy, but the thread is blocked and only wakes up for communication events or a time-out.

    The second solution is to use the synchronization objects of Win32. Whenever something happens, the appropriate event handles are signaled and you can take appropriate action to handle the event. This method is available in most modern operating systems, but the details vary. Unix systems use the select call, where Win32 applications mostly use WaitForMultipleObjects or one of the related functions. The trick is to call the WaitEvent function asynchronously by supplying an overlapped structure, which contains a handle which will be signaled when an event occurred. Using WaitForMultipleObjects you can wait until one of the handles become signaled. I think this is the most suitable for most non-GUI applications. It's definitely the most efficient option available. When you choose to use this option, you'll notice that the serial classes are only a thin layer around the Win32 API.

    The last solution is one which will be appreciated by most Windows GUI programmers. Whenever something happens a message is posted to the application's message queue indicating what happened. Using the standard message dispatching this message will be processed eventually. This solution fits perfect in the event-driven programming environment and is therefore useful for most GUI (both non-MFC and MFC) applications. Unfortunately, the Win32 API offers no support to accomplish this, which is the primary reasons why the serial classes were created. The old Win16 API uses the SetCommEventMask and EnableCommNotification to do exactly this, but these were dropped from the Win32 API.

    Block until something happens

    Blocking is the easiest way to wait for data and will therefore be discussed first. The CSerial class exposes a method called WaitEvent, which will block until an event has been received. You can (optionally) specify a time-out for this call (if overlapped I/O is enabled), so it won't block forever if no data arrives anymore. The WaitEvent method can wait for several events, which must be registered during setup. The following events can occur on a COM port:

    When a serial port is opened, then the EEventBreak, EEventError and EEventRecv are being registered. If you would like to receive the other events then you have to register them using the SetMask method.

    Now you can use the WaitEvent method to wait for an event. You can then call GetEventType to obtain the actual event. This function will reset the event, so make sure you call it only once after each WaitEvent call. Multiple events can be received simultaneously (i.e. when the event character is being received, then (EEventRecv|EEventRcvEv) is returned. Never use the == operator to check for events, but use the & operator instead.

    Reading can be done using the Read method, but reading is trickier then you might think at first. You get only an event that there is some data, but not how much. It could be a single byte, but it can also be several kilobytes. There is only one way to deal with this. Just read as much as you can handle (efficiently) and process it.

    First make sure that the port is in EReadTimeoutNonblocking mode by issuing the following call:

        // Use 'non-blocking' reads, because we don't know how many bytes
        // will be received. This is normally the most convenient mode
        // (and also the default mode for reading data).
        serial.SetupReadTimeouts(CSerial::EReadTimeoutNonblocking);
    

    The Read method will now read as much as possible, but will never block. If you would like Read to block, then specify EReadTimeoutBlocking. Read always returns the number of bytes read, so you can determine whether you have read the entire buffer. Make sure you always read the entire buffer after receiving the EEventRecv event to avoid you lose data. A typical EEventRecv will look something like this:

        // Read data, until there is nothing left
        DWORD dwBytesRead = 0;
        BYTE  abBuffer[100];
        do
        {
            // Read data from the COM-port
            serial.Read(abBuffer,sizeof(abBuffer),&dwBytesRead);
            if (dwBytesRead > 0)
            {
                // TODO: Process the data
            }
        }
        while (dwBytesRead == sizeof(abBuffer));
    

    The Listener sample (included in the ZIP-file) demonstrates the technique as described above. The entire sample code isn't listed in this document, because it would take too much space.

    Using the Win32 synchronization objects

    In most cases, blocking for a single event (as described above) isn't appropriate. When the application blocks, then it is completely out of your control. Suppose you have created a service which listens on multiple COM-ports and also monitors a Win32 event (used to indicate that the service should stop). In such a case, you'll need multithreading, message queues or the Win32 function for synchronization. The synchronization objects are the most efficient method to implement this, so I'll try to explain them. Before you continue reading I assume you're a bit familiar with the use of the synchronization objects and overlapped operations. If you're not, then first read the section about Synchronization in the Win32 API.

    The only call that blocks for a fairly long time is the WaitEvent method. In the next paragraphs, I will show you how to implement this call using the Win32 synchronization objects (all other overlapped calls work identical). A complete implementation can be found in the Overlapped project, which is quite similar to the Listener project, but it now uses overlapped I/O.

    First the the COM-port needs to be initialized. This works identical as in the Listener sample. Then two events are created. The first event will be used in the overlapped structure. Note that it should be a manual reset event, which is initially not signaled. The second one is an external event, which is used to stop the program. The first event will be stored inside the OVERLAPPED structure.

     

        // Create a handle for the overlapped operations
        HANDLE hevtOverlapped = ::CreateEvent(0,TRUE,FALSE,0);;
    
        // Open the "STOP" handle
        HANDLE hevtStop = ::CreateEvent(0,TRUE,FALSE,_T("Overlapped_Stop_Event"));
    
        // Setup the overlapped structure
        OVERLAPPED ov = {0};
        ov.hEvent = hevtOverlapped;
    

    All events have been setup correctly and the overlapped structure has been initialized. We can now call the WaitEvent method in overlapped mode.

        // Wait for an event
        serial.WaitEvent(&ov);
    

    The overlapped I/O operation is now in progress and whenever an event occurs, that would normally unblock this call, the event handle in the overlapped structure will become signalled. It is not allowed to perform an I/O operation on this port, before it has completed, so we will wait until the event arrives or the stop event has been set.

        // Setup array of handles in which we are interested
        HANDLE ahWait[2];
        ahWait[0] = hevtOverlapped;
        ahWait[1] = hevtStop;
    
        // Wait until something happens
        switch (::WaitForMultipleObjects(2,ahWait,FALSE,INFINITE))
        {
        case WAIT_OBJECT_0:
            // Serial port event occurred
            ...
    
        case WAIT_OBJECT_0+1:
            // Stop event raised
            ...
        }
    

    That's all you need to do, when you want to use the serial class in overlapped I/O mode. N

    Using Windows messages

    Most Windows developers are used to receive a Windows message, whenever a certain event occurs. This fits perfectly in the Windows event-driven model, but the Win32 API doesn't provide such a mechanism for serial communication. This library includes a class called CSerialWnd, which will send a special message whenever a serial event occurs. It is pretty simple, when you are already familiar with the event-driven programming model of Windows.

    Instead of using the CSerial class, you must use the CSerialWnd class (which is in fact derived from CSerial). CSerialWnd works just like CSerial, but there are some tiny differences in opening the port and waiting on its events. Note that the CSerialWnd doesn't have a window itself and neither should you derive from it, when you want to use it. Just define a member variable and use that from within your window.

    Because CSerialWnd posts its messages to a window, it requires additional information. Therefore the Open method accepts three additional parameters, which specify the window handle, message and optional argument. The prototype looks like:

        LONG Open (
                   LPCTSTR lpszDevice, 
                   HWND    hwndDest, 
                   UINT    nComMsg    = WM_NULL,
                   LPARAM  lParam     = 0, 
                   DWORD   dwInQueue  = 0, 
                   DWORD   dwOutQueue = 0
                  )
    

    The lpszDevice, dwInQueue and dwOutQueue are used as in CSerial. The hwndDest argument specifies the window, where the message should be sent to. The library registers a default message during startup, which can be used in most cases. Simply pass WM_NULL to use this message. The value of this message is stored in the CSerialWnd::mg_nDefaultComMsg variable, which is a static member variable of CSerialWnd. If you prefer one of your own messages, then you can use that instead. The optional lParam argument is sent as the second parameter (lParam) in each message that is being sent by CSerial. The serial library doesn't do anything with this value, so be free to use it as you like.

    Sending data and setting up the serial port is exactly the same as with CSerial, so I won't discuss that again anymore. The biggest difference is the way you receive the events, but that is exactly why you want to use this class anyway.

    If everything is fine, then you have registered all interesting events with the SetMask method. Whenever one of these events occur, the specified message will be sent to the window you have registered before. The wParam will contain the event and error-code. The lParam contains whatever you passed to CSerialWnd::Open, when you have opened the port. A typical handler for these messages looks like:

    LRESULT CALLBACK MyWndProc (HWND hwnd, UINT nMsg, WPARAM wParam, LPARAM lParam)
    {
       if (nMsg == CSerialWnd::mg_nDefaultComMsg)
       {
           // A serial message occurred
           const CSerialWnd::EEvent eEvent = CSerialWnd::EEvent(LOWORD(wParam));
           const CSerialWnd::EError eError = CSerialWnd::EError(HIWORD(wParam));
    
           switch (eEvent)
           {
           case CSerialWnd::EEventRecv:
               // TODO: Read data from the port
               break;
    
               ...
           }
             
           // Return successful
           return 0;
       }
    
       // Perform other window processing
       ...
    }
    

    The methods WaitEvent, GetEventType and GetError from CSerial are hidden in the CSerialWnd class, because they cannot be used anymore. All the information is passed in the window message, so it shouldn't be necessary to use them anymore.

    Using the library with MFC

    Personally, I don't like MFC, but I know many people out there use it so there is also support in this library for MFC. Instead of using CSerialWnd, you can use CSerialMFC. It works exactly the same, but it can also handle a CWnd pointer and it provides a macro, which can be used in the message map for better readability. The message map of a window, which can receive events from CSerialMFC should look like this:

        BEGIN_MESSAGE_MAP(CMyClass,CWnd)
       //{{AFX_MSG_MAP(CMyClass)
            ...
       //}}AFX_MSG_MAP
       ...
            ON_WM_SERIAL(OnSerialMsg)
            ...
        END_MESSAGE_MAP()
    

    Note that the ON_WM_SERIAL macro is placed outside the AFX_MSG_MAP block, otherwise the MFC Class Wizard becomes confused. The handler itself looks something like this:

    afx_msg LRESULT CMyClass::OnSerialMsg (WPARAM wParam, LPARAM lParam)
    {
        const CSerialMFC::EEvent eEvent = CSerialMFC::EEvent(LOWORD(wParam));
        const CSerialMFC::EError eError = CSerialMFC::EError(HIWORD(wParam));
    
        switch (eEvent)
        {
        case CSerialMFC::EEventRecv:
            // TODO: Read data from the port
            break;
            ...
        }
       
        // Return successful
        return 0;
    }
    

    A complete sample, including property sheets for setting up the COM-port, is shipped with this library. Look for the SerialTestMFC project for an example how to use this library in your MFC programs.

    Integrating this library into your code.

    This library is very lightweight, so it can easily be integrated into your application without using a separate DLL. I used a static library for the CSerial (and derived) classes, because I think that is exactly where a library is meant for. Just insert the Serial project into your workspace and make a dependency to it. The linker will then automatically compile and link the serial classes to your application. Some people don't like libraries. In that case you can just add the Serial files to your project and recompile.

    If you use precompiled headers, then you need to remove the following lines from both Serial.cpp and SerialWnd.cpp:

    #define STRICT
    #include <crtdbg.h>
    #include <tchar.h>
    #include <windows.h>
    

    Replace these lines with the following line:

    #include "StdAfx.h"
    

    Sample programs

    The Serial library comes with some sample programs, which can all be compiled from the Serial.dsw workspace. Make sure that you always open the Serial.dsw file, when building the library and/or sample applications. Opening the individual .dsp files won't work, because these files don't have the dependencies, which results in unresolved externals.

    Note that all samples can be compiled as ANSI or Unicode versions. The Unicode versions don't run on the Windows 95/98/ME platforms, because they don't support Unicode. The SerialTestMFC sample uses the Unicode version of the MFC library (when compiled with Unicode enabled). The default installation options of Visual C++ v6.0 don't install the Unicode libraries of MFC, so you might get an error that mfc42ud.dll or mfc42u.dll cannot be found.

    Windows 95 support and CancelIo

    A lot of people still need to support the Windows 95 environment, which doesn't support the CancelIo function. When an overlapped operation times out, then the pending call need to be cancelled with the CancelIo call. Therefore time-outs (other then 0 and INFINTE) cannot be used when Windows 95 compatibility is enabled. Fortunately, the CSerialEx, CSerialWnd and CSerialMFC don't rely on this call, so these classes can be used on Windows 95 without any restrictions. If you define the SERIAL_NO_CANCELIO symbol, then I/O cancellation is disabled, so you should always define this symbol when you target Windows 95.

    Other problems, specific to Windows 95/98/ME are the queue sizes. If the buffer size is far too small then you might get a blue screen, when receiving or transmitting data. The default settings should be fine for normal serial communication.

    Windows CE support and the lack of overlapped I/O

    I have got a lot of requests to implement a windows CE version of the library. The old version of this library always used overlapped I/O, so it didn't work on Windows CE. Due to the huge amount of requests I started working on this issue. I have rewritten the CSerialEx class, so it doesn't rely on overlapped I/O internally. Cancelling the WaitCommEvent now uses a documented trick, which sets the event mask to its current value. This effectively cancels the WaitCommEvent method and makes the use of overlapped I/O unnecessary in CSerialEx.

    SetCommMask call blocks, when it is already waiting for an event (using WaitCommEvent). This would render this method useless on the Windows CE platform, because it doesn't support overlapped I/O. Fortunately, the serial driver is implemented as an overlapped driver internally on Windows CE, so it allows multiple calls (described in the KB, article Q175551). Using this Windows CE feature it is also possible to use all the classes on the Windows CE platform.

    To include this library into you Windows CE project, just add the Serial.dsp project file to your own workspace. If you use eMbedded Visual C++, then the project file is automatically converted. Make sure you define the SERIAL_NO_OVERLAPPED symbol to avoid the use of overlapped I/O.

    Porting issues

    This paragraph describes some porting issues that you should be aware of when porting from an older version to a newer version. Always retest your application, when using a different version of the serial library. I always try to keep the code source-level compatible, but if you use a new library version, then make sure you recompile your code completely with the same SERIAL_xxx symbols defined as were used to compile the library itself.

    Virtual COM ports (USB dongles, Bluetooth dongles, ...)

    Some people use virtual COM-ports to emulate an ordinary serial port (i.e. USB serial dongle, Bluetooth dongle, ...). These so-called virtual COM ports use a different driver then ordinary serial ports. Unfortunately, most drivers are pretty buggy. In most cases normal communication using the default settings works pretty good, but you run into problems when you need more sophisticated communication.

    Most virtual COM ports have difficulties with the CancelIo function. In some cases the process freezes completely (and cannot even be killed by the task manager) and I have even seen Windows XP systems reboot after calling CancelIo. Some drivers can handle CancelIo for an overlapped ReadFile call, but fail when cancelling a WaitCommEvent request. Terminating a thread with a pending I/O request also implies an implicit call of CancelIo, so if you have problems terminating a thread then you might have pending requests that cannot be cancelled. The CSerialEx (and derived classes) effectively cancels the pending WaitEvent, so you should be pretty safe when you use these classes.

    Receiving spurious events is another problem, which is quite common when using virtual COM ports. Even events that have been masked out can be sent by some virtual COM ports. For this reason the events are filtered inside the library, but you might get some empty events in your code (you can simply ignore these events).

    Some virtual COM ports also have problems when you don't use the common 8 databit, no parity and 1 stopbit settings. Sending a break is also problematical for some virtual COM ports. There is no standard workaround possible for these bugs, so make sure you test these features, when you intend to use your application with a virtual COM port.

    Some people claim that the dongle and its driver is fine, because HyperTerminal works fine. This isn't correct in most cases. HyperTerminal only uses a small subset of the Win32 API for serial communication and doesn't seem to use CancelIo very much. The most important lesson from the last few paragraphs is that you need to test your application very thorough with each driver that can be used. Although the Win32 API is the same for each (virtual) COM port, the implementation below the API might be completely different.

    This serial library utilizes the overlapped I/O mechanism, which should be supported by each Win32 driver. Unfortunately, a lot of drivers don't implement this feature correct. However, I keep trying to improve this library so if you have any suggestions, please contact me.

    References

    Of course the first place to look for information about serial communications is in the Platform SDK section "Windows Base Services, Files and I/O, Communications". There's probably enough in there to implement your own serial communications, but for a better explanation read Allen Denver's article called "Serial Communications in Win32", which is in the MSDN's Technical Articles.

    Changes

    Future features

    Comments and disclaimer

    If you have any comments or questions about these classes, then you can reach me using email at Ramon.de.Klein@ict.nl. This library is free for both commercial and non-commercial use (if you insist, you can send me money though). Unfortunately, I cannot guarantee any support for these classes. I cannot test every situation and the use of these classes is at your own risk.

    Because this library is distributed with full source-code included, I cannot stop you from changing the code. I don't mind if you change the code, but I don't want to be blamed for your bugs. So please mark your changes and keep my name in the copyrights as well. If you added a cool feature, then please let me know so I might integrate it with a new version of this library. The library is released under the conditions of the Lesser GNU Public License (LGPL). The sample programs are distributed under the terms of the GNU Public License (GPL).

    Please don't mirror this code or documentation on another website or removable media (such as a CD-ROM) with the intent to redistribute it. I don't want to have old versions floating around, which might contain bugs that are solved in later versions. Just mention the URL where users can download the archive and documentation.

    I would like to thank my friend and ex-colleague Remon Spekreijse for pointing out some problems, adding some features in this library and encouraging me to put this library on the net. I also want to thank all other people who have shown their appreciation one way or another.

    Download

    Before you download one of the packages, listed below, you must make sure that you've read the legal statement.

    Serial.zip
     
    Serial library including sample applications and documentation. It contains all source code and project files. You need Visual C++ to compile and link the applications. This library is also submitted to Codeproject, which is an excellent site to find freeware code. It might be useful to check out the serial class there at http://www.codeproject.com/system/serial.asp to check for the latest comments and discussions about it. It was chosen as article of the week right after it was submitted to the site.

    Serial Communications in Win32

     

    Allen Denver
    Microsoft Windows Developer Support

    December 11, 1995

    Applies to:
       Microsoft® Win32®
       Microsoft Windows®

    Summary: Learn how serial communications in Microsoft Win32 is significantly different from serial communications in 16-bit Microsoft Windows. This article assumes a familiarity with the fundamentals of multiple threading and synchronization in Win32. In addition, a basic understanding of the Win32 heap functions is useful to fully comprehend the memory management methods used by the Multithreaded TTY (MTTTY) sample included with this article. (35 printed pages)

    Download the MTTTY sample (4918.exe) for this technical article.

    Contents

    Overview
    Introduction
    Opening a Port
    Reading and Writing
    Serial Status
    Serial Settings
    Conclusion
    Bibliography

    Overview

    Serial communications in Microsoft® Win32® is significantly different from serial communications in 16-bit Microsoft Windows®. Those familiar with 16-bit serial communications functions will have to relearn many parts of the system to program serial communications properly. This article will help to accomplish this. Those unfamiliar with serial communications will find this article a helpful foundation for development efforts.

    This article assumes you are familiar with the fundamentals of multiple threading and synchronization in Win32. In addition, a basic familiarity of the Win32 heap functions is useful to fully comprehend the memory management methods used by the sample, MTTTY, included with this article.

    For more information regarding these functions, consult the Platform SDK documentation, the Microsoft Win32 SDK Knowledge Base, or the Microsoft Developer Network Library. Application programming interfaces (APIs) that control user interface features of windows and dialog boxes, though not discussed here, are useful to know in order to fully comprehend the sample provided with this article. Readers unfamiliar with general Windows programming practices should learn some of the fundamentals of general Windows programming before taking on serial communications. In other words, get your feet wet before diving in head first. (36 printed pages)

    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 sample included with this article, MTTTY: Multithreaded TTY (4918.exe), 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:

    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:

    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:

    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 Direction Effect on DTE/DCE
    CTS
    (Clear To Send)
    Output flow control
    DCE sets the line high to indicate that it can receive data. DCE sets the line low to indicate that it cannot receive data.

    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
    (Data Set Ready)
    Output flow control
    DCE sets the line high to indicate that it can receive data. DCE sets the line low to indicate that it cannot receive data.

    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
    (Data Set Ready)
    Input flow control
    If the DSR line is low, then data that arrives at the port is ignored. If the DSR line is high, data that arrives at the port is received.

    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
    (Ready To Send)
    Input flow control
    The RTS line is controlled by the DTE.

    If the fRtsControl member of the DCB is set to 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
    (Data Terminal Ready)
    Input flow control
    The DTR line is controlled by the DTE.

    If the fDtrControl member of the DCB is set to DTR_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 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 character Behavior
    XOFF received by DTE DTE transmission is suspended until XON is received. 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 DTE XON is automatically sent by the DTE when the receive 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:

    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)

     

     

    The Palm OS® serial communications software provides high-performance serial communications capabilities, including byte-level serial I/O, best-effort packet-based I/O with CRC-16, reliable data transport with retries and acknowledgments, connection management, and modem dialing capabilities.

    This chapter helps you understand the different parts of the serial communications system and explains how to use them, discussing these topics:


    NOTE: Although the Palm OS supports Bluetooth connections, Bluetooth requires additional hardware and software that is not available as of this writing.


    Serial HW/SW en PDA PALM

    The Palm OS platform device serial port is used for implementing desktop PC connectivity or other external communication. The serial communication is fully interrupt-driven for receiving data. Currently, interrupt-driven transmission of data is not implemented in software, but the hardware does support it. Five external signals are used for this communication:

    Some devices also have a configurable DTR (data terminal ready) signal. Normally, the DTR signal is always high.

    The Palm OS platform device has an external connector that provides:

    Palm, Inc. publishes information designed to assist hardware developers in creating devices to interface with the serial communications port on Palm OS platform products. You can obtain this information by joining the Alliance Program and enrolling in the Plugged-In Program. For more information about this program and the serial port hardware, see the Palm developer web page at http://www.palmone.com/developers/pluggedin/.

    Byte Ordering ^TOP^

    By convention, all data coming from and going to the Palm OS device use Motorola byte ordering. That is, data of compound types such as UInt16 (2 bytes) and UInt32 (4 bytes), as well as their integral counterparts, are packaged with the most-significant byte at the lowest address. This contrasts with Intel byte ordering.

    Serial Communications Architecture Hierarchy ^TOP^

    The serial communications software has multiple layers. Higher layers depend on the more primitive functionality provided by lower layers. Applications can use the functionality of all layers. The software consists of the following layers, described in more detail below:

    Figure 5.1 illustrates the communications layers.

    Figure 5.1  Palm OS Serial Communications Architecture

    The Serial Manager ^TOP^

    The Palm OS Serial Manager is responsible for byte-level serial I/O and control of the RS-232, IR, Bluetooth, or USB signals.


    NOTE: Although the Palm OS supports Bluetooth connections, Bluetooth requires additional hardware and software that is not available as of this writing.


    To ensure that the Serial Manager does not slow down processing of user events, the Serial Manager receives data asynchronously. Sending data is performed synchronously in the current implementation.

    This section describes the Serial Manager and how to write the virtual serial drivers that it can use. It covers the following topics:


    NOTE: You must check which Serial Manager is present before making any calls. See the next section for details. When in doubt, the old Serial Manager API is always available.


    Which Serial Manager Version To Use ^TOP^

    There are several versions of the Serial Manager available. The first several releases of Palm OS had a Serial Manager that supported only a a single serial port. The API for this Serial Manager is documented in the chapter "Old Serial Manager" of the Palm OS Programmer's API Reference.

    If the New Serial Manager Feature Set is present, the Serial Manager has a different set of API (described in the chapter "Serial Manager" of the Palm OS Programmer's API Reference) and can support multiple physical serial hardware devices and virtual serial devices. Physical serial drivers manage communication with the hardware as needed, and virtual drivers manage blocks of data to be sent to some sort of block-based serial code. The detailed operation of drivers is abstracted from the main serial management code.

    The newest versions of Palm OS may have an updated version of the new Serial Manager installed. Version 2 provides USB and Bluetooth virtual drivers and provides a few enhancements to the Serial Manager and virtual driver APIs.

    When deciding which API to use, note the following:

    Checking the Serial Manager Version

    To check whether you can use the new Serial Manager API, check for the existence of the new Serial Manager feature set by calling FtrGet as follows:


    err = FtrGet(sysFileCSerialMgr,  
      sysFtrNewSerialPresent, &value); 
    

    If the new Serial Manager is installed, the value parameter is non-zero and the returned error is zero (for no error).

    To check for the existence of version 2 of the new Serial Manager, you should check both the Serial Manager version number and the Palm OS version number as follows:


    err = FtrGet(sysFileCSerialMgr,  
      sysFtrNewSerialVersion, &value); 
    err = FtrGet(sysFtrCreator,  
      sysFtrNumROMVersion, &romVersion); 
    

    If the value parameter is 2, the romVersion is 0x04003000, and both calls to FtrGet return 0 (for no error), version 2 of the new Serial Manager feature set is present.

    Version 2 of the new Serial Manager ships with roughly Palm OS 4.0 and higher; however, some Handspring devices that run Palm OS 3.5 have a Serial Manager that returns a version number of 2. This Serial Manager has a slightly different feature set than the Serial Manager that ships with Palm OS 4.0. It contains virtual driver operation codes and virtual driver enhancements to support USB, but it does not contain any of the public Serial Manager functions added in version 2. Therefore, you need to check both the Serial Manager version number and the Palm OS version number before you use the version 2 Serial Manager functions.

    About the New Serial Manager

    The new Serial Manager manages multiple serial devices with minimal duplication of hardware drivers and data structures. In older Palm systems, the serial library managed any and all connections to the serial hardware in the 68328 (Dragonball) processor, which was the only serial device in the system. Newer systems contain additional serial devices, such as an IR port and possibly a USB port.

    The figure below shows the layering of communication software with the Serial Manager and hardware drivers.

    Figure 5.2  Serial Communications Architecture with Serial Manager

    The Serial Manager maintains a database of installed hardware and currently open connections. Applications, libraries, or other serial communication tasks open different pieces of serial hardware by specifying a logical port number or a four-character code identifying the exact piece of serial hardware that a task wishes to open a connection with. The Serial Manager then performs the proper actions on the hardware through small hardware drivers that are opened dynamically when the port is needed. One hardware driver is needed for each serial communication hardware device available to the Palm unit.

    At system restart, the Serial Manager searches for all serial drivers on the Palm device. Serial drivers are independent .prc files with a code resource and a version resource and are of type 'sdrv' (for physical serial drivers) or 'vdrv' (for virtual serial drivers). Once a driver is found, it is asked to locate its associated hardware and provide information on the capabilities of that hardware. This is done for each driver found and the Serial Manager always maintains a list of hardware currently on the device.

    Once a port is opened, the Serial Manager allocates a structure for maintaining the current information and settings of the particular port. The task or application that opens the port is returned a port ID and must supply the port ID to refer to this port when other Serial Manager functions are called.

    Upon closing the port, the Serial Manager deallocates the open port structure and unlocks the driver code resource to prevent memory fragmentation.

    Note that applications can use the Connection Manager to obtain the proper port name and other serial port parameters that the user has stored in connection profiles for different connection types. For more information, see the section "The Connection Manager".

    Steps for Using the Serial Manager ^TOP^

    Regardless of which version of the API you use, the main steps to perform serial communication are the same. They are:

    1. Open a serial port.

      To open a port in the new Serial Manager, you specify which port to open and obtain a port ID that uniquely identifies this connection. You pass that port ID to every other Serial Manager call you make.

      Because the old Serial Manager only has one port, it uses the serial library reference number to uniquely identify the connection. Therefore, with the old Serial Manager, you must first obtain the serial library reference number and then open the port.

      See "Opening a Port".

    2. If necessary, configure the connection.

      You might need to change the baud rate or increase the size of the receive queue before you use any other Serial Manager calls. See "Configuring the Port".

    3. Send or receive data.

      See "Sending Data" and "Receiving Data".

    4. Close the port.

      See "Closing a Port".

    The next several sections describe these steps in more detail. Where the old and new Serial Manager APIs are similar, the task is described in terms of using the new Serial Manager, and the old Serial Manager API is given in parentheses. In these cases, the only difference is in the name of the function and the ID you pass to identify the connection. Where the two APIs differ considerably, both are described.


    TIP: See "Serial Manager Tips and Tricks" for debugging information and information on how to fix common errors.


    Opening a Port ^TOP^

    The Serial Manager is installed when the device is booted. Before you can use it, however, you must enable the serial hardware by opening a port.

    You open a port for the Serial Manager differently depending on which API you are using: the new Serial Manager or the old Serial Manager.


    IMPORTANT: Applications that open a serial port are responsible for closing it. Opening a serial port powers up the UART and drains batteries. To conserve battery power, don't keep the port open longer than necessary.


    When you attempt to open a serial port, regardless of which API you use, you must check for errors upon return:

    Opening a Port With the New Serial Manager

    To open a port using the new Serial Manager, call the SrmOpen function, specifying the port (see "Specifying the Port") and the initial baud rate of the UART. SrmOpen returns a port ID that uniquely identifies this connection. You pass this port ID to all other Serial Manager calls.

    Version 2 of the new Serial Manager supports USB and Bluetooth connections as well as RS-232 and IR connections. With the Bluetooth and USB protocols, it is often more important to specify the reason why the application is opening the port. The baud rate is unimportant as that is negotiated in USB and Bluetooth protocols. To open a USB or Bluetooth connection, use SrmExtOpen instead of SrmOpen. This function takes a SrmOpenConfigType structure, which allows you to specify the purpose of the connection instead of the baud rate.

    Once the SrmOpen or SrmExtOpen call is made successfully, it indicates that the Serial Manager has successfully allocated internal structures to maintain the port and has successfully loaded the serial driver for this port.

    Listing 5.1  Opening the port (new Serial Manager)


    UInt16 portId; 
    Boolean serPortOpened = false; 
      
    err = SrmOpen(serPortCradlePort /* port */, 57600, /* baud */ 
      &portId); 
    if (err) { 
       // display error message here. 
    } 
    //record our open status in global. 
    serPortOpened = true; 
    

    A port may be opened with either a foreground connection (SrmOpen or SrmExtOpen) or background connection (SrmOpenBackground or SrmExtOpenBackground). A foreground connection makes an active connection to the port and controls usage of the port until the connection is closed. A background connection opens the port but relinquishes control to any other task requesting a foreground connection. Background connections are provided to support tasks (for example, a keyboard driver) that want to use a serial device to receive data only when no other task is using the port.

    Note that background ports have limited functionality: they can only receive data and notify owning clients of what data has been received.

    Specifying the Port

    Ports must be specified using one of the following methods:

    Note that other 4-character codes for the physical and virtual ports will be added in the future. Also note that the port IDs, like creator IDs, are 4-character constants, not strings. Therefore, they are enclosed in single quotes (' '), not double quotes (" ").

    Opening a Port with the Old Serial Manager

    If you are using the old Serial Manager, there is only one port, so you always pass 0 (or the constant serPortLocalHotSync) to identify the port. The serial library reference number identifies the connection. To obtain the reference number, call SysLibFind, passing "Serial Library" for the library name.

    The reference number remains the same within one invocation of the application. You can close and open the library as needed using the number. Between invocations, the reference number may change. Because of that, you should call SysLibFind each time you reopen the Serial Manager.

    After the call to SysLibFind, use SerOpen to open the port. Like SrmOpen, you pass the baud rate along with the reference number.

    Listing 5.2  Opening the port (old Serial Manager)


    UInt16 refNum = sysInvalidRefNum; 
    Boolean serPortOpened = false; 
    Err err; 
      
    err = SysLibFind("Serial Library", &refNum); 
    err = SerOpen(refNum, 0 /* port is always 0*/,  
      57600 /* baud */); 
    if (err == serErrAlreadyOpen) { 
      err = SerClose(refNum);  
      // display error message here. 
    } 
    //record our open status in global. 
    serPortOpened = true; 
    

    Closing a Port ^TOP^

    Once an application is finished with the serial port, it must close the port using the SrmClose function (or SerClose function if you are using the old Serial Manager). If SrmClose returns no error, it indicates that the Serial Manager has successfully closed the driver and deallocated the data structures used for maintaining the port.

    To conserve battery power, it is important not to leave the serial port open longer than necessary. It is generally better to close and reopen the connection multiple times than it is to leave it open unnecessarily.

    Configuring the Port ^TOP^

    A newly opened port has the default configuration. The default port configuration is:

    You can change this configuration if necessary before sending or receiving data.

    Increasing the Receive Queue Buffer Size

    The default receive queue size is 512 bytes. If you notice a large number of hardware overruns or software overruns while running your application, consider replacing the default receive queue with a bigger one.

    To use a custom receive queue, an application must:

    The following code fragment illustrates replacing the default queue with a custom queue.

    Listing 5.3  Replacing the receive queue


    #define myCustomSerQueueSize 1024 
    void *customSerQP; 
    // Allocate a dynamic memory chunk for our custom receive  
    // queue. 
    customSerQP = MemPtrNew(myCustomSerQueueSize); 
    // Replace the default receive queue. 
    if (customSerQP) { 
      err = SrmSetReceiveBuffer(portId, customSerQP,  
         myCustomSerQueueSize); 
    } 
      
    // ... do Serial Manager work 
      
    // Now restore default queue and delete custom queue. 
    // Pass NULL for the buffer and 0 for bufSize to restore the  
    // default queue. 
    err = SrmSetReceiveBuffer(portId, NULL, 0); 
    if(customSerQP) { 
       MemPtrFree(customSerQP); 
       customSerQP = NULL; 
    } 
    

    Changing Other Configuration Settings

    To change the other serial port settings, use SrmControl (or SerSetSettings in the old Serial Manager API).

    Listing 5.4 configures the serial port for 19200 baud, 8 data bits, even parity, 1 stop bit, and full hardware handshake (input and output) with a CTS timeout of 0.5 seconds. The CTS timeout specifies the maximum number of system ticks the serial library will wait to send a byte when the CTS input is not asserted. The CTS timeout is ignored if srmSettingsFlagCTSAutoM is not set.

    Listing 5.4  Changing the configuration (new Serial Manager)


    Err err; 
    Int32 paramSize; 
    Int32 baudRate = 19200; 
    UInt32 flags = srmSettingsFlagBitsPerChar8 |
    srmSettingsFlagParityOnM | srmSettingsFlagParityEvenM |
    srmSettingsFlagStopBits1 | srmSettingsFlagRTSAutoM |
    srmSettingsFlagCTSAutoM; 
    Int32 ctsTimeout = SysTicksPerSecond() / 2; 
      
    paramSize = sizeof(baudRate); 
    err = SrmControl(portId, srmCtlSetBaudRate, &baudRate,  
       &paramSize); 
      
    paramSize = sizeof(flags); 
    err = SrmControl(portId, srmCtlSetFlags, &flags, &paramSize); 
      
    paramSize = sizeof(ctsTimeout); 
    err = SrmControl(portId, srmCtlSetCtsTimeout, &ctsTimeout,  
       &paramSize); 
    

    Listing 5.5 shows how to set up the same configuration in the old Serial Manager.

    Listing 5.5  Changing the configuration (old Serial Manager)


    SerSettingsType serSettings; 
      
    serSettings.baudRate = 19200; 
    serSettings.flags = serSettingsFlagBitsPerChar8 |
    serSettingsFlagParityOnM | serSettingsFlagParityEvenM |
    serSettingsFlagStopBits1 | serSettingsFlagRTSAutoM |
    serSettingsFlagCTSAutoM; 
    serSettings.ctsTimeout = SysTicksPerSecond() / 2; 
    err = SerSetSettings(refNum, &serSettings); 
    

    The settings remain in effect until you change them again or close the connection. As you configure the Serial Manager, note the following points:

    If you want to find out what the current configuration is, pass one of the srmCtlGet... op codes to the SrmControl function. For example, to find out the current baud rate, pass srmCtlGetBaudRate. To find out the current configuration in the old Serial Manager, use the SerGetSettings function.

    Sending Data ^TOP^

    To send data, use SrmSend (or SerSend in the old Serial Manager). Sending data is performed synchronously. To send data, the application only needs to have an open connection with a port that has been configured properly and then specify a buffer to send. The larger the buffer to send, the longer the send function operates before returning to the calling application. The send function returns the actual number of bytes that were placed in the UART's FIFO. This makes it possible to determine what was sent and what wasn't in case of an error.

    Listing 5.6 illustrates the use of SrmSend.

    Listing 5.6  Sending data


    UInt32 toSend, numSent; 
    Err err; 
    Char msg[] = "logon\n"; 
    toSend = StrLen(msg); 
    numSent = SrmSend(portId, msg, toSend, &err); 
    if (err == serErrTimeOut) { 
      //cts timeout detected 
    } 
    

    If SrmSend returns an error, or if you simply want to ensure that all data has been sent, you can use any of the following functions:

    Receiving Data ^TOP^

    Receiving data is a more involved process because it depends on the receiving application actually listening for data from the port.

    To receive data, an application must do the following:


    TIP: For many applications, the auto-off feature presents no problem. Use EvtResetAutoOffTimer with discretion; applications that use it drain the battery.



    IMPORTANT: Always check for line errors. Due to unpredictable conditions, there is no guaranteed of success. If a line error occurs, all other Serial Manager calls fail until you clear the error.


    For example code that shows how to receive data, see "Receive Data Example".

    In the new Serial Manager, you can directly access the receive queue using SrmReceiveWindowOpen, and SrmReceiveWindowClose. These functions allow fast access to the buffer to reduce buffer copying. These functions are not supported on systems where the new Serial Manager feature set is not present.

    Handling Errors

    If an error occurs on the line, all of the receive functions return the error condition serErrLineErr. This error will continue to be returned until you explicitly clear the error condition and continue.

    To clear line errors, call SrmClearErr (or SerClearErr).

    If you want more information about the error, call SrmGetStatus (or SerGetStatus) before you clear the line.

    Listing 5.7 checks whether a framing or parity error have returned and clears the line errors.

    Listing 5.7  Handling line errors (new Serial Manager)


    void HandleSerReceiveErr(UInt16 portId, Err err) { 
       UInt32 lineStatus; 
       UInt16 lineErrs; 
      
       if (err == serErrLineErr) { 
          SrmGetStatus(portId, &lineStatus, &lineErrs); 
          // test for framing or parity error. 
          if (lineErrs & serLineErrorFraming | serLineErrorParity)  
          { 
                //framing or parity error occurred. Do something. 
          } 
           SrmClearErr(portId); 
       } 
    } 
    

    Listing 5.8 performs the same tasks using the old Serial Manager. Note that the SerGetStatus call looks a little different from the SrmGetStatus call.

    Listing 5.8  Handling line errors (old Serial Manager)


    void HandleSerReceiveErr(UInt16 refNum, Err err) { 
       UInt16 lineErrs; 
       Boolean ctsOn, dsrOn; 
      
       if (err == serErrLineErr) { 
          lineErrs = SerGetStatus(refNum, &ctsOn, &dsrOn); 
          // test for framing or parity error. 
          if (lineErrs & serLineErrorFraming | serLineErrorParity)  
          { 
                //framing or parity error occurred. Do something. 
          } 
           SerClearErr(refNum); 
       } 
    } 
    


    TIP: See "Common Errors" for some common causes of line errors and how to fix them.


    In some cases, you may want to discard any received data when an error occurs. For example, if your protocol is packet driven and you detect data corruption, you should flush the buffer before you continue. To do so, call SrmReceiveFlush (or SerReceiveFlush). This function flushes any bytes in the receive queue and then calls SrmClearErr for you.

    SrmReceiveFlush takes a timeout value as a parameter. If you specify a timeout, it waits that period of time for any other data to be received in the queue and flushes it as well. If you pass 0 for the timeout, it simply flushes the data currently in the queue, clears the line errors, and returns. The flush timeout has to be large enough to flush out the noise but not so large that it flushes part of the next packet.

    Receive Data Example

    Listing 5.9 shows how to receive large blocks of data using the Serial Manager.

    Listing 5.9  Receiving Data Using the Serial Manager


    #include <PalmOS.h> // all the system toolbox headers 
    #include <SerialMgr.h> 
    #define k2KBytes 2048 
    /************************************************************ 
    * 
    * FUNCTION: RcvSerialData 
    * 
    * DESCRIPTION: An example of how to receive a large chunk of data 
    * from the Serial Manager. This function is useful if the app 
    * knows it must receive all this data before moving on. The 
    * YourDrainEventQueue() function is a chance for the application 
    * to call EvtGetEvent and handle other application events. 
    * Receiving data whenever it's available during idle events 
    * might be done differently than this sample. 
    * 
    * PARAMETERS:  
    * thePort -> valid portID for an open serial port. 
    * rcvDataP -> pointer to a buffer to put the received data. 
    * bufSize <-> pointer to the size of rcvBuffer and returns 
    *   the number of bytes read. 
    * 
    ************************************************************/ 
    Err RcvSerialData(UInt16 thePort, UInt8 *rcvDataP, UInt32 *bufSizeP) 
    { 
    UInt32 bytesLeft, maxRcvBlkSize, bytesRcvd, waitTime, totalRcvBytes = 0; 
    UInt8 *newRcvBuffer; 
    UInt16 dataLen = sizeof(UInt32); 
    Err* error; 
      
       // The default receive buffer is only 512 bytes; increase it if  
       // necessary. The following lines are just an example of how to  
       // do it, but its necessity depends on the ability of the code 
       // to retrieve data in a timely manner. 
       newRcvBuffer = MemPtrNew(k2KBytes); // Allocate new rcv buffer. 
       if (newRcvBuffer) 
          // Set new rcv buffer. 
          error = SrmSetReceiveBuffer(thePort, newRcvBuffer, k2KBytes); 
          if (error) 
             goto Exit; 
       else 
          return memErrNotEnoughSpace; 
      
       // Initialize the maximum bytes to receive at one time. 
       maxRcvBlkSize = k2KBytes; 
       // Remember how many bytes are left to receive. 
       bytesLeft = *bufSizeP; 
       // Only wait 1/5 of a second for bytes to arrive. 
       waitTime = SysTicksPerSecond() / 5; 
        
       // Now loop while getting blocks of data and filling the buffer. 
       do { 
          // Is the max size larger then the number of bytes left? 
          if (bytesLeft < maxRcvBlkSize) 
             // Yes, so change the rcv block amount. 
        maxRcvBlkSize = bytesLeft;  
          // Try to receive as much data as possible,  
          // but wait only 1/5 second for it. 
          bytesRcvd = SrmReceive(thePort,  rcvDataP, maxRcvBlkSize, waitTime,  
             &error); 
          // Remember the total number of bytes received. 
          totalRcvBytes += bytesRcvd; 
          // Figure how many bytes are left to receive. 
          bytesLeft -= bytesRcvd; 
          rcvDataP += bytesRcvd; // Advance the rcvDataP. 
          // If there was a timeout and no data came through... 
          if ((error == serErrTimeOut) && (bytesRcvd == 0)) 
             goto ReceiveError; // ...bail out and report the error. 
          // If there's some other error, bail out. 
          if ((error) && (error != serErrTimeOut)) 
             goto ReceiveError; 
      
          // Call a function to handle any pending events because 
          // someone might press the cancel button. 
          YourDrainEventQueue(); 
       // Continue receiving data until all data has been received. 
       } while (bytesLeft); 
        
       ReceiveError: 
          // Clearing the receive buffer can also be done right before  
          // the port is to be closed. 
          // Set back the default buffer when we're done. 
          SrmSetReceiveBuffer(thePort, 0L, 0); 
      
       Exit: 
          MemPtrFree(newRcvBuffer); // Free the space. 
          *bufSizeP = totalRcvBytes; 
          return error; 
    } 
    

    Serial Manager Tips and Tricks ^TOP^

    The following tips and tricks help you debug your serial application and help avoid errors in the first place.

    Debugging Tips

    The following are some tips to help you track down errors while debugging.


    NOTE: Restrict writing to the HotSync log to debugging. Users will not appreciate having your debugging messages in their HotSync log.


    Common Errors

    Even if you're careful, errors may crop up. Here are some frequently encountered problems and their solutions.

    Writing a Virtual Device Driver ^TOP^

    If the new Serial Manager feature set is present, the Serial Manager supports the ability to add virtual device drivers to the system. Virtual serial device drivers transmit and receive data in blocks instead of a byte at a time.

    A virtual driver is a code resource (ID=0) that is independently compiled and installed on a Palm device. Virtual driver .prc files are of file type 'vdrv' and their creator type is chosen by the developer (and must be registered with PalmSource, Inc. in the creator ID database). When the Serial Manager is installed, it searches the Database Manager for code resources of the 'vdrv' type and then calls the driver's entry point function to get information about the features and capabilities of this virtual device. Unlike physical serial device drivers, virtual device drivers send and receive data in blocks instead of transferring one byte at a time. Their purpose is to abstract a level of communication protocol away from serial devices without forcing applications to work through a different API than the Serial Manager that may already be used for normal RS-232 serial communication.


    NOTE: Creator types with all lowercase letters are reserved by PalmSource, Inc. For more information about assigning and registering creator types, see "Assigning a Database Type and Creator ID" on page 15 of the Palm OS Programmer's Companion, vol. I.


    Virtual Driver Functions

    There are six functions that each virtual driver must minimally support in order to work with the Serial Manager. These functions are briefly described in this section. For details on the exact operations each function must perform, see the function descriptions in the Palm OS Programmer's API Reference.

    The functions a virtual driver must implement include:

    Note that there is no virtual read function in the current implementation. Virtual devices must save received data by using the functions provided in the DrvrRcvQType when they are notified that data is available using some callback mechanism.

    For an example of how to implement a virtual serial driver, download the CryptoDrvr example from the Palm OS Developer Knowledge Base.

    The Connection Manager ^TOP^

    The Connection Manager allows applications to access, add, and delete connection profiles contained in the Connection preferences panel. Earlier releases of the Palm OS have a Modem preferences panel. The Connection panel replaces the Modem panel. This change was made as more connection choices (serial cable, IR, modem, network and so on) became available to users.

    The Connection Manager was introduced at the same time as the Connection panel to manage connection profiles that save preferences for various connection types. A connection profile includes information on the hardware port to be used for a particular connection, the port details (speed, flow control, modem initialization string), and any other pertinent information.

    The Connection Manager is not available on all Palm devices. You must ensure that it is present before you can make Connection Manager calls. If the New Serial Manager Feature Set is present, then at least the basic version of the Connection Manager is available. If the Connection Manager Feature Set is present, then an expanded version of the Connection Manager is available. This expanded Connection Manager allows profiles that specify communications with mobile phones and profiles that specify communications with Bluetooth devices. It is also more extensible, allowing you to create your own profile parameters if necessary.


    NOTE: Although the Connection Manager supports Bluetooth connections, Bluetooth requires additional hardware and software that is not available as of this writing.


    The basic version of the Connection Manager provides functions that list the saved connection profiles (CncGetProfileList), return details for a specific profile (CncGetProfileInfo), add a profile (CncAddProfile), and delete a profile (CncDeleteProfile).

    When you create a profile with the basic Connection Manager, each profile parameter is passed as a parameter to the CncAddProfile function. Similarly, when you request profile information, each profile parameter is passed in an output parameter to CncGetProfileInfo.

    Because the newer, expanded Connection Manager supports more types of connections than the basic Connection Manager, it also supports many more types of profile parameters. For this reason, you now retrieve profile information one parameter at a time using CncProfileSettingGet. In the new API, constants specify the predefined profile parameters. (See "Profile Parameter Constants" of the Palm OS Programmer's API Reference.) For example, to retrieve the connection's port, you use code similar to that shown in Listing 5.10.

    Listing 5.10  Retrieving port information


    UInt16 dataSize; 
    UInt32 portCreator; 
      
    dataSize = kCncParamPortSize; 
    err = CncProfileSettingGet(profileID, kCncParamPort,  
       &portCreator, &dataSize); 
    

    To create a profile, you first must obtain a unique profile ID and then set the profile parameters one by one as shown in Listing 5.11. Note that Listing 5.11 uses CncProfileOpenDB to open the Connection Manager profile database and CncProfileCloseDB to close it. These are not required calls. If you don't explicitly open and close the database, each Connection Manager function opens the database, performs its work, and then closes the database. By calling CncProfileOpenDB in front of a series of Connection Manager calls and calling CncProfileCloseDB at the end, you save the overhead of having each function open and close the database.

    Listing 5.11  Creating a connection profile


    // Open the Connection Manager profile database; 
    err = CncProfileOpenDB(); 
    // obtain new profile ID.  
    err = CncProfileCreate(&profileId); 
      
    if (!err) { 
       // Create a name for the profile.  
       err = CncProfileSettingSet(profileId, kCncParamName,  
             myProfileName, StrLen(myProfileName)+1); 
      
       // Set some other required parameters.  
       port = serPortLocalHotSync; 
       err = CncProfileSettingSet(profileId, kCncParamPort,  
          &port, kCncParamPortSize); 
       baud = 57600; 
       err = CncProfileSettingSet(profileId, kCncParamBaud,  
          &baud, kCncParamBaudSize); 
       deviceKind = kCncDeviceKindSerial; 
       err = CncProfileSettingSet(profileId, kCncParamDeviceKind,  
          &deviceKind, kCncParamDeviceKindSize); 
    } 
      
    // close the profile database. 
    err = CncProfileCloseDB(); 
    

    The expanded Connection Manager API also allows you to create profile parameters that are unique to your type of connection. You can do so with the CncDefineParamID macro. See its description in the Palm OS Programmer's API Reference for more information.

    The Serial Link Protocol ^TOP^

    The Serial Link Protocol (SLP) provides an efficient packet send and receive mechanism that is used by the Palm desktop software and debugger. SLP provides robust error detection with CRC-16. SLP is a best-effort protocol; it does not guarantee packet delivery (packet delivery is left to the higher-level protocols). For enhanced error detection and implementation convenience of higher-level protocols, SLP specifies packet type, source, destination, and transaction ID information as an integral part of its data packet structure.

    SLP Packet Structures ^TOP^

    The following sections describe:

    SLP Packet Format

    Each SLP packet consists of a packet header, client data of variable size, and a packet footer, as shown in Figure 5.3.

    Figure 5.3  Structure of a Serial Link Packet

    Packet Type Assignment

    Packet type values in the range of 0x00 through 0x7F are reserved for use by the system software. The following packet type assignments are currently implemented:

     

    0x00

    Remote Debugger, Remote Console, and System Remote Procedure Call packets.

    0x02

    PADP packets.

    0x03

    Loop-back test packets.

     

    Socket ID Assignment

    Socket IDs are divided into two categories: static and dynamic. The static socket IDs are "well-known" socket ID values that are reserved by the components of the system software. The dynamic socket IDs are assigned at runtime when requested by clients of SLP. Static socket ID values in the ranges 0x00 through 0x03 and 0xE0 through 0xFF are reserved for use by the system software. The following static socket IDs are currently implemented or reserved:

     

    0x00

    Remote Debugger socket.

    0x01

    Remote Console socket.

    0x02

    Remote UI socket.

    0x03

    Desktop Link Server socket.

    0x04 -0xCF

    Reserved for dynamic assignment.

    0xD0 - 0xDF

    Reserved for testing.

     

    Transaction ID Assignment

    Transaction ID values are not interpreted by the Serial Link Protocol and are for the sole benefit of the higher-level protocols. The following transaction ID values are currently reserved:

     

    0x00 and 0xFF

    Reserved for use by the system software.

    0x00

    Reserved by the Palm OS implementation of SLP to request automatic transaction ID generation.

    0xFF

    Reserved for the connection manager's WakeUp packets.

     

    Transmitting an SLP Packet ^TOP^

    This section provides an overview of the steps involved in transmitting an SLP packet. The next section describes the implementation.

    Transmission of an SLP packet consists of these steps:

    1. Fill in the packet header and compute its checksum.
    2. Compute the CRC-16 of the packet header and client data.
    3. Transmit the packet header, client data, and packet footer.
    4. Return an error code to the client.

    Receiving an SLP Packet ^TOP^

    Receiving an SLP packet consists of these steps:

    1. Scan the serial input until the packet header signature is matched.
    2. Read in the rest of the packet header and validate its checksum.
    3. Read in the client data.
    4. Read in the packet footer and validate the packet CRC.
    5. Dispatch/return an error code and the packet (if successful) to the client.

    The Serial Link Manager ^TOP^

    The serial link manager is the Palm OS implementation of the Serial Link Protocol.

    Serial link manager provides the mechanisms for managing multiple client sockets, sending packets, and receiving packets both synchronously and asynchronously. It also provides support for the Remote Debugger and Remote Procedure Calls (RPC).

    Using the Serial Link Manager ^TOP^

    Before an application can use the services of the serial link manager, the application must open the manager by calling SlkOpen. Success is indicated by error codes of 0 (zero) or slkErrAlreadyOpen. The return value slkErrAlreadyOpen indicates that the serial link manager has already been opened (most likely by another task). Other error codes indicate failure.

    When you finish using the serial link manager, call SlkClose. SlkClose may be called only if SlkOpen returned 0 (zero) or slkErrAlreadyOpen. When the open count reaches zero, SlkClose frees resources allocated by SlkOpen.

    To use the serial link manager socket services, open a Serial Link socket by calling SlkOpenSocket. Pass a reference number or port ID (for the Serial Manager) of an opened and initialized communications library (see SlkClose), a pointer to a memory location for returning the socket ID, and a Boolean indicating whether the socket is static or dynamic. If a static socket is being opened, the memory location for the socket ID must contain the desired socket number. If opening a dynamic socket, the new socket ID is returned in the passed memory location. Sharing of sockets is not supported. Success is indicated by an error code of 0 (zero). For information about static and dynamic socket IDs, see "Socket ID Assignment".

    When you have finished using a Serial Link socket, close it by calling SlkCloseSocket. This releases system resources allocated for this socket by the serial link manager.

    To obtain the communications library reference number for a particular socket, call SlkSocketRefNum. The socket must already be open. To obtain the port ID for a socket, if you are using the Serial Manager, call SlkSocketPortID.

    To set the interbyte packet receive timeout for a particular socket, call SlkSocketSetTimeout.

    To flush the receive stream for a particular socket, call SlkFlushSocket, passing the socket number and the interbyte timeout.

    To register a socket listener for a particular socket, call SlkSetSocketListener, passing the socket number of an open socket and a pointer to the SlkSocketListenType structure. Because the serial link manager does not make a copy of the SlkSocketListenType structure but instead saves the pointer passed to it, the structure may not be an automatic variable (that is, allocated on the stack). The SlkSocketListenType structure may be a global variable in an application or a locked chunk allocated from the dynamic heap. The SlkSocketListenType structure specifies pointers to the socket listener procedure and the data buffers for dispatching packets destined for this socket. Pointers to two buffers must be specified:

    Both buffers can be application global variables or locked chunks allocated from the dynamic heap.

    The socket listener procedure is called when a valid packet is received for the socket. Pointers to the packet header buffer and the packet body buffer are passed as parameters to the socket listener procedure. The serial link manager does not free the SlkSocketListenType structure or the buffers when the socket is closed; freeing them is the responsibility of the application. For this mechanism to function, some task needs to assume the responsibility to "drive" the serial link manager receiver by periodically calling SlkReceivePacket.

    To send a packet, call SlkSendPacket, passing a pointer to the packet header (SlkPktHeaderType) and a pointer to an array of SlkWriteDataType structures. SlkSendPacket stuffs the signature, client data size, and the checksum fields of the packet header. The caller must fill in all other packet header fields. If the transaction ID field is set to 0 (zero), the serial link manager automatically generates and stuffs a new non-zero transaction ID. The array of SlkWriteDataType structures enables the caller to specify the client data part of the packet as a list of noncontiguous blocks. The end of list is indicated by an array element with the size field set to 0 (zero). Listing 5.12 incorporates the processes described in this section.

    Listing 5.12  Sending a Serial Link Packet


    Err                     err; 
    //serial link packet header 
    SlkPktHeaderType        sendHdr; 
    //serial link write data segments 
    SlkWriteDataType         writeList[2];  
    //packet body(example packet body) 
    UInt8           body[20]; 
      
    // Initialize packet body 
    ... 
      
    // Compose the packet header. Let Serial Link Manager 
    // set the transId.  
    sendHdr.dest = slkSocketDLP; 
    sendHdr.src = slkSocketDLP; 
    sendHdr.type = slkPktTypeSystem; 
    sendHdr.transId = 0;  
      
    // Specify packet body 
    writeList[0].size = sizeof(body);     //first data block size 
    writeList[0].dataP = body;    //first data block pointer 
    writeList[1].size = 0;    //no more data blocks  
      
    // Send the packet 
    err = SlkSendPacket( &sendHdr, writeList ); 
       ... 
    } 
    

    Listing 5.13  Generating a New Transaction ID


    // 
    // Example: Generating a new transaction ID given the  
    // previous transaction ID. Can start with any seed value. 
    // 
      
    UInt8 NextTransactionID (UInt8 previousTransactionID) 
    { 
       UInt8  nextTransactionID; 
        
       // Generate a new transaction id, avoid the  
       // reserved values (0x00 and 0xFF) 
       if ( previousTransactionID >= (UInt8)0xFE ) 
          nextTransactionID = 1;    // wrap around 
       else 
          nextTransactionID = previousTransactionID + 1; 
          // increment 
        
       return nextTransactionID; 
    } 
    

    To receive a packet, call SlkReceivePacket. You may request a packet for the passed socket ID only, or for any open socket that does not have a socket listener. The parameters also specify buffers for the packet header and client data, and a timeout. The timeout indicates how long the receiver should wait for a packet to begin arriving before timing out. A timeout value of (-1) means "wait forever." If a packet is received for a socket with a registered socket listener, the packet is dispatched via its socket listener procedure.

    Summary of Serial Communications ^TOP^

     

    New and Old Serial Manager Functions

    Opening and Closing the Port

    SrmOpen
    SrmOpenBackground
    SerOpen
    SerClose

    SrmExtOpen
    SrmExtOpenBackground
    SrmClose

    Receiving Data

    SrmReceive
    SrmReceiveCheck
    SrmReceiveFlush
    SrmReceiveWait
    SrmReceiveWindowClose
    SrmReceiveWindowOpen

    SerReceive
    SerReceiveCheck
    SerReceiveFlush
    SerReceiveWait

    Sending Data

    SrmSend
    SrmSendCheck
    SrmSendFlush
    SrmSendWait

    SerSend
    SerSendFlush
    SerSendWait

    Configuring the Port

    SrmSetReceiveBuffer
    SrmControl
    SrmCustomControl

    SerControl
    SerSetReceiveBuffer
    SerSetSettings
    SerGetSettings

    Error Checking

    SrmClearErr
    SrmGetStatus

    SerClearErr
    SerGetStatus

    Obtaining Device Information

    SrmGetDeviceCount

    SrmGetDeviceInfo

    Implementing a Wakeup Handler

    SrmPrimeWakeupHandler

    SrmSetWakeupHandler

     

     

    Virtual Driver Functions

     

    DrvEntryPointProcPtr
    GetSizeProcPtr
    GetSpaceProcPtr
    VdrvControlProcPtr
    VdrvOpenProcPtr
    VdrvOpenProcV4Ptr

    VdrvStatusProcPtr
    VdrvWriteProcPtr
    VdrvControlCustomProcPtr
    WriteBlockProcPtr
    WriteByteProcPtr
    SignalCheckPtr

     

     

    Connection Manager Functions

     

    Basic Connection Manager Functions

    CncAddProfile
    CncDeleteProfile

    CncGetProfileInfo
    CncGetProfileList

    Extended Connection Manager Functions

    CncProfileCreate
    CncProfileDelete
    CncProfileGetCurrent
    CncProfileGetIDFromIndex
    CncProfileGetIDFromName
    CncProfileGetIndex
    CncProfileOpenDB
    CncProfileSetCurrent
    CncProfileSettingGet
    CncProfileSettingSet

    CncGetParamType
    CncGetSystemFlagBitnum
    CncGetTrueParamID
    CncIsFixedLengthParamType

    CncIsSystemFlags
    CncIsSystemRange
    CncIsThirdPartiesRange
    CncIsVariableLengthParamType
    CncProfileCloseDB
    CncProfileCount

     

     

    Serial Link Manager Functions

     

    SlkClose
    SlkCloseSocket
    SlkFlushSocket
    SlkOpen
    SlkOpenSocket

    SlkReceivePacket
    SlkSendPacket
    SlkSetSocketListener
    SlkSocketPortID
    SlkSocketSetTimeout

    sdasd