Betreff: Re: Accessing serial ports with Delphi 3???
Datum: Wed, 26 Aug 1998 16:52:03 +0200
Von: Carl Smotricz
Firma: FRA AE-FPS/FDL
Foren: comp.lang.pascal.delphi.misc
Patrick Echterbruch wrote:
>
> Hi all!!
>
> Is there a way to gain access to the serial ports under Delphi 3 ?
>
> I need to write an application which reads data from and writes to e.g.
> COM1. I've not found any predefined components, nor were there informations
> in the Delphi online-help.
>
> I'd appreciate you sending me a Mail directly to Echterbruch@td-service.de,
> because i don't have the time to check the newsgroups frequently.
>
> Thanks in advance
>
> Patrick Echterbruch
Patrick,
I've recently written a simple Delphi project which makes extensive use
of serial communications. The customer was unwilling to pay and thus the
following code was rotting away in my garbage can until I came across
your post. The comments are in German but I don't think you will have
any problem with that! :)
This program was written for and ran successfully under D1. It
(probably) uses the 16 bit comms API, but ran successfully under NT 4.0
and would probably run under W95 and W98. Also, I am guessing these APIs
are still supported under D2/D3/D4.
Essentially, Delphi is making C-like calls to the Windows API, which
offers a number of functions for sending characters, checking the number
of input characters available and reading those characters. The program
does not do much else, so for simplicity the input here is polled. An
interrupt driven implementation would be more CPU efficient but more
complicated.
The following is not demo code but an actual running program (minus the
form). I've forgotten how it works, so please try to figure it out for
yourself. If you have questions remaining, contact me at
'carls@ipf.de'
and I can try to help you further.
re's,
Carl
---------- snip here -----------
unit DeusMain;
interface
uses
SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls,
Forms, Dialogs, StdCtrls, Buttons, Gauges, ExtCtrls, IniFiles,
DeusImg;
type
TfrmMain = class(TForm)
pnlMain: TPanel;
Balken: TGauge;
txtData1: TLabel;
txtData4: TLabel;
txtData2: TLabel;
txtData5: TLabel;
Timer1: TTimer;
txtData3: TLabel;
txtData6: TLabel;
pnlStatus: TPanel;
pnlStatusLabel: TPanel;
pnlStatusText: TPanel;
lblStatusText: TLabel;
pbxLed: TPaintBox;
lblTaktText: TLabel;
lblHeaderLinks: TLabel;
lblHeaderRechts: TLabel;
procedure Timer1Timer(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure FormShow(Sender: TObject);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
procedure pbxLedPaint(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
frmMain: TfrmMain;
implementation
{$R *.DFM}
const
CR = #13;
{ *** FEHLER *** }
const
FehlerAllesOK = 0;
FehlerTafelTimeout = 1;
FehlerTafelKaputt = 2;
FehlerHostTimeout = 4;
type
TFehler = class(TObject)
private
FStatus: Integer;
public
constructor Create;
procedure displayStatus;
procedure setTafelTimeout;
procedure setTafelKaputt;
procedure setTafelOK;
procedure setHostTimeout;
procedure setHostOK;
property Status: Integer read FStatus;
end;
{ *** HOST *** }
type
THostStatus = (HostOK, OpenFehler, HostTimeout);
THost = class(TObject)
private
FStatus: THostStatus;
procedure setStatus(st: THostStatus);
public
property Status: THostStatus read FStatus write setStatus;
end;
{ *** TAFEL *** }
type
TTafelStatus = (Gepollt, TafelTimeout, TafelOK, Tafelkaputt);
TTafel = class(TObject)
private
FStatus: TTafelStatus;
procedure setStatus(st: TTafelStatus);
public
Spalte1, Spalte2: String[12];
property Status: TTafelStatus read FStatus write setStatus;
procedure setActive(active: Boolean);
procedure setText(Sp1, Sp2: String);
end;
var
Host: THost;
Tafel: TTafel;
Fehler: TFehler;
SettingPort: Integer; { COMn }
SettingSpeed: LongInt; { Baud }
SettingInterval: Integer; { Sekunden }
SettingDateiname: String; { Eingabedateiname }
SettingStation: Byte; { Tafel-Stationsadresse }
Takt: Integer;
Zeit: Integer;
CommDCB: TDCB;
CommOffen: Boolean;
procedure SeriellZeichenAusgeben(Zeichen: char); forward;
{ --------------------------------------------------------
Allgemeine Routinen
-------------------------------------------------------- }
{ Das Verzeichnis des EXE-Files zurückgeben }
function ExeDir: String;
begin
ExeDir := ExtractFilePath(Application.ExeName);
end;
{ Daten umwandeln und auf COM-Port ausgeben }
procedure GibDatenAus(var Data1, Data2: String);
var
GewandelteDaten: String;
j: Integer;
begin
GewandelteDaten := Data1 + Data2;
for j := 1 to Length(GewandelteDaten) do
SeriellZeichenAusgeben(GewandelteDaten[j]);
end;
{ Die Daten einlesen }
procedure LiesVomHost;
label 1, 2, 3;
var
Datei: Text;
VollerDateiName: String;
OK: Boolean;
Zeile1, Zeile2: String;
begin
OK := false;
VollerDateiName := ExeDir + SettingDateiName;
if not FileExists(VollerDateiName) then goto 1;
{$I-}
AssignFile(Datei, VollerDateiName);
if IOResult <> 0 then goto 1;
Reset(Datei);
if IOResult <> 0 then goto 3;
if Eof(Datei) then goto 1;
Readln(Datei, Zeile1);
if IOResult <> 0 then goto 1;
if Eof(Datei) then goto 1;
Readln(Datei, Zeile2);
if IOResult <> 0 then goto 1;
{ Hier alles OK }
OK := true;
3: { Fehler-Recovery mit Close und Delete }
CloseFile(Datei);
if IOResult <> 0 then goto 2;
2: { Fehler-Recovery mit Delete }
DeleteFile(VollerDateiName);
if IOResult <> 0 then goto 1;
1: { Fehler-Recovery ohne Close oder Delete }
{$I+}
if not OK then begin
if Host.Status = HostOK then
Host.Status := OpenFehler
else
Host.Status := HostTimeout;
end else begin
{ Daten sind OK - jetzt fuer die Tafel aufbereiten }
Tafel.setText(Zeile1, Zeile2);
Host.Status := HostOK;
end;
end;
{ Ein Telegramm vom seriellen Port einlesen }
function SeriellEinlesen: String;
var
Stat: TComStat;
NChars: Word;
Buf: Array[0..200] of Char;
j: Integer;
InputStr: String;
begin
{ Versuche, Text vom Port zu bekommen }
InputStr := '';
GetCommError(CommDCB.ID, Stat);
NChars := Stat.cbInQue;
if NChars > 0 then begin
NChars := ReadComm(CommDCB.ID, Buf, 200);
if NChars > 0 then begin
for j := 1 to NChars do
InputStr := InputStr + Buf[j-1];
end; { if }
end; { if }
Result := InputStr;
end;
{ Die serielle Schnittstelle oeffnen }
function SeriellOeffnen: Boolean;
const { from DOC\WINTYPES.INT }
ie_BadID = (-1); { Invalid or unsupported id }
ie_Open = (-2); { Device Already Open }
ie_NoPen = (-3); { Device Not Open }
ie_Memory = (-4); { Unable to allocate queues }
ie_Default = (-5); { Error in default parameters }
ie_Hardware = (-10); { Hardware Not Present }
ie_ByteSize = (-11); { Illegal Byte Size }
ie_BaudRate = (-12); { Unsupported BaudRate }
var
CommStat: Integer;
PortName: Array[1..20] of char; { 'COM1' + #0; }
CommParms: Array[1..50] of char; { 'COM1:2400,N,8,1' + #0; }
begin
StrPCopy(@PortName, Format('COM%d', [SettingPort]));
StrPCopy(@CommParms, Format('COM%d:%d,N,8,1', [SettingPort,
SettingSpeed]));
{ Open COM port }
CommStat := OpenComm(@PortName, 1024, 1024);
if CommStat < 0 then begin
MessageDlg(
Format('Der Port %s konnte nicht geöffnet werden. ' +
'(Fehlerstatus: %d)' + CR + CR +
'Bitte wählen Sie einen anderen Port oder ' +
'beheben Sie ggfs. das Hardware-Problem!',
[StrPas(@PortName), CommStat]),
mtError, [mbOK], 0);
Result := False;
end else begin
CommStat := BuildCommDCB(@CommParms, CommDCB);
if CommStat < 0 then begin
MessageDlg(
Format('BuildCommDCB Error; String = "%s"', [CommParms]),
mtError, [mbOK], 0);
Result := False;
end;
CommStat := SetCommState(CommDCB);
if CommStat < 0 then begin
MessageDlg(
Format('SetCommState Error %d', [CommStat]),
mtError, [mbOK], 0);
Result := False;
end else begin
CommOffen := True;
Result := True;
end;
end;
end; { SeriellOeffnen }
{ Schnittstelle dicht }
procedure SeriellSchliessen;
begin
if CommDCB.ID <> $FF then
CloseComm(CommDCB.ID);
end;
{ Ein Zeichen aufs serielle Port ausgeben }
procedure SeriellZeichenAusgeben(Zeichen: char);
var
Stat: TComStat;
Buf: Array[0..0] of Char;
begin
GetCommError(CommDCB.ID, Stat);
Buf[0] := Zeichen;
WriteComm(CommDCB.ID, @Buf[0], 1);
end;
{ Takt 0:
Ggw. Status auf Display ausgeben, Tafel pollen. }
procedure Takt0;
begin
Fehler.displaystatus;
Zeit := Zeit + 1;
if Zeit > SettingInterval then
Zeit := 0;
frmMain.Balken.Progress := Zeit;
{ Pollen = Stationsadresse ausgeben mit High Bit }
SeriellZeichenAusgeben(Char($80 + SettingStation));
Tafel.Status := Gepollt;
end;
{ Takt 1:
Lies Daten von der Tafel ein.
Entweder Kurztelegramm (= OK)
oder Fehlertelegramm
oder nix }
procedure Takt1;
var
Telegramm: String;
begin
Telegramm := SeriellEinlesen;
if Telegramm = '' then begin
Tafel.Status := TafelTimeout;
Fehler.setTafelTimeout;
end else if Length(Telegramm) = 1 then begin
(* Kurztelegramm *)
Tafel.Status := TafelOK;
Fehler.setTafelOK;
end else begin
(* Fehlertelegramm auswerten *)
Tafel.Status := TafelKaputt;
Fehler.setTafelKaputt;
end;
end;
{ Takt 2:
1. Versuch, Daten vom Host zu lesen.
Dies passiert nur, wenn das Intervall genau fertig ist. }
procedure Takt2;
begin
if Zeit = SettingInterval then
LiesVomHost;
end;
{ Takt 3:
2. Versuch, Daten vom Host zu lesen.
Nur, wenn der 1. Versuch gescheitert war. }
procedure Takt3;
begin
if (Zeit = SettingInterval) and (Host.Status = OpenFehler) then
LiesVomHost;
end;
{ Takt 4:
Dummy-Read of Antwort vom Telegramm (sofern vorhanden) }
procedure Takt4;
begin
SeriellEinlesen;
end;
{ --------------------------------------------------------
Routine(n) des Objekts 'Fehler'
-------------------------------------------------------- }
constructor TFehler.Create;
begin
inherited Create;
FStatus := 0;
end;
procedure TFehler.displayStatus;
const
TempStatus: Integer = 0;
begin
if TempStatus = 0 then
TempStatus := Fehler.Status;
if (TempStatus and FehlerTafelTimeout) > 0 then begin
frmMain.lblStatusText.Caption := 'Keine Verbindung zur Tafel';
frmMain.lblStatusText.Color := clRed;
TempStatus := TempStatus and not FehlerTafelTimeout;
end else if (TempStatus and FehlerTafelKaputt) > 0 then begin
frmMain.lblStatusText.Caption := 'Tafel defekt';
frmMain.lblStatusText.Color := clRed;
TempStatus := TempStatus and not FehlerTafelKaputt;
end else if (TempStatus and FehlerHostTimeout) > 0 then begin
frmMain.lblStatusText.Caption := 'Keine Host-Daten';
frmMain.lblStatusText.Color := clRed;
TempStatus := TempStatus and not FehlerHostTimeout;
end else begin
frmMain.lblStatusText.Caption := 'OK';
frmMain.lblStatusText.Color := clLime;
end;
end;
procedure TFehler.setTafelTimeout;
begin
FStatus := FStatus or FehlerTafelTimeout;
end;
procedure TFehler.setTafelKaputt;
begin
FStatus := FStatus or FehlerTafelKaputt;
end;
procedure TFehler.setTafelOK;
begin
FStatus := FStatus and not (FehlerTafelTimeout or FehlerTafelKaputt);
end;
procedure TFehler.setHostTimeout;
begin
FStatus := FStatus or FehlerHostTimeout;
end;
procedure TFehler.setHostOK;
begin
FStatus := FStatus and not FehlerHostTimeout;
end;
{ --------------------------------------------------------
Routine(n) des Objekts 'Host'
-------------------------------------------------------- }
{ Ändert den Status des Hosts und sorgt
dafuer, daß das Display richtig anzeigt }
procedure THost.setStatus(st: THostStatus);
begin
if (FStatus <> st) then begin
FStatus := st;
(* Display richtig aufsetzen *)
case st of
HostTimeout: begin
Fehler.setHostTimeout;
Tafel.setActive(false);
end;
OpenFehler,
HostOK: begin
Fehler.setHostOK;
Tafel.setActive(true);
end;
end;
end;
end;
{ --------------------------------------------------------
Routine(n) des Objekts 'Tafel'
-------------------------------------------------------- }
{ Setzt den Tafeltext aktiv oder inaktiv }
procedure TTafel.setActive(active: Boolean);
var
Farbe: TColor;
begin
if active then begin
Farbe := clBlack;
end else begin
setText(' ', ' ');
Farbe := clGray;
end;
with frmMain do begin
txtData1.Color := Farbe;
txtData2.Color := Farbe;
txtData3.Color := Farbe;
txtData4.Color := Farbe;
txtData5.Color := Farbe;
txtData6.Color := Farbe;
end;
end;
{ Ändert den Status der Tafel und sorgt dafuer, daß die LED umschaltet }
procedure TTafel.setStatus(st: TTafelStatus);
begin
if (FStatus <> st) then begin
FStatus := st;
frmMain.pbxLed.invalidate;
end;
end;
{ Setzt die Tafel-Texte ins Display }
procedure TTafel.setText(Sp1, Sp2: String);
var
Telegramm: String;
j: Integer;
begin
Tafel.Spalte1 := Sp1;
Tafel.Spalte2 := Sp2;
{ Daten auf Bildschirm abbilden }
frmMain.txtData1.Caption := Copy(Sp1,2,4);
frmMain.txtData2.Caption := Copy(Sp1,6,4);
frmMain.txtData3.Caption := Copy(Sp1,10,4);
frmMain.txtData4.Caption := Copy(Sp2,2,4);
frmMain.txtData5.Caption := Copy(Sp2,6,4);
frmMain.txtData6.Caption := Copy(Sp2,10,4);
{ Daten als Telegramm für Tafel aufbereiten }
Telegramm := '';
(* formatieren!! *)
{ ausgeben }
for j := 1 to Length(Telegramm) do
SeriellZeichenAusgeben(Telegramm[j]);
end;
{ --------------------------------------------------------
Die folgenden Routinen haengen an Events, d.h. an
Windows-Ereignissen. Sie stehen hier in der Reihenfolge,
in der sie normalerweise aufgerufen werden.
-------------------------------------------------------- }
{ Wenn die Form erzeugt wird, d.h. beim Programmstart. }
procedure TfrmMain.FormCreate(Sender: TObject);
var
IniFile: TIniFile;
begin
IniFile := TIniFile.Create(ChangeFileExt(Application.ExeName,
'.INI'));
SettingPort := IniFile.ReadInteger('Comm', 'Port', 2);
SettingSpeed := IniFile.ReadInteger('Comm', 'Speed', 9600);
SettingStation := IniFile.ReadInteger('Comm', 'Station', 1);
SettingInterval := IniFile.ReadInteger('Data', 'Interval', 120);
Top := IniFile.ReadInteger('Window', 'Top', 0);
Left := IniFile.ReadInteger('Window', 'Left', 0);
WindowState := TWindowState(IniFile.ReadInteger('Window', 'State',
Ord(wsNORMAL)));
SettingDateiName := IniFile.ReadString('Data', 'File', 'DATEN.DAT');
lblHeaderLinks.Caption := IniFile.ReadString('Header', 'Links',
'TAFEL1');
lblHeaderRechts.Caption := IniFile.ReadString('Header', 'Rechts',
'TAFEL2');
IniFile.Free;
CommDCB.ID := $FF; { keine Schnittstelle aktiv }
Balken.MaxValue := SettingInterval;
end;
{ Wenn die Form gezeigt wird }
procedure TfrmMain.FormShow(Sender: TObject);
begin
if not SeriellOeffnen then begin
Close;
end;
end;
{ Dies passiert jede 1/5 Sekunde beim Tick des Timers }
procedure TfrmMain.Timer1Timer(Sender: TObject);
begin
Takt := (Takt + 1) mod 5;
lblTaktText.Caption := IntToStr(Takt);
case Takt of
0: Takt0;
1: Takt1;
2: Takt2;
3: Takt3;
4: Takt4;
end;
end;
{ Darstellung der LED }
procedure TfrmMain.pbxLedPaint(Sender: TObject);
procedure {internal} KopiereBild(nach: TPaintBox; von: TImage);
var
bgColor: TColor;
begin
bgColor := von.Canvas.Pixels[0,0];
nach.Canvas.BrushCopy(Rect(0,0,15,15), TBitmap(von.Picture.Graphic),
Rect(0,0,15,15), bgColor);
end;
begin
case Tafel.Status of
Gepollt: KopiereBild(frmMain.pbxLed, frmImages.imgLedAus);
TafelTimeout: KopiereBild(frmMain.pbxLed, frmImages.imgLedRot);
TafelOK: KopiereBild(frmMain.pbxLed, frmImages.imgLedGruen);
end;
end;
{ Beim Schliessen des Programms }
procedure TfrmMain.FormClose(Sender: TObject; var Action: TCloseAction);
var
IniFile: TIniFile;
begin
Timer1.enabled := False;
SeriellSchliessen;
IniFile := TIniFile.Create(ChangeFileExt(Application.ExeName,
'.INI'));
IniFile.WriteInteger('Comm', 'Port', SettingPort);
IniFile.WriteInteger('Comm', 'Speed', SettingSpeed);
IniFile.WriteInteger('Comm', 'Station', SettingStation);
IniFile.WriteInteger('Data', 'Interval', SettingInterval);
IniFile.WriteString ('Data', 'File', SettingDateiName);
IniFile.WriteInteger('Window', 'Top', Top);
IniFile.WriteInteger('Window', 'Left', Left);
IniFile.WriteInteger('Window', 'State', Ord(WindowState));
IniFile.WriteString ('Header', 'Links', lblHeaderLinks.Caption);
IniFile.WriteString ('Header', 'Rechts', lblHeaderRechts.Caption);
IniFile.Free;
end;
initialization
Tafel := TTafel.Create;
Tafel.Status := Gepollt;
Host := THost.Create;
Host.Status := HostOK;
Fehler := TFehler.Create;
end.
--------------------------------
--
If the price is right my programming skill is yours.
But unless otherwise stated, my opinions are my own.
               (
geocities.com/~franzglaser)