Forum: PC-Programmierung c# <-> Serielle Schnittstelle


von Jan H. (janiiix3)


Lesenswert?

Moin Moin,

ich brauche mal euer Wissen..
Und zwar sende ich Befehle von meiner GUI an einen µC. Das funktioniert 
soweit auch wunderbar ( wenn es Zyklisch passiert.. ). Das heißt ich 
arbeite die ganzen Befehle mit nem Timer nach und nach ab..

Nun habe ich versucht direkt nach dem Start ein paar Infos vom µC 
abzurufen.. Das klappt leider nicht. Ich muss an den stellen wo ich die 
Informationen haben möchte, warten bis das Datenpaket eingetroffen ist..

P.s

Die Daten hole ich via Event von der Seriellen ab.

Wie stelle ich das an?
- Delay ist keine Alternative..
- while natürlich auch nicht..

gibt es noch andere Möglichkeiten?

: Bearbeitet durch User
von Martin S. (sirnails)


Lesenswert?

Ist ja klar, dass das nicht geht. Schau mal in Zeile 37. Da hast Du eine 
Race Condition. Die serielle Schnittstelle ist Asynchron.

Ich würde da eher schreiben.
1
// Daten vom RS-232 auslesen
2
s = port.ReadLine();

Dann sollte es gehen.

von Oliver R. (superberti)


Lesenswert?

Hi,

Zeile 37? Ich seh' hier keinen Code...

von Martin S. (sirnails)


Lesenswert?

Oliver R. schrieb:
> Hi,
>
> Zeile 37? Ich seh' hier keinen Code...

Ich auch nicht. Das war auch purer Sarkasmus von mir mit einem hauch 
ernst gemeinter Hilfe. Der asynchrone Zugriff hat mir auch schon viel 
Ärger bereitet.

Aber wenn man als TO nichtmal in der Lage ist, den Quelltext anzuhängen, 
dann ist ihm wahrlich nicht zu helfen.

von Oliver R. (superberti)


Lesenswert?

Oha, jetzt blick ich's auch :-()

Nein, die Kristallkugel hab' ich auch nicht mit...

von Micha (Gast)


Lesenswert?

Du müsstest dort eine StateMachine verwenden und die am einfachsten per 
Timer aufrufen. Dort drin kannst du dann Daten schicken und auf Daten 
warten.

Die Schrittkette kannst du dann bereits am Programmstart ausführen 
(zumindest anstoßen).

von W.S. (Gast)


Lesenswert?

Micha schrieb:
> Du müsstest dort eine StateMachine verwenden

Im Prinzp: ja. Praktisch geht sowas auch per Timer, der in sinnvollen 
Abständen einen Timerevent in das Framework feuert. Immerhin puffert das 
OS ja den ganzen seriellen Stream.

Aber normalerweise macht man das eher so, daß man sich in seinem eigenen 
Programm einen zweiten Thread startet, der rein garnix mit dem Framework 
der Oberfläche zu schaffen hat. Dieser Thread arbeitet streng 
prozedural, wartet also per sleep oder so, bis die Serielle wieder ein 
Zeichen verfügbar hat. Dann wird das entweder zu irgendwelchen 
Datenstrukturen zusammengesetzt, die nur der TO kennt, oder es wird für 
den GUI_Thread zwischengepuffert und für den GUI-Thread ein dazu 
passender Event ausgelöst.

Das ist natürlich etwas komplizierter als schlichtweg im GUI-Thread auf 
I/O zu warten oder per Timer zu pollen. Aber letzteres geht in manchen 
Fällen auch.

W.S.

von Jan H. (janiiix3)


Lesenswert?

Was soll denn mein Quellkode groß dazu beitragen? Im Endeffekt brauche 
ich Ideen wie ich am besten auf ein komplett empfangenes Kommando warten 
kann..
Aber klar, ich poste mal mein Kode..

Hier empfange ich die Daten:
1
        private void Client_DataReceived(object sender, SerialDataReceivedEventArgs e)
2
        {
3
            /*  Empfangene Daten abholen
4
            */
5
            try
6
            {
7
                length += Client.Read(buffer, length, 50 );
8
            }
9
            catch { }
10
            
11
12
            /*  Kommando Start Parsen
13
             *  Rückgabewert: - 1 = Kein Start gefunden..
14
            */
15
            int index = Cmd.ParseCommandoStart(buffer);
16
            if (index != -1)
17
            {
18
                /*  Anzahl der zu empfangenen Bytes auslesen
19
                    * HIER WIRD NOCH KEIN CRC BERECHNET!
20
                    * Es könnten Übertragungsfehler nicht erkannt werden..
21
                */
22
                bytesToReceive = buffer[index + (byte)Cmd.Communication_Header_Enum.CMD_HEADER_LENGHT];
23
            }
24
25
            /*  Wurden alle Bytes empfangen?
26
                *  Wenn nicht, direkt wieder raus hier!
27
            */
28
            if (length < bytesToReceive)
29
            {
30
                return;
31
            }
32
            else
33
            {
34
                length = 0;
35
                bytesToReceive = 0;
36
            }
37
38
            /*  Kommando untersuchen..
39
            */
40
            Cmd.ParseCommando( buffer, ref Cmd.CommandoParsed );
41
            manaualResetEvent.Set();
42
            NewCommando = true;
43
        }

Das hört sich nach einer guten Idee an. Den Thread der die ganzen Daten 
sendet bzw. empfängt kann dann ja ruhig wie Du schon sagtest, per Sleep 
warten..

W.S. schrieb:
> Aber normalerweise macht man das eher so, daß man sich in seinem eigenen
> Programm einen zweiten Thread startet, der rein garnix mit dem Framework
> der Oberfläche zu schaffen hat. Dieser Thread arbeitet streng
> prozedural, wartet also per sleep oder so, bis die Serielle wieder ein
> Zeichen verfügbar hat. Dann wird das entweder zu irgendwelchen
> Datenstrukturen zusammengesetzt, die nur der TO kennt, oder es wird für
> den GUI_Thread zwischengepuffert und für den GUI-Thread ein dazu
> passender Event ausgelöst.

von Oliver R. (superberti)


Lesenswert?

Hi,

Natürlich hilft der Quellcode weiter. Was denn sonst?
Leider hast Du uns die Stelle, wo Du auf die Antwort des MCs wartest, 
trotzdem vorenthalten!
Deshalb vermute ich mal ganz stark, dass Du dort auf "manaualResetEvent" 
wartest. Dabei blockierst Du Dein UI und der Dispatcher kann dann auch 
"Client_DataReceived" nicht mehr ausführen.

Dafür gibt es zwei Lösungen:

1. Mach es so wie von W.S. vorgeschlagen

2. Benutze async/await, dann läuft der Dispatcher weiter:
1
private async void WarteAufEvent()
2
{
3
  await Task.Run(new Action(() =>
4
  {
5
    [...]
6
    manaualResetEvent.Wait();
7
    [...]
8
  }));
9
}

Gruß,
Oliver

von Jan H. (janiiix3)


Lesenswert?

Oliver R. schrieb:
> Leider hast Du uns die Stelle, wo Du auf die Antwort des MCs wartest,
> trotzdem vorenthalten!

Ah, sorry!
Also nach dem öffnen des Portes, stelle ich ein paar Anfragen an nen µC 
und möchte halt gerne warten bis diese ankommen.
1
                Jobs.Read.Version(sender, e);
2
                Serial.manaualResetEvent.Reset();
3
                Serial.manaualResetEvent.WaitOne();

Dispatcher..?! Okay da muss ich mich noch einlesen.
Das muss ich quasie machen, wenn ich die Anfragen stelle, richtig?

von Wolfgang H. (drahtverhau)


Lesenswert?

Hallo!
Hier der beispielcode von ms... Ist zwar für Raspberry, kann aber 
angepasst werden...

using Windows.Storage.Streams;
using Windows.Devices.Enumeration;
using Windows.Devices.SerialCommunication;

public async void Serial()
{
    string aqs = SerialDevice.GetDeviceSelector("UART0"); 
/* Find the selector string for the serial device   */
    var dis = await DeviceInformation.FindAllAsync(aqs); 
/* Find the serial device with our selector string  */
    SerialDevice SerialPort = await SerialDevice.FromIdAsync(dis[0].Id); 
/* Create an serial device with our selected device */

    /* Configure serial settings */
    SerialPort.WriteTimeout = TimeSpan.FromMilliseconds(1000);
    SerialPort.ReadTimeout = TimeSpan.FromMilliseconds(1000);
    SerialPort.BaudRate = 9600; 
/* mini UART: only standard baudrates */
    SerialPort.Parity = SerialParity.None; 
/* mini UART: no parities */
    SerialPort.StopBits = SerialStopBitCount.One; 
/* mini UART: 1 stop bit */
    SerialPort.DataBits = 8;

    /* Write a string out over serial */
    string txBuffer = "Hello Serial";
    DataWriter dataWriter = new DataWriter();
    dataWriter.WriteString(txBuffer);
    uint bytesWritten = await 
SerialPort.OutputStream.WriteAsync(dataWriter.DetachBuffer());

    /* Read data in from the serial port */
    const uint maxReadLength = 1024;
    DataReader dataReader = new DataReader(SerialPort.InputStream);
    uint bytesToRead = await dataReader.LoadAsync(maxReadLength);
    string rxBuffer = dataReader.ReadString(bytesToRead);
}

Quelle: 
https://docs.microsoft.com/de-de/windows/iot-core/learn-about-hardware/pinmappings/pinmappingsrpi

Musst eben den read Teil in einen extra Thread packen und ein Do 
rundherum machen. Hoffe das hilft

von Oliver R. (superberti)


Lesenswert?

Hallo nochmal,

der Dispatcher arbeitet bei WINDOWS-Programmen alle vorkommenden 
Ereignisse (z.B. Button gedrückt oder Daten vom Port empfangen etc.) ab 
und zwar synchron.
D.h. ich kann nicht gleichzeitig mit WaitOne() auf ein 
Synchronisationsobjekt warten (der ManualResetEvent) und meine Daten vom 
Port empfangen.
Pack Deine drei Zeilen unter das await... Statement und gut ist.
Aber: Benutze einen AutoResetEvent. Ansonsten muss das .Reset() NACH dem 
WaitOne() erfolgen. Wie der Name schon sagt macht das ein AutoResetEvent 
automatisch.
Da Du aber sicherlich auf mehrere Datenpakete dauerhaft warten möchtest, 
ist die Vorgehensweise von W.S. deutlich übersichtlicher. Sammel in 
Deinem Thread die Daten und löse im GUI ein Ereignis aus, wenn Du damit 
fertig bist.

Gruß,

von Martin S. (sirnails)


Lesenswert?

Oder man nutzt DataBinding. Das macht das arbeiten mit asynchronen Tasks 
und Threads auch sehr komfortabel.

von Jan H. (janiiix3)


Lesenswert?

Nö, also das mit dem warten klappt nach wie vor nicht.
1
                    Jobs.Read.Version(sender, e);
2
                    Serial.WaitOfCommando();
3
                    Jobs.Read.HardwareVersion(sender, e);
4
                    Serial.WaitOfCommando();
5
                    Jobs.Read.State(sender, e);
6
                    Serial.WaitOfCommando();
7
                    Jobs.Read.Serialnumber(sender, e);
8
                    Serial.WaitOfCommando();
9
                    Jobs.Read.CalibrationDate(sender, e);
10
                    Serial.WaitOfCommando();
1
        public static async void WaitOfCommando()
2
        {
3
            await Task.Run(new Action(() =>
4
            {
5
                autoResetEvent.WaitOne();
6
            }));
7
        }

Mache ich noch was falsch?

von Jan H. (janiiix3)


Lesenswert?

Wenn ich zwischen den senden der Kommandos eine Messagebox aufrufe,
kommen alle Daten rein wie es soll..
1
                    DialogResult res;
2
3
                    Jobs.Read.Version(sender, e);
4
                    res = MessageBox.Show("Okay?");
5
6
                    Jobs.Read.CalibrationDate(sender, e);
7
                    res = MessageBox.Show("Okay?");
8
9
                    Jobs.Read.HardwareVersion(sender, e);
10
                    res = MessageBox.Show("Okay?");

von Oliver R. (superberti)


Lesenswert?

Hi,

es wäre wirklich gut, wenn Du Dir die Doku zum async/await-Pattern 
durchlesen würdest. Wir kennen hier nur Auszüge Deines Codes, das macht 
eine Hilfe nicht gerade einfacher.
Es sieht so aus, als ob Du die Methode mit async/await in eine eigene 
Klasse gepackt hast. Das Pattern funktioniert aber nur dann, wenn die 
Methode Bestandteil des GUIs ist. Also eine Member-Funktion des Forms!

Gruß,

von Martin S. (sirnails)


Lesenswert?

Warum sollte man aber auch den Zugriff auf die serielle Schnittstelle im 
GUI-Thread haben wollen? Das ist doch vollkommener Unsinn?!

Wenn ich so meine RS232 Codes durchsehe, dann erstelle ich eine Instanz 
der Klasse, habe das asynchrone Data-Receive-Event und hole mir die 
Daten dann per Invoke in meinen Thread rüber.

Das läuft absolut stabil und ich habe seitdem keinerlei Probleme mehr 
mit Race Conditions oder Zugriffsverletzungen.

von Jan H. (janiiix3)


Lesenswert?

Oliver R. schrieb:
> Hi,
>
> es wäre wirklich gut, wenn Du Dir die Doku zum async/await-Pattern
> durchlesen würdest. Wir kennen hier nur Auszüge Deines Codes, das macht
> eine Hilfe nicht gerade einfacher.
> Es sieht so aus, als ob Du die Methode mit async/await in eine eigene
> Klasse gepackt hast. Das Pattern funktioniert aber nur dann, wenn die
> Methode Bestandteil des GUIs ist. Also eine Member-Funktion des Forms!
>
> Gruß,

Sie ist ein Member der eigentlichen Form. Mein Serielles "Modul" ist in 
einer eigenen Klasse, stört das?

von Oliver R. (superberti)


Lesenswert?

Ja!

es gibt ja viele Wege zum Ziel, besonders mit C#. Ich selbst benutze in 
keinem meiner Programme diese eventbasierte serielle Klasse, sondern 
stets synchrone serielle read/write Funktionen, die in eigenen Threads 
laufen.
Fast immer muss man nämlich auf eine vollständige Antwort von einem 
Gerät warten und das Aufteilen der Antwort in mehrere Events macht 
irgendwie überhaupt keinen Sinn.
Tu Dir also selbst einen Gefallen und mach es genauso.

Gruß,
Oliver

von Jan W. R. (Gast)


Lesenswert?

Martin S. schrieb :
>Warum sollte man aber auch den Zugriff auf die serielle Schnittstelle im
>GUI-Thread haben wollen? Das ist doch vollkommener Unsinn?!

>Wenn ich so meine RS232 Codes durchsehe, dann erstelle ich eine Instanz
>der Klasse, habe das asynchrone Data-Receive-Event und hole mir die
>Daten dann per Invoke in meinen Thread rüber.

>Das läuft absolut stabil und ich habe seitdem keinerlei Probleme mehr
>mit Race Conditions oder Zugriffsverletzungen.

Das sehe ich genau so. Ich habe es auch so gelösst, funktioniert sehr 
zuverlässig.


Folgendes ist aber zu bedenken (vielleicht ist das, das Problem was Du 
hast):

Wenn der Mikrokontroller Daten (n Bytes) an PC Sendet, dann „kommen die 
Daten in Häppchen an“.  D.h. der DataReceivedHandler wird mehrfach 
aufgerufen. Beim jeden Aufruf  kriegt man das nächste Häppchen was man 
abspeichern/verarbeiten kann. (Bitte sehe mein Beispiel)
1
        private void DataReceivedHandler(object sender, SerialDataReceivedEventArgs e)
2
        {
3
            SerialPort sp = (SerialPort)sender;
4
            string indata = sp.ReadExisting();
5
            textBox1.InvokeIfRequired(() => textBox1.Text += indata); // +=indata); (Häpchenweise Anhängen)
6
        }

InvokeIfRequired ist eine sinvolle Ergenzungsmethode für das 
Threadsichere Aufrufe von Windows Forms-Steuerelementen. mehr Info unter 
:
https://stackoverflow.com/questions/2367718/automating-the-invokerequired-code-pattern

Gruss,
Jan

von Martin S. (sirnails)


Lesenswert?

Jan W. R. schrieb:
> Wenn der Mikrokontroller Daten (n Bytes) an PC Sendet, dann „kommen die
> Daten in Häppchen an“.

Natürlich tun sie das. Deswegen empfängt man sowas auch nicht im 
GUI-Thread.

Man hat eine Klasse, die die Funktionalität RS232 kapselt, erstellt 
einen neuen Thread in dem die Klasse arbeitet und wenn innerhalb der 
Klasse nach n Events festgestellt wird, dass die Daten nun vollständig 
sind, dann feuert sie ein Event an den GUI - Thread, der sagt, dass die 
Verarbeitung nun abgeschlossen ist.

DANN hat man auch sicher verwertbare Daten und erst dann ist es 
überhautp sinnvoll, etwas anzuzeigen. Es sei denn, man steht drauf, 
zuzusehen, wie die Daten kommen. Dann muss man das Event halt bei jedem 
Receive auslösen.

C# bietet doch geniale Möglichkeiten, das ganze so abstrakt zu halten, 
dass man im GUI Thread sich mit nichts mehr herumzuärgern braucht.

Das Konzept ist dann absolut failsafe.

von Jan W. R. (Gast)


Lesenswert?

Martin S schrieb :

>DANN hat man auch sicher verwertbare Daten und erst dann ist es
>überhautp sinnvoll, etwas anzuzeigen. Es sei denn, man steht drauf,
>zuzusehen, wie die Daten kommen. Dann muss man das Event halt bei jedem
>Receive auslösen.

>C# bietet doch geniale Möglichkeiten, das ganze so abstrakt zu halten,
>dass man im GUI Thread sich mit nichts mehr herumzuärgern braucht.

>Das Konzept ist dann absolut failsafe.

Das hört sich gut an.
Was C# angeht bin ich ein Anfänger.

Das Multithreading finde ich nicht so ganz einfach.
Vielleicht ist jemand so freundlich und bereit ein minimalistisches 
Beispiel zu posten?

z.B.
Eine SerielDataReceive Klasse die nach dem N bytes empfangen wurden ein 
event an das GUI thread schickt.

Gruss,
Jan

von Jan H. (janiiix3)


Lesenswert?

Jan W. R. schrieb:
> Martin S schrieb :
>
>>DANN hat man auch sicher verwertbare Daten und erst dann ist es
>>überhautp sinnvoll, etwas anzuzeigen. Es sei denn, man steht drauf,
>>zuzusehen, wie die Daten kommen. Dann muss man das Event halt bei jedem
>>Receive auslösen.
>
>>C# bietet doch geniale Möglichkeiten, das ganze so abstrakt zu halten,
>>dass man im GUI Thread sich mit nichts mehr herumzuärgern braucht.
>
>>Das Konzept ist dann absolut failsafe.
>
> Das hört sich gut an.
> Was C# angeht bin ich ein Anfänger.
>
> Das Multithreading finde ich nicht so ganz einfach.
> Vielleicht ist jemand so freundlich und bereit ein minimalistisches
> Beispiel zu posten?
>
> z.B.
> Eine SerielDataReceive Klasse die nach dem N bytes empfangen wurden ein
> event an das GUI thread schickt.
>
> Gruss,
> Jan
1
        /*  Event Delegate
2
         */
3
        public delegate void EventDelegate();
4
5
6
        public static event EventDelegate NewCommandoPackageEvent;
7
        public static void NewCommandoPackageEventFunc()
8
        {
9
            // Prüft ob das Event überhaupt einen Abonnenten hat.
10
            NewCommandoPackageEvent?.Invoke();    
11
        }
12
13
14
        Das rufst du einfach auf 
15
        // Event feuern..
16
        NewCommandoPackageEventFunc();
17
18
        Da wo das Event ne Meldung zeigen soll, musst du es nur noch Abonieren

von c-hater (Gast)


Lesenswert?

Oliver R. schrieb:

> der Dispatcher arbeitet bei WINDOWS-Programmen alle vorkommenden
> Ereignisse (z.B. Button gedrückt oder Daten vom Port empfangen etc.) ab
> und zwar synchron.
> D.h. ich kann nicht gleichzeitig mit WaitOne() auf ein
> Synchronisationsobjekt warten

Doch, kann man natürlich. Man muss nur den entsprechenden API-Call 
verwenden, der genau dies erlaubt. Also MsgWaitForMultipleObjects() oder 
MsgWaitForMultipleObjectsEx().

Sehr sinnvoll ist das allerdings in aller Regel nicht. Im Besonderen 
nicht: für die Verarbeitung der Ereignisse eines COM-Ports. Denn der 
wird mit dem FileAPI (ein ein wenig mehr) schon absolut hinreichend für 
den asynchronen Betrieb unterstützt. Wenn man den Scheiß irgendwann 
überhaupt im GUI-Thread benötigt, gibt es genug Mechanismen, in dorthin 
zu lenken. Alles bis zu diesem Zeitpunkt kann und sollte im Kontext der 
OS-Threads passieren. Denn im Kern ist die asynchrone Betriebsart auch 
nix anderes als ein Thread. Bloß dass der implizit vom OS erzeugt wird. 
Sprich: das ABSOLUT DÜMMSTE, was man machen kann, ist: Polling des 
synchronen API mit einem eigenen Thread. Das senkt nur den Durchsatz, 
löst aber nicht das Problem der Synchronisation mit dem GUI-Thread. Da 
muss man ganz genauso lösen, als würde man direkt aus dem Kontext der 
OS-Threads agieren.

von Oliver R. (superberti)


Lesenswert?

c-hater schrieb:


> Doch, kann man natürlich. Man muss nur den entsprechenden API-Call
> verwenden, der genau dies erlaubt. Also MsgWaitForMultipleObjects() oder
> MsgWaitForMultipleObjectsEx().

Wir reden hier ja über C# und niemand wird wohl ernsthaft auf die Idee 
kommen, das dort reinzufrickeln!
Wahrscheinlich wird aber async/await intern ganz ähnlich arbeiten.
Eine Sache muss ich aber noch richtig stellen: In der Hilfe zu 
SerialPort steht, dass der DataReceived-Event tatsächlich im Kontext 
eines zweiten (nicht-GUI) Threads abgearbeitet wird.

Gruß,

von Martin S. (sirnails)


Lesenswert?

Oliver R. schrieb:
> In der Hilfe zu
> SerialPort steht, dass der DataReceived-Event tatsächlich im Kontext
> eines zweiten (nicht-GUI) Threads abgearbeitet wird.

Jup, so isses auch. Ohne Invoker knallt's. Nicht immer, nur ab und zu. 
Das macht den Fehler mies zu finden.

von Jan W. (jan_woj)


Lesenswert?

Martin S schrieb :

>Oliver R. schrieb:

>> In der Hilfe zu
>> SerialPort steht, dass der DataReceived-Event tatsächlich im Kontext
>> eines zweiten (nicht-GUI) Threads abgearbeitet wird.

>Jup, so isses auch. Ohne Invoker knallt's. Nicht immer, nur ab und zu.
>Das macht den Fehler mies zu finden.

Martin S schrieb aber auch :

>Natürlich tun sie das. Deswegen empfängt man sowas auch nicht im
>GUI-Thread.

>Man hat eine Klasse, die die Funktionalität RS232 kapselt, erstellt
>einen neuen Thread in dem die Klasse arbeitet und wenn innerhalb der
>Klasse nach n Events festgestellt wird, dass die Daten nun vollständig
>sind, dann feuert sie ein Event an den GUI - Thread, der sagt, dass die
>Verarbeitung nun abgeschlossen ist.

Warum soll man den einen neuen Thread für diese "SerialReceive" Klasse 
(so nenne ich sie) erstellen?
Das data receive event arbeitet doch so wie so in einem nicht GUI 
Thread.
…???

Ich würde es mit meinem jetzigen Verständnis folgendermaßen machen :

Eine Klasse erstellen, "SerialReceive" die ein SerialPort kapselt und 
das Protokoll beinhaltet. Die Protokollimplementierung befindet sich in 
der SerialReceive Klasse. Diese privaten Protokollmethoden  werden von 
DataReceivedHandler aufgerufen. In den Protokollmethoden wird ein 
NewDataPacketReceived event an das GUI thread abgefeuert.

Eine Instanz der SerialReceive Klasse würde ich im GUI thread erstellen.
Ich würde also keinen weiter Thread erstellen.

Was hält ihr von dieser Lösung. Wäre das eine saubere und richtige 
Implementierung? oder habe ich irgendwas Falsch verstanden?

Gruss,
Jan

von Jan H. (janiiix3)


Lesenswert?

Jan W. schrieb:
> Was hält ihr von dieser Lösung. Wäre das eine saubere und richtige
> Implementierung? oder habe ich irgendwas Falsch verstanden?

Genau so habe ich das jetzt auch gemacht.
Das funktioniert soweit auch richtig super.
Das mit dem "Warten", habe ich jetzt auch lösen können.

Ist zwar sehr unsauberer Code aber ich weiß gerade nicht weiter und so 
wichtig ist es jetzt auch nicht. Habe an der Stelle, wo ich warten muss, 
ein "await Task.Delay(x)" eingebaut. Siehe da, das "DateReceivedEvent" 
wird weiterhin gefeuert und kann ohne Probleme in der Zeit meine 
eingehenden Daten auswerten.

von Martin S. (sirnails)


Lesenswert?

Jan W. schrieb:
> Martin S schrieb :
>
>>Oliver R. schrieb:
>
>>> In der Hilfe zu
>>> SerialPort steht, dass der DataReceived-Event tatsächlich im Kontext
>>> eines zweiten (nicht-GUI) Threads abgearbeitet wird.
>
>>Jup, so isses auch. Ohne Invoker knallt's. Nicht immer, nur ab und zu.
>>Das macht den Fehler mies zu finden.
>
> Martin S schrieb aber auch :
>
>>Natürlich tun sie das. Deswegen empfängt man sowas auch nicht im
>>GUI-Thread.
>
>>Man hat eine Klasse, die die Funktionalität RS232 kapselt, erstellt
>>einen neuen Thread in dem die Klasse arbeitet und wenn innerhalb der
>>Klasse nach n Events festgestellt wird, dass die Daten nun vollständig
>>sind, dann feuert sie ein Event an den GUI - Thread, der sagt, dass die
>>Verarbeitung nun abgeschlossen ist.
>
> Warum soll man den einen neuen Thread für diese "SerialReceive" Klasse
> (so nenne ich sie) erstellen?

"Man" macht das nicht, das macht das Framework selbst.

> Das data receive event arbeitet doch so wie so in einem nicht GUI
> Thread.
> …???

Richtig.

> Ich würde es mit meinem jetzigen Verständnis folgendermaßen machen :
>
> Eine Klasse erstellen, "SerialReceive" die ein SerialPort kapselt und
> das Protokoll beinhaltet. Die Protokollimplementierung befindet sich in
> der SerialReceive Klasse. Diese privaten Protokollmethoden  werden von
> DataReceivedHandler aufgerufen. In den Protokollmethoden wird ein
> NewDataPacketReceived event an das GUI thread abgefeuert.
>
> Eine Instanz der SerialReceive Klasse würde ich im GUI thread erstellen.
> Ich würde also keinen weiter Thread erstellen.
>
> Was hält ihr von dieser Lösung. Wäre das eine saubere und richtige
> Implementierung? oder habe ich irgendwas Falsch verstanden?

Das verschiebt das Problem, aber löst es nicht. Das ist genau der Fall, 
den ich weiter oben schon beschrieben habe. Irgendwann rumpelt es und 
man kann suchen, woran es liegt.

Ich sehe auch ehrlichgesagt das Problem nicht. Es gibt millionen 
Beispiele im Internet und unzählige zugehörige Antworten zu den Fragen 
auf Stackoverflow. Da steht das alles lang und breit erklärt. Warum 
nimmst Du nicht einfach so einen Code - der funktioniert wenigstens.

von Jan W. (jan_woj)


Lesenswert?

Martin S. schrieb :
>Das verschiebt das Problem, aber löst es nicht. Das ist genau der Fall, den ich 
weiter oben schon beschrieben habe. Irgendwann rumpelt es und man kann suchen, 
woran es liegt.

In dem  NewDataPacketReceived event handler verwende ich natürlich das
threadsichere Aufrufes von Windows Forms-Steuerelementen.


textBox1.InvokeIfRequired(() => textBox1.Text += indata); // +=indata);
InvokeIfRequired ist eine sinnvolle Ergänzungsmethode für das
Threadsichere Aufrufe von Windows Forms-Steuerelementen.
mehr Info unter :
https://stackoverflow.com/questions/2367718/automa...


>Ich sehe auch ehrlichgesagt das Problem nicht. Es gibt millionen Beispiele im 
Internet und unzählige zugehörige Antworten zu den Fragen auf Stackoverflow. Da 
steht das alles lang und breit erklärt. Warum nimmst Du nicht einfach so einen 
Code - der funktioniert wenigstens.

Ich möchte verstehen warum etwas funktioniert, und warum nicht.

Gibt es eine Möglichkeit das NewDataPacketReceived event so zu 
implementieren das es synchron zum GUI thread kommt?

Dann könnte man sich das threadsichere Aufrufen der Windows 
Forms-Steuerelementen sparen.
Mann könnte also im NewDataPacketReceived event Handler schreiben:

textBox1.Text += indata;


Gruss,
Jan

von Martin S. (sirnails)


Lesenswert?

Jan W. schrieb:
> Dann könnte man sich das threadsichere Aufrufen der Windows
> Forms-Steuerelementen sparen.

Das kannst Du dir nie sparen. Aber du kannst es asynchron gestalten per 
.BeginInvoke(). Dann bist du threadsicher und hast keine Verzögerung.

von c-hater (Gast)


Lesenswert?

Jan W. schrieb:

> Ich möchte verstehen warum etwas funktioniert, und warum nicht.

Lobenswerter Ansatz. Hoffentlich schaffst du es...

> Gibt es eine Möglichkeit das NewDataPacketReceived event so zu
> implementieren das es synchron zum GUI thread kommt?

Natürlich: Man verlagert das Problem der Synchronisierung mit dem 
GUI-Tread in die Klasse, die das Event generiert. Aber schon allein die 
dazu zu importierenden Abhängigkeiten sagen: das ist keine sehr 
nützliche Idee im Sinne der Codeisolation (auch wenn sie natürlich 
durchaus funktioniert).

Dein Problem ist das typische Anfängerproblem: du hältst deine 
GUI-Anwendung für den Kern des Universums, weil es zu diesem Zeitpunkt 
so ziemlich alles ist, was du kannst.
Ist sie aber nicht, sollte sie jedenfalls nicht sein. Normalerweise 
sollte das GUI nur Schnittstelle zwischen Anwender und der eigentlichen 
Funktion des Programms sein.

Wenn du dieses Konzept konsequent umsetzt, wirst du sehr bald bemerken, 
dass Synchronisationen mit dem GUI-Thread zu einer eher seltene Aufgabe 
wird und das die das Innenleben deiner Form praktisch nur noch daraus 
besteht, die GUI-Logik abzubilden und die wenigen Events des 
eigentlichen Nutz-Programms zu empfangen und zu synchronisieren, die 
wirklich das GUI erreichen müssen.

von Martin S. (sirnails)


Lesenswert?

Schön zusammengefasst.

von Winfried J. (Firma: Nisch-Aufzüge) (winne) Benutzerseite


Lesenswert?

....
hier mein Seriell-IO-Test

öffnet am Mac die seriellen "RS232" USB Schnittstelle 
"/dev/cu.usbmodem14101",

setzt DTR low (low-aktiv),

liest vom ARDUINO eine Zeile (auf Escapesequenz \n endend),
sendet einen String (auf Escapesequenz \n endend) an den ARDUINO,
liest vom ARDUINO noch eine Zeile (auf Escapesequenz \n endend),

setzt DTR high,
schließt den seriellen Port,


1
#include <stdio.h>   /* Standard input/output definitions */
2
#include <stdlib.h>
3
#include <stdbool.h>
4
#include <string.h>  /* String function definitions */
5
#include <unistd.h>  /* UNIX standard function definitions */
6
#include <fcntl.h>   /* File control definitions */
7
#include <errno.h>   /* Error number definitions */
8
#include <termios.h> /* type and macro definitions.  Linux version. */
9
#include <sys/ioctl.h>
10
#define PORT "/dev/cu.usbmodem14101"/* change this to your USB port Device Name */
11
12
13
int fd;// Filediscriptor
14
int iflags = TIOCM_DTR; // DTR-flag
15
16
const unsigned char bsz = 255;// Buffersize
17
18
char OutBuffer[bsz]="HFP1234567890ABCDXtest\n";
19
char InBuffer[bsz]="";
20
21
int USBSerialInit(void);
22
void USBSerialGetLine(int fd,char *buffer,int bufsize);
23
24
25
26
/*
27
 ** this is just a sample c Main program.
28
 */
29
int main(int argc, char *argv[])
30
{
31
    n=ioctl(fd, TIOCMBIS, &iflags); // DTR -> low
32
    USBSerialInit();
33
    n=ioctl(fd, TIOCMBIS, &iflags); // DTR -> low
34
    n=tcflush(fd, TCIOFLUSH); // puffer leeren
35
    n=ioctl(fd, TIOCMBIC, &iflags); // DTR -> low
36
    USBSerialGetLine(fd,InBuffer,bsz);
37
    printf(" %s\n",InBuffer);
38
    scanf("Eingabe%40s",Eingabe);
39
    write(fd,OutBuffer,bsz);
40
    printf("OutBuffer = %s",OutBuffer);
41
    USBSerialGetLine(fd, InBuffer,bsz);
42
    printf("InBuffer  = %s\n",InBuffer);
43
    ioctl(fd, TIOCMBIS, &iflags);
44
    close(fd);
45
    printf("Port geschlossen\n");
46
}
47
48
int USBSerialInit()
49
{
50
    struct termios options;
51
    struct termios toptions;
52
    fd = open(PORT, O_RDWR | O_NOCTTY | O_NDELAY);
53
    if (fd == -1 ){
54
        perror(PORT);
55
        return -1;
56
    }
57
    else fcntl(fd, F_SETFL, 0);{
58
        tcgetattr(fd, &options);
59
        speed_t brate =B230400;
60
        cfsetispeed(&options, brate);
61
        cfsetospeed(&options, brate);
62
        toptions.c_cflag &= ~PARENB;
63
        toptions.c_cflag &= ~CSTOPB;
64
        toptions.c_cflag &= ~CSIZE;
65
        toptions.c_cflag |= CS8;
66
        toptions.c_cflag &= ~CRTSCTS;
67
        toptions.c_cflag |= CREAD | CLOCAL;
68
        toptions.c_iflag &= ~(IXON | IXOFF | IXANY);
69
        toptions.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
70
        toptions.c_oflag &= ~OPOST;
71
        toptions.c_cc[VMIN]  = 0;
72
        toptions.c_cc[VTIME] = 0;
73
        tcsetattr(fd, TCSANOW, &options);
74
        return fd;
75
    }
76
}
77
/*
78
 ** this reads an entire line of text, up to a Newline
79
 ** and discards any Carriage Returns
80
 ** The resulting line has the Newline stripped and
81
 ** is null-terminated
82
 */
83
void USBSerialGetLine(int fd,char *buffer,int bufsize)
84
{
85
    char *bufptr;
86
    long nbytes=0;
87
    char inchar;
88
    
89
    bufptr = buffer;
90
    while ((nbytes = read(fd, &inchar, 1)) > 0)
91
    {
92
        if (inchar == '\r') continue;
93
        if (inchar == '\n') break;
94
        *bufptr = inchar;
95
        ++bufptr;
96
    }
97
    *bufptr = '\0';
98
}

: Bearbeitet durch User
von nicht"Gast" (Gast)


Lesenswert?

Winfried J. schrieb:
> ...


Was willst du uns damit zeigen? Weder beantwortet das die Frage vom TO 
noch ist das C# Code.

Außerdem liest du blocking. Genau das will der TO nicht.

von Winfried J. (Firma: Nisch-Aufzüge) (winne) Benutzerseite


Lesenswert?

Das Beispiel zeigt im Detail undvollständig wie die Kommunikation 
funktioniert und was zu beachten ist. Was der TO wünscht, C# und und 
nonblockig ist leicht anzupassen. Die Entsprechungen in Borland C# kann 
er selbst vornehmen und wie man die den Fluss regelmaäßig auf einzelne 
oder Gruppen von Zeichen checkt ist eine Teilmenge der Funktion 
USBSerialgetline. Etwas Selber-Denken wird erlaubt sein, ottach?

Namaste

von Keiner N. (nichtgast)


Lesenswert?

Winfried J. schrieb:
> Borland C#

Der war gut. Das muss ich mir merken.

PS: Dein Code ist kacke. Da werden unterschiedliche Abstraktionsebenen 
wild durcheinander gemischt. Den solltest du mal refaktorieren.

von Alias W. (Gast)


Lesenswert?

Keiner N. schrieb:
> Winfried J. schrieb:
>> Borland C#
>
> Der war gut. Das muss ich mir merken.
>
> PS: Dein Code ist kacke. Da werden unterschiedliche Abstraktionsebenen
> wild durcheinander gemischt. Den solltest du mal refaktorieren.

Kein wunder das so mancher Aufzug stehen bleibt :D

von Winfried J. (Firma: Nisch-Aufzüge) (winne) Benutzerseite


Lesenswert?

> Kein wunder das so mancher Aufzug stehen bleibt :D

Aber nicht weil ich etwas in C programmiere.



> Dein Code ist kacke.
Zeig mir bitte deinen Besseren. ich wäre dankbar.


> Da werden unterschiedliche Abstraktionsebenen
> wild durcheinander gemischt.

Ja, stammt aus verschiedene Quellen und wurde von mir modifiziert bis er 
zuverlässig funktionierte wie ich es wünschte.


>Den solltest du mal refaktorieren.
                        |
                        r

Ist nur ein Test zu grundsätzlichen Funktionsweise
In der späteren Anwendung wird das noch durchstrukturiert

: Bearbeitet durch User
von Keiner N. (nichtgast)


Lesenswert?

Winfried J. schrieb:
>> Kein wunder das so mancher Aufzug stehen bleibt :D
>
> Aber nicht weil ich etwas in C programmiere.
>
>> Dein Code ist kacke.
> Zeig mir bitte deinen Besseren. ich wäre dankbar.
1
static void Main(string[] args){
2
    using (var myPort = new SerialPort("/dev/ttyACM0", 115200)){
3
        myPort.Open();
4
        myPort.Write("Hallo Welt");
5
        var readData = myPort.ReadLine();
6
        Console.WriteLine(readData);
7
    }
8
}

Bitte schön. Nicht, dass der Code dem TO weiterhelfen würde. Trozdem ist 
der näher dran als deiner, weil wenigstens die gleiche 
Programmiersprache.

Zu beachten ist auch, dass ich keinen Low Level Krams (ioctrl) 
dazwischen streue. Darauf war meine Kritik bezogen.

>> Da werden unterschiedliche Abstraktionsebenen
>> wild durcheinander gemischt.
>
> Ja, stammt aus verschiedene Quellen und wurde von mir modifiziert bis er
> zuverlässig funktionierte wie ich es wünschte.

Toll zusammenkopierter Kram. Das ist natürlich die Welt. Dazu das ganze 
noch hochtrabend mit USB betitelt. Wolltest wohl ein wenig angeben? Ist 
nur eine 08/15 serielle Schnittstelle.
1
>>Den solltest du mal refaktorieren.
2
>                         |
3
>                         r
Da gehört definitiv kein r dazwischen.
https://de.wikipedia.org/wiki/Refactoring

> Ist nur ein Test zu grundsätzlichen Funktionsweise
> In der späteren Anwendung wird das noch durchstrukturiert

Man sollte seinem Code von Anfang an eine Struktur geben. Das macht die 
Sache wesentlich einfacher zu testen und debuggen.
Will meinen, Kapsel noch das write und das DTR setzen. Gib den 
Funktionen vernünftige Namen, dann reicht das schon und die Kommentare 
brauchst du dann auch nicht mehr.

von Winfried J. (Firma: Nisch-Aufzüge) (winne) Benutzerseite


Angehängte Dateien:

Lesenswert?

sieht zwar nett aus
bringt aber bei Xcode unter cocoa diese Meldungen. Als Console ähnlich

von Keiner N. (nichtgast)


Lesenswert?

Du hast keine Ahnung, was C# ist oder?

von Winfried J. (Firma: Nisch-Aufzüge) (winne) Benutzerseite


Lesenswert?

Schon, aber ist schon 10 Jahre her seit ich mich damit rum gezankt habe.

Ich bleib lieber beim einfachen C, ohne # ohne ++ und ohne Visual davor.

Das Konzept ist ja noch zu verstehen, aber zurecht komme ich mit den 
Klassen nicht wirklich.

Funktionen sind mir da sympathischer.

Namaste

von Keiner N. (nichtgast)


Lesenswert?

Der TO fragt aber nach einer C# Lösung. Warum steuerst du dann C Code 
bei? Damit ist dem TO nicht geholfen.

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.