Fehlerbehandlung RS232
-
da ich mehrere Messgeräte über RS232 auslese habe ich eine Klasse für die Verbindung geschrieben. Das funktioniert auch so weit.
class SerialInterface { private: HANDLE hCom; void ShowLastError(AnsiString where); public: // Oeffnen der seriellen Schnittstelle bool OpenComm(char* port); // setzen der Einstellungen void SetDCB(unsigned int baudrate); // setzen der Timeout-Zeiten void SetReadTimeouts(int intervalTO, int readSingleTO, int writeSingleTO); // Daten senden int SendData(char Data[],int n); // Daten empfangen DWORD ReceiveData(char* Data,int n); // schließen der seriellen Schnittstelle void CloseComm(void); }; void SerialInterface::ShowLastError(AnsiString where) { MessageBox(NULL,SysErrorMessage(GetLastError()).c_str(), where.c_str(), MB_OK|MB_ICONERROR); } //--------------------------------------------------------------------------- // Öffnet den Port und gibt ein Handle zurück bool SerialInterface::OpenComm(char* port) { hCom = CreateFile(port,// z.B. "COM1", GENERIC_READ|GENERIC_WRITE,//zum Senden und Empfangen 0, // Für comm devices exclusive-access notwendig 0, // Keine security attributes OPEN_EXISTING, // Für comm devices notwendig 0, // Kein overlapped I/O 0); // Für comm devices muss hTemplate NULL sein if(hCom == INVALID_HANDLE_VALUE) { ShowLastError("CreateFile: " + AnsiString(port)); return false; } else return true; } //--------------------------------------------------------------------------- // setzen der Einstellungen void SerialInterface::SetDCB(unsigned int baudrate) { DCB dcb; // Device Control Block ZeroMemory(&dcb,sizeof(DCB)); dcb.DCBlength = sizeof(DCB); if (!GetCommState(hCom, &dcb)) ShowLastError("GetCommState"); BuildCommDCB(baudrate + ",N,8,1", &dcb); if (!SetCommState(hCom, &dcb)) ShowLastError("SetCommState"); } //--------------------------------------------------------------------------- // setzen der Timeout-Zeiten void SerialInterface::SetReadTimeouts(int intervalTO, int readSingleTO, int writeSingleTO) { COMMTIMEOUTS t; // Alle Wert in Millisekunden // Werte für ReadFile: t.ReadIntervalTimeout = intervalTO; // Zeit zwischen zwei Zeichen t.ReadTotalTimeoutMultiplier = readSingleTO; // pro Zeichen t.ReadTotalTimeoutConstant = readSingleTO; // Werte für WriteFile: wenn beide 0 sind, kein Timeout // beim Schreiben t.WriteTotalTimeoutMultiplier = writeSingleTO; t.WriteTotalTimeoutConstant = writeSingleTO; SetCommTimeouts(hCom,&t); } //--------------------------------------------------------------------------- // Daten senden int SerialInterface::SendData(char Data[],int n) { DWORD NumberOfBytesWritten; //Anzahl der gesendeten Bytes WriteFile(hCom, // handle des Com-Ports Data, // Adresse der Daten n, // Anzahl der zu sendenden Bytes &NumberOfBytesWritten, // Adresse übergeben 0); // kein overlapped I/O return NumberOfBytesWritten; } //--------------------------------------------------------------------------- // Daten empfangen DWORD SerialInterface::ReceiveData(char* Data,int n) { DWORD NumberOfBytesRead; // Anzahl der gelesenen Bytes ReadFile(hCom, // handle des Com-Ports Data, // Adresse des Datenpuffers n, // Anzahl der zu lesenden Bytes &NumberOfBytesRead, // Adresse übergeben 0); // kein overlapped I/O return NumberOfBytesRead; } //--------------------------------------------------------------------------- // schließen der seriellen Schnittstelle void SerialInterface::CloseComm(void) { CloseHandle(hCom); }
Zum Auslesen der Messwerte habe ich für jedes Messgerät einen Thread verwendet:
class BlueSens : public TThread { private: bool connected; short abtastrate; AnsiString status; SerialInterface serialInterface; double co2; double druck; void ReceiveData(); protected: void __fastcall Execute(); public: __fastcall BlueSens(bool CreateSuspended); void __fastcall UpdateCaption(); void Connect(AnsiString port); __fastcall ~BlueSens(); AnsiString GetStatus(); bool GetConnectionStatus(); }; __fastcall BlueSens::BlueSens(bool CreateSuspended) : TThread(CreateSuspended), connected(false), abtastrate(1000), status(""), co2(0.0f), druck(0.0f) { } //--------------------------------------------------------------------------- __fastcall BlueSens::~BlueSens() { if (connected) serialInterface.CloseComm(); } //--------------------------------------------------------------------------- void __fastcall BlueSens::Execute() { while (!Terminated) { ReceiveData(); Synchronize(UpdateCaption); } } //--------------------------------------------------------------------------- void __fastcall BlueSens::UpdateCaption() { //Anzeige auf der Hauptform frmMain->labAnzeigeCO2Gehalt->Caption = FloatToStrF(co2, ffFixed, 6, 3); frmMain->labDruck->Caption = FloatToStrF(druck, ffFixed, 6, 3); frmMain->messwerte.SetCo2(co2); frmMain->messwerte.SetPressure(druck); } //--------------------------------------------------------------------------- void BlueSens::Connect(AnsiString port) { connected = serialInterface.OpenComm(port.c_str()); if (connected) { serialInterface.SetDCB(19200); serialInterface.SetReadTimeouts(100, 10, 10); } else ShowMessage("BlueSens nicht verbunden!"); } //--------------------------------------------------------------------------- void BlueSens::ReceiveData() { if (connected) { const int buffSize = 50; char buffer[buffSize] = {0}; unsigned char s[]= {'&', 'E', 13}; serialInterface.SendData(s, 3); // Befehl senden Sleep(250); serialInterface.ReceiveData(buffer, buffSize); // Daten empfangen AnsiString receivedData = AnsiString(buffer); // empfangener Datenstring if (receivedData.SubString(1, 4) == "Wait") { status = "Aufheizphase"; co2 = 0.0f; druck = 0.0f; } // wenn das 2. Zeichen eine zahl ist else if (isdigit(buffer[5])) { status = "Sensor aktiv"; DecimalSeparator = '.'; int pos = receivedData.Pos(" ")-1; co2 = StrToFloat(receivedData.SubString(1, pos)); druck = StrToFloat(receivedData.SubString(pos+2, receivedData.Pos(" :")-pos-2)); } else { status = "Sensorfehler"; co2 = 0.0f; druck = 0.0f; } } } //--------------------------------------------------------------------------- AnsiString BlueSens::GetStatus() { return status; } //--------------------------------------------------------------------------- bool BlueSens::GetConnectionStatus() { return connected; }
Fragen:
1. Wenn das Messgerät nicht angeschlossen ist bzw ein falscher Port ausgewählt wird, wird die Verbindung trotzdem aufgebaut, d.h. die Methode OpenComm(char* port) gibt true zurück. Wie kann ich die Fehlerbehandlung besser machen?2. Mir wurde gesagt, dass das Sleep(250) in der Methode ReceiveData() nicht so gut ist. Das hatte ich eingebaut um sicherzugehen, dass alle Daten gesendet wurden. Wie kann ich das besser machen? Ich denk ich brauch da sowas wie eine Rückmeldung.
3. Kann man das so mit dem Thread machen?
-
1. Du kennst doch das Messgerät und weißt, dass auf einen bestimmten Befehl eine bestimmte Antwort folgt. Das kann deine Überprüfung werden, ob das Gerät tatsächlich angesprochen werden kann. Das ist dann natürlich gerätespezifisch und nicht mehr allgemeingültig in der Klasse.
-
Zu 1. kann ich nix sagen, aber zu einigen anderen Dingen:
2. Wenn du Overlapped IO benutzt kannst du mit WaitForSingleObject/WaitForMultipleObjects auf das Ergebnis der Operation warten.
3. Nein. Die VCL ist nicht multithreading fähig, Zugriffe auf GUI Elemente führen aus einem anderen Thread zu UB. Das betrifft insbesondere die Methoden SerialInterface::ShowLastError() und BlueSens::UpdateCaption(). Da musst du dir überlegen, wie du das mit dem GUI Mainthread synchronisierst, z.B. bietet TThread dazu die Synchronize Methode an, mit der du parameterlose Methoden im Kontext des GUI Mainthreads aufrufen kannst. SendMessage() wäre eine andere Alternative, allerdings kann der Thread dann nur mit GUI Elementen kommunizieren.
4. SerialInterface::ShowLastError() halte ich für kein gutes Design, eine Schnittstelle sollte kein eigenen Fenster aufmachen oder Fehlermeldungen anzeigen, besser sie informiert einen Controller, der daraufhin vielleicht das GUI aktualisiert (Stichwort Consumer/Producer oder Observer Pattern).
-
Wenn du Overlapped IO benutzt kannst du mit WaitForSingleObject/WaitForMultipleObjects auf das Ergebnis der Operation warten.
Würde das Overlapped IO gern nutzen. Leider hab ich dazu nichts konkretes gefunden. Wie ich das verstanden habe definier ich ein Event, dass eintritt sobald gesendet wurde. Erst dann werden die Daten empfangen. Wie mach ich das?
bietet TThread dazu die Synchronize Methode an
Die Synchronize-Methode habe ich doch verwendet in der Execute. Wo liegt der Fehler?