So I had time to do some test on ARM M0+ in chip MKL17Z256VFT4. Using CMSIS-RTOS RTX (v 4.75).
It works like this:
void os_idle_demon(void) { // task with lowest priority - scheduled by
//system when there is no action to do
for (;;) {
timeToSleep = os_suspend(); // stop OS from switching tasks and get maximum allowed sleep time
__disable_irq();
LPTMR_Init(timeToSleep...); // set Low Power sleep timer
SCB->SCR |= SCB_SCR_SLEEPDEEP_Msk;//set DeepSleep
GPIO(pin=0,val=1); // signalize on GPIO pad that CPU is (almost) in sleep
__enable_irq();
__wfi(); // go to DeepSleep
GPIO(pin=0,val=0); // signalize on GPIO pad that CPU just wakeup
sleptTime = LPTMR_GetCounterValue(); // get sleepTime after wakeup
os_resume(sleptTime); // set system to schedule tasks and give os info about sleep time
}
I did observe what happens when I stimulate interrupt at different places of code execution. I used NVIC_SetPendingIRQ(PORTCD_IRQn);
for enforcing IRQ. I observed which task is running by logic analyzer observing GPIO pins.
case 1) easy case is: IRQ is triggered before os_suspend()
the ISR is called and I use in ISR system signalling osSignalSet(ID_Thread1, SIGNAL_X)
. Since every thread has higher priority than os_idle_demon
the thread ID_Thread1
which is waiting in event = osSignalWait(ANY_SIGNAL, osWaitForever);
is switched to (by RTOS) and signal is processed. After that thread starts to waiting again for any signal and os_idle_demon
task is scheduled and ARM goes to sleep.
case 2) Another case is: IRQ is set between os_suspend()
and __disable_irq()
. I found that when IRQ is called just before __disable_irq()
the ARM is not quick enough to process IRQ and actually __disable_irq()
is executed first. So IRQ waits until __enable_irq()
is called. And so all goes into another case.
case 3) IRQ is set before __enable_irq()
. Right after enabling IRQ and before CPU do DeepsSleep (__wfi();
) the ISR is executed. Signal is set. But system can not switch thread (as we called os_suspend()
). But apparently WFI is somehow magically (I'm still looking into spec why) not executed. The deep sleep is not eneterd and code continues to os_resume()
. Then OS switch tasks and signal is processed correctly.
So the only buggy case is when you put something between instructions:
__enable_irq();
// do not put anything here
__wfi();
If you put there anything then case 3 would react like this: the ISR is executed right after __enable_irq()
. ISR set OS signal but signaled task is not scheduled (because we called os_suspend()
before). Then deep sleep is entered by __wfi()
. System then sleeps forever or until LPTMR. But that is error because there is signal which shall be handled as soon as possible and it is not!
So conclusion is that it seems that sequence depicted in response is safe. As long as you do not put any instruction in between __enable_irq();
and __wfi();
. Also you shall not put any instruction in between: os_suspend();
and __disable_irq();
. This is valid at least for MKL17Z256VFT4. Dunno about other chip. But you can test yourself by enforcing the IRQ flag using function NVIC_SetPendingIRQ()
--- EDIT ---
So my friend showed me also documentation where is written that even when you disable interrupt CPSID
the ARM wake up from WFI. So maybe safer sequence is
__wfi(); // go to DeepSleep
// optionally enable peripherals that might been disabled
__enable_irq();
Do not forget to call __enable_irq();
at latest before you call os_resume(sleptTime);
otherwise on my chip I get HardFault.
--- EDIT 2 ---
Also I found that we could use __WFE();
instruction to be sure there is no racing condition. WFE is Wait For Event. It puts CPU to same sleep mode as WFI. But it also checks "event register". This register is set on every ISR (at the end). When there was IRQ before WFE then WFE will not go to sleep. You can optionally set "event register" by calling instruction __SEV();
. "event register" is not accessible from SW. If you want to be sure to clear it you can call
__SEV(); // set event register if it was not set
__WFE(); // clear event register and don't goto sleep because we set event register just before
But note that this instruction has slightly different behavior from WFI. For example WFE can wake up also on pending IRQ if SEVONPEND is set (see WFE spec). (Note interrupt is pending if has less priority than configured in Base Priority Mask Register)See also Entering Sleep Mode. Here is also very nice table about difference WFI vs WFE