stm32_hal2/
low_power.rs

1//! This module contains code used to place the MCU in low power modes.
2//! Reference section 5.3.3: `Low power modes` of the L4 Reference Manual.
3
4use cfg_if::cfg_if;
5use cortex_m::{Peripherals, asm::wfi};
6
7#[cfg(any(feature = "l4", feature = "l5"))]
8use crate::clocks::{Clocks, MsiRange};
9#[cfg(any(feature = "l4", feature = "l5"))]
10use crate::pac;
11#[cfg(not(feature = "h7"))]
12use crate::pac::PWR;
13use crate::{
14    error::{Error, Result},
15    util::bounded_loop,
16};
17
18// See L4 Reference Manual section 5.3.6. The values correspond to the PWR_CR1 LPMS bits.
19// todo PWR_CR1, LPMS field.
20#[derive(Clone, Copy)]
21#[repr(u8)]
22pub enum StopMode {
23    Zero = 0,
24    One = 1,
25    #[cfg(not(feature = "g4"))]
26    Two = 2,
27}
28
29/// L4 RM, table 24
30/// This assumes you're using MSI as the clock source, and changes speed by lowering the MSI speed.
31/// You must select an MSI speed of 2Mhz or lower. Note that you may need to adjust peripheral
32/// implementations that rely on system clock or APB speed.
33#[cfg(any(feature = "l4", feature = "l5"))]
34pub fn low_power_run(clocks: &mut Clocks, speed: MsiRange) -> Result<()> {
35    let rcc = unsafe { &(*pac::RCC::ptr()) };
36    let pwr = unsafe { &(*PWR::ptr()) };
37
38    // Decrease the system clock frequency below 2 MHz
39    if speed as u8 > MsiRange::R2M as u8 {
40        panic!("Selected Msi speed must be 2Mhz or lower to enter use low power run.")
41    }
42    clocks.change_msi_speed(speed)?;
43    // LPR = 1
44    pwr.cr1().modify(|_, w| w.lpr().bit(true));
45
46    Ok(())
47}
48
49/// L4 RM, table 24
50/// Return to normal run mode from low-power run. Requires you to increase the clock speed
51/// manually after running this.
52#[cfg(any(feature = "l4", feature = "l5"))]
53pub fn return_from_low_power_run() -> Result<()> {
54    let pwr = unsafe { &(*PWR::ptr()) };
55
56    // LPR = 0
57    pwr.cr1().modify(|_, w| w.lpr().clear_bit());
58
59    // Wait until REGLPF = 0
60    bounded_loop!(
61        pwr.sr2().read().reglpf().bit_is_set(),
62        Error::RegisterUnchanged
63    );
64
65    // Increase the system clock frequency
66    Ok(())
67}
68
69/// Place the system in sleep now mode. To enter `low-power sleep now`, enter low power mode
70/// (eg `low_power_mode()`) before running this. RM, table 25 and 26
71#[cfg(not(feature = "h7"))]
72pub fn sleep_now() {
73    sleep();
74}
75
76/// F303 RM, table 19.
77pub fn sleep_on_exit() {
78    let mut scb = unsafe { Peripherals::steal().SCB };
79
80    // WFI (Wait for Interrupt) (eg `cortext_m::asm::wfi()) or WFE (Wait for Event) while:
81
82    // SLEEPDEEP = 0 and SLEEPONEXIT = 1
83    scb.clear_sleepdeep();
84    // Sleep-on-exit: if the SLEEPONEXIT bit is set, the MCU enters Sleep mode as soon
85    // as it exits the lowest priority ISR.
86    scb.set_sleeponexit();
87
88    wfi();
89}
90
91cfg_if! {
92    if #[cfg(any(feature = "f3", feature = "f4"))] {
93        /// Enter `Stop` mode: the middle of the 3 low-power states avail on the
94        /// STM32f3.
95        /// To exit:  Any EXTI Line configured in Interrupt mode (the corresponding EXTI
96        /// Interrupt vector must be enabled in the NVIC). Refer to Table 82.
97        /// F303 RM, table 20. F4 RM, Table 27. H742 RM, Table 38. (CSrtop on H7).
98        /// Run `Clocks::reselect_input()` after to re-enable PLL etc after exiting this mode.
99        pub fn stop() {
100            let mut scb = unsafe { Peripherals::steal().SCB };
101            let pwr = unsafe { &(*PWR::ptr()) };
102
103            // todo: On some F4 variants, you may need to `select voltage regulator
104            // todo mode by configuring LPDS, MRUDS, LPUDS and UDEN bits in PWR_cr().`
105            //WFI (Wait for Interrupt) or WFE (Wait for Event) while:
106
107            // Set SLEEPDEEP bit in ARM® Cortex®-M4 System Control register
108            scb.set_sleepdeep();
109
110            // Clear PDDS bit in Power Control register (PWR_CR)
111            // This bit is set and cleared by software. It works together with the LPDS bit.
112            // 0: Enter Stop mode when the CPU enters Deepsleep. The regulator status
113            // depends on the LPDS bit.
114            // 1: Enter Stop mode when the CPU enters Deepsleep.
115            pwr.cr().modify(|_, w| {
116                w.pdds().clear_bit();
117                // Select the voltage regulator mode by configuring LPDS bit in PWR_CR
118                // This bit is set and cleared by software. It works together with the PDDS bit.
119                // 0: Voltage regulator on during Stop mode
120                // 1: Voltage regulator in low-power mode during Stop mode
121                // pwr.cr().modify(|_, w| w.pdds().clear_bit());
122                 w.lpds().bit(true)
123            });
124
125
126            wfi();
127        }
128
129        /// Enter `Standby` mode.
130        /// To exit: WKUP pin rising edge, RTC alarm event’s rising edge, external Reset in
131        /// NRST pin, IWDG Reset.
132        /// F303 RM, table 21.
133        /// Run `Clocks::reselect_input()` after to re-enable PLL etc after exiting this mode.
134        pub fn standby() {
135            let mut scb = unsafe { Peripherals::steal().SCB };
136            let pwr = unsafe { &(*PWR::ptr()) };
137
138            // WFI (Wait for Interrupt) or WFE (Wait for Event) while:
139
140            // Set SLEEPDEEP bit in ARM® Cortex®-M4 System Control register
141            scb.set_sleepdeep();
142
143            // Set PDDS bit in Power Control register (PWR_CR)
144            // This bit is set and cleared by software. It works together with the LPDS bit.
145            // 0: Enter Stop mode when the CPU enters Deepsleep. The regulator status
146            // depends on the LPDS bit.
147            // 1: Enter Standby mode when the CPU enters Deepsleep.
148            pwr.cr().modify(|_, w| {
149                w.pdds().bit(true);
150                // Clear WUF bit in Power Control/Status register (PWR_CSR) (Must do this by setting CWUF bit in
151                // PWR_cr().)
152                w.cwuf().bit(true)
153            });
154
155            wfi();
156        }
157    } else if #[cfg(any(feature = "l4", feature = "l5", feature = "g0", feature = "g4"))] {
158        /// Enter Stop 0, Stop 1, or Stop 2 modes. L4 Reference manual, section 5.3.6. Tables 27, 28, and 29.
159        /// G0 RMs, tables 30, 31, 32.
160        /// G4 Table 45, 47, 47.
161        /// Run `Clocks::reselect_input()` after to re-enable PLL etc after exiting this mode.
162        pub fn stop(mode: StopMode) {
163            let mut scb = unsafe { Peripherals::steal().SCB };
164            let pwr = unsafe { &(*PWR::ptr()) };
165
166            // WFI (Wait for Interrupt) or WFE (Wait for Event) while:
167            // – SLEEPDEEP bit is set in Cortex®-M4 System Control register
168            scb.set_sleepdeep();
169            // – No interrupt (for WFI) or event (for WFE) is pending
170            // – LPMS = (according to mode) in PWR_CR1
171            pwr.cr1().modify(|_, w| unsafe { w.lpms().bits(mode as u8) });
172
173            // Or, unimplemented:
174            // On Return from ISR while:
175            // – SLEEPDEEP bit is set in Cortex®-M4 System Control register
176            // – SLEEPONEXIT = 1
177            // – No interrupt is pending
178            // – LPMS = “000” in PWR_CR1
179
180            wfi();
181        }
182
183
184        /// Enter `Standby` mode. See L44 RM table 28. G4 table 47.
185        /// Run `Clocks::reselect_input()` after to re-enable PLL etc after exiting this mode.
186        pub fn standby() {
187            let mut scb = unsafe { Peripherals::steal().SCB };
188            let pwr = unsafe { &(*PWR::ptr()) };
189
190            // – SLEEPDEEP bit is set in Cortex®-M4 System Control register
191            scb.set_sleepdeep();
192
193            // – No interrupt (for WFI) or event (for WFE) is pending
194
195            // – LPMS = “011” in PWR_CR1
196            pwr.cr1().modify(|_, w| unsafe { w.lpms().bits(0b011) });
197
198            // – WUFx bits are cleared in power status register 1 (PWR_SR1)
199            // (Clear by setting cwfuf bits in `pwr_scr`.)
200            cfg_if! {
201                if #[cfg(all(feature = "l4", not(any(feature = "l412", feature = "l4x2"))))] {
202                    pwr.scr().write(|w| {
203                        w.wuf1().bit(true);
204                        w.wuf2().bit(true);
205                        w.wuf3().bit(true);
206                        w.wuf4().bit(true);
207                        w.wuf5().bit(true)
208                    });
209                } else if #[cfg(feature = "g0")] {
210                    pwr.scr().write(|w| {
211                        w.cwuf1().bit(true);
212                        w.cwuf2().bit(true);
213                        // w.cwuf3().bit(true); // todo: PAC ommission?
214                        w.cwuf4().bit(true);
215                        w.cwuf5().bit(true);
216                        w.cwuf6().bit(true)
217                    });
218                } else {
219                    pwr.scr().write(|w| {
220                        w.cwuf1().bit(true);
221                        w.cwuf2().bit(true);
222                        w.cwuf3().bit(true);
223                        w.cwuf4().bit(true);
224                        w.cwuf5().bit(true)
225                    });
226                }
227            }
228
229            // todo: `The RTC flag corresponding to the chosen wakeup source (RTC Alarm
230            // todo: A, RTC Alarm B, RTC wakeup, tamper or timestamp flags) is cleared`.
231
232            // Or, unimplemented:
233            // On return from ISR while:
234            // – SLEEPDEEP bit is set in Cortex®-M4 System Control register
235            // – SLEEPONEXIT = 1
236            // – No interrupt is pending
237            // – LPMS = “011” in PWR_CR1 and
238            // – WUFx bits are cleared in power status register 1 (PWR_SR1)
239            // – The RTC flag corresponding to the chosen wakeup source (RTC Alarm
240            // A, RTC Alarm B, RTC wakeup, tamper or timestamp flags) is cleared
241
242            wfi();
243        }
244
245        /// Enter `Shutdown mode` mode: the lowest-power of the 3 low-power states avail. See
246        /// L4 Table 31. G4 table 48.
247        pub fn shutdown() {
248            let mut scb = unsafe { Peripherals::steal().SCB };
249            let pwr = unsafe { &(*PWR::ptr()) };
250
251            // – SLEEPDEEP bit is set in Cortex®-M4 System Control register
252            scb.set_sleepdeep();
253            // – No interrupt (for WFI) or event (for WFE) is pending
254            // – LPMS = “011” in PWR_CR1
255            pwr.cr1().modify(|_, w| unsafe { w.lpms().bits(0b100) });
256            // – WUFx bits are cleared in power status register 1 (PWR_SR1)
257            // (Clear by setting cwfuf bits in `pwr_scr`.)
258
259            // pwr.scr().write(|_, w| {
260            //     w.cwuf1().bit(true);
261            //     w.cwuf2().bit(true);
262            //     w.cwuf3().bit(true);
263            //     w.cwuf4().bit(true);
264            //     w.cwuf5().bit(true);
265            // })
266
267            // Or, unimplemented:
268            // On return from ISR while:
269            // – SLEEPDEEP bit is set in Cortex®-M4 System Control register
270            // – SLEEPONEXT = 1
271            // – No interrupt is pending
272            // – LPMS = “1XX” in PWR_CR1 and
273            // – WUFx bits are cleared in power status register 1 (PWR_SR1)
274            // – The RTC flag corresponding to the chosen wakeup source (RTC
275            // Alarm A, RTC Alarm B, RTC wakeup, tamper or timestamp flags) is
276            // cleared
277            wfi();
278        }
279    } else { // H7
280        /// The CSleep mode applies only to the CPU subsystem. In CSleep mode, the CPU clock is
281        /// stopped. The CPU subsystem peripheral clocks operate according to the values of
282        /// PERxLPEN bits in RCC_C1_xxxxENR or RCC_Dnxxxxenr(). See H743 RM, Table 37.
283        pub fn csleep() {
284            sleep();
285        }
286
287        ///  Stops clocks on the CPU subsystem. H742 RM, Table 38.
288        pub fn cstop() {
289            let mut scb = unsafe { Peripherals::steal().SCB };
290
291            // WFI (Wait for Interrupt) or WFE (Wait for Event) while:
292
293            // Set SLEEPDEEP bit in ARM® Cortex®-M4 System Control register
294            scb.set_sleepdeep();
295
296            // – CPU NVIC interrupts and events cleared.
297            // – All CPU EXTI Wakeup sources are cleared.
298
299            wfi();
300        }
301
302        // /// Stops clocks on the D1 and D2 domain. H742 RM, Table 40.
303        // pub fn dstop(scb: &mut SCB, pwr: &mut PWR) {
304        //
305        //     // -The domain CPU subsystem enters CStop.
306        //     // (Don't call self.cstop, since that handles WFI and reselecting clocks as well).
307        //     scb.set_sleepdeep();
308        //
309        //
310        //     // todo?
311        //     // – The CPU subsystem has an allocated peripheral in the D2 domain and
312        //     // enters CStop.
313        //     // – The CPU subsystem deallocated its last peripheral in the D2 domain.
314        //     // – The PDDS_Dn bit for the domain selects Stop mode.
315        //
316        //     wfi();
317        // }
318
319        // /// Enter `Standby` mode: the lowest-power of the 3 low-power states avail on the
320        // /// STM32f3.
321        // /// To exit: WKUP pin rising edge, RTC alarm event’s rising edge, external Reset in
322        // /// NRST pin, IWDG Reset.
323        // /// RM, table 21.
324        // /// Run `Clocks::reselect_input()` after to re-enable PLL etc after exiting this mode.
325        // pub fn standby() {
326        //     // WFI (Wait for Interrupt) or WFE (Wait for Event) while:
327        //
328        //     // Set SLEEPDEEP bit in ARM® Cortex®-M4 System Control register
329        //     scb.set_sleepdeep();
330        //
331        //     // Set PDDS bit in Power Control register (PWR_CR)
332        //     // This bit is set and cleared by software. It works together with the LPDS bit.
333        //     // 0: Enter Stop mode when the CPU enters Deepsleep. The regulator status
334        //     // depends on the LPDS bit.
335        //     // 1: Enter Standby mode when the CPU enters Deepsleep.
336        //     pwr.cr().modify(|_, w| {
337        //         w.pdds().bit(true);
338        //         // Clear WUF bit in Power Control/Status register (PWR_CSR) (Must do this by setting CWUF bit in
339        //         // PWR_cr().)
340        //         w.cwuf().bit(true)
341        //     });
342
343        //     wfi();
344        // }
345    }
346}
347
348/// This function is used by both `sleep_now` (non-H7), and `csleep` (H7), so that the names
349/// can correctly reflect functionality.
350fn sleep() {
351    // todo: This function is identical to `sleep_now`, but we'd like to keep the separate
352    // todo name for H7.
353
354    let mut scb = unsafe { Peripherals::steal().SCB };
355
356    // WFI (Wait for Interrupt) (eg `cortext_m::asm::wfi()) or WFE (Wait for Event) while:
357    // – SLEEPDEEP = 0
358    // – No interrupt (for WFI) or event (for WFE) is pending
359    scb.clear_sleepdeep();
360
361    // Or, unimplemented:
362    // On return from ISR while:
363    // // SLEEPDEEP = 0 and SLEEPONEXIT = 1
364    // scb.clear_sleepdeep();
365    // scb.set_sleeponexit();
366
367    // Sleep-now: if the SLEEPONEXIT bit is cleared, the MCU enters Sleep mode as soon
368    // as WFI or WFE instruction is executed.
369    scb.clear_sleeponexit();
370
371    wfi();
372}