Laden...

Vom Seriellen Port empfangene Daten in eine Textbox schreiben

Erstellt von Janiiix3 vor 5 Jahren Letzter Beitrag vor 5 Jahren 4.606 Views
J
Janiiix3 Themenstarter:in
38 Beiträge seit 2015
vor 5 Jahren
Vom Seriellen Port empfangene Daten in eine Textbox schreiben

Hallo,

sitze gerade vor einem Problem, wo ich leider alleine nicht weiter komme.
Mein Problem.

Meine GUI bekommt von einem Mikrocontroller Daten via. RS232 geliefert.
Das ganze triggere ich von einem Button aus an. In dem Button Event, trage ich dann die empfangenen Werte in eine Textbox ein. Soweit so gut..

Nun ist das schreiben in die Textbox deutlich schneller als das empfangen der Daten.
Das heißt erst beim zweiten mal betätigen des Buttons stehen die richtig empfangen Werte drinn, vorher nur "0".

Wie kann ich das jetzt am klügsten lösen? warten?

P
441 Beiträge seit 2014
vor 5 Jahren

Hi,

das klingt als würdest du nur einmalig, direkt nach dem Button Klick die Daten lesen?

Generell würde ich das lesen der Daten in den Hintergrund auslagern. Dafür müsstest du allerdings erkennen, wann der µC fertig geschrieben hat - ausser du kannst davon ausgehen, dass wann immer etwas gesendet wird, du es in die Textbox schreiben kannst.

Ich würde dir empfehlen dir die Drei-Schichten-Architektur anzuschauen, dann wärst du vermutlich gar nicht in diese Lage gekommen:
[Artikel] Drei-Schichten-Architektur

16.806 Beiträge seit 2008
vor 5 Jahren

warten?

"Warten" ist in jeder Art von Software ein potentiell falscher Weg.

Die Kommunikation Deiner Software gehört in den entsprechenden Software Layer, was Dir Papst schon verlinkt hat.
Aktionen werden aber nicht durch Warten gelöst, sondern durch Events, auf die dann reagiert werden kann.

J
Janiiix3 Themenstarter:in
38 Beiträge seit 2015
vor 5 Jahren

Ich lese die Daten nur nach der Aktion. Ganz genau. Das mache
Ich aber auch nur damit der BUS vom Mikrokontroller nicht belastet wird.
Das mit dem Drei Schichten Model habe ich schon mehr oder weniger so strukturiert.

Hinweis von Abt vor 5 Jahren

Keine Full Quotes. Danke.
[Hinweis] Wie poste ich richtig?

P
441 Beiträge seit 2014
vor 5 Jahren

Eine serielle Schnittstelle ist kein Bus sondern eine P2P Verbindung. Einzig allein, wenn du darauf liest erzeugst du keine Last auf dem angeschlossenen zweiten Teilnehmer.

Die SerialPort Klasse von .NET ist dabei ein Wrapper um das Serielle Interface und bietet dir zwei Möglichkeiten Daten zu lesen:
-> Ein Event wird ausgelöst, wenn ein neuer Char anliegt
-> Eine Blockierende Methode, die bis zu einem bestimmten Punkt lesen kann (z.B. 1 Char, EOF, EOL)

Was du davon verwendest liegt an dir, aber wenn du nur einmal kurz liest, nachdem du ein Steuerzeichen/-kommando auf die Verbindung gelegt hast, beachtest du nicht die Verarbeitungszeit auf deinem µC und auch nicht die Übertragungszeigt.

P.S.: Der Artikel mit der drei Schichten Architektur bezog sich darauf, dass du den Klick als auslöser genommen hast um auf der Schnittstelle zu schreiben. Die Datenkommunikationssicht deiner Anwendung sollte nichts von einem Klick wissen, sondern lediglich, dass sie jetzt Daten abrufen soll.

J
Janiiix3 Themenstarter:in
38 Beiträge seit 2015
vor 5 Jahren

Das heißt doch wiederum das ich die Daten
Zyklisch gelesen werden müssen und wenn ich
Den Button klicke, müssten die Daten aus dem Speicher übernommen werden oder wie verstehe ich das?

16.806 Beiträge seit 2008
vor 5 Jahren

Naja, das sagt doch Papst, Du hast zwei Möglichkeiten:

-> Ein Event wird ausgelöst, wenn ein neuer Char anliegt
-> Eine Blockierende Methode, die bis zu einem bestimmten Punkt lesen kann (z.B. 1 Char, EOF, EOL)

J
Janiiix3 Themenstarter:in
38 Beiträge seit 2015
vor 5 Jahren

Das habe ich doch bereits erledigt.
Derzeit nutze ich das DataReceived Event dafür.
Wenn ich meine Anzahl an bytes zusammen habe, läuft mein Parser drüber.

P
441 Beiträge seit 2014
vor 5 Jahren

Mit einem Event liest du nicht zyklisch sondern event getriggert.
Sobald du die SerialPort Instanz mit einem seriellen Port verbindest lauscht dieser auf eingehenden Datenverkehr.

Zyklisch wäre, wenn du immer versuchst einen char zu lesen, allerdings ist dann der zyklus nicht zeitbasiert sondern ebenfalls eventbasiert.
Im Endeffekt ist das SerialPort.Read() also nur ein ausprogrammieren des Eventgetriebenen Ansatzes.

Ist es das was du meinst?

J
Janiiix3 Themenstarter:in
38 Beiträge seit 2015
vor 5 Jahren

Also. Wenn ich auf den Button klicke, sende ich einen String zu meinem Mikrocontroller. Dieser wertet den Befehl aus und schickt ggf. eine Antwort. Diese kann je nach Befehl von der Länge und vom Inhalt variieren.

Den ganzen Stream den der Mikrocontroller sendet und der Pc empfängt wird Event gesteuert.

Hier das Event


         private void serialPort1_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
        {
            cmd parser = new cmd();

            /*  Puffer anlegen..
            */
            byte[] buffer = new byte[ 50 ];

            /*  Empfangene Daten abholen
            */
            length += Client.Read(buffer, 0, buffer.Length);

            /*  Ist die mindest Anzahl an Bytes empfangen worden?
            */
            if ( length < (int)cmd.Answer_Byte_Position_enum.CMD_ANSWER_DATA_BYTE_BEGINN )
            {
                return;
            }

            length = 0;

            /*  Sollen die empfangenen Daten angezeigt werden?
            */
            if ( showInStreams.CheckState == CheckState.Checked )
            {
                string __debug__ = null;
                for (uint x = 0; x < 10; x++)
                {
                    __debug__ += Convert.ToByte(buffer[x]).ToString() + " ";
                }
                MessageBox.Show(__debug__ , "Eingehender Stream" , MessageBoxButtons.OK , MessageBoxIcon.Information);
            }

            richtxtbx_rawdata.Invoke(new Action(() => richtxtbx_rawdata.Clear()));

            /*  Kommando untersuchen..
            */
            ReceiveCommando = parser.ParseCommandoReceive( buffer );

            /*  Wurde kein gültiges Kommando gefunden, zurück!
            */
            if( ReceiveCommando.noBeginn == true )
            {
                return;
            }

            /*  Checksummen vergleich..
            */
            if ( ReceiveCommando.ChecksummOk )
            {
                CommandoCrcOkCounter++;
                txtbx_id.Invoke(new Action(() => txtbx_crc_ok.Text = CommandoCrcOkCounter.ToString())); 
            }
            else
            {
                CommandoCrcErrorCounter++;
                txtbx_crc_error.Invoke(new Action(() => txtbx_crc_error.Text = CommandoCrcErrorCounter.ToString()));
            }

            /*  Wenn Checksumme Ok, dann Bytes abspeichern..
            */
            if ( ReceiveCommando.ChecksummOk )
            {
                txtbx_id.Invoke(new Action(() => txtbx_id.Text = ReceiveCommando.Id.ToString()));
                txtbx_exitcode.Invoke(new Action(() => txtbx_exitcode.Text = ReceiveCommando.Exitcode.ToString()));
                txtbx_rawdatabytes.Invoke(new Action(() => txtbx_rawdatabytes.Text = ReceiveCommando.DataLen.ToString()));
                txtbx_length.Invoke(new Action(() => txtbx_length.Text = ReceiveCommando.AnswerLen.ToString()));
                txtbx_crc.Invoke(new Action(() => txtbx_crc.Text = ReceiveCommando.Checksumm.ToString()));

                if ( ReceiveCommando.DataLen > 0 )
                {
                    for( uint x = 0; x < ReceiveCommando.DataLen; x++ )
                    {
                        richtxtbx_rawdata.Invoke(new Action(() => richtxtbx_rawdata.AppendText
                        (
                            "[" + x.ToString() + "]: " + ReceiveCommando.Data[x] + "\r\n"
                        )));
                    }
                }
            }
        }

5.657 Beiträge seit 2006
vor 5 Jahren

Hi Janiiix3,

Sagtest du nicht:

Das mit dem Drei Schichten Model habe ich schon mehr oder weniger so strukturiert.

Du hast da aber alles zusammen: UI, Logik zur Auswertung der Daten, Logik zur Kommunikation mit dem Microcontroller. Alles in einer Methode. So läßt sich das nur schwer lesen, debuggen und testen.

Mein Vorschlag: Schreib dir eine Klasse, die die Kommunikation mit dem Controller übernimmt, eine Methoden zum Senden von Befehlen anbietet, und über eine Antwort des Controllers per Event informiert. Dann kannst du diesen Teil debuggen und testen. Dann brauchst du nur noch eine Klasse, die die empfangenen Daten auswertet, und dann kannst die diese in der UI verwenden, um die die Steuerung zu übernehmen und die Antworten anzuzeigen.

Weeks of programming can save you hours of planning

J
Janiiix3 Themenstarter:in
38 Beiträge seit 2015
vor 5 Jahren

Das ist eine gute Idee. Nur löst es nicht das Problem was ich eigentlich habe.
Ich möchte gerne wissen wann die Übertragung abgeschlossen ist und ein Kommando empfangen wurde.
Wie bekomme ich das innerhalb des Buttons raus?
Es sind keine großen Anforderung an die GUI.
Muss ich irgendwie solange in dem Button
Ereignis warten bis ein solches Kommando
Empfangen wurde...

5.657 Beiträge seit 2006
vor 5 Jahren

Die Fragen wurden dir ja bereits beantwortet. Wenn du das noch nicht verstanden hast, schau mal in diese Artikel:

SerialPort.DataReceived Event
Events (C# Programming Guide)

Es geht darum: Du mußt nicht warten. Du wirst benachrichtigt!

Weeks of programming can save you hours of planning

J
Janiiix3 Themenstarter:in
38 Beiträge seit 2015
vor 5 Jahren

Wir schreiben aneinander vorbei.
Das Serialport Event nutze ich doch bereits..

P
441 Beiträge seit 2014
vor 5 Jahren

Ist dein Problem, dass beim auslösen des SerialPort.DataReceived Events noch nicht alle Daten, die du benötigst im Buffer liegen?

Wenn dem so ist, dann musst du dir einfach das zwischenergebnis merken und die Auswertung erst dann starten, wenn alles da ist. --> Das wäre durch eine eigene Klasse zum Handling mit dem SerialPort einfacher gelöst.

Merke: Ein DataReceived Event könnte auch zwei unterschiedliche Informationen vom µC enthalten.

Da du aber nicht genau sagst, was der Fehler oder das Fehlverhalten ist können wir nur ein wenig reinstochern...

Interessant wäre:
-> Was kommt an?
-> Was sollte ankommen?

J
Janiiix3 Themenstarter:in
38 Beiträge seit 2015
vor 5 Jahren

Also anbei mal ein Foto von meiner GUI.

Empfangenes Kommando: Dort stehen die ganzen Daten die angekommen sind.

Multimeter: Volt -> Hier sollte das Ergebnis der "Roh Daten" eingetragen sein.
In dem Beispiel "[0]: 42 [1]: 1" sind das meine Roh Daten. Diese werden noch berechnet und dann sollten diese in der Textbox neben "Volt" eingetragen werden.

Beim zweiten mal "Lesen" stehen dann die entsprechenden Daten drin, da sie dann ja schon im Speicher vorhanden sind. Bei dem ersten "klick" jedoch noch nicht weil ich nicht auf die Daten warten kann..
Da ist mein Problem.. Ich muss warten bis diese angekommen sind.

P
64 Beiträge seit 2011
vor 5 Jahren

Erstaunlich, dass das überhaupt funktioniert.

Du schmeißt nämlich so den Inhalt deines Buffers weg, wenn nicht genug daten eingetroffen sind.

J
Janiiix3 Themenstarter:in
38 Beiträge seit 2015
vor 5 Jahren

Wo schmeiße ich denn den Inhalt weg?

4.931 Beiträge seit 2008
vor 5 Jahren

Bei jedem DataReceived löscht du doch die TextBox-Daten:

richtxtbx_rawdata.Invoke(new Action(() => richtxtbx_rawdata.Clear()));

Und da aber nicht sichergestellt ist, daß bei jedem Ereignisaufruf die kompletten Daten vorliegen, mußt du eben einen eigenen Puffer benutzen.

Du kannst dir auch mal Template SerialPort anschauen.

J
Janiiix3 Themenstarter:in
38 Beiträge seit 2015
vor 5 Jahren

Das ist nur ein Zähler. Der zeigt mir bei jedem neuen Kommando an wie viele "Nutzdaten" mit gesendet wurden.

P
64 Beiträge seit 2011
vor 5 Jahren

Wo schmeiße ich denn den Inhalt weg?


  */
            byte[] buffer = new byte[ 50 ];

            /*  Empfangene Daten abholen
            */
            length += Client.Read(buffer, 0, buffer.Length);

            /*  Ist die mindest Anzahl an Bytes empfangen worden?
            */
            if ( length < (int)cmd.Answer_Byte_Position_enum.CMD_ANSWER_DATA_BYTE_BEGINN )
            {
                return;
            }

Du legst den Buffer in der Methode an. Beim Verlassen dieser, werden die Daten weggeschmissen. Du speicherst die nirgends.

T
708 Beiträge seit 2008
vor 5 Jahren

Pre-Post: panicJonny war schneller, aber ich denke ich bin eine Idee ausführlicher 😉

Wo schmeiße ich denn den Inhalt weg?

Hier:

  private void serialPort1_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
        {
            /*  Puffer anlegen..
            */
            byte[] buffer = new byte[ 50 ];

Du erstellst den Puffer innerhalb des Datenempfangs.

Wenn Du nun erwartest, dass "Test123" eingeht, der Port aber schnell genug reagiert, kommt nur "Tes" an und in dem nächsten ausgelösten Event "t123".
Beides ist von der Länge her kleiner als Deine erwarteten Daten. Also springst Du aus der Methode und die Daten sind futsch.

Eine mögliche Lösung ist es einen Empfangspuffer außerhalb Deiner Methode anzulegen und solange zusammensetzen, bis Deine Länge erreicht ist. Dann kannst Du diesen auswerten und wieder löschen.

Zum Aufbau:
Verwende eine eigene Klasse, die sich genau um das o.g. kümmert. Lerne wie man selbst Events erstellt und nutze es, wenn Deine Daten komplett sind.
Erstelle Dir eine Daten-Klasse, die Deine Werte beinhaltet und verknüpfe diese per Databinding an die Controls.
Nun füllst/änderst Du den Wert in Deiner Datenklasse aus dem Event aus Deiner SerialPort-Klasse mit der Pufferungs-Logik.

4.931 Beiträge seit 2008
vor 5 Jahren

Verstehst du deinen eigene Code nicht?
richtxtbx_rawdata ist doch das selbe Control, in der du die ReceiveCommando.Data-Werte einträgst:


richtxtbx_rawdata.Invoke(new Action(() => richtxtbx_rawdata.AppendText
(
    "[" + x.ToString() + "]: " + ReceiveCommando.Data[x] + "\r\n"
)));

Ich kann dir nur raten, deinen Code noch mal komplett neu aufzusetzen (wie von den anderen schon geschrieben).

@panicJonny:
Er parst jedesmal den Puffer:

ReceiveCommando = parser.ParseCommandoReceive( buffer );

Das führt aber im Endeffekt zum gleichen Ergebnis wie bei meiner Aussage oben.

J
Janiiix3 Themenstarter:in
38 Beiträge seit 2015
vor 5 Jahren

Wie genau gehe ich vor?
Ich empfange Daten via Serial Port Event.
Von wo aus soll ich den Parser aufrufen? Noch in dem Event?

16.806 Beiträge seit 2008
vor 5 Jahren

Im Prinzip ist der serielle Port noch Teil Deiner Kommunikationsschicht.
Den Parser kannst Du ja erst aufrufen - soweit ich das verstehe - wenn Du alle notwendigen Informationen hast.

Ergo muss nun die Logik, die entscheidet, wann Du alle Informationen hast, dann den Parser aufrufen.

T
461 Beiträge seit 2013
vor 5 Jahren

Wie genau gehe ich vor?

a) eine eigene Klasse erstellen, die sich um die Kommunikation mit dem Seriellen Port kümmert
b) darin eine globale Variable Klassenvariable (Feld) erstellen, die den gesamten Buffer einer Anfrage-Antwort darstellt
c) wenn der Button im ViewModel betätigt wird, kann die Anfrage in deiner neuen Klasse gestellt werden (Methode die du dafür erzeugt hast)
d) während in deiner neuen Klasse von der Anfrage immer wieder Antworten reinkommen, alle Teile in den globalen Buffer die Klassenvariable (Buffer) schreiben, solange bis alles da ist
e) sind nun alle Daten eingetrudelt, können sie geparst werden und am besten danach den globalen Buffer leeren/null'en (wie auch immer)
f) Deinem ViewModel mitteilen, daß die Anfrage beendet wurde und sie mittels Event benachrichtigen
g) Im ViewModel kann dann der fertige Wert angezeigt werden (Im Event können alle möglichen Informationen mitgegeben werden, Rohdaten, geparste Daten,...)

Wie was wo funktioniert, siehe andere Beiträge...

SG Thomas

Ich habe den Titel mal angepasst, so dass Suchende auch etwas damit anfangen können. EDIT: Ich sollte beim Wort "Shift" im Titel das "f" nicht vergessen... 😄

5.657 Beiträge seit 2006
vor 5 Jahren

eine globale Variable erstellen

Bitte nicht.

Weeks of programming can save you hours of planning

3.003 Beiträge seit 2006
vor 5 Jahren

Naja, er schrieb "darin eine globale Variable erstellen", womit er wahrscheinlich ein Feld der Klasse meint. Aber ja, wording ist wichtig 😉.

LaTino

"Furlow, is it always about money?"
"Is there anything else? I mean, how much sex can you have?"
"Don't know. I haven't maxed out yet."
(Furlow & Crichton, Farscape)

T
461 Beiträge seit 2013
vor 5 Jahren

a) eine eigene Klasse erstellen, die sich um die Kommunikation mit dem Seriellen Port kümmert
b) **:::

Ich wüßte nicht was man da falsch verstehen kann wenn man richtig liest 😉

Ich habe den Titel mal angepasst, so dass Suchende auch etwas damit anfangen können. EDIT: Ich sollte beim Wort "Shift" im Titel das "f" nicht vergessen... 😄

5.657 Beiträge seit 2006
vor 5 Jahren

In computer programming, a global variable is a variable with global scope

Weeks of programming can save you hours of planning

F
10.010 Beiträge seit 2004
vor 5 Jahren

Da muss ich Abt ausnahmsweise recht geben.

Eine Globale Variable und eine Klassenvariable sind zwei verschiedene paar Schuhe

C
224 Beiträge seit 2009
vor 5 Jahren

Gemeint ist wohl:
b) darin eine öffentliche Eigenschaft erstellen, die den gesamten Buffer einer Anfrage-Antwort darstellt

T
461 Beiträge seit 2013
vor 5 Jahren

Hallo,

ok ich gebe mich geschlagen 😉

Ich meinte eine Klassenvariable die Klassenintern global ist. Wußte ehrlich gesagt gar nicht, daß dieser Ausdruck so gar nicht verwendet werden kann, da ich noch nie eine 'globale Variable' selbst erstellen mußte oder damit direkt zu tun hatte.

Danke für die Info 😁

b) darin eine öffentliche Eigenschaft erstellen, ...

Nein, mit Variable meine ich ein 'Feld' und keine Eigenschaft, ist ja dann doch ein eindeutiger Unterschied. Zudem sind bei mir Felder immer 'private'.

Ich habe den Titel mal angepasst, so dass Suchende auch etwas damit anfangen können. EDIT: Ich sollte beim Wort "Shift" im Titel das "f" nicht vergessen... 😄

P
64 Beiträge seit 2011
vor 5 Jahren

@panicJonny:
Er parst jedesmal den Puffer:

ReceiveCommando = parser.ParseCommandoReceive( buffer );  

Das führt aber im Endeffekt zum gleichen Ergebnis wie bei meiner Aussage oben.

Nein, tut er nicht. Wenn nicht genügend Daten ankommen, springt er vorher aus der Methode mit return raus.

C
224 Beiträge seit 2009
vor 5 Jahren

Mit öffentliche Eigenschaft erstellen dachte ich eher an sowas:


        public class Communicator : IDisposable
        {
            //öffentliche Eigenschaft
            private MemoryStream _buffer = new MemoryStream();
            public byte[] buffer { get { return _buffer.ToArray(); } }

            public void AddBuffer(byte[] toAdd)
            {
                _buffer.Write(toAdd, 0, toAdd.Length);
            }

            private void _FreeManaged()
            {
                if (_buffer != null)
                {
                    _buffer.Dispose();
                    _buffer = null;
                }
            }
            private void _FreeUnmanaged()
            {
                //
            }

            public bool isDisposed { get { return this._disposed; } }

            #region Dispose

            // Track whether Dispose has been called.
            private bool _disposed = false;

            // Implement IDisposable.
            // Do not make this method virtual.
            // A derived class should not be able to override this method.
            public void Dispose()
            {
                Dispose(true);
                // This object will be cleaned up by the Dispose method.
                // Therefore, you should call GC.SupressFinalize to
                // take this object off the finalization queue
                // and prevent finalization code for this object
                // from executing a second time.
                GC.SuppressFinalize(this);
            }

            // Dispose(bool disposing) executes in two distinct scenarios.
            // If disposing equals true, the method has been called directly
            // or indirectly by a user's code. Managed and unmanaged resources
            // can be disposed.
            // If disposing equals false, the method has been called by the
            // runtime from inside the finalizer and you should not reference
            // other objects. Only unmanaged resources can be disposed.
            private void Dispose(bool disposing)
            {
                // Check to see if Dispose has already been called.
                if (!this._disposed)
                {
                    // If disposing equals true, dispose all managed
                    // and unmanaged resources.
                    if (disposing)
                    {
                        // Dispose managed resources.
                        _FreeManaged();
                    }

                    // Call the appropriate methods to clean up
                    // unmanaged resources here.
                    // If disposing is false,
                    // only the following code is executed.
                    {
                        //Unmanaged code like Handles
                        _FreeUnmanaged();
                    }

                    // Note disposing has been done.
                    _disposed = true;

                }
            }

            // Use C# destructor syntax for finalization code.
            // This destructor will run only if the Dispose method
            // does not get called.
            // It gives your base class the opportunity to finalize.
            // Do not provide destructors in types derived from this class.
            ~Communicator()
            {
                // Do not re-create Dispose clean-up code here.
                // Calling Dispose(false) is optimal in terms of
                // readability and maintainability.
                Dispose(false);
            }
            #endregion
        }

Aufruf:


            using (Communicator communicator = new Communicator())
            {
                communicator.AddBuffer(Encoding.Default.GetBytes("Hallo "));
                communicator.AddBuffer(Encoding.Default.GetBytes("Welt."));

                //Ausgabe: "Hallo Welt."
                byte[] current = communicator.buffer;
                MessageBox.Show(Encoding.Default.GetString(current, 0, current.Length));
            }

T
461 Beiträge seit 2013
vor 5 Jahren

Mit öffentliche Eigenschaft erstellen dachte ich eher an sowas:

Das ist dann eher eine Design-Frage, mein Beispiel hätte anders ausgesehen 😉

Gemeint ist wohl:
b) darin eine öffentliche Eigenschaft erstellen....

Hätte eher gesagt werden müssen, Meine Idee wäre:

Aber offtopic..

Ich habe den Titel mal angepasst, so dass Suchende auch etwas damit anfangen können. EDIT: Ich sollte beim Wort "Shift" im Titel das "f" nicht vergessen... 😄