Moin Leute, man bekommt ja immer wieder mit dass sog. "blockeirende" Programmierung verpöhnt ist (wohl auch zurecht). Nun mal aber die Frage: Wie macht man es denn Richtig? Beispiel: Ich habe eine Steuerung, die über eine Schnittstelle einen Befehl sendet und dann auf die Antwort wartet, um diese auszuwerten. Dazwischen bzw nebenher passiert eigentlich in der Zeit garnichts. Aufgrund des Aufbaus und der doch recht unterschiedlichen Nachrichten finde ich es hier am konkreten Beispiel mal einfach übersichtlicher mit einzelnen UART_READ() in den Subroutinen zu Arbeiten statt einem riesen Automaten in der ISR. Wie gesagt, für die eigentliche Funktion des Programmes hat es eigentlich auch keine Bedeutung. Jetzt stellt sich nur die Frage: Was mache ich, wenn die Gegenstelle mal garnicht Antwortet? Mit der ISR-Variante steht mein Programm ja auch erst mal. Wie löst man das "richtig"? In einer Schleife das Receive-Flag abfragen und einen Timer loslaufen lassen, der dann irgendwann die Schleife unterbricht? Würd mich einfach mal interessieren, was man da so empfiehlt, da ich es bisher halt immer ungefähr so gemacht habe.
Jens Plappert schrieb: > Nun mal aber die Frage: Wie macht man es denn Richtig? Das System sieht eigentlich oft immer gleich aus:
1 | int main() |
2 | {
|
3 | ....
|
4 | |
5 | while( 1 ) { |
6 | |
7 | if( Ereignis ) |
8 | ....
|
9 | |
10 | |
11 | if( anderes Ereignis ) |
12 | .....
|
13 | |
14 | etc. etc. |
15 | }
|
16 | }
|
d.h. in der Hauptschleife werden nacheinander alle möglichen Ereignisauslöser geprüft und wenn eines davon vorliegt, wird es bearbeitet. > Beispiel: Ich habe eine Steuerung, die über eine Schnittstelle einen > Befehl sendet und dann auf die Antwort wartet, um diese auszuwerten. Auch das geht nach dem gleichen Schema. Denn das eintreffen eines Zeichens ist auch nichts anderes als ein Ereignis. Das Zeichen hängt man zb an einen String an, in dem die bisherig empfangenen Zeichen gesammelt wurden und prüft vielleicht noch, ob damit die Antwort vollständig ist. Wenn ja, dann setzt man zb seinerseits ein Ereignis. Denn: niemand sagt, dass ein 'Ereignis' etwas ist, dass nur von externer Hardware ausgelöst werden kann. Ein 'Ereignis', das kann genausogut eine Variable sein, die einen bestimmten Wert annimmt und welches später bei der Ereignisprüfung auf genau diesen Wert geprüft wird. Eine Statemachine ist eine andere Variante, die in eine ähnliche Richtung geht. Zudem sagt ja auch niemand, dass ein Programm nur aus einer einzigen Statemaschine bestehen darf. Da drfen ja ruhig auch mehrere derartige Mechanismen gleichzeitig (hintereinander) am Werk sein. Im konkreten Beispiel der UART bietet sich dann auch noch eine UART für die ISR an, die das Zeichen sammeln übernimmt. Aber wie auch immer man es macht. Der kleinste gemeinsame Nenner besteht immer darin, dass es nur eine einzige Schleife gibt, die ständig läuft und das ist die Hauptschleife in main
1 | int main() |
2 | {
|
3 | ...
|
4 | |
5 | while( 1 ) { |
6 | |
7 | ...
|
8 | }
|
9 | }
|
natürlich kann es vorkommen, dass zwischendurch mal kurz eine Schleife etwas mehr Zeit verbrutzelt, wenn sicher gestellt ist, dass es bei dieser kurzen Zeit bleibt. Aber sobald das nicht mehr erfüllt ist, wandelt man seine Aufgabe um in eine Abfolge von Ereignissen und die Hauptschleife fungiert wie ein Schrittschalter, der durch diese Ereigniskette durcharbeitet und so auch anderen Programmteilen die Gelegenheit gibt, ihr Ding zu machen. > Würd mich einfach mal interessieren, was man da so empfiehlt, da ich es > bisher halt immer ungefähr so gemacht habe. Jedes Programm ist anders. Genau das ist es ja, was Programmierung so interessant macht. Wie gut eine bestimmte Technik funktioniert, hängt von der Aufgabenstellung und von den Nebenbedingungen ab. Das kann auch bedeuten, dass man sich ab und an mal etwas Neues einfallen lassen muss. Aber so im Großen und Ganzen funktioniert Ereignisbasiertes Arbeiten bzw. Statemaschines in den meisten Fällen sehr gut. Zumindest als Ausgangspunkt ist das immer gut geeignet.
Wenn es nix anderes zu tun gibt, kann man ruhig warten. Der Funktion einen "TimeOut" Parameter zu spendieren kann nützlich sein, aber nur wenn du dann was damit anfangen kannst. In diesem Konkretem Beispiel wäre vermutlich ein Watchdog eine Möglichkeit, falls ein Reset in Ordnung ist, oder alternativ den WD Interrupt nutzen um den User vorher nochmal darüber zu informieren, dass ein Timeout auftrat oder ein Schleife via Flag zu unterbrechen.
Blockierende Warteschleifen sind völlig Ok, wenn der Mikrocontroller in dieser Zeit nicht anderes zu tun hat. Ansonsten lies Dich mal in das Thema "Endlicher Automat" bzw "State Machine" ein. Der Forscher Adam Dunkels hat zu diesem Zweck ein Rahmenwerk konstruiert, welches den Code leichter lesbar machen soll. Ob er sein Ziel erreicht hat, mag jeder selbst beurteilen. Ich habe diese "Protothreads" von ihm mal auf deutsch beschrieben: http://stefanfrings.de/avr_io/protosockets.html
Hi Wenn es um Datenkomunikation geht, Empfang mit ISR, senden aus Programm heraus. Hintergrund: Ein Controller, der nur auf einen Empfang wartet und nur dann aktiv werden soll ist vielleicht speziell hier der Fall, aber nicht immer Usus. Der Aufwand, Empfang in ISR ist dagegen minimal: ISR: Zeichen aus Empfangspuffer holen In Ringpuffer schreiben Schreibzeiger Ringpuffer erhöhen Wenn Grenzwert erreicht, dann Anfang Ringpuffer Ende ISR Im Haupt programm gibt es dann einen Lesezeiger auf den Ringpuffer. Sind Daten eingetroffen, sind Lese- und Schreibzeiger unterschiedlich. Bearbeite Empfang: Vergleiche Lese- und Schreibzeiger Gleich, dann Ende Hole Zeichen aus Ringpuffer Bearbeite Zeichen Erhöhe Lesezeiger Wenn Grenzwert erreicht, dann Lesezeiger auf Anfang Ringpuffer ende Damit verlierst du kein einziges Zeichen, egal welche Baudrate. Gruß oldmax
Stefan schrieb: > Blockierende Warteschleifen sind völlig Ok, wenn der Mikrocontroller in > dieser Zeit nicht anderes zu tun hat. Sehe ich gar nicht so! Man sollte von Anfang an "richtig" (siehe Karl Heinz) programmieren. Genau das ist der Gedankenfehler, den man oft macht. Man bekommt ein Stück bestehende Firmware und jemand sagt "Bau doch noch mal ganz schnell XYZ ein, der Rest funktioniert". Und dann läuft es auf ein Re-design hinaus, weil der ursprüngliche Programmierer eben genau so gedacht hat.
oldmax schrieb: > Damit verlierst du kein einziges Zeichen, egal welche Baudrate. > Gruß oldmax Naja ;-) Außer, wenn die Daten (über längere Zeit) schneller hereinkommen als sie verarbeitet werden. Ein Ringpuffer ist aber auch nicht die Eierlegende WoMiSau. Ich habe in ein anderes Projekt vor kurzem eine XMODEM Implementierung eingebaut und mich u.A. deswegen gegen einen Ringpuffer entschieden, weil ich den Datenpuffer direkt an die datenverarbeitende Applikation übergeben kann ohne (erneut) zu kopieren. Zumal die Blockgröße fix ist und nach dem Empfang eines Blocks sowieso genug Zeit zur Verarbeitung ist (also dort keine weiteren Zeichen mehr eintreffen).
Hallo, solche Probleme werden normalerweise mit einer "Statusmaschine" bearbeitet, auch wenn das dem Programmierer nicht immer bewusst ist. Es gibt dazu auch eine Theorie der Mealy/Moore-Maschinen, da kannst du dich auch theoretisch damit beschäftigen. Kurz gesagt: es gibt einen Status (z.B. idle,waitforirgendwas) und es treten Ereignisse ein (Zeichen empfangen), und zu jeder Kombination von Status und Ereignis gibt es eine Reaktion und einen neuen Status. Wenn man das bewusst so nach einer Tabelle macht (es gibt auch Software dafür, oder man packt das ganze in eine Sprungtabelle), dann hat man den RIESIGEN Vorteil, dass das System vollständig definiert ist, Aufhänger durch unerwartete Ereignisse gibt es nicht. Die Hauptschleife in main wartet auf Ereignisse und ruft beim Eintreffen die passende Maschine auf. Die jeweilige Reaktion darf die Hauptschleife nicht lange ausser Betrieb setzen, das ist der Grund, warum lokale Warteschleifen so böse sind. Es können ganz unterschiedliche Maschinen bearbeitet werden und auch verschachtelte: eine Maschine empfängt einzelne Zeichen vom UART (wird also bei jedem Zeichen aufgerufen) und packt sie in einen Puffer, bis eine Message komplett ist, diese Message ist dann ein Ereignis für die übergeordnete Maschine, die den Austausch mit einem Client darstellt. So ist das auch gut wartbar, man kann die Maschine für den zeichenweisen Empfang austauschen ohne die übergeordnete Protokollebene zu ändern. Soweit hat das ganze noch keinen Zeitbezug, daher ist es zweckmässig, für die Maschine ein Timerereignis zu definieren, damit zählt die Maschine ihren eigenen Zeitzähler hoch und generiert ev. ein Timeout-Ereignis - das Mindeste wäre dann alles rücksetzen auf Null. Wie oben schon erwähnt - macht man das mit einer Status-Ereignistabelle und setzt da gleich überall eine Dummy-Reaktion ein, ist die Logik von Anfang an vollständig definiert und man kann sich den einzelnen Reaktionen widmen, die den Ablauf festlegen. Die Statusmaschinen gab es schon lange vor der Objekt-Orientierung, aber für die sind sie hervorragend geeignet, eine Maschine = ein Objekt mit Status, Reaktionstabelle und den Reaktions-prozeduren. Gruss Reinhard
Hi >Naja ;-) Außer, wenn die Daten (über längere Zeit) schneller >hereinkommen als sie verarbeitet werden. In einem solchen Fall verlierst du auf jeden Fall die Daten.... Der Ringpuffer ist natürlich nicht die EiWoSau, aber damit erschlägt man mindestens 50-60%. Und wenn er groß genug ist, hält er länger durch, als ein statischer Puffer. Aber egal, ich hab nur manchmal das Gefühl, das Panik ausbricht, sobald das Wort "Interrupt" erwähnt wird. Vielleicht liegt hier auch der Grund für diese Frage. Dabei ist es überhaupt kein Hexenwerk. Wenn der Postbote klingelt, dann unterbrechen wir doch auch die aktuelle Tätigkeit, um ihm seine Post abzunehmen. Kein Mensch erwartet, das diese auch sofort gelesen wird. Die Vorgehensweise ist vielfältig beschrieben. Einfach mal ausprobieren und feststellen, "das geht ja gut ". Wer es durchschaut hat, und das ist gar nicht sooo schwierig, wird mit Interrupts gern arbeiten, zumindest beim Datenempfang. Gruß oldmax
Nicht falsch verstehen: Ich habe das schon oft genug gemacht, meistens auch mit Interrupts, mir gehts eigentlich nur um die Prinzipfrage an sich.
Jens Plappert schrieb: > mir gehts eigentlich nur um die Prinzipfrage an > sich. Es gibt keine Prinzipfrage an sich. Es gibt unterschiedliche Probleme und unterschiedliche Lösungen. Es gibt keine einheitlich 'beste' Lösung aka Eierlegende Wollmilchsau am besten aber vegetarisch. Was 'good practices' betrifft stimme ich dem zu was Karl Heinz, Oldmax und Simon gesagt haben.
Es gibt dafür sogar einen mathematischen Beweis. Ich habe darüber einmal in einem Seminar referiert. Es gibt keine allgemeinoptimalen Algorithmen! "Ob ISR oder nicht" hat nicht so viel mit dem blockierenden Programmieren zu tun wie man zunächst meint. Was das einfach oder nicht betrifft: Die ISR läßt sich auch in der UART_READ() verbergen. Ich kann also genauso einfach und/oder blockierend mit ISR programmieren. Es sieht nur intern anders aus. Man muß aber im Hinterkopf behalten, daß die Codeausführung jederzeit unterbrochen werden kann, sobald man irgendwo Interrupts zuläßt. Dabei ist es irrelevant ob der aktuelle Code-Abschnitt selbst ISR verwendet. Umgekehrt kann ich damit fremden Code der für eine interruptfreihe Umgebung entwickelt wurde, auch wenn ich ihn nicht anrühre, durch Hinzufügen von Interrupts in meinem Code verhackstücken. Meine ISR unterbrechen dann den ansonsten fehlerfreien fremden Code bei der Ausführung und erzegen so sporadische kaum auffindbare Fehler zur Laufzeit, natürlich im Einsatz und nicht im der Entwicklungsabteilung. Sollte das ein Problem darstellen, so gibt es auch dafür Mittel und Wege. Aber das wäre ein anderes Thema. Blockierend oder nicht ist eine Frage des Designs/der Planung. ISR sind "nur" ein mächtiges Werkzeug, das man richtig oder falsch benutzen kann. Das Thema ist zu komplex um es in einem Beitrag zu erklären. Selbst wenn man es aus einen Vorlesungsscript herauskopiert, so ist damit nicht viel gewonnen. Um es zu verstehen und sinnvoll einzusetzen ist der Stoff der gesamten Vorlesung nötig. Konkrete Fragen daraufhin sind in Beiträgen zu lösbar. Erste Stichpunkte wäre in einem Script zu Betriebssystemen: asynchron, atomar, Mutex, Monitore, Semaphore ... Sicher kann man auch ISR ohne das verwenden. Lustig wird dann aber die Fehlersuche wenn einmal eine ISR genau da reinhaut, wo etwas eigentlich keine Unterbrechug verträgt. Das sind dann schwer reproduzierbare Fehler die mitunter extrem schwer zu finden sind. Gruß Carsten
Der Anfänger schreibt gerne:
1 | void task1() |
2 | {
|
3 | while( nix_zu_tun ) |
4 | ; // drehe Däumchen |
5 | tue_was(); |
6 | }
|
Damit ist klar, das nichts anderes mehr zum Zuge kommt. Der Trick ist nun, immer abweisend zu warten:
1 | void task1() |
2 | {
|
3 | if( nix_zu_tun ) |
4 | return; // gibt die Wartezeit zurück zur Main-Loop |
5 | tue_was(); |
6 | }
|
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
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.