Forum: PC-Programmierung C++ Serieller Empfang (Event) - Probleme


von Patrick L. (crashdemon)


Lesenswert?

Hallo Leute,

ich habe ein Problem mit einem Programm das ich zurzeit schreibe. Das 
Programm soll Befehle über die serielle Schnittstelle an ein externes, 
von mir gebautes Messgerät senden sowie Messwerte empfangen. Ersteres 
klappt ohne Probleme. Der zweite Teil macht allerdings Probleme. Zur 
Ansteuerung der seriellen Schnittstelle nutze ich den folgenden Code: 
http://www.tetraedre.com/advanced/serial2.php

Zum Programmieren des Steuerungsprogrammes nutze ich Borland C++ 6.

Ich werde einmal kurz die Verfahrensweise erläutern nach der ich 
vorgehe.
Jedes mal wenn ein Ereignis der seriellen Schnittstelle eintritt wird 
die nachfolgende Funktion aufgerufen (SERIAL_DATA_ARRIVAL ist hier 
wichtig), das ganze läuft intern wohl thread-basiert (Serial2 von 
Tetraedre):
1
//---------------------------------------------------------------------------
2
void SerialEventManager(uint32 object, uint32 event)
3
{
4
    Tserial_event *com;
5
    com = (Tserial_event *) object;
6
7
    if(com != 0)
8
    {
9
        switch(event)
10
        {
11
            case SERIAL_CONNECTED:
12
                // CONNECTED
13
                //Application->MessageBox("CONNECTED", "INFO", MB_OK);
14
            break;
15
16
            case SERIAL_DISCONNECTED:
17
                // DISCONNECTED
18
                //Application->MessageBox("DISCONNECTED", "INFO", MB_OK);
19
            break;
20
21
            case SERIAL_DATA_SENT:
22
                // DATA_SENT
23
                //Application->MessageBox("DATA SENT", "INFO", MB_OK);
24
            break;
25
26
            case SERIAL_DATA_ARRIVAL:
27
                MainForm->size   = com->getDataInSize();
28
                MainForm->buffer = com->getDataInBuffer();
29
30
                // write data into fifo buffer
31
                UART_writeFIFO(&MainForm->rxFifo, (unsigned char)(*MainForm->buffer));
32
33
                // WE SHOULDN'T DO THIS HERE!!!!!!!!
34
                MainForm->updateForms();
35
36
                com->dataHasBeenRead();
37
38
                //Application->MessageBox("DATA RECEIVED", "INFO", MB_OK);
39
            break;
40
        }
41
    }
42
}

Wenn ich also ein Zeichen empfange, schreibe ich dass in einen 
Ringbuffer und Rufe eine Funktion auf die div. Felder neu besetzt, bzw. 
die Daten solange über die serielle Schnittstelle einließt bis ein 
Telegramm beendet ist (\n).

Die Funktion sieht wie folgt aus:
1
//---------------------------------------------------------------------------
2
void TMainForm::updateForms(void)
3
{
4
    MEAS meas_comp;
5
    static unsigned int index = 0;
6
    static unsigned int xAxis = 0;
7
    unsigned char rxByteBuffer;
8
9
    UART_readFIFO(&rxFifo, &rxByteBuffer);
10
11
    // read from ringbuffer unless newline (LF) reached
12
    if(rxByteBuffer != '\n')
13
    {
14
        tempRecvMsg[index++] = rxByteBuffer;
15
    }
16
17
    // if newline reached process received string 
18
    else
19
    {
20
        tempRecvMsg[index++] = '\0';
21
        index = 0;
22
23
        recvMsg = AnsiString((char *)(tempRecvMsg));
24
25
        if(recvMsg.Pos("RESULT:"))
26
        {
27
            // recvMsg contains "RESULT:" string --> meas cycle finished
28
            MainForm->L1_DC_RES_Edit->Text = recvMsg.SubString(8, 6);
29
            MainForm->L2_DC_RES_Edit->Text = recvMsg.SubString(15, 6);
30
            MainForm->L3_DC_RES_Edit->Text = recvMsg.SubString(22, 6);
31
        }
32
33
        else if(recvMsg.Pos("BUSY"))
34
        {
35
            Application->NormalizeTopMosts();
36
            Application->MessageBox("Messgerät beschäftigt!\nBitte warten!", "Fehler", (MB_OK | MB_ICONWARNING));
37
            Application->RestoreTopMosts();
38
        }
39
40
        if(splitMsg(&recvMsg, &meas_comp) == true)
41
        {
42
            setValues(&meas_comp);
43
44
            // convert to locale decimal seperator
45
            meas_comp.DC_current[L1] = StringReplace(meas_comp.DC_current[L1], ".", "," , TReplaceFlags() << rfReplaceAll);
46
            meas_comp.DC_current[L2] = StringReplace(meas_comp.DC_current[L2], ".", "," , TReplaceFlags() << rfReplaceAll);
47
            meas_comp.DC_current[L3] = StringReplace(meas_comp.DC_current[L3], ".", "," , TReplaceFlags() << rfReplaceAll);
48
49
            // write to chart
50
            MainForm->Series1->AddXY(xAxis, meas_comp.DC_current[L1].ToDouble(), "", clRed);
51
            MainForm->Series2->AddXY(xAxis, meas_comp.DC_current[L2].ToDouble(), "", clGreen);
52
            MainForm->Series3->AddXY(xAxis, meas_comp.DC_current[L3].ToDouble(), "", clBlue);
53
54
            // repaint chart if end reached
55
            if(xAxis >= 26)
56
            {
57
                xAxis = 0;
58
                MainForm->Series1->Clear();
59
                MainForm->Series2->Clear();
60
                MainForm->Series3->Clear();
61
            }
62
63
            else xAxis++;
64
        }
65
66
        MainForm->RawMemo->Lines->Add(recvMsg);
67
        MainForm->DebugEdit->Text = recvMsg;
68
69
        Application->ProcessMessages();
70
    }
71
}

Darin mache ich ziemlich viele Sachen wie z.B. einen Graphen mit den 
emfangenen Messwerten zu zeichnen. Weiterhin schreibe ich die Rohwerte 
die ich über die serielle Schnittstelle empfange in ein Memo ( 
MainForm->RawMemo->Lines->Add(recvMsg);).

Jetzt zum eigentlichen Problem: Wenn ich die Zeile: 
MainForm->RawMemo->Lines->Add(recvMsg);
auskommientiere klappt alles ohne Probleme, sobald ich diese drin habe 
bekomme ich bei beenden die Fehlermeldung "Code 1400: Ungültiges 
Fensterhandle". Hinzuzufügen ist, dass ich auch in die Memo schreibe 
wenn diese nicht im Fokus ist....

Ich denke das Problem wird sein, dass ich in einem Thread auf die Memo 
schreibe während diese schon zerstört wurde, oder das es ein Problem ist 
das ich darauf schreibe wenn diese nicht im Focus ist.

Vllt. könnt ihr mir da einen Tipp geben...

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

Patrick L. schrieb:
> MainForm->RawMemo->Lines->Add(recvMsg);
> auskommientiere klappt alles ohne Probleme, sobald ich diese drin habe
> bekomme ich bei beenden die Fehlermeldung "Code 1400: Ungültiges
> Fensterhandle"

Du kannst nicht aus einem beliebigen Thread heraus auf Objekte des 
GUI-Threads zugreifen.

von Patrick L. (crashdemon)


Lesenswert?

Wie finde ich den heraus in welchen Thread updateForms(); ausgeführt 
wird?

Ich hab angenommen, dass das im Kontext des MainThread aufgerufen wird. 
Wenn das nicht der Fall ist, wie würdest du das lösen?

Ich finde die Lösung wie ich sie im Moment habe auch nicht gerade 
elegant, vorallem wie wird sichergestellt, dass die Verarbeitung des 
empfangenen Telegrammes beendet ist bevor ein neues eintrifft?

Von der Messgerät-Seite sende ich mit 115200 Baud einen Burst von 
Messwerten (ca. 40 Zeichen) alle 100ms.

Was mir noch aufgefallen ist, wenn ich
1
MainForm->RawMemo->Lines->Add(recvMsg);

nur durchführe wenn der Reiter für die Rohwerte aktiv ist (also somit im 
Fokus), also so:
1
if(PageControl->ActivePage == RawTabSheet)
2
{
3
    MainForm->RawMemo->Lines->Add(recvMsg);
4
}

dann scheint die Fehlermeldung zu verschwinden.

: Bearbeitet durch User
von Patrick L. (crashdemon)


Lesenswert?

Was mich auch noch wundert ist, dass das füllen des Graphen scheinbar 
keine Probleme verursacht:

MainForm->Series1->AddXY(...)

Das hinzufügen der Messwerte zu einer MemoBox

MainForm->RawMemo->Lines->Add

allerdings schon, wie auch schon oben beschrieben.

von Stephan (Gast)


Lesenswert?

Hi,
um auf GUI Elemente zu schreiben gibt es eine Sync-Funktion!
Ich hab mal meine alten Links durchsucht und diesen noch gefunden:

http://bcb-tutorial.c-plusplus.de/Thread/artikel5.html

vielleicht hilft dir das.

>Von der Messgerät-Seite sende ich mit 115200 Baud einen Burst von
>Messwerten (ca. 40 Zeichen) alle 100ms.
Das wird sehr schwierig, die 100ms sind zu klein!
Je nach dem PC und den Programmen die ausgeführt werden kann es sein das 
ein Programm mal nicht ran kommt!
Aber Du hast ja zum Glück einen End-Kenner im Telegramm, vielleicht 
reicht das ja doch.
Aus meinen Erfahrungen sollten es min 500ms sein oder besser 
>1000ms.(bei Burst-Betrieb)
Die bessere Lösung für den PC ist es wenn er die Daten abfragen kann, so 
kommt er nicht durch einander.(1 Abfrage -> 1 Antwort)

von Patrick L. (crashdemon)


Lesenswert?

Stephan schrieb:
> Hi,
> um auf GUI Elemente zu schreiben gibt es eine Sync-Funktion!
> Ich hab mal meine alten Links durchsucht und diesen noch gefunden:
>
> http://bcb-tutorial.c-plusplus.de/Thread/artikel5.html
>
> vielleicht hilft dir das.

Danke für den Link, diesen hatte ich auch schon gefunden und auch 
ausprobiert. Dabei ist mir dann aufgefallen, dass Daten z.B. der MemoBox 
hinzugefügt werden (Zu erkennen daran, das die Scrollbar kleiner wurde) 
aber diese nicht angezeigt wurden.

von Patrick L. (crashdemon)


Lesenswert?

Stephan schrieb:
> Das wird sehr schwierig, die 100ms sind zu klein!
> Je nach dem PC und den Programmen die ausgeführt werden kann es sein das
> ein Programm mal nicht ran kommt!
> Aber Du hast ja zum Glück einen End-Kenner im Telegramm, vielleicht
> reicht das ja doch.
> Aus meinen Erfahrungen sollten es min 500ms sein oder besser

Kann ich leider nicht verlängern. Außerdem kommen ja Terminal Programme 
wie HTerm auch damit klar.

von Stephan (Gast)


Lesenswert?

>dass Daten z.B. der MemoBox
>hinzugefügt werden (Zu erkennen daran, das die Scrollbar kleiner wurde)
>aber diese nicht angezeigt wurden.
Ja das kenne ich so auch.
Es liegt daran das das Memo nicht neu gezeichnet wird(ist länger her)
Zum Testen einfach mal 'Application->Process....' aufrufen(Hab den Namen 
vergessen), oder das Memo einen 'Refresh' oder 'Update' ausführen 
lassen.
Ist schon länger her als ich mit BC++6 gearbeitet habe.

>Kann ich leider nicht verlängern. Außerdem kommen ja Terminal Programme
>wie HTerm auch damit klar.
Ja, das kenne ich auch. :-[
Ich weiß bis heute nicht warum der Laptop vom Chef damals nicht unter 1 
sec Timeout gearbeitet hat!!! (war ein älterer Celeron Laptop mit XP)
Mir sind dort die Events verloren gegangen.

Probier es aus, vielleicht gehts bei dir gut. Mit meinem PC damals hat 
es ja auch geklappt. :-)
Aber bei einigen Kunden (alte PCs) und der Laptop vom Chef wollten damit 
nicht laufen.
Bei XP war der Taskwechsel doch bei 10ms oder so. Wie siehts denn bei 
heutigen Win Versionen aus?

von Patrick L. (crashdemon)


Lesenswert?

Stephan schrieb:
> Hi,
> um auf GUI Elemente zu schreiben gibt es eine Sync-Funktion!
> Ich hab mal meine alten Links durchsucht und diesen noch gefunden:
>
> http://bcb-tutorial.c-plusplus.de/Thread/artikel5.html
>
> vielleicht hilft dir das.

Ich habe das Beispiel nochmal komplett von vorne durchprobiert, ich 
hatte bei meiner vorherigen Implementierung einen Fehler eingebaut. 
Jetzt scheint auch eine Ausgabe zu funktionieren, danke..

von Stephan (Gast)


Lesenswert?

Noch eins,
bei der RS232 API gabs bei mir mal so ein Event-Char, damit kann man die 
'Eventlast' im System minimieren. (anstatt bei jedem Byte ein Event 
auszulösen)

Aber dies nur als Hinweis!!!!

von Stephan (Gast)


Lesenswert?

>Jetzt scheint auch eine Ausgabe zu funktionieren, danke..
Wenn es klapp, super

:-)

von Patrick L. (crashdemon)


Lesenswert?

Ja, das Problem lag wohl tatsächlich daran, dass updateForms(); aus dem 
Kontext des "serielle Daten" Thread aufgerufen worden ist. Da wurde auf 
VCL Komponenten, die dann immer sporadisch Exceptions verursacht haben 
(List index out of Bounds, Ungültiges Fensterhandle, ...).

Jetzt habe ich das ganz so wie in dem Beispiellink getrennt.

Die Daten werden in einen Ringbuffer geschrieben, sobald das Ende 
erkannt wird wird ein Thread geöffnet der über Synchronize die Daten 
aufteilt und die entsprechenden Felder, EditBoxen, Graphen, Memos, 
etc... besetzt.
Ist das vollendet wird der Thread beendet, "parallel" werden natürlich 
schon wieder neue Daten in den Ringbuffer geschrieben.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.