Frage:
Wie verwende ich flüchtige Variablen in Arduino richtig?
daltonfury42
2015-12-17 00:00:53 UTC
view on stackexchange narkive permalink

Ich habe ein kleines Projekt mit einem Arduino Uno gemacht. Es handelte sich um Interrupts, da ich Encoder verwende, um zu messen, wie weit sich das Differentialradsystem vorwärts bewegt. Mein Roboter bewegt sich nur vorwärts. Ich verwende also nur einen einzigen Kanal von jedem Encoder. Hier sind meine beiden Interrupt-Routinen:

  ISR (INT0_vect) {encoderRPos = encoderRPos + 1; } ISR (INT1_vect) {encoderLPos = encoderLPos + 1;}  

Die Variablen encoderRPos und encoderLPos sind vom Typ flüchtig int . Ich verstehe, dass die Variablen, die sich in einer Interruptroutine ändern, vom Typ flüchtig sein müssen. Dies dient dazu, andere Teile des Codes, die diese Variablen verwenden, zu warnen, dass sie sich jederzeit ändern können.

Aber was in meinem Code passiert ist, war etwas seltsam und ich konnte es nicht erklären. So berechne ich die vom linken Rad zurückgelegte Strecke:

  #define distancePerCount 0.056196868 float SR = distancePerCount * (encoderRPos - encoderRPosPrev); float SL = distancePerCount * (encoderLPos - encoderLPosPrev); encoderRPosPrev = encoderRPos; encoderLPosPrev = encoderLPos;  

Wenn ich jedoch Folgendes auf meinen seriellen Monitor drucke, stelle ich eine Anomalie fest:

enter image description here

Wenn Sie sich die dritte Spalte (SL) ansehen, ist ihr Wert nur für einige Zeit zu hoch. Das stört alle meine Berechnungen.

Der einzige Hinweis, den ich erhalten kann, wenn ich den Wert von SL (3682), der immer eine Konstante ist, nehme und (encodeLPos - encoderLPosPrev) zurückrechne Ich erhalte 65519.66, was nahe am Maximalwert von unsigned int liegt. Was bedeutet, dass (encoderLPos - encoderLPosPrev) einen Überlauf verursacht, während beide Werte, deren Differenz angenommen wird, nur etwa 5000 betragen!

Und ich habe es geschafft, ihn zu lösen. Es war ein Glücksfall. So habe ich den Code geändert:

  static int encoderRPosPrev = 0; statisch int encoderLPosPrev = 0; int diffL = (encoderLPos - encoderLPosPrev);
int diffR = (encoderRPos - encoderRPosPrev); float SR = distancePerCount * diffR; float SL = distancePerCount * diffL; encoderRPosPrev = encoderRPos; encoderLPosPrev = encoderLPos;  

Ich kann nicht verstehen, was passiert ist. Gibt es etwas über flüchtige Variablen, über das ich hätte Bescheid wissen müssen?

Update: Hier ist der gesamte Code, wenn Sie jemals einen Blick darauf werfen möchten. Und es funktioniert sehr gut, nachdem es auf das geändert wurde, was in der akzeptierten Antwort vorgeschlagen wurde.

Ihre Frage besagt, was die dritte Spalte der Ausgabe ist ... was sind die anderen Spalten? Bitte bearbeiten Sie die Frage und fügen Sie Spaltenüberschriften hinzu
@jwpat7 Ich habe sie absichtlich entfernt, da dies den Leser nur verwirren wird. Aber die Frage wurde von Majenko bereits gut beantwortet.
Es ist schwierig, detaillierte Antworten aus Ihren Ausschnitten zu geben. Können Sie erklären, warum dies nicht zufällig geschieht, sondern jedes Mal, wenn ich den Code ausführe, zu einer bestimmten Zeit? Auch warum gibt es den besonderen Wert? `- Ich könnte das wahrscheinlich tun, wenn ich den ganzen Code sehen würde. In der Zwischenzeit lesen Sie dies: http://www.gammon.com.au/interrupts
@NickGammon Hier geht's: http://paste.ubuntu.com/14085127/
`3683 / .056196868 = 65537` es sieht also so aus, als ob es im falschen Moment erhöht wurde, ja? Sie greifen auf eine Variable zu, die in einem Interrupt möglicherweise mehrmals in diesem Code geändert wird. Daher ist es viel sicherer, eine lokale Kopie zu erhalten, während die Interrupts deaktiviert sind.
@NickGammon Ja, das habe ich letztendlich getan.
Einer antworten:
Majenko
2015-12-17 00:08:37 UTC
view on stackexchange narkive permalink

Sie müssen sich mit kritischen Abschnitten vertraut machen.

Was wahrscheinlich passiert, ist, dass die Variablen während der Berechnungen durch die Interrupt-Routinen geändert werden. Ihr 'Fix' reduziert den Zeitaufwand für die Berechnung mit den flüchtigen Variablen, wodurch die Wahrscheinlichkeit einer Kollision verringert wird.

Kopieren Sie die flüchtigen Variablen in lokale Variablen, für die Interrupts deaktiviert sind kurze Zeit.

  cli (); int l = encoderLpos; int r = encoderRpos; sei ();  

Da das Arduino eine 8-Bit-CPU ist Es sind mehrere Montageanweisungen erforderlich, um mathematische Operationen an 16-Bit-Werten auszuführen. Der Gleitkommawert ist noch schlimmer, wenn viele, viele Anweisungen für eine einfache Addition verwendet werden. Division und Multiplikation verbrauchen wesentlich mehr. Ein Interrupt hat während dieser Anweisungsliste reichlich Gelegenheit, zu feuern. Wenn Sie eine solche Zuordnung vornehmen und dann die neuen lokalen Variablen in Ihren Berechnungen verwenden, werden die Anweisungen zum Umgang mit den flüchtigen Variablen auf ein absolutes Minimum beschränkt. Durch Deaktivieren von Interrupts während der Zuweisung stellen Sie sicher, dass die Variablen niemals geändert werden können, während Sie sie verwenden. Dieser Codeausschnitt wird als kritischer Abschnitt bezeichnet.

Dies könnte einfach der Fall sein. Können Sie sich nur fragen, warum dies nicht zufällig, sondern jedes Mal, wenn ich den Code ausführe, zu einem bestimmten Zeitpunkt geschieht? Auch warum gibt es den besonderen Wert?
Hier ist ein guter Hinweis auf die Cli / Sei. http://www.nongnu.org/avr-libc/user-manual/optimization.html#optim_code_reorder. Mit der Speicherbarriere wird eine flüchtige Deklaration im obigen Code nicht wirklich benötigt. Hier ist ein bisschen Spaß beim Lesen zu diesem Thema. https://www.kernel.org/doc/Documentation/volatile-considered-harmful.txt
@MikaelPatel Schön, aber nicht so relevant für MCUs. In dieser Situation ist Volatile erforderlich, um zu verhindern, dass der Compiler Instanzen optimiert, in denen er denkt, dass er nicht verwendet wird (Wert ändert sich nie). Das cli / sei ist dazu da, die Operation Atomic WRT zum einzigen anderen Thread (Interrupts) zu machen, der ausgeführt wird.
Haben Sie versucht, den Code mit und ohne flüchtig zu kompilieren? Aber mit dem kritischen Abschnitt (cli / sei). Was ich zu diskutieren versuche, ist das Konzept der Speicherbarriere und wie dies einen flüchtigen Zugriff (und eine korrekte Reihenfolge) vom Compiler ermöglicht, wenn Variablen als flüchtig deklariert werden müssen. Den meisten Programmierern wird beigebracht, dass jede Variable, auf die in einem ISR zugegriffen wird, als flüchtig deklariert werden muss, aber diese Geschichte enthält noch viel mehr.
Ich glaube nicht, dass der Compiler eine Vorstellung davon hat, was cli () und sei () tun und wie sich dies auf Dinge wie die Optimierung von Variablen auswirken würde, die nicht optimiert werden sollten. Alles, was sei () und cli () tun, ist das globale Interrupt-fähige Flag in seinem Register zu manipulieren. Sie tun nichts für den Codefluss.
Schließlich werden sie nur einer Assembly-Anweisung zugeordnet: `# define sei () __asm__ __volatile__ (" sei ":::" memory ")`
Es ist das magische Wort "Speicher", das dem Compiler sagt, dass es auch eine Speicherbarriere ist. https://gcc.gnu.org/onlinedocs/gcc/Volatiles.html
Das stoppt die Nachbestellung von Anweisungen. Es hat nichts mit der vollständigen Entfernung des vermuteten toten Codes zu tun.
@MikaelPatel Hier ist ein extremes Beispiel (extrem, damit es die Art von Optimierung erzwingt, die ich nach der Demonstration bin): http://pastebin.com/u9bfiN4R
@Majenko Das war ein extremes Beispiel. Haben Sie sowohl flüchtige als auch cli / sei ausprobiert? Ein Gegenbeispiel ist Cosa / RTT https://github.com/mikaelpatel/Cosa/blob/master/cores/cosa/Cosa/RTT.cpp, es ist nicht flüchtig. Alle Zugriffe werden stattdessen synchronisiert.


Diese Fragen und Antworten wurden automatisch aus der englischen Sprache übersetzt.Der ursprüngliche Inhalt ist auf stackexchange verfügbar. Wir danken ihm für die cc by-sa 3.0-Lizenz, unter der er vertrieben wird.
Loading...