|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
IntroductionSerial 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... 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. 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. Why is serial communication that hard?
Serial communication in Win32 uses the standard Baudrates, parity, databits, handshaking, etc...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). 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:
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. 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. Asynchronous I/O makes things more complexFile 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. 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. The event driven programming model doesn't fit
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
// Start the message-pump until a WM_QUIT is received MSG msg; while (::GetMessage(&msg,0,0,0)) { ::TranslateMessage(&msg); ::DispatchMessage(&msg); }
Because the
If you implement your own message-pump, you can use the
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; while (::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; } } 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 OWL 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.
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 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. Which class you should use in your codeThe current implementation contains four different classes, which all have their own purpose. The following three classes are available.
If you're not using a message pump in the thread that performs the
serial communication, then you should use the
The
GUI applications, which want to use the event-driven programming model
for serial communications should use
MFC application should use the Using the serial classes in your programUsing 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. Sending dataLet'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): #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; } 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.
The interesting part is inside the main routine. At the top we declare
the
Setting up the serial port is also pretty straightforward. The
settings from the control panel (or Device Manager) are being used as
the port's default settings. Call Setup(CSerial::EBaud9600,CSerial::EData8,CSerial::EParNone,CSerial::EStop1); Setup(CSerial::EBaudrate(9600), CSerial::EDataBits(8), CSerial::EParity(NOPARITY), CSerial::EStopBits(ONESTOPBIT));
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
Writing data is also very easy. Just call the 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. Receiving dataLike 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.
The first solution is easy. Just block until some data arrives on the
serial port. Just call
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
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 Block until something happens
Blocking is the easiest way to wait for data and will therefore be
discussed first. The
When a serial port is opened, then the
Now you can use the
Reading can be done using the
First make sure that the port is in
// 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);
The // Read data, until there is nothing left DWORD dwBytesRead = 0; BYTE abBuffer[100]; do { // Read data from the COM-port serial.Read(abBuffer,sizeof(abBuffer),&dwBytesRead); if (dwBytesRead > 0) { // TODO: Process the data } } while (dwBytesRead == sizeof(abBuffer)); The Listener sample (included in the ZIP-file) demonstrates the technique as described above. The entire sample code isn't listed in this document, because it would take too much space. Using the Win32 synchronization objectsIn most cases, blocking for a single event (as described above) isn't appropriate. When the application blocks, then it is completely out of your control. Suppose you have created a service which listens on multiple COM-ports and also monitors a Win32 event (used to indicate that the service should stop). In such a case, you'll need multithreading, message queues or the Win32 function for synchronization. The synchronization objects are the most efficient method to implement this, so I'll try to explain them. Before you continue reading I assume you're a bit familiar with the use of the synchronization objects and overlapped operations. If you're not, then first read the section about Synchronization in the Win32 API.
The only call that blocks for a fairly long time is the
First the the COM-port needs to be initialized. This works identical
as in the Listener sample. Then two events are created. The first
event will be used in the overlapped structure. Note that it should
be a manual reset event, which is initially not signaled. The second
one is an external event, which is used to stop the program. The first
event will be stored inside the
// Create a handle for the overlapped operations HANDLE hevtOverlapped = ::CreateEvent(0,TRUE,FALSE,0);; // Open the "STOP" handle HANDLE hevtStop = ::CreateEvent(0,TRUE,FALSE,_T("Overlapped_Stop_Event")); // Setup the overlapped structure OVERLAPPED ov = {0}; ov.hEvent = hevtOverlapped;
All events have been setup correctly and the overlapped structure has
been initialized. We can now call the // Wait for an event serial.WaitEvent(&ov); The overlapped I/O operation is now in progress and whenever an event occurs, that would normally unblock this call, the event handle in the overlapped structure will become signalled. It is not allowed to perform an I/O operation on this port, before it has completed, so we will wait until the event arrives or the stop event has been set. // Setup array of handles in which we are interested HANDLE ahWait[2]; ahWait[0] = hevtOverlapped; ahWait[1] = hevtStop; // Wait until something happens switch (::WaitForMultipleObjects(2,ahWait,FALSE,INFINITE)) { case WAIT_OBJECT_0: // Serial port event occurred ... case WAIT_OBJECT_0+1: // Stop event raised ... } That's all you need to do, when you want to use the serial class in overlapped I/O mode. N Using Windows messages
Most Windows developers are used to receive a Windows message,
whenever a certain event occurs. This fits perfectly in the Windows
event-driven model, but the Win32 API doesn't provide such a
mechanism for serial communication. This library includes a class
called
Instead of using the
Because LONG Open (
LPCTSTR lpszDevice,
HWND hwndDest,
UINT nComMsg = WM_NULL,
LPARAM lParam = 0,
DWORD dwInQueue = 0,
DWORD dwOutQueue = 0
)
The
Sending data and setting up the serial port is exactly the same as
with
If everything is fine, then you have registered all interesting events
with the LRESULT CALLBACK MyWndProc (HWND hwnd, UINT nMsg, WPARAM wParam, LPARAM lParam)
{
if (nMsg == CSerialWnd::mg_nDefaultComMsg)
{
// A serial message occurred
const CSerialWnd::EEvent eEvent = CSerialWnd::EEvent(LOWORD(wParam));
const CSerialWnd::EError eError = CSerialWnd::EError(HIWORD(wParam));
switch (eEvent)
{
case CSerialWnd::EEventRecv:
// TODO: Read data from the port
break;
...
}
// Return successful
return 0;
}
// Perform other window processing
...
}
The methods Using the library with MFC
Personally, I don't like MFC, but I know many people out there use it
so there is also support in this library for MFC. Instead of using
BEGIN_MESSAGE_MAP(CMyClass,CWnd)
//{{AFX_MSG_MAP(CMyClass)
...
//}}AFX_MSG_MAP
...
ON_WM_SERIAL(OnSerialMsg)
...
END_MESSAGE_MAP()
Note that the afx_msg LRESULT CMyClass::OnSerialMsg (WPARAM wParam, LPARAM lParam)
{
const CSerialMFC::EEvent eEvent = CSerialMFC::EEvent(LOWORD(wParam));
const CSerialMFC::EError eError = CSerialMFC::EError(HIWORD(wParam));
switch (eEvent)
{
case CSerialMFC::EEventRecv:
// TODO: Read data from the port
break;
...
}
// Return successful
return 0;
}
A complete sample, including property sheets for setting up the COM-port, is shipped with this library. Look for the SerialTestMFC project for an example how to use this library in your MFC programs. Integrating this library into your code.
This library is very lightweight, so it can easily be integrated into
your application without using a separate DLL. I used a static library
for the If you use precompiled headers, then you need to remove the following lines from both Serial.cpp and SerialWnd.cpp: #define STRICT #include <crtdbg.h> #include <tchar.h> #include <windows.h> Replace these lines with the following line: #include "StdAfx.h" Sample programsThe Serial library comes with some sample programs, which can all be compiled from the Serial.dsw workspace. Make sure that you always open the Serial.dsw file, when building the library and/or sample applications. Opening the individual .dsp files won't work, because these files don't have the dependencies, which results in unresolved externals. Note that all samples can be compiled as ANSI or Unicode versions. The Unicode versions don't run on the Windows 95/98/ME platforms, because they don't support Unicode. The SerialTestMFC sample uses the Unicode version of the MFC library (when compiled with Unicode enabled). The default installation options of Visual C++ v6.0 don't install the Unicode libraries of MFC, so you might get an error that mfc42ud.dll or mfc42u.dll cannot be found. Windows 95 support and CancelIo
A lot of people still need to support the Windows 95 environment,
which doesn't support the Other problems, specific to Windows 95/98/ME are the queue sizes. If the buffer size is far too small then you might get a blue screen, when receiving or transmitting data. The default settings should be fine for normal serial communication. Windows CE support and the lack of overlapped I/O
I have got a lot of requests to implement a windows CE version of the
library. The old version of this library always used overlapped
I/O, so it didn't work on Windows CE. Due to the huge amount of
requests I started working on this issue. I have rewritten the
<code>SetCommMask call blocks, when it is already waiting for
an event (using
To include this library into you Windows CE project, just add the Serial.dsp project file to your own workspace. If you use
eMbedded Visual C++, then the project file is automatically
converted. Make sure you define the Porting issues
This paragraph describes some porting issues that you should be aware
of when porting from an older version to a newer version. Always
retest your application, when using a different version of the serial
library. I always try to keep the code source-level compatible, but if
you use a new library version, then make sure you recompile your code
completely with the same
Virtual COM ports (USB dongles, Bluetooth dongles, ...)Some people use virtual COM-ports to emulate an ordinary serial port (i.e. USB serial dongle, Bluetooth dongle, ...). These so-called virtual COM ports use a different driver then ordinary serial ports. Unfortunately, most drivers are pretty buggy. In most cases normal communication using the default settings works pretty good, but you run into problems when you need more sophisticated communication.
Most virtual COM ports have difficulties with the
Receiving spurious events is another problem, which is quite common when using virtual COM ports. Even events that have been masked out can be sent by some virtual COM ports. For this reason the events are filtered inside the library, but you might get some empty events in your code (you can simply ignore these events). Some virtual COM ports also have problems when you don't use the common 8 databit, no parity and 1 stopbit settings. Sending a break is also problematical for some virtual COM ports. There is no standard workaround possible for these bugs, so make sure you test these features, when you intend to use your application with a virtual COM port.
Some people claim that the dongle and its driver is fine, because
HyperTerminal works fine. This isn't correct in most cases.
HyperTerminal only uses a small subset of the Win32 API for serial
communication and doesn't seem to use This serial library utilizes the overlapped I/O mechanism, which should be supported by each Win32 driver. Unfortunately, a lot of drivers don't implement this feature correct. However, I keep trying to improve this library so if you have any suggestions, please contact me. ReferencesOf course the first place to look for information about serial communications is in the Platform SDK section "Windows Base Services, Files and I/O, Communications". There's probably enough in there to implement your own serial communications, but for a better explanation read Allen Denver's article called "Serial Communications in Win32", which is in the MSDN's Technical Articles. Changes
Future features
Comments and disclaimerIf you have any comments or questions about these classes, then you can reach me using email at Ramon.de.Klein@ict.nl. This library is free for both commercial and non-commercial use (if you insist, you can send me money though). Unfortunately, I cannot guarantee any support for these classes. I cannot test every situation and the use of these classes is at your own risk. Because this library is distributed with full source-code included, I cannot stop you from changing the code. I don't mind if you change the code, but I don't want to be blamed for your bugs. So please mark your changes and keep my name in the copyrights as well. If you added a cool feature, then please let me know so I might integrate it with a new version of this library. The library is released under the conditions of the Lesser GNU Public License (LGPL). The sample programs are distributed under the terms of the GNU Public License (GPL). Please don't mirror this code or documentation on another website or removable media (such as a CD-ROM) with the intent to redistribute it. I don't want to have old versions floating around, which might contain bugs that are solved in later versions. Just mention the URL where users can download the archive and documentation. I would like to thank my friend and ex-colleague Remon Spekreijse for pointing out some problems, adding some features in this library and encouraging me to put this library on the net. I also want to thank all other people who have shown their appreciation one way or another.
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||