Re: Reading Serial Port
- From: Joseph M. Newcomer <newcomer@xxxxxxxxxxxx>
- Date: Sat, 09 Feb 2008 15:26:38 -0500
See below...
On Mon, 4 Feb 2008 01:53:18 -0800 (PST), clinisbut <clinisbut@xxxxxxxxx> wrote:
On Feb 2, 3:07 am, Joseph M. Newcomer <newco...@xxxxxxxxxxxx> wrote:****
There's no need to read a byte at a time; you need to decouple packet logic from transport
logic. I just read bytes. I parse them, and when I get a full message, I PostMessage,
and hold onto what is left, and append to that (this is why I call my code a "schema" for
serial programming). I would get a byte of "I'm a header" followed by a byte of length.
Use a modified Finite State Machine to parse the input and it works well. You just don't
send everything just one packet's worth each time.
By timeouts I mean the SettCommTimeout time, in particular, my intercharacter timeout was
set to 20ms, which meant the thread was nearly always running.
Yes, you might receive a half-frame, a frame-and-quarter, two-and-a-third, etc. frames,
but that doesn't matter. You handle frame parsing as a different part of the protocol (I
also handled checksums, sending NAKs, and retries)
joe
On Thu, 31 Jan 2008 23:26:32 -0800 (PST), clinisbut <clinis...@xxxxxxxxx> wrote:
Your high CPU usage is probably due to your request to ReadFile to
read only one byte and return. That is very inefficient and wasteful if the
characters are arriving every millisecond!
In this test I set up frames of 16 bytes length only for testing
purposes, but in final version each frame will have different lengths
(16 will be the maximum). That's beacuse I grab a byte each time (the
first byte will say me how many bytes).
If your timeouts are too short, then ReadFile will complete successfully in a short periodWith timeout do you mean the timeout of the WFMO?
of time, and return 0 bytes read, so the event will essentially become signalled after
virtually no delay, and your CPU will quickly spike to 100% (in the RS485 case, I had the
timeouts wrong, and the reader thread went into an infinite high-priority loop,
effectively disabling the entire machine; I changed the timeouts and it worked quite fine
after that)
DWORD result = WaitForMultipleObjects( 2, hArray, FALSE,
INFINITE ); //<---- This
Will incrementing the bytes per read cause to receive a frame (with
length>bytes_per_read) in two pieces? (Not receive the second part
until a second FRAME arrives) I can't deal with this behaviour.
Joseph M. Newcomer [MVP]
email: newco...@xxxxxxxxxxxx
Web:http://www.flounder.com
MVP Tips:http://www.flounder.com/mvp_tips.htm
oh, that timeout, ok i see.
About the parsing algorithm I know i have to analize every byte until
I identify my frames, but the problem I mean is that I can't allow to
receive the second half of a frame until a second frame is sended to
me (like happened to me when I was using the MSComm Control). Doesn't
matter if I don't receive a entire frame in a single loop, I know how
to deal this.
Whether or not you receive a second frame depends on the nature of your problem domain;
there's no one "right" answer as to how to do this. Not sure what you mean by "allow". If
the data is there, typically all you do is receive it. You parse the header, read the
contents, and when you have a complete packet, you deliver that packet. If there is
another packet, you read that.
You keep reading bytes until you have a complete packet. For example, I had a protocol in
which the packets were
<HeaderByte> <LengthLSB> <LengthHSB> <Opcode> <data[0]> ... <data[n]> <checksum>
and I could have many such packets in the stream. I read a byte. If it wasn't a header
byte, I threw it away. I kept reading until I had a header byte. Then I read the
low-order byte of the length and the high order byte of the length. I would then read
length-2 bytes (the length including the length bytes themselves), performing a checksum.
I would then read the checksum byte. If they matched, I sent the packet to my main
thread; if they didn't match, I sent a NAK to the device. Meantime, the device might have
sent other packets to me, so I kept reading. If at any point I didn't have a complete
packet (e.g., the length was 128 and I had read only 64 bytes) I simply stopped until more
bytes were available. There was no question about "allowed"; if the data was there, I
read it.
No packet could be > 64K bytes long, so in fact I would only have to allocate a 64K+2
[header and checksum] buffer in my read function, and kept an offset. In fact, nearly all
messages were < 100 bytes, and although in principle they could be 64K, the longest in
practice was about 221 bytes (no, don't ask why the funny number; I'd have say too much
about the logic of the device, and I can't talk about that; the nature of the device was
that I could allocate a 256-byte buffer, and if any message got to be longer than 256
bytes, it was actually an error and I'd reject the whole thing; in fact, any message
length > 256 would be rejected immediately due to the specific requirements of the actual
implementation of the protocol on real devices; they had allowed for a >256 byte message,
but no device ever did that, and the specs of the devices and the specs for the protocol
were somewhat different in this regard, and they specifically requested that I reject any
apparently long message)
Because machines are fast and messages short, when I finally got a message, if I had
pre-read any bytes of the subsequent message, I just shifted all the bytes downward in the
buffer using memmove (not memcpy), computed the correct offsets to resume reading, and
kept on reading. I used a very simple FSM (with count) to parse the message.
DWORD limit = 0;
LPBYTE start = NULL;
LPBYTE end = NULL;
int offset = 0;
BYTE buffer[SOME_MAXIMUM_SIZE];
DWORD bytesRead;
BYTE checksum = 0;
typedef enum { STATE_HEADER, STATE_LSB, STATE_MSB, STATE_DATA, STATE_CHECKSUM} ParseState;
ParseState state = STATE_HEADER;
while(TRUE)
{ /* read loop */
BOOL b = ReadFile(port, &buffer[offset], sizeof(buffer) - offset, &bytesRead, NULL);
... usual stuff here, worry about thread shutdown, etc.
if(bytesRead == NULL)
continue;
// if we get here, the read has added bytes
limit = offset + bytesRead;
while(TRUE)
{ /* parse message */
if(offset >= limit)
break; // from parsing loop
switch(state)
{
case STATE_HEADER:
if(buffer[offset] != HEADER_BYTE)
{ /* not header */
ASSERT(offset == 0);
memmove(buffer, &buffer[1], limit - 1);
continue;
} /* not header */
state = STATE_LSB;
header = &buffer[offset];
offset++;
continue;
case STATE_LSB:
msglength = buffer[offset];
state = STATE_MSB;
offset++;
continue;
case STATE_MSB:
msglength = MAKEWORD(msglelngth, buffer[offset]);
state = STATE_DATA;
offset++;
checksum = 0;
start = &buffer[offset];
continue;
case STATE_DATA:
checksum += buffer[offset];
msglength--;
if(msglength == 0)
{ /* got message */
state = STATE_CHECKSUM;
end = &buffer[offset];
offset++;
continue;
} /* got message */
offset++;
continue;
case STATE_CHECKSUM:
if(checksum == ~buffer[offset])
{ /* checksum OK */
LPBYTE data = new BYTE[end - start];
memcpy(data, start, end-start);
PostMessage(wnd, UWM_DATA, (WPARAM)(end - start), (LPARAM)data);
} /* checksum OK */
else
{ /* checksum failed */
... send NAK to device
} /* checksum failed */
memmove(buffer, &buffer[offset + 1], limit - (offset + 1));
limit -= offset;
offset = 0; // new message now starts at beginning of buffer
start = NULL;
end = NULL;
state = STATE_HEADER;
continue;
} /* parse message */
} /* read loop */
This is pretty close; I may have an off-by-one error somewhere since I just typed this in
and didn't read it too closely, but you get the idea. Transport and parsing are now
separate levels of what is going on.
*****
By the way, I think I'm not deleting correctly my UI thread. I'm****
always getting a "huge" memory leak of about 200bytes every time I
close the app. This is what I do to create my UI thread from GUI:
(MySerial*) pMySerial = (MySerial*)
AfxBeginThread( RUNTIME_CLASS( MySerial), THREAD_PRIORITY_NORMAL, 0,
CREATE_SUSPENDED );
pMyThread->ResumeThread();
And this is what I write to kill my thread in his destructor:
First I stop the reader thread:
MySerial::Stop()
{
SetEvent( ShutdownEvent );
WaitForSingleObject( ShutdownEvent, INFINITE );
This doesn't make any sense. Having done the SetEvent, the WFSO does nothing, because the
event is already set!
****
}****
Then I close the app and this should be executed:
MySerial::~MySerial()
{
CloseHandle(ShutdownEvent);
CloseHandle(ReadEvent);
CloseHandle(WriteEvent);
// Wait for the thread to exit before deleting
WaitForSingleObject( this->m_hThread, INFINITE );
Note that the destructor should not say 'delete this' because the destructor is only
called when the 'delete' has already been done. In addition, because you have not set
m_bAutoDelete to FALSE, the normal thread destructor has been called, and the handle has
been closed.
joe
****
delete this;Joseph M. Newcomer [MVP]
}
email: newcomer@xxxxxxxxxxxx
Web: http://www.flounder.com
MVP Tips: http://www.flounder.com/mvp_tips.htm
.
- References:
- Re: Reading Serial Port
- From: clinisbut
- Re: Reading Serial Port
- From: Joseph M . Newcomer
- Re: Reading Serial Port
- From: clinisbut
- Re: Reading Serial Port
- Prev by Date: Re: Macro compilation problem
- Next by Date: Re: Is there any problems if there are multiple MFC dlls in one pr
- Previous by thread: Re: Reading Serial Port
- Next by thread: overlapped serial coms
- Index(es):
Relevant Pages
|