Meilleur modèle pour WFI (attente d'interruption) sur les microcontrôleurs Cortex (ARM)

18

Je cherche à développer un logiciel alimenté par batterie en utilisant les contrôleurs EFM Gekko (http://energymicro.com/) et je voudrais que le contrôleur soit en veille chaque fois qu'il n'y a rien d'utile à faire. L'instruction WFI (Wait For Interrupt) est utilisée à cet effet; il mettra le processeur en veille jusqu'à ce qu'une interruption se produise.

Si le sommeil était activé en stockant quelque chose quelque part, on pourrait utiliser des opérations de chargement exclusif / magasin exclusif pour faire quelque chose comme:

  // dont_sleep est chargé de 2 chaque fois que quelque chose arrive
  // devrait forcer la boucle principale à tourner au moins une fois. Si une interruption
  // se produit qui entraîne sa réinitialisation à 2 lors de l'instruction suivante,
  // le comportement sera comme si l'interruption s'était produite après.

  store_exclusive (load_exclusive (dont_sleep) >> 1);

  while (! dont_sleep)
  {
    // Si une interruption se produit entre la prochaine instruction et store_exclusive, ne dors pas
    load_exclusive (SLEEP_TRIGGER);
    si (! dont_sleep)             
      store_exclusive (SLEEP_TRIGGER);
  }

Si une interruption devait se produire entre les opérations load_exclusive et store_exclusive, l'effet serait d'ignorer store_exclusive, ce qui obligerait le système à parcourir la boucle une fois de plus (pour voir si l'interruption avait défini dont_sleep). Malheureusement, le Gekko utilise une instruction WFI plutôt qu'une adresse d'écriture pour déclencher le mode veille; écrire du code comme

  si (! dont_sleep)
    WFI ();

courrait le risque qu'une interruption puisse se produire entre le «si» et le «wfi» et définirait dont_sleep, mais le wfi irait de l'avant et s'exécuterait de toute façon. Quel est le meilleur schéma pour éviter cela? Réglez PRIMASK sur 1 pour empêcher les interruptions d'interrompre le processeur juste avant d'exécuter le WFI, et effacez-le immédiatement après? Ou y a-t-il une meilleure astuce?

ÉDITER

Je m'interroge sur le bit Event. D'après la description générale, il aimerait qu'il soit destiné à la prise en charge multi-processeur, mais se demandait si quelque chose comme ce qui suit pouvait fonctionner:

  if (dont_sleep)
    SEV (); / * Rendra le drapeau d'événement WFE clair mais ne dormira pas * /
  WFE ();

Chaque interruption qui définit don't_sleep doit également exécuter une instruction SEV, donc si l'interruption se produit après le test "if", la WFE effacera l'indicateur d'événement mais ne se mettra pas en veille. Cela ressemble-t-il à un bon paradigme?

supercat
la source
1
L'instruction WFI ne met pas le cœur en veille si sa condition de réveil est vraie lorsque l'instruction est exécutée. Par exemple, s'il y a un IRQ non clarifié lorsque WFI est exécuté, il agit comme un NOP.
Mark
@Mark: Le problème serait que si une interruption est prise entre le "if (! Dont_sleep)" et le "WFI", la condition d'interruption ne serait plus en attente lorsque le WFI s'exécute, mais l'interruption pourrait avoir défini dont_sleep car il a fait quelque chose qui justifierait que la boucle principale exécute une autre itération. Sur une application Cypress PSOC, toutes les interruptions qui devraient provoquer un réveil prolongé entraîneraient la pile si le code de la ligne principale était sur le point de dormir, mais cela semble assez épineux et je comprends que ARM décourage ces manipulations de pile.
supercat
@supercat L'interruption peut ou non être supprimée lors de l'exécution de WFI. C'est à vous et quand / où vous choisissez d'effacer l'interruption. Débarrassez-vous de la variable dont_sleep et utilisez simplement une interruption masquée pour indiquer quand vous voulez rester éveillé ou dormir. Vous pouvez simplement vous débarrasser de l'instruction if tous ensemble et laisser WFI à la fin de la boucle principale. Si vous avez répondu à toutes les demandes, désactivez l'IRQ pour pouvoir dormir. Si vous devez rester éveillé, déclenchez l'IRQ, il est masqué pour que rien ne se passe, mais lorsque WFI essaie de l'exécuter, cela ne fonctionnera pas.
Mark
2
@supercat À un niveau plus fondamental, il semble que vous essayez de mélanger une conception basée sur les interruptions avec une conception de `` grande boucle principale '', qui n'est généralement pas critique en termes de temps, souvent basée sur l'interrogation et a un minimum d'interruptions. Le mélange de ceux-ci peut devenir plutôt moche plutôt rapide. Si possible, choisissez un paradigme de conception ou l'autre à utiliser. N'oubliez pas qu'avec les contrôleurs d'interruption modernes, vous obtenez essentiellement un multitâche préemptif entre les interruptions et ce qui équivaut à des files d'attente de tâches (réparer une interruption, puis la priorité supérieure suivante, etc.). Utilisez cela à votre avantage.
Mark
@Mark: J'ai développé un système qui utilisait assez bien un PIC 18x dans une application alimentée par batterie; en raison des limites de la pile, il ne pouvait pas gérer trop de choses au cours d'une interruption, de sorte que la grande majorité des choses est gérée dans la boucle principale de manière aussi pratique. Cela fonctionne généralement assez bien, bien qu'il y ait quelques endroits où les choses sont bloquées pendant une seconde ou deux en raison d'opérations de longue durée. Si je migre vers un ARM, je peux utiliser un simple RTOS pour faciliter la répartition des opérations de longue durée, mais je ne sais pas s'il faut utiliser le multitâche préemptif ou coopératif.
supercat

Réponses:

3

Je n'ai pas bien compris la dont_sleepchose, mais une chose que vous pourriez essayer est de faire le "travail principal" dans le gestionnaire PendSV, défini sur la priorité la plus basse. Ensuite, planifiez simplement un PendSV à partir d'autres gestionnaires chaque fois que vous avez besoin de quelque chose. Voir ici comment faire (c'est pour M1 mais M3 n'est pas trop différent).

Une autre chose que vous pourriez utiliser (peut-être avec l'approche précédente) est la fonction Veille à la sortie. Si vous l'activez, le processeur se mettra en veille après avoir quitté le dernier gestionnaire ISR, sans que vous ayez à appeler WFI. Voir quelques exemples ici .

Igor Skochinsky
la source
5
l'instruction WFI n'exige pas que les interruptions soient activées pour réveiller le processeur, les bits F et I dans CPSR sont ignorés.
Mark
1
@Mark J'ai dû manquer ce morceau dans la documentation, avez-vous des liens / pointeurs à ce sujet? Qu'arrive-t-il au signal d'interruption qui a réveillé le cœur? Reste-t-il en attente jusqu'à ce que l'interruption soit à nouveau activée?
Igor Skochinsky
Le manuel de référence ASM est ici: infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0489c/… des informations plus spécifiques pour le cortex-m3 sont ici: infocenter.arm.com/help/ index.jsp? topic = / com.arm.doc.dui0552a /… en bref lorsqu'une interruption masquée devient en attente, le cœur se réveille et continue de fonctionner après l'instruction WFI. Si vous avez essayé d'émettre un autre WFI sans effacer cette interruption en attente, le WFI agirait comme un NOP (le noyau ne dormirait pas car la condition de réveil pour WFI est vraie).
Mark
@Mark: Une chose que j'envisageais serait d'avoir tout gestionnaire d'interruption qui définit dont_sleep exécute également une instruction SEV ("Set Event"), puis utilise WFE ("Wait For Event") plutôt que WFI. Les exemples de Gekko semblent utiliser WFI, mais je pense que WFE pourrait également fonctionner. Des pensées?
supercat
10

Mettez-le dans une section critique. Les ISR ne s'exécuteront pas, vous ne courez donc pas le risque de changer dont_sleep avant WFI, mais ils réveilleront toujours le processeur et les ISR s'exécuteront dès la fin de la section critique.

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

Votre environnement de développement a probablement des fonctions de section critiques, mais c'est à peu près comme ceci:

EnterCriticalSection est:

MRS r0, PRIMASK /* Save interrupt state. */
CPSID i /* Turn off interrupts. */
BX lr /* Return. */

ExitCriticalSection est:

MSR PRIMASK, r0 /* Restore interrupt states. */
BX lr /* Return. */
Kenzi
la source
2
Curieusement, un certain nombre de bibliothèques ARM utilisent une implémentation de section critique qui utilise un compteur global au lieu de conserver le statut localement. Je trouve cela ahurissant car l'approche du compteur est plus compliquée et ne fonctionnera que si tout le code à l'échelle du système utilise le même compteur.
supercat
1
Les interruptions ne seront-elles pas désactivées jusqu'à ce que nous quittions la section critique? Si oui, WFI ne fera-t-il pas attendre indéfiniment le CPU?
Corneliu Zuzu
1
La réponse de @Kenzi Shrimp pointe vers un fil de discussion Linux qui répond à ma question précédente. J'ai modifié sa réponse et la vôtre pour clarifier cela.
Corneliu Zuzu
@CorneliuZuzu Modifier la réponse de quelqu'un d'autre pour interposer votre propre discussion n'est pas une bonne idée. L'ajout du devis pour améliorer une réponse «lien uniquement» est une autre affaire. Si vous avez une vraie question sur votre won, posez-la peut-être sous forme de question et liez-la à celle-ci.
Sean Houlihane
1
@SeanHoulihane Je n'ai pas invalidé ni supprimé quoi que ce soit de sa réponse. Une brève clarification sur la raison pour laquelle cela fonctionne n'est pas une discussion séparée. Honnêtement, je ne pense pas que cette réponse mérite un vote positif sans la clarification du WFI, mais elle le mérite le plus avec.
Corneliu Zuzu
7

Votre idée va bien, c'est exactement ce que Linux implémente. Voyez ici .

Citation utile du fil de discussion mentionné ci-dessus pour clarifier pourquoi WFI fonctionne même avec des interruptions désactivées:

Si vous avez l'intention de rester inactif jusqu'à la prochaine interruption, vous devez faire un peu de préparation. Pendant cette préparation, une interruption peut devenir active. Une telle interruption peut être un événement de réveil que vous recherchez.

Peu importe la qualité de votre code, si vous ne désactivez pas les interruptions, vous aurez toujours une course entre la préparation au sommeil et le fait de vous endormir, ce qui entraîne des événements de réveil perdus.

C'est pourquoi tous les processeurs ARM dont je suis conscient se réveilleront même s'ils sont masqués sur le processeur principal (bit CPSR I).

Autre chose et vous devez oublier d'utiliser le mode veille.

Crevette
la source
1
Vous faites référence à la désactivation des interruptions au moment de l'instruction WFI ou WFE? Voyez-vous une distinction significative entre l'utilisation du WFI ou du WFE à cette fin?
supercat
1
@supercat: J'utiliserais certainement WFI. WFE IMO est principalement destiné aux astuces de synchronisation entre les cœurs dans un système multicœur (par exemple, faire WFE sur spinlock en cas d'échec et émettre SEV après être sorti de spinlock). En outre, WFE prend en compte l'indicateur de masquage d'interruption, il n'est donc pas aussi utile ici que WFI. Ce modèle fonctionne vraiment bien sous Linux.
Crevettes
2

En admettant que:

  1. Le thread principal exécute les tâches en arrière-plan
  2. Les interruptions exécutent uniquement des tâches de haute priorité et aucune tâche d'arrière-plan
  3. Le thread principal peut être interrompu à tout moment (il ne masque normalement pas les interruptions)

Ensuite, la solution consiste à utiliser PRIMASK pour bloquer les interruptions entre la validation de l'indicateur et WFI:

mask_interrupts();
if (!dont_sleep)
    wfi();
unmask_interrupts();
hdante
la source
0

Qu'en est-il du mode veille à la sortie? Cela se met automatiquement en veille chaque fois qu'un gestionnaire IRQ se termine, il n'y a donc pas vraiment de "mode normal" en cours d'exécution une fois configuré. Un IRQ se produit, il se réveille et exécute le gestionnaire, puis se rendort. Aucun WFI nécessaire.

billt
la source
2
Comment gérer au mieux le fait que le type de sommeil dans lequel le processeur doit tomber peut varier en fonction de ce qui se passe pendant une interruption? Par exemple, un événement de changement de broche peut indiquer que des données série peuvent arriver, et que le processeur doit donc maintenir l'oscillateur d'horloge principal en marche en attendant les données. Si la boucle principale efface l'indicateur d'événement, examine ce qui se passe et place le processeur dans un mode de veille approprié avec un WFI, alors toute interruption qui aurait pu affecter le mode approprié définirait l'indicateur d'événement ...
supercat
... et interrompre le sommeil. Avoir un gestionnaire de boucle principale contrôlant le mode veille semble plus propre que d'avoir à s'en soucier à chaque interruption. Devoir "tourner" cette partie de la boucle principale à chaque interruption peut ne pas être efficace de manière optimale, mais cela ne devrait pas être trop mauvais, surtout si toutes les interruptions qui pourraient affecter le comportement de sommeil frappent un indicateur.
supercat