Forum: PC-Programmierung Linux C++ RS232 - Sleep Problem


von auron2008 (Gast)


Lesenswert?

Hi,

ich muss unter Linux mittels C++ (g++ Compiler) ein Konsolenprogramm 
erstellen welches mit einem Messgerät kommuniziert und hab da folgendes 
Problem.

Die Kommunikation erfolgt über RS232 und das senden/empfangen der Daten 
klappt wunderbar nur wenn ich solche Dinge versuche gibt es Probleme. 
(modul ist eine Instanz die eine Instanz SerialPort enthält welche für 
die Kommunikation zuständig ist )
1
modul->sendCommand(FrequLimit);
2
sleep(1);
3
modul->sendCommand(FrequLimit);

Es gibt hier Probleme mit dem Sleep. Wenn ich debugge und in Zeile 2 ein 
Breakpoint setze, dann kann ich auch fortsetzen und er sendet den 
zweiten Befehl.

Kann mir jemand dabei helfen ? bzw. einen Tipp geben in welcher Richtung 
das Problem liegen könnte ?

von Dr. Sommer (Gast)


Lesenswert?

auron2008 schrieb:
> Es gibt hier Probleme mit dem Sleep.

Geht's noch unpräziser? Wie soll man mit so wenig Information 
herausfinden was da nicht funktioniert?

von auron2008 (Gast)


Lesenswert?

Dr. Sommer schrieb:
> auron2008 schrieb:
>> Es gibt hier Probleme mit dem Sleep.
>
> Geht's noch unpräziser? Wie soll man mit so wenig Information
> herausfinden was da nicht funktioniert?

Genau da ist mein Problem, ich habe nicht mehr Informationen. Immer wenn 
Sleep, usleep aufgerufen wird dann hängt das Programm dort drinne fest. 
Um herauszufinden ob's an dem Sleep liegt hatte ich auch folgendes 
versucht.
1
modul->sendCommand(FrequLimit); // <- Messgerät sendet eine Antwort zurück
2
3
while(1) {
4
   sleep(1);
5
   cout << "Test" << endl;
6
}

Und auch hier bleibt's scheinbar an dem sleep hängen aber nur wenn ich 
Daten von der RS232 Schnittstelle empfangen habe. Kann auch über ein 
Terminal Daten an das Programm senden und es bleibt am Sleep hängen.

Den Empfang der Daten habe ich so gelöst :

Initialisierung im Konstruktor
1
SerialPort::SerialPort()
2
{
3
...
4
    ReceiveSignal.sa_handler = Receive_Handler;
5
    ReceiveSignal.sa_flags = 0;
6
    ReceiveSignal.sa_restorer = NULL;
7
    sigaction(SIGIO,&ReceiveSignal,NULL);
8
9
    Receive_Helper(this);
10
...
11
}

Da ein Signal nur auf eine static Methode gelegt werden kann habe ich 
zwei kleine Hilfsfunktion genutzt.
1
void SerialPort::Receive_Handler(int stat)
2
{
3
    Receive_Helper(NULL);
4
}
5
6
void *SerialPort::Receive_Helper(void *pthis)
7
{
8
    static SerialPort *pc_this;
9
    if(!pthis == 0)
10
    {
11
        pc_this = (SerialPort *) pthis;
12
    }else{
13
        pc_this->ReadWithEvent();
14
    }
15
}
1
void SerialPort::ReadWithEvent()
2
{
3
    unsigned char buf[255];
4
5
    ReadPort(buf);
6
7
    if(!SerialPortReceive_Handler == NULL)
8
    {
9
10
        (*SerialPortReceive_Handler)(); //funktion im Hauptprogramm
11
    }
12
}
13
14
int SerialPort::ReadPort(unsigned char* _result)
15
{
16
    int res = 0;
17
    unsigned char buffer[255];
18
19
    res = read(fd, buffer,255);
20
    buffer[res] = '\0';
21
22
    memccpy(_result,buffer,'\0',255);
23
24
    tcsetattr(fd, TCSAFLUSH, &options);
25
26
    std::cout << dec << "Read :" << (int)res << std::endl;
27
28
    for(int i = 0;i<res;i++)
29
    {
30
        std::cout << hex << static_cast<int>(buffer[i]) << std::endl;
31
    }
32
33
34
    if(res == -1)
35
    {
36
        return 0;
37
    }else{
38
        return res;
39
    }
40
}

Wenn noch mehr Informationen benötigt werden dann bitte bescheid geben.

von Dr. Sommer (Gast)


Lesenswert?

auron2008 schrieb:
> Dr. Sommer schrieb:
>> auron2008 schrieb:
>>> Es gibt hier Probleme mit dem Sleep.
>>
>> Geht's noch unpräziser? Wie soll man mit so wenig Information
>> herausfinden was da nicht funktioniert?
>
> Genau da ist mein Problem, ich habe nicht mehr Informationen. Immer wenn
> Sleep, usleep aufgerufen wird dann hängt das Programm dort drinne fest.
Und das ist keine Information? "gibts Probleme" hätte ja auch sein 
können dass sleep sofort zurückkehrt oder das Programm abstürzt etc...
> Um herauszufinden ob's an dem Sleep liegt hatte ich auch folgendes
> versucht.
> modul->sendCommand(FrequLimit); // <- Messgerät sendet eine Antwort
> zurück
>
> while(1) {
>    sleep(1);
>    cout << "Test" << endl;
> }
>
> Und auch hier bleibt's scheinbar an dem sleep hängen aber nur wenn ich
> Daten von der RS232 Schnittstelle empfangen habe. Kann auch über ein
> Terminal Daten an das Programm senden und es bleibt am Sleep hängen.
Mit anderen Worten, es bleibt nicht am sleep hängen, sondern am 
read/write ... Mach doch mal eine Debug-Ausgabe zwischen read/write 
und sleep. Vermutlich hast du vergessen, deinen filedescriptor auf 
non-blocking zu setzen ("fcntl (fd, F_SETFL, fcntl (fd, F_GETFL) | 
O_NONBLOCK);"). IO mit Signalen ist im Allgemeinen etwas gefährlich, 
versuchs besser mal mit select(), dann sparst du dir die statische 
Member-Funktion. signale können schließlich immer zu beliebigen 
Zeitpunkten kommen, zB vor dem 1. Aufruf von Receive_Helper oder während 
ReadPort gerade läuft...
> void *SerialPort::Receive_Helper(void *pthis)
> {
>     static SerialPort *pc_this;
>     if(!pthis == 0)
Was ist das denn, eine umständliche Schreibweise für "if (pthis)" ?!
Und was ist das überhaupt für eine kreative Konstruktion, warum machst 
du keine globale Variable "static SerialPort* theSerialPort;", weist 
diese im Konstruktor zu, und verwendest sie in der statischen Funktion? 
Das wäre klarer und leichter verständlich...

> void SerialPort::ReadWithEvent()
> {
>     unsigned char buf[255];
>
>     ReadPort(buf);
>
>     if(!SerialPortReceive_Handler == NULL)
>     {
>
>         (*SerialPortReceive_Handler)(); //funktion im Hauptprogramm
>     }
> }
Protip: http://en.cppreference.com/w/cpp/utility/functional/function
> int SerialPort::ReadPort(unsigned char* _result)
Eigene bezeichner die mit "_" anfangen sind gefährlich. In dem Fall ists 
zwar erlaubt, aber es schadet nicht es ganz zu lassen. Siehe 
http://stackoverflow.com/questions/228783/what-are-the-rules-about-using-an-underscore-in-a-c-identifier/228797#228797 
.
> {
>     int res = 0;
>     unsigned char buffer[255];
>
>     res = read(fd, buffer,255);
Auch hier, read hängt wenn O_NONBLOCK nicht gesetzt ist.
>     buffer[res] = '\0';
>
>     memccpy(_result,buffer,'\0',255);
Voll umständlich&ineffizient, warum lässt du "read" nicht direkt nach 
"_result" lesen?  Woher weiß ReadPort dass _result tatsächlich 255 bytes 
groß ist?
>         std::cout << hex << static_cast<int>(buffer[i]) << std::endl;
Der cast ist unnötig, "int { buffer[i] }" ist kürzer

von Dr. Sommer (Gast)


Lesenswert?

Dr. Sommer schrieb:
> warum machst
> du keine globale Variable "static SerialPort* theSerialPort;", weist
> diese im Konstruktor zu, und verwendest sie in der statischen Funktion?
Oder natürlich einfach eine statische Member-Variable.

von auron2008 (Gast)


Lesenswert?

Erstmal vielen Dank für deine ausführliche Antwort, die muss ich erstmal 
sacken lassen. Habe erst angefangen mich mit C++ zu befassen (vorher 
VB.NET) und probiere halt noch viel aus, deswegen der unsaubere Code.


Dr. Sommer schrieb:

> Mit anderen Worten, es bleibt nicht am sleep hängen, sondern am
> read/write ... Mach doch mal eine Debug-Ausgabe zwischen read/write
> und sleep. Vermutlich hast du vergessen, deinen filedescriptor auf
> non-blocking zu setzen ("fcntl (fd, F_SETFL, fcntl (fd, F_GETFL) |
> O_NONBLOCK);").

Hat leider nicht den gewünschten Effekt gebracht. Aber ist es erstmal 
nicht so wichtig, nach deinem Post hab ich scheinbar wichtigere 
Baustellen die ich zuerst beheben muss. Aber die Richtung scheint zu 
stimmen denn wenn ich
das read(...) aus der Methode ReadPort(...) entferne dann läuft es 
wunderbar weiter.

> IO mit Signalen ist im Allgemeinen etwas gefährlich,
> versuchs besser mal mit select(), dann sparst du dir die statische
> Member-Funktion. signale können schließlich immer zu beliebigen
> Zeitpunkten kommen, zB vor dem 1. Aufruf von Receive_Helper oder während
> ReadPort gerade läuft...

Daran habe ich gar nicht gedacht, wenn ich aber in ReadPort das Signal 
mittels sigaction auf ignorieren stelle und Receive_Helper gleich zu 
Beginn des Konstruktor aufrufe dann dürfte doch dahingehend nichts mehr 
passieren.

Da ich wie gesagt in C++ und Linux nicht gerade sehr bewandert bin habe 
ich keine richtige alternative zu den Signalen gesehen (außer ein Thread 
der ständig den Port abfragt). Werde mir ansehen was du mit dem select 
meinst und wie man es besser machen kann. In meinen zwei Büchern hier 
steht leider nicht darüber.


> Und was ist das überhaupt für eine kreative Konstruktion, warum machst
> du keine globale Variable "static SerialPort* theSerialPort;", weist
> diese im Konstruktor zu, und verwendest sie in der statischen Funktion?
> Das wäre klarer und leichter verständlich...

Da fehlt's einfach an Erfahrung in C++ .


> Eigene bezeichner die mit "_" anfangen sind gefährlich. In dem Fall ists
> zwar erlaubt, aber es schadet nicht es ganz zu lassen. Siehe
> http://stackoverflow.com/questions/228783/what-are...
>

Ich fand das (bis jetzt) immer ganz praktisch da ich so immer weiß was 
gemeint ist und ich mir's in VB.Net so angewöhnt hatte.

>> {
>>     int res = 0;
>>     unsigned char buffer[255];
>>
>>     res = read(fd, buffer,255);
> Auch hier, read hängt wenn O_NONBLOCK nicht gesetzt ist.
>>     buffer[res] = '\0';
>>
>>     memccpy(_result,buffer,'\0',255);
> Voll umständlich&ineffizient, warum lässt du "read" nicht direkt nach
> "_result" lesen?  Woher weiß ReadPort dass _result tatsächlich 255 bytes
> groß ist?
1
memccpy(_result,buffer,'\0',255);
 sollte eigentlich
1
memccpy(_result,buffer,'\0',res);
sein.
Aber du hast recht, ich kann's direkt auf _result schreiben. Keine 
Ahnung was mich hier geritten hat.

>>         std::cout << hex << static_cast<int>(buffer[i]) << std::endl;
> Der cast ist unnötig, "int { buffer[i] }" ist kürzer

Diese Ausgabe war nur zum debuggen gedacht und da hatte ich verschiedene 
Schreibweisen ausprobiert und die ist es am Ende geblieben. Wird aber 
wieder entfernt.

Also erstmal vielen Dank für deine hilfreichen Tipps.

von Dr. Sommer (Gast)


Lesenswert?

> Daran habe ich gar nicht gedacht, wenn ich aber in ReadPort das Signal
> mittels sigaction auf ignorieren stelle und Receive_Helper gleich zu
> Beginn des Konstruktor aufrufe dann dürfte doch dahingehend nichts mehr
> passieren.
Alles Frickelei :/
>
> Da ich wie gesagt in C++ und Linux nicht gerade sehr bewandert bin habe
> ich keine richtige alternative zu den Signalen gesehen (außer ein Thread
> der ständig den Port abfragt). Werde mir ansehen was du mit dem select
> meinst und wie man es besser machen kann. In meinen zwei Büchern hier
> steht leider nicht darüber.
Wenn das Bücher über UNIX-Systemprogrammierung sein sollen, gehören die 
dann in Ablage Rund. Aber zum Glück gibt es das Internet (Google "linux 
select") und zum Glück hat Linux alle nötigen Anleitungen dabei ("man 
select" eingeben). Mit select wird das ganze viel sauberer, denn du 
brauchst dann bei ordentlicher Umsetzung keine statischen Funktionen 
mehr - d.h. du kannst dann problemlos auch mehrere Instanzen von 
SerialPort haben - und die Callbacks kommen zu definierten Zuständen.
Alternativ kannst du auch ein Framework wie Gtk+ oder Qt verwenden 
welches eine solche Funktionalität schon bietet und Callbacks aufruft 
wenn Daten ankommen/gesendet werden können.

> Ich fand das (bis jetzt) immer ganz praktisch da ich so immer weiß was
> gemeint ist und ich mir's in VB.Net so angewöhnt hatte.
Und ohne den Unterstrich weiß man nicht dass das ein Parameter ist? o.O

von auron2008 (Gast)


Lesenswert?

Dr. Sommer schrieb:

> Wenn das Bücher über UNIX-Systemprogrammierung sein sollen, gehören die
> dann in Ablage Rund. Aber zum Glück gibt es das Internet (Google "linux
> select") und zum Glück hat Linux alle nötigen Anleitungen dabei ("man
> select" eingeben). Mit select wird das ganze viel sauberer, denn du
> brauchst dann bei ordentlicher Umsetzung keine statischen Funktionen
> mehr - d.h. du kannst dann problemlos auch mehrere Instanzen von
> SerialPort haben - und die Callbacks kommen zu definierten Zuständen.
> Alternativ kannst du auch ein Framework wie Gtk+ oder Qt verwenden
> welches eine solche Funktionalität schon bietet und Callbacks aufruft
> wenn Daten ankommen/gesendet werden können.

Das klingt ja sehr interessant. Is eigentlich das was ich wollte und saß 
an meiner Lösung mit den statischen Funktionen ne halbe Ewigkeit. Werd 
wohl nochmal in de Bibo gehen und mir direkt Systemprogrammierung 
suchen. Über google und co zu suchen hab ich mir teilweise abgewöhnt 
weil's in Copy and Paste ausartet und der Lerneffekt gegen null geht.

>> Ich fand das (bis jetzt) immer ganz praktisch da ich so immer weiß was
>> gemeint ist und ich mir's in VB.Net so angewöhnt hatte.
> Und ohne den Unterstrich weiß man nicht dass das ein Parameter ist? o.O

So war das nicht gemeint. Als Bsp. ich habe eine Klasse Kreis
1
class Kreis {
2
   private : int radius;
3
   public setRadius(int _radius);
4
}
Ich finde das damit die Lesbarkeit erhöht wird. Aber wenn's kein guter 
Programmierstil ist dann lege ich's beiseite. Andere müssen mit meinem 
Quelltext am Ende klar kommen.

von Dr. Sommer (Gast)


Lesenswert?

auron2008 schrieb:
> Über google und co zu suchen hab ich mir teilweise abgewöhnt
> weil's in Copy and Paste ausartet und der Lerneffekt gegen null geht.
Man muss nur einen Bogen um Foren-Threads machen und direkt die 
jeweilige Doku ansehen...

auron2008 schrieb:
> Ich finde das damit die Lesbarkeit erhöht wird.
Man kann auch einfach
1
class Kreis {
2
  private:
3
    int radius;
4
  public:
5
    void setRadius(int radius_);
6
}
machen. Oder
1
class Kreis {
2
  private:
3
    int m_radius;
4
  public:
5
    void setRadius(int radius);
6
}
(m_ für member)
Oder
1
class Kreis {
2
  private:
3
    int radius;
4
  public:
5
    void setRadius(int radius) { this->radius = radius; }
6
}
Alles besser als _radius ...

von A. H. (ah8)


Lesenswert?

Dr. Sommer schrieb:
> IO mit Signalen ist im Allgemeinen etwas gefährlich,
> versuchs besser mal mit select(), ... signale können
> schließlich immer zu beliebigen Zeitpunkten kommen

Recht hat er, obwohl ich die Formulierung für leicht untertrieben, ja 
gerade zu verharmlosend halte. Nimm auf jeden Fall select. Sobald Du 
mit Signalen programmierst holst Du Dir Nebenläufigkeit in Dein Programm 
und damit einen ganzen Schwung Probleme, die Du als weniger erfahrener 
Programmierer nicht wirklich haben willst. Selbst elementare Annahmen 
über die Ausführung von Programmen können zusammenbrechen, weshalb Du 
kaum ein Buch über Programmiersprachen finden wirst, das darauf eingeht. 
Du wirst auf Literatur zu Multithreading oder Kernelprogrammierung 
zurückgreifen müssen. Google mal nach Begriffen wie atomic oder race 
condition um einen Eindruck zu bekommen. Mit select kannst Du all 
diese Probleme einfach vermeiden.

von auron2008 (Gast)


Lesenswert?

A. H. schrieb:
 Sobald Du
> mit Signalen programmierst holst Du Dir Nebenläufigkeit in Dein Programm
> und damit einen ganzen Schwung Probleme, die Du als weniger erfahrener
> Programmierer nicht wirklich haben willst.

Und genau das hatte ich vor. Mein Plan sah folgendermaßen aus.

Ich habe ein Messgerät welches mit der Klasse Mess beschrieben wird. In 
dieser gibt einen Member SerialPort welcher die ganze Kommunikation 
regelt. Dabei soll in regelmäßigen Abständen (ca aller 2 Sekunden) 
geprüft werden ob das Gerät noch vorhanden ist. Ein Echo Befehl hin der 
wieder zurückgeschickt werden soll.
Nebenbei sollte es möglich sein Befehle an das Gerät zu senden und 
wieder zu empfangen. Auf die empfangene Antwort muss dann reagiert 
werden. Unter NET ist sowas kein Problem, einfach für jede Aufgabe eine 
Klasse mit Events und fertig.
Genau das wollte ich mit den Signalen auch erreichen. Daten werden an 
das Messgerät geschickt und dieses antwortet. Die Klasse Mess bekommt 
über den SerialPort eine Information das da was ist. Ein weiterer Member 
Response kümmert sich um die Auswertung und löst ein Event aus welches 
entsprechend reagiert.

Mit select habe ich im Moment zwar kein Problem, aber ich bin gezwungen 
eine Schleife zu betreiben die nichts anderes macht außer alle Methoden 
nacheinander aufzurufen. Sowas wollte ich verhindern.
1
WerteAntwortaus()
2
{
3
   erzeuge neuen Befehl;
4
}
5
while(1)
6
{
7
   sendBefehl(Befehl);
8
   Antwort = warteAufAntwort();
9
   WerteAntwortaus(Antwort);
10
}

Und soweit ich das jetzt verstanden habe macht man mit select nichts 
anderes. Man wartet bis etwas ankommt oder geht nach einem timeout 
weiter im Programm. Auf der anderen Seite wird mit select vieles 
vereinfacht.

von A. H. (ah8)


Lesenswert?

Grundsätzlich ist Deine Idee ja nicht schlecht, aber das Grundproblem 
bleibt: Mit Signalen holst Du Dir Nebenläufigkeit und die damit 
verbundenen potentiellen Probleme möchtest Du mit ziemlicher Sicherheit 
unter allen Umständen vermeiden.

Auch für select schreibst Du eine Art Handler, die die eigentliche 
Arbeit erledigen. Ein durch select gerufener Handler wird vollständig 
abgearbeitet, bevor der nächste anlaufen kann. Bei Signalhandlern ist 
das nicht so. Die können sich und das Hauptprogramm an beliebiger Stelle 
unterbrechen. Das kann zu schwer durchschaubaren Fehlern führen, die 
sich obendrein nur sehr schwer bis gar nicht reproduzieren lassen. 
Solche Situationen zu erkennen – oder noch besser: von Anfang an zu 
vermeiden – setzt einiges an Erfahrung voraus.

Was verwendest Du eigentlich für eine Oberfläche? Eine graphische 
Oberfläche wird mit ziemlicher Sicherheit schon ein select in der 
Main-Message-Loop haben, in die du Dich dann entsprechend einklinken 
kannst/musst.

von Dr. Sommer (Gast)


Lesenswert?

auron2008 schrieb:
> Unter NET ist sowas kein Problem, einfach für jede Aufgabe eine
> Klasse mit Events und fertig.
Ja weil .NET Zeug gleich ein ganzes Framework mitliefert. Wenn du unter 
C++ ein Framework wie Qt oder Gtk+ verwendest hast du das da ganz 
genauso. Da kannst du dir pro Sekunde eine Funktion aufrufen lassen und 
wenn Daten ankommen. Nur weil der C++ Sprachkern das nicht mitliefert 
heißt das nicht dass das nicht geht...

von auron2008 (Gast)


Lesenswert?

A. H. schrieb:
> Was verwendest Du eigentlich für eine Oberfläche? Eine graphische
> Oberfläche wird mit ziemlicher Sicherheit schon ein select in der
> Main-Message-Loop haben, in die du Dich dann entsprechend einklinken
> kannst/musst.

Es wird eine Konsolenanwendung da der spätere Einsatz ein mini PC 
(raspberry Pi) sein soll. Die konfiguration soll mittels TCP ermöglich 
werden.


>Auch für select schreibst Du eine Art Handler, die die eigentliche
>Arbeit erledigen.

Nur eine Verständnisfrage, du meinst solch ein vorgehen :
1
if(FD_ISSET(...))
2
{
3
   lese();
4
}else{
5
   cout << "nix empfangen";
6
}

Dr. Sommer schrieb:
> Ja weil .NET Zeug gleich ein ganzes Framework mitliefert. Wenn du unter
> C++ ein Framework wie Qt oder Gtk+ verwendest hast du das da ganz
> genauso. Da kannst du dir pro Sekunde eine Funktion aufrufen lassen und
> wenn Daten ankommen. Nur weil der C++ Sprachkern das nicht mitliefert
> heißt das nicht dass das nicht geht...

Ich hatte mir schon Qt angesehen und muss sagen das ich lieber ohne 
anfangen möchte um in die Sprache besser einzusteigen.

Aber so sieht'S jetzt schon ganz gut aus. Die SeialPort Klasse wird 
stark vereinfacht und auch die Verarbeitung der Daten ist einfacher da 
ich keine static Funktionen mehr benötige.

von Dr. Sommer (Gast)


Lesenswert?

auron2008 schrieb:
> Nur eine Verständnisfrage, du meinst solch ein vorgehen :

Pseudocode mäßig so:
1
while (true) {
2
  fd_set rfds;
3
  for (IOObject& obj : myIOObjects) {
4
    FD_SET (obj.getFD (), &rfds);
5
  }
6
  select (..., &rfds, ...);
7
  for (IOObject& obj : myIOObjects) {
8
    if (FD_ISSET (obj.getFD (), &rfds)) {
9
      obj.onIO ();
10
    }
11
  }
12
}
Und um deinen 1sec-Timer zu realisieren verwendest du den 
Timeout-Parameter an select(). Geht alles, die anderen Frameworks machen 
auch nichts anderes, erfordern aber weniger Fummelei.

von Peter II (Gast)


Lesenswert?

Dr. Sommer schrieb:
> Und um deinen 1sec-Timer zu realisieren verwendest du den
> Timeout-Parameter an select(). Geht alles, die anderen Frameworks machen
> auch nichts anderes, erfordern aber weniger Fummelei.

so einfach ist das aber nicht. Was ist wenn der 1sek timer durch die 
RS232 abgebrochen wurde. Dann muss man sich merken das man jetzt noch 
0.2sek warten muss. Und was ist wenn man noch mehr Timer braucht ...

Wenn man dann auch etwas in eine DB schreiben will, wo man kaum 
Kontrolle darüber hat wie lange der Aufruft dauert, dann verpasst man 
noch wichtige Read oder Timerevents.

Und in Anbetracht der Sache, das heute ein Handy schon 4 cores hat, 
würde ich es einfach mit Threads lösen. Dann bekommt jeder RS232 einen 
Thread und gut ist.
Klar muss man sich die über die wie Synchronisation Gedanken machen, 
aber das finde ich einfacher als alles über ein select laufen zu lassen.

von Dr. Sommer (Gast)


Lesenswert?

Peter II schrieb:
> Dr. Sommer schrieb:
>> Und um deinen 1sec-Timer zu realisieren verwendest du den
>> Timeout-Parameter an select(). Geht alles, die anderen Frameworks machen
>> auch nichts anderes, erfordern aber weniger Fummelei.
>
> so einfach ist das aber nicht. Was ist wenn der 1sek timer durch die
> RS232 abgebrochen wurde. Dann muss man sich merken das man jetzt noch
> 0.2sek warten muss. Und was ist wenn man noch mehr Timer braucht ...
Ja man muss sich pro Timer merken wann er abläuft, und dann immer 
abfragen ob die schon abgelaufen sind. Als Timeout muss man die 
Zeitspanne zum am nächsten ablaufenden Timer angeben. Soo schwierig ist 
das auch wieder nicht.
>
> Wenn man dann auch etwas in eine DB schreiben will, wo man kaum
> Kontrolle darüber hat wie lange der Aufruft dauert, dann verpasst man
> noch wichtige Read oder Timerevents.
Das sollte man dann natürlich auch asynchron machen.
> Und in Anbetracht der Sache, das heute ein Handy schon 4 cores hat,
> würde ich es einfach mit Threads lösen. Dann bekommt jeder RS232 einen
> Thread und gut ist.
> Klar muss man sich die über die wie Synchronisation Gedanken machen,
> aber das finde ich einfacher als alles über ein select laufen zu lassen.
Kommt drauf an, Thread-Synchronisation kann ganz schön tricky sein.

von A. H. (ah8)


Lesenswert?

Peter II schrieb:
> so einfach ist das aber nicht. Was ist wenn der 1sek timer durch die
> RS232 abgebrochen wurde. Dann muss man sich merken das man jetzt noch
> 0.2sek warten muss. Und was ist wenn man noch mehr Timer braucht ...

Im Prinzip – wie immer :-) – ganz einfach: Du erzeugst für jeden 
Vorgang, der in der Zukunft ablaufen soll, ein Timer-Event. Diese Events 
werden, aufsteigend sortiert nach Ablaufzeit, in eine Warteschlange 
gestellt. Vor dem select prüfst Du auf abgelaufene Events (Ablaufzeit < 
aktuelle Zeit), sie werden aus der Warteschlange entfernt und die 
entsprechenden Handler abgearbeitet (wobei durch die Handler durchaus 
neue Events in die Queue gestellt werden können). Das erste Event, dass 
noch nicht abgelaufen ist, bestimmt die Wartezeit ( = Ablaufzeit – 
aktuelle Zeit). Je nach geforderter Genauigkeit und Laufzeit der 
IO-Handler kannst Du die Abarbeitung der Event-Queue zusätzlich noch vor 
und zwischen den IO-Handlern anstoßen.

PS: Das funktioniert sogar, wenn Du gar kein IO überwachen musst. 
select wirkt dann als reines delay, aber genauer als viele andere 
Lösungen.

: Bearbeitet durch User
von A. H. (ah8)


Lesenswert?

Peter II schrieb:
> Klar muss man sich die über die wie Synchronisation Gedanken machen,
> aber das finde ich einfacher als alles über ein select laufen zu lassen.

Ich finde, man sollte bei solchen Ratschlägen auch den persönlichen 
Erfahrungshorizont des Fragenden berücksichtigen. Auch wenn Threads 
vielleicht die technisch elegantere Lösung sind, er muss sie auch 
beherrschen können. Nicht dass ich ihm da irgendetwas ausreden möchte – 
um Gottes Willen – man kann letztlich ja nur lernen. Er sollte aber 
wenigstens wissen, was auf ihn zukommt. Übrigens halte ich das sichere 
Beherrschen des Timings bei einem select fast für eine Voraussetzung, 
um sich an Threads oder Signalen zu versuchen und nein, einfacher ist 
die Lösung mit Threads ganz sicher nicht, vielleicht codeseitig weniger 
aufwendig, aber das ist nicht dasselbe.

: Bearbeitet durch User
von auron2008 (Gast)


Lesenswert?

Dr. Sommer schrieb:
> auron2008 schrieb:
>> Nur eine Verständnisfrage, du meinst solch ein vorgehen :
>
> Pseudocode mäßig so:
> while (true) {
>   fd_set rfds;
>   for (IOObject& obj : myIOObjects) {
>     FD_SET (obj.getFD (), &rfds);
>   }
>   select (..., &rfds, ...);
>   for (IOObject& obj : myIOObjects) {
>     if (FD_ISSET (obj.getFD (), &rfds)) {
>       obj.onIO ();
>     }
>   }
> }Und um deinen 1sec-Timer zu realisieren verwendest du den
> Timeout-Parameter an select(). Geht alles, die anderen Frameworks machen
> auch nichts anderes, erfordern aber weniger Fummelei.


Tut mir leid das ich nochmal frage, aber so richtig einleuchtend ist mir 
das gerad nicht.

Zeile 1 + 2 is klar.
In Zeile 3 gehst du davon aus das es mehrere Objekte gibt (Bsp. 
SerialPort) und die gehst du nacheinander durch ? Mit FD_SET setzt du 
dann die Filediscriptoren.
Zeile 4 select() setzt den Timer und den größten FD und wartest genau 
dort bis Daten ankommen oder die Zeit abgelaufen ist.
Anschließend gehst du wieder alle erzeugten Objekte durch und schaust 
mit dem Filediskriptor ob sich was getan hat oder nicht.
Hab ich das soweit richtig verstanden ?

Wenn ja, dann stellt sich mir die Frage zu Zeile 4 und eurer 
anschließenden Diskussion über die Unterbrechung des Timers durch die 
RS232 Schitsstelle ? Das einzige was ich mir vorstellen kann ist, dass 
durch das empfangen der Daten der Timer unterbrochen wird. Dann geht's 
gleich zum lesen und hier könnte es ein Problem geben weil eventuell 
noch nicht alles empfangen wurde. Einfache Lösung wäre dann eine kurze 
Wartefuntkion.

Im Moment sieht's so aus und funktioniert super.
1
unsigned char Serial::readPort(unsigned char *result)
2
{
3
    int r = 0;
4
    tv.tv_sec = 1;
5
    tv.tv_usec = 0;
6
7
    FD_ZERO(&read_fd);
8
    FD_SET(fd,&read_fd);
9
10
    select(fd + 1,&read_fd,NULL,NULL,&tv);
11
12
    if(FD_ISSET(fd,&read_fd))
13
    {   
14
        usleep(100000);
15
        r = read(fd,result,255);
16
        result[r] = '\0';
17
    }
18
    return r;
19
}

von Dr. Sommer (Gast)


Lesenswert?

auron2008 schrieb:
> Hab ich das soweit richtig verstanden ?

Ja - das war so hauptsächlich um zeigen wie man mit select auf mehrere 
dateideskriptoren wartet.

auron2008 schrieb:
> Das einzige was ich mir vorstellen kann ist, dass durch das empfangen
> der Daten der Timer unterbrochen wird. Dann geht's gleich zum lesen

Nein, denn dann liefert FD_ISSET false zurück und nix wird gelesen.

Ich würde zudem immer nach select prüfen ob Timer abgelaufen sind und 
sie ausführen. Zum Rechnen mit Zeitspannen und -Punkten gibt es im 
Standard C++ jetzt das std::chrono API.

auron2008 schrieb:
> Im Moment sieht's so aus und funktioniert super.

Wenn du dass so machst und im SerialPort das select ausführst blockiert 
du natürlich während des wartens das gesamte restliche Programm. Wenn du 
nebenher noch auf andere Eingaben warten willst solltest du das select 
in die main () packen und da alle Deskriptoren rein stecken.

von A. H. (ah8)


Lesenswert?

auron2008 schrieb:
> Wenn ja, dann stellt sich mir die Frage zu Zeile 4 und eurer
> anschließenden Diskussion über die Unterbrechung des Timers durch die
> RS232 Schitsstelle ? Das einzige was ich mir vorstellen kann ist, dass
> durch das empfangen der Daten der Timer unterbrochen wird.

Ein select ist eigentlich nichts anderes als eine Art erweitertes 
sleep. Es suspendiert den Prozess bis ein „interessantes Ereignis“ 
eintritt. Das kann entweder der Ablauf einer gewissen Zeit sein oder 
aber eine entsprechende Statusänderung an einem der überwachten 
Filedeskriptoren. Nicht mehr und nicht weniger. Es liegt dann an Dir als 
Programmierer auszuwerten, welche Ereignisse eingetreten sind und 
entsprechend zu reagieren. Dabei gilt es gegebenfalls auch zu 
berücksichtigen, welche Ereignisse eventuell noch nicht eingetreten 
sind, etwa dass der Timer (ich würde übrigens lieber von einem Timeout 
sprechen) noch nicht abgelaufen ist oder das eben noch nicht alle Daten 
da sind.

> Dann geht's
> gleich zum lesen und hier könnte es ein Problem geben weil eventuell
> noch nicht alles empfangen wurde.

Richtig, für ein als zum Lesen/Schreiben bereit markierten Fildeskriptor 
ist garantiert – soweit ich die man-Page richtig im Kopf habe – dass das 
nächste read/write nicht blocken würde, selbst wenn der FD blockierend 
gelesen/geschrieben wird. Es ist nicht garantiert, dass eine bestimmte 
Datenmenge da ist. (Ja nicht einmal, dass überhaupt Daten da sind, nur 
dass  read/write sofort zurückkommt. Das kann auch mit einem EOF oder 
einem Fehler der Fall sein.) Du musst selbst dafür sorgen den Vorgang so 
lange zu wiederholen, bis alle Daten da sind (und natürlich auf mögliche 
Fehler achten!).

> Einfache Lösung wäre dann eine kurze Wartefuntkion.

Ganz falscher Ansatz! Erstens kann auch ein noch so langes Warten nicht 
garantieren, dass alle Daten da sind. Zweitens wartest Du hier an einer 
Stelle nur auf ein Ereignis während alle anderen unberücksichtigt 
bleiben und das System folglich an allen anderen Stellen hängt. Das 
führt die ganze Idee von select ad absurdum. Du wartest im ganzen 
System nur an einer Stelle und das ist im select.

> Im Moment sieht's so aus und funktioniert super.

Das sieht nur so aus, aber wir sind auf einem guten Weg. :-)

von auron2008 (Gast)


Lesenswert?

So nu bin ich wieder hier, musste auf Dienstreise.

Also um's besser zu machen sollte man das Timeout wohl sehr niedrig 
halten bzw. ganz weglassen und einfach prüfen ob was da ist oder nicht.

Wäre an sich auch kein Problem da die ankommenden Daten so aufgebaut 
sind. Id, ~Id, länge, daten, crc16 prüfsumme . Also einfach schauen ob 
Daten da sind, bis zum 3. Byte lesen (immer) und dann weiß ich wie viele 
Bytes ich zu erwarten habe.

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.