stm32-rs / stm32f3xx-hal

A Rust embedded-hal HAL for all MCUs in the STM32 F3 family
https://crates.io/crates/stm32f3xx-hal
Apache License 2.0
163 stars 67 forks source link

Low-power modes #108

Open David-OConnor opened 4 years ago

David-OConnor commented 4 years ago

The STM32f3xx supports 3 low-power modes: Sleep, Stop, and Standby, in order of high-low power. How can I activate these? Searching the docs for these terms doesn't produce results. Do we need to manually modify registers? Thank you.

Something like this?

stm32::pwr::csr.modify(|_, w| w.sbf().set_bit(1));
strom-und-spiele commented 4 years ago

Disclaimer: I never set a chip to low-power mode with this crate.

However I feel like this is a task rather specific to your needs and use of the chip that it's not a feature I would expect in an all-purpose HAL.

Checking the Referencemanual (RM) for the chip I'm using (stm32f303vc), I can tell you that your approach wont work on my chip b.c. the sbf bit in said register is read only. The RM reads:

SBF: Standby flag This bit is set by hardware and cleared only by a POR/PDR (power on reset/power down reset) or by setting the CSBF bit in the Power control register (PWR_CR) 0: Device has not been in Standby mode 1: Device has been in Standby mode Bit 0 WUF: Wakeup flag

searching for SBF in the RM further gives me the information how it is set. However this might be different on your chip so enjoy reading the fine manual and come back with questions ;)

David-OConnor commented 4 years ago

Thank you. After digging more in the Datasheet, as you allude to. (I think I'm using a very similar chip), I've come up with this WIP code. Not quite right, but I think it's getting there. Still need to figure out how to set up WFI/WFE, and add to all three. Uses a mix of cortex_m, and this crate's features.

/// Enter `Sleep now` mode: the lightest of the 3 low-power states avail on the
/// STM32f3.
/// TO exit: Interrupt. Refer to Table 82.
fn sleep_now(scb: &mut cortex_m::peripheral::SCB) {
    // WFI (Wait for Interrupt) or WFE (Wait for Event) while:

    // SLEEPDEEP = 0 and SLEEPONEXIT = 0
    scb.clear_sleepdeep();
    scb.clear_sleeponexit();
}

/// Enter `Stop` mode: the middle of the 3 low-power states avail on the
/// STM32f3.
/// To exit:  Any EXTI Line configured in Interrupt mode (the corresponding EXTI
/// Interrupt vector must be enabled in the NVIC). Refer to Table 82.
fn stop(scb: &mut cortex_m::peripheral::SCB) {
    //WFI (Wait for Interrupt) or WFE (Wait for Event) while:

    // Set SLEEPDEEP bit in ARM® Cortex®-M4 System Control register
    scb.set_sleepdeep();

    // Clear PDDS bit in Power Control register (PWR_CR)
tm32::pwr::cr::PDDS_W.clear_bit();  // todo not quite right. Right struct I think, but need to access it, not call the struct directly.

    // Select the voltage regulator mode by configuring LPDS bit in PWR_CR
//    stm32::pwr::cr::LPDS_W.modify(|r, w| w.set_bit(1));  todo: not quite right
}

/// Enter `Standby` mode: the lowest-power of the 3 low-power states avail on the
/// STM32f3.
/// To exit: WKUP pin rising edge, RTC alarm event’s rising edge, external Reset in
/// NRST pin, IWDG Reset.
fn standby(scb: &mut cortex_m::peripheral::SCB) {
    // WFI (Wait for Interrupt) or WFE (Wait for Event) while:

    // Set SLEEPDEEP bit in ARM® Cortex®-M4 System Control register
    scb.set_sleepdeep();

    // Set PDDS bit in Power Control register (PWR_CR)
    stm32::pwr::cr::PDDS_W.set_bit(); // todo not quite right. see note above

    // Clear WUF bit in Power Control/Status register (PWR_CSR)

    stm32::pwr::csr::WUF_R.clear_bit();  // todo: Only read bit seems to be avail, but need to write
}

Do you think it's too specific? It seems like a general feature used by the family this HAL crate is for. Ie the info in the comments above comes from the family's datasheet. I think the way to handle this is by adding an example. The comments show the known problems, in addition to not being sure how to set up the WFI/WFE they all need.

strom-und-spiele commented 4 years ago

You're right about the implementation beeing general. What I wanted to say is that using it requires you to understand all the implications the different modes have as they change the behaviour of your device and might interfere with what you want the chip to do. E.g. Right now the crate does not provide a possibility to set the stop bit duration on UART. If I was implementing something to improve the crate I'd go for something more widely used like this. However if you feel like using and even implementing it for the crate that would be great.

Its seems you're struggeling with the same issues I had when I first came accross the docs and the way to use all of this. I hope those suggestions help you to get on the right track:

The cortex-m docs suggest you to get an instance of the peripheral by calling let mut peripherals = Peripherals::take().unwrap(); The same holds for Peripherals in this crate. This should be your starting point. Don't try to use pointers or structs directly as this messes up the safety idea created by this take functionality. If you know what you're doing, use steal rather than trying to work your way around it. Up till now I'd rather restructure my code than use steal.

I'll try to walk you through how I'd approach the last TODO stm32::pwr::csr::WUF_R.clear_bit(); // todo: Only read bit seems to be avail, but need to write

Find out, what to write I open up my Reference Manual and searched for it. I found:

WUF: Wakeup flag This bit is set by hardware and cleared by a system reset or by setting the CWUF bit in the Power control register (PWR_CR)

The last one is a link to the PWR_CR register where we can find:

CWUF: Clear wakeup flag. This bit is always read as 0. 0: No effect 1: Clear the WUF Wakeup Flag after 2 System clock cycles. (write) Bit 1 PDDS: Power down deepsleep.

Nice. So what next?

I use the search function in the crate with CWUF and find stm32f3xx_hal::stm32::pwr::cr::CWUF_W | Write proxy for field CWUF so we're set.

Find out, how to write The Doc tells's me I'm at the right spot. So how do I get there in the code? In the heading, ("Struct stm32f3xx_hal::stm32::pwr::cr::CWUF_W") each part leading to this struct is clickable. I click on cr which brings me to the doc of the cr module controlling the cr register.

I notice below "Type Definitions" that there exists an Writer for register CR for this module. As I know the struct of this crate, this tells me I can write something like cr.modify(|_, w| w.cwuf().set_bit() ) (see #36 and the docs, if you wonder why I'm not using write, see the RM why I'm setting the bit, see the CWUF struct for all the methods I can call. )

So when using it in a program, I'd write something link this (not tested)

// with use stm32f3xx_hal::stm32;
    let dp = stm32::Peripherals::take().unwrap();
    let pwr = dp.PWR;
    pwr.cr.modify(|_, w| w.cwuf().set_bit() );

Hope that clears up some things. I still need to learn more Rust and more about this crate, so if it's confusing dont hesitate to ask and we can try to figure it out together ;)

David-OConnor commented 4 years ago

Thank you very much - that's exactly what I need to get started (And hopefully get an example of all this into this repo eventually) - both in strategy, and specifics. Looks like I passed in the cortex periph correctly, but missed the bit about passing in the stm32 periph using the same approach, among other misunderstandings.

Two lingering questions - Could you clarify how you found cwuf, (And by proxy, I imagine there's an equiv lpds, and pdds? :

As I know the struct of this crate, this tells me I can write something like
cr.modify(|_, w| w.cwuf().set_bit() )

Do you just pattern-match the examples you linked, knowing there's a CWUF_W?

And, the Datasheet indicates cwuf is in the csr, vice cr, but as you've found and I can confirm, the correct code uses cr.

This correction to the above appears to work, in that it at least compiles:

/// Enter `Sleep now` mode: the lightest of the 3 low-power states avail on the
/// STM32f3.
/// TO exit: Interrupt. Refer to Table 82.
fn sleep_now(scb: &mut cortex_m::peripheral::SCB) {
    // WFI (Wait for Interrupt) or WFE (Wait for Event) while:

    // SLEEPDEEP = 0 and SLEEPONEXIT = 0
    scb.clear_sleepdeep();
    scb.clear_sleeponexit();
}

/// Enter `Stop` mode: the middle of the 3 low-power states avail on the
/// STM32f3.
/// To exit:  Any EXTI Line configured in Interrupt mode (the corresponding EXTI
/// Interrupt vector must be enabled in the NVIC). Refer to Table 82.
fn stop(scb: &mut cortex_m::peripheral::SCB, pwr: &mut stm32::PWR) {
    //WFI (Wait for Interrupt) or WFE (Wait for Event) while:

    // Set SLEEPDEEP bit in ARM® Cortex®-M4 System Control register
    scb.set_sleepdeep();

    // Clear PDDS bit in Power Control register (PWR_CR)
    pwr.cr.modify(|_, w| w.pdds().clear_bit() );

    // Select the voltage regulator mode by configuring LPDS bit in PWR_CR
    pwr.cr.modify(|_, w| w.lpds().set_bit() );  // todo: Set or clear?
}

/// Enter `Standby` mode: the lowest-power of the 3 low-power states avail on the
/// STM32f3.
/// To exit: WKUP pin rising edge, RTC alarm event’s rising edge, external Reset in
/// NRST pin, IWDG Reset.
fn standby(scb: &mut cortex_m::peripheral::SCB, pwr: &mut stm32::PWR) {
    // WFI (Wait for Interrupt) or WFE (Wait for Event) while:

    // Set SLEEPDEEP bit in ARM® Cortex®-M4 System Control register
    scb.set_sleepdeep();

    // Set PDDS bit in Power Control register (PWR_CR)
    pwr.cr.modify(|_, w| w.pdds().set_bit() );

    // Clear WUF bit in Power Control/Status register (PWR_CSR)
    pwr.cr.modify(|_, w| w.cwuf().clear_bit() );
}