Ich habe ein kleines Projekt mit einem Arduino Uno durchgeführt. 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 volatile 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:
Wenn Sie sich die dritte ansehen Spalte, (SL) Der Wert ist nur für einige Zeit zu hoch. Dies stört alle meine Berechnungen.
Der einzige Hinweis, den ich erhalten kann, wenn ich den Wert von SL nehme, den ich erhalten habe ( 3682), die immer eine Konstante ist, und (encodeLPos - encoderLPosPrev)
zurückrechnen, erhalte ich 65519.66, was nahe am Maximalwert von unsigned int
liegt Das bedeutet, dass (encoderLPos - encoderLPosPrev)
einen Überlauf verursacht, während beide Werte, deren Differenz angenommen wird, nur etwa 5000 betragen!
Und ich habe es geschafft, es zu lösen So habe ich den Code geändert:
static int encoderRPosPrev = 0; static 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, falls Sie jemals einen Blick darauf werfen möchten. Und es funktioniert sehr gut, nachdem es in das geändert wurde, was in der akzeptierten Antwort vorgeschlagen wurde.
Kommentare
- Ihre Frage sagt, was die dritte Spalte von 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 verwirrt. Die Frage wurde jedoch von Majenko bereits gut beantwortet.
- Es ist schwierig, detaillierte Antworten aus Ihren Snippets zu geben.
could you explain why it is not happening randomly but at a specific time every time I run the code? Also why does it give the particular value?
– Ich könnte das wahrscheinlich tun, wenn ich den gesamten Code sehen würde. Lesen Sie in der Zwischenzeit Folgendes: gammon.com.au/interrupts - @NickGammon Hier gehts: 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 möglicherweise in einem Interrupt geändert wird mehrmals in diesem Code, daher wäre es viel sicherer, eine lokale Kopie zu erhalten, während die Interrupts deaktiviert sind.
Antwort
Sie müssen mehr über kritische Abschnitte erfahren.
Was Wahrscheinlich passiert es, dass die Variablen in der Mitte 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 der Arduino eine 8-Bit-CPU ist, sind mehrere Montageanweisungen erforderlich, um mathematische Operationen mit 16-Bit-Werten auszuführen. Der Gleitkommawert ist noch schlimmer, wenn 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.
Kommentare
- Dies könnte der Fall sein. Sie fragen sich nur, warum dies nicht zufällig, sondern jedes Mal, wenn ich den Code ausführe, zu einem bestimmten Zeitpunkt geschieht. Auch warum gibt es der besondere Wert?
- Hier ist ein guter Verweis auf die cli / sei. nongnu.org/avr-libc/user-manual/… . 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. Kernel .org / doc / Documentation / flüchtig-als-schädlich.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 glaubt, dass ‚ nicht verwendet wird (Wert ändert sich nie). Das cli / sei ist dazu da, die Operation atomares WRT zum einzigen anderen Thread (Interrupts) zu machen, der ausgeführt wird.
- Haben Sie versucht, den Code mit und ohne flüchtigen Code 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 ‚ hat viel Konzept darüber, 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.