Erstens ist es volatile
nicht Volatile
. Ich beschreibe diese Konzepte auf meiner Seite über Interrupts. Um jedoch zu vermeiden, dass nur ein Link beantwortet wird, wiederhole ich die relevanten Bits.
Was sind "flüchtige" Variablen?
Variablen, die zwischen ISR-Funktionen und normalen Funktionen geteilt werden, sollten deklariert werden volatile
. Dies teilt dem Compiler mit, dass sich solche Variablen jederzeit ändern können. Daher muss der Compiler die Variable immer dann neu laden, wenn Sie darauf verweisen, anstatt sich auf eine Kopie zu verlassen, die sich möglicherweise in einem Prozessorregister befindet.
Zum Beispiel:
volatile boolean flag;
// Interrupt Service Routine (ISR)
void isr ()
{
flag = true;
} // end of isr
void setup ()
{
attachInterrupt (0, isr, CHANGE); // attach interrupt handler
} // end of setup
void loop ()
{
if (flag)
{
// interrupt has occurred
}
} // end of loop
Kritische Abschnitte ... Zugriff auf flüchtige Variablen
Es gibt einige subtile Probleme in Bezug auf Variablen, die von Interrupt Service Routines (ISRs) und dem Hauptcode (dh dem Code, der nicht in einem ISR enthalten ist) gemeinsam genutzt werden.
Da ein ISR jederzeit ausgelöst werden kann, wenn Interrupts aktiviert sind, müssen Sie beim Zugriff auf solche gemeinsam genutzten Variablen vorsichtig sein, da diese möglicherweise in dem Moment aktualisiert werden, in dem Sie auf sie zugreifen.
Erstens ... wann verwenden Sie "flüchtige" Variablen?
Eine Variable sollte nur dann als flüchtig markiert werden, wenn sie sowohl innerhalb als auch außerhalb eines ISR verwendet wird.
- Variablen nur verwendet außerhalb eines ISR sollte nicht flüchtig sein.
- Variablen nur verwendet innerhalb einer ISR sollte nicht flüchtig sein.
- Variablen, die sowohl innerhalb als auch außerhalb eines ISR verwendet werden, sollten volatil sein.
z.B.
volatile int counter;
Das Markieren einer Variablen als flüchtig weist den Compiler an, den Inhalt der Variablen nicht in einem Prozessorregister "zwischenzuspeichern", sondern ihn bei Bedarf immer aus dem Speicher zu lesen. Dies kann die Verarbeitung verlangsamen, weshalb Sie nicht jede Variable flüchtig machen, wenn sie nicht benötigt wird.
Atomarer Zugang
Betrachten Sie diesen Code:
volatile byte count;
ISR (TIMER1_OVF_vect)
{
count = 10;
}
void setup ()
{
}
void loop ()
{
count++;
}
Der Code, für den generiert wird count++
(addiere 1, um zu zählen), lautet wie folgt:
14c: 80 91 00 02 lds r24, 0x0200
150: 8f 5f subi r24, 0xFF <<---- problem if interrupt occurs before this is executed
152: 80 93 00 02 sts 0x0200, r24 <<---- problem if interrupt occurs before this is executed
(Beachten Sie, dass 1 durch Subtrahieren von -1 addiert wird.)
Hier gibt es zwei Gefahrenpunkte, wie markiert. Wenn der Interrupt nach dem lds (Laderegister 24 mit der Variablen count
), aber vor den sts (zurück in die Variable count
speichern) ausgelöst wird, kann die Variable vom ISR (TIMER1_OVF_vect) geändert werden, diese Änderung geht jedoch jetzt verloren , da die Variable im Register wurde stattdessen verwendet.
Wir müssen die gemeinsam genutzte Variable schützen, indem wir Interrupts wie folgt kurz ausschalten:
volatile byte count;
ISR (TIMER1_OVF_vect)
{
count = 10;
} // end of TIMER1_OVF_vect
void setup ()
{
} // end of setup
void loop ()
{
noInterrupts ();
count++;
interrupts ();
} // end of loop
Jetzt erfolgt die Aktualisierung von count
"atomar" ... das heißt, sie kann nicht unterbrochen werden.
Multi-Byte-Variablen
Lassen Sie uns count
eine 2-Byte-Variable erstellen und das andere Problem sehen:
volatile unsigned int count;
ISR (TIMER1_OVF_vect)
{
count++;
} // end of TIMER1_OVF_vect
void setup ()
{
pinMode (13, OUTPUT);
} // end of setup
void loop ()
{
if (count > 20)
digitalWrite (13, HIGH);
} // end of loop
OK, wir ändern uns nicht count
mehr. Gibt es also immer noch ein Problem? Leider schon. Schauen wir uns den generierten Code für die "if" -Anweisung an:
172: 80 91 10 02 lds r24, 0x0210
176: 90 91 11 02 lds r25, 0x0211 <<---- problem if interrupt occurs before this is executed
17a: 45 97 sbiw r24, 0x15
17c: 50 f0 brcs .+20
Stellen Sie sich vor, count
das wäre 0xFFFF und würde gleich wieder auf Null "umlaufen". Wir laden 0xFF in ein Register, aber bevor wir das zweite 0xFF laden, ändert sich die Variable in 0x00. Jetzt denken wir, count
ist 0x00FF, was weder der Wert ist, den es vorher (0xFFFF) noch jetzt (0x0000) hatte.
Also müssen wir den Zugriff auf die gemeinsam genutzte Variable wieder wie folgt "schützen":
void loop ()
{
noInterrupts ();
if (count > 20)
digitalWrite (13, HIGH);
interrupts ();
} // end of loop
Was ist, wenn Sie nicht sicher sind, ob Interrupts aktiviert oder deaktiviert sind?
Hier gibt es ein letztes "Gotcha". Was ist, wenn Interrupts bereits ausgeschaltet sind? Dann ist es eine schlechte Idee, sie danach wieder einzuschalten.
In diesem Fall müssen Sie das Prozessorstatusregister wie folgt speichern:
unsigned int getCount ()
{
unsigned int c;
byte oldSREG = SREG; // remember if interrupts are on or off
noInterrupts (); // turn interrupts off
c = count; // access the shared variable
SREG = oldSREG; // turn interrupts back on, if they were on before
return c; // return copy of shared variable
} // end of getCount
Dieser "sichere" Code speichert den aktuellen Status von Interrupts, schaltet sie aus (sie sind möglicherweise bereits ausgeschaltet), verwandelt die gemeinsam genutzte Variable in eine temporäre Variable, schaltet Interrupts wieder ein - wenn sie bei der Eingabe der Funktion aktiviert waren - und kehrt dann zurück die Kopie der gemeinsam genutzten Variablen.
Zusammenfassung
Code scheint "zu funktionieren", auch wenn Sie die oben genannten Vorsichtsmaßnahmen nicht treffen. Dies liegt daran, dass die Wahrscheinlichkeit, dass der Interrupt genau zum falschen Zeitpunkt auftritt, relativ gering ist (möglicherweise 1 zu 100, abhängig von der Codegröße). Aber früher oder später wird es im falschen Moment passieren und Ihr Code wird entweder abstürzen oder gelegentlich die falschen Ergebnisse zurückgeben.
Achten Sie daher für zuverlässigen Code auf den Schutz des Zugriffs auf gemeinsam genutzte Variablen.