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}