Blame |
Last modification |
View Log
| RSS feed
/****************************************************************************
*
* Copyright (c) 2003 Dave Hylands
* All Rights Reserved
*
* Permission is granted to any individual or institution to use, copy, or
* redistribute this software so long as it is not sold for profit, and that
* this copyright notice is retained.
*
****************************************************************************/
/**
*
* @file SerialComm.cpp
*
* @brief Implements the abstract communications device using a serial port.
*
****************************************************************************/
// ---- Include Files -------------------------------------------------------
#include "Error.h"
#include "LogMessage.h"
#include "SerialComm.h"
#include "StrToken.h"
#include "Str.h"
/**
* @addtogroup SerialComm
* @{
*/
#if 0
#define DBG(x) LOG_DEBUG(x)
#else
#define DBG(x)
#endif
// ---- Public Variables ----------------------------------------------------
// ---- Private Constants and Types -----------------------------------------
// ---- Private Variables ---------------------------------------------------
// ---- Private Function Prototypes -----------------------------------------
// ---- Functions -----------------------------------------------------------
//***************************************************************************
/**
* Constructor
*/
SerialComm::SerialComm()
{
m_serialHndl = INVALID_HANDLE_VALUE;
memset( &m_readOverlapped, 0, sizeof( m_readOverlapped ));
memset( &m_writeOverlapped, 0, sizeof( m_writeOverlapped ));
memset( &m_connectionInfo, 0, sizeof( m_connectionInfo ));
}
//***************************************************************************
/**
* Destructor
*
* virtual
*/
SerialComm::~SerialComm()
{
Close();
}
//***************************************************************************
/**
* Aborts a read, if one is pending.
*
* virtual
*/
void SerialComm::AbortRead()
{
SetEvent( m_abortEvent );
} // AbortRead
//***************************************************************************
/**
* Close a previsouly opened device.
*
* virtual
*/
void SerialComm::Close()
{
if ( m_serialHndl != INVALID_HANDLE_VALUE )
{
CloseHandle( m_serialHndl );
m_serialHndl = INVALID_HANDLE_VALUE;
m_name = "";
}
if ( m_readOverlapped.hEvent != NULL )
{
CloseHandle( m_readOverlapped.hEvent );
m_readOverlapped.hEvent = NULL;
}
if ( m_writeOverlapped.hEvent != NULL )
{
CloseHandle( m_writeOverlapped.hEvent );
m_writeOverlapped.hEvent = NULL;
}
if ( m_abortEvent != NULL )
{
CloseHandle( m_abortEvent );
m_abortEvent = NULL;
}
}
//***************************************************************************
/**
* Returns connection information (for display purposes)
*
* virtual
*/
const char *SerialComm::ConnectionInfo() const
{
return m_connectionInfo;
}
//***************************************************************************
/**
* Opens a serial port.
*
* @returns true if the port was opened successfully, false otherwise.
*
* virtual
*/
bool SerialComm::Open
(
/// (in) Determines the serial port and parameters. The parameters are the same
/// as what the MS-DOS MODE command supports.
const char *openStr
)
{
DWORD err;
char errStr[ 200 ];
// Extract the filename (terminated by a colon). The remaining parameters
// are passed in to BuildCommDCB
//
// COM1: baud=9600 parity=N data=8 stop=1
char token[ 20 ];
StrTokenizer tokenizer( openStr, token, sizeof( token ));
char *comPort = tokenizer.NextToken( " " );
if ( comPort == NULL )
{
LOG_ERROR( "No COM port specified" );
return false;
}
const char *dcbParms = tokenizer.Remainder();
m_serialHndl = CreateFile( comPort, // lpFileName
GENERIC_READ | GENERIC_WRITE, // dwDesiredAccess
0, // dwShareMode
NULL, // lpSecurityAttributes
OPEN_EXISTING, // dwCreationDisposition
FILE_FLAG_OVERLAPPED, // dwFlagsAndAttributes
NULL ); // hTemplateFile
if ( m_serialHndl == INVALID_HANDLE_VALUE )
{
err = GetLastError();
LOG_ERROR( "Unable to open COM port '" << comPort << "'." );
LOG_ERROR( GetErrorStr( err, errStr, sizeof( errStr )));
return false;
}
m_name = comPort;
DCB dcb;
COMMTIMEOUTS timeouts;
memset( &dcb, 0, sizeof( dcb ));
dcb.DCBlength = sizeof( dcb );
memset( &timeouts, 0, sizeof( timeouts ));
if ( !GetCommState( m_serialHndl, &dcb ))
{
err = GetLastError();
LOG_ERROR( "Call to GetCommState failed for '" << comPort << "'." );
LOG_ERROR( GetErrorStr( err, errStr, sizeof( errStr )));
Close();
return false;
}
if ( !GetCommTimeouts( m_serialHndl, &timeouts ))
{
err = GetLastError();
LOG_ERROR( "Call to GetCommTimeouts failed for '" << comPort << "'." );
LOG_ERROR( GetErrorStr( err, errStr, sizeof( errStr )));
Close();
return false;
}
dcb.BaudRate = 19200;
dcb.ByteSize = 8;
dcb.Parity = NOPARITY;
dcb.StopBits = ONESTOPBIT;
dcb.fBinary = 1;
dcb.fParity = 0;
dcb.fOutX = 0;
dcb.fOutxCtsFlow = 0;
dcb.fOutxDsrFlow = 0;
dcb.fInX = 0;
dcb.fDtrControl = DTR_CONTROL_ENABLE;
dcb.fRtsControl = RTS_CONTROL_ENABLE;
dcb.fDsrSensitivity = 0;
dcb.fTXContinueOnXoff = 1;
timeouts.ReadIntervalTimeout = 20;
if ( !BuildCommDCBAndTimeouts( dcbParms, &dcb, &timeouts ))
{
err = GetLastError();
LOG_ERROR( "Unable to parse COM parameters: '" << dcbParms << "'." );
LOG_ERROR( GetErrorStr( err, errStr, sizeof( errStr )));
Close();
return false;
}
char parityChar;
switch ( dcb.Parity )
{
case EVENPARITY: parityChar = 'E'; break;
case ODDPARITY: parityChar = 'O'; break;
case NOPARITY: parityChar = 'N'; break;
case MARKPARITY: parityChar = 'M'; break;
case SPACEPARITY: parityChar = 'S'; break;
default: parityChar = '*'; break;
}
const char *stopStr;
switch ( dcb.StopBits )
{
case ONESTOPBIT: stopStr = "1"; break;
case ONE5STOPBITS: stopStr = "1.5"; break;
case TWOSTOPBITS: stopStr = "2"; break;
default: stopStr = "*"; break;
}
if ( !SetCommState( m_serialHndl, &dcb ))
{
err = GetLastError();
LOG_ERROR( "Call to SetCommState failed for '" << comPort << "'." );
LOG_ERROR( GetErrorStr( err, errStr, sizeof( errStr )));
Close();
return false;
}
if ( !SetCommTimeouts( m_serialHndl, &timeouts ))
{
err = GetLastError();
LOG_ERROR( "Call to SetCommTimeouts failed for '" << comPort << "'." );
LOG_ERROR( GetErrorStr( err, errStr, sizeof( errStr )));
Close();
return false;
}
if ( !SetupComm( m_serialHndl, 32768, 32768 ))
{
err = GetLastError();
LOG_ERROR( "Call to SetupComm failed for '" << comPort << "'." );
LOG_ERROR( GetErrorStr( err, errStr, sizeof( errStr )));
Close();
return false;
}
StrPrintf( m_connectionInfo, sizeof( m_connectionInfo ),
"%d-%c-%d-%s", dcb.BaudRate, parityChar, dcb.ByteSize, stopStr );
// Note that overlapped functions want a Manual-Reset event
m_readOverlapped.hEvent = CreateEvent( NULL, // lpEventAttributes
TRUE, // bManualReset
FALSE, // bInitialState
NULL ); // lpName
m_writeOverlapped.hEvent = CreateEvent( NULL, // lpEventAttributes
TRUE, // bManualReset
FALSE, // bInitialState
NULL ); // lpName
m_abortEvent = CreateEvent( NULL, // lpEventAttributes
FALSE, // bManualReset
FALSE, // bInitialState
NULL ); // lpName
#if 0
COMMTIMEOUTS ct;
if ( GetCommTimeouts( m_serialHndl, &ct ))
{
LOG_INFO( "ReadIntervalTimeout = " << ct.ReadIntervalTimeout );
LOG_INFO( "ReadTotalTimeoutMultiplier = " << ct.ReadTotalTimeoutMultiplier );
LOG_INFO( "ReadTotalTimeoutConstant = " << ct.ReadTotalTimeoutConstant );
LOG_INFO( "WriteTotalTimeoutMultiplier = " << ct.WriteTotalTimeoutMultiplier );
LOG_INFO( "WriteTotalTimeoutConstant = " << ct.WriteTotalTimeoutConstant );
}
#endif
return true;
}
//***************************************************************************
/**
* Read data from the communication device.
*
* @returns true if the read was successful, flse otherwise.
*
* virtual
*/
bool SerialComm::Read
(
void *buf, ///< (out) Place to store the read data.
size_t bufSize, ///< (in) Size of the data buffer.
size_t *bytesRead ///< (out) Number of bytes actually read.
)
{
DWORD err;
char errStr[ 200 ];
*bytesRead = 0;
// Use a while loop, since we don't always call WaitCommEvent, it's possible
// for the event to be in a 'set' state and there not be any data in
// the buffer. This basically just prevents us from returning no
// data to the caller.
DBG( "Entered SerialComm::Read" );
while ( *bytesRead == 0 )
{
// Wait for some data to arrive
if ( !SetCommMask( m_serialHndl, EV_RXCHAR ))
{
err = GetLastError();
LOG_ERROR( "SerialComm::Read call to SetCommMask failed: "
<< GetErrorStr( err, errStr, sizeof( errStr )));
return false;
}
// Check to see if there are any characters in the buffer already. If
// there are, it's possible that they were missed by a previous
// read (due to buffer full) and the event won't get triggered.
//
// Characters which arrive after the ReadFile call will set the event
// but since ReadFile clears the event, characters which didn't fit
// in the read buffer won't set the event.
DWORD commErrors;
COMSTAT commStat;
if ( !ClearCommError( m_serialHndl, &commErrors, &commStat ))
{
err = GetLastError();
LOG_ERROR( "SerialComm::Read call to ClearCommErros failed: "
<< GetErrorStr( err, errStr, sizeof( errStr )));
return false;
}
if ( commStat.cbInQue == 0 )
{
DWORD evtMask;
DBG( "Calling WaitCommEvent" );
if ( !WaitCommEvent( m_serialHndl, &evtMask, &m_readOverlapped ))
{
err = GetLastError();
if ( err != ERROR_IO_PENDING )
{
LOG_ERROR( "SerialComm::Read call to WaitCommEvent failed: "
<< GetErrorStr( err, errStr, sizeof( errStr )));
return false;
}
// Wait for an event to occur
DWORD rc;
HANDLE waitHandle[ 2 ];
waitHandle[ 0 ] = m_readOverlapped.hEvent;
waitHandle[ 1 ] = m_abortEvent;
DBG( "I/O Pending - waiting for event" );
rc = WaitForMultipleObjects( 2, waitHandle, FALSE, INFINITE );
if ( rc == ( WAIT_OBJECT_0 + 1 ))
{
// We've been asked to quit. Cancel the I/O.
DBG( "Abort detected" );
CancelIo( m_serialHndl );
return false;
}
if ( rc != WAIT_OBJECT_0 )
{
err = GetLastError();
LOG_ERROR( "SerialComm::Read Wait for WaitCommEvent failed: " << GetErrorStr( err, errStr, sizeof( errStr )));
return false;
}
// Get the result of the overlapped operation
DWORD dummy;
if ( !GetOverlappedResult( m_serialHndl, &m_readOverlapped, &dummy, TRUE ))
{
err = GetLastError();
LOG_ERROR( "SerialComm::Read GetOverlappedResult for WaitCommEvent failed: " << GetErrorStr( err, errStr, sizeof( errStr )));
return false;
}
}
DBG( "WaitCommEvent finished: " << HEX( evtMask ));
}
// Start the Read. Since we've been told that there's data available
// this should suceed.
DWORD bytesReadDword;
DBG( "About to call ReadFile" );
if ( !ReadFile( m_serialHndl, buf, bufSize, &bytesReadDword, &m_readOverlapped ))
{
err = GetLastError();
if ( err != ERROR_IO_PENDING )
{
LOG_ERROR( "SerialComm::Read failed: " << GetErrorStr( err, errStr, sizeof( errStr )));
return false;
}
// Wait for the read to finish, or to be aborted. Sometimes, the
// EV_RXCHAR event is set even when there aren't any characters
// in the buffer.
DWORD rc;
HANDLE waitHandle[ 2 ];
waitHandle[ 0 ] = m_readOverlapped.hEvent;
waitHandle[ 1 ] = m_abortEvent;
DBG( "Waiting for read to complete" );
rc = WaitForMultipleObjects( 2, waitHandle, FALSE, INFINITE );
if ( rc == ( WAIT_OBJECT_0 + 1 ))
{
// We've been asked to quit. Cancel the I/O.
DBG( "Abort detected" );
CancelIo( m_serialHndl );
return false;
}
if ( rc != WAIT_OBJECT_0 )
{
err = GetLastError();
LOG_ERROR( "SerialComm::Read Wait failed: " << GetErrorStr( err, errStr, sizeof( errStr )));
return false;
}
// Get the result of the overlapped operation
if ( !GetOverlappedResult( m_serialHndl, &m_readOverlapped, &bytesReadDword, TRUE ))
{
err = GetLastError();
LOG_ERROR( "SerialComm::Read GetOverlappedResult failed: " << GetErrorStr( err, errStr, sizeof( errStr )));
return false;
}
DBG( "Read completed" );
}
*bytesRead = bytesReadDword;
}
//LOG_DEBUG( "SerialComm::Read returned " << *bytesRead << " bytes." );
return true;
}
//***************************************************************************
/**
* Write data to the communication device.
*
* @returns true if the write was successful, flse otherwise.
*
* virtual
*/
bool SerialComm::Write
(
const void *buf, ///< (in) Buffer containing data to write
size_t bytesToWrite, ///< (in) Number of bytes of data to write
size_t *bytesWritten ///< (out) Number of bytes actually written
)
{
DWORD err;
char errStr[ 200 ];
*bytesWritten = 0;
// Start the write operation
DWORD bytesWrittenDword;
if ( !WriteFile( m_serialHndl, buf, bytesToWrite, &bytesWrittenDword, &m_writeOverlapped ))
{
err = GetLastError();
if ( err != ERROR_IO_PENDING )
{
LOG_ERROR( "SerialComm::Write failed: " << GetErrorStr( err, errStr, sizeof( errStr )));
return false;
}
// Wait for the write to complete
if ( WaitForSingleObject( m_writeOverlapped.hEvent, INFINITE ) != WAIT_OBJECT_0 )
{
err = GetLastError();
LOG_ERROR( "SerialComm::Write Wait failed: " << GetErrorStr( err, errStr, sizeof( errStr )));
return false;
}
// Get the result of the overlapped operation
if ( !GetOverlappedResult( m_serialHndl, &m_writeOverlapped, &bytesWrittenDword, TRUE ))
{
err = GetLastError();
LOG_ERROR( "SerialComm::Write GetOverlappedResult failed: " << GetErrorStr( err, errStr, sizeof( errStr )));
return false;
}
}
*bytesWritten = bytesWrittenDword;
return true;
}
/** @} */