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?


Anmelden zum Antworten