Autor |
Beitrag |
Kay E.
      
Beiträge: 118
|
Verfasst: 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):
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?
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
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:
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
      
Beiträge: 19312
Erhaltene Danke: 1747
W11 x64 (Chrome, Edge)
Delphi 11 Pro, Oxygene, C# (VS 2022), JS/HTML, Java (NB), PHP, Lazarus
|
Verfasst: 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
      
Beiträge: 1248
Erhaltene Danke: 187
XP - Server 2008R2
D2 - Delphi XE
|
Verfasst: 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. 
      
Beiträge: 118
|
Verfasst: 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:
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; 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
      
Beiträge: 19312
Erhaltene Danke: 1747
W11 x64 (Chrome, Edge)
Delphi 11 Pro, Oxygene, C# (VS 2022), JS/HTML, Java (NB), PHP, Lazarus
|
Verfasst: 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. 
      
Beiträge: 118
|
Verfasst: 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:
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 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:
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'); Application.ProcessMessages; if FPressureValue >= Grenzwert then 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
      
Beiträge: 19312
Erhaltene Danke: 1747
W11 x64 (Chrome, Edge)
Delphi 11 Pro, Oxygene, C# (VS 2022), JS/HTML, Java (NB), PHP, Lazarus
|
Verfasst: 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. 
      
Beiträge: 118
|
Verfasst: 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:
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
|
Verfasst: Di 08.02.11 08:41
Zitat: | Kay 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. 
      
Beiträge: 118
|
Verfasst: 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:
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); FLastReadBytes := ComPort.Read(FData, cBufferSize); FData[FLastReadBytes - 1] := #0; vStr := string(FData); if FLastReadBytes > 1 then 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; Comport.Timeouts.ReadTotalConstant := 500; Comport.Open; end; |
Vielleicht hilfts ja noch dem einen oder anderen, der das hier auch liesst.
Grüße Kay
|
|
jaenicke
      
Beiträge: 19312
Erhaltene Danke: 1747
W11 x64 (Chrome, Edge)
Delphi 11 Pro, Oxygene, C# (VS 2022), JS/HTML, Java (NB), PHP, Lazarus
|
Verfasst: 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.
|
|
|