STM32L4 never wakes from Stop mode — button EXTI interrupt just doesn't fire
Asked by stale_biscuit_03 ·
Working on a battery-powered sensor node, STM32L476RG (Nucleo board for now). Idea is: device sits in Stop 2 mode most of the time, wakes when someone presses an external button wired to PB13, takes a reading, goes back to sleep.
In Run mode the button works perfectly — I can trigger HAL_GPIO_EXTI_Callback() all
day from the debugger. The problem is only after HAL_PWR_EnterSTOPMode(). The MCU
goes to sleep (current drops on my bench supply, so I know it's actually in Stop), but
pressing the button does nothing. No callback, no wake, current stays low forever. Have
to power-cycle to get it back.
Things I've checked:
- EXTI13 is configured in CubeMX as "External Interrupt Mode with Rising edge trigger" on PB13, pull-down enabled
- NVIC has EXTI15_10_IRQn enabled, I can see it in the generated
stm32l4xx_it.c - Multimeter on PB13 confirms it's reading 3.3V when I press the button, so the signal is definitely getting to the pin
This is the exact same GPIO/EXTI config that works fine in Run mode, just doesn't wake the device from Stop. What am I missing?
4 Replies
Almost certainly a line-sharing conflict, and PB13 is exactly the pin that makes this bite people. Is anything else in the project using PC13? On a Nucleo board that's the onboard User button (B1), and it's very common to have it wired up for something else in the same project — even just as a secondary debug input, or left over from an earlier version of the firmware.
Here's the mechanism: STM32's EXTI lines are numbered 0–15, and each number corresponds
to "pin N on whichever port you've routed it to" — but only one port at a time can be
routed to a given line number. PB13 and PC13 are both "pin 13," so they both compete for
EXTI13. The routing lives in SYSCFG->EXTICR[3] (EXTICR4 covers lines 12–15), and
HAL_GPIO_Init() overwrites the relevant EXTICR bits every time you initialise a pin in
EXTI mode — with no warning that it's stealing the line from whatever was there before.
So if MX_GPIO_Init() configures PC13 as EXTI first (because CubeMX generates pins in
port/pin order, and it's auto-added as the User Button), then your PB13 init runs after
it and reassigns EXTI13 to port B — that part is actually fine, PB13 wins in that case.
But if you've got PC13 configured anywhere after your PB13 init (a library, a later
manual call, a HAL_GPIO_Init() you added for a different feature), it silently steals
EXTI13 back to port C. Your button's rising edge on PB13 then does nothing at all,
because the EXTI13 line is now watching PC13 instead — which explains why it works fine
when you trigger it from the debugger (you're calling the ISR/callback directly or the
GPIO state happens to line up during your test) but never actually fires from a real
button press in Stop mode, where the last SYSCFG_EXTICR write before you enter Stop is
what determines which port actually wakes you.
Fastest way to confirm: drop a breakpoint after all your MX_xxx_Init() calls and read
SYSCFG->EXTICR[3] directly in the debugger's watch window. Bits [7:4] of that register
hold the EXTI13 port selector — 0000 = PA, 0001 = PB, 0010 = PC. If it's not 0001, that's
your bug, and you'll find the last HAL_GPIO_Init() call touching pin 13 on some other
port somewhere in your init order.
Two ways to fix it: reorder your inits so the PB13 config genuinely runs last (fragile — breaks again the next time someone touches init order), or just move your wake button off pin 13 entirely onto a pin number nothing else in the project uses. That second option is the one I'd actually ship.
Can confirm this one, lost half a day to it on a G4 project. Worth adding: CubeMX's Pinout view will flag it if you look — right-click a pin that's fighting for a shared EXTI line and there's a small warning in the pin config panel about the line already being assigned, but it's easy to miss because it doesn't stop you generating code, and it doesn't show up as a build error, it's purely a GUI hint you have to go looking for.
If reassigning the pin isn't an option (locked into a board revision, say), CubeMX does let you pick a genuinely free line number instead — go through your Pinout view and check which EXTI lines 0–15 are actually free across every port you're using, not just the one you're currently wiring. On an L4 with a handful of buttons, an accelerometer interrupt, and a radio IRQ pin, it's surprisingly easy to run out of unique low-numbered lines and not notice until this exact symptom shows up.
Once you get the EXTI routing sorted and the wake actually fires, don't be surprised if
the next symptom is your UART or SPI transactions immediately after wake coming out
garbled, or a delay-based timing routine running way too fast or slow. Stop mode always
resumes on HSI regardless of what was clocking the core before entry — worth calling
SystemClock_Config() again as literally the first thing in your wake-up handling, before
touching any peripheral whose timing depends on the system clock. Covered in more detail
in the STM32 low-power modes writeup
if you haven't hit that one yet — you will the moment the EXTI fix above starts working.
That was it exactly. Had HAL_GPIO_Init() for PC13 sitting in a debug helper module I
added weeks ago for a "hold button at boot to force DFU" feature, completely forgot it
was still in there and it runs after the main GPIO init. Watched SYSCFG->EXTICR[3]
like you said — sure enough, 0010 instead of 0001.
Moved my wake button to PA0 instead since nothing else in the project touches it, works
first try now. Also added the SystemClock_Config() call in the wake handler before I
even got to test it, so thanks for the heads up on that one too — would've been a fun
second bug to chase.
Related Discussions
STM32 GPIO interrupt configured but ISR never fires — what am I missing?
Trying to use a button on PA0 to trigger an interrupt on an STM32F411 Nucleo board. Using HAL, generated the init code with CubeMX. The GPIO
STM32H743 HAL_UART_Receive_DMA fires error callback immediately — TEIF1 set, RxCplt never fires
Upgrading a project from STM32F4 to STM32H743. UART DMA receive worked on the F4 without any issues — standard CubeMX setup, call HAL_UART_R
STM32F401 UART printing garbage after switching to 84 MHz PLL — same 115200 baud in CubeMX and PuTTY
Got a WeAct Black Pill (STM32F401CCU6) project that's been running happily on the default HSI clock at 16 MHz. Using USART1 on PA9/PA10 thro
STM32 USB not detected by Windows after jumping to bootloader mode
Working on a custom STM32F411 board, trying to jump into the built-in USB DFU bootloader from application code instead of holding BOOT0 on p