Best pattern for WFI (wait-for-interrupt) on Cortex (ARM) microcontrolers
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. */
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.
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.