📄 serial.html
字号:
<li>
<code>CSerial</code> is the base serial class, which provides a
wrapper around the Win32 API. It is a lot easier to use, because it
combines all relevant calls in one single class. It allows the
programmer to mix overlapped and non-overlapped calls, provides
reasonable default settings, better readability, etc, etc.
</li>
<li>
<code>CSerialWnd</code> fits in the Windows event driven model.
Whenever a communication event occurs a message is posted to the
owner window, which can process the event.
</li>
<li>
<code>CSerialMFC</code> is an MFC wrapper around
<code>CSerialWnd</code>, which make the serial classes fit better in
MFC based programs.
</li>
</ul>
<p>
If you're not using a message pump in the thread that performs the
serial communication, then you should use the <code>CSerial</code>
class. You can use blocking calls (the easiest solution) or one of
the synchronization functions (i.e.
<code>WaitForMultipleObjects</code>) to wait for communication events.
This approach is also used in most Unix programs, which has a similar
function as <code>WaitForMultipleObjects</code> called 'select'. This
approach is often the best solution in non-GUI applications, such as
NT services.
</p>
<p>
GUI applications, which want to use the event-driven programming model
for serial communications should use <code>CSerialWnd</code>. It is a
little less efficient, but the performance degradation is minimal
if you read the port efficiently. Because it fits perfectly in the
event-driven paradigm the slight performance degradation is a minimal
sacrifice. Note that you can use <code>CSerial</code> in GUI based
applications (even MFC/WTL based), but then you might block the
message pump. This is, of course, bad practice in in a commercial
application (blocking the message pump hangs the application from the
user's point of view for a certain time). As long as you know what the
impact is of blocking the message pump, you can decide for yourself if
it is acceptable in your case (could be fine for testing).
</p>
<p>
MFC application should use the <code>CSerialMFC</code> wrapper if
they want to pass CWnd pointers instead of handles. Because this
wrapper is very thin you can also choose to use CSerialWnd directly.
</p>
<h2>
Using the serial classes in your program
</h2>
<p>
Using the serial classes can be divided into several parts. First
you need to open the serial port, then you set the appropriate
baudrate, databits, handshaking, etc... This is pretty
straightforward. The tricky part is actually transmitting and
receiving the data, which will probably cause the most time to
implement. At last you need to close the serial port and as a
bonus if you don't then the library will do it for you.
</p>
<h3>
Sending data
</h3>
<p>
Let's start with a classic example from K&R and be polite and say
hello. The implementation is very straightforward and looks like this
(there is no error checking here for simplicity, it is there in the
actual project):
</p>
<pre>
#define STRICT
#include <tchar.h>
#include <windows.h>
#include "Serial.h"
int WINAPI _tWinMain
(
HINSTANCE /*hInst*/,
HINSTANCE /*hInstPrev*/,
LPTSTR /*lptszCmdLine*/,
int /*nCmdShow*/
)
{
CSerial serial;
// Attempt to open the serial port (COM1)
serial.Open(_T("COM1"));
// Setup the serial port (9600,N81) using hardware handshaking
serial.Setup(CSerial::EBaud9600,CSerial::EData8,CSerial::EParNone,CSerial::EStop1);
serial.SetupHandshaking(CSerial::EHandshakeHardware);
// The serial port is now ready and we can send/receive data. If
// the following call blocks, then the other side doesn't support
// hardware handshaking.
serial.Write("Hello world");
// Close the port again
serial.Close();
return 0;
}
</pre>
<p>
Of course you need to include the serial class' header-file. Make sure
that the header-files of this library are in your compiler's include
path. All classes depend on the Win32 API, so make sure that you have
included them as well. I try to make all of my programs ANSI and
Unicode compatible, so that's why the tchar stuff is in there. So
far about the header-files.
</p>
<p>
The interesting part is inside the main routine. At the top we declare
the <code>serial</code> variable, which represents exactly one COM
port. Before you can use it, you need to open the port. Of course
there should be some error handling in the code, but that's left as an exercise
for the reader. Besides specifying the COM port, you can
also specify the input and output buffer sizes. The default is 2Kb for
both buffers which will be sufficient for most applications. If you
need larger buffers, then specify them yourself.
</p>
<p>
Setting up the serial port is also pretty straightforward. The default
settings (9600,8N1) are applied when the device has been opened. Call
<code>Setup</code> if these settings do not apply for your
application. If you prefer to use integers instead of the enumerated
types then just cast the integer to the required type. So the
following two initializations are equivalent:
</p>
<pre>
Setup(CSerial::EBaud9600,CSerial::EData8,CSerial::EParNone,CSerial::EStop1);
Setup(CSerial::EBaudrate(9600),
CSerial::EDataBits(8),
CSerial::EParity(NOPARITY),
CSerial::EStopBits(ONESTOPBIT));
</pre>
<p>
In the latter case, the types are not validated. So make sure that you
specify the appropriate values. Once you know which type of
handshaking you need, then just call <code>SetupHandshaking</code>
with one of the appropriate handshaking.
</p>
<p>
Writing data is also very easy. Just call the <code>Write</code>
method and supply a string. The <code>Write</code> routine will detect
how long the string is and send these bytes across the cable. If you
have written Unicode applications (like this one) then you might have
noticed that I didn't send a Unicode string. I think that it's pretty
useless to send Unicode strings, so you need to send them as binary or
convert them back to ANSI yourself. Because we are using hardware
handshaking and the operation is non-overlapped, the
<code>Write</code> method won't return until all bytes have been sent
to the receiver. If there is no other side, then you might block
forever at this point.
</p>
<p>
Finally, the port is closed and the program exits. This program is
nice to display how easy it is to open and setup the serial
communication, but it's not really useful. The more interesting
programs will be discussed later.
</p>
<h3>
Receiving data
</h3>
<p>
Like in real life it's easier to tell something what to do then
listening to another and take appropriate actions. The same holds for
serial communication. As we saw in the Hello world example writing to
the port is just as straightforward as writing to a file. Receiving
data is a little more difficult. Reading the data is not that hard,
but knowing that there is data and how much makes it more difficult.
You'll have to wait until data arrives and when you're waiting you
cannot do something else. That is exactly what causes problems in
single-threaded applications. There are three common approaches to
solve this.
</p>
<p>
The first solution is easy. Just block until some data arrives on the
serial port. Just call <code>WaitEvent</code> without specifying the
overlapped structure. This function blocks until a communication event
occurs (or an optional time-out expires). Easy, but the thread is
blocked and only wakes up for communication events or a time-out.
</p>
<p>
The second solution is to use the synchronization objects of Win32.
Whenever something happens, the appropriate event handles are signaled and you can take appropriate action to handle the event.
This method is available in most modern operating systems, but the
details vary. Unix systems use the <code>select</code> call, where
Win32 applications mostly use <code>WaitForMultipleObjects</code> or
one of the related functions. The trick is to call the
<code>WaitEvent</code> function asynchronously by supplying an
overlapped structure, which contains a handle which will be signaled when an event occurred. Using <code>WaitForMultipleObjects</code> you
can wait until one of the handles become signaled. I think this is
the most suitable for most non-GUI applications. It's definitely the
most efficient option available. When you choose to use this option,
you'll notice that the serial classes are only a thin layer around
the Win32 API.
</p>
<p>
The last solution is one which will be appreciated by most Windows GUI
programmers. Whenever something happens a message is posted to the
application's message queue indicating what happened. Using the
standard message dispatching this message will be processed
eventually. This solution fits perfect in the event-driven programming
environment and is therefore useful for most GUI (both non-MFC and
MFC) applications. Unfortunately, the Win32 API offers no support to
accomplish this, which is the primary reasons why the serial classes
were created. The old Win16 API uses the <code>SetCommEventMask</code>
and <code>EnableCommNotification</code> to do exactly this, but these
were dropped from the Win32 API.
</p>
<h3>Block until something happens</h3>
<p>
Blocking is the easiest way to wait for data and will therefore be
discussed first. The <code>CSerial</code> class exposes a method
called <code>WaitEvent</code>, which will block until an event has
been received. You can (optionally) specify a time-out for this call,
so it won't block forever if no data arrives anymore. The
<code>WaitEvent</code> method can wait for several events, which must
be registered during setup. The following events can occur on a COM
port:
</p>
<ul>
<li>
<code>EEventBreak</code> is sent whenever a break was detected on
input.
</li>
<li>
<code>EEventCTS</code> means that the CTS (clear to sent) signal has
changed.
</li>
<li>
<code>EEventDSR</code> means that the DSR (data set ready) signal has
changed.
</li>
<li>
<code>EEventError</code> indicates that a line-status error has
occured.
</li>
<li>
<code>EEventRing</code> indicates that the ring indicator was set
high. Only transitions from low to high will generate this event.
</li>
<li>
<code>EEventRLSD</code> means that the RLSD
(receive line signal detect) signal has changed. Note that this
signal is often called CD (carrier detect).
</li>
<li>
<code>EEventRecv</code> is probably one of the most important events,
because it signals that data has been received on the COM-port.
</li>
<li>
<code>EEventRcvEv</code> indicates that a certain character (the
event character) has been received. This character can be set using
the <code>SetEventChar</code> method.
</li>
<li>
<code>EEventSend</code> indicates that the entire output buffer has
been sent to the other side.
</li>
</ul>
<p>
When a serial port is opened, then the <code>EEventBreak</code>,
<code>EEventError</code> and <code>EEventRecv</code> are being
registered. If you would like to receive the other events then you
have to register them using the <code>SetMask</code> method.
</p>
<p>
Now you can use the <code>WaitEvent</code> method to wait for an
event. You can then call <code>GetEventType</code> to obtain the
actual event. This function will reset the event, so make sure you
call it only once after each <code>WaitEvent</code> call. Multiple
events can be received simultaneously (i.e. when the event character
is being received, then <code>(EEventRecv|EEventRcvEv)</code> is
returned. Never use the <code>==</code> operator to check for events,
but use the <code>&</code> operator instead.
</p>
<p>
Reading can be done using the <code>Read</code> method, but reading is
more tricky then you might think at first. You get only an event that
there is some data, but not how much. It could be a single byte, but
it can also be several kilobytes. There is only one way to deal with
this. Just read as much as you can handle (efficiently) and process
it.
</p>
<p>
First make sure that the port is in
<code>EReadTimeoutNonblocking</code> mode by issuing the following
call:
</p>
<pre>
// Use 'non-blocking' reads, because we don't know how many bytes
// will be received. This is normally the most convenient mode
// (and also the default mode for reading data).
serial.SetupReadTimeouts(CSerial::EReadTimeoutNonblocking);
</pre>
<p>
The <code>Read</code> method will now read as much as possible, but
will never block. If you would like <code>Read</code> to block, then
specify <code>EReadTimeoutBlocking</code>. <code>Read</code> always
returns the number of bytes read, so you can determine whether you have
read the entire buffer. Make sure you always read the entire buffer
after receiving the <code>EEventRecv</code> event to avoid you lose
data. A typical <code>EEventRecv</code> will look something like this:
</p>
<pre>
// Read data, until there is nothing left
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -