Forum: Mikrocontroller und Digitale Elektronik [Qt-Creator] - Daten über die Serielle Schnittstelle empfangen.


von Rudi (Gast)


Lesenswert?

Guten Morgen zusammen,

*(Wer sich die ganze Vorgeschichte sparen möchte der kann direkt nach 
unten zum Code springen, da ich habe nicht gut Qt auskenne möchte ich 
lieber das Problem schon gut erklärt haben. Nicht das ich mich schon mit 
generellen Fehlern ein Bein Stelle.)

ich stehe vor einem Problem mit der Qt-Creator und meiner 
Programmierung. =)
In meiner Werkstatt würde ich gerne ein paar Temperaturen auf meinem 
Monitor anzeigen lassen.
Dafür habe ich schon einen Microcontroller genommen der alle 
Temperaturen sammelt und in ein Array schreibt. Dieses Array sende ich 
über die serielle Schnittstelle zu meinem PC.

Um die Daten entgegen zu nehmen und Visuell etwas aufzuhübschen habe ich 
nun auf vielen Seiten gelesen das der Qt-Creater(Designer) dafür recht 
gut geeignet wäre. Also habe ich mich mal dahinter geklemmt und ein paar 
Tutorials durch gearbeitet.

Ich bin jetzt mittlerweile auch soweit das ich in meinem Qt die ganze 
Zeit die Daten vom uController einlesen kann.
Der uC sendet das Array in folgendem Format:

Mein Array:
double temperatur[] = {10.00, 20.00, 30.00, 40.00, 50.00}
gesendet wird dann:
10.00,20.00,30.00, etc.
Das "," dient als Trenner zwischen den Werten.

Das erste Problem was ich hatte war das im Qt die ganzen Werte so 
eingelesen wurden wie die Serielle schnittestelle gerade zeit hatte die 
Daten abzurufen. da war ein ziemliches Kauderwelsch :D.

Dann habe ich in einem Beispiel Programm eine Routine gefunden welche 
mit einen Messwert separiert.
1
void MainWindow::readSerial()
2
{
3
    //qDebug() << "SerialPort works";
4
    QStringList buffer_split = serialBuffer.split(",");
5
    if(buffer_split.length() < 5){
6
            // no parsed value yet so continue accumulating bytes from serial in the buffer.
7
            serialData = arduino->readAll();
8
            serialBuffer = serialBuffer + QString::fromStdString(serialData.toStdString());
9
            qDebug() << buffer_split << "\n";
10
            serialData.clear();
11
        }else{
12
        // the second element of buffer_split is parsed correctly, update the temperature value on temp_lcdNumber
13
        serialBuffer = "";
14
        qDebug() << buffer_split << "\n";
15
        parsed_data = buffer_split[1];
16
}

in buffer_split[1] ist dann genau ein vollständig empfangender Messwert. 
jubel

Bei nur einem Messwert der empfangen wird ist das ja kein Problem, aber 
wenn ich nun mein Array mit z.B. 5 Messwerten sende, dann weiß Qt ja 
nicht welches davon das Ende und was der Anfang ist den es gerade 
empfangen hat.
Mhm bisschen schwer zu erklären am Kopf kratz ich hoffe ihr versteht 
was ich meine.

Wie mache ich das denn nun am geschicktesten das ich am Besten in Qt 
exakt das gleiche Array wie im uC liegen habe? Ich bin natürlich 
flexibel was die Trennzeichen angeht, das kann ich einfach in meinem uC 
anpassen.

Vielleicht hat da ja jemand eine Idee.
Viele grüße
Rudi

von Kodex (Gast)


Lesenswert?

Das hat mit QT gar nicht viel zu tun... das wäre eher eine Frage Deines 
Protokoll-Designs, denn irgendwie muss der Empfänger die Datenpakete ja 
auseinanderhalten. Das kann üblicherweiser über ein Telegramm-Ende 
Zeichen erfolgen, über eine Zeitkonstante oder über eine fixe 
Telegramlänge.

Beispiele:

Endezeichen: ["1,2,3,4,5\n"]["1,2,3,4,5\n"]["1,2,3,4,5\n"]
Timeout ["1,2,3,4,5"]...100ms Pause...["1,2,3,4,5"]...
Länge   ["001.00,002.00,003.00,004.00,005.00"] = 34 Byte


Bevor Du also Deinen String mit Messwerten zerlegst, musst Du 
sicherstellen, dass er vollständig empfangen wurde. Dazu liest die Daten 
von der seriellen Schnittstelle ein, bis Dein Kriterium erfüllt ist.
Anschließend den String zerteilen, die empfangenen Daten prüfen und dann 
ausgeben.

Das wäre der erste Schritt. Man könnte dann noch ausbauen und z.B. eine 
Prüfsumme an das Telegram anhängen, welche der Empfänger mit den 
gelesenen Daten vergleichen kann. Das schafft ein bisschen mehr 
Sicherheit.

von georg (Gast)


Lesenswert?

Kodex schrieb:
> z.B. eine
> Prüfsumme an das Telegram anhängen

Vorab: bei einer seriellen Schnittstelle ist eine Prüfung unbedingt zu 
empfehlen, da sonst jede Störung die Daten verfälschen kann. Man kann 
höchstens argumentieren, zum reinen Ablesen der Werte ist das egal, wenn 
sie mal nicht stimmen, aber das muss man selber entscheiden.

Prinzipiell gibt es 2 Möglichkeiten: entweder man verarbeitet den ganzen 
Array dann, wenn er komplett und korrekt empfangen wurde, oder man packt 
die Werte in einzelne Telegramme mit einer Kennzeichnung, um welchen 
Wert es sich handelt, z.B. eine Nummer für jede Messstelle. Das scheint 
zunächst mehr Aufwand, ist aber bei Bedarf viel leichter zu erweitern 
und lässt auch mehrere Slaves zu.

Für das Protokoll gibt es viele Möglichkeiten, einfach ist es mit 
Kontrollzeichen zu arbeiten. Im ASCII-Code sind solche vorgesehen, z.B. 
STX (Start of Text) und ETX (End of Text). Wird also STX empfangen, wird 
der Empfang eines Telegramms gestartet, alle folgenden Zeichen gehen in 
den Empfangsbuffer, bis ein ETX empfangen wird, dann kann das Telegramm 
verarbeitet werden. Einfacher gehts nicht.

Georg

von Rudi (Gast)


Lesenswert?

Kodex schrieb:
> Das hat mit QT gar nicht viel zu tun...
Das habe ich mir schon fast gedacht ;-).

Kodex schrieb:
> Beispiele:
>
> Endezeichen: ["1,2,3,4,5\n"]["1,2,3,4,5\n"]["1,2,3,4,5\n"]
> Timeout ["1,2,3,4,5"]...100ms Pause...["1,2,3,4,5"]...
> Länge   ["001.00,002.00,003.00,004.00,005.00"] = 34 Byte
Bei dem Beispiel "Endezeichen" könnte ja aber auch das Problem auftreten 
das ich die Daten erst ab "3,4,5\n" empfangen habe. Das Endezeichen sagt 
mir ja nicht das die Daten auch vollständig sind. Das müsste vermutlich 
eine Kombination aus Endezeichen und Länge sein.
Also das Array ist nur richtig Empfangen wenn das Endezeichen wirklich 
am Ende ist und die Daten min XX Bytes groß sind.

Ich merke gerade das ich den Code aus dem Tutorial doch noch nicht 
richtig verstanden habe. Ich Drösel das nochmal auf und schreibe meinen 
Kommentar da hinter, ob ich das richtig verstanden habe.
1
void MainWindow::readSerial()
2
{
3
    //qDebug() << "SerialPort works";
4
    QStringList buffer_split = serialBuffer.split(","); //*Die Empfangenden Daten aus "serialBuffer" werden mit einem "," getrennt.*
5
    if(buffer_split.length() < 5){                      //*Sammel mindestens 5 Datensätze.* 
6
            // no parsed value yet so continue accumulating bytes from serial in the buffer.
7
            serialData = arduino->readAll();            //*schreibe die bisher empfangenden Daten serialData*
8
            serialBuffer = serialBuffer + QString::fromStdString(serialData.toStdString());//*Speicher die Daten von serialData + serialBuffer als String*
9
            qDebug() << "buffer_split=" << buffer_split << " serialData="<< serialData << "\n"; //*Debug Messages*
10
            serialData.clear();                         //*serialData wieder freimachen für die nächsten Daten*
11
        }else{//*Wenn 5 Datensätze empfangen sind -> Daten an ui übergeben.*
12
        // the second element of buffer_split is parsed correctly, update the temperature value on temp_lcdNumber
13
        serialBuffer = "";
14
        qDebug() << buffer_split << "\n";
15
        parsed_data = buffer_split[1];
16
        temperature_value = parsed_data.toDouble();
17
        //temperature_value = (9/5.0) * (parsed_data.toDouble()) + 32; // convert to fahrenheit
18
        qDebug() << "Temperature: " << temperature_value << "\n";
19
        parsed_data = QString::number(temperature_value, 'g', 4); // format precision of temperature_value to 4 digits or fewer
20
        MainWindow::updateTemperature(parsed_data);
21
    }
22
23
}

Mit fällt da gerade auf das ich "buffer_split" nach dem 5 Datensätze 
empfangen wurde gar nicht freigemacht wird. Aber das muss anscheinend 
irgendwie anders funktionieren. Denn es tut ja eigentlich das was es 
soll.

Ich hatte mir ja auch schon eine kleine Debug Ausgabe geschrieben damit 
ich sehe wie sich die Datenreihe so langsam zusammen setzt.
1
buffer_split= ("")  serialData= "20.00,30.00," 
2
buffer_split= ("20.00", "30.00", "")  serialData= "40.00," 
3
buffer_split= ("20.00", "30.00", "40.00", "")  serialData= "50.00," 
4
buffer_split= ("20.00", "30.00", "40.00", "50.00", "") //<5 Daten gesammelt
hier sieht man auch schon das z.B. die 10.00 abgeschnitten wurde. Der 
Start wert dieses Arrays ist auch willkürlich der kann auch mit 50.00 
anfangen. ;)

Wie nennt man denn so was, wenn man einen String/Array zu einem 
Bestimmten Start/Endzeitpunkt befüllen möchte. "Sortieren" ist ja der 
falsche Ausdruck dafür.
Mit fehlen da so ein bisschen die Suchbegriffe oder Beispiele um das 
nachvollziehen zu können.

Viele Grüße
Rudi

von Stefan F. (Gast)


Lesenswert?

Rudi schrieb:
> dann weiß Qt ja
> nicht welches davon das Ende und was der Anfang ist

Deswegen brauchst du weitere Trennzeichen, zum Beispiel Zeilenumbrüche.

Du musst bei der klassischen GUI Programmierung (zu der Qt zählt) immer 
bedenken, dass man nicht aktiv Daten empfängt bzw. darauf wartet, 
sondern die Daten kommen irgendwann von alleine herein.

Schau dir mal auf der Seite http://stefanfrings.de/serial_io/index.html 
wie ich dort die Kommunikation zwischen Mikrocontroller und PC gemacht 
habe und wie die downloadbare "Beispiel App" (das ist ein Qt Programm) 
dazu aufgebaut ist.

von Rudi (Gast)


Lesenswert?

georg schrieb:
> Vorab: bei einer seriellen Schnittstelle ist eine Prüfung unbedingt zu
> empfehlen, da sonst jede Störung die Daten verfälschen kann. Man kann
> höchstens argumentieren, zum reinen Ablesen der Werte ist das egal, wenn
> sie mal nicht stimmen, aber das muss man selber entscheiden.

Danke für den Beitrag, ja man könnte natürlich argumentieren das es bei 
meinen Temperaturen piep egal wäre falls das mal was falschen angezeigt 
wird. Aber ich möchte ja auch was dabei lernen und mich weiterbilden, 
deswegen würde ich das schon gerne etwas "sicherer" machen.

Stefan ⛄ F. schrieb:
> Deswegen brauchst du weitere Trennzeichen, zum Beispiel Zeilenumbrüche.
>
> Du musst bei der klassischen GUI Programmierung (zu der Qt zählt) immer
> bedenken, dass man nicht aktiv Daten empfängt bzw. darauf wartet,
> sondern die Daten kommen irgendwann von alleine herein.

Stimmt, "einfach" ein weiteres Trennzeichen einfügen. Okay das schaue 
ich mir mal in deinem Beispiel an. Vielen Dank dafür.

Ich melde mich nochmal dann.
Danke für Eure Hilfe
Gruß
Rudi

von Rudi (Gast)


Lesenswert?

Stefan ⛄ F. schrieb:
> Schau dir mal auf der Seite http://stefanfrings.de/serial_io/index.html
> wie ich dort die Kommunikation zwischen Mikrocontroller und PC gemacht
> habe und wie die downloadbare "Beispiel App" (das ist ein Qt Programm)
> dazu aufgebaut ist.

Hi,
so auf der Seite das "15 Min. Programmier Beispiel" konnte ich nachbauen 
das hat auch alles ganz leicht funktioniert.

Das "I/O Schnittstellen Modul" in QT habe ich mir auch angeguckt, aber 
das ist mir eine Nummer zu schwer das verstehe ich noch nicht ganz.

Ich vermute das sich das gesuchte in der Funktion 
"IoModule::sendCommand" verbirgt. Insbesondere im Abschnitt  "// Wait 
for the response"

Aber ich kann da leider noch nichts in meinem Fall anwenden. Ich 
versuche das weiter zu verstehen, aber vielleicht könnte mir da jemand 
noch einen weiteren Tipp geben.
1
QString IoModule::sendCommand(const QString command, const int maxWait) {
2
    if (!ioDevice || !ioDevice->isOpen()) {
3
        qWarning("Cannot send, no open connection");
4
        return QString::null;
5
    }
6
7
    // Send the command
8
    emit trace(tr("Sending: %1").arg(command));
9
    ioDevice->write(command.toLocal8Bit());
10
    ioDevice->write("\n");
11
12
    // Wait for the response
13
    QTime time;
14
    time.start();
15
    while (ioDevice && !ioDevice->canReadLine()) {
16
        if (time.elapsed()>maxWait) {
17
            qWarning("Receive timeout after command %s",qPrintable(command));
18
            emit trace(tr("Receive timeout"));
19
            return QString::null;
20
        }
21
        QThread::msleep(10);
22
        QCoreApplication::processEvents();
23
    }
24
25
    // Read and return the response
26
    QByteArray buffer=ioDevice->readLine();
27
    QString response=QString::fromLatin1(buffer).trimmed();
28
    emit trace(tr("Received: %1").arg(response));
29
    return response;
30
}

von Stefan F. (Gast)


Lesenswert?

Da wird einfach maxWait lange gewartet, bis eine Zeile Text empfangen 
wurde. Also die Antwort auf das Kommando. Meine i/o Firmware ist so 
gestrickt, dass sie jedes Kommando mit genau einer Zeile Text 
beantwortet.

Der Aufruf von processEvents() ist notwendig, damit während dieser 
Warteschleife andere Ereingnisse noch bearbeitet werden. Sonst würde das 
Programm an dieser Stelle einfrieren.

von Rudi (Gast)


Lesenswert?

Hi Stefan,

jau, das ist mir noch ein bisschen zu abstrakt mich da rein zu fuchsen. 
Ich schaue mir sowas aber gerne immer an, aber das ist mir doch noch was 
zu hoch ;-).
Ich hatte mir das leichter vorgestellt allá Daten empfangen -> in 
richtiger Reihenfolge in Array rein schrieben -> Ausgabe auf den 
"Anzeigen" auf dem Monitor. Aber bei mir Hängts noch an der Reihenfolge 
im Array.

Aber ich habe schonmal aus dem Tutorial gelernt das in diesem Beispiel 
die Daten eingelesen werden und als String abgespeichert werden. :D

Eventuell hat ja jemand noch ein für mich leichteres Beispiel an dem ich 
das besser nachvollziehen kann.

Danke aber auf jedenfall für Eure Hilfe.

Viele Grüße
Rudi

von Stefan F. (Gast)


Lesenswert?

Rudi schrieb:
> Aber ich habe schonmal aus dem Tutorial gelernt das in diesem Beispiel
> die Daten eingelesen werden und als String abgespeichert werden.

Das war mein Plan.

Jetzt kommt der nächste Schritt, den String in ein Array zu Splitten. 
Ich bin sicher, dass du das ohne Hilfe hinbekommen wirst. Die split() 
Methode hast du ja schon gefunden.

von Rudi (Gast)


Lesenswert?

Au, ja da hab ich schon was gefunden. freu ich muss nur mal gleich 
mein Auto zum TüV bringen ;) dann probier ich das aus. Ich melde mich 
nochmal =)

Danke Dir

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.