Basic Serial Communications

< BACK  NEXT >
[oR]

Let's start out by writing routines to implement basic serial communications through the standard COM1 port. The basic functions that need to be implemented are the following:

  • Open the communications port using CreateFile

  • Create a thread to read from the communications port using ReadFile

  • Write to the communications port using WriteFile

  • Close the communications port using CloseHandle

The CreateFile function is used to open a communications port this is the same function described in Chapter 2 for opening and creating files. In addition, the communications port will need to be configured for speed of transmission, handshaking protocols, and timeout values. A thread is used to read data from the communications port so that the process's primary thread is not blocked waiting for data to arrive. Serial communications in Windows CE provides the SetCommMask and WaitCommEvent to provide non-busy thread blocking while waiting for data to arrive.

Opening and Configuring a Serial Communications Port

There are, in general, four steps in opening a serial communications port:

  • Open the port using the CreateFile function.

  • Set the read and write timeout values using the SetCommTimeouts function.

  • Call GetCommState to get the current port configuration values, update the values as appropriate, and call SetCommState to reconfigure the communications port.

  • Create a thread to read data from the communications port.

Opening a serial communications port Listing 9.1a shows how CreateFile is called to open a communications port (the CreateFile function is described in detail in Chapter 2). When using this function to open a communications port, you should specify the name of the port in the first argument (the file name). With Windows CE it is essential that a trailing colon be used in naming the port (such as "COM1:"), otherwise, the function will fail. The communications port is opened using the following parameters passed to CreateFile:

  • The name of the communications port ("COM1:" in this case).

  • Opened for reading and writing (GENERIC_READ and GENERIC_WRITE).

  • A zero value for sharing (since the port cannot be shared).

  • Security attributes are always NULL.

  • OPEN_EXISTING, since the communications port must exist if it is to be opened.

  • Overlapped I/O is not supported, so 0 is passed.

  • Templates are not supported, so the last parameter is always NULL.

CreateFile returns INVALID_HANDLE_VALUE if the port could not be opened (note that a NULL handle is not returned), or a valid handle if successful. The most likely cause of a failure is that the communications port is already opened, in which case GetLastError will return 55. This will happen if you are using the Windows CE device's serial port for ActiveSync and attempting to open the port for communications. In this case, you will need to use ActiveSync to download the compiled executable using eMbedded Visual C++, then disable ActiveSync's use of the communications port, run your application, and open the communications port. The easiest way to disable ActiveSync's use of the communications port is to do the following:

  • Download your application as normal

  • Open ActiveSync and select the File+Connection Settings dialog

  • Un-check the "Allow Serial Cable or Infrared connection to this COM Port" check box

Doing this will automatically disconnect the Windows CE device, and the port on the device will be closed. Your application can then open the port. When you have finished testing your application, you will need to ensure that your application has closed the communications port and then reestablish an ActiveSync session. This is easily done by checking the "Allow Serial Cable or Infrared connection to this COM Port" check box in the ActiveSync Connection settings dialog. A new ActiveSync session will automatically be established.

Listing 9.1a Opening and configuring a serial communications port
 void ReportCommError(LPTSTR lpszMessage); DWORD WINAPI CommReadThreadFunc(LPVOID lpParam); BOOL SendText(HWND hWnd); HANDLE hCommPort = INVALID_HANDLE_VALUE; void Listing9_1() {   hCommPort = CreateFile (_T("COM1:"),     GENERIC_READ | GENERIC_WRITE,     0,        // COM port cannot be shared     NULL,    // Always NULL for Windows CE     OPEN_EXISTING,     0,          // Non-overlapped operation only     NULL);      // Always NULL for Windows CE   if(hCommPort == INVALID_HANDLE_VALUE)   {   ReportCommError(_T("Opening Comms Port."));     return;   }   // set the timeouts to specify the behavior of   // reads and writes.   COMMTIMEOUTS ct;   ct.ReadIntervalTimeout = MAXDWORD;   ct.ReadTotalTimeoutMultiplier = 0;   ct.ReadTotalTimeoutConstant = 0;   ct.WriteTotalTimeoutMultiplier = 10;   ct.WriteTotalTimeoutConstant = 1000;   if(!SetCommTimeouts(hCommPort, &ct))   {     ReportCommError(_T("Setting comm. timeouts."));     Listing9_2();  // close comm port     return;   }   // Get the current communications parameters,   // and configure baud rate   DCB dcb;   dcb.DCBlength = sizeof(DCB);   if(!GetCommState(hCommPort, &dcb))   {     ReportCommError(_T("Getting Comms. State."));     Listing9_2(); // close comm port     return;   }   dcb.BaudRate = CBR_19200; // set baud rate to 19,200   dcb.fOutxCtsFlow = TRUE;   dcb.fRtsControl        = RTS_CONTROL_HANDSHAKE;   dcb.fDtrControl        = DTR_CONTROL_ENABLE;   dcb.fOutxDsrFlow       = FALSE;   dcb.fOutX              = FALSE; // no XON/XOFF control   dcb.fInX               = FALSE;   dcb.ByteSize           = 8;   dcb.Parity            = NOPARITY;   dcb.StopBits          = ONESTOPBIT;   if(!SetCommState(hCommPort, &dcb))   {     ReportCommError(_T("Setting Comms. State."));     Listing9_2(); // close comm port     return;   }   // now need to create the thread that will   // be reading the comms port   HANDLE hCommReadThread = CreateThread(NULL, 0,       CommReadThreadFunc, NULL, 0, NULL);   if(hCommReadThread == NULL)   {     ReportCommError(_T("Creating Thread."));     Listing9_2(); // close comm port     return;   }   else     CloseHandle(hCommReadThread); } void ReportCommError(LPTSTR lpszMessage) {   TCHAR szBuffer[200];   wsprintf(szBuffer,     _T("Communications Error %d \r\n%s"),     GetLastError(),     lpszMessage);   cout   szBuffer   endl; } 

Setting communications port timeouts Once the communications port has been opened, timeout values need to be configured using the SetCommTimeouts function that is passed the handle to an open communications port and a pointer to a COMMTIMEOUTS structure. The primary purpose of setting timeouts is to ensure that your application does not block while attempting to read data from a port. For example, if you use ReadFile to read 100 bytes of data, the call will block until 100 characters are available to be read. If this never happens the call to ReadFile will block forever. Using timeouts you can have ReadFile return immediately from any ReadFile operation regardless of thenumber of bytes found, or return after a specified number of milliseconds. Table 9.1 shows the COMMTIMEOUTS structure members that can be used to setvarious timeout parameters.

Table 9.1. COMMTIMEOUTS structure members
Structure Member Purpose
DWORD ReadIntervalTimeout The maximum amount of time in milliseconds to elapse between characters arriving at the port.
DWORD ReadTotalTimeoutMultiplier Read timeout multiplier in milliseconds. This figure is multiplied by the number of characters being read to obtain the overall timeout period.
DWORD ReadTotalTimeoutConstant Timeout constant in milliseconds. This value is added to the value calculated using the ReadTotalTimeout-Multiplier.
DWORD WriteTotalTimeoutMultiplier Write timeout multiplier in milliseconds. This figure is multiplied by the number of characters being written to obtain the overall timeout period.
DWORD WriteTotalTimeoutConstant Timeout constant in milliseconds. This value is added to the value calculated using the WriteTotalTimeout-Multiplier.

The ReadIntervalTimeout value controls timeouts on the interval between characters. For example, you might set it to 1000 to indicate that a ReadFile operation should return if two characters are spaced out by more than one second. If you set the ReadIntervalTimeout value to 0, no interval timeouts will be used. A value of MAXDWORD is used, and ReadTotalTimeoutMultiplier and ReadTotalTimeoutConstant are both set to zero. ReadFile will return immediately with whatever the input buffer contains.

The ReadTotalTimeoutMultiplier and ReadTotalTimeoutConstant values let you set a timeout based on the number of bytes specified in the ReadFile statement. If you ask to read 100 bytes, for example, the following calculation determines the timeout value:

total timeout value = 100 * readtotaltimeoutmultiplier + readtotaltimeoutconstant

After the specified number of milliseconds have elapsed, the Read-File function returns regardless of how many bytes have actually been read. The same process is used for the WriteTotalTimeoutMultiplier and WriteTotalTimeoutConstant for data being written out to the communications port.

The default timeout values used when a communications port is opened are the values used by the last application that opened the port. Therefore it is important to set the timeout values each time you open the port.

Configuring a port Windows CE uses the default port settings when you call the function CreateFile. You generally need to set the port settings (such as Baud rate and parity) to match the host with which you are communicating. The DCB structure contains members for all the configurable port settings. The functions GetCommState and SetCommState use the DCB structure to retrieve or set the port settings. You should configure the port after opening it but before reading or writing to it.

The DCB structure has about 30 different members that are used to configure the communications port. Luckily, only a few of the members are frequently used and the easiest way of configuring a port is to read the current values using GetCommState, then modify the structure as required, and set the new values using GetCommState as shown in this code from Listing 9.1a.

 DCB dcb; dcb.DCBlength = sizeof(DCB); if(!GetCommState(hCommPort, &dcb)) {   ReportCommError(_T("Getting Comms. State."));   Listing9_2(); // close comm port   return; } dcb.BaudRate = CBR_19200; // set baud rate to 19,200 dcb.fOutxCtsFlow = TRUE; dcb.fRtsControl    = RTS_CONTROL_HANDSHAKE; dcb.fDtrControl    = DTR_CONTROL_ENABLE; dcb.fOutxDsrFlow   = FALSE; dcb.fOutX          = FALSE; // no XON/XOFF control dcb.fInX           = FALSE; dcb.ByteSize       = 8; dcb.Parity         = NOPARITY; dcb.StopBits       = ONESTOPBIT; if(!SetCommState(hCommPort, &dcb)) {   ReportCommError(_T("Setting Comms. State."));   Listing9_2(); // close comm port   return; } 

The members commonly used are:

  • DCBlength. This member should be set to the length of the DCB structure.

  • BaudRate. The required Baud rate should be assigned to this member, either as a numeric (for example, 9600) or as a constant (for example, CBR_9600).

  • ByteSize, Parity, and StopBits. These members are used collectively to specify the number of bits that are transmitted and their meaning. These days, hardly anyone uses parity, so the code shown above nearly always suffices.

  • fOutX and fInX. These members are used to specify whether XON/XOFF software flow control is to be used. Although you can specify XON/XOFF in only one direction (for example, when sending), it is usual to configure XON/XOFF for both sending and receiving. You can assign TRUE to fOutX and fInX to enable XON/XOFF flow control.

  • fRtsControl and fOutxCtsFlow. Ready to Send (RTS) and Clear to Send (CTS) is the most common form of hardware handshaking and is enabled by assigning RTS_CONTROL_HANDSHAKE to fRtsControl and TRUE to fOutxCtsFlow.

  • fOutxDsrFlow and fDtrControl. Data Set Ready (DSR) and Data Terminal Ready (DTR) can be used for flow control, although that is much less common than CTR/RTS. More commonly, hosts require that the DTR line be turned on for the entire time the connection remains open, and this is enabled by assigning DTR_CONTROL_ENABLE to fDtrControl.

You should always use either hardware flow control or software flow control, but not both at the same time. It is very dangerous not to use any flow control, because this will result in data loss.

Creating a thread for reading the communications port The last task in Listing 9.1a is the creation of a thread that will be responsible for reading incoming data from the communications port. The thread is created using a call to CreateThread passing the usual parameters (described in Chapter 5).

 HANDLE hCommReadThread = CreateThread(NULL, 0,     CommReadThreadFunc, NULL, 0, NULL); if(hCommReadThread == NULL) {   ReportCommError(_T("Creating Thread."));   Listing9_2(); // close comm port   return; } else   CloseHandle(hCommReadThread); 

Reading from the communications port is done through a thread so that the primary thread is not blocked waiting for data. The thread function is described in the next section.

Reading Data from the Communications Port

The thread function CommReadThreadFunc is used to read data from the communications port using ReadFile. With Windows CE you cannot read and write data to the port at the same time. If you call ReadFile with a long timeout, any calls to WriteFile wait until the ReadFile call completes or a timeout occurs. This can disrupt the transfer of data. Instead of calling ReadFile and waiting for data to arrive, you should use communications events that will block until data arrives. You can then call ReadFile to read this data without blocking. Communications events enable non-busy blocking, so they are ideally suited for multithreaded applications. To use communication events, you should do the following:

  • Call SetCommMask. The call to this function specifies the event or events that you are interested in responding to. For example, the constant EV_RXCHAR specifies that the application is interested in blocking until characters are received.

  • Call WaitCommEvent. The call to this function will block until the specified event (for example EV_RXCHAR) has occurred. Since you can specify several different events in a single call to SetCommMask, the function WaitCommEvent returns the event that caused WaitCommEvent to unblock in the second argument.

  • Call SetCommMask. Once WaitCommEvent returns, you will need to call SetCommMask again with the same arguments before calling WaitCommEvent again.

In Listing 9.1b the thread function calls SetCommMask specifying the EV_RXCHAR event. Then it enters a while loop which does the following:

  • Calls WaitCommEvent to block until characters arrive at the port. This function is passed the communications handle, the DWORD variable to receive the event constant, and a third parameter not supported in Windows CE.

  • Calls SetCommMask to reenable the event.

  • Reads the characters into a buffer one by one using the ReadFile function.

  • Displays the data in the buffer when the buffer is nearly full.

The ReadFile function is used to read up to 999 characters from the communications port the actual number read is returned in dwBytesRead. The function assumes that this data consists of ANSI characters (which is usually the case for serial communications) and uses the mbstowcs function to convert to Unicode after appending a NULL character, which terminates the string returned from ReadFile.

Listing 9.1b Thread function for reading from communications port
 DWORD WINAPI CommReadThreadFunc(LPVOID lpParam) {   DWORD dwBytesRead;   DWORD fdwCommMask;   TCHAR szwcsBuffer[1000];   char szBuffer[1000];   SetCommMask (hCommPort, EV_RXCHAR);   szBuffer[0] = '\0';   while(hCommPort != INVALID_HANDLE_VALUE)   {     if(!WaitCommEvent (hCommPort, &fdwCommMask, 0))     {       // has the comms port been closed?       if(GetLastError() != ERROR_INVALID_HANDLE)         ReportCommError           (_T("WaitCommEvent."));       return 0;     }     SetCommMask (hCommPort, EV_RXCHAR);     // Read ANSI characters     if(!ReadFile(hCommPort,          szBuffer,          999,          &dwBytesRead,          NULL))     {       ReportCommError(_T("Reading comms port."));       return 0;     }     szBuffer[dwBytesRead] = '\0';  // NULL terminate     // now convert to Unicode     mbstowcs(szwcsBuffer, szBuffer, 1000);     cout   szwcsBuffer;   }   return 0; } 

The function SetCommMask can be used to set any number of the available values specified in Table 9.2. When multiple values are specified, the function WaitCommEvent returns the event mask that occurred and a program should take appropriate action based on the event.

 SetCommMask(hCommPort, EV_RXCHAR | EV_ERR); WaitCommEvent(hCommPort, &fdwCommMask, 0); if(fdwCommMask == EV_RXCHAR) {   // read characters } else if(fdwCommMask == EV_ERR) {   // deal with communications error } 

Table 9.2. Events mask values for SetCommMask
Mask Purpose
EV_BREAK A break was detected on input.
EV_CTS The CTS (clear-to-send) signal changed state.
EV_DSR The DSR (data-set-ready) signal changed state.
EV_ERR A line-status error occurred. Line-status errors are CE_FRAME, CE_OVERRUN, and CE_RXPARITY.
EV_RING A ring indicator was detected.
EV_RLSD The RLSD (receive-line-signal-detect) signal changed state.
EV_RXCHAR A character was received and placed in the input buffer.
EV_RXFLAG The event character was received and placed in the input buffer. The event character is specified in the device's DCB structure.
EV_TXEMPTY The last character in the output buffer was sent.

Closing a Communications Port

The CloseHandle function is used to close a communications port. The code in Listing 9.2 closes the port and sets the hCommPort variable to NULL. If you look back at Listing 9.1b, you will see that the "while" loop used to read data from the communications port terminates when hCommPort becomes NULL. As it happens, the loop is most likely to be blocked in a call to WaitCommEvent. When the communications port is closed by another thread, WaitCommEvent returns FALSE, and GetLastError returns an error number 6 (ERROR_INVALID_HANDLE) indicating that the communications port handle is no longer valid. The thread is then terminated when the function executes a "return" statement.

Listing 9.2 Closing a communications port
 void Listing9_2() {   if(hCommPort != INVALID_HANDLE_VALUE)   {     CloseHandle(hCommPort);     hCommPort = INVALID_HANDLE_VALUE;     cout   _T("Com. port closed")   endl;   }   else     cout   _T("Com. port was not open")   endl; } 

Writing to a Communications Port

Data can be written out to an open communications port using the WriteFile function. The parameters passed are the following:

  • The handle to the open communications port

  • A pointer to the buffer containing the data to be written

  • The number of bytes to write

  • A pointer to a DWORD that contains the actual number of bytes written

  • A NULL for an unsupported parameter

In Listing 9.3 the user is prompted to enter text in a dialog, and this text will be sent to the communications port using WriteFile. The Unicode text is first converted to ANSI using the wcstombs function.

Listing 9.3 Writing to a communications port
 #define BUFF_SIZE 200 void Listing9_3() {   DWORD dwBytesToWrite;   DWORD dwBytesWritten;   TCHAR szwcsBuffer[BUFF_SIZE];   char szBuffer[BUFF_SIZE];   if(!GetTextResponse(_T("Text to send:"),       szwcsBuffer, 200))     return;   // convert to ANSI character set   dwBytesToWrite = wcstombs(szBuffer,       szwcsBuffer, BUFF_SIZE);   // append a carriage return/line feed pair   szBuffer[dwBytesToWrite++] = '\r';   szBuffer[dwBytesToWrite++] = '\n';   if(!WriteFile(hCommPort,       szBuffer,       dwBytesToWrite,       &dwBytesWritten,       NULL))   {     ReportCommError(_T("Sending text."));     return;   } } 

Testing Communications

The communications code in Listings 9.1a, 9.1b, and 9.2 can be tested using the "examples" application supplied on the CDROM. You can hook up your Windows CE device to a Windows NT or 2000 desktop PC using a serial cable, and then run Hyperterminal by selecting the Start + Programs + Accessories + Communications + Hyperterminal menu command on the desktop. Make sure that you first disable ActiveSync if it is configured to use the same communications port. You should create a new connection in Hyperterminal by selecting File+ New Connection ignore any dialog boxes requesting dialup information, and change the bits per second (Baud rate) to 19200 (to match the value used when opening the communications port on the Windows CE device).

Once set up, any characters you type into Hyperterminal should appear in the "Examples" main window. Note that pressing the enter key on the desktop PC will result in a box character appearing in the Examples window on the Windows CE device. This is because Hyperterminal sends a new line character, whereas the edit box expects a carriage return and a new line character to move onto the next line.

You can test your serial communications applications under emulation. With the Pocket PC emulation running under Windows 2000, your code should work without any changes. Remember, though, you will need to check that no other application has the desktop PC communications port open. On other platforms you may need to enable a serial port services emulator and disable the default Windows NT serial service. You can do this by executing the following two lines of code at a command prompt:

 NET STOP SERIAL NET START WCEEMULD 

< BACK  NEXT >


Windows CE 3. 0 Application Programming
Windows CE 3.0: Application Programming (Prentice Hall Series on Microsoft Technologies)
ISBN: 0130255920
EAN: 2147483647
Year: 2002
Pages: 181

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net