1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
//! Interface to the Power control (PWR) peripheral
//!
//! See STM32L0x2 reference manual, chapter 6.

use cortex_m::{asm, peripheral::SCB};

use crate::{
    pac,
    rcc::{ClockSrc, Clocks, Enable, PLLSource, Rcc},
};

/// Entry point to the PWR API
pub struct PWR(pac::PWR);

impl PWR {
    /// Create an instance of the PWR API
    pub fn new(pwr: pac::PWR, rcc: &mut Rcc) -> Self {
        // Peripheral is not being reset here. First, there's no type state here
        // that would require any specific configuration. Second, there are
        // specific requirements that make resetting the peripheral complicated.
        // See STM32L0x2 reference manual, section 6.4.1 (VOS field).

        // Enable peripheral clock
        pac::PWR::enable(rcc);

        // Disable backup write protection. This is required to access various
        // register of various peripherals, so don't remove this unless you know
        // what you're doing and also change the affected peripheral APIs
        // accordingly.
        pwr.cr.modify(|_, w| w.dbp().set_bit());

        Self(pwr)
    }

    /// Switch voltage range of internal regulator
    ///
    /// Please note that switching Vcore has consequences, so please make sure
    /// you know what you're doing. See STM32L0x2 reference manual, sections
    /// 6.1.3 and following.
    pub fn switch_vcore_range(&mut self, range: VcoreRange) {
        // The STM32L0x2 reference manual, section 6.1.5 describes the procedure
        // being followed here.

        while self.0.csr.read().vosf().bit_is_set() {}

        // Safe, as `VcoreRange` only provides valid bit patterns.
        self.0
            .cr
            .modify(|_, w| unsafe { w.vos().bits(range as u8) });

        while self.0.csr.read().vosf().bit_is_set() {}
    }

    /// Returns currently configured internal regulator voltage range
    pub fn get_vcore_range(&mut self) -> VcoreRange {
        let vos = self.0.cr.read().vos().bits();

        // Shouldn't panic, as reading the field from the register should always
        // return a valid value.
        VcoreRange::from_bits(vos)
    }

    /// Enters low-power run mode
    ///
    /// Please note that there are some restrictions placed on low-power run
    /// mode. Please refer to the STM32L0x2 reference manual, section 6.3.4 for
    /// more information.
    ///
    /// # Panics
    ///
    /// To enter low-power run mode, the system clock frequency should not
    /// exceed the MSI frequency range 1 (131.072 kHz). This method will panic,
    /// if that is the case.
    pub fn enter_low_power_run_mode(&mut self, clocks: Clocks) {
        // This follows the procedure laid out in the STM32L0x2 reference
        // manual, section 6.3.4.

        // Panic, if system clock frequency is outside of allowed range. See
        // STM32L0x1/STM32L0x2/STM32L0x3 reference manuals, sections 6.3.4 and
        // 7.2.3.
        assert!(clocks.sys_clk().0 <= 131_072);

        self.switch_vcore_range(VcoreRange::Range2);

        // First set LPSDSR, then LPRUN, to go into low-power run mode. See
        // STM32L0x2 reference manual, section 6.4.1.
        self.set_lpsdsr();
        self.0.cr.modify(|_, w| w.lprun().set_bit());
    }

    /// Exit low-power run mode
    ///
    /// Please note that entering low-power run mode sets Vcore to range 2. This
    /// method will not switch Vcore again, so please make sure to restore the
    /// previous Vcore setting again, if you want to do so. See
    /// [`PWR::switch_vcore_range`]/[`PRW::get_vcore_range`] for more info.
    pub fn exit_low_power_run_mode(&mut self) {
        // First reset LPRUN, then LPSDSR. See STM32L0x2 reference manual,
        // section 6.4.1.
        self.0.cr.modify(|_, w| w.lprun().clear_bit());
        self.clear_lpsdsr();
    }

    /// Returns a struct that can be used to enter Sleep mode
    pub fn sleep_mode<'r>(&'r mut self, scb: &'r mut SCB) -> SleepMode<'r> {
        SleepMode { pwr: self, scb }
    }

    /// Returns a struct that can be used to enter low-power sleep mode
    ///
    /// # Panics
    ///
    /// To enter low-power sleep mode, the system clock frequency should not
    /// exceed the MSI frequency range 1 (131.072 kHz). This method will panic,
    /// if that is the case.
    pub fn low_power_sleep_mode<'r>(
        &'r mut self,
        scb: &'r mut SCB,
        rcc: &mut Rcc,
    ) -> LowPowerSleepMode<'r> {
        // Panic, if system clock frequency is outside of allowed range. See
        // STM32L0x1/STM32L0x2/STM32L0x3 reference manuals, sections 6.3.8 and
        // 7.2.3.
        assert!(rcc.clocks.sys_clk().0 <= 131_072);

        LowPowerSleepMode { pwr: self, scb }
    }

    /// Returns a struct that can be used to enter Stop mode
    pub fn stop_mode<'r>(
        &'r mut self,
        scb: &'r mut SCB,
        rcc: &'r mut Rcc,
        config: StopModeConfig,
    ) -> StopMode<'r> {
        StopMode {
            pwr: self,
            scb,
            rcc,
            config,
        }
    }

    /// Returns a struct that can be used to enter Standby mode
    pub fn standby_mode<'r>(&'r mut self, scb: &'r mut SCB) -> StandbyMode<'r> {
        StandbyMode { pwr: self, scb }
    }

    /// Private method to set LPSDSR
    fn set_lpsdsr(&mut self) {
        self.0.cr.modify(|_, w| w.lpsdsr().low_power_mode());
    }

    /// Private method to clear LPSDSR
    fn clear_lpsdsr(&mut self) {
        self.0.cr.modify(|_, w| w.lpsdsr().main_mode());
    }
}

/// Voltage range selection for internal voltage regulator
///
/// Used as an argument for [`PWR::switch_vcore_range`].
#[repr(u8)]
pub enum VcoreRange {
    /// Range 1 (1.8 V)
    Range1 = 0b01,

    /// Range 2 (1.5 V)
    Range2 = 0b10,

    /// Range 3 (1.2 V)
    Range3 = 0b11,
}

impl VcoreRange {
    /// Creates a `VcoreRange` instance from a bit pattern
    ///
    /// # Panics
    ///
    /// Panics, if an invalid value is passed. See STM32L0x2 reference manual,
    /// section 6.4.1 (documentation of VOS field) for valid values.
    pub fn from_bits(bits: u8) -> Self {
        match bits {
            0b01 => VcoreRange::Range1,
            0b10 => VcoreRange::Range2,
            0b11 => VcoreRange::Range3,
            bits => panic!("Bits don't represent valud Vcore range: {}", bits),
        }
    }
}

/// Implemented for all low-power modes
pub trait PowerMode {
    /// Enters the low-power mode
    fn enter(&mut self);
}

/// Sleep mode
///
/// You can get an instance of this struct by calling [`PWR::sleep_mode`].
///
/// The `PowerMode` implementation of this type will block until something wakes
/// the microcontroller up again. Please make sure to configure an interrupt, or
/// it could block forever.
///
/// Please note that entering Sleep mode may change the SCB configuration.
pub struct SleepMode<'r> {
    pwr: &'r mut PWR,
    scb: &'r mut SCB,
}

impl PowerMode for SleepMode<'_> {
    fn enter(&mut self) {
        self.pwr.clear_lpsdsr();
        self.scb.clear_sleepdeep();

        asm::dsb();
        asm::wfi();
    }
}

/// Low-power sleep mode
///
/// You can get an instance of this struct by calling
/// [`PWR::low_power_sleep_mode`].
///
/// The `PowerMode` implementation of this type will block until something wakes
/// the microcontroller up again. Please make sure to configure an interrupt, or
/// it could block forever.
///
/// Please note that entering low-power sleep mode may change the SCB
/// configuration.
pub struct LowPowerSleepMode<'r> {
    pwr: &'r mut PWR,
    scb: &'r mut SCB,
}

impl PowerMode for LowPowerSleepMode<'_> {
    fn enter(&mut self) {
        // Switch Vcore to range 2. This is required to enter low-power sleep
        // mode, according to the reference manual, section 6.3.8.
        let old_vcore = self.pwr.get_vcore_range();
        self.pwr.switch_vcore_range(VcoreRange::Range2);

        self.pwr.set_lpsdsr();
        self.scb.clear_sleepdeep();

        asm::dsb();
        asm::wfi();

        // Switch back to previous voltage range.
        self.pwr.switch_vcore_range(old_vcore);
    }
}

/// Stop mode
///
/// You can get an instance of this struct by calling [`PWR::stop_mode`].
///
/// The `PowerMode` implementation of this type will block until something wakes
/// the microcontroller up again. Please make sure to configure an interrupt, or
/// it could block forever.
///
/// This method will always disable the internal voltage regulator during Stop
/// mode.
///
/// Please note that entering Stop mode may change the SCB configuration.
///
/// # Panics
///
/// Panics, if the external clock is selected as clock source. In principle, it
/// is possible to enter Stop mode with the external clock enabled, although
/// that might require special handling. This is explained in the STM32L0x2
/// Reference Manual, section 6.3.9.
pub struct StopMode<'r> {
    pwr: &'r mut PWR,
    scb: &'r mut SCB,
    rcc: &'r mut Rcc,
    config: StopModeConfig,
}

impl PowerMode for StopMode<'_> {
    fn enter(&mut self) {
        self.scb.set_sleepdeep();

        // Restore current clock source after waking up from Stop mode.
        self.rcc
            .rb
            .cfgr
            .modify(|_, w| match self.rcc.clocks.source() {
                // Use MSI as clock source after wake-up
                ClockSrc::MSI(_) => w.stopwuck().clear_bit(),
                // Use HSI16 as clock source after wake-up
                ClockSrc::HSI16(_) | ClockSrc::PLL(PLLSource::HSI16(_), _, _) => {
                    w.stopwuck().set_bit()
                }
                // External clock selected
                //
                // Unfortunately handling the external clock is not as
                // straight-forward as handling MSI or HSI16. We need to
                // know whether the external clock is going to be shut down
                // during Stop mode. If it is, we need to either shut it
                // down before entering Stop mode, or enable the clock
                // security system (CSS) and handle any failures using it.
                // This is explained in sectoin 6.3.9 of the STM32L0x2
                // Reference Manual.
                //
                // In principle, we could ask the user (through
                // `StopModeConfig`), whether to shut down the external
                // clock then restore is after we wake up again. However, to
                // do this we'd either need to refactor the `rcc` module,
                // making it more flexible so we can reuse the relevant code
                // here, or duplicate that code. I (hannobraun) am not to
                // keen on either right now, given that I don't have a test
                // setup with an external clock source at hand.
                //
                // One might ask why we need to restore the configuration at
                // all after waking up, but that's absolutely required. This
                // HAL's architecture assumes that the clocks are configured
                // once, then never changed again. If we left Stop mode with
                // a different clock frequency than we entered it with, a
                // lot of peripheral would stop working correctly.
                //
                // For now, I've decided to just not support this case and
                // panic, which is also documented in this method's doc
                // comment.
                _ => panic!("External clock not supported for Stop mode"),
            });

        // Configure Stop mode
        self.pwr.0.cr.modify(|_, w| {
            // Ultra-low-power mode
            w.ulp().bit(self.config.ultra_low_power);
            // Clear WUF
            w.cwuf().set_bit();
            // Enter Stop mode
            w.pdds().stop_mode();
            // Disable internal voltage regulator
            w.lpds().set_bit()
        });

        // Wait for WUF to be cleared
        while self.pwr.0.csr.read().wuf().bit_is_set() {}

        // Enter Stop mode
        asm::dsb();
        asm::wfi();
    }
}

/// Configuration for entering Stop mode
///
/// Used by `StopMode`'s `PowerMode` implementation.
pub struct StopModeConfig {
    /// Disable additional hardware when entering Stop mode
    ///
    /// When set to `true`, the following hardware will be disabled:
    ///
    /// - Internal voltage reference (Vrefint)
    /// - Brown out reset (BOR)
    /// - Programmable voltage detector (PVD)
    /// - Internal temperature sensor
    pub ultra_low_power: bool,
}

/// Standby mode
///
/// You can get an instance of this struct by calling [`PWR::standby_mode`].
///
/// The `PowerMode` implementation of this type will block until something wakes
/// the microcontroller up again. Please make sure to configure an interrupt, or
/// it could block forever. Once woken up, the method will not return. Instead,
/// the microcontroller will reset.
pub struct StandbyMode<'r> {
    pwr: &'r mut PWR,
    scb: &'r mut SCB,
}

impl PowerMode for StandbyMode<'_> {
    fn enter(&mut self) {
        // Configure Standby mode
        self.scb.set_sleepdeep();
        self.pwr.0.cr.modify(|_, w| {
            // Clear WUF
            w.cwuf().set_bit();
            // Standby mode
            w.pdds().standby_mode()
        });

        // Wait for WUF to be cleared
        while self.pwr.0.csr.read().wuf().bit_is_set() {}

        // Enter Standby mode
        asm::dsb();
        asm::wfi();
    }
}