HOME - - - - - - - TUTORIALS INDEX - - - - - - - - - - - - Other material for programmers

Delphi: RS-232, serial comms, COM1, COM2

This has good information, and a search button at the bottom of the page

Please don't dismiss it because it isn't full of graphics, scripts, cookies, etc!

Click here if you want to know more about the source and format of these pages.


The RS-232 serial interface is a wondrous thing. It has many marvelous capabilities. It is also something I've put of wrestling with for a long time. Search the web for "How To"s and you may see why. It turns out that things are not as bad as you might think.

STOP PRESS: I wrote this tutorial in April of 2003. In April of 2010, I attacked the same project again, but by then the external device I wanted to work with was the wonderful inexpensive, programmer friendly, Arduino microprocessor. I would advise you to go to the newer tutorial about talking to things over a serial cable from a Windows computer. If that doesn't tell you everything you want to know, or if it leaves you feeling that you've "nearly got it", perhaps then come back here and skim what's below to see if reading a second explanation of, mostly, the same things helps.

FOLLOWING COMMENTS APPLY TO WHAT FOLLOWS here, NOT TO THE new-at-Apr-2010 tutorial DISCUSSED A MOMENT AGO

(But I must admit, the last program below doesn't work yet! But online resources assure me it "should"! Read on?)

The programs presented here will do certain jobs, but they do not implement everything specified within RS-232. The big gap in my "solution" is that there is no handshaking.

I wanted to be able to communicate with a nice little PIC based microcontroller called the Pascalite. You can learn more about that (there's a free compiler and hardware simulator) in my Pascalite pages.

My quest for serial communications took a leap forward thanks to Robert Clemenzi's FAQ answers.

Much of the following is taken from there, with alterations. He himself credited the old http://community.borland.com/article/0,1410,16400,00.html Borland's FAQ 16400 as the inspiration and source of what he posted.

I subsequently did a Google search on "commtimeouts delphi" and turned up a number of useful things, not least Peter Johnson's good essay. Peter has written a number of articles, components and applications, all of which can be found on his web page. He also addresses how you can set the port's baud rate, etc, which I have not covered here. (Yet!)

If you need help with the low level electronics, cables, pinouts, connectors of RS-232, visit Arc Electronics' helpful page.

Still with me, I hope? First we'll write a Delphi program which can send RS-232 data to the Pascalite (or other RS-232 receiving device!) The Pascalite will display what is sent to it on the Pascalite's LCD display.

The program in the Pascalite will be:
program Demo3RS232;
var bTmp,bFrmRS232:byte;

begin {main}
write(LCD,255); {clears LCD}
repeat
if RS232_New_Byte then begin
  read(RS232,bFrmRS232);
  write(LCD,bFrmRS232);
 end;
until 4=5;
end.
If only Windows programming was so straightforward! (You may want to visit my Pascalite tutorial if the above isn't clear to you.)

The Pascalite Plus has a one byte RS-232 receive buffer. If a second byte of data is received before the first has been processed, the first is lost. Happily, the filling of the buffer is done by an interrupt-triggered routine. The Pascalite can be doing something else at the moment the byte's bits begin to arrive. Furthermore, the RS232_New_Byte allows the PAscalite to know if there is an unused byte waiting in the buffer for processing. (The Plus has a 32byte buffer). In our simple approach to serial communication over the comm port, we're going to have to do in software what can be done in hardware with more sophisticated solutions.

If you think about the code given above, and the fact that there's only a single byte buffer, you will see that the machine sending information to the Pascalite must send it a character at a time, slowly enough to ensure that the Pascalite has dealt with the previous byte before the next one is sent. There are lots of ways to overcome the problems inherent in that situation, which I'll try to discuss later, but for now: let's get the essentials working.

We're still not ready to start our Delphi program. RS-232 is fraught with many connection issues. The cable between the machines has to be right. At each end, baud rates and other things have to be set. In respect of connecting a Pascalite to a Windows machine, these issues are discussed at my Pascalite RS232 tutorial. For the rest of you, I will point out that the program I am going to develop here does not have parameter setting routines. Set up your Windows comm port via the Settings | System Settings | Device manager. Set up your remote device however it has to be done. Note in particular that you should select no handshaking, not RTS/CTS, not XON/XOFF.

Before embarking on the Delphi program here, it would probably be best to see if you can send to your remote device using Hyperterminal. (Again, my Pascalite RS232 tutorial discusses aspects of this.)

So! At last! The Delphi Program. I've tested it with Delphi 2.

It is pretty simple! The form has one button. When you click the button, the machine the Delphi program is running in sends a phrase to the other machine via the RS-232.

First I'll list the whole program, then I'll explain odds and ends. I used the power of Delphi to add a button to the form, and to generate the shell of Button1Click. I've put (***** n *) after lines I had to type in by hand.
unit DD28;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls;

type
  TDD28F1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
    procedure SendChar(sToSend:string);(***** 1 *)
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  DD28F1: TDD28F1;
  NumberWritten:dword;           (***** 2 *)
  sPhraseToSend,CommPort:string; (***** 3 *)
  hCommFile:THandle;             (***** 3 *)
  c1:byte;                       (***** 3 *)

implementation

{$R *.DFM}

procedure TDD28F1.SendChar(sToSend:string);
begin
CommPort := 'COM1';                      (***** 4 start *)
hCommFile := CreateFile(PChar(CommPort),
                          GENERIC_WRITE,
                          0,
                          nil,
                          OPEN_EXISTING,
                          FILE_ATTRIBUTE_NORMAL,
                          0);
WriteFile(hCommFile,
            PChar(sToSend)^,
            Length(sToSend),
            NumberWritten,
            nil);                        (***** 4 end *)
CloseHandle(hCommFile);                  (***** 5 *)
end;

procedure TDD28F1.Button1Click(Sender: TObject);
begin
sPhraseToSend:='Testing 1 2 3';           (***** 6 start *)
for c1:=1 to length(sPhraseToSend) do begin
  SendChar(copy(sPhraseToSend,c1,1));
  application.processmessages;            (***** 6 end *)
  sleep(10);                              (***** 7 *)
  end;
end;

end.
(I'll try to give you more in the way of comments another time. For now, two things to note....

If your comm link is via COM2, you need to alter the code in the line marked "(***** 4 start *)"

In the line marked "(***** 2 *)", I've declared NumberWritten to be of type dword. This disagrees with Borland's FAQ answer, but agrees with something in the other source I mentioned... and it works!

=============
So much for sending things FROM the Delphi program. Now we'll move on to receiving things by the Delphi program.

Remember that it will be up to you to org anise things so that the machines in "conversation" don't interrupt each other, i.e. remember that hardware handshaking has been replaced with careful programming.

For the exercise below, we are going to have the "main" computer (i.e. the one running the Delphi program" be in charge. The "slave" (a Pascalite in my case) will wait on the big machine, and speak after it is spoken to.

Specifically, the slave's program will be a loop looking for a message from the main machine. If an "f" is sent, the slave will "reply" with "I saw an f". If something else is sent, the slave will reply with "I saw something other than an f". (The reply will be displayed as the caption of a label on the Delphi program's form.)

The Pascalite program... which can be tested with the main machine running Hyperterminal.... is as follows....
program Demo3RS232;
var bTmp,bFrmRS232:byte;

begin {main}
repeat
bFrmRS232:=255;
if RS232_New_Byte then begin
  read(RS232,bFrmRS232);
  end;
if bFrmRS232<255 then begin
   {delay(200); see below}
   if bFrmRS232=102 {102 is code for "f"}
      then write(RS232,'I saw an f') {no ; here}
      else write(RS232,'I saw something other than f');
end;{not 255}
until 4=5;
end.
The following SHOULD work!! with the above... but it doesn't yet, sigh. It RUNS... but it doesn't seem to pick up the message coming back from the Pascalite. The Pascalite is working fine with Hyperterminal. (And yes, I did remember to disconnect the Hyperterminal connection before trying to run the above.) I tried putting in the delay(200) remmed out in the Pascalite code above in case the response from the Pascalite was appearing before the main PC was looking... but it didn't help.

The following is the code behind a simple Delphi form with two buttons, two labels....
unit DD29u1;

(*DOESN'T WORK... but NEARLY does.... I think!*)

(*Yes! I know this could be more elegant! Furthermore, it is "badly written"
in a number of respects. In particular, it is not very robust and fails to
incorporate sundry available error detection and handling provisions of
Windows. Improve it... without obscuring the basic things it is trying to
illustrate... and send me the improved version!!*)

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls;

type
  TDD29F1 = class(TForm)
    buSendf: TButton;
    buSendx: TButton;
    Label1: TLabel;
    Label2: TLabel;
    procedure buSendfClick(Sender: TObject);
    procedure buSendxClick(Sender: TObject);
    procedure SendChar(sToSend:string);
    procedure EstablishHandle;
    function ReadStringFrmRS232:string;
    procedure FormCreate(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  DD29F1: TDD29F1;
  sPhraseToSend:string;
  hCommFile:THandle;
  c1:byte;

const ver='18 Apr 03';
CommPort = 'COM1';

implementation

{$R *.DFM}

function TDD29F1.ReadStringFrmRS232:string;
(*hCommFile must be valid before this is called*)
var sTmp:string;
    c1:integer;
    chBuffer:array[0..255] of char;
    NumberOfBytesRead : dword;

begin
if hCommFile=INVALID_HANDLE_VALUE then exit;

if not ReadFile (hCommFile, chBuffer, sizeof(chBuffer),
              NumberOfBytesRead, Nil) then
               showmessage('Problem with ReadStringFrmRS232')
               { Better: Raise an exception } ; { <- semicolon IS needed here!}

{The program will go off and watch the RS-232 data source
 for incoming data. It will continue to watch (and collect
 data as it appears) for a time determined by the CommTimeouts
 settings... thus you dont need to know in advance how many
 bytes will be coming, nor does the connected device need
 to send a flag marking the last byte.... though that
 protocol could be incorporated on top of the other
 provisions for signalling the end of the data.}

label2.caption:='Bytes seen: '+inttostr(NumberOfBytesRead - 1);
for c1:= 0 to NumberOfBytesRead - 1 do
      sTmp:= sTmp+chBuffer[c1];

result:=sTmp;
end;

procedure TDD29F1.SendChar(sToSend:string);
(*hCommFile must be valid before this is called*)
var NumberWritten:dword;
begin
if hCommFile = INVALID_HANDLE_VALUE then
        showmessage('Problem with SendChar')
        {Better: raise an exception } ; (* <- semi colon IS needed here*)
WriteFile(hCommFile,
            PChar(sToSend)^,
            Length(sToSend),
            NumberWritten,
            nil);
end;

procedure TDD29F1.EstablishHandle;
var CommTimeouts : TCommTimeouts;
begin
hCommFile := CreateFile(PChar(CommPort),
                          GENERIC_READ or GENERIC_WRITE,
                          0,
                          nil,
                          OPEN_EXISTING,
                          FILE_ATTRIBUTE_NORMAL,
                          0); {Delphi 2 helpfile said use "NULL" here,
                                and program compiled, but raised error when
                                it was run. Two references said use 0}
{Ever been frustrated by something that would not work when it
"should"? I spent about an hour tinkering with this program
with "generic_write or generic_write" (WRITE twice instead of
one read, one write) in the parameters above.....
ARGHH!!, as Snoopy would say.}
with CommTimeouts do
begin
  {put cursor on 'CommTimeouts', press f1 to learn
  about what the following specify. The numbers here
  are crude, and need consideration.... though they
  MAY work for yoyu in your situation! The 'right'
  numbers depend on many things, including the settings
  in the device you are communicating with. If the
  numbers are too small, the Delphi program will not
  "see" the attached device. If they are too large,
  the program will appear to hang while communicating....
  If too large, they may also interfere with Windows'
  proper functioning.... but may not.}
  ReadIntervalTimeout := 0;
  ReadTotalTimeoutMultiplier := 0;
  ReadTotalTimeoutConstant := 1500;
  WriteTotalTimeoutMultiplier := 0;
  WriteTotalTimeoutConstant := 500;
end; {with}

if not SetCommTimeouts(hCommFile, CommTimeouts) then
       showmessage('Problem with SetCommTimeouts')
       { Better: raise an exception }  ; (* <- semi colon IS needed here*)

end;

procedure TDD29F1.buSendfClick(Sender: TObject);
begin
sPhraseToSend:='f';
EstablishHandle;
for c1:=1 to length(sPhraseToSend) do begin
  SendChar(copy(sPhraseToSend,c1,1));
  application.processmessages;
  sleep(10);
  end;
label1.caption:='hardcoded f'{ReadStringFrmRS232};
CloseHandle(hCommFile);
end;

procedure TDD29F1.buSendxClick(Sender: TObject);
begin
sPhraseToSend:='x';
EstablishHandle;
for c1:=1 to length(sPhraseToSend) do begin
  SendChar(copy(sPhraseToSend,c1,1));
  application.processmessages;
  sleep(10);
  end;
CloseHandle(hCommFile);  {Probably not necessary.... but was trying....}
EstablishHandle;            {.... things in desperation!}
label1.caption:=ReadStringFrmRS232;
CloseHandle(hCommFile);
end;

procedure TDD29F1.FormCreate(Sender: TObject);
begin
caption:='DD29, sheepdogsoftware.co.uk, '+ver;
end;

end.


Sorry to have to leave this "half-baked". But! In 2010, I figured out what the problem was. Instead of re-writing the above, I did a new tutorial from scratch, which you may want to visit: How to do bi-directional communications from a Windows machine over a serial link.

If you want to wade through an untidy collection of various things I harvested from the web in the work leading up to this tutorial, you'll find it at my collection of serial i/o information.

REMINDER: (I know I said this at the start, but for anyone who missed that...) I wrote this tutorial in April of 2003. In April of 2010, I attacked the same project again. I would advise you to go to the newer tutorial about talking to things over a serial cable from a Windows computer
            powered by FreeFind
  Site search Web search
Site Map    What's New    Search This search merely looks for the words you enter. It won't answer "Where can I download InpOut32?"

Click here if you're feeling kind! (Promotes my site via "Top100Borland")


If you visit 1&1's site from here, it helps me. They host my website, and I wouldn't put this link up for them if I wasn't happy with their service. They offer things for the beginner and the corporation.www.1and1.com icon

Ad from page's editor: Yes.. I do enjoy compiling these things for you... hope they are helpful. However.. this doesn't pay my bills!!! If you find this stuff useful, (and you run an MS-DOS or Windows PC) please visit my freeware and shareware page, download something, and circulate it for me? Links on your page to this page would also be appreciated!
Click here to visit editor's freeware, shareware page.

Link to Tutorials main page
How to email or write this page's editor, Tom Boyd

Valid HTML 4.01 Transitional Page WILL BE tested for compliance with INDUSTRY (not MS-only) standards, using the free, publicly accessible validator at validator.w3.org


If this page causes a script to run, why? Because of things like Google panels, and the code for the search button. Why do I mention scripts? Be sure you know all you need to about spyware.

....... P a g e . . . E n d s .....