Sending stm32f1 to deep sleep with rust
In the video above, bluepill board runs code (blinks) and goes into standby mode, to be woken up by a rising edge on
A0
pin, only to blink and go into sleep again.
About a week ago I tried to enable standby mode on by bluepill board, but couldn’t find an explanation of how it can be done with rust. After spending some time reading the docs, manuals and nagging people on IRC, I decided to share my findings to hopefully save someone else from having to dive deeper into documentation than is probably necessary. So here we go.
While STM32F1 reference manual1 mentions multiple low power modes (Sleep mode, Stop mode, and Standby mode), the most power efficient one is Standby mode.
Page 76 of reference manual contains a detailed description of all the features this mode
provides, but in my simple example I will implement a minimal case of running some code,
initializing sleep mode, and waiting for a rising edge on WKUP
pin to turn the
microcontroller back on.
As per instructions, we must:
- Set SLEEPDEEP in System Control register
- Set PDDS bit in Power Control register (PWR_CR)
- Clear WUF bit in Power Control/Status register (PWR_CSR)
- Idle with WFI or WFE
The first part is pretty straightforward. cortex-m
2 crate provides us with
a helpful function that sets the right registers:
core_peripherals.SCB.set_sleepdeep();
Setting power control registers is basically the way for you to decide which low power mode you want to enable:
device_peripherals.PWR.cr.modify(|_r, w| {
// Stop mode is 0, standby mode is 1
w.pdds().set_bit()
// Voltage regulators to low power mode
// Only for stop mode
// .lpds().set_bit()
});
Clearing WUF
bit is again very straightforward.
let standby_flag = dp.PWR.csr.read().sbf().bit();
if standby_flag {
// Clear standby flag
dp.PWR.cr.modify(|_, w| {
w.csbf().clear_bit()
});
// Clear Wakeup flag
dp.PWR.cr.modify(|_, w| w.cwuf().set_bit());
}
One last thing that docs do not tell us explicitly we need to do, but which is extremely
important if we are going to use WKUP
pin is to set EWUP
bit in PWR_CSR
register.
But for pin to work we also need to first set PWREN
bit, which enables power interface clock.
What we’d like to do ideally would look something like this:
rcc.apb1.enr().modify(|_r, w| {
w.pwren().set_bit()
});
Unfortunately, it seems stm32f1xx-hal
does not allow apb1
access from outside the crate.
We can call constrain
3 on rcc.bkp
, which does set PWREN
bit, but also
does some other things, that may or may not be desirable, depending on what you are doing.
It doesn’t affect our usage scenario in any way, but it is definitely something to keep in mind.
rcc.bkp.constrain(dp.BKP, &mut rcc.apb1, &mut device_peripherals.PWR);
With power interface clock enabled, we enable WKUP
pin and we are good to go.
device_peripherals.PWR.csr.modify(|_, w| w.ewup().set_bit());
Now whenever our microcontroller encounters wfi
or wfe
instruction, it will go into
Standby mode and wait to be woken up again.
loop {
asm::wfi();
}
WKUP
pin on STM32F1 MCUs is A0
. When MCU is in deep sleep and has WKUP
pin enabled,
rising edge on A0
pin will wake it up. As standby mode disables pretty much all peripherals,
RAM contents are lost as well, and what happens on wakeup is basically a system reset, as if
RESET
pin was triggered, except for those wakeup and standby flags we are clearing,
which are the only indicators that MCU was woken up and not simple reset.
Full code can be found here: https://github.com/skammer/stm32f1xx-sleep-example
-
https://www.st.com/content/ccc/resource/technical/document/reference_manual/59/b9/ba/7f/11/af/43/d5/CD00171190.pdf/files/CD00171190.pdf/jcr:content/translations/en.CD00171190.pdf ↩︎
-
https://github.com/rust-embedded/cortex-m/blob/master/src/peripheral/scb.rs#L585 cortex-m/src/peripheral/scb.rs ↩︎
-
https://github.com/stm32-rs/stm32f1xx-hal/blob/master/src/rcc.rs#L367 stm32f1xx-hal/src/rcc.rs ↩︎