El mejor patrón para WFI (esperar para interrumpir) en los microcontroladores Cortex (ARM)

15

Estoy estudiando el desarrollo de software alimentado por batería que use los controladores EFM Gekko (http://energymicro.com/) y me gustaría que el controlador esté dormido cuando no haya nada útil que esté haciendo. La instrucción WFI (Wait For Interrupt) se usa para este propósito; pondrá el procesador en suspensión hasta que se produzca una interrupción.

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

  // dont_sleep gets loaded with 2 any time something happens that
  // should force the main loop to cycle at least once.  If an interrupt
  // occurs that causes it to be reset to 2 during the following statement,
  // behavior will be as though the interrupt happened after it.

  store_exclusive(load_exclusive(dont_sleep) >> 1);

  while(!dont_sleep)
  {
    // If interrupt occurs between next statement and store_exclusive, don't sleep
    load_exclusive(SLEEP_TRIGGER);
    if (!dont_sleep)             
      store_exclusive(SLEEP_TRIGGER);
  }

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

  if (!dont_sleep)
    WFI();

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

EDIT

Me pregunto sobre el bit de Evento. Según la descripción general, le gustaría que estuviera destinado a la compatibilidad con varios procesadores, pero se preguntaba si algo como lo siguiente podría funcionar:

  if (dont_sleep)
    SEV();  /* Will make following WFE clear event flag but not sleep */
  WFE();

Cada interrupción que establece la función de desactivación o desactivación 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 pondrá en suspensión. ¿Eso suena como un buen paradigma?

    
pregunta supercat

5 respuestas

3

No entendí completamente la cosa dont_sleep , pero una cosa que podrías intentar es hacer el "trabajo principal" en el controlador PendSV, establecido en la prioridad más baja. Luego solo programe un PendSV de otros manejadores cada vez que necesite algo. Consulte 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 Suspender para salir. Si lo habilita, el procesador se pondrá en suspensión después de salir del último controlador ISR, sin que tenga que llamar a WFI. Vea algunos ejemplos aquí .

    
respondido por el Igor Skochinsky
8

Ponlo dentro de una sección crítica. Los ISR no se ejecutarán, por lo que no corres el riesgo de que dont_sleep cambie antes de WFI, pero seguirán activando el 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);

Es probable que su entorno de desarrollo 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. */
    
respondido por el Kenzi
5

Tu idea está bien, esto es exactamente lo que implementa Linux. Consulte 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 hacerlo   algo de preparacion Durante esa preparación, una interrupción puede llegar a ser   activo. Tal interrupción puede ser un evento de despertar que estás buscando   para.

     

No importa qué tan bueno sea su código, si no deshabilita las interrupciones, usted   Siempre habrá una carrera entre prepararse para ir a dormir y en realidad   va a dormir, lo que resulta en eventos de despertar perdidos.

     

Esta es la razón por la que todas las CPUs ARM que conozco se activarán incluso si están   enmascarado en la CPU central (CPSR I bit.)

     

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

    
respondido por el Shrimp
3

Suponiendo 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 enmascara 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();
    
respondido por el hdante
0

¿Qué pasa con el modo de espera en modo de salida? Esto se pone en suspensión automáticamente cada vez que sale un controlador IRQ, por lo que realmente no se ejecuta ningún "modo normal" después de que está configurado. Ocurre un IRQ, se despierta y ejecuta el controlador, y vuelve a dormir. No se necesita WFI.

    
respondido por el billt

Lea otras preguntas en las etiquetas