Autor Beitrag
Kay E.
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 118



BeitragVerfasst: Mo 07.02.11 17:33 
Hallo zusammen!

Ich muss einen Druckmesscontroller über RS232 ansteuern. Dazu nutze ich die TComport-Bibliothek.
Die Spezifikationen für das Gerät sind: 9600 Baud, Odd Parity, 7 Datenbits, 1 Stopbit, Befehle werden mit #13 abgeschlossen.
In einem ersten Testprogramm habe ich die TComport Komponente benutzt, Was auch ganz gut funktioniert hat, also das Gerät hat reagiert.
Mit folgendem Code habe ich die ID des Geräts erfahren (Id besteht aus einer 14 stelligen Buchstaben-Zahlen-Kombi):

ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
procedure TForm1.SendButtonClick(Sender: TObject);
var
  str, str2: string;
  int: Integer;
begin
  Comport.ClearBuffer(true, true);
  if SendEdit.Text <> '' then
  begin
    str := SendEdit.Text + #13;
    ComPort.WriteStr(str);
  end;
  sleep(50);
  int := ComPort.Readstr(str2, 32);
  memo1.Lines.Add('Gelesene Zeichen: ' + inttostr(int));
  memo1.Lines.Add(str2);
end;


Ich bekomme damit die richtige ID zurückgeliefert. Hier ist das erste Problem, und zwar ist das sleep(50) sehr unsauber. Nutze ich aber stattdessen die Timeouts, funktioniert es nicht mehr. Wenn ich ReadInterval auf 10 setze und die anderen beiden auf 0, liefert mir Readstr 14 eingelesene Zeichen zurück, aber alle Zeichen sind #0. Wenn ich Intervall auf 0 setze und Constant auf 1000, multiplier auf 100, dann wartet er seine etwa 4 Sekunden und liefert dann einmal 6 Zeichen zurück, das nächste mal 12, etc, aber auch wieder nur #0. Kombiniere ich beide, kommen 14 Zeichen, aber wieder nur #0. Es funktioniert also nur im Standartmodus (Interval -1, Rest 0) mit Sleep. Das kanns doch aber nicht sein, oder?


Beim zweiten Problem will ich die Comport-Komponente in ein Objekt kapseln, damit ich sie bequem zwischen Formularen hin und her reichen kann. Ich bin mir nicht sicher, ob die Klasse korrekt aufgebaut ist, also wenn da mal einer generell drüber schauen könnte, wär das super.
Aber: Die Funktion GetID, die im Prinzip gleich aufgebaut ist wie die oben geschilderte, liefert in der Form gar nichts zurück. Woran liegt das?

ausblenden volle Höhe 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:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
unit PressureControl;

interface

uses SysUtils, Classes, CPort, CPortCtl;

type
  TPressureControl = class(TObject)
    ComPort: TComPort;
  private

  public
    constructor Create(Port: TPort; Baud: TBaudRate; Parity: TParityBits;
      AOwner: TComponent);
    destructor Destroy; override;
    function GetID: string;
    function IsConnected: Boolean;
    function SetBaud(Baud: TBaudRate): Boolean;
    function SetPort(Port: TPort): TPort;
    function SetParity(Parity: TParityBits): Boolean;
  end;

implementation

{ TPressureControl }

constructor TPressureControl.Create(Port: TPort; Baud: TBaudRate;
  Parity: TParityBits; AOwner: TComponent);
begin
  inherited Create;
  Comport := TComport.Create(AOwner);
  SetBaud(Baud);
  Comport.DataBits := dbSeven;
  Comport.FlowControl.FlowControl := fcNone;
  SetParity(Parity);
  SetPort(Port);
  Comport.StopBits := sbOneStopBit;
  Comport.Open;
end;

destructor TPressureControl.Destroy;
begin
  if Assigned(Comport) then
    ComPort.Free;
  inherited Destroy;
end;

function TPressureControl.GetID: string;
var
  str: string;
  vInt: Integer;
begin
  Comport.ClearBuffer(true, true);
  ComPort.WriteStr('ID' + #13);
  sleep(50);
  vint := ComPort.Readstr(str, 32);
  result := 'Gelesene Zeichen: ' + inttostr(vInt) + '; ' + str;
end;


Aufruf in der Main:
ausblenden Delphi-Quelltext
1:
PC := TPressureControl.Create('Com1', br9600, prOdd, self);					



Wäre super, wenn mir jemand helfen könnte. Ich versteh das hinten und vorne nicht, warum sich die Codes so verhalten, wie sie es tun...
Danke schonmal für eure Zeit!

Grüße Kay
jaenicke
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 19312
Erhaltene Danke: 1747

W11 x64 (Chrome, Edge)
Delphi 11 Pro, Oxygene, C# (VS 2022), JS/HTML, Java (NB), PHP, Lazarus
BeitragVerfasst: Mo 07.02.11 17:42 
Also ganz grundsätzlich folgendes:
Sleep und dann lesen ist meistens keine gute Idee. Benutze besser die Events oder lagere das ganze in Threads aus, die dann richtig warten können.

Mehr kann ich nachher nach der Arbeit schreiben...
bummi
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 1248
Erhaltene Danke: 187

XP - Server 2008R2
D2 - Delphi XE
BeitragVerfasst: Mo 07.02.11 18:18 
Ich häng Dir mal Beispiele bestehender Implementierungen an, bin leider etwas im Zeitdruck.
Einloggen, um Attachments anzusehen!
_________________
Das Problem liegt üblicherweise zwischen den Ohren H₂♂
DRY DRY KISS
Kay E. Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 118



BeitragVerfasst: Mo 07.02.11 18:39 
Ohne jetzt die Beispiele von bummi angesehen zu haben, müsste nach meiner neuesten Theorie der Fehler am Readstr liegen. Wenn er jedes Byte, das er einliesst als string sieht, und die strings unter Windows ja alle #0-terminiert sind, dann könnte es doch sein, dass nur Platz für 1 Byte im Buffer ist und das #0 dann das bestehende zeichen überschreibt, was in einer Kette von #0 resultieren würde. Was haltet ihr von der Theorie?

Um das mal zu testen hab ich statt Readstr die Read-fkt genommen. Dabei passiert etwas sehr dubioses (Timeouts eingestellt, Intervall 20, Rest 0).
Ursprünglicher Code:
ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
procedure TForm1.SendButtonClick(Sender: TObject);
var
  str: string;
  str2: string;
  vint: Integer;
begin
  Comport.ClearBuffer(true, true);
  if SendEdit.Text <> '' then
  Comport.ClearBuffer(True, True);
  begin
    str := SendEdit.Text + #13;
    ComPort.WriteStr(str);
  end;
  //sleep(50);
  vint := 0;
  vint := ComPort.Readstr(str2, 32);
  memo1.Lines.Add('Gelesene Zeichen: ' + inttostr(vint));
  memo1.Lines.Add(str2);
end;

Debugger zeigt: vint = 14; str = #0#0#0#0#0#0#0#0#0#0#0#0#0#0


Ändere ich jetzt ReadStr in Read und setze bei Add ein Typecast auf Char, kommt in der Zeile davor (also bei inttostr(vint)) eine AccessViolation - Lesen von Adresse!
Debugger zeigt: vInt = 14; str = "Nicht verfügbarer Wert"

Irgendwas läuft doch da so gar nicht rund...
So, und jetzt die Beispiele
jaenicke
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 19312
Erhaltene Danke: 1747

W11 x64 (Chrome, Edge)
Delphi 11 Pro, Oxygene, C# (VS 2022), JS/HTML, Java (NB), PHP, Lazarus
BeitragVerfasst: Mo 07.02.11 18:49 
Wie gesagt, ich rate wirklich dazu es über die Events zu machen. Da bekommst du Bescheid sobald wirklich Zeichen da sind. Ganz bequem.

Oder im Thread mit WaitForMultipleObjects, ReadFile, ... (so habe ich es das letzte Mal gemacht, aber den Code kann ich nicht einfach posten, da der von der Arbeit stammt).
Kay E. Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 118



BeitragVerfasst: Mo 07.02.11 19:31 
Naja, das mit den Events wird meinem verständnis nach nicht funktionieren.
Ich erzeug den Comport in Form1, geb ihn an Form2, Form2 erzeugt Thread, Comport wird an Thread übergeben, Thread beendet, Comport wird an Form3 übergeben und da geschlossen.
Der meiste Zugriff wird im Thread stattfinden. Da sieht der pseudocode in etwa so aus:

ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
procedure StartProcedure;
begin
  for i := 0 to Count -1 do
  begin
    OpenValve(x);
    sleep(ValveTime);
    CloseValve(x);
    if PressureControl.GetPressure >= Grenzwert then  //Liesst den aktuellen Druck  aus dem Controller aus
    begin
      raise Exception;
    end;
  end;
end;


Ich wüsste jetzt nicht, wie ich das mit nem Event lösen soll. Das müsste ja dann sowas in der Art sein:

ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
procedure StartProcedure;
begin
  for i := 0 to Count -1 do
  begin
    OpenValve(x);
    sleep(ValveTime);
    CloseValve(x);
    pressureController.WriteSyntax('AV1'); //Befehl für Druckmessung
    Application.ProcessMessages;  
    if FPressureValue >= Grenzwert then  //Das Event schreibt den erhaltenen Wert in das Feld
      raise Exception;
  end;
end;


Aber das mit Application.ProcessMessages kann so ja auch nicht funktionieren. Außerdem sieht mir das irgendwie sehr... unelegant aus.
Ich lass mich gern eines besseren Belehren, aber ich halte Events (oder auch noch nen Thread) für nicht unbedingt praktikabel.

Grüße Bloody
jaenicke
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 19312
Erhaltene Danke: 1747

W11 x64 (Chrome, Edge)
Delphi 11 Pro, Oxygene, C# (VS 2022), JS/HTML, Java (NB), PHP, Lazarus
BeitragVerfasst: Mo 07.02.11 22:54 
Das Problem hier ist, dass du die Behandlung nicht als Reaktion auf das Event durchführst, sondern stattdessen in der ursprünglichen Prozedur wartest. So funktioniert Windows aber eben nicht. Windows ist ereignis- bzw. botschaftsbasiert.

Das heißt du bekommst die Nachricht, dass die Antwort am Com Port angekommen ist, und reagierst dann darauf. Der Code, der dann daraufhin ausgeführt werden soll, gehört dann auch direkt dorthin. Und eben nicht nur die Speicherung des Ergebnisses, auf das du anderweitig wartest.
Kay E. Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 118



BeitragVerfasst: Mo 07.02.11 23:56 
Das hilft mir aber nicht weiter. Das ganze ist ein sehr lineares Problem. So wie du es mir vorschlägst, soll ich in etwa folgendes machen:

ausblenden 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:
procedure Rumpf;
begin
  for i := 0 to Count do
  begin
    ErsterTeil;
  end;
end;

procedure ErsterTeil;
begin
  OpenValve(x);
  if not Druckmnessung then
    ZweiterTeil
  else
    PressureControl.WriteSyntax('Blubb');
end;

procedure ZweiterTeil;
begin
  CloseValve(x);
  WeitererCode;
end;

procedure PressureControl.OnRxirgendwas;
begin
  str := AlgoZumStringZusammenbauenStehtHier;
  if strtoint(str) >= Grenzwert then
    raise Exception
  else ZweiterTeil;
end;


Das wäre das einzige, was mir jetzt mein Problem lösen würde mit Events - jedenfalls so, wie es mir einfällt. Und schön ist anders in meinen Augen. Vor allem, dass das Programm abhängig ist von dem Event, sofern man die Druckprüfung macht. Ist zum Beispiel der Controller abgeschalten, so wartet mein Programm bis zum St. Nimmerleinstag.
Und um ehrlich zu sein, fehlt mir auch die Zeit, nochmal das komplette Konzept meines Programms zu überdenken, da die Uni meine DA irgendwann haben möchte. Ich sitze hier also ein wenig auf Kohlen. Es wäre also super, wenn sich jemand meiner Probleme im obigen Code annehmen würde oder anderweitig konstruktiv sein würde.

Vielen Dank schonmal!
hathor
Ehemaliges Mitglied
Erhaltene Danke: 1



BeitragVerfasst: Di 08.02.11 08:41 
Zitat:
user profile iconKay E....oder anderweitig konstruktiv sein würde.

Vielen Dank schonmal!


Vor einigen Tagen habe ich mir einen HEAD TRACKER programmiert und die Empfangsroutine (4 Bytes ca. 50x/sec lesen) in eine DLL gepackt. Die Daten werden in ein Shared Memory geschrieben. Darauf kann man mit beliebigen (eigenen!) Programmen zugreifen...
Kay E. Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 118



BeitragVerfasst: Di 08.02.11 18:53 
Hab das Problem gelöst, indem ich "Read" statt "ReadStr" benutze. Als Variable übergebe ich ein array of AnsiChars, was ich später in einen string wandle. Hier der Code:

ausblenden volle Höhe 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:
type
  TData = array[0..cBufferSize] of AnsiChar;

...
private
  FData: TData;
...

function TPressureControl.ReceiveData: string;
var
  vStr: string;
begin
  result := '';
  FillChar(FData, SizeOf(FData), #0);                  //Füllt Array mit Nullbytes zur bessern Umwandlung später
  FLastReadBytes := ComPort.Read(FData, cBufferSize);  //Daten werden mit Intervall-Timeout gelesen (s.u.)
  FData[FLastReadBytes - 1] := #0;                     //gerät schließt Msg mit #13 ab, wird umgewandelt in NullByte
  vStr := string(FData);                               //umwandlung in String
  if FLastReadBytes > 1 then                           //Exceptionhandling
  begin
    if vStr[1] = '#' then
    begin
      GenerateError(vStr);
    end;
  end;
  result := vStr;
end;

constructor TPressureControl.Create(Port: TPort; Baud: TBaudRate;
  Parity: TParityBits; AOwner: TComponent);
begin
  inherited Create;
  Comport := TComport.Create(AOwner);
  SetBaud(Baud);
  Comport.DataBits := dbSeven;
  Comport.FlowControl.FlowControl := fcNone;
  SetParity(Parity);
  SetPort(Port);
  Comport.StopBits := sbOneStopBit;
  Comport.Timeouts.ReadInterval := 20;             //Setze das Intervall zwischen zwei Bytes auf 20ms
  Comport.Timeouts.ReadTotalConstant := 500;       //Setze Gesamttimeout auf 500ms
  Comport.Open;
end;


Vielleicht hilfts ja noch dem einen oder anderen, der das hier auch liesst.

Grüße Kay
jaenicke
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 19312
Erhaltene Danke: 1747

W11 x64 (Chrome, Edge)
Delphi 11 Pro, Oxygene, C# (VS 2022), JS/HTML, Java (NB), PHP, Lazarus
BeitragVerfasst: Di 08.02.11 20:23 
Wo ich das mit den AnsiChars sehe: Kann es sein, dass du die alte TComPort Komponente, aber Delphi 2009 oder höher nutzt?

Dann ist das nämlich schlicht ein Unicodeproblem gewesen. ;-)
Es gibt extra eine neue TComPort Komponente für die neuen Delphiversionen.