Bestes Muster für WFI (Wait-for-Interrupt) auf Cortex (ARM) -Mikrocontrollern

18

Ich bin gerade dabei, batteriebetriebene Software mit den EFM Gekko-Controllern (http://energymicro.com/) zu entwickeln, und möchte, dass der Controller schläft, wenn es nichts Sinnvolles dafür gibt. Zu diesem Zweck wird der WFI-Befehl (Wait For Interrupt) verwendet. Dadurch wird der Prozessor in den Ruhezustand versetzt, bis ein Interrupt auftritt.

Wenn der Schlaf durch Speichern von etwas in Anspruch genommen würde, könnte man Lade- / Speicherexklusivoperationen verwenden, um etwas zu tun wie:

  // dont_sleep wird immer dann mit 2 geladen, wenn etwas passiert
  // sollte die Hauptschleife zwingen, mindestens einmal zu zyklieren. Wenn ein Interrupt
  // tritt auf, wodurch es während der folgenden Anweisung auf 2 zurückgesetzt wird,
  // Verhalten wird so sein, als ob der Interrupt danach passiert wäre.

  store_exclusive (load_exclusive (dont_sleep) >> 1);

  while (! nicht schlafen)
  {
    // Wenn zwischen der nächsten Anweisung und store_exclusive ein Interrupt auftritt, schlafe nicht
    load_exclusive (SLEEP_TRIGGER);
    if (! dont_sleep)             
      store_exclusive (SLEEP_TRIGGER);
  }

Wenn ein Interrupt zwischen den Operationen load_exclusive und store_exclusive auftritt, wird store_exclusive übersprungen, wodurch das System die Schleife erneut durchläuft (um festzustellen, ob der Interrupt dont_sleep gesetzt hat). Leider verwendet der Gekko einen WFI-Befehl anstelle einer Schreibadresse, um den Schlafmodus auszulösen. Schreiben von Code wie

  if (! dont_sleep)
    WFI ();

Es besteht die Gefahr, dass ein Interrupt zwischen 'if' und 'wfi' auftritt und 'dont_sleep' gesetzt wird, aber die wfi wird trotzdem ausgeführt. 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 der WFI unterbrechen, und löschen Sie sie sofort nach? Oder gibt es einen besseren Trick?

BEARBEITEN

Ich wundere mich über das Ereignis. Nach der allgemeinen Beschreibung wäre es für die Unterstützung mehrerer Prozessoren gedacht, aber ich habe mich gefragt, ob etwas wie das Folgende funktionieren könnte:

  if (dont_sleep)
    SEV (); / * Löscht das Ereignis-Flag der folgenden WFE, ohne jedoch zu schlafen * /
  WFE ();

Jeder Interrupt, der "don't_sleep" setzt, sollte auch einen SEV-Befehl ausführen. Wenn der Interrupt also nach dem "if" -Test auftritt, löscht die WFE das Ereignis-Flag, geht aber nicht in den Ruhezustand. Klingt das nach einem guten Paradigma?

Superkatze
quelle
1
Der WFI-Befehl versetzt den Core nicht in den Ruhezustand, wenn seine Weckbedingung bei Ausführung des Befehls wahr ist. Wenn beispielsweise bei der Ausführung von WFI ein nicht geklärter IRQ vorliegt, fungiert er als NOP.
Mark
@Mark: Das Problem wäre, dass, wenn ein Interrupt zwischen "if (! Dont_sleep)" und "WFI" ausgeführt wird, die Interruptbedingung nicht mehr ansteht, wenn die WFI ausgeführt wird, der Interrupt jedoch möglicherweise dont_sleep gesetzt hat hat etwas getan, das die Ausführung einer weiteren Iteration durch die Hauptschleife rechtfertigen würde. In meiner Cypress-PSOC-Anwendung würden alle Interrupts, die zu einem längeren Aufwecken führen sollten, den Stack verunsichern, wenn der Hauptzeilencode in den Energiesparmodus versetzt würde. Dies scheint jedoch ziemlich schwierig zu sein, und ich verstehe, dass ARM von solchen Stack-Manipulationen abhält.
Supercat
@supercat Der Interrupt kann gelöscht werden oder nicht, wenn WFI ausgeführt wird. Es liegt an Ihnen und wann / wo Sie den Interrupt löschen möchten. Entfernen Sie die Variable dont_sleep und verwenden Sie einfach einen maskierten Interrupt, um anzuzeigen, wann Sie wach bleiben oder schlafen möchten. Sie können die if-Anweisung einfach alle zusammen loswerden und WFI am Ende der Hauptschleife verlassen. Wenn Sie alle Anfragen bearbeitet haben, löschen Sie den IRQ, damit Sie schlafen können. Wenn Sie wach bleiben müssen, lösen Sie den IRQ aus, der maskiert ist, damit nichts passiert. Wenn WFI jedoch versucht, ihn auszuführen, wird NOP ausgeführt.
Mark
2
@supercat Auf einer grundlegenderen Ebene scheint es so zu sein, als würden Sie versuchen, ein Interrupt-gesteuertes Design mit einem 'Big-Main-Loop'-Design zu mischen, das normalerweise nicht zeitkritisch ist, häufig auf Abfragen basiert und nur minimale Interrupts aufweist. Das Mischen kann ziemlich schnell ziemlich hässlich werden. Wenn möglich, wählen Sie das eine oder das andere Design-Paradigma aus. Denken Sie daran, dass Sie bei modernen Interrupt-Controllern im Grunde genommen ein vorbeugendes Multitasking zwischen Interrupts und der Summe der Task-Warteschlangen (Service eines Interrupts, dann der nächsthöheren Priorität usw.) erhalten. Nutzen Sie das zu Ihrem Vorteil.
Mark
@Mark: Ich habe ein System entwickelt, das einen PIC 18x in einer batteriebetriebenen Anwendung ziemlich gut verwendet. Aufgrund von Stapelbeschränkungen konnte es innerhalb eines Interrupts nicht zu viel verarbeiten, sodass die überwiegende Mehrheit der Daten in der Hauptschleife nach Belieben verarbeitet wird. Es funktioniert meistens ziemlich gut, obwohl es ein paar Stellen gibt, an denen die Dinge wegen langer Betriebszeiten für ein oder zwei Sekunden blockiert werden. Wenn ich zu einem ARM migriere, verwende ich möglicherweise ein einfaches RTOS, um die Aufteilung der lang laufenden Vorgänge zu vereinfachen. Ich bin mir jedoch nicht sicher, ob ich präventives oder kooperatives Multitasking verwenden soll.
Supercat

Antworten:

3

Ich habe die dont_sleepSache nicht vollständig verstanden , aber Sie könnten versuchen, die "Hauptarbeit" im PendSV-Handler zu erledigen, der auf die niedrigste Priorität eingestellt ist. Dann planen Sie einfach jedes Mal einen PendSV von anderen Handlern ein, wenn Sie etwas erledigen müssen. Sehen Sie hier, wie es geht (es ist für M1, aber M3 ist nicht zu unterschiedlich).

Eine weitere Möglichkeit, die Sie (möglicherweise zusammen mit dem vorherigen Ansatz) nutzen können, ist die Funktion "Sleep-on-exit". Wenn Sie dies aktivieren, wird der Prozessor nach dem Beenden des letzten ISR-Handlers in den Ruhezustand versetzt, ohne dass Sie WFI anrufen müssen. Sehen Sie einige Beispiele hier .

Igor Skochinsky
quelle
5
Für den WFI-Befehl müssen keine Interrupts aktiviert sein, um den Prozessor zu aktivieren. Die F- und I-Bits in CPSR werden ignoriert.
Mark
1
@Mark Ich muss dieses Bit in den Dokumenten verpasst haben. Haben Sie einige Links / Verweise dazu? Was passiert mit dem Interrupt-Signal, das den Core aufgeweckt hat? Bleibt es anstehend, bis der Interrupt wieder aktiviert wird?
Igor Skochinsky
Das ASM-Referenzhandbuch finden Sie hier: infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0489c/… Weitere Informationen zum cortex-m3 finden Sie hier: infocenter.arm.com/help/ index.jsp? topic = / com.arm.doc.dui0552a /… kurz gesagt, wenn ein maskierter Interrupt ansteht, wacht der Core auf und setzt den Betrieb nach dem WFI-Befehl fort. Wenn Sie versuchen, eine andere WFI auszugeben, ohne diese anstehende Unterbrechung zu löschen, fungiert die WFI als NOP (der Kern würde nicht schlafen, da die Weckbedingung für WFI wahr ist).
Mark
@Mark: Eine Sache, über die ich nachgedacht habe, wäre, dass ein Interrupt-Handler, der dont_sleep setzt, auch eine SEV-Anweisung ("Set Event") ausführt und dann WFE ("Wait For Event") anstelle von WFI verwendet. Die Gekko-Beispiele scheinen WFI zu verwenden, aber ich würde denken, dass WFE auch funktionieren könnte. Irgendwelche Gedanken?
Supercat
10

Legen Sie es in einen kritischen Bereich. ISRs werden nicht ausgeführt, sodass Sie nicht das Risiko eingehen, dass sich dont_sleep vor WFI ändert. Sie wecken jedoch den Prozessor und die ISRs werden ausgeführt, sobald der kritische Abschnitt endet.

uint8 interruptStatus;
interruptStatus = EnterCriticalSection();
if (!dont_sleep)
  WFI();
ExitCriticalSection(interruptStatus);

Ihre Entwicklungsumgebung verfügt wahrscheinlich über wichtige Abschnittsfunktionen, die jedoch ungefähr so ​​aussehen:

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. */
Kenzi
quelle
2
Seltsamerweise verwenden einige ARM-Bibliotheken eine Implementierung für kritische Abschnitte, die einen globalen Zähler verwendet, anstatt den Status lokal beizubehalten. Ich finde das irrsinnig, da der Counter-Ansatz komplizierter ist und nur funktioniert, wenn der gesamte Code systemweit denselben Counter verwendet.
Supercat
1
Werden Interrupts nicht deaktiviert, bis wir den kritischen Bereich verlassen? Wenn ja, lässt WFI die CPU nicht auf unbestimmte Zeit warten?
Corneliu Zuzu
1
Die Antwort von @Kenzi Shrimp verweist auf einen Linux-Diskussionsthread, der meine vorherige Frage beantwortet. Ich habe seine und deine Antwort bearbeitet, um das zu klären.
Corneliu Zuzu
@CorneliuZuzu Es ist keine gute Idee, die Antwort eines anderen zu bearbeiten, um Ihre eigene Diskussion einzuschalten. Das Hinzufügen des Zitats zur Verbesserung einer "Nur-Link" -Antwort ist eine andere Sache. Wenn Sie eine echte Frage zu Ihrem Won haben, stellen Sie diese möglicherweise als Frage und verlinken Sie auf diese Frage.
Sean Houlihane
1
@ SeanHoulihane Ich habe nichts von ihrer Antwort entkräftet oder entfernt. Eine kurze Erläuterung, warum dies funktioniert, ist keine separate Diskussion. Ehrlich gesagt bin ich nicht der Meinung, dass diese Antwort ohne die Klarstellung des WFI eine Gegenstimme verdient, aber sie verdient sie am meisten.
Corneliu Zuzu
7

Ihre Idee ist in Ordnung, genau das implementiert Linux. Sehen Sie hier .

Nützliches Zitat aus dem oben erwähnten Diskussionsthread, um zu verdeutlichen, warum WFI auch bei deaktivierten Interrupts funktioniert:

Wenn Sie bis zum nächsten Interrupt inaktiv bleiben möchten, 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 einen Wettlauf zwischen der Vorbereitung des Einschlafens und dem tatsächlichen Einschlafen haben, was zu verlorenen Weckereignissen führt.

Aus diesem Grund werden alle mir bekannten ARM-CPUs aktiviert, auch wenn sie in der Kern-CPU maskiert sind (CPSR I-Bit).

Alles andere und Sie sollten den Leerlaufmodus vergessen.

Garnele
quelle
1
Sie beziehen sich auf das Deaktivieren von Interrupts zum Zeitpunkt der WFI- oder WFE-Anweisung? Sehen Sie einen sinnvollen Unterschied zwischen der Verwendung von WFI oder WFE für diesen Zweck?
Supercat
1
@supercat: Ich würde definitiv WFI verwenden. WFE IMO ist hauptsächlich für Synchronisationshinweise zwischen Kernen in Multicore-Systemen gedacht (z. B. WFE ausführen, wenn Spinlock fehlschlägt und SEV nach dem Verlassen von Spinlock ausgegeben wird). Außerdem berücksichtigt WFE das Unterbrechungsmaskierungsflag, so dass es hier nicht so nützlich ist wie WFI. Dieses Muster funktioniert wirklich gut unter Linux.
Shrimp
2

Vorausgesetzt, dass:

  1. Der Hauptthread führt Hintergrundaufgaben aus
  2. Die Interrupts führen nur Aufgaben mit hoher Priorität und keine Hintergrundaufgaben aus
  3. Der Haupt-Thread kann jederzeit unterbrochen werden (er maskiert normalerweise keine Interrupts)

Dann besteht die Lösung darin, PRIMASK zu verwenden, um Interrupts zwischen der Flagvalidierung und WFI zu blockieren:

mask_interrupts();
if (!dont_sleep)
    wfi();
unmask_interrupts();
hdante
quelle
0

Was ist mit Sleep on Exit-Modus? Dieser geht automatisch in den Ruhezustand, wenn ein IRQ-Handler beendet wird, sodass nach der Konfiguration kein "normaler Modus" mehr ausgeführt wird. Ein IRQ passiert, er wacht auf und führt den Handler aus und geht wieder in den Schlaf. Kein WFI benötigt.

billt
quelle
2
Wie sollte man am besten damit umgehen, dass die Art des Schlafes, in den der Prozessor fallen sollte, aufgrund von Ereignissen, die während eines Interrupts auftreten, variieren kann? Beispielsweise kann ein Pin-Change-Ereignis darauf hinweisen, dass möglicherweise serielle Daten anstehen, und der Prozessor sollte daher den primären Taktoszillator laufen lassen, während er auf die Daten wartet. Wenn die Hauptschleife das Ereignis-Flag löscht, prüft, was vor sich geht, und den Prozessor mit einer WFI in einen geeigneten Schlafmodus versetzt, dann würde jeder Interrupt, der sich auf den geeigneten Modus
ausgewirkt
... und den Schlaf abbrechen. Wenn Sie eine Hauptschleifensteuerung haben, ist der Ruhemodus sauberer, als dass Sie sich in jedem Interrupt darum kümmern müssen. Das "Drehen" dieses Teils der Hauptschleife bei jedem Interrupt ist möglicherweise nicht optimal, sollte aber nicht zu schlecht sein, insbesondere, wenn alle Interrupts, die das Schlafverhalten beeinflussen könnten, eine Markierung treffen.
Supercat