20
\$\begingroup\$

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?

\$\endgroup\$
7
  • 1
    \$\begingroup\$ The WFI instruction doesn't put the core to sleep if its wake condition is true when the instruction is executed. For example if there is an uncleared IRQ when WFI is executed it act as a NOP. \$\endgroup\$
    – Mark
    Apr 5, 2011 at 18:56
  • \$\begingroup\$ @Mark: The issue would be that if an interrupt is taken between the "if (!dont_sleep)" and the "WFI", the interrupt condition would no longer be pending when the WFI executes, but the interrupt might have set dont_sleep because it did something that would justify the main loop running another iteration. On a Cypress PSOC application of mine, any interrupts which should cause an extended wakeup would jinx the stack if the main-line code was about to sleep, but that seems pretty icky and I understand ARM discourages such stack manipulations. \$\endgroup\$
    – supercat
    Apr 5, 2011 at 19:32
  • \$\begingroup\$ @supercat The interrupt may or may not be cleared when WFI executes. Its up to you and when/where you choose to clear the interrupt. Get rid of the dont_sleep variable and just use a masked interrupt to signify when you want to stay awake or sleep. You can just get rid of the if statement all together and leave WFI at the end of the main loop. If you've serviced all requests, clear the IRQ so you can sleep. If you need to stay awake, trigger the IRQ, its masked so nothing happens, but when WFI tries to execute it will NOP. \$\endgroup\$
    – Mark
    Apr 5, 2011 at 20:04
  • 2
    \$\begingroup\$ @supercat At a more fundamental level, it seems that your trying to mix an interrupt-driven design with a 'big main loop' design, which is usually non time critical, often polling based and has minimal interrupts. Mixing these can get rather ugly rather fast. If at all possible choose one design paradigm or the other to use. Remember that with modern interrupt controllers you basically get preemptive multitasking between interrupts and what amounts to task queues (service one interrupt, then the next higher priority, etc). Use that to your advantage. \$\endgroup\$
    – Mark
    Apr 5, 2011 at 20:10
  • \$\begingroup\$ @Mark: I developed a system which used a PIC 18x pretty well in a battery-powered application; because of stack limitations, it couldn't handle too much within an interrupt, so the vast majority of stuff gets handled in the main loop on an as-convenient basis. It mostly works pretty well, though there are a couple of spots where things get blocked for a second or two because of long-running operations. If I migrate to an ARM, I may use a simple RTOS to make it easier to split up the long-running operations, but I'm not sure whether to use preemptive or cooperative multitasking. \$\endgroup\$
    – supercat
    Apr 5, 2011 at 22:07

5 Answers 5

3
\$\begingroup\$

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.

\$\endgroup\$
4
  • 5
    \$\begingroup\$ the WFI instruction does not require interrupts to be enabled to wake the processor, the F and I bits in CPSR are ignored. \$\endgroup\$
    – Mark
    Apr 5, 2011 at 18:05
  • 1
    \$\begingroup\$ @Mark I must have missed that bit in the docs, do you have some links/pointers about it? What happens to the interrupt signal that woke up the core? Does it stay pended until interrupt are enabled again? \$\endgroup\$ Apr 5, 2011 at 18:44
  • \$\begingroup\$ The ASM reference manual is here: infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0489c/… more specific information for the cortex-m3 is here: infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0552a/… in short when a masked interrupt becomes pending the core wakes up and continues operation after the WFI instruction. If you tried to issue another WFI without clearing that pending interrupt the WFI would act as a NOP (the core wouldn't sleep since the wake condition for WFI is true). \$\endgroup\$
    – Mark
    Apr 5, 2011 at 19:08
  • \$\begingroup\$ @Mark: One thing I was considering would be to have any interrupt handler which sets dont_sleep also execute an SEV ("Set Event") instruction, and then use WFE ("Wait For Event") rather than WFI. The Gekko examples seem to use WFI, but I would think WFE might also work. Any thoughts? \$\endgroup\$
    – supercat
    Apr 5, 2011 at 19:37
15
\$\begingroup\$

Put it inside a critical section. ISRs won't run, so you don't run the risk of dont_sleep changing before WFI, but they will still wake the processor and the ISRs will execute as soon as the critical section ends.

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

Your development environment probably has critical section functions, but it's roughly like this:

EnterCriticalSection is:

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

ExitCriticalSection is:

MSR PRIMASK, r0 /* Restore interrupt states. */
BX lr /* Return. */
\$\endgroup\$
7
  • 2
    \$\begingroup\$ Curiously, a number of ARM libraries use a critical-section implementation that uses a global counter instead of preserving status locally. I find that mind boggling since the counter approach is more complicated and will only work if all code system-wide uses the same counter. \$\endgroup\$
    – supercat
    Jan 23, 2017 at 19:37
  • 1
    \$\begingroup\$ Won't interrupts be disabled until we exit the critical section? If so, won't WFI cause the CPU to wait indefinitely? \$\endgroup\$ Sep 11, 2017 at 9:06
  • 1
    \$\begingroup\$ @Kenzi Shrimp's answer points to a Linux discussion thread which answers my previous question. I edited his answer and yours to clarify that. \$\endgroup\$ Sep 11, 2017 at 9:14
  • 1
    \$\begingroup\$ @SeanHoulihane I haven't invalidated nor removed anything from her answer. A short clarification on why this works is not a separate discussion. Honestly I don't feel this answer deserves an up-vote without the WFI clarification, but it does deserve it the most with it. \$\endgroup\$ Sep 11, 2017 at 10:51
  • 1
    \$\begingroup\$ @CorneliuZuzu The interrupts aren't disabled, just masked. WFI will still resume at the right time, but the interrupt routines won't run until the end of the critical section. \$\endgroup\$
    – Kenzi
    Mar 12, 2020 at 13:33
9
\$\begingroup\$

Your idea is fine, this is exactly what Linux implements. See here.

Useful quote from the above-mentioned discussion thread to clarify why WFI works even with interrupts disabled:

If you're intending to idle until the next interrupt, you have to do some preparation. During that preparation, an interrupt may become active. Such an interrupt may be a wake up event that you're looking for.

No matter how good your code is, if you don't disable interrupts, you will always have a race between preparing to go to sleep and actually going to sleep, which results in lost wake up events.

This is why all ARM CPUs I'm aware of will wake up even if they are masked at the core CPU (CPSR I bit.)

Anything else and you should forget using idle mode.

\$\endgroup\$
2
  • 1
    \$\begingroup\$ You're referring to disabling interrupts at the time of the WFI or WFE instruction? Do you see any meaningful distinction between using WFI or WFE for the purpose? \$\endgroup\$
    – supercat
    Apr 16, 2012 at 15:43
  • 2
    \$\begingroup\$ @supercat: I would definitely use WFI. WFE IMO is mainly for synchronization hints between cores in multicore system (for example doing WFE on spinlock taking fail and issuing SEV after going out from spinlock). Besides, WFE takes interrupt masking flag into account so it is not as useful here as WFI. This pattern really works well in Linux. \$\endgroup\$
    – Shrimp
    Apr 22, 2012 at 10:33
2
\$\begingroup\$

Assuming that:

  1. The main thread runs background tasks
  2. The interrupts only run high priority tasks and no background tasks
  3. The main thread can be interrupted any time (it does not normally masks interrupts)

Then the solution is to use PRIMASK to block interrupts between the flag validation and WFI:

mask_interrupts();
if (!dont_sleep)
    wfi();
unmask_interrupts();
\$\endgroup\$
0
\$\begingroup\$

What about Sleep on Exit mode? This automatically goes to sleep any time an IRQ handler exits, so there is not really any "normal mode" running after that's configured. An IRQ happens, it wakes up and runs the handler, and goes back to sleep. No WFI needed.

\$\endgroup\$
2
  • 2
    \$\begingroup\$ How should one best deal with the fact that the type of sleep into which the processor should fall might vary based upon something that happens during an interrupt? For example, a pin-change event might indicate that serial data may be forthcoming, and that the processor should therefore keep the primary clock oscillator running while awaiting the data. If the main loop clears the event flag, examines what's going on, and puts the processor to an appropriate sleep mode with a WFI, then any interrupt which might have affected what mode would be appropriate would set the event flag... \$\endgroup\$
    – supercat
    Aug 23, 2011 at 14:12
  • \$\begingroup\$ ...and abort the sleep. Having one main-loop handler control the sleep mode seems cleaner than having to worry about it in every interrupt. Having to "spin" that part of the main loop on every interrupt may not be optimally efficient, but it shouldn't be too bad, especially if all the interrupts that might affect sleeping behavior hit some flag. \$\endgroup\$
    – supercat
    Aug 23, 2011 at 14:15

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.