18 votos

Mejor patrón para WFI (espera por interrupción) en microcontroladores Cortex (ARM)

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

Si el sueño se comprometiera almacenando algo en algún lugar, se podrían utilizar operaciones de carga-exclusiva/almacenamiento-exclusivo para hacer algo así:

  // 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 saltarse la store_exclusive, haciendo así que el sistema recorriera el bucle una vez más (para ver si la interrupción había activado dont_sleep). Desafortunadamente, el Gekko utiliza una instrucción WFI en lugar de una dirección de escritura para activar el modo sleep; escribir 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 fijara dont_sleep, pero el wfi seguiría adelante y se ejecutaría de todos modos. ¿Cuál es el mejor patrón para evitar eso? ¿Poner PRIMASK a 1 para evitar que las interrupciones interrumpan el procesador justo antes de ejecutar el WFI, y borrarlo inmediatamente después? ¿O hay algún truco mejor?

EDITAR

Me pregunto por la parte del Evento. Por la descripción general, parece que está pensado para soporte multiprocesador, pero me 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 activa don't_sleep también debería ejecutar una instrucción SEV, de modo que si la interrupción ocurre después de la prueba "if", el WFE borraría la bandera de evento pero no entraría en reposo. ¿Le parece un buen paradigma?

14voto

Caleb Woodman Puntos 110

Ponlo dentro de una sección crítica. Los ISRs no se ejecutarán, así que no corres el riesgo de que dont_sleep cambie antes que WFI, pero aún así despertarán al procesador y los ISRs se ejecutarán tan pronto como la sección crítica termine.

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

Su entorno de desarrollo probablemente tiene funciones de sección crítica, 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. */

8voto

Abe Heidebrecht Puntos 16417

Tu idea está bien, esto es exactamente lo que implementa Linux. Véase aquí .

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

Si tiene intención de esperar hasta que alguna preparación. Durante esa preparación, una interrupción puede convertirse en activa. Tal interrupción puede ser un evento de despertar que estás buscando. que estás buscando.

No importa lo bueno que sea tu código, si no desactivas las interrupciones, te siempre habrá una carrera entre la preparación para ir a dormir y realmente ir a dormir, lo que resulta en eventos de despertar perdidos.

Esta es la razón por la que todas las CPUs ARM que conozco o enmascarados en el núcleo de la CPU (CPSR I bit.)

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

3voto

Mark McDonald Puntos 246

No entendía del todo el dont_sleep pero una cosa que podrías intentar es hacer el "trabajo principal" en el manejador PendSV, configurado con la prioridad más baja. A continuación, sólo tiene que programar un PendSV de otros manipuladores cada vez que necesite hacer algo. Ver aquí cómo hacerlo (es para M1 pero M3 no es muy diferente).

Otra cosa que podría utilizar (tal vez junto con el enfoque anterior) es la función Sleep-on-exit. Si la activas, el procesador se irá a dormir después de salir del último manejador ISR, sin que tengas que llamar a WFI. Ver algunos ejemplos aquí .

2voto

vy32 Puntos 239

Suponiendo que:

  1. El hilo principal ejecuta tareas en segundo plano
  2. Las interrupciones sólo ejecutan tareas de alta prioridad y no tareas en segundo plano
  3. El hilo principal puede ser interrumpido en cualquier momento (normalmente no enmascara las interrupciones)

Entonces la solución es utilizar PRIMASK para bloquear las interrupciones entre la validación de la bandera y WFI:

mask_interrupts();
if (!dont_sleep)
    wfi();
unmask_interrupts();

0voto

travega Puntos 140

¿Qué pasa con el modo Sleep on Exit? Este se va automáticamente a dormir cada vez que un controlador IRQ sale, por lo que no hay realmente ningún "modo normal" en ejecución después de que está configurado. Una IRQ ocurre, se despierta y ejecuta el manejador, y vuelve a dormir. No se necesita WFI.

i-Ciencias.com

I-Ciencias es una comunidad de estudiantes y amantes de la ciencia en la que puedes resolver tus problemas y dudas.
Puedes consultar las preguntas de otros usuarios, hacer tus propias preguntas o resolver las de los demás.

Powered by:

X