I recently noticed very interesting behavior in ClearCommError() function of Win32.
This code returns how many bytes there are in incoming buffer for a serial port.
DWORD CSerialPort::BytesWaiting()
{
//Validate our parameters
ASSERT(IsOpen());
//Check to see how many characters are unread
COMSTAT stat;
GetStatus(stat);
return stat.cbInQue;
}
void CSerialPort::GetStatus(COMSTAT& stat)
{
//Validate our parameters
ASSERT(IsOpen());
DWORD dwErrors;
if (!ClearCommError(m_hComm, &dwErrors, &stat))
{
DWORD dwLastError = GetLastError();
TRACE(_T("CSerialPort::GetStatus, Failed in call to ClearCommError, Error:%d\n"), dwLastError);
ThrowSerialException(dwLastError);
}
}
However, after actually debugging it I found out that there are cases where
- The number of bytes in its incoming buffer doesn’t increase while it is calling BytesWaiting() to see if it received enough bytes to read. Then, it should read its incoming buffer for the given COM port to have BytesWaiting() starts to return accumulated number of bytes after reading.
- When BytesWaiting() returns 0, you still need to read the buffer not to fail in detecting any further data arrived from the other side of communication. reading 0 bytes in this case, will cause the BytesWaiting() keeps returning the number of bytes read after that point.
Normally, whether you read or not, callying BytesWaiting() correctly reports the number of bytes in an incoming buffer.
However, if any of those listed above happens, it stops reporting correct number of bytes.
So, this code fails.
int CLensControllerDlg::ReadIncomingPacket(BYTE incomingBuffer[], int numOfBytesToRead, bool *pShouldGiveUp)
{
DWORD bytesInReceptionQueue;
int readNumOfBytes, extraReadNumOfBytes;
readNumOfBytes = extraReadNumOfBytes = 0;
// First read
readNumOfBytes = m_pSerialPort->Read( incomingBuffer, numOfBytesToRead );
int lengthToReadFurther;
int secondTryReadLength = 0;
static int trialCount = 0;
// Check if it needs to read once more
if( (incomingBuffer[ 0] == (BYTE) 0x50) &&
(incomingBuffer[ 1] == (BYTE) 0xAF ))
{
lengthToReadFurther = static_cast<int >(incomingBuffer[4]) + 1; // payload length + check sum
// Read length + 1 more ( 1 is for checksum )
//!!!!! [1] !!!!!
// There are cases where bytesInReceptionQueue doesn't have accumulated number of
// bytes in a reception bufffer.
while( (bytesInReceptionQueue = m_pSerialPort->BytesWaiting()) < lengthToReadFurther )
{
Sleep( 3 ); // 10 msec
trialCount++;
//!!!!! [2] !!!!!
// So, if the bytesInReceptionQueue doesn't contain the accumulated number of bytes
// through the iteration, try to read some.
// Then it will start to work again.
if( trialCount > 10 )
{
if( bytesInReceptionQueue > 0 )
{
// Give another chance
secondTryReadLength = m_pSerialPort->Read( &incomingBuffer[readNumOfBytes], bytesInReceptionQueue );
readNumOfBytes += secondTryReadLength;
lengthToReadFurther -= secondTryReadLength;
trialCount = 0;
}
else
{
// let's see what it happens if try reading for 0 bytes and read further
BYTE temp[ 16];
int tempReadLength;
// bytesInReceptionQueue == 0
tempReadLength = m_pSerialPort->Read( &incomingBuffer[readNumOfBytes], bytesInReceptionQueue );
readNumOfBytes += tempReadLength;
lengthToReadFurther -= tempReadLength;
//// Once read, somehow, bytesInReceptionQueue starts to be accumulated again.
//bytesInReceptionQueue = m_pSerialPort->BytesWaiting();
//tempReadLength = m_pSerialPort->Read( &incomingBuffer[readNumOfBytes], bytesInReceptionQueue );
//readNumOfBytes += tempReadLength;
//lengthToReadFurther -= tempReadLength;
CString msgString;
msgString.Format( _T( ">> Will give up this incoming data : %x %x %x %x %x\r\n"),
incomingBuffer[0], incomingBuffer[1 ],
incomingBuffer[2], incomingBuffer[3 ],
incomingBuffer[4] );
WriteLogMessage( msgString );
TRACE( msgString );
if( pShouldGiveUp != NULL )
{
*pShouldGiveUp = true;
}
trialCount = 0;
return 0;
}
}
}
// In the queue, now there are lengthToReadFurther or more to read.
extraReadNumOfBytes = m_pSerialPort->Read( &incomingBuffer[readNumOfBytes], lengthToReadFurther );
}
return (readNumOfBytes + extraReadNumOfBytes);
}
The code above was written while I was debugging. So, it’s not clean nor compact.
So, here is better version if you want to see.
int CLensControllerDlg::ReadIncomingPacket(BYTE incomingBuffer[], int numOfBytesToRead, bool *pShouldGiveUp)
{
DWORD bytesInReceptionQueue;
int readNumOfBytes, extraReadNumOfBytes;
readNumOfBytes = extraReadNumOfBytes = 0;
// First read
readNumOfBytes = m_pSerialPort->Read( incomingBuffer, numOfBytesToRead );
int lengthToReadFurther;
int secondTryReadNumOfBytes = 0;
// Check if it needs to read once more
if( (incomingBuffer[0] == (BYTE)0x50) &&
(incomingBuffer[1] == (BYTE)0xAF ))
{
lengthToReadFurther = static_cast<int>(incomingBuffer[4]) + 1; // payload length + check sum
// Read length + 1 more ( 1 is for checksum )
while( lengthToReadFurther > 0 )
{
Sleep( 1 ); // 10 msec
bytesInReceptionQueue = m_pSerialPort->BytesWaiting();
// If it's normal, just waiting until bytesInReceptionQueue >= lengthToReadFurther,
// and read the whole "lengthToReadFurther" amount of data all at once.
//
// However, sometimes the inbound buffer should be read to have further data accumulated.
// If not,
secondTryReadNumOfBytes = m_pSerialPort->Read( &incomingBuffer[readNumOfBytes], lengthToReadFurther );
readNumOfBytes += secondTryReadNumOfBytes;
lengthToReadFurther -= secondTryReadNumOfBytes;
}
}
return readNumOfBytes;
}
Recent Comments