Bei einem Mikrocontroller, auf dem der folgende Code ausgeführt wird:
volatile bool has_flag = false;
void interrupt(void) //called when an interrupt is received
{
clear_interrupt_flag(); //clear interrupt flag
has_flag = true; //signal that we have an interrupt to process
}
int main()
{
while(1)
{
if(has_flag) //if we had an interrupt
{
has_flag = false; //clear the interrupt flag
process(); //process the interrupt
}
else
sleep(); //place the micro to sleep
}
}
Angenommen, die if(has_flag)
Bedingung wird als falsch ausgewertet und wir sind dabei, die Schlafanweisung auszuführen. Kurz bevor wir den Schlafbefehl ausführen, erhalten wir eine Unterbrechung. Nachdem wir den Interrupt verlassen haben, führen wir die Schlafanweisung aus.
Diese Ausführungssequenz ist nicht wünschenswert, weil:
- Der Mikrocontroller schlief ein, anstatt aufzuwachen und anzurufen
process()
. - Der Mikrocontroller wird möglicherweise nie aktiviert, wenn danach keine Unterbrechung empfangen wird.
- Der Anruf an
process()
wird auf den nächsten Interrupt verschoben.
Wie kann der Code geschrieben werden, um das Auftreten dieser Rennbedingung zu verhindern?
Bearbeiten
Einige Mikrocontroller, wie z. B. das ATMega, verfügen über ein Sleep-Aktivierungsbit, das das Auftreten dieses Zustands verhindert (danke Kvegaoro, dass Sie darauf hingewiesen haben). JRoberts bietet eine Beispielimplementierung, die dieses Verhalten veranschaulicht.
Andere Mikros, wie bei PIC18s, haben dieses Bit nicht und das Problem tritt immer noch auf. Diese Mikros sind jedoch so konzipiert, dass Interrupts den Kern immer noch aufwecken können, unabhängig davon, ob das globale Interrupt-Aktivierungsbit gesetzt ist (danke Supercat, dass Sie darauf hingewiesen haben). Für solche Architekturen besteht die Lösung darin, globale Interrupts unmittelbar vor dem Einschlafen zu deaktivieren. Wenn ein Interrupt unmittelbar vor dem Ausführen des Sleep-Befehls ausgelöst wird, wird der Interrupt-Handler nicht ausgeführt, der Kern wird aktiviert, und sobald globale Interrupts wieder aktiviert werden, wird der Interrupt-Handler ausgeführt. Im Pseudocode würde die Implementierung folgendermaßen aussehen:
int main()
{
while(1)
{
//clear global interrupt enable bit.
//if the flag tested below is not set, then we enter
//sleep with the global interrupt bit cleared, which is
//the intended behavior.
disable_global_interrupts();
if(has_flag) //if we had an interrupt
{
has_flag = false; //clear the interrupt flag
enable_global_interrupts(); //set global interrupt enable bit.
process(); //process the interrupt
}
else
sleep(); //place the micro to sleep
}
}
interrupt_flag
als einint
und erhöhe es jedes Mal dort Unterbrechung. Dann wechseln Sieif(has_flag)
zuwhile (interrupts_count)
und schlafen Sie dann. Der Interrupt kann jedoch auftreten, nachdem Sie die while-Schleife verlassen haben. Wenn dies ein Problem ist, führt die Verarbeitung dann den Interrupt selbst durch?Antworten:
Für diesen Fall gibt es normalerweise eine Art Hardware-Unterstützung. Beispielsweise verzögert die
sei
Anweisung der AVRs zum Aktivieren von Interrupts die Aktivierung, bis die folgende Anweisung abgeschlossen ist. Damit kann man machen:Der Interrupt, der im Beispiel übersehen worden wäre, würde in diesem Fall angehalten, bis der Prozessor seine Schlafsequenz abgeschlossen hat.
quelle
Auf vielen Mikrocontrollern gibt es nicht nur die Möglichkeit, bestimmte Interrupt-Ursachen (normalerweise innerhalb eines Interrupt-Controller-Moduls) zu aktivieren oder zu deaktivieren, sondern auch ein Master-Flag im CPU-Kern, das bestimmt, ob Interrupt-Anforderungen akzeptiert werden. Viele Mikrocontroller verlassen den Ruhemodus, wenn eine Interrupt-Anforderung den Kern erreicht, unabhängig davon, ob der Kern bereit ist, sie tatsächlich zu akzeptieren.
Bei einem solchen Entwurf besteht ein einfacher Ansatz zum Erreichen eines zuverlässigen Schlafverhaltens darin, dass die Hauptschleifenprüfung ein Flag löscht und dann prüft, ob sie weiß, warum der Prozessor wach sein sollte. Jeder Interrupt, der während dieser Zeit auftritt und einen dieser Gründe beeinflussen kann, sollte das Flag setzen. Wenn die Hauptschleife keinen Grund gefunden hat, wach zu bleiben, und wenn das Flag nicht gesetzt ist, sollte die Hauptschleife Interrupts deaktivieren und das Flag erneut überprüfen [möglicherweise nach ein paar NOP-Anweisungen, wenn es möglich ist, dass ein Interrupt ansteht während eines Deaktivierungs-Interrupt-Befehls kann verarbeitet werden, nachdem der dem folgenden Befehl zugeordnete Operandenabruf bereits ausgeführt wurde]. Wenn die Flagge immer noch nicht gesetzt ist, schlafen Sie ein.
In diesem Szenario setzt ein Interrupt, der auftritt, bevor die Hauptschleife Interrupts deaktiviert, das Flag vor dem letzten Test. Ein Interrupt, der zu spät ansteht, um vor dem Sleep-Befehl gewartet zu werden, verhindert, dass der Prozessor in den Ruhezustand wechselt. Beide Situationen sind in Ordnung.
Sleep-on-Exit ist manchmal ein gutes Modell, aber nicht alle Anwendungen "passen" wirklich dazu. Beispielsweise kann ein Gerät mit einem energieeffizienten LCD am einfachsten mit Code programmiert werden, der wie folgt aussieht:
Wenn keine Tasten gedrückt werden und nichts anderes passiert, gibt es keinen Grund, warum das System während der Ausführung der
get_key
Methode nicht in den Ruhezustand wechseln sollte. Während es möglich sein kann, dass Schlüssel einen Interrupt auslösen und alle Interaktionen zwischen Benutzeroberflächen über die Zustandsmaschine verwalten, ist Code wie der oben beschriebene häufig die logischste Methode, um für kleine Mikrocontroller typische hochmodale Benutzeroberflächenflüsse zu verarbeiten.quelle
Programmieren Sie das Mikro so, dass es bei Unterbrechung aufwacht.
Die spezifischen Details variieren je nach verwendetem Mikro.
Ändern Sie dann die main () - Routine:
quelle