STM32 Sleep Mode: Interrupt gets executed but the CPU stays in WFI

I'm fairly new to the ARM architecture and I'm working with a board that contains STM32F0 microcontroller and an RF modem, which sends an interrupt every time it receives a message. I'm trying to implement a sleep mode in a microcontroller to save power but I don't quite understand why it behaves the way it does.

I started with a simple program that puts the CPU into sleep mode at the beginning of the execution. Then it waits for the external interrupt to wake it up, execute the interrupt and continue with the main program. In the ISR I toggle an LED on pin PA0 every time the routine is called, but then use pin PA1 in the main() that toggles and LED on and off with a short delay, indicating that the microcontroller exited the sleep mode.

My code basically looks like this:

int main()
{
periph_init();
modem_init();

HAL_PWR_EnterSLEEPMode(0,1);

while(1)
{
HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_1);
Delay();
}
}


And the ISR for the EXTI

void EXTI0_1_IRQHandler(void)
{
HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_0);

// Some other stuff

HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_1);
}


The ARMv6 Architecture Reference Manual states the following regarding the Wait For Interrupt on the page B1-243:

When a processor issues a WFI instruction it can suspend execution and enter a low-power state. It can remain in that state until the processor detects one of the following WFI wake-up events:

• A reset.

• An asynchronous exception at a priority that, if PRIMASK.PM was set to 0, would preempt any currently active exceptions.

• If debug is enabled, a debug event.

• An IMPLEMENTATION DEFINED WFI wakeup event.

If my understanding is correct, the second line applies in this case. Which means, that calling any interrupt regardless of its priority would wake up the processor, since the WFI was called from the main. For information, all the implemented interrupt priorities are set to 0 except for the EXTI, which is set to -1.

Now this is what actually happens. The LED on PA0 toggles appropriately, indicating that the ISR does indeed execute. But the LED on PA1 stays turned off, which means that upon completing the ISR, the CPU remains in a WFI. This is confirmed even during the debugging, while I inserted a breakpoint in the interrupt service routine. After letting the code run, it succesfully stops at the breakpoint. If I then continue debugging with a step-by-step function, the program jumps to the WFI instruction (upon which it continues to the main, since debugging can cause to terminate the WFI).

My question is, why does the CPU return to the WFI when the interrupt clearly gets called? Given my understanding, if the CPU is in WFI, it should continue with it's operation wherever it left off as soon as any interrupt get called.

• What does "Delay()" with no argument do? Maybe you're stuck in that? – Scott Seidman Jul 20 '16 at 13:16
• All the init functions and delay and such are just a simplified replacement for the actual code. I'm using a for loop in place of Delay() and I've tested the program with commenting the HAL_PWR_EnterSLEEPMode(0,1) out and the LED blinks nicely. And even if the uC was stuck at the delay, it would first execute the toggle instruction, which would turn on the LED but the LED stays off. – aljaz41 Jul 20 '16 at 13:19
• Well, step 1 should be to GET RID OF ACTUAL CODE. Reduce your code to the minimum necessary to replicate your problem. By the time you're done doing this, chances are you'll have solved your own issue. – Scott Seidman Jul 20 '16 at 13:22
• Note: void HAL_Delay(__IO uint32_t Delay) – Bence Kaulics Jul 20 '16 at 17:41
• I assume this code is a simplification of your end goal - generally its best to assume that a WFI on its own might not cause the processor to enter sleep state (due to the impdef wakeup clause). Somewhere, you'll need to loop back and re-try the WFI (assuming there is no work remaining). – Sean Houlihane Jul 21 '16 at 9:51

1 Answer

Check if the SLEEPONEXIT bit in SCB->SCR is set. Setting that to 1 would cause exactly what you've described:

Bit 1 SLEEPONEXIT

Configures sleep-on-exit when returning from Handler mode to Thread mode. Setting this bit to 1 enables an interrupt-driven application to avoid returning to an empty main application.

0: Do not sleep when returning to Thread mode.

1: Enter sleep, or deep sleep, on return from an interrupt service routine.