Entwickler-Ecke

Delphi Tutorials - Serielle Schnittstelle ansprechen und pollen


SchelmVomElm - Do 03.02.05 17:26
Titel: Serielle Schnittstelle ansprechen und pollen
Serielle Schnittstelle ansprechen und pollen

Da ich bisher eigentlich nur Fragen gestellt habe, wollte auch mal was für die Allgemeinheit tun…

Da ich viel Erfahrungen mit seriellen Schnittstellen gemacht habe, hier ein kleines, Crashkurs- artiges Tutorial. Gewisse Kenntnisse über die RS232 und Delphi bzw. API Programmierung setze ich voraus.

1. Benutzte GlobaleVariablen


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
var
  PortTimeout : _COMMTIMEOUTS;
  PortHandle : Integer;
  PortDCB : TDCB;
  PortNr  : Integer;
  PortState : Cardinal;
  WriteOverlapped,ReadOverlapped,StatusOs: TOverlapped;


2. Die Schnittstelle öffnen / schließen / testen

Um die Schnittstelle zu öffnen und ein gültiges Schnittstellen- Handle zu bekommen, ruft man die Funktion CreateFile auf. Vereinfachtes Bsp:


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
function OpenCOM(Port: byte): boolean; stdcall;
begin
  PortHandle :=
  CreateFile(PChar('\\.\COM'+IntToStr(Port)),GENERIC_READ or GENERIC_WRITE,0,
             nil,OPEN_EXISTING,FILE_FLAG_OVERLAPPED,LongInt(0));
  
  //Overlapped = Asynchron auf Microsoftianisch
 
  {Da wir nun asynchron auf die Schnittstelle zugreifen (weiss der Teufel 
  warum – aber es sieht so aus als wäre diese Art des Zugriffs ab W2K
  praktisch ZWINGEND notwendig – selbst wenn man gar nicht asynchron übertragen will)  
  muss man auch die dafür notwendigen Structs initialisieren}
  
  if PortHandle > 0 then
  begin
    Result := true;
    InitOverlapped(WriteOverlapped);
    InitOverlapped(ReadOverlapped);
    InitOverlapped(StatusOs);
  end else Result := false;
end;


Die Ini Funktion für die Overlapped Strukturen:


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
procedure InitOverlapped(var Overlapped : TOverlapped);
begin
  Overlapped.Offset := 0;
  Overlapped.OffsetHigh := 0;
  Overlapped.Internal := 0;
  Overlapped.InternalHigh := 0;
  Overlapped.hEvent := CreateEvent(nil,True,False,'');
end;


Um die Schnittstelle wieder zu schließen (und es gibt X mistige Programm die das vergessen, was zur Folge hat, dass danach andere Programme nicht mehr auf die Schnittstelle zugreifen können) geht man wie folgt vor:


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
procedure CloseCOM; stdcall;
begin
  PurgeComm(PortHandle, PURGE_RXABORT or PURGE_RXCLEAR or PURGE_TXABORT or PURGE_TXCLEAR);
  SetCommMask(PortHandle,0); //unterbricht WaitCommEvent im Polling thread
  CloseHandle(PortHandle);
  PortHandle := 0;
end;


Bevor man jedoch die Schnittstelle öffnet, sollte man vielleicht auch testen, ob Sie denn verfügbar ist (oder ob die tolle Com-Konsole von Pipi Lüdi- Soft vergessen hat die Schnittstelle zu schließen). Dies mach ich ziemlich billig (Verbesserungsvorschläge jederzeit willkommen):


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
function ComAvailable(ComNr: byte): boolean; stdcall;
var
  TestHandle : integer;
begin
  TestHandle :=
  CreateFile(PChar('\\.\COM'+IntToStr(ComNr)),GENERIC_READ or GENERIC_WRITE,0,
             nil,OPEN_EXISTING,FILE_FLAG_OVERLAPPED,LongInt(0));
  if (TestHandle <= 0then
    Result := false
  else
  begin
    Result := true;
    CloseHandle(TestHandle);
  end;
end;



2. Die Schnittstelle konfigurieren:

Die Konfiguration der Schnittstelle erfolgt über den Device Control Block (PortDCB) siehe Windows.pas – TDCB. Mit dieser Struktur lassen sich Parameter wie Baudrate, Flusssteuerung, StopBits usw. setzen und zwar wie im folgenden Beispiel die Baudrate:


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
function SetBaudRate(baud: cardinal): boolean; stdcall;
begin
  GetCommState(PortHandle,PortDCB);
  PortDCB.BaudRate := baud;
  Result := SetCommState(PortHandle,PortDCB);
end;


Die Hilfe des MS-SDK gibt auch bereitwillig Auskunft zu den verwendeten Funktionen.


3. Abfragen und Setzen der Status Leitungen

Um CTS, DCD, DSR und Ring abzufragen, eignet sich die Funktion GetCommModemStatus.

Bsp:


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
var PortState: cardinal;

begin
GetCommModemStatus(PortHandle,PortState); //PortHandle erhält man mit CreateFile – kommt später
//Nun die Abfrage – Hier auf Clear to send
if ((Portstate and MS_CTS_ON) <> 0then Result := true
    else Result := false;


Um RTS, DTR, XOFF, XON oder einen Break zu setzen oder zurückzusetzen benutzt man die Funktion EscapeCommFunction:

Bsp. Setzen und zurücksetzen der DTR Leitung


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
procedure SetDTR; stdcall;
begin
  if (PortHandle <> 0then EscapeCommFunction(PortHandle,5);
end;

procedure ClearDTR; stdcall;
begin
  if (PortHandle <> 0then EscapeCommFunction(PortHandle,CLRDTR);
end;


In diesem Zusammenhang bedeutet XON/XOFF, dass ein Empfangen der entsprechenden XON/XOFF Chars simuliert wird. Ein Break ist, wenn ich die TX – Leitung dauerhaft auf 1 setze. ACHTUNG, ist ein Break gesetzt und ich versuche zu senden (mit WriteFile), wird das Programm eine sauberen Abgang hinlegen. Am Besten man lässt den Quatsch – braucht eh keiner.


4. Lesen und Schreiben

Jetzt wird’s endlich interessant. Zum Lesen und Schreiben verwendet man die Funktionen ReadFile und wie sollte es auch sonst sein, WriteFile. Eigentlich ist die Benutzung dieser Funktionen im Zusammenhang mit der seriellen Schnittstelle ganz einfach – wenn man ein Programmierbeispiel hat:


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
function SendString(Str: PChar): boolean; stdcall;
var
  written  : cardinal;
  tmpStr : string;
  i: LongBool;
begin
  if (PortHandle <> 0then
  begin
    tmpStr := string(Str);
    Result := not WriteFile(PortHandle,tmpStr[1],Length(tmpStr),written,@WriteOverlapped);
  end;
end;

function ReadString(var Buf: PChar): Cardinal; stdcall;
var
  StrBuf: string;
  Error,BytesRead: DWORD;
  Status: TComStat;
  RdSuccess: boolean;
begin
  BytesRead := 0;
  if (PortHandle <> -1then
  begin
    ClearCommError(PortHandle,Error,@Status);
    case Error of
      CE_BREAK,CE_DNS,CE_FRAME,CE_IOE,CE_MODE,CE_OOP,CE_OVERRUN,CE_PTO,CE_RXOVER,
      CE_RXPARITY,CE_TXFULL:
      begin
        BytesRead := 0;
      end
    else
      begin
        SetLength(StrBuf,Status.cbInQue);
        RdSuccess := ReadFile(PortHandle,StrBuf[1],Status.cbInQue,BytesRead,@ReadOverlapped);
        if not RdSuccess then BytesRead := 0;
      end;
    end;
  end;
  if (BytesRead > 0then
  begin
    Result := BytesRead;
    try
      StrPCopy(Buf,StrBuf);
    except
      Result := 0;
    end;
  end else Result := 0;
end;



An dieser Stelle eine Anmerkung:
Ich habe mir für den Schnittstellen Zugriff zunächst eine Delphi Unit und dann eine DLL geschrieben. Aus der stammen die ganzen Funktionen. Daher übergebe ich auch PChars. Ich bitte etwaige Unelegantheiten im Bezug auf PChar – string zu entschuldigen – die kommen durch das Hin- und Her. Um das ganze als Delphi Unit zu verwenden, sollte überall die Direktive stdcall entfernt werden.

Und nun *Trommelwirbel* das interessanteste:

5. Schnittstellen Polling

Möchte man benachrichtigt werden, wenn auf die Schnittstelle (von aussen) zugegriffen wird (Daten gesendet oder Statusleitungen gesetzt werden), so musste man in grauer Vorzeit die Schnittstelle manuell zyklisch abfragen (Polling). Dies kann man heute sehr elegant mit einem Thread erledigen. Das ganze funktioniert im Groben so:
Man erstelle einen Thread, man halte in an mit der Funktion WaitCommEvent. Der Thread ist nun angehalten und zwar bis eines der Ereignisse eingetreten ist, die man vorher mit SetCommMask spezifiziert hat. Wird der Thread fortgesetzt ist eines der Ereignisse eingetreten. Will man den Thread von „aussen“ aus dem Wartezustand befreien macht man das so:


Delphi-Quelltext
1:
SetCommMask(PortHandle,0);                    


Hier nun ein Bsp. Für einen Polling- Thread. In diesem Beispiel wird, wenn ein Ereignis eintritt, die Funktion ReleaseEvent aufgerufen, die ich hier weglasse, da sie zu speziell wäre.


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
type
  TSerialThread = class(TThread)
  protected
    fComEvent: cardinal;
    procedure Execute; override;
  public
    constructor Create;
    property ComEvent: cardinal read fComEvent;
  end;

constructor TSerialThread.Create;
begin
  FreeOnTerminate:= True;
  inherited Create(False);
end;

procedure TSerialThread.Execute;
var
  SerialEvent, WaitResult, BytesRead: Cardinal;
begin
  SetCommMask(PortHandle,EV_CTS or EV_BREAK or EV_DSR or
  EV_ERR or EV_RING or EV_RLSD or EV_RXCHAR or EV_RXFLAG or EV_TXEMPTY);
  if not WaitCommEvent(PortHandle,SerialEvent,@StatusOs) then
  begin
    if (GetLastError = ERROR_IO_PENDING) then
    begin
      WaitResult := WaitForSingleObject(StatusOs.hEvent,INFINITE);
      case WaitResult of
        WAIT_OBJECT_0:
        begin
          if GetOverlappedResult(PortHandle,StatusOs,BytesRead,false) then
            ReleaseEvent(SerialEvent);
        end;
      end;
    end;
  end else ReleaseEvent(SerialEvent);
end;


Dieser Thread stellt lediglich die Mindestanforderung an einen Polling thread dar. Für den Hausgebrauch (und mehr) sollte das reichen.

So, bevor ich mich noch mehr zu dem Thema auslasse, will ich erst mal sehen ob das irgendein Schwein interessiert.

MfG SchelmVomElm


jasocul - Do 03.02.05 17:31
Titel: Serielle Schnittstelle ansprechen und pollen
Vor 6 Jahren hätte ich das gebrauchen können :wink:


digi_c - Mo 07.02.05 21:12

Danke füt den Artikel, was mich noch interessieren würde, wäre das Timing unter Windows. Der normale Timer ist ja sehr ungenau, reicht es da für periodische Messungen z.B. TcTimer(Multimediatimer) zu benutzen oder gibt es da andere Wege?

Achso und Elektor hat auch ne sehr nützliche DLL dazu geschrieben, die das alles Kapselt, Link such ich nochmal;)


SchelmVomElm - Di 08.02.05 10:28

Kenn ich schon 8) Läuft nicht unter W2K :?
Du kannst ja Die Funktionen der Elektor DLL mal mit
meinen vergleichen da wirst Du einige Unterschiede
feststellen....
Wieso Timing? Solange Du nur über RX/TX Zeichen austauscht
braucht Du eigentlich kein Timing - das ist der Sinn des
Polling threads. Anders sieht es aus wenn Du die Leitungen
selbst (bitweise) manipulierst um Daten zu übertragen.
Dann findest Du im Forum glaub ich jede Menge zum Thema Timer.

PS Du hast mit diesen Methoden keine Kontrolle darüber, wann
genau ein Zeichen physikalisch über die Schnittstelle
gesendet oder empfangen wurde.


digi_c - Di 08.02.05 20:13

Doch die haben eine neue wo in 2 Heften der WinAPI COMM Zugriff erklärt wird, ich fin den Link schon noch ...;)


Pkan - So 27.03.05 14:28

Hi
:wink: super turial
aber ich hab ein riesen Problem.
Ich will meine Rs232 schnittstelle ganz einfach steuern.
die 3 sendeleitungen gaanz einfach an oder aus sachalten (zur Pumpensterung)
und die 4 empfamsleitungen einfach nur auslesen, spannung oder keine ( an oder aus)
ich habs versucht aber ich komm einfach nicht dahinter wie ich das richtig mache.
Wenn mir einer helfen könnte oder einen Vorschlag machen könnte.....
Danke :D


hedie - So 27.03.05 18:02

Klar Doch

wen du Den LPT Port benutzen würdest, dann wäre gleich alles viel einfacher (für mich :D )
fals du dich deshalb entschliesst den LPT Zu benutzen , dann kann ich dir helfen (wirklich ganz einfach gesteuert 8) )


Pkan - So 27.03.05 18:09

für dich einfacher?? :D
neija...hast du schon ein programm??
wenns wirklich viel einfacher ist nehm ich den und bau den stecker um
Danke :)


hedie - So 27.03.05 18:12

Klar war heute Fleissig am Proggen 8)
wenn du willst, kann ich dir eines meiner programme geben.

dazu musst du mir nur sagen, welche der leitungen du verwenden willst und welcher LPT (LPT1, LPT2 ....)


Pkan - So 27.03.05 18:19

loooooooooooool mein HIGH END PC besitz nur eine Druckerschnittstelle
^^du bist elktroniler?? is ja der hammer ich auch....und das kann ich wesentlcih besser als delphi... :) hast du icq??
:)


hedie - So 27.03.05 18:21

nein :( aber MSN :D
Elektroniker mit Leidenschaft :D

dein Progg ist gleich fertig 8)


Pkan - So 27.03.05 18:26

goil....
^^bin gerade auf deiner homepage.
Ich hab auch son ritig goile Projekte gemacht...
neija ich müsste auch mal homepage machen..aber die zeit
vlt. erkennst du die extrmen vorteile von icq..dann können wir ja mal chatten :wink:
mit welchen delphi schreibst du das programm(vers??)
ich werde später noch timer und so dazu machen


hedie - So 27.03.05 18:31

Weshalb lädst du dir nicht msn (hat ton und sogar webcam übertragung :D )

also ich schreibe es in Delphi7
fals du nicht draus kommst, einfach fragen (im forum oder hedie2@tiscali.ch)

deine programm habe ich angehängt 8)

(freue mich über mails)


Pkan - So 27.03.05 18:40

Danke
jo ich hab auch delphi 7
^^ja webcam ist auch auch gut aber ich lass mich nit gern von msn ausspioniern..und bei meinen system ist das auch besser wenn onkel bil gates nicht alles weis :)
ich probier gleich mal es programm aus


Pkan - So 27.03.05 18:59

is ja alles ganz fein :D aber du steuerst ja die LPT an...
welchen pin schaltest du jetzt( hast du manchmal en plan con LPT port mit leitungsnamen(und welchen du ansteuerst))
muss mir erstmal en neuen stecker anlöten


hedie - So 27.03.05 19:03

besch du schwizer wen ja dan können wir ja so schreiben (einfacher)

und ich steuere den pin 4 an also D2 weil es auch noch d 0 gibt


Karlson - So 27.03.05 19:08

user profile iconhedie hat folgendes geschrieben:
besch du schwizer wen ja dan können wir ja so schreiben (einfacher)


Mag sein, aber das Forum hier ist Deutsch, folglich werdet ihr auch bitte Deutsch schreiben, schließlich sollen nicht-schweizer auch was verstehen.


Pkan - So 27.03.05 19:10

gut...Nee bin ich net auch wenn meine rechtschreibung/schrift manchmal wie "hinter chinesisch aussieht...ich komm ausn :!: OSTEN :!:
auch sachsen..
neija müssen uns unbedingt mal über unsere elkto projekte unterhalten :D


hedie - So 27.03.05 19:11

Klar machen wir freu mich schon :D


SchelmVomElm - Mi 30.03.05 10:12

@Hedie: Ich habe mir mal deine Seite angeschaut. Bin auch leidenschaftlicher Elektroniker - und Elektor Leser. Ich wollte dich nur warnen :!: - ich glaube nicht dass es Elektor toll findet dass Du Artikel von denen veröffentlichst. Die Dinger sind kostenpflichtig - Download 1,50 €. Da kannst Du 'nen mords Ärger kriegen...


hedie - Mi 30.03.05 13:25

NA GUT WERDE ES LÖSCHEN :cry: ich weis es übrigens schon :D


SchelmVomElm - Mi 30.03.05 16:16

:mrgreen: Hi, hi... mir ist das egal! Meinetwegen kannst Du die drauflassen - zumal der GoldCap- Akku ja echt cool ist... Naja sollte nur 'ne Warnung sein


hedie - Mi 30.03.05 16:19

na gut ich lass es drauf aber nichts sagen :D


Smartie - Mo 18.04.05 09:05

hej, super sache! ISt GENAU das was ich grad brauche!
Hätt ich schon letzte Woche finden können.

NUr ne "kleine" Frage

Wie mach ich des, wenn ich eine Datei, die ich auf der Festplatte gespeichert habe, versenden möchte, bzw. eine Datei die ankommt, auf der FEstplatte abspeichern will?

Lieben Gruß

Smartie


Pytho - Mi 25.05.05 10:21

Zitat:
Und nun *Trommelwirbel* das interessanteste:

5. Schnittstellen Polling

...

Hier nun ein Bsp. Für einen Polling- Thread. In diesem Beispiel wird, wenn ein Ereignis eintritt, die Funktion ReleaseEvent aufgerufen, die ich hier weglasse, da sie zu speziell wäre.


Nur leider findet man garade über eben diese Funktion ReleaseEvent so gut wie gar nichts im INet, schon gar nicht in Bezug auf Delphi. Bestenfalls soviel, dass sie wohl aus der WinAPI stammt.

@SchelmVomElm:
Hättest Du eine eigene Beschreibung oder einen brauchbaren Link dazu parat ? Aus Deiner eigenen Anwendung müsste doch sogar eine exemplarische Anwendung von ReleaseEvent() für diesen Zweck hier vorliegen, aus der man lernen könnte.

Gruss
Pytho


j-a-n@gmx.de - Fr 01.07.05 12:14
Titel: Re: Serielle Schnittstelle ansprechen und pollen
Hallo Schelm,
Danke für das gute Tutorial, ich kann das jetzt gut gebrauchen. <ot>Aber warum diskutiert Ihr hier über LPT und ICQ?</ot>

Ich habe da noch ein paar Fragen/Anmerkungen allgemein zur Seriellen Schnittstelle.

Auf der seriellen Schnittstelle kommen ja permanent Daten rein oder gar keine. Das bedeutet ja, dass wenn ich ihm sage "lese mir 5 Zeichen" und es kommen nur 3, dann wartet das ding bis mehr kommt, oder? Ich müsste also quasi permanent in einer Schleife lesen und auf ein definiertes Start- und End-Zeichen warten.

Das mit den Status-Leitungen habe ich nicht kapiert, wozu die da sind und vorallem was ich damit machen muss. Ich hoffe mal, Deine Functions machen das richtige damit. :-)

Du hat gesagt, du hättest das ganze in eine Unit/Klasse gepackt. Kann ich mir die irgendwo runterladen?

oink, oink, mich interessiert es brennend; lass dich gerne noch ein bischen mehr darüber aus.

Jan


Sputnik - Mo 19.06.06 15:24

Zitat:
5. Schnittstellen Polling

Möchte man benachrichtigt werden, wenn auf die Schnittstelle (von aussen) zugegriffen wird (Daten gesendet oder Statusleitungen gesetzt werden), so musste man in grauer Vorzeit die Schnittstelle manuell zyklisch abfragen (Polling). Dies kann man heute sehr elegant mit einem Thread erledigen. Das ganze funktioniert im Groben so:
Man erstelle einen Thread, man halte in an mit der Funktion WaitCommEvent. Der Thread ist nun angehalten und zwar bis eines der Ereignisse eingetreten ist, die man vorher mit SetCommMask spezifiziert hat.


Gibts sowas auch für den LPT? Also das ein Thread solange wartet bis sich an den Eingängen was tut?
(Polling ist zu ungenau und in einer Schleife abfragen = 100% Auslastung)


delphimike - Do 27.07.06 11:40

schöne Routine, bei mir scheitert alles daran, daß ich nicht weiß welche units ich einbinden muß um die Typendeklarationen unterzubringen....

Mike


SchelmVomElm - Do 18.10.07 14:42

Frequently Annoying Questions

1. Die Funktion ReleaseEvent, die in meinem Tutoial vorkommt ist keine API, Delphi, .net, C++ oder sonst was Funktion sondern von mir geschrieben.

2. Der Quelltext der Funktion ReleaseEvent wird von mir nicht zur Verfügung gestellt.
Ich wüßte auch nicht wozu ich das tun sollte, denn dies ist die Funktion mit der ich auf ein Schnittstellen Event reagiere. Und dies tu ich nicht immer gleich. Mal wird eine Message an ein Fenster gesendet, mal eine Callback Funktion aufgerufen und ein anderes mal sende ich meiner Großmutter ein eMail. Der geneigte User könnte in der Funktion den Schnittstellenpuffer auslesen (Mal so als Anregung...)

3. Der Schnittstellenpuffer wird mit ReadString ausgelesen.

4. Mit den beschriebenen Funktionen kann man nur die serielle Schnittstelle auslesen.

5. Diese Funktionen existieren nicht als Klasse - selbermachen!

Grüße, Schelm


rusty - Di 30.10.07 23:01

@ SchelmVomElm

Danke für Deinen Programmcode bezüglich der Ansteuerung der RS232-Schnittstelle. Ich bin Delphi-Anfänger und stolz das Programm gleich zum Laufen gebracht zu haben - außer das Pollen, aber da werde ich später mal danach sehen.

Aktuell habe ich das Problem, daß der String, den ich einlese zum Teil aus Steuerzeichen besteht. Wie kann ich den String in z.B. einen Array mit Integer-Werten umwandeln?
Danke.