Serial Communication with Borland C++ Builder
==============================================
作者 :David Poinsett
日期 :November 1999
e-mail :d.poinsett@traverse.com
--------------------------------------------------------------------------------
Contents... Introduction
Example
Notes
-------------------------------------------------------------------------------- Introduction... I wish this site had been around when I was trying to figure out how to make serial communications work in Windows95. I, like many programmers, was hit with the double-whammy of having to learn Windows programming and Win95 serial comm programming at the same time. I found both tasks confusing at best. It was particularly frustrating because I had, over the years, written so much stuff (including lots of serial comm software) for the DOS environment and numerous embedded applications. Interrupt driven serial comm, DMA transfer serial comm, TSR serial comm, C, assembler, various processors... you name it, it had written it. Yet, everything I knew seemed upside-down in the message-driven-callback world of Windows. After spending lots of money on books and seemingly endless effort, I have finally gotten enough of a handle on Win95 and serial comm programming to write something usable in this environment. Borland's C++ Builder has done a lot to help make Win95 programming easier and, once you know the tricks, the serial communications stuff is pretty easy, too. The purpose of this site is to spare you hardship of my early efforts and get you up and running with your Win9x/NT serial comm programming as quickly as possible. If you're already familiar with using BCB to develop Windows programs, the example code should be plenty to get you going. You can also download the source code in BCBComm.zip. Good luck. -------------------------------------------------------------------------------- The Example... In the example that follows we're going to write a bare-bones program to do serial communication. It will consist of a Form with a Memo object (for text I/O) and a Thread object that handles incoming serial data. There are no menus or other features to distract us from focusing on the serial comm aspect of the program. Obviously, you'll want to add these and other elements to a fully functioning program. Fire up BCB and start a New Project. Place a Memo object on Form1. Using the Object Inspector, set Memo1 properties as follows: Alignment = alClient
MaxLength = 0
ScrollBars = ssVertical
WantReturns = true
WantTabs = false
WordWrap = true
Next, under the File | New menu, add a Thread Object. Use TRead for the class name when asked. You should now have two Unit files: Unit1.cpp for Form1 activity and Unit2.cpp for the thread. Using the Object Inspector again, create event handlers for the following events. The easiest way to create events handlers is as follows: Go to the event tab sheet in Object Inspector.
Find the event of interest.
Double-click the blank space next to the event name.
If you follow this scheme, Object Inspector will create and automatically name the event handlers to the same name used in our examples. OK, here are the objects and the events we need to handle: Form1 OnCreate
Form1 OnClose
Memo1 OnKeyPress
The framework for Unit1.cpp is now in place. Using the following listing as a guide, fill in Unit1.cpp with the following code. Be sure to note the #includes and global variables. If the framework for event handlers is missing in your program, DO NOT put it there by typing in the framework code! Go back and figure out what you missed. BCB MUST CREATE THE FRAMEWORK FOR YOU. The Main Form... //---------------------------------------------------------------------------
#include
#pragma hdrstop #include "Unit1.h" // YOU MUST INCLUDE THE HEADER FOR UNIT2 (THE THREAD UNIT) #include "Unit2.h" // GLOBAL VARIABLES HANDLE hComm = NULL;
TRead *ReadThread;
COMMTIMEOUTS ctmoNew = {0}, ctmoOld; //---------------------------------------------------------------------------
#pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
}
//---------------------------------------------------------------------------
void __fastcall TForm1::FormCreate(TObject *Sender)
{
DCB dcbCommPort; // OPEN THE COMM PORT.
// REPLACE "COM2" WITH A STRING OR "COM1", "COM3", ETC. TO OPEN
// ANOTHER PORT. hComm = CreateFile("COM2",
GENERIC_READ | GENERIC_WRITE,
0,
0,
OPEN_EXISTING,
0,
0); // IF THE PORT CANNOT BE OPENED, BAIL OUT. if(hComm == INVALID_HANDLE_VALUE) Application->Terminate(); // SET THE COMM TIMEOUTS IN OUR EXAMPLE. GetCommTimeouts(hComm,&ctmoOld);
ctmoNew.ReadTotalTimeoutConstant = 100;
ctmoNew.ReadTotalTimeoutMultiplier = 0;
ctmoNew.WriteTotalTimeoutMultiplier = 0;
ctmoNew.WriteTotalTimeoutConstant = 0;
SetCommTimeouts(hComm, &ctmoNew); // SET BAUD RATE, PARITY, WORD SIZE, AND STOP BITS.
// THERE ARE OTHER WAYS OF DOING SETTING THESE BUT THIS IS THE EASIEST.
// IF YOU WANT TO LATER ADD CODE FOR OTHER BAUD RATES, REMEMBER
// THAT THE ARGUMENT FOR BuildCommDCB MUST BE A POINTER TO A STRING.
// ALSO NOTE THAT BuildCommDCB() DEFAULTS TO NO HANDSHAKING. dcbCommPort.DCBlength = sizeof(DCB);
GetCommState(hComm, &dcbCommPort);
BuildCommDCB("9600,N,8,1", &dcbCommPort);
SetCommState(hComm, &dcbCommPort); // ACTIVATE THE THREAD. THE FALSE ARGUMENT SIMPLY MEANS IT HITS THE
// GROUND RUNNING RATHER THAN SUSPENDED. ReadThread = new TRead(false);
}
//---------------------------------------------------------------------------
void __fastcall TForm1::FormClose(TObject *Sender, TCloseAction &Action)
{
// TERMINATE THE THREAD. ReadThread->Terminate(); // WAIT FOR THREAD TO TERMINATE,
// PURGE THE INTERNAL COMM BUFFER,
// RESTORE THE PREVIOUS TIMEOUT SETTINGS,
// AND CLOSE THE COMM PORT. Sleep(250);
PurgeComm(hComm, PURGE_RXABORT);
SetCommTimeouts(hComm, &ctmoOld);
CloseHandle(hComm); }
//---------------------------------------------------------------------------
void __fastcall TForm1::Memo1KeyPress(TObject *Sender, char &Key)
{
// TRANSMITS ANYTHING TYPED INTO THE MEMO AREA. TransmitCommChar(hComm, Key); // THIS PREVENTS TYPED TEXT FROM DISPLAYING GARBAGE ON THE SCREEN.
// IF YOU ARE CONNECTED TO A DEVICE THAT ECHOES CHARACTERS, SET
// Key = 0 WITHOUT THE OTHER STUFF. if(Key != 13 && (Key < ' ' || Key > 'z')) Key = 0;
}
//---------------------------------------------------------------------------
Now we turn our attention to the thread code in Unit2.cpp. The framework should already be in place. Use this listing as a guide and fill in Unit2.cpp with the following code.
The Thread... //---------------------------------------------------------------------------
#include
#pragma hdrstop // YOU MUST INCLUDE THE HEADER FOR UNIT1 #include "Unit1.h"
#include "Unit2.h" extern HANDLE hComm;
char InBuff[100]; //---------------------------------------------------------------------------
// Important: Methods and properties of objects in VCL can only be
// used in a method called using Synchronize, for example:
//
// Synchronize(UpdateCaption);
//
// where UpdateCaption could look like:
//
// void __fastcall TRead::UpdateCaption()
// {
// Form1->Caption = "Updated in a thread";
// }
//---------------------------------------------------------------------------
__fastcall TRead::TRead(bool CreateSuspended)
: TThread(CreateSuspended)
{
}
//--------------------------------------------------------------------------- void __fastcall TRead::DisplayIt()
{ // NOTE THAT IN THIS EXAMPLE, THERE IS NO EFFORT TO MONITOR
// HOW MUCH TEXT HAS GONE INTO Memo1. IT CAN ONLY HOLD ABOUT 32K.
// ALSO, NOTHING IS BEING DONE ABOUT NON-PRINTABLE CHARACTERS
// OR CR-LF'S EMBEDDED IN THE STRING. // DISPLAY THE RECEIVED TEXT. Form1->Memo1->SetSelTextBuf(InBuff); } //---------------------------------------------------------------------------
void __fastcall TRead::Execute()
{
//---- Place thread code here ---- DWORD dwBytesRead; // MAKE THE THREAD OBJECT AUTOMATICALLY DESTROYED WHEN THE THREAD
// TERMINATES. FreeOnTerminate = true; while(1)
{ // TRY TO READ CHARACTERS FROM THE SERIAL PORT.
// IF THERE ARE NONE, IT WILL TIME OUT AND TRY AGAIN.
// IF THERE ARE, IT WILL DISPLAY THEM. ReadFile(hComm, InBuff, 50, &dwBytesRead, NULL); if(dwBytesRead)
{
InBuff[dwBytesRead] = 0; // NULL TERMINATE THE STRING
Synchronize(DisplayIt);
} } } //--------------------------------------------------------------------------- One last thing... To do a synchronized call to DisplayIt() from within the thread's Execute() function, DisplayIt() it must be declared as a __fastcall type in the header file. Here's how to do it. Open the header file "unit2.h" and add the DisplayIt() line as shown below: //---------------------------------------------------------------------------
class TRead : public TThread
{
private:
protected:
void __fastcall DisplayIt(void); // ADD THIS LINE
void __fastcall Execute();
public:
__fastcall TRead(bool CreateSuspended);
};
//--------------------------------------------------------------------------- Notes...
As mentioned earlier this example focuses strictly on the core elements that make the serial communication functions work. In its present form it's unlikely to be particularly useful or acceptable in an actual application. In other words, you need to add what's missing. If you've followed along this far, that should not be too difficult. To minimize any confusion on what's missing, I'll highlight some of the areas that should be addressed: There is little or no provision for error handling
The 32K display limit of the Memo object is not handled
For proper text display in Memo, ignore linefeeds and replace carriage returns with a CR-LF pair
Menus
Storing incoming serial data to disk
Sending disk contents out serial port
Handshaking
Protocol (Xmodem, Zmodem, etc.)
There are several ways to test your work. One method is to perform a loop-back test by jumping pins 2 and 3 on your computer's RS-232 connector. With the loop-back connection anything you type into the Memo area will be echoed back. Here are some online references that you might find useful: Serial Communications in Win32 . This is a comprehensive reference.
www.ontrak.net . Excellent example of simple serial port access.
www.temporaldoorway.com . Good example of threaded serial program with overlapped I/O.
www.codeguru.com . Yet another example (more for VC ).
Good luck. --------------------------------------------------------------------------------