Probleme bei Messwertabfrage über RS232 und Anzeige auf Form
-
-
Hier noch mal ein Minimalbeispiel einer Klasse, die benutzt wird um Daten in Stringform von mehreren Threads zu einem Formular zu bekommen:
class TData { private: TCriticalSection *pSync; TStringList *pData; public: TData(); ~TData(); void AddMessage(const AnsiString &s); void GetStrings(TStrings *ps); };
TData::TData() { pSync = new TCriticalSection; pData = new TStringList; } TData::~TData() { delete pSync; delete pData; } void TData::AddMessage(const AnsiString &s) { pSync->Enter(); pData->Add(s); pSync->Leave(); } void TData::GetStrings(TStrings *ps) { pSync->Enter(); ps->AddStrings(pData); pData->Clear(); pSync->Leave(); }
Mehrere Threads können gleichzeitig AddMessage aufrufen, die Oberfläche holt sich die Daten mit GetStrings. Immer wenn ein Thread in AddMessage oder GetStrings ist, müssen die anderen warten, aber jeweils nur für den kurzen Moment des Funktionsaufrufs. Wenn sich ein Thread sekundenlang mit der seriellen Schnittstelle befasst, stört das die anderen nicht.
-
oh das ist Neuland für mich, aber das brauch ich doch auch gar nicht oder? Ich will es auch nicht allzu kompliziert machen.
Hab mal für ein Gerät den Thread geschrieben:
__fastcall Edu::Edu(bool CreateSuspended) : TThread(CreateSuspended), volumen(0) { Priority = tpHighest; } //--------------------------------------------------------------------------- void __fastcall Edu::Execute() { while(!Terminated) { volumen = ErfasseVolumen(); Sleep(1000); } } //--------------------------------------------------------------------------- void Edu::PortOeffnen(AnsiString port) { status = com.OpenComm(port.c_str()); com.SetDCB(9600); com.SetReadTimeouts(100, 10, 10); } //--------------------------------------------------------------------------- double Edu::ErfasseVolumen() { char rec[50] = {0}; unsigned char send[] = {0x16}; while(com.ReceiveData(rec,1)>0); //Buffer leeren com.SendData(send,1); com.ReceiveData(rec,50); return StrToFloat(AnsiString(rec).SubString(4,9).Trim()); } //--------------------------------------------------------------------------- double Edu::GetVolumen() { return volumen; } //--------------------------------------------------------------------------- bool Edu::GetStatus() { return status; }
Hab es grad getestet und es funktioniert so weit erst einmal. Was meinst dazu?
-
Meistens wird es gutgehen
Das Problem steckt hier
while(!Terminated) { volumen = ErfasseVolumen(); Sleep(1000); }
double Edu::GetVolumen() { return volumen; }
Es kann passieren, das ein Thread volumen einen neuen Wert zuweist, während ein anderer die Variable gerade liest. Dann kann es bei komplexeren Datentypen Müll beim Lesen geben ...
Die Critical Section verhindert das, da kann immer nur ein Thread drin sein. Also besser so, auch wenn es mehr Arbeit ist:
while(!Terminated) { double tmp = ErfasseVolumen(); pSync->Enter(); volumen = tmp; pSync->Leave(); Sleep(1000); }
double Edu::GetVolumen() { double tmp; pSync->Enter(); tmp = volumen; pSync->Leave(); return tmp; }
(Und das Anlegen von pSync im Konstruktor nicht vergessen.)
So ist sichergestellt, das immer nur einer gleichzeitig auf volumen zugreift.
-
Schlau ist es mit Sicherheit auch, den Wert von hCom zu prüfen, bevor man ihn an irgendeine Funktion übergibt.
-
Es kann passieren, das ein Thread volumen einen neuen Wert zuweist, während ein anderer die Variable gerade liest.
Welcher thread könnte volumen einen neuen Wert zuweisen? Den Wert bekommt volumen doch nur im eigenen Thread. Aber ich denke den meinst du auch. Und mit den anderen Thread der die Variable liest meinst du den Hauptthread der Anwendung? Ich hätte nicht gedacht, das dies kritisch sein könnte, dass Variablen gleichzeitig geschrieben und gelesen werden. Ich hätte gedacht, dass Variablen doch gerade durch die Zugriffsmodifizierer der Klassen (private, public) sicher sind.
In den Büchern, die ich mir in dem letzten halben Jahr angeschafft habe, findet sich leider nichts dazu. Aber man lernt ja nie aus. Vielen Dank für den Tip. Ich werd das gleich mal ausprobieren.den Wert von hCom zu prüfen
wie mach ich das am schlausten? Leider kenn ich mich mit Handles noch gar nicht aus. Ein Handle ist doch sowas wie ein Bool oder?
-
rudpower schrieb:
Ein Handle ist doch sowas wie ein Bool oder?
Nein, eher nicht. Ein Handle ist irgendein Dingsbums, das dein OS benutzt, um Objekte zu identifizieren. Was genau dahintersteckt braucht man nicht zu wissen. Aber es gibt gültige und ungültige Handles, schau dir mal an, was CreateFile im Fehlerfall zurückgibt.
Das Thema Threadsynchronisation musst du dann behandeln, wenn deine Daten konsistent bleiben sollen. Angenommen, du hast einen Messwert und einen Zeitstempel zu diesem Messwert, dann kann es vorkommen, dass der Datenerfassungsthread den Messwert aktualisiert, aber vor der Aktualisierung des dazugehörigen Zeitstempels die CPU an die Datenauswertung abgibt. Diese liest den neuen Messwert aber den alten Zeitstempel -> Inkonsistenz. Durch Critical Sections oder Mutexe musst du dafür sorgen, dass alle zusammengehörige Daten in einem Aktualisierungszyklus aktualisiert werden.
-
ah ok. Im Fehlerfall wird ein INVALID_HANDLE_VALUE zurückggegeben. Die Nummer des letzten Fehlers erhält man dann mit GetLastError(). Also steht das HANDLE einfach für die Datei mit den Attributen?
Hab das mit Critical Sections mal ausprobiert. Erhalte jetzt flogenden Fehler:
[Linker Fataler Fehler] Fatal: Could not open C:\Programme\Borland\CBuilder5\Projects\mehrereThreads\Project1.exe (program still running?)
Hab aber das Programm beendet.
Hier meine neue Threadklasse:
__fastcall Edu::Edu(bool CreateSuspended) : TThread(CreateSuspended), status(false), volumen(0), volumenstrom(0), pSync(new TCriticalSection) { Priority = tpHighest; } //--------------------------------------------------------------------------- __fastcall Edu::~Edu() { if(status == true) com.CloseComm(); delete pSync; } //--------------------------------------------------------------------------- void __fastcall Edu::Execute() { while(!Terminated) { double tmp = ErfasseVolumen(); pSync->Enter(); volumen = tmp; pSync->Leave(); double tmp2 = ErfasseVolumenstrom(); //oder kann ich hier wieder tmp nehmen ohne eine neue Variable tmp2 anzulegen? pSync->Enter(); volumenstrom = tmp2; pSync->Leave(); Sleep(1000); } } //--------------------------------------------------------------------------- void Edu::PortOeffnen(AnsiString port) { status = com.OpenComm(port.c_str()); if (status == true) { com.SetDCB(9600); com.SetReadTimeouts(100, 10, 10); } else ShowMessage("Gaszähler Edu nicht verbunden!"); } //--------------------------------------------------------------------------- double Edu::ErfasseVolumen() { if(status == true) { char rec[50] = {0}; unsigned char send[] = {0x16}; while(com.ReceiveData(rec,1) > 0); //Buffer leeren com.SendData(send, 1); com.ReceiveData(rec, 50); return StrToFloat(AnsiString(rec).SubString(4,9).Trim()); } else return 0; } //--------------------------------------------------------------------------- double Edu::ErfasseVolumenstrom() { if(status == true) { char rec[50] = {0}; unsigned char send[] = {0x06}; while(com.ReceiveData(rec, 1) > 0); //Buffer leeren com.SendData(send, 1); com.ReceiveData(rec, 50); return StrToFloat(AnsiString(rec).SubString(6,8).Trim()); } else return 0; } //--------------------------------------------------------------------------- double Edu::GetVolumen() { double tmp; pSync->Enter(); tmp = volumen; pSync->Leave(); return tmp; } //--------------------------------------------------------------------------- double Edu::GetVolumenstrom() { double tmp; pSync->Enter(); tmp = volumenstrom; pSync->Leave(); return tmp; } //--------------------------------------------------------------------------- bool Edu::GetStatus() { return status; }
hatte das vorher ohne den critical sections mal ausprobiert und auch für die anderen messgeräte eine seperate Threadklasse geschrieben und es funktionierte. Und ich war ganz erstaunt, dass es nun nicht mehr ruckelt. Was leider noch nicht geht, ist bei stehender Verbindung den Port zu ändern.
Edit: Hab den Rechner neugestartet und das Programm laufen lassen. Dann konnte ich kompilieren und das Programm lief. Nach ca. 1 min. hat sich das Programm aufgehangen und ich konnte es auch nicht beenden. Dann kam eine Fehlermeldung "Fehler beim warten auf Prozessabbruch" oder so. Wo könnte der Fehler liegen?
Hab mal alles mit CriticalSection rausgenommen. Dann bricht das Programm ebenfalls nach kurzer Zeit ab ohne Fehlermeldung.
-
nochmal zu der commclass. Ich hab mir in der MSDN die Member der DCB angeschaut. Ein paar sind ja bekannt. Nun will ich die Parameter nicht einfach ändern. Da es so ja funktioniert kann er doh einfach die Standardparameter nehmen oder nicht? Wie erfahre ich welchen Parameter ich wie setzen muss?
Laut Handbuch des Geräts sind nur bekannt: 9600 Baud, No Parity, 8 bit, 1 stopbit. Das gilt für alle Messgeräte.Die Alternative einfach Deine Funktion zu nehmen wär da vielleicht einfacher. Wie sieht die dann aus?
void comclass::SetDCB(int baud) { DCB dcb; // Device Control Block ZeroMemory(&dcb,sizeof(DCB)); char Params[maxLen]; sprintf(Params,"baud=9600 parity=N data=8 stop=1 dtr=on rts=on"); //dtr on? rts on? if ( !BuildCommDCB(Params, &dcb) ) { //wie sieht der Code hier konkret aus? } }
Edit: DTR und RTS müssen denk ich off. Hab grad in einem Handbuch eines der Geräte gesehen, dass man für dieses (Hardware Handshake) noch 2 Pins (DTR und RTS)dafür belegen müsste. Das hab ich aber nicht gemacht. Verwende aber nur RX, TX und Masse.
-
rudpower schrieb:
Wie sieht die dann aus?
Verwende halt sprintf oder AnsiString::sprintf mit Parametern. Dafür ist es da.
rudpower schrieb:
Hab grad in einem Handbuch eines der Geräte gesehen, dass man für dieses (Hardware Handshake) noch 2 Pins (DTR und RTS)dafür belegen müsste. Das hab ich aber nicht gemacht. Verwende aber nur RX, TX und Masse.
Kann auch eine Ursache für sporadische Fehler sein.
-
das ich nur 3 Adern verwende kann ich jetzt nicht mehr ändern, da hardwareseitig bereits alles verdrahtet ist. Aber laut Handbuch der Geräte müssend DTR und RTS nicht zwingend belegt werden.
Mein Programm läuft im Moment. Wenn ich es allerdings länger laufen lasse, bricht es mit einer Exception ab. Den Fehler konnte ich bisher leider nicht ausfindig machen.
Was jetzt aber klappt ist das Ändern der Ports während des Programmlaufs. Dazu hab ich einfach vor dem Öffnen erst mal den Port geschlossen, falls dieser offen ist:bool comclass::OpenComm(char* port){ CloseHandle(hCom); 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) return false; else return true; }
Woran könnte das liegen, dass mein Programm ständig abschmiert?
-
Die Aussage "irgendwann schmiert mein Programm mit einer Exception ab" enthält nicht ein Mal ansatzweise irgendwelche Informationen.
Wenn´s gar nicht anders geht bau halt Debug Ausgaben in deinen Programmcode ein, mit OutputDebugString kannst du Ausgaben in die Ereigniskonsole schreiben:void f() { OutputDebugString( "Betrete f()" ); // mach was OutputDebugString( "Verlasse f()" ); }
Damit solltest du eingrenzen können, wo der Fehler auftritt.