Ich beschäftige mich mit der Entwicklung batteriebetriebener Software mit den EFM Gekko-Controllern (http://energymicro.com/) und möchte, dass der Controller schläft, wenn er nichts Sinnvolles zu tun hat. Zu diesem Zweck wird der Befehl WFI (Wait For Interrupt) verwendet; Es versetzt den Prozessor in den Ruhezustand, bis ein Interrupt auftritt.
Wenn der Schlaf aktiviert würde, indem etwas irgendwo gespeichert wird, könnte man ausschließliche Lade-/Speicher-exklusive Operationen verwenden, um Folgendes zu tun:
// dont_sleep wird jedes Mal mit 2 geladen, wenn etwas passiert // sollte die Hauptschleife zwingen, mindestens einmal zu durchlaufen. Wenn ein Interrupt // tritt auf, wodurch es während der folgenden Anweisung auf 2 zurückgesetzt wird, // Das Verhalten ist so, als ob der Interrupt danach passiert wäre. store_exclusive(load_exclusive(dont_sleep) >> 1); while(!dont_sleep) { // Falls Interrupt zwischen nächster Anweisung und store_exclusive auftritt, nicht schlafen load_exclusive(SLEEP_TRIGGER); wenn (!dont_sleep) store_exclusive(SLEEP_TRIGGER); }
Wenn zwischen den Operationen load_exclusive und store_exclusive ein Interrupt auftritt, würde die Wirkung store_exclusive überspringen, wodurch das System veranlasst wird, die Schleife noch einmal zu durchlaufen (um zu sehen, ob der Interrupt dont_sleep gesetzt hat). Leider verwendet der Gekko einen WFI-Befehl anstelle einer Schreibadresse, um den Schlafmodus auszulösen. Code schreiben wie
wenn (!dont_sleep) WFI();
würde das Risiko eingehen, dass ein Interrupt zwischen dem 'if' und dem 'wfi' auftreten und dont_sleep setzen könnte, aber das wfi würde trotzdem weitermachen und ausführen. Was ist das beste Muster, um das zu verhindern? Setzen Sie PRIMASK auf 1, um zu verhindern, dass Interrupts den Prozessor kurz vor der Ausführung des WFI unterbrechen, und löschen Sie es sofort danach? Oder gibt es einen besseren Trick?
BEARBEITEN
Ich wundere mich über das Event-Bit. Der allgemeinen Beschreibung nach wäre es für die Unterstützung mehrerer Prozessoren gedacht, habe mich aber gefragt, ob so etwas wie das Folgende funktionieren könnte:
wenn (dont_sleep) SEV(); /* Wird das folgende WFE-Ereignis-Flag löschen, aber nicht schlafen */ WFE();
Jeder Interrupt, der don't_sleep setzt, sollte auch eine SEV-Anweisung ausführen, wenn also der Interrupt nach dem "if"-Test auftritt, würde das WFE das Ereignis-Flag löschen, aber nicht schlafen gehen. Klingt das nach einem guten Paradigma?
Ich habe die Sache nicht ganz verstanden dont_sleep
, aber eine Sache, die Sie versuchen könnten, ist, die "Hauptarbeit" im PendSV-Handler zu erledigen, der auf die niedrigste Priorität eingestellt ist. Planen Sie dann einfach jedes Mal, wenn Sie etwas erledigen müssen, einen PendSV von anderen Handlern ein. Sehen Sie hier , wie es geht (es ist für M1, aber M3 ist nicht allzu anders).
Eine andere Sache, die Sie verwenden könnten (vielleicht zusammen mit dem vorherigen Ansatz), ist die Sleep-on-Exit-Funktion. Wenn Sie es aktivieren, geht der Prozessor nach dem Verlassen des letzten ISR-Handlers in den Ruhezustand, ohne dass Sie WFI aufrufen müssen. Sehen Sie hier einige Beispiele .
Legen Sie es in einen kritischen Abschnitt. ISRs werden nicht ausgeführt, sodass Sie nicht das Risiko eingehen, dass sich dont_sleep vor WFI ändert, aber sie wecken den Prozessor trotzdem auf und die ISRs werden ausgeführt, sobald der kritische Abschnitt endet.
uint8 interruptStatus;
interruptStatus = EnterCriticalSection();
if (!dont_sleep)
WFI();
ExitCriticalSection(interruptStatus);
Ihre Entwicklungsumgebung hat wahrscheinlich kritische Abschnittsfunktionen, aber es ist ungefähr so:
EnterCriticalSection ist:
MRS r0, PRIMASK /* Save interrupt state. */
CPSID i /* Turn off interrupts. */
BX lr /* Return. */
ExitCriticalSection ist:
MSR PRIMASK, r0 /* Restore interrupt states. */
BX lr /* Return. */
Ihre Idee ist in Ordnung, das ist genau das, was Linux implementiert. Siehe hier .
Nützliches Zitat aus dem oben genannten Diskussionsthread, um zu verdeutlichen, warum WFI auch mit deaktivierten Interrupts funktioniert:
Wenn Sie beabsichtigen, bis zum nächsten Interrupt im Leerlauf zu bleiben, müssen Sie einige Vorbereitungen treffen. Während dieser Vorbereitung kann ein Interrupt aktiv werden. Eine solche Unterbrechung kann ein Weckereignis sein, nach dem Sie suchen.
Egal wie gut Ihr Code ist, wenn Sie Interrupts nicht deaktivieren, werden Sie immer ein Wettrennen zwischen der Vorbereitung auf das Einschlafen und dem tatsächlichen Einschlafen haben, was zu verlorenen Weckereignissen führt.
Aus diesem Grund werden alle mir bekannten ARM-CPUs aufwachen, auch wenn sie an der Kern-CPU (CPSR I-Bit) maskiert sind.
Alles andere und Sie sollten den Leerlaufmodus vergessen.
Vorausgesetzt, dass:
Dann besteht die Lösung darin, PRIMASK zu verwenden, um Interrupts zwischen der Flag-Validierung und WFI zu blockieren:
mask_interrupts();
if (!dont_sleep)
wfi();
unmask_interrupts();
Was ist mit dem Sleep on Exit-Modus? Dies geht jedes Mal automatisch in den Ruhezustand, wenn ein IRQ-Handler beendet wird, sodass nach der Konfiguration kein "normaler Modus" mehr ausgeführt wird. Ein IRQ tritt auf, er wacht auf und führt den Handler aus und geht wieder in den Ruhezustand. Kein WFI erforderlich.
Markieren
Superkatze
Markieren
Markieren
Superkatze
Superkatze
Superkatze