Forum: PC-Programmierung [Linux OS] UART mit Interrupts (C++)


von X. A. (wilhem)


Lesenswert?

Hallo zusammen!!!!
Ich habe eine kurze Verständnisfrage.
Prämisse: an meinem Linux-Rechner (Raspberry) habe ich einen Sensor 
angeschlossen, aus dem ich Daten über UART problemlos verschicken und 
empfangen kann. Das von mir geschriebene C Programm ist relativ 
einfach gestaltet und dank solcher Beiträge (wie diesem hier: 
http://stackoverflow.com/a/15455672/2302911) kann man Interrupt relativ 
schnell aktivieren und benutzen.
Dadurch muss ich den UART-Port nicht ständig testen (duch polling). Wird 
ein Zeichen empfangen, dann wird ein Interrupt ausgelöst. Bisher alles 
gut...

Nun habe ich dennoch das folgende Problem => ich habe angefangen, mein 
Programm in C++ umzuschreiben, weil ich gerne eine Klasse möchte, aus 
der ich UART-Objekte ableiten kann.
Also:
1
#include "klass.h"
2
3
SERIALclass Uart1;
4
5
....
Nun stehe ich aber vor einem großen Dilemma. Wie kann ich die Methoden 
in meiner Klasse bei Auslösen eines Interrupts abrufen? Dies ist 
eigentlich nicht machbar...selbst wenn die Methoden als public 
deklariert werden. Der einzige Weg ist - meiner Meinung nach - die 
Methode als static zu deklarieren. Dadurch kann ich beim Eintreten eines 
Ereignisses die statische Methode aufrufen.

Ist der obige Lösungsweg die einzige Möglichkeit?
Wie kann ich mein Problem anderweitig lösen?

Gruß

: Bearbeitet durch User
von Peter II (Gast)


Lesenswert?

Dafür würde ich mit Threads abreiten und nicht mit Signalen. Das Passt 
einfach nicht zur OOP Programmierung.

Die Klasse SERIALclass macht intern einfach einen Threads auf und 
arbeitet dort blockierend schon ist das Problem gelöst.

von Programmierer (Gast)


Lesenswert?

Du kannst unter Linux keine Interrupts nutzen. Das was in dem Artikel 
beschrieben wird, sind Unix-Signale. Diese sind relativ problematisch, 
da sie Interrupts nicht unähnlich, das Programm an irgendeiner Stelle 
unterbrechen können. Für eine saubere OOP-basierte Behandlung von 
Serial-Ports ist die Verwendung einer main()-Schleife z.B. mit select() 
oder epoll() sinnvoller:
Du machst eine extra Hauptschleifenklasse (z.B. "MainLoop"). Du 
erstellst eine weitere Klasse "Port", von der für jeden offenen 
Serial-Port oder z.B. Sockets eine Instanz angelegt wird. Jede 
Port-Instanz wird der einzigen MainLoop-Instanz bekannt gemacht. Wenn 
die Anwendung nichts zu tun hat, ruft sie eine "run" Funktion in 
MainLoop auf, welche per select() oder epoll() darauf wartet, dass Daten 
angekommen sind. Dann ruft sie Callbacks in den jeweiligen 
Port-Instanzen auf, welche dann wiederum Callbacks im Usercode aufrufen, 
der dann auf angekommene Daten reagieren kann. So kannst du asynchron 
sauber sogar mehrere Ports ohne Polling abhandeln.

Solltest du tatsächlich nur genau 1 Port brauchen, und auch sonst nicht 
auf irgendwelche Eingaben warten, reicht es per read() auf Daten zu 
warten. Das sieht dann aus wie Polling, aber da die Funktion blockiert 
bis Daten ankommen machts nichts.

von X. A. (wilhem)


Lesenswert?

Seeehrrrr geile Lösung.

Allerdings mir ist es noch nicht so ganz klar: mit dem Thread kann ich 
die Peripherie UART blockieren. Wie kann ich die richtige Methode 
aufrufen, wenn der Interrupt ausgelöst wird?

von Rolf M. (rmagnus)


Lesenswert?

Peter II schrieb:
> Dafür würde ich mit Threads abreiten und nicht mit Signalen. Das Passt
> einfach nicht zur OOP Programmierung.
>
> Die Klasse SERIALclass macht intern einfach einen Threads auf und
> arbeitet dort blockierend schon ist das Problem gelöst.

Und wo ist da der Vorteil gegenüber einem Signal? Mit den Thread macht 
man doch das gleiche, nur umständlicher.

von Programmierer (Gast)


Lesenswert?

Rolf M. schrieb:
> Und wo ist da der Vorteil gegenüber einem Signal? Mit den Thread macht
> man doch das gleiche, nur umständlicher.
Bei Thread(s) und Aufruf der blockierenden Funktion weiß man, welcher 
der Ports betroffen ist. Somit erübrigt sich das Ausgangsproblem. 
Außerdem kann man mit den üblichen Mitteln zur Thread-Synchronisation 
die Zugriffe auf Variablen außerhalb des Threads sichern. Bei Signalen 
geht das nicht, denn die kommen einfach irgendwann, und man hat keine 
Ahnung wo das Programm gerade ist und ob irgendeine der Variablen einen 
gültigen Inhalt hat.

von c-hater (Gast)


Lesenswert?

Peter II schrieb:

> Dafür würde ich mit Threads abreiten und nicht mit Signalen. Das Passt
> einfach nicht zur OOP Programmierung.

So ein Unsinn. Es widerspricht in keinster Weise dem OOP-Ansatz, mit 
Ereignissen zu arbeiten, was man allein schon daran sieht, dass 
praktisch alle OOP-Sprachen eben solche im Sprachkonzept vorsehen.

Und auch ein Signal ist letztlich nur ein (asynchrones) Ereignis. Es 
muss also ggf. einfach nur an geeigneter Stelle synchronisiert werden.

Und wenn ein Signal für mehrere Instanzen einer Klasse relevant ist, 
dann muß man halt den Signalhandler statisch implementieren und der muß 
in der Lage sein, die passende Instanz herauszufinden um dann darin den 
zuständigen Eventhandler bzw. die zuständige Callback-Methode 
aufzurufen.

Sowas zu implementieren gehört zu den grundlegenden und absolut 
erforderlichen Fähigkeiten der OOP-Programmierung.

Nur weil du es nicht beherrschst, kannst du doch nicht behaupten, dass 
es nicht zur OOP-Programmierung passt. Es passt bloß nicht zum Stand 
deiner diesbezüglichen Fähigkeiten, das ist alles.

von Peter II (Gast)


Lesenswert?

c-hater schrieb:
> Und wenn ein Signal für mehrere Instanzen einer Klasse relevant ist,
> dann muß man halt den Signalhandler statisch implementieren und der muß
> in der Lage sein, die passende Instanz herauszufinden um dann darin den
> zuständigen Eventhandler bzw. die zuständige Callback-Methode
> aufzurufen.

genau das statisch implementieren finde ich nicht OOP konform. Damit ist 
ein Objekt nicht mehr eigenständig.

> Nur weil du es nicht beherrschst, kannst du doch nicht behaupten, dass
> es nicht zur OOP-Programmierung passt. Es passt bloß nicht zum Stand
> deiner diesbezüglichen Fähigkeiten, das ist alles.
was soll immer diesen persönlichen Angriffe? Woher willst du wissen das 
ich das nicht kann, nur weil ich es persönlich anders mache? Heute hat 
jede CPU mehre Kerne und Threads gehören genauso zu Standard der 
Programmierung.

Und wenn man mal zu anderes Sprachen schaut (Java, .net) das wird es 
genauso gelöst, also scheinst es doch auch sinn zu machen.

von Mikro 7. (mikro77)


Lesenswert?

Ich sehe auch nicht wirklich warum Signale nicht zur OOP passen sollten.

Richtig ist, dass das C/UNIX Signalhandling "fehleranfällig" ist. Man 
sollte schon ziemlich genau wissen was man da macht, ob in C oder in 
C++.

Ich verzichte gern darauf, wenn möglich.

Ansonsten, ist (m)ein typischer Ansatz in einer "main loop" (dispatcher) 
auf Ereignisse (FD,Signals,timer queue,whatever) zu warten (poll/select 
mit ungeblockten Signalen) und die Events dann zu verarbeiten (während 
der Verarbeitung werden die Signale geblockt). Das geht sehr schön mit 
C++ wo man sich eine Dispatcher class schreibt, an der sich Clients für 
ausgewählte Events registrieren können.

Nichts gegen Threads. Kann man sicher auch dafür benutzen. 
Synchronisierung und Debugging machen das Leben aber nicht unbedingt 
einfacher. ;-)

von Gerd E. (robberknight)


Lesenswert?

Wenn man verschiedene Quellen hat auf die man in einem C++-Programm 
asynchron reagieren möchte, kann man auch mal einen Blick auf Boost.Asio 
werfen:
http://www.boost.org/doc/libs/1_59_0/doc/html/boost_asio.html

von Peter II (Gast)


Lesenswert?

S. J. schrieb:
> Nichts gegen Threads. Kann man sicher auch dafür benutzen.
> Synchronisierung und Debugging machen das Leben aber nicht unbedingt
> einfacher. ;-)

ich finde es sogar einfacher, wenn alles in Main-Loop verarbeitet wird, 
muss man ständig aufpassen NIE zu blockieren. Mal schnell eine Datenbank 
abfragen und schon gehen Daten vom UART verloren. Obwohl sie nichts 
miteinander zu tun haben.

von Mikro 7. (mikro77)


Lesenswert?

Mir ist nicht ganz klar, welcher Bezug zwischen deiner Aussage und dem 
zitierten Text besteht.

von Peter II (Gast)


Lesenswert?

S. J. schrieb:
> Mir ist nicht ganz klar, welcher Bezug zwischen deiner Aussage und dem
> zitierten Text besteht.

es ist der (mein)Grund "für Thread".

von Mikro 7. (mikro77)


Lesenswert?

Alles klar. Bezog sich also nicht auf deine Behauptung, dass Signale 
nicht zur OOP passen (was der Ausgangspunkt für mein Post war), und auch 
nicht darauf, dass man bei Verwendung von Threads die Synchronisierung 
bedenken muss und das Debuggen schwieriger wird (der zitierte Text); 
sondern dass man das Problem des TS "einfacher" mit Threads löst als mit 
einer Main Loop. Falls der TS Interesse hat, kann man diese Behauptung 
sicherlich diskutieren. ;-)

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


Lesenswert?

Um einmal auf die ursprüngliche Frage zurück zu kommen: 
Selbstverständlich kannst Du aus einem Signalhandler heraus die Methoden 
einer Klasse aufrufen. Das Problem ist nur, dass Du dazu eine Referenz 
oder einen Pointer auf die Instanz der Klasse benötigst. Da Du dem 
Signalhandler aber keine Parameter übergeben kannst, bleibt Dir nur die 
Möglichkeit einer globalen Variable, mit allen Nachteilen die das hat.

Hinzu kommt, dass Du Dir mit Signalen echte Nebenläufigkeit in Dein 
Programm holst. Wie andere bereits beschrieben haben zieht das einen 
ganzen Rattenschwanz weiterer Probleme nach sich. Insbesondere kann ein 
Signal Dein Programm an jeder beliebigen Stelle unterbrechen. Du wirst 
also entweder die Methoden, die auch aus dem Signalhandler  heraus 
angesprochen werden können, atomar machen oder dafür sorgen müssen, dass 
beim Aufruf dieser Methoden aus dem Hauptprogramm heraus die 
betreffenden Signale geblockt werden. (Das entspricht dann etwa einem 
Disable Interrupt.)

Die bereits vorgeschlagene main-loop mit select hat vor allem den 
Vorteil, diese Nebenläufigkeit zu vermeiden. Die Handler der main-loop 
werden streng sequentiell aufgerufen. Außerdem kannst Du ihnen beliebige 
Parameter mitgeben und vermeidest so globale Variablen.

Für den Fall, dass es unbedingt Signale sein sollen (oder müssen) 
schlagen sowohl Stroustrup als auch Alexandrescu die Verwendung von 
Proxy-Klassen vor. Das sind Klassen, in denen man Konstruktor und 
Destruktor nutzt, um bestimmte Aktionen auszuführen. In Deinem Fall böte 
sich an, eine Handlerklasse zu schreiben, deren Instanz statisch und 
global gemeinsam mit dem Signalhandlers  in einer eigenen Source-Datei 
definiert ist. Für den Zugriff aus dem Hauptprogramm gibt es dann eine 
Funktion, die einen Pointer-Proxy liefert. Der Konstruktor dieses 
Pointer-Proxys sperrt dann das (oder die) relevante(n) Signal(e), der 
Destruktor gibt es (oder sie) wieder frei. Also etwa so (ungetestet):

my_signal_handler.h:
1
class mySignalHandlerClass{  };
2
3
class myPointerProxy {
4
         mySignalHandlerClass *pointer;
5
         static int refCount;
6
    public:
7
        myPointerProxy(mySignalHandlerClass* p) : pointer(p) { if ( refCount++ == 0 ) disabelSignals(); }
8
        ~myPointerProxy() { if ( --refCount == 0 ) enableSignals(); }
9
        mySignalHandlerClass* operator-> { return pointer; } 
10
};
11
12
myPointerProxy mySignalHandlerPointer();
13
14
int mySignalHandler(int signal);

my_signal_handler.cpp:
1
static mySignalHandlerClass  mySignalHandlerInstance;
2
3
int myPointerProxy::refCount = 0;
4
5
myPointerProxy mySignalHandlerPointer() { 
6
    return  myPointerProxy(&mySignalHandlerInstance);
7
}
8
9
int mySignalHandler(int signal) {
10
    mySignalHandlerInstance. ...
11
}

Im Hauptprogramm verhält sich ein Pointer-Proxy dann wie ein normaler 
Pointer.

Aber wie gesagt, ich würde eine main-loop mit select in diesem Fall 
definitiv vorziehen.

Übrigens, Threads wären in diesem Fall wohl mindestens ein absoluter 
Overkill, wenn nicht sogar kontraproduktiv. Threads wurden erfunden, um 
explizit Nebenläufigkeit in Programme einzuführen und bieten daher 
sicher bessere Möglichkeiten diese zu synchronisieren. Du möchtest aber 
(trust me) eine bereits vorhandene Nebenläufigkeit los werden oder 
zumindest so kapseln, dass sie Dir nicht mehr auf die Füße fällt. Dafür 
ist select genau das Mittel der Wahl.

von asdfasd (Gast)


Lesenswert?

Das meiste ist schon gesagt.

SIGIO ist (nicht ohne Grund) ein Stiefkind der Unix-API.  Es korrekt zu 
benutzen ist sehr schwierig (sowohl das Beispiel auf Stackoverflow als 
auch das aus der Serial-Programming-HOWTO ist fehlerhaft) und wirkliche 
Vorteile gegenüber select/poll/epoll muss man mit der Lupe suchen.  Und 
auf die Idee, dafür einen extra Thread zu erstellen, käme ein 
Unix-Programmierer nie!

Nimm das übliche select oder poll und dein Problem mit statischen 
Methoden verschwindet von selbst.

von Dave Anadyr (Gast)


Lesenswert?

A. H. schrieb:
> Die bereits vorgeschlagene main-loop mit select hat vor allem den
> Vorteil, diese Nebenläufigkeit zu vermeiden. Die Handler der main-loop
> werden streng sequentiell aufgerufen. Außerdem kannst Du ihnen beliebige
> Parameter mitgeben und vermeidest so globale Variablen.

Vielen Dank an allen für Eure Antworten.

Durch das Lesen habe ich dennoch den Eindruck, dass ich - vielleicht - 
mein Problem nicht ganz deutlich geschildert habe. Oder verstehe ich 
Euch nicht ganz richtig.

Jedenfalls:
Mein Programm sieht momentan folgendermaßen aus:

[c]
int main( int argc, char **argv )
{
    SerialClass UART1;

    UART1.init(<serial_port>, <baud_rate> );

    while(1)
    {
      if(UART1.availableSerial() > 0)
      {
         unsigned char *data = UART1.readSingleCharSerial();
      }

      usleep( 500000 );  // Dieser wird durch einen richtigen Timer 
ersetzt. Hier nur ein Beispiel.
    }

}
[\c]

Das ist ein - sogenannte - minimalbeispiel.

Ich hätte nämlich kein Problem, bei jedem Loop zu prüfen, ob neue Daten 
über UART empfangen wurden oder nicht. Allerdings was mich stört ist, 
dass ich keine "beliebige" Funktion nun in dem Loop aufrufen kann. 
Warum? Weil ich nicht wissen kann, wie lange ich die while-Schleife 
zeitlich ausdehnen kann. Ich möchte nicht, dass Zeichen sich in dem 
seriellen Puffer überschreiben. Außerdem weiß ich nicht mit welcher 
Frequenz Daten per UART empfangen werden. Der Baudrate ist konstant, 
dennoch können Strings mit mehr oder wenigen Zeichnen empfangen werden.

Ihr sprecht oft von main-loop und epool() und pool(). Ich muss ehrlich 
sein und sagen, ich habe nicht ganz verstanden, wie ihr es meint. Mein 
Beispiel oben ist, meiner M. nach, die Implementierung einer 
poll()-Funktion. Ist genau das, was ihr auch meint?
Fall ja, ist es mir immer noch nicht klar, wie ich meine Daten asynchron 
empfangen kann. Zwar kann ich eine derartige Funktion in meiner Klasse 
erstellen, aber ich werden nie wissen, mit welcher max. Frequenz der 
main-loop laufen darf.

> Für den Fall, dass es unbedingt Signale sein sollen (oder müssen)
> schlagen sowohl Stroustrup als auch Alexandrescu die Verwendung von
> Proxy-Klassen vor.

Sehr interessantes Thema. Ich kannte es nicht. Es sieht aber 
komplizierter als das, was ich vorhatte.

> Aber wie gesagt, ich würde eine main-loop mit select in diesem Fall
> definitiv vorziehen.


Falls ich einen Denkfehler begehe, könnt ihr mir bitte ein 
minimal-Beispiel zeigen? Das wäre für mich sehr hilfreich.

> Du möchtest aber (trust me) eine bereits vorhandene Nebenläufigkeit los werden 
oder
> zumindest so kapseln, dass sie Dir nicht mehr auf die Füße fällt. Dafür
> ist select genau das Mittel der Wahl.

Richtig.
Daher...bevor ich mit dem Programm loslege, möchte ich ein paar 
Programmier-Technicken mehr lernen.

Gruß

von Peter II (Gast)


Lesenswert?

Dave Anadyr schrieb:
> Warum? Weil ich nicht wissen kann, wie lange ich die while-Schleife
> zeitlich ausdehnen kann. Ich möchte nicht, dass Zeichen sich in dem
> seriellen Puffer überschreiben.

genau das ist das große Problem wenn man mit select/poll/epoll arbeitet. 
Aus dem Grund von mir der Vorschlag mit den Threads. Da ist ein Thread 
nur für die Serielle Schnittstelle vorhanden, damit gehen keine Daten 
verloren, weil es unabhängig davon ist was in der Main gemacht wird.

von Mikro 7. (mikro77)


Lesenswert?

Hallo Dave,

grundsätzlich sieht die Verarbeitung von Events folgendermaßen aus...

while (true)
* wait for event (e.g poll)
* block signals
* identify event (what signal, which fd, timeout?)
* process event
* unblock signals

Jetzt kann es passieren, dass ein neues Ereignis während der 
Verarbeitung des vorherigen Ereignisses eintritt. Meist ist dies kein 
Problem, weil die Daten, die mit dem Ereignis zusammenhängen, gepuffert 
werden. Sie liegen auch noch beim nächsten Schleifendurchlauf vor.

Die wichtige Frage ist somit: Was genau benutzt du um die UART 
auszulesen?

von X. A. (wilhem)


Lesenswert?

S. J. schrieb:

> Die wichtige Frage ist somit: Was genau benutzt du um die UART
> auszulesen?

Der herkömmliche read-Befehl (linux):
1
 
2
int byte_counter = read( serial_port_id_, (void*)rx_message, RX_LENGTH_BUFFER - 1 );

von Mikro 7. (mikro77)


Lesenswert?

Ich kenn' nicht den Treiber und habe auch noch nix mit UART gemacht; 
tendenziell würde ich aber vermuten, dass der Puffer dort mehr als 
ausreichend ist. De es sich wohl um ein slow device handelt, sollte eine 
"normale" Ereignisbehandlung (wie oben beschrieben) keine Probleme haben 
(natürlich muss die Verarbeitungzeit im Durchschnitt unter der Anzahl 
der Ereignisse je Zeiteinheit liegen). Vielleicht kennt sich jemand hier 
im Detail mit UART aus und kann konkrete Zahlenwerte nennen.

von A. H. (ah8)


Lesenswert?

Dave Anadyr schrieb:
> Falls ich einen Denkfehler begehe, könnt ihr mir bitte ein
> minimal-Beispiel zeigen? Das wäre für mich sehr hilfreich.

Tippe doch einfach mal man select in Deine Unix-Shell oder Deinen 
Browser. Viele select (2) man-pages enthalten bereits ein 
Programmbeispiel. Auch die englischsprachige Wikipedia hat eine Seite zu 
select mit einem ausführlichen Beispiel.

Es gibt sicher auch etliche Online-Tutorial.

Ansonsten wird ein gutes Buch über Unix-Systemprogrammierung helfen. 
ISBN 0131411543 oder ISBN 013937681X zum Beispiel wären ein guter 
Einstieg. Die gibt es beide auch in deutscher Übersetzung und ganz 
sicher auch in der Bibliothek Deines Vertrauens.

Dave Anadyr schrieb:
> Sehr interessantes Thema. Ich kannte es nicht. Es sieht aber
> komplizierter als das, was ich vorhatte.

Tröste Dich, das Leben ist meistens komplizierter als das, was man 
vorhatte :-)

von Peter II (Gast)


Lesenswert?

S. J. schrieb:
> Ich kenn' nicht den Treiber und habe auch noch nix mit UART gemacht;
> tendenziell würde ich aber vermuten, dass der Puffer dort mehr als
> ausreichend ist.

falsch, die Puffer sind meist recht klein. Und wenn man dort Daten recht 
schnell überträgt, darf man sich keine großen pausen erlauben.

von Rolf Magnus (Gast)


Lesenswert?

Peter II schrieb:
> S. J. schrieb:
> Ich kenn' nicht den Treiber und habe auch noch nix mit UART gemacht;
> tendenziell würde ich aber vermuten, dass der Puffer dort mehr als
> ausreichend ist.
>
> falsch, die Puffer sind meist recht klein. Und wenn man dort Daten recht
> schnell überträgt, darf man sich keine großen pausen erlauben.

Dann ist der Treiber schlecht programmiert. Das Betriebssystem hat ja 
mehr als genug Speicher dafür zur Verfügung.

von Peter II (Gast)


Lesenswert?

Rolf Magnus schrieb:
> Dann ist der Treiber schlecht programmiert. Das Betriebssystem hat ja
> mehr als genug Speicher dafür zur Verfügung.

nur ist es nicht die Aufgabe von BS Fehler in der Anwendungssoftware 
auszugleichen.

von Mikro 7. (mikro77)


Lesenswert?

Peter II schrieb:
> S. J. schrieb:
>> Ich kenn' nicht den Treiber und habe auch noch nix mit UART gemacht;
>> tendenziell würde ich aber vermuten, dass der Puffer dort mehr als
>> ausreichend ist.
>
> falsch, die Puffer sind meist recht klein. Und wenn man dort Daten recht
> schnell überträgt, darf man sich keine großen pausen erlauben.

Tatsächlich? Quelle? Was heißt eigentlich schnell. Also UART und RPi. 
Jo, das kann kein RPi schaffen, braucht man wahrscheinlich 'nen 
Supercomputer. ;-)

Natürlich ist die durchschnittliche Processing Time eines Events von 
Bedeutung. Wenn die dann aber tatsächlich so hoch ist, dass es einen 
Backlog gibt, dann hilft auch "deine" Thread Lösung nix. Ich bin 
übrigens ein großer Fan von Threads, allerdings ein noch größerer Fan 
von Verhältnismäßigkeit. Guck Dir nochmal an was der TS machen will.

Und jetzt OT: Du programmierst schon, oder? Ich sehe von dir hier im 
Forum häufig nur (imho sinnlose) Eskalationen. Schön hier im Thread zu 
sehen: (1) Signale passen nicht zur OOP. (2) Beste Lösung sind Threads. 
(3) Puffer sind zu klein. Ist alles grundsätzlich ok, wenn man es denn 
zur Diskussion stellt. Aber immer nur weiter eskalieren... Habe ich 
keinen Bock drauf. Darum bin ich raus aus dem Thread. /OT

von Sheeva P. (sheevaplug)


Lesenswert?

Dave Anadyr schrieb:
1
>     SerialClass UART1;
2
>     UART1.init(<serial_port>, <baud_rate> );

Ein Konstruktor ist zur Initialisierung da. Warum nicht:
1
    SerialClass UART1(<serial_port>, <baud_rate>);

?

von Sheeva P. (sheevaplug)


Lesenswert?

Peter II schrieb:
> Rolf Magnus schrieb:
>> Dann ist der Treiber schlecht programmiert. Das Betriebssystem hat ja
>> mehr als genug Speicher dafür zur Verfügung.
>
> nur ist es nicht die Aufgabe von BS Fehler in der Anwendungssoftware
> auszugleichen.

Im Fall "Treiber schlecht programmiert" würde es sich nicht um einen 
Fehler in der Anwendungssoftware, sondern um einen in der 
Treibersoftware handeln.

: Bearbeitet durch User
von asdfasd (Gast)


Lesenswert?

Dave Anadyr schrieb:
> Ich hätte nämlich kein Problem, bei jedem Loop zu prüfen, ob neue Daten
> über UART empfangen wurden oder nicht. Allerdings was mich stört ist,
> dass ich keine "beliebige" Funktion nun in dem Loop aufrufen kann.
> Warum? Weil ich nicht wissen kann, wie lange ich die while-Schleife
> zeitlich ausdehnen kann. Ich möchte nicht, dass Zeichen sich in dem
> seriellen Puffer überschreiben.

Dir ist aber schon klar, dass das Kernel bereits einen mehrere Kilobyte 
großen Empfangsbuffer bereitstellt?  Hast du überhaupt schon mal das 
triviale blocking-Read ausprobiert und da Überläufe bemerkt?  Für die 
meisten Programme reicht der Kernelbuffer aus.  Solltest du doch 
zwischen zwei reads eine 5-Minuten Kaffeepause einlegen wollen, hilft eh 
kein Buffer (jeder Buffer läuft irgendwann über) - dann benutzt man 
Flusskontrolle (bei UARTs RTS/CTS oder XON/XOFF) um den Sender zu 
bremsen.  Dann darfst du in der Loop auch stundenlang rumbummeln ohne 
Daten zu verlieren.

Bei der ganzen Diskussion um SIGIO/select/poll (nicht pool btw ;-)) geht 
es darum, effizient mehrere Geräte gleichzeitig bedienen zu können.  Das 
scheint bei dir (nach dem Minimalbeispiel) aber gar nicht das Thema zu 
sein.

Mit SIGIO könnte man theoretisch den Eingangsbuffer weiter vergrößern 
ohne die "beliebige Funktion" ändern zu müssen.  Aber, auch der Buffer 
läuft irgendwann über (vergrößern geht im Signalhandler nicht), die 
Implementation ist nicht trivial und es ist eine gute Methode, 
mangelhafte EINTR-Behandlung in "beliebige Funktion" zu finden ;-)

von X. A. (wilhem)


Lesenswert?

Sheeva P. schrieb:
> Dave Anadyr schrieb:
>
1
>>     SerialClass UART1;
2
>>     UART1.init(<serial_port>, <baud_rate> );
3
>
>
> Ein Konstruktor ist zur Initialisierung da. Warum nicht:
>
>
1
>     SerialClass UART1(<serial_port>, <baud_rate>);
2
>
>
> ?

Richtig.
Und es ist auch so,wie du es beschrieben hast. Ich wollte durch init() 
die Struktur des Codes für alle klar machen.
Die Gliederung macht doch in der Praxis keinen Sinn.

von X. A. (wilhem)


Lesenswert?

asdfasd schrieb:
> Das
> scheint bei dir (nach dem Minimalbeispiel) aber gar nicht das Thema zu
> sein.

Deswegen habe ich das Minimalbeispiel gepostet. Irgendwie habe ich 
gemerkt, dass die Diskussion völlig anders läuft als erwartet. Die 
vorgeschlagenen Lösungen sind zwar sehr interessant, aber letztendlich 
geht es eher darum, die Daten asynchron zu empfangen. Polling finde ich 
sehr ineffizient.
Hätte ich das gleiche Programm für PICs oder Atmel geschrieben, dann 
hätte ich die Interrupts verwendet.

Gruß

: Bearbeitet durch User
von Peter II (Gast)


Lesenswert?

S. J. schrieb:
> Tatsächlich? Quelle? Was heißt eigentlich schnell. Also UART und RPi.
> Jo, das kann kein RPi schaffen, braucht man wahrscheinlich 'nen
> Supercomputer. ;-)
nein, das hat auch niemand behauptet. Es gibt auch Funktionen die lange 
brauchen ohne CPU zu nutzen. (z.b. Namensauflösung über DNS)

> Natürlich ist die durchschnittliche Processing Time eines Events von
> Bedeutung. Wenn die dann aber tatsächlich so hoch ist, dass es einen
> Backlog gibt, dann hilft auch "deine" Thread Lösung nix.
darum geht es doch überhaupt nicht. Er will im LOOP beliebige Funktionen 
aufrufen ohne zu wissen wie lange sie laufen und wenn es nur ein 
Sleep(1000) ist, schon läuft der Puffer über.

> Ich bin
> übrigens ein großer Fan von Threads, allerdings ein noch größerer Fan
> von Verhältnismäßigkeit.
merkt man.

> Guck Dir nochmal an was der TS machen will.
er will beliebige Funktion aufrufen ohne angst haben das sein Puffer 
überläuft.

> Und jetzt OT: Du programmierst schon, oder? Ich sehe von dir hier im
> Forum häufig nur (imho sinnlose) Eskalationen. Schön hier im Thread zu
> sehen: (1) Signale passen nicht zur OOP. (2) Beste Lösung sind Threads.
> (3) Puffer sind zu klein. Ist alles grundsätzlich ok, wenn man es denn
> zur Diskussion stellt. Aber immer nur weiter eskalieren... Habe ich
> keinen Bock drauf. Darum bin ich raus aus dem Thread. /OT
Ich fand die Diskussion recht sachlich, und stehe auch dazu das ich 
Signale und OOP nicht richtig zusammenpassen. Ein Signal ist erst mal 
nur eine Funktion ohne Kontext. Damit kann man kein Objekt direkt 
ansprechen.

von Peter II (Gast)


Lesenswert?

asdfasd schrieb:
> Dir ist aber schon klar, dass das Kernel bereits einen mehrere Kilobyte
> großen Empfangsbuffer bereitstellt?

https://www.raspberrypi.org/forums/viewtopic.php?f=44&t=61955

gerade mal 4k. Bei 1Mbaud sind das nur 40ms. Wenn man da eine Datenbank 
abfragen will, oder Daten per http versenden will wird das verdammt eng.

von A. H. (ah8)


Lesenswert?

Dave A. schrieb:
> Deswegen habe ich das Minimalbeispiel gepostet. Irgendwie habe ich
> gemerkt, dass die Diskussion völlig anders läuft als erwartet.

Das könnte daran liegen, dass aus Deinem Minimalbeispiel leider nicht 
hervor geht, was Du mit den empfangen Daten tun willst, was Deine 
„beliebige Funktion“ tun soll und wie beides zusammen hängt.

Du schreibst aber, dass Du Daten senden und empfangen möchtest. 
Unterstellen wir mal, dass die zu sendenden Daten nicht gerade durch 
einen Zufallszahlengenerator oder ähnliches erzeugt werden sollen, dann 
müssen sie ja irgendwo her kommen. Nehmen wir ferner mal an, dass Du 
dafür nicht unbedingt Mittel und Methoden der fortgeschrittenen 
Interprozesskommunikation verwenden möchtest (also Signale, Messages, 
Semaphore, Shared Memory oder ähnliches), dann wirst Du dafür wohl einen 
Filedescriptor benötigen, womit Du insgesamt schon mal zwei hast und wir 
wieder bei poll oder select sind.

Sollte Deine „beliebige Funktion“ so beliebig sein, dass sie mit den 
empfangen Daten gar nichts zu tun hat, stellt sich natürlich zunächst 
einmal die Frage, warum Du überhaupt beide Funktionen in ein Programm 
packen möchtest. Wahrscheinlicher ist aber, dass sie nur in dem Sinne 
„beliebig“ ist, dass sie sich um den zu sendenden Datenstrom kümmern 
soll. Lang anhaltende Berechnungen sind in solchen Fällen eher 
ungewöhnlich. Wahrscheinlicher ist, dass die Laufzeit Deiner „beliebige 
Funktion“ nur deshalb unvorhersehbar ist, weil sie auf ein externes 
Event wartet. Damit wären wir wieder bei der bereits mehrfach erwähnten 
main(-event)-loop.

Du kannst das Problem durchaus asynchron lösen, siehe oben. In manchen 
Fällen mag das sogar sinnvoll sein. Das hat aber seine Tücken. Das 
Betriebssystem bietet Dir Werkzeuge, diese Tücken zu umgehen und Du bist 
in der Regel gut beraten, diese Werkzeuge zu nutzen.

Ob das in Deinem Fall tatsächlich so ist kann man an Hand Deines 
Beispiels leider nicht abschließend beurteilen.

: Bearbeitet durch User
von X. A. (wilhem)


Lesenswert?

A. H. schrieb:
> Das könnte daran liegen, dass aus Deinem Minimalbeispiel leider nicht
> hervor geht, was Du mit den empfangen Daten tun willst, was Deine
> „beliebige Funktion“ tun soll und wie beides zusammen hängt.

Leute...ruhig...wieso diese Aufregung?

Ich habe mehrere Sensoren an einem Arduino angeschlossen.
Diese werden eingelesen, in einer Nachricht verpackt und dem Rpi per 
UART übertragen. Allerdings werden die Sensoren mit verschiedenen 
Taktraten abgefragt. Eine Vereinheitlichung scheint zur Zeit nicht 
möglich.

Die Daten werden dann verarbeitet (Rpi). Der Arduino dient nur dazu, die 
Sensorenausgaben in einer Nachricht (Protokoll) zusammenzufügen. Jede 
Sensorausgabe hat eine entsprechende msgid - also ihre eigene Nachricht 
-.

Das ist der erste Schritt.

Es funktioniert alles wunderbar, wenn ich es in C teste.
Ich habe alles schon am Laufen und bisher gab es gar kein Problem. Alles 
einfach und trivial.

Das Problem - wie ich in meinem ersten Post geschrieben habe - ist, dass 
ich mein Programm nun in C++ umschreiben will (soll).
Da aber ich gerne eine Klasse für die serielle Kommunikation hätte, habe 
ich das Problem, dass ich nicht weiß, wie ich Daten asynchron empfangen 
kann. WEIL die OOP eine ganz andere Softwarestruktur als C erfordert.

Auf dem Rpi werden wohl andere Programme für die Datenverarbeitung 
implementiert. Die existieren nämlich noch nicht.

Irgendjemand hatte die boost:asio hier erwähnt.
Vielleicht ist diese die bessere Lösung.

: Bearbeitet durch User
von Clemens L. (c_l)


Lesenswert?

Dave A. schrieb:
> Polling finde ich sehr ineffizient.

poll() ist kein Polling. (Ja, toller Name ...)

> Hätte ich das gleiche Programm für PICs oder Atmel geschrieben, dann
> hätte ich die Interrupts verwendet.

Vereinfacht gesagt: poll() wartet (ohne CPU-Belastung), bis ein 
Interrupt ankommt und dadurch mindestens ein Byte lesbar wird. Das ist 
letztendlich genau das gleiche (aber mit einer standardisierten API, 
mehreren Interrupt-Quellen, Timeout etc.).

von A. H. (ah8)


Lesenswert?

Dave A. schrieb:
> Das Problem - wie ich in meinem ersten Post geschrieben habe - ist, dass
> ich mein Programm nun in C++ umschreiben will (soll).

C ist (fast) vollständig eine Untermenge von C++. Folglich ist (fast) 
jedes C Programm auch ein gültiges C++ Programm. Du versuchst also ein 
Problem zu lösen, das es gar nicht gibt.

Dave A. schrieb:
> Da aber ich gerne eine Klasse für die serielle Kommunikation hätte, habe
> ich das Problem, dass ich nicht weiß, wie ich Daten asynchron empfangen
> kann.

Es spricht ja nichts dagegen, Klassen zu verwenden. Klassen bieten in 
vielen Situationen viele Vorteile. Aber eben nicht in allen. Wenn Du uns 
nicht mehr über Deinen Code sagen willst – was Dein gutes Recht ist – 
dann schau ihn Dir an und entscheide, wo er durch Klassen einfacher und 
sicherer wird. Daraus ergibt sich dann meist von alleine, wie die Klasse 
aussehen muss. Wenn Dein Code durch Klassen komplexer und 
undurchsichtiger wird, dann lass ihn wie er ist. Ein Klasse nur um der 
Klasse willen zu haben ist so ziemlich die schlechteste 
Design-Entscheidung, die Du treffen kannst. (Das kannst Du ruhig auch 
dem sagen, der eine Klasse von Dir will, falls es den gibt.)

Grundsätzlich ist jede Methode einer Klasse nichts anderes als eine 
Funktion mit einem impliziten Pointer-Argument. Du kannst also jede 
C Funktion, deren Signatur Dir nicht explizit vorgeben ist, in die 
Methode einer Klasse umwandeln, sofern Du das implizite Argument liefern 
kannst. Wie man das im Falle eines Signalhandlers machen kann, haben wir 
oben schon diskutiert. Darüber hinaus läuft die asynchrone Kommunikation 
in C++ ganz genau so wie in C.


Dave A. schrieb:
> Leute...ruhig...wieso diese Aufregung?
Also ich für meinen Teil bin gerade völlig entspannt. :-)

von A. H. (ah8)


Lesenswert?

Dave A. schrieb:
> WEIL die OOP eine ganz andere Softwarestruktur als C erfordert.

Diese Aussage dürfte nur auf einer sehr abstrakten Ebene gelten und auch 
nur dann, wenn man die Meinung teilt, dass der Begriff der OOP wohl der 
am meisten missverstandene Begriff der ganzen Informatik ist.

Grady Booch definiert in ISBN 020189551X ein Objekt als eine 
Dateneinheit, die durch Identität, Zustand und Verhalten eindeutig 
bestimmt ist. Im Gegensatz zu einer Variable, die durch  Identität, Type 
(i.e. der Menge aller möglichen Werte) und Zustand (i.e. einem Wert aus 
der Menge aller möglichen Werte) bestimmt ist und deren Zustand nur 
durch explizite Zuweisung von außen geändert werden kann, bestimmt ein 
Objekt die Zustandsübergänge selbst. Diese werden in der Regel zwar 
durch externe Ereignisse (Messages) getriggert. Das muss aber nicht 
zwangsläufig so sein. Ein Objekt kann sein Zustand durchaus auch 
selbsttätig ändern, zum Beispiel nach Ablauf einer gewissen Zeit. Daraus 
folgt aber, das ein Objekt einen eigenen Steuerfluss hat. Bereits in 
Simula waren die Koroutinen ein zentrales, wenn nicht das zentrale 
Element. Damit wäre das objektorientierte Modell im Kern ein 
Nebenläufigkeitsmodell und die Programmstruktur eines 
objektorientierten Programmes dann tatsächlich fundamental anders als 
die der klassischen imperativen Programmierung.

Nun ist allerdings meines Wissens nach keine der derzeit gebräuchlichen 
und als objektorientiert bezeichneten Sprachen objektorientiert in 
diesem Sinne, auch C++ nicht. Abgesehen vielleicht von Exceptions – die 
Du in asynchron gerufen Funktionen aber ohnehin nicht verwenden darfst – 
gibt es in C++ nichts, dass Du nicht auch in C implementieren könntest. 
Insofern gibt es auch kein C++ Programm, dass nicht unter Beibehaltung 
der grundsätzlichen Struktur in ein äquivalentes C Programm 
transformieren werden könnte.

Ich möchte damit keinesfalls die C++ spezifischen Sprachmittel als im 
Prinzip überflüssigen „syntactical sugar“ deklassieren. Sie haben 
absolut ihre Berechtigung denn sie machen das Leben des Programmierers 
einfacher und vor allem sicherer und effizienter. (Ich bin begeisterter 
C++ Programmierer!) An der grundsätzlichen Programmstruktur aber ändern 
sie nichts. Wer's nicht glaubte schaue sich mal die VFS-Schicht des 
Linux Kernels an: ein ganz klassischer objektorientierter Entwurf in 
klassischem C, allerdings mit viel Overhead und einigen hässlichen 
Casts.

von asdfasd (Gast)


Lesenswert?

Dave Anadyr schrieb:
> [...] dass ich nicht weiß, wie ich Daten asynchron empfangen kann.

Das macht doch das Kernel schon für dich.  Solange der Gerät geöffnet 
ist, nimmt das Kernel die Daten von der UART entgegen und packt sie in 
einen Kernel-internen Empfangsbuffer (also genau das, was du auf einem 
Mikrocontroller per Interruptroutine auch machen würdest).  Dabei ist es 
egal, was dein Programm gerade macht (oder auch nicht macht - 
Multitasking!).  Diesen Buffer musst du nur ab und zu per read 
auslesen/leeren.  Analog läuft es auch beim Senden: per write schreibst 
du in einen Kernel-internen Sendebuffer der dann im Hintergrund 
Byte-für-Byte übertragen wird.

> Es funktioniert alles wunderbar, wenn ich es in C teste.  Ich habe alles
> schon am Laufen und bisher gab es gar kein Problem.

Mit SIGIO?  Dann bin ich mir ziemlich sicher, dass du die 
Probleme/Fehler bisher nur noch nicht gefunden hast ;-)

von intarapt (Gast)


Lesenswert?

Programmierer schrieb:
> Du kannst unter Linux keine Interrupts nutzen.

Doch klar, für den Rpi kannste z.B. nen Interrupt Handler für jeden GPIO 
auflegen.
Macht aber keinen Sinn für UART.

von c-hater (Gast)


Lesenswert?

Peter II schrieb:

> Ein Signal ist erst mal
> nur eine Funktion ohne Kontext. Damit kann man kein Objekt direkt
> ansprechen.

Das ist richtig.

> und stehe auch dazu das ich
> Signale und OOP nicht richtig zusammenpassen.

Das ist trotzdem grundfalsch.

Du vergisst einfach, das OOP nicht nur aus Objekten besteht, sondern 
ganz wesentlich auch aus Klassen. Objekte sind Instanzen von Klassen, 
die den Code der Klasse sharen und obendrein die Daten der Klasse. Denn 
in allen mir bekannten OOP-Sprachen dürfen auch Klassen Daten besitzen!

Eben genau die, die für alle ihre Instanzen gleichermaßen relevant sind. 
Es wäre doch völliger Schwachsinn, wenn z.B. eine SerialPort-Klasse 
NICHT die Information darüber bereitstellen würde, welche Instanzen 
überhaupt möglich sind, denn das hängt doch davon ab, wieviele 
Schnittstellen zur Laufzeit (!!!) überhaupt verfügbar sind. Und noch 
größerer Schwachsinn wäre, wenn jede Instanz das erneut ermitteln würde. 
Nein, natürlich ist der einzige sinnvolle Ansatz, solche "globalen" (nur 
klassenglobalen!) Daten in der Klasse selber zu ermitteln und zu 
verwalten.

Und diese Daten müssen keinesfalls wirklich "global" sein, wie im Thread 
schwachsinnigerweise (sicher von einem C-ler: der Seuche an sich) 
geschrieben wurde, also global im Sinne von: Für jeden Arsch 
RW-zugreifbar. Nein, ihre Sichtbarkeit und Zugreifbarkeit kann natürlich 
genauso fein kontrolliert werden wie der auf die Daten von 
Klassen-Instanzen (also: Objekten).

Ich bleibe dabei: deine OOP-Kenntnisse sind absolut unzureichend! Deine 
Erfahrung beschränkt sich ganz eindeutig auf die Rolle als OOP-Anwender, 
du hast niemals als OOP-Infrastruktur-Ersteller gearbeitet. Du versuchst 
jetzt offensichtlich gerade, in diese Rolle zu schlüpfen, und dein 
bisher angesammeltes Scheinverständnis des OOP-Konzeptes löst sich dabei 
einfach mal in Luft auf. Das (und nur das) ist im Kern deines Problems.

Abhilfe: Lernen.

von X. A. (wilhem)


Lesenswert?

A. H. schrieb:
> Grady Booch definiert in ISBN 020189551X ein Objekt als eine
> Dateneinheit, die durch Identität, Zustand und Verhalten eindeutig
> bestimmt ist. Im Gegensatz zu einer Variable, die durch  Identität, Type
> (i.e. der Menge aller möglichen Werte) und Zustand (i.e. einem Wert aus
> der Menge aller möglichen Werte) bestimmt ist und deren Zustand nur
> durch explizite Zuweisung von außen geändert werden kann, bestimmt ein
> Objekt die Zustandsübergänge selbst.

Gut.
Vielen Dank Euch alle, die Diskussion war sehr hilfreich und 
interessant. Besonders Interessant fand ich den letzten Beitrag von 
A.H..

Mittlerweile habe ich die von Boost entwickelten Bibliotheken in meinem 
Programm implementiert. Sie funktionieren einwandfrei... Ich habe mich 
an dieses Beispiel orientiert: https://gist.github.com/yoggy/3323808

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.