Electronic – Best pattern for WFI (wait-for-interrupt) on Cortex (ARM) microcontrolers

armcortex-m3sleep

I'm looking into developing battery-powered software using the EFM Gekko controllers (http://energymicro.com/) and would like the controller to be asleep whenever there's nothing useful for it to be doing. The WFI (Wait For Interrupt) instruction is used for this purpose; it will put the processor to sleep until an interrupt occurs.

If sleep were engaged by storing something someplace, one could use load-exclusive/store-exclusive operations to do something like:

  // 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);
  }

If an interrupt were to occur between the load_exclusive and store_exclusive operations,
the effect would be to skip the store_exclusive, thus causing the system to run through the loop one more time (to see if the interrupt had set dont_sleep). Unfortunately, the Gekko uses a WFI instruction rather than a write address to trigger sleep mode; writing code like

  if (!dont_sleep)
    WFI();

would run the risk that an interrupt could occur between the 'if' and the 'wfi' and set dont_sleep, but the wfi would go ahead and execute anyway. What's the best pattern to prevent that? Set PRIMASK to 1 to prevent interrupts from interrupting the processor just before executing the WFI, and clear it immediately after? Or is there some better trick?

EDIT

I'm wondering about the Event bit. By the general description, it woulds like it's intended for multi-processor support, but was wondering whether something like the following might work:

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

Every interrupt that sets don't_sleep should also execute an SEV instruction, so if the interrupt happens after the "if" test, the WFE would clear the event flag but not go to sleep. Does that sound like a good paradigm?

Best Answer

I did not fully understand the dont_sleep thing, but one thing you could try is do the "main work" in the PendSV handler, set to the lowest priority. Then just schedule a PendSV from other handlers each time you need something done. See here how to do it (it's for M1 but M3 is not too different).

Another thing you could use (maybe together with the previous approach) is the Sleep-on-exit feature. If you enable it, the processor will go to sleep after exiting the last ISR handler, without you having to call WFI. See some examples here.