📄 serialcomm.asp
字号:
TRACE ( "CSerialCommHelper : Failed to Set Comm State Reason: %d",GetLastError());
return E_FAIL;
}
TRACE ( "CSerialCommHelper : Current Settings, (Baud Rate %d; Parity %d; Byte Size %d; Stop Bits %d", dcb.BaudRate,
</font></pre>
</td>
</tr>
</table>
<p><font size="2" face="Verdana">Most of the time you won't need to change the
other fields of this structure. But if you need to change the structure you need
to be very careful about the fields as changing the fields will affect the
behavior of the serial communication and hence you should be very sure what you
want to change. <br>
<b><br>
Event Driven Approach<br>
</b>Coming back to our earlier problem with the reading of data. If we do not
want to keep polling the COM port for any data then we need to have some
kind of event mechanism available. Fortunately there is a way that you can ask
the system to notify you when certain events happen. The API to use is
</font></p>
<table border="0" cellspacing="1" width="100%" bgcolor="#C0C0C0">
<tr>
<td width="100%"><font size="2" face="Verdana">SetCommMask( HANDLE
hHandle,DWORD dwEvtMask)</font></td>
</tr>
</table>
<p><font size="2" face="Verdana">The first parameter is the handle to the open
COM port. The second parameter is used to specify a list of events which we are
interested in. <br>
The events that need to be specified in the mask depends upon the application
needs. For simplicity, lets say we are interested in getting notified
whenever a character arrives at the serial port, we would need to specify
EV_RXCHAR as the event mask. Similarly if we are interested to know when all the
data has been sent, we need to specify EV_TXEMPTY flag also. So out call would
look like this:
</font></p>
<table border="0" cellspacing="1" width="100%" bgcolor="#DADADA">
<tr>
<td width="100%"><font size="2" face="Verdana">SetCommMask(
m_hCommPort,EV_TXTEMPTY|EV_RXCHAR);</font></td>
</tr>
</table>
<p><font size="2" face="Verdana"> The interesting thing here is that
although we told system about the events of our interest, we did not however
told system what to do when these events occur. Like how would system let us
know that a particular event occurred. An obvious thing seems to be a callback
mechanism. But there is not such mechanism available. Here is when things get a
little tricky. In order for system to let us know about the communication event
occurrence, we need to call <b>WaitCommEvent</b> This function waits for the
events specified in SetCommMask. But if your think a little more, it sounds like
we are turning a notification mechanism back to polling mechanism. Actually its
even worse that than . WaitCommEvent blocks till an event occurs. So whats the
use of WaitCommEvent ? Well , the answer lies in overlapped IO.<br>
If you look at the WaitCommEvent signature it looks like this:
</font></p>
<table border="0" cellspacing="1" width="100%" bgcolor="#DADADA">
<tr>
<td width="100%"><font face="Verdana" size="2">BOOL</font><font face="Verdana" size="2" color="#0000FF">
WaitCommEvent</font><font face="Verdana" size="2">(HANDLE hCommPort,
LPDWORD dwEvtMask,LPOVERLAPPED lpOverlapped);</font></td>
</tr>
</table>
<p><font size="2" face="Verdana"> The third parameter is the
key here. <br>
Think of overlapped IO as asynchronous IO. Whenever a function makes a call and
specifies the overlapped IO structure, it means that try to do the current
operation but if you are not able to complete it immediately let me know when
you are done with this IO. The way system lets you know about the completion is
by setting an kernel event object that is part of the lpOverlapped structure.
So, all you do is spawn a thread and make the thread wait for that event object
using one of the WaitForSingleObject() APIs.<br>
Lets look at the overlapped structure:<br>
</font></p>
<table border="0" cellspacing="1" width="100%" bgcolor="#DADADA">
<tr>
<td width="100%">
<pre><font size="2" face="Verdana"><font color="#0000FF">typedef struct </font>_OVERLAPPED {
DWORD Internal;
DWORD InternalHigh;
DWORD Offset;
DWORD OffsetHigh;
<b>HANDLE hEvent</b>;
} OVERLAPPED, *LPOVERLAPPED;</font></pre>
</td>
</tr>
</table>
<p><font size="2" face="Verdana">The last parameter is the event handle that you
need to create . This event is generally a manual reset event. When you make a
call like WaitCommEvent () passing overlapped structure as the last parameter,
and the system could not complete call meaning it did not see any characters at
the port, it would return immediately but would return FALSE. If you now make a
call to GetLastError() you would get ERROR_IO_PENDING which means that the call
has been accepted but no characters have yet arrived at the COM port. Also it
means whenever the characters will arrive, the system will set the hEvent of the
overlapped structure that you passed in. So if your thread would wait for single
object on hEvent and you pass INFINITE, then whenever your Wait fn. returns
WAIT_OBJECT_0 it means some character has arrived or all the data in the
output buffer has been sent.<br>
In our current case since we are interested in more than one events we would
need to check what event did we get by making call to GetCommMask and checking
the dword against each event.Following pseudo code will explain it:</font></p>
<p><font size="2" face="Verdana"> You can read the data from the com port
and reset the event and make the call to WaitCommEvent again and so on.
</font></p>
<table border="0" cellspacing="1" width="100%" bgcolor="#EEE8EE">
<tr>
<td width="100%">
<pre><font face="Verdana" size="2" color="#417194">unsigned __stdcall CSerialCommHelper::ThreadFn(void*pvParam)
{
OVERLAPPED ov;
memset(&ov,0,sizeof(ov));
ov.hEvent = CreateEvent( 0,true,0,0);
HANDLE arHandles[2];
arHandles[0] = apThis->m_hThreadTerm;
DWORD dwWait;
SetEvent(apThis->m_hThreadStarted);
while ( abContinue )
{
</font><font face="Verdana" size="2"> BOOL abRet = ::WaitCommEvent(apThis->m_hCommPort,&dwEventMask, &ov) ;
</font><font face="Verdana" size="2" color="#417194"> if ( !abRet )
{
ASSERT( GetLastError () == ERROR_IO_PENDING);
}
arHandles[1] = ov.hEvent ;
dwWait = WaitForMultipleObjects (2,arHandles,FALSE,INFINITE);
switch ( dwWait )
{
case WAIT_OBJECT_0:
{
_endthreadex(1);
}
break;
case WAIT_OBJECT_0 + 1:
{
</font><font face="Verdana" size="2">DWORD dwMask;
if (GetCommMask(apThis->m_hCommPort,&dwMask) )
{
if ( dwMask & EV_TXEMPTY )
TRACE("Data sent");
ResetEvent ( ov.hEvent );
continue;
}<font color="#417194">
</font>else
{
//read data here and reset ov.hEvent
}
</font><font color="#417194"><font face="Verdana" size="2"> }
}//switch
}//while
return 0;
}</font></font></pre>
</td>
</tr>
</table>
<p><font size="2" face="Verdana"> If you understood the above code , you
will understand the whole of this article and the source code provided.<br>
The above piece of code is simple using the overlapped IO method to do its
job.<br>
Once we have received the indication that the data has arrived we need to read
the data. Important thing to note here is that the when data arrives at the
serial port, it is copied over to system buffer. The data is removed from
the system buffer only when you have read the data using API such as ReadFile.
Like any buffer, system buffer has a limited size. So if you do not read the
data from the buffers quick enough the system buffers can be become full if more
data is arriving. What happens to further data depends upon the configuration
that you have set in the device configuration block (in call to SetCommState ).
Usually the applications do some kind of handshaking at the application level
but you can also make configurations such that the com port does not accept any
further data upon buffer-full events. But all that is beyond the scope of this
discussion. If possible its always better to have applications themselves
implementing some kind of handshaking - like do not send next block of
data until you get okay for the first block. Generally this kind of handshaking
is implemented using some sort of ACK / NAK and ENQ protocol.<br>
<br>
In order for us to read data we need to use ReadFile() API. ReadFile API has to
specify how much data to read. Lets say we are monitoring character arrivals and
10 characters arrive at the port. As soon as first character arrives at the port
the system will set the overlapped structure's event object and out
WaitSingleObject will return. Next we would need to read the data. So how much
data should we read? Should we read 1 byte or 10 bytes? That is a good
question. The way it works is as follows (Note:this
is not documented anywhere but this is what I have found by research on
Win2K,NT4.0) :<br>
When one (or more) characters arrive at the port, the event object associated with the
overlapped structure set once. Now lets say that you made a call to read and you
read 1 character. After reading 1 character , you would finally Reset the
overlapped structure's event object. Now you would go back to the WaitCommEvent
but it would return false since no "new" character has arrived. So you
will not be able to read any more characters. Now when another character
arrives, system will set the overlapped event and you would read one more
character but this time it will be the character that had arrived earlier and
you never read. This clearly is a problem.<br>
So what is the solution? The easiest solution is that as soon as you got the
event object indicating the arrival of a character, you should read all the
characters that are present in the port. (<i>If you are familiar with win API
MsgWaitForMultipleObjects you can draw a analogy here.</i>) </font></p>
<p><font size="2" face="Verdana">So again the question remains how many
characters to read. The answer is read all the characters in a loop using
ReadFile().
<br>
Here is the pseudo code</font></p>
<table border="0" cellspacing="1" width="100%" bgcolor="#DADADA">
<tr>
<td width="100%">
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -