Hallo Zusammen, ich entwickle gerade eine Software, die Daten in Form von CSV-formatierten ASCII-Strings zwischen einer GUI und einem Arduino austauscht. Dabei werden Daten vom Arduino zur GUI innerhalb einer Interrupt-Routine alle 100 Millisekunden versendet und Daten von der GUI innerhalb der main-loop des Arduinos empfangen, sobald Daten an der seriellen Schnittstelle ankommen. In etwa so: // Daten an die GUI senden, Aufruf alle 100 ms ISR { Serial.print(variable1); Serial.print(","); Serial.print(variable2); // [usw.] } // Daten von der GUI empfangen void loop { while (Serial.available() > 0) { var1 = Serial.parseInt(); var2 = Serial.parseInt(); // [usw.] } } Wird in der GUI z.B. ein Button angeklickt, so wird ein csv-String an den µC geschickt, der daraufhin eine Aktion ausführt und zur Bestätigung einen csv-String an die GUI zurückschickt. Das Problem liegt nun darin, dass teilweise das Senden und Empfangen von Daten vermischt wird. D.h. noch während in der main-loop der empfangene String geparst wird, wird die ISR aufgerufen und der µC sendet. DAs resultliert dann natürlich in fehlerhaften Verhalten. Als Lösung wollte ich die Interrupts im Block in der main-loop mittels cli() und sei() sperren. Also so: void loop { while (Serial.available() > 0) { cli(); var1 = Serial.parseInt(); var2 = Serial.parseInt(); // [usw.] sei(); } } Das funktioniert aber scheinbar nicht, denn sobald man einen csv-string an den µC sendet, wird die ISR gar nicht mehr ausgeführt, d.h. es werden keine Daten mehr an die GUI gesendet. Hat jemand eine Idee, woran das liegen könnte? Habe ich die Funktionsweise von sei() und cli() u.U. falsch verstanden? Das gewollte Verhalten ist ja, dass das Interrupt während des Parsen des empfangenen Strings pausiert und nach Abschluss des Parsens wieder ausgeführt werden kann. Wäre echt toll, wenn mir diesebzüglich jemand auf die Sprünge helfen könnte. Viele Grüße Lukas
Ich kapiere es nicht: warum und wie sollten sich gesendete und empfangene Daten vermischen? Echoed der Empfänger etwa die vom Arduino gesendeten Daten?
Hallo The Daz, danke erstmal für deine Antwort! Die vom Arduino gesendeten Daten werden in der GUI wieder eingelesen. Konkret sieht es z.B. so aus, dass ein Wert in eine Edit-Box geschrieben wird und im Edit-Box-Callback der Wert an den Arduino gesendet wird. Dieser sendet den gleichen Wert wieder an die GUI zurück (dort gibt es einen Event-Handler, der bei ankommende Daten ausgeführt wird) und wird in die Edit-Box eingetragen. Sprich, es liegt ein Echo vor. Im Grunde könnte ich natürlich auch einfach die Daten an den Arduino senden und sie nicht zurückübertragen. Aber im diesem Fall ist nicht sichergestellt, dass die Daten in der GUI und auf dem µC konsistent sind. Ich müsste ja nur die ISR im µC kurz pausieren und nach dem Einlesen der ankommmenden Daten wieder aktivieren. Aber das funktioniert wie gesagt aus irgendeinem Grund nicht wie erwartet. Die ISR enthält zugegebenermaßen auch relativ viele Befehle (35x Serial.print() und einen PID-Regelalgorithmus). Ich suche gerade nach einer Möglichkeit, diese zu verkürzen.
Ich habs auch nicht verstanden, aber es könnte gut sein, dass genau das dein Problem ist: Lukas Bommes schrieb: > Die ISR enthält zugegebenermaßen auch relativ viele Befehle (35x > Serial.print() und einen PID-Regelalgorithmus). Ich suche gerade nach > einer Möglichkeit, diese zu verkürzen. Aber ich weiss nicht, wie serial.print arbeitet. Interruptgesteuert oder polling? Die übliche Lösungsweise: serielle Schnittstelle im Interruptbetrieb mit ausreichend Ringbuffer für Senden und Empfangen. Interruptroutinen selbst sind kurzzuhalten. Ja, kurz ist relativ. Aber deine ist definitiv nicht "kurz".
Dein Problem ist, Du benutzt kein Protkoll. Du must Dir erstmal ein Protokoll ausdenken, was die Synchronisation sicherstellt und Übertragungsfehler erkennt und korrigiert. Oder Du benutzt irgendein Standardprotokoll.
Serielle Ausgaben in einer ISR sind selten eine gute Idee.
H.Joachim S. schrieb: > Aber ich weiss nicht, wie serial.print arbeitet. Interruptgesteuert oder > polling? Wenn das Arduino Zeugs ist, dann kann ich da aushelfen. Serial arbeitet Interruptgesteuert. Es ist also verboten Serial.print() in einer ISR aufzurufen. Das geht so lange gut, bis der Buffer voll ist. Dann gibts einen Deadlock. ISRs schlank halten. Kein Serial, kein delay!
Lukas Bommes schrieb: > ISR { > Serial.print(variable1); > Serial.print(","); > Serial.print(variable2); > // [usw.] > } Setze Dir in der ISR nur ein Flag, dass Du etwas zu senden hast, also:
1 | static volatile uint8_t send_data = 0; |
2 | ...
|
3 | ISR
|
4 | {
|
5 | send_data = 1; |
6 | }
|
Und dann in der Main-Loop:
1 | if (send_data) |
2 | {
|
3 | send_data = 0; |
4 | Serial.print(variable1); |
5 | Serial.print(","); |
6 | Serial.print(variable2); |
7 | ...
|
8 | }
|
Hallo ihr alle, herzlichsten Dank erstmal für eure hilfreichen Antworten! @Peter: Ein Protokoll wäre natürlich die solidere Lösung für das Problem gewesen, aber ich dachte mir, die Lösung mit den versendeten Zeichenketten funktioniert auch und ist (für mich persönlich) einfacher zu verstehen und implementieren. Ansonsten besten Dank für den Tipp mit dem Status-Flag. Damit tritt das Problem nicht mehr auf. Ich bin zwar mir nicht ganz sicher, warum die längere ISR zu dem Problem geführt hat, aber offensichtlich war sie die Ursache. Viele Grüße Lukas
So, wie es anfänglich war: -in der 100ms-ISR werden Zeichen verschickt, aber nicht direkt, sondern in einen Buffer geschrieben -eine serielle ISR (hier unsichtbar, Arduino intern) schickt die Zeichen aus dem Buffer dann tatsächlich raus. Nun ist ein Buffer nicht beliebig gross. Nehmen wir einfach mal 16Byte an. Normalerweise verschickst du alle 100ms nur sagen wir 12Byte, alles läuft. Die 100ms-ISR kann die 12Byte in den Buffer schreiben und wird verlasssen. Jetzt kann die serielle ISR diesen Buffer abarbeiten. Nun kommt der Fall, dass schon ein par Byte im Sendebuffer stehen, wenn die 100ms-ISR aufgerufen wird, d.h. die zu sendenden 12Byte passen nicht rein. Und dann wird dort solange gewartet, bis es passt. Passiert aber nie, da du dich in einer ISR befindest und demzufolge nicht ein einziges Byte aus dem Buffer verschickt wird, da der serielle Int nicht zum Zuge kommt -> dead lock.
Lukas Bommes schrieb: > Dabei werden Daten vom Arduino zur GUI innerhalb einer > Interrupt-Routine alle 100 Millisekunden versendet und Daten von der GUI > innerhalb der main-loop des Arduinos empfangen Du machst es grundfalsch. Das ist alles. Also: 1. arbeite mit Events und einer sinnvoll eingerichteten Systemuhr in deinem Arduino. Wie man ein Event-System schreibt und es mit Events versorgt (z.B. alle 100 ms ein "event100ms_um" oder so), kannst du in der Lernbetty nachlesen. 2. arbeite dir ein Übertragungsprotokoll für den Kanal aus. (serielle Schnittstelle oder was such immer du verwenden willst) Also was welche Seite senden darf und was die Bedeutung dessen ist und wie die Gegenseite darauf reagieren muß und wie du es einrichtest, daß du beide Seiten auch bei Synchron-Verlust wieder synchronisiert bekommst. 3. mache längliche Arbeiten (Senden längerer Texte, Berechnungen usw.) NIE NIE NIE in irgendeinem Interrupt-Handler, sondern verlagere dieses in Funktionen, die du von der Grundschleife in main aus aufrufst. Eben so, daß Interrupts niemals blockiert werden. 4. richte dir größere Ringpuffer für's Senden ein. W.S.
Frank M. schrieb: > Setze Dir in der ISR nur ein Flag, dass Du etwas zu senden hast, > also:static volatile uint8_t send_data = 0; > ... > ISR > { > send_data = 1; > } > > Und dann in der Main-Loop:if (send_data) > { > send_data = 0; > Serial.print(variable1); > Serial.print(","); > Serial.print(variable2); > ... > } Auf Umwegen kommt man auch ans Ziel. Eine ISR, in der nur ein Flag gesetzt wird, welches dann in der Mainloop abgefragt wird, kann man sich mitsamt dem Interrupt ganz sparen und das Interruptflag auch in der Mainloop abfragen:
1 | if(IRQREG & (1 << INTFLAG)) |
2 | {
|
3 | IRQREG |= (1 << INTFLAG); |
4 | Serial.print(variable1); |
5 | Serial.print(","); |
6 | Serial.print(variable2); |
7 | }
|
W.S. schrieb: > NIE NIE NIE in irgendeinem Interrupt-Handler Immer diese Dogmen. Naja, meistens ist es ja richtig.
Mein grosses V. schrieb: > Immer diese Dogmen. Naja, meistens ist es ja richtig. Du meinst "Wiedereintrittsfähigkeit"? Da gilt es dann aber einiges an Extrawürsten zu backen...
Mein grosses V. schrieb: > if(IRQREG & (1 << INTFLAG)) > { > IRQREG |= (1 << INTFLAG); Da schlägt die AVR-Falle zu und löscht alle Interrupts in dem Register. Beim AVR muß es heißen:
1 | if(IRQREG & (1 << INTFLAG)) |
2 | {
|
3 | IRQREG = (1 << INTFLAG); |
Mein grosses V. schrieb: > Auf Umwegen kommt man auch ans Ziel. Der "Umweg" hat einen nennenswerten Vorteil: Er beschreibt ein Konzept, ist leicht um weitere "Handler" erweiterbar und ist überdies plattformunabhängig und damit auch portabel. Vollkommen egal, ob Du ATmega, Arduino, STM32 oder ESP8266 programmierst. Dein "direkter" Weg schlägt sich mit prozessorspezifischen Interrupt-Registern rum, die dann auch mal falsch bedient werden können - wie Peter gezeigt hat.
Fuer solche Geschichten, dh ein zusammengemurkstes Protokoll ist erhoehter Aufwand erforderlich. Dh ein aufwendigeres Verfahren. Minimale Arbeit macht ein MasterSlave Protokol. Der Master(PC) sendet Anfragen, der Slave(Controller) antwortet. Dh der Slave sendet nie von selbst. Dann haben die Meldungen einen direkten Bezug, kein Vermischen, der Master macht die Priorisierung der Anfragen. Falls der Controller nun periodisch senden soll, so muessen diese Packete als solche gekennzeichnet sein. Anfragen des PC haben nun wahrscheinlich Prioritaet. Dh der Controller muss in Bloecken arbeiten. Dh kommt eine Anfrage rein, so wird diese verarbeitet, und das Resultat in den Sendebuffer gefuellt, hinter den bereits zu sendenden Daten. Die periodischen Daten werden nicht mehr strikt nach Timing gesendet, sondern niedriger priorisiert gegenueber den direkten Anfragen, dh wenn die Anfrage vorbei ist, kommt wieder ein anstehendes Periodisches Packet dran. Die Antwortpackete werden auch als solche gekennzeichnet. So einfach CSV Packete auf dem Schirm zu lesen sind, so schwierig sind sie zu verarbeiten. Man kann einer Anfrage eine feste dem Befehl entsprechende Nummer mitgeben, die in der Antwort wieder enthalten ist. So kann man Anfrage und Antwort zuordnen.
Peter D. schrieb: > Da schlägt die AVR-Falle zu und löscht alle Interrupts in dem Register. Hast recht. Wahr wohl schon zu spät.
Schau Dir z.B. mal SCPI an. https://de.wikipedia.org/wiki/Standard_Commands_for_Programmable_Instruments
Frank M. schrieb: > Dein "direkter" Weg schlägt sich mit prozessorspezifischen > Interrupt-Registern rum, die dann auch mal falsch bedient werden können > - wie Peter gezeigt hat. Den Fehler habe ich auch schon bemerkt. Klar, daß du dein Konzept verteidigst. Aber es ist nicht das allein gültige, sondern meine Alternative ist die mit der besten Performance. Und was die ewig zitierte Platformunabhängigkeit angeht: Es muß nicht alles zu allem kompatibel sein.
Mein grosses V. schrieb: > Und was die ewig > zitierte Platformunabhängigkeit angeht: Es muß nicht alles zu allem > kompatibel sein. Hmm... https://de.wikipedia.org/wiki/Softwarekrise
Frank M. schrieb: > Setze Dir in der ISR nur ein Flag, dass Du etwas zu senden hast, > also:static volatile uint8_t send_data = 0; > ... > ISR > { > send_data = 1; > } Das war wohl nix. Merke: Wir haben hier zwei voneinander unabhängige Prozesse. Wenn du in einem nur ein Flag setzen willst, was der andere dann nach Kenntnisnahme wieder löschen muß, hast du das klassische Konkurrenzproblem. Das Event-Prinzip geht anders: 1. es gibt nen Fifo für "events". Was ein event ist, ist Geschmackssache, z.B. eine Integerzahl ungleich Null. 2. es gibt eine Funktion zum einstapeln von Events, also sowas wie void AddEvent(int event); und die ist saukurz und schnell. 3. Die Interrupt-Routine plaziert über AddEvent eben so einen Event in dem Fifo. 4. in der Grundschleife in main gibt es eine Abfrage: if (EventAvailable()) DispatchEvent(GetEvent()); Damit wird der Fifo wieder geleert und die entsprechende Aktion ausgeführt. Das Ganze ähnelt einem flankengetriggerten Bauteil oder auch der Abarbeitung von Tastendrücken. W.S.
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.