Mikrocontroller Schlaf Race Zustand

11

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
    }
}
TRISAbits
quelle
Ist das eine praktische oder theoretische Frage?
AndrejaKo
Theoretisch verwenden Sie einen Timer, der Sie alle ms (akzeptablen Wert eingeben) einmal aufweckt und dann wieder in den Ruhezustand wechselt, wenn nichts getan werden muss.
Grady Player
1
Ich tun würde , interrupt_flagals ein intund erhöhe es jedes Mal dort Unterbrechung. Dann wechseln Sie if(has_flag)zu while (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?
Angelatlarge
1
Nun, es hängt davon ab, welches Mikro Sie verwenden. Wenn es ein ATmega328 wäre, könnten Sie möglicherweise den Schlafmodus für den Interrupt deaktivieren. Wenn also die von Ihnen beschriebene Race-Bedingung eintritt, würde die Sleep-Funktion überschrieben, eine Schleife zurückgesetzt und Sie würden verarbeiten der Interrupt mit einer kleinen Latenz. Aber auch die Verwendung eines Timers zum Aufwachen in einem Intervall, das Ihrer maximalen Latenz entspricht oder
darunter liegt,
1
@TRISAbits: Auf dem PIC 18x funktioniert der in meiner Antwort beschriebene Ansatz einwandfrei (es ist mein normales Design, wenn ich diesen Teil verwende).
Supercat

Antworten:

9

Für diesen Fall gibt es normalerweise eine Art Hardware-Unterstützung. Beispielsweise verzögert die seiAnweisung der AVRs zum Aktivieren von Interrupts die Aktivierung, bis die folgende Anweisung abgeschlossen ist. Damit kann man machen:

forever,
   interrupts off;
   if has_flag,
      interrupts on;
      process interrupt;
   else,
      interrupts-on-and-sleep;    # won't be interrupted
   end
end

Der Interrupt, der im Beispiel übersehen worden wäre, würde in diesem Fall angehalten, bis der Prozessor seine Schlafsequenz abgeschlossen hat.

JRobert
quelle
Gute Antwort! Der von Ihnen bereitgestellte Algorithmus funktioniert auf einem AVR tatsächlich sehr gut. Danke für den Vorschlag.
TRISAbits
3

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:

void select_view_user(int default_user)
{
  int current_user;
  int ch;
  current_user = default_user;
  do
  {
    lcd_printf(0, "User %d");
    lcd_printf(1, ...whatever... );
    get_key();
    if (key_hit(KEY_UP)) {current_user = (current_user + 1) % MAX_USERS};
    if (key_hit(KEY_DOWN)) {current_user = (current_user + MAX_USERS-1) % MAX_USERS};
    if (key_hit(KEY_ENTER)) view_user(current_user);
  } while(!key_hit(KEY_EXIT | KEY_TIMEOUT));
}

Wenn keine Tasten gedrückt werden und nichts anderes passiert, gibt es keinen Grund, warum das System während der Ausführung der get_keyMethode 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.

Superkatze
quelle
Danke supercat für die tolle Antwort. Das Deaktivieren von Interrupts und das anschließende Einschlafen ist eine fantastische Lösung, vorausgesetzt, der Kern erwacht aus allen Interruptquellen, unabhängig davon, ob das globale Interrupt-Bit gesetzt / gelöscht ist. Ich habe mir das PIC18-Interrupt-Hardware-Schema angesehen, und diese Lösung würde funktionieren.
TRISAbits
1

Programmieren Sie das Mikro so, dass es bei Unterbrechung aufwacht.

Die spezifischen Details variieren je nach verwendetem Mikro.

Ändern Sie dann die main () - Routine:

int main()
{
    while(1)
    {
        sleep();
        process(); //process the interrupt
    }
}
jwygralak67
quelle
1
In der Frage wird eine Wake-on-Interrupt-Architektur angenommen. Ich glaube nicht, dass Ihre Antwort die Frage / das Problem löst.
Angelatlarge
@angelatlarge Point akzeptiert. Ich habe ein Codebeispiel hinzugefügt, das meiner Meinung nach hilfreich ist.
jwygralak67
@ jwygralak67: Danke für den Vorschlag, aber der von Ihnen bereitgestellte Code verschiebt das Problem lediglich in die process () - Routine, die nun testen muss, ob der Interrupt aufgetreten ist, bevor der process () - Body ausgeführt wird.
TRISAbits
2
Wenn die Unterbrechung nicht aufgetreten ist, warum sind wir dann wach?
JRobert
1
@JRobert: Wir könnten von einem vorherigen Interrupt wach sein, die process () - Routine abschließen und wenn wir den if (has_flag) -Test beenden und kurz vor dem Sleep, erhalten wir einen weiteren Interrupt, der das Problem verursacht, das ich im beschrieben habe Frage.
TRISAbits