El mejor patrón para WFI (esperar por interrupción) en microcontroladores Cortex (ARM)

18

Estoy buscando desarrollar software alimentado por batería usando los controladores EFM Gekko (http://energymicro.com/) y me gustaría que el controlador esté dormido cada vez que no haya nada útil para hacerlo. La instrucción WFI (Wait For Interrupt) se utiliza para este propósito; pondrá el procesador en suspensión hasta que ocurra una interrupción.

Si el sueño se activara almacenando algo en algún lugar, uno podría usar operaciones exclusivas de carga / exclusivas de tienda para hacer algo como:

  // dont_sleep se carga con 2 cada vez que sucede algo que
  // debería forzar al ciclo principal a circular al menos una vez. Si una interrupción
  // ocurre que hace que se restablezca a 2 durante la siguiente declaración,
  // el comportamiento será como si la interrupción ocurriera después.

  store_exclusive (load_exclusive (dont_sleep) >> 1);

  mientras que (! dont_sleep)
  {
    // Si se produce una interrupción entre la siguiente instrucción y store_exclusive, no duermas
    load_exclusive (SLEEP_TRIGGER);
    si (! dont_sleep)             
      store_exclusive (SLEEP_TRIGGER);
  }

Si ocurriera una interrupción entre las operaciones load_exclusive y store_exclusive, el efecto sería omitir store_exclusive, haciendo que el sistema se ejecute a través del ciclo una vez más (para ver si la interrupción había establecido dont_sleep). Desafortunadamente, Gekko utiliza una instrucción WFI en lugar de una dirección de escritura para activar el modo de suspensión; escribir código como

  si (! dont_sleep)
    WFI ();

correría el riesgo de que se produzca una interrupción entre el 'if' y el 'wfi' y establezca dont_sleep, pero el wfi continuará y se ejecutará de todos modos. ¿Cuál es el mejor patrón para evitar eso? Establezca PRIMASK en 1 para evitar que las interrupciones interrumpan el procesador justo antes de ejecutar el WFI, y bórrelo inmediatamente después. ¿O hay algún truco mejor?

EDITAR

Me pregunto sobre el bit del evento. Según la descripción general, desearía que fuera compatible con varios procesadores, pero se preguntaba si algo como lo siguiente podría funcionar:

  if (no dormir)
    SEV (); / * Hará que la siguiente marca de evento WFE sea clara pero no duerma * /
  WFE ();

Cada interrupción que establezca don't_sleep también debe ejecutar una instrucción SEV, por lo que si la interrupción ocurre después de la prueba "if", el WFE borrará el indicador de evento pero no se irá a dormir. ¿Suena eso como un buen paradigma?

Super gato
fuente
1
La instrucción WFI no pone el núcleo en suspensión si su condición de activación es verdadera cuando se ejecuta la instrucción. Por ejemplo, si hay una IRQ sin borrar cuando se ejecuta WFI, actúa como un NOP.
Mark
@Mark: El problema sería que si se toma una interrupción entre "if (! Dont_sleep)" y "WFI", la condición de interrupción ya no estaría pendiente cuando se ejecute el WFI, pero la interrupción podría haber establecido dont_sleep porque hizo algo que justificaría que el bucle principal ejecutara otra iteración. En una aplicación de Cypress PSOC mía, cualquier interrupción que debería causar un despertar prolongado podría dañar la pila si el código de la línea principal estaba a punto de dormir, pero eso parece bastante asqueroso y entiendo que ARM desalienta tales manipulaciones de la pila.
supercat
@supercat La interrupción puede o no borrarse cuando se ejecuta WFI. Depende de usted y cuándo / dónde elija borrar la interrupción. Deshágase de la variable dont_sleep y simplemente use una interrupción enmascarada para indicar cuándo desea permanecer despierto o dormir. Puede deshacerse de la declaración if todos juntos y dejar WFI al final del ciclo principal. Si ha atendido todas las solicitudes, borre la IRQ para que pueda dormir. Si necesita permanecer despierto, active el IRQ, está enmascarado para que no pase nada, pero cuando WFI intente ejecutarlo NOP.
Mark
2
@supercat En un nivel más fundamental, parece que estás tratando de mezclar un diseño impulsado por interrupciones con un diseño de 'gran bucle principal', que generalmente no es crítico con el tiempo, a menudo basado en encuestas y tiene interrupciones mínimas. Mezclar estos puede volverse bastante feo bastante rápido. Si es posible, elija un paradigma de diseño u otro para usar. Recuerde que con los controladores de interrupción modernos básicamente obtiene una multitarea preventiva entre interrupciones y lo que equivale a colas de tareas (atender una interrupción, luego la siguiente prioridad más alta, etc.). Use eso para su ventaja.
Marcar el
@ Mark: desarrollé un sistema que utilizaba un PIC 18x bastante bien en una aplicación con batería; debido a las limitaciones de la pila, no podía manejar demasiado dentro de una interrupción, por lo que la gran mayoría de las cosas se manejan en el bucle principal de manera conveniente. En su mayoría funciona bastante bien, aunque hay un par de puntos donde las cosas se bloquean por un segundo o dos debido a operaciones de larga duración. Si migro a un ARM, puedo usar un RTOS simple para que sea más fácil dividir las operaciones de larga duración, pero no estoy seguro de si usar la multitarea preventiva o cooperativa.
supercat

Respuestas:

3

No entendí completamente el dont_sleepasunto, pero una cosa que podría intentar es hacer el "trabajo principal" en el controlador PendSV, configurado con la prioridad más baja. Luego, solo programe un PendSV de otros controladores cada vez que necesite hacer algo. Vea aquí cómo hacerlo (es para M1 pero M3 no es muy diferente).

Otra cosa que podría usar (tal vez junto con el enfoque anterior) es la función Dormir al salir. Si lo habilita, el procesador se suspenderá después de salir del último controlador ISR, sin que tenga que llamar a WFI. Vea algunos ejemplos aquí .

Igor Skochinsky
fuente
55
la instrucción WFI no requiere que se habiliten las interrupciones para activar el procesador, se ignoran los bits F e I en CPSR.
Mark
1
@ Mark Debo haber perdido ese bit en los documentos, ¿tienes algunos enlaces / punteros al respecto? ¿Qué sucede con la señal de interrupción que despertó el núcleo? ¿Permanece pendiente hasta que la interrupción se habilite nuevamente?
Igor Skochinsky
El manual de referencia de ASM está aquí: infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0489c/… información más específica para el cortex-m3 está aquí: infocenter.arm.com/help/ index.jsp? topic = / com.arm.doc.dui0552a / ... en resumen, cuando una interrupción enmascarada queda pendiente, el núcleo se activa y continúa la operación después de la instrucción WFI. Si trató de emitir otro WFI sin borrar esa interrupción pendiente, el WFI actuaría como un NOP (el núcleo no dormiría ya que la condición de activación para WFI es verdadera).
Mark
@ Mark: Una cosa que estaba considerando sería tener un controlador de interrupciones que establezca dont_sleep también ejecutar una instrucción SEV ("Establecer evento"), y luego usar WFE ("Esperar evento") en lugar de WFI. Los ejemplos de Gekko parecen usar WFI, pero creo que WFE también podría funcionar. ¿Alguna idea?
supercat
10

Ponlo dentro de una sección crítica. Los ISR no se ejecutarán, por lo que no corre el riesgo de que Dont_sleep cambie antes de WFI, pero aún así despertará al procesador y los ISR se ejecutarán tan pronto como finalice la sección crítica.

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

Su entorno de desarrollo probablemente tenga funciones de sección críticas, pero es más o menos así:

EnterCriticalSection es:

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

ExitCriticalSection es:

MSR PRIMASK, r0 /* Restore interrupt states. */
BX lr /* Return. */
Kenzi
fuente
2
Curiosamente, varias bibliotecas ARM usan una implementación de sección crítica que usa un contador global en lugar de preservar el estado localmente. Me parece alucinante ya que el enfoque del contador es más complicado y solo funcionará si todo el código del sistema utiliza el mismo contador.
supercat
1
¿No se deshabilitarán las interrupciones hasta que salgamos de la sección crítica? Si es así, ¿WFI no hará que la CPU espere indefinidamente?
Corneliu Zuzu
1
La respuesta de @Kenzi Shrimp apunta a un hilo de discusión de Linux que responde a mi pregunta anterior. Edité su respuesta y la suya para aclarar eso.
Corneliu Zuzu
@CorneliuZuzu Editar la respuesta de otra persona para interponer su propia discusión no es una gran idea. Agregar la cita para mejorar una respuesta de 'solo enlace' es una cuestión diferente. Si tiene una pregunta real sobre su ganado, tal vez hágalo como una pregunta y enlace a esta.
Sean Houlihane
1
@SeanHoulihane No he invalidado ni eliminado nada de su respuesta. Una breve aclaración sobre por qué esto funciona no es una discusión separada. Honestamente, no creo que esta respuesta merezca un voto positivo sin la aclaración de WFI, pero sí la merece más.
Corneliu Zuzu
7

Su idea está bien, esto es exactamente lo que implementa Linux. Ver aquí .

Cita útil del hilo de discusión mencionado anteriormente para aclarar por qué WFI funciona incluso con interrupciones deshabilitadas:

Si tiene la intención de estar inactivo hasta la próxima interrupción, debe hacer algo de preparación. Durante esa preparación, una interrupción puede activarse. Tal interrupción puede ser un evento de despertar que estás buscando.

No importa qué tan bueno sea su código, si no deshabilita las interrupciones, siempre tendrá una carrera entre prepararse para ir a dormir y realmente ir a dormir, lo que resulta en eventos de despertador perdidos.

Es por eso que todas las CPU ARM que conozco se activarán incluso si están enmascaradas en la CPU central (CPSR I bit).

Cualquier otra cosa y deberías olvidar usar el modo inactivo.

Camarón
fuente
1
¿Se refiere a la desactivación de interrupciones en el momento de la instrucción WFI o WFE? ¿Ves alguna distinción significativa entre usar WFI o WFE para este propósito?
supercat
1
@supercat: Definitivamente usaría WFI. WFE IMO es principalmente para sugerencias de sincronización entre núcleos en un sistema multinúcleo (por ejemplo, hacer WFE en el spinlock, fallar y emitir SEV después de salir del spinlock). Además, WFE tiene en cuenta el indicador de enmascaramiento de interrupción, por lo que no es tan útil aquí como WFI. Este patrón realmente funciona bien en Linux.
Camarones
2

Asumiendo que:

  1. El hilo principal ejecuta tareas en segundo plano
  2. Las interrupciones solo ejecutan tareas de alta prioridad y no tareas en segundo plano.
  3. El hilo principal se puede interrumpir en cualquier momento (normalmente no oculta las interrupciones)

Entonces, la solución es usar PRIMASK para bloquear las interrupciones entre la validación del indicador y WFI:

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

¿Qué pasa con el modo Sleep on Exit? Esto se desactiva automáticamente cada vez que sale un controlador IRQ, por lo que no hay realmente ningún "modo normal" ejecutándose después de que esté configurado. Ocurre una IRQ, se despierta, ejecuta el controlador y vuelve a dormir. No se necesita WFI.

billt
fuente
2
¿Cómo debería uno lidiar mejor con el hecho de que el tipo de suspensión en la que debe caer el procesador puede variar en función de algo que sucede durante una interrupción? Por ejemplo, un evento de cambio de clavija podría indicar que los datos en serie pueden estar disponibles y que, por lo tanto, el procesador debe mantener el oscilador de reloj primario en funcionamiento mientras espera los datos. Si el bucle principal borra el indicador de evento, examina lo que está sucediendo y pone el procesador en un modo de suspensión adecuado con un WFI, entonces cualquier interrupción que pueda haber afectado el modo apropiado establecería el indicador de evento ...
supercat
... y abortar el sueño. Tener un controlador de bucle principal que controle el modo de suspensión parece más limpio que tener que preocuparse por ello en cada interrupción. Tener que "girar" esa parte del bucle principal en cada interrupción puede no ser óptimamente eficiente, pero no debería ser tan malo, especialmente si todas las interrupciones que podrían afectar el comportamiento del sueño golpean alguna bandera.
supercat