Throughout the process of working on a side project, I got interested in putting an ARM microcontroller into the deepest sleep for the best power savings. I believe this is how things like your TV remote can last for years on two AAs. I'm using rust right now for an embedded electronic project with the blue pill development board, and I got deep sleep (standby) working, so I trimmed the code and will paste it here.
An interesting thing about standby is that upon waking, it's as if the board was unplugged and just received power. No variables are instantiated and the code starts from the beginning of main()
. Keep this in mind if you intended on keeping state throughout deep sleep.
In standby mode only a few things can wake up the microcontroller. One of those is a 'real time clock' peripheral which can keep running while the microcontroller is sleeping. An example use case is a solar-powered IoT device which has to be careful with power consumption and only needs to report some data infrequently like every minute or once a day. It doesn't make sense for the microcontroller to be busy-spinning if it's not doing anything interesting during the vast majority of time.
This code will blink a light for a quarter second every time it wakes up. Then it sleeps for 10 seconds.
Here is the code that works on the blue pill (STM32F103).
#![deny(unsafe_code)]
#![no_std]
#![no_main]
use cortex_m::asm::wfi;
use cortex_m_rt::entry;
use cortex_m_semihosting::hprintln;
use embedded_hal::{digital::v2::OutputPin, prelude::*};
use panic_halt as _;
use stm32f1xx_hal::{delay::Delay, pac, prelude::*, rtc::Rtc};
#[entry]
fn main() -> ! {
// Get access to the core peripherals from the cortex-m crate
let cp = cortex_m::Peripherals::take().unwrap();
// Get access to the device specific peripherals from the peripheral access crate
let dp = pac::Peripherals::take().unwrap();
// Take ownership over the raw flash and rcc devices and convert them into the corresponding
// HAL structs
let mut flash = dp.FLASH.constrain();
let mut rcc = dp.RCC.constrain();
// Freeze the configuration of all the clocks in the system and store the frozen frequencies in
// `clocks`
let clocks = rcc.cfgr.freeze(&mut flash.acr);
// Set up internal RTC alarm to trigger
let mut pwr = dp.PWR;
let mut backup_domain = rcc.bkp.constrain(dp.BKP, &mut rcc.apb1, &mut pwr);
let mut rtc = Rtc::rtc(dp.RTC, &mut backup_domain);
rtc.set_time(0);
rtc.set_alarm(10); // 10 seconds
rtc.listen_alarm();
// Set deep sleep when wfi
let mut scb = cp.SCB;
scb.set_sleepdeep();
pwr.cr.write(|w| {
w.pdds().standby_mode();
w.cwuf().set_bit()
});
// Acquire the GPIOC peripheral
let mut gpioc = dp.GPIOC.split(&mut rcc.apb2);
// Configure gpio C pin 13 as a push-pull output. The `crh` register is passed to the function
// in order to configure the port. For pins 0-7, crl should be passed instead.
let mut led = gpioc.pc13.into_push_pull_output(&mut gpioc.crh);
let mut delay = Delay::new(cp.SYST, clocks);
// Turn on LED
led.set_low().unwrap();
delay.delay_ms(250u16);
// Turn off LED
led.set_high().unwrap();
hprintln!("Entering deep sleep mode!").unwrap();
loop {
wfi();
// Code never gets here, because it essentially shuts down starts over upon waking
}
}
That's all I got!