vasiliev.me Posts About Contact

Sending stm32f1 to deep sleep with rust

#rust #deep sleep #electronics

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:

The first part is pretty straightforward. cortex-m2 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 constrain3 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


  1. 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 ↩︎

  2. https://github.com/rust-embedded/cortex-m/blob/master/src/peripheral/scb.rs#L585 cortex-m/src/peripheral/scb.rs ↩︎

  3. https://github.com/stm32-rs/stm32f1xx-hal/blob/master/src/rcc.rs#L367 stm32f1xx-hal/src/rcc.rs ↩︎