Board index » cppbuilder » Problem with serial communications

Problem with serial communications


2005-05-31 05:38:55 PM
cppbuilder74
Some time ago I wrote a serial port component using overlapped I/O. The
component creates a thread to monitor the serial port an trigger some event
handlers.
The component works fine on both Win98 and WinXP, that is, most of the time.
Sometimes an error occurs while writing to the serial port, in which case
the WriteFile() fails and GetLastError() returns ERROR_OPERATION_ABORTED
(995).
Here is the code for the thread:
#define EV_ALL 0x01FF
class TCOMStatThread : public TThread
{
typedef TThread inherited;
private:
TCustomCOMPort *Port;
OVERLAPPED Overlapped;
HANDLE TerminateEvent;
DWORD dwParam;
void __fastcall HandleCommEvent (DWORD EventMask);
void __fastcall DoOnDataAvailable ();
void __fastcall DoOnError ();
protected:
void __fastcall Execute ();
public:
__fastcall TCOMStatThread (TCustomCOMPort *APort, bool Suspended);
__fastcall ~TCOMStatThread ();
void __fastcall Terminate ();
};
__fastcall TCOMStatThread::TCOMStatThread (TCustomCOMPort *APort, bool
Suspended) : TThread(Suspended)
{
Port = APort;
FreeOnTerminate = true;
Overlapped.hEvent = CreateEvent(NULL, true, false, NULL);
TerminateEvent = CreateEvent(NULL, true, false, NULL);
}
__fastcall TCOMStatThread::~TCOMStatThread ()
{
CloseHandle(Overlapped.hEvent);
CloseHandle(TerminateEvent);
}
void __fastcall TCOMStatThread::Execute ()
{
HANDLE EventHandles[2] = {Overlapped.hEvent, TerminateEvent};
DWORD EventMask, ByteCount;
SetCommMask(Port->Handle, EV_ALL);
while (!Terminated)
{
if (WaitCommEvent(Port->Handle, &EventMask, &Overlapped))
HandleCommEvent(EventMask);
else
{
if (GetLastError() == ERROR_IO_PENDING)
{
switch (WaitForMultipleObjects(2, EventHandles, false, INFINITE))
{
case WAIT_OBJECT_0: // Overlapped I/O completed.
if (GetOverlappedResult(Port->Handle, &Overlapped, &ByteCount,
false))
HandleCommEvent(EventMask);
break;
case WAIT_OBJECT_0 + 1: // Termination signalled.
break;
}
}
}
}
}
void __fastcall TCOMStatThread::HandleCommEvent (DWORD EventMask)
{
if (EventMask & (EV_TXEMPTY | EV_RXCHAR | EV_ERR))
{
COMSTAT Status;
DWORD Errors;
if (!ClearCommError(Port->Handle, &Errors, &Status))
return;
if (Errors != 0 && Port->OnError != NULL)
{
dwParam = Errors;
Synchronize(DoOnError);
}
if (Status.cbInQue>0 && Port->OnDataAvailable != NULL)
{
dwParam = Status.cbInQue;
Synchronize(DoOnDataAvailable);
}
}
if (EventMask & EV_RXFLAG)
{
...
}
if (EventMask & (EV_BREAK | EV_CTS | EV_DSR | EV_RING | EV_RLSD))
{
...
}
}
void __fastcall TCOMStatThread::DoOnDataAvailable ()
{
Port->OnDataAvailable(Port, dwParam);
}
void __fastcall TCOMStatThread::DoOnError ()
{
Port->OnError(Port, dwParam);
}
void __fastcall TCOMStatThread::Terminate ()
{
inherited::Terminate();
SetEvent(TerminateEvent);
}
Here is the code for reading from and writing to the serial port:
DWORD __fastcall TCustomCOMPort::Read (void *Buffer, DWORD Size)
{
DWORD BytesRead;
if (FHandle == INVALID_HANDLE_VALUE)
throw Exception(SPortClosed);
if (!ReadFile(FHandle, Buffer, Size, &BytesRead, &osRead))
{
if (GetLastError() == ERROR_IO_PENDING)
WaitForAsync(osRead, BytesRead);
}
return(BytesRead);
}
DWORD __fastcall TCustomCOMPort::Write (void *Buffer, DWORD Size)
{
DWORD BytesWritten;
if (FHandle == INVALID_HANDLE_VALUE)
throw Exception(SPortClosed);
if (!WriteFile(FHandle, Buffer, Size, &BytesWritten, &osWrite))
{
if (GetLastError() == ERROR_IO_PENDING)
WaitForAsync(osWrite, BytesWritten);
}
return(BytesWritten);
}
void __fastcall TCustomCOMPort::WaitForAsync (OVERLAPPED &Overlapped, DWORD
&ByteCount)
{
switch (WaitForSingleObject(Overlapped.hEvent, INFINITE))
{
case WAIT_FAILED:
ByteCount = 0;
break;
default:
case WAIT_OBJECT_0:
if (!GetOverlappedResult(FHandle, &Overlapped, &ByteCount, false))
ByteCount = 0;
}
}
I read somewhere that WaitCommEvent() doesn't report all errors. For EV_ERR,
only line-status errors are reported (CE_FRAME, CE_OVERRUN and CE_RXPARITY).
Other errors, like CE_RXOVER and CE_TXFULL, go unnoticed. I think this is
part of the problem, but how do I fix it?
Should I forget about WaitCommEvent() and simply call ClearCommError() in
the thread's main loop? This would cause a big load on the processor, right?
Any help would be greatly appreciated.
Martin
 
 

Re:Problem with serial communications

Martin Nijhoff wrote:
Quote
Some time ago I wrote a serial port component using overlapped I/O. The
component creates a thread to monitor the serial port an trigger some event
handlers.

The component works fine on both Win98 and WinXP, that is, most of the time.
Sometimes an error occurs while writing to the serial port, in which case
the WriteFile() fails and GetLastError() returns ERROR_OPERATION_ABORTED
(995).
You removed some code. Did any of that code contain CancelIO() ?
You didn't show osRead or osWrite.
Your class has One OVERLAPPED struct, not 2, and you wait on 2 events.
I would think you would want to wait on 3 events, evRead, evWrite and
evTerminate
You are blocking after read and write, so why are you using overlapped
instead of blocking i/o?
You aren't handling any errors from ReadFile() or WriteFile().
Quote
DWORD __fastcall TCustomCOMPort::Write (void *Buffer, DWORD Size)
{
DWORD BytesWritten;

if (FHandle == INVALID_HANDLE_VALUE)
throw Exception(SPortClosed);

if (!WriteFile(FHandle, Buffer, Size, &BytesWritten, &osWrite))
{
if (GetLastError() == ERROR_IO_PENDING)
WaitForAsync(osWrite, BytesWritten);
}
return(BytesWritten);
}
 

Re:Problem with serial communications

Quote
You removed some code. Did any of that code contain CancelIO() ?
No.
Quote
You didn't show osRead or osWrite.
Here is (a simplified version of) the class definition:
class TCustomCOMPort : public TComponent
{
friend TCOMStatThread;
private:
TCOMStatThread *StatThread;
OVERLAPPED osRead;
OVERLAPPED osWrite;
TCOMDataEvent FOnDataAvailable;
TCOMErrorEvent FOnError;
void __fastcall WaitForAsync (OVERLAPPED &Overlapped, DWORD &ByteCount);
protected:
bool __fastcall Open ();
bool __fastcall Close ();
DWORD __fastcall Read (void *Buffer, DWORD Size);
DWORD __fastcall Write (void *Buffer, DWORD Size);
__property TCOMDataEvent OnDataAvailable = {read=FOnDataAvailable,
write=FOnDataAvailable};
__property TCOMErrorEvent OnError = {read=FOnError, write=FOnError};
public:
__fastcall TCustomCOMPort (TComponent *Owner);
__fastcall ~TCustomCOMPort ();
};
The thread is created in the Open() method and destroyed in the Close()
thread, which means that the thread runs as long as the COM port is open.
Quote
Your class has One OVERLAPPED struct, not 2, and you wait on 2 events.
My class has 1 OVERLAPPED struct (for WaitCommEvent) and 1 event for
signalling thread termination (doesn't require an OVERLAPPED struct).
Quote
I would think you would want to wait on 3 events, evRead, evWrite and
evTerminate
In TCOMStatThread? Reading and writing is handled in TCustomCOMPort. I don't
need reading/writing in the background, which would be done in
TCOMStatThread. Blocking read/write (with timeouts) is all I need. The COM
port component is used for communicating with devices that use a
command/reply communication model (the PC sends a request and waits for a
reply from the device).
Quote
You are blocking after read and write, so why are you using overlapped
instead of blocking i/o?
Non-overlapped I/O doesn't work in WinXP.
Quote
You aren't handling any errors from ReadFile() or WriteFile().
Can you tell me which errors to handle and how to handle them?
Thanks for your reply.
Martin
 

{smallsort}

Re:Problem with serial communications

Martin Nijhoff wrote:
Quote
>You are blocking after read and write, so why are you using overlapped
>instead of blocking i/o?

Non-overlapped I/O doesn't work in WinXP.
What makes you think that?
 

Re:Problem with serial communications

Martin Nijhoff wrote:
Quote
>You are blocking after read and write, so why are you using overlapped
>instead of blocking i/o?

Non-overlapped I/O doesn't work in WinXP.
What makes you think that?
See the thread from earlier this month in this same group
"Response not in que?"
 

Re:Problem with serial communications

Bob Gonder < XXXX@XXXXX.COM >wrote:
Quote
>Non-overlapped I/O doesn't work in WinXP.

What makes you think that?
Well, it appears that I/O doesn't work, so presumably non-overlapped I/O
is included <grin>
(I must admit that I hadn't noticed that XP was catastrophically broken
when I used it.)
Alan Bellingham
--
ACCU Conference 2006 - 19-22 April, Randolph Hotel, Oxford, UK
 

Re:Problem with serial communications

Quote
>Non-overlapped I/O doesn't work in WinXP.
I should have added the phrase 'in my serial port component'.
Quote
What makes you think that?
Because I tried it before I started messing around with overlapped I/O. When
I started out, with my applications running on Win98 exclusively, I used
non-overlapped I/O and it worked like a charm.
Once we started using WinXP, the application locked up in the status thread
(the WaitCommEvent() to be exact) and wouldn't continue unless something was
received on the COM port.
Some applications rely on the OnDataAvailable event, which is triggered by
the status thread in response to EV_RXCHAR. As far as I know, there is no
other way to get this working than with overlapped I/O (except polling the
serial port, which is not an option for me).
Whether I use overlapped I/O or not is not the issue. What I need advice
about is which errors I need to handle, and how to handle them. Do I need to
call ClearCommError() before/after the call to ReadFile() and WriteFile() to
handle errors? Or is there another way to detect errors in the status thread
and handle them there? WaitCommEvent() doesn't notify me of errors like
CE_RXOVER or CE_TXFULL, does it?
Thanks for your help sofar...
Martin