📄 serial.html
字号:
<!--------------------------------------------------------------------------->
<!-- INTRODUCTION
The Code Project article submission template (HTML version)
Using this template will help us post your article sooner. To use, just
follow the 3 easy steps below:
1. Fill in the article description details
2. Add links to your images and downloads
3. Include the main article text
That's all there is to it! All formatting will be done by our submission
scripts and style sheets.
-->
<!--------------------------------------------------------------------------->
<!-- IGNORE THIS SECTION -->
<html>
<head>
<title>The Code Project</title>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
<Style>
BODY, P, TD { font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 10pt }
H2,H3,H4,H5 { color: #ff9900; font-weight: bold; }
H2 { font-size: 13pt; }
H3 { font-size: 12pt; }
H4 { font-size: 10pt; color: black; }
PRE { BACKGROUND-COLOR: #FBEDBB; FONT-FAMILY: "Courier New", Courier, mono;
WHITE-SPACE: pre; }
CODE { COLOR: #990000; FONT-FAMILY: "Courier New", Courier, mono; }
</style>
</head>
<body bgcolor="#FFFFFF" color=#000000>
<!--------------------------------------------------------------------------->
<!------------------------------- STEP 1 --------------------------->
<!-- Fill in the details (CodeProject will reformat this section for you) -->
<pre>
Title: Serial library for C++
Author: Ramon de Klein
Email: R.de.Klein@iaf.nl
Environment: VC++ 6.0, Windows NT v4.0, Windows 98/ME, Windows 2000
Keywords: Serial, RS232, COM-port
Level: Intermediate
Description: A high-performance, complete and compact serial library for C++
Section System
SubSection General
</pre>
<hr width=100% noshade>
<!------------------------------- STEP 2 --------------------------->
<!-- Include download and sample image information. -->
<li class=download><a href="Serial.zip">Download demo project (with source) - XXX Kb </a></li>
<!-- <p><img src="SerialScreenDump.gif" alt="Sample Image" width=740 height=532></p> -->
<!------------------------------- STEP 3 --------------------------->
<!-- Add the article text. Please use simple formatting (<h2>, <p> etc) -->
<h2>
Introduction
</h2>
<p>
Serial communications is needed in several types of applications, but
the Win32 API isn't a very easy to use API to implement it. Things get
even more complicated when you want to use serial communication in an
MFC based program. The classes provided in the library try to make
life a little easier. Its documentation is extensive, because I want
to give you a good background. Serial communication is hard and good
knowledge of its implementation saves you a lot of work, both now and
in the future...
</p>
<p>
First I'll briefly discuss why serial communications is hard. After
reading that chapter you'll probably be convinced as well that you
need a class which deals with serial communication. The classes
provided in the library are not the only classes, which handle the
serial communication. Many other programmers wrote their own classes,
but I found many of them too inefficient or they weren't robust,
scalable or suitable for non-MFC programs. I tried to make these
classes as efficient, reliable and robust as possible, without
sacrificing ease of use too much.
</p>
<p>
The library has been developed as a public domain library some time
ago, but it has been used in several commercial applications. I think
most bugs have been solved, but unfortunately I cannot guarantee that
there are no bugs left. If you find one (or correct a bug), please
inform me so I can update the library.
</p>
<h2>
Why is serial communication that hard?
</h2>
<p>
Serial communication in Win32 uses the standard ReadFile/WriteFile
functions to receive and transmit data, so why should serial
communication be any harder then just plain file I/O? There are
several reasons, which I'll try to explain. Some problems are solved
in this library, but some others cannot be solved by a library.
</p>
<h3>
Baudrates, parity, databits, handshaking, etc...
</h3>
<p>
Serial communication uses different formats to transmit data on the
wire. If both endpoints doesn't use the same setting you get garbled
data. Unfortunately, no class can help you with these problems. The
only way to cope with this is that you understand what these settings
are all about. Baudrate, parity, databits and stopbits are often quite
easy to find out, because when they match with the other endpoint, you
won't have any problems (if your computer is fast enough to handle the
amount of data at higher baudrates).
</p>
<p>
Handshaking is much more difficult, because it's more difficult to
detect problems in this area. Handshaking is being used to control the
amount of data that can be transmitted. If the sending machine can
send data more quickly then the receiving machine can process we get
more and more data in the receiver's buffer, which will overflow at a
certain time. It would be nice when the receiving machine could tell
the sending machine to stop sending data for a while, so it won't
overflow the receiver's buffers. This process of controlling the
transmission of data is called handshaking and there are basically
three forms of handshaking:
<ol>
<li>
No handshaking, so data is always send even if the receiver cannot
handle the data anymore. This can lead to data loss, when the sender
is able to transmit data faster then the receiver can handle. Of
course this option isn't recommended, but it can be used for
situations where only a few bytes are transmitted once in a while.
</li>
<li>
Hardware handshaking, where the RTS/CTS lines are used to indicate
if data can be sent. This mode requires that both ports and the
cable support hardware handshaking. Hardware handshaking is the most
reliable and efficient form of handshaking available, but is
hardware dependant. Make sure you have a proper cable, which is
fully wired. There are a lot of wrong cables around, so make sure
you use the right one.
</li>
<li>
Software handshaking, where the XON/XOFF characters are used to
throttle the data. A major drawback of this method is that these
characters cannot be used for data anymore. The XON/XOFF characters
are the CTRL-S/CTRL-Q characters, which cannot be used in the data
stream anymore. This makes software handshaking pretty useless, when
you want to send binary data. For ASCII data it's pretty useful.
It's being used on the old UNIX terminals as well. Scrolling starts
and stops with CTRL-S/CTRL-Q on these, so the user provides its own
handshaking there (without even knowing it perhaps).
</li>
</ol>
<p>
Problems with handshaking are pretty hard to find, because it will
often only fail in cases where buffers overflow. These situations are
hard to reproduce so make sure that you did setup handshaking
correctly and that the used cable is working correct (if you're using
hardware handshaking) before you continue.
</p>
<p>
The Win32 API provides more handshaking options, which aren't directly
supported by this library. These types of handshaking are rarely used,
so it would probably only complicate the classes. If you do need these
handshaking options, then you can use the Win32 API to do that and
still use the classes provided by the library.
</p>
<h3>Asynchronous I/O makes things more complex</h3>
<p>
File I/O is relatively fast so if the call blocks for a while, this
will probably only be a few milliseconds which is acceptable for most
programs. Serial I/O is much slower, which causes unacceptable delays
in your program. Another problem is that you don't know when the data
arrives and often you don't even know how much data will arrive.
</p>
<p>
Win32 provides asynchronous function calls (also known as overlapped
operations) to circumvent these problems. Asynchronous programming is
often an excellent way to increase performance, but it certainly
increases complexity as well. This complexity is the reason that a lot
of programs have bugs in their serial communication routines. This
library solves some asynchronous I/O problems by allowing the
programmer to use overlapped and non-overlapped operations mixed
throughout the code, which is often quite convenient.
</p>
<h3>The event driven programming model doesn't fit</h3>
<p>
Things get even more complex in GUI applications, which uses the event
driven model that they're used to. This programming model is a
heritage of the old 16-bit days and it isn't even that bad. The basic
rule is simple... All events are send using a windows message, so you
need at least one window to receive the events. Most GUI applications
are single-threaded (which is often the best solution to avoid a lot
of complexity) and they use the following piece of code in the
<code>WinMain</code> function to process all messages:
</p>
<pre>
// Start the message-pump until a WM_QUIT is received
MSG msg;
while (::GetMessage(&msg,0,0,0))
{
::TranslateMessage(&msg);
::DispatchMessage(&msg);
}
</pre>
<p>
Because the <code>GetMessage</code> function blocks until there is a
message in the message queue, there's no way to wake up when a serial
event occurs. Of course you can set a timer and check the ports
there, but this kind of polling is bad design and certainly doesn't
scale well. Unfortunately the Win32 serial communication API doesn't
fit in this event driven model. It would be easier for GUI
applications that the Win32 API posted a message to a window when a
communication event occurred (this is exactly what the 16-bit
implementation looked like).
</p>
<p>
If you implement your own message-pump, you can use the
<code>MsgWaitForMultipleObjects</code> to wait for a windows message
or a windows object to become signaled. The following piece of code
demonstrates how to do this (it assumes that the event handle that is
being used for asynchronous events is stored in the variable
<code>hevtCommEvent</code>):
</p>
<pre>
bool fQuit = false;
while (!fQuit)
{
// Wait for a communication event or windows message
switch (::MsgWaitForMultipleObjects(1,&hevtCommEvent,FALSE,INFINITE,QS_ALLEVENTS))
{
case WAIT_OBJECT_0:
{
// There is a serial communication event, handle it...
HandleSerialEvent();
}
break;
case WAIT_OBJECT_0+1:
{
// There is a windows message, handle it...
MSG msg;
if (::PeekMessage(&msg,0,0,0,PM_REMOVE))
{
// Abort on a WM_QUIT message
if (msg.message == WM_QUIT) { fQuit = true; break; }
// Translate and dispatch the message
::TranslateMessage(&msg);
::DispatchMessage(&msg);
}
}
break;
default:
{
// Error handling...
}
break;
}
}
</pre>
<p>
This code is much more complex then the simple message pump displayed
above. This isn't that bad, but there is another problem with this
code which is much more serious. The message pump is normally in one
of the main modules of your program. You don't want to pollute that
piece of code with serial communication from a completely different
module. The handle is probably not even valid at all times, which can
cause problems of its own. This solution is therefore not recommended.
MFC and WTL programmers cannot implement this at all, because these
frameworks already their own message pumps. You might be able to
override that message pump, but it probably requires a lot of tricky
code and undocumented tricks.
</p>
<p>
Using serial communications in a single-threaded event-driven program
is difficult as I've just explained, but you probably found that out
yourself. How can we solve this problem for these types of
applications? The answer is in the <code>CSerialWnd</code> class,
which posts a message to a window (both the message and window can be
specified by the programmer) whenever a serial event occurs. This
makes using a serial port in GUI based applications much easier.
There is also a very thin MFC wrapper class, which is called
<code>CSerialMFC</code> but it's that thin, that it's hardly worth
mentioning.
</p>
<p>
This library cannot perform magic, so how can it send messages without
blocking the message pump? The answer is pretty simple. It uses a
separate thread, which waits on communication events. If such an event
occurs, it will notify the appropriate window. This is a very common
approach, which is used by a lot of other (serial) libraries. It's not
the best solution (in terms of performance), but it is suitable for
99% of the GUI based communication applications. The communication
thread is entirely hidden for the programmer and doesn't need to
affect your architecture in any way.
</p>
<h2>
Which class you should use in your code
</h2>
<p>
The current implementation contains three different classes, which all
have their own purpose. The following three classes are available.
</p>
<ul>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -