icm20948/
power.rs

1//! Power management and low-power modes
2//!
3//! The ICM-20948 supports various power modes to reduce power consumption:
4//! - **Sleep mode**: Minimum power, all sensors off
5//! - **Low-power mode**: Accel-only at reduced sample rates (0.24 Hz - 500 Hz)
6//! - **Cycle mode**: Periodic wake-up for sensor readings
7//! - **Wake-on-motion (`WoM`)**: Interrupt on motion detection
8//!
9//! # Power Consumption (typical)
10//! - Full operation (all sensors): ~3.4 mA
11//! - Low-power accel (31.25 Hz): ~15 μA
12//! - Sleep mode: ~7 μA
13//!
14//! # Example
15//!
16//! ```ignore
17//! # use icm20948::{Icm20948Driver, power::{LowPowerConfig, LowPowerRate, WomMode}};
18//! # let mut imu: Icm20948Driver<_> = todo!();
19//! // Configure low-power mode with wake-on-motion
20//! let config = LowPowerConfig {
21//!     accel_rate: LowPowerRate::Hz31_25,
22//!     enable_wake_on_motion: true,
23//!     wom_threshold: 20, // mg
24//!     wom_mode: WomMode::CompareCurrentSample,
25//! };
26//!
27//! imu.enter_low_power_mode(&config)?;
28//! # Ok::<(), icm20948::Error<()>>(())
29//! ```
30
31use crate::Error;
32
33/// Power mode selection
34#[derive(Debug, Clone, Copy, PartialEq, Eq)]
35#[cfg_attr(feature = "defmt", derive(defmt::Format))]
36pub enum PowerMode {
37    /// Normal operation mode - all sensors available
38    Normal,
39    /// Low-power mode - accelerometer only at reduced rate
40    LowPower,
41    /// Sleep mode - minimum power consumption
42    Sleep,
43}
44
45/// Clock source selection
46#[derive(Debug, Clone, Copy, PartialEq, Eq)]
47#[cfg_attr(feature = "defmt", derive(defmt::Format))]
48pub enum ClockSource {
49    /// Internal 20 MHz oscillator
50    Internal20MHz = 0,
51    /// Auto-select best available clock
52    AutoSelect = 1,
53    /// Stop clock (lowest power)
54    Stop = 7,
55}
56
57/// Low-power accelerometer sample rates
58///
59/// These rates are available in low-power mode. Lower rates consume less power.
60#[derive(Debug, Clone, Copy, PartialEq, Eq)]
61#[cfg_attr(feature = "defmt", derive(defmt::Format))]
62pub enum LowPowerRate {
63    /// 0.24 Hz (4.17 second period) - lowest power
64    Hz0_24 = 0,
65    /// 0.49 Hz (2.04 second period)
66    Hz0_49 = 1,
67    /// 0.98 Hz (1.02 second period)
68    Hz0_98 = 2,
69    /// 1.95 Hz (512 ms period)
70    Hz1_95 = 3,
71    /// 3.91 Hz (256 ms period)
72    Hz3_91 = 4,
73    /// 7.81 Hz (128 ms period)
74    Hz7_81 = 5,
75    /// 15.63 Hz (64 ms period)
76    Hz15_63 = 6,
77    /// 31.25 Hz (32 ms period)
78    Hz31_25 = 7,
79    /// 62.50 Hz (16 ms period)
80    Hz62_50 = 8,
81    /// 125 Hz (8 ms period)
82    Hz125 = 9,
83    /// 250 Hz (4 ms period)
84    Hz250 = 10,
85    /// 500 Hz (2 ms period)
86    Hz500 = 11,
87}
88
89impl LowPowerRate {
90    /// Get the sample rate in Hz
91    pub const fn rate_hz(&self) -> f32 {
92        match self {
93            Self::Hz0_24 => 0.24,
94            Self::Hz0_49 => 0.49,
95            Self::Hz0_98 => 0.98,
96            Self::Hz1_95 => 1.95,
97            Self::Hz3_91 => 3.91,
98            Self::Hz7_81 => 7.81,
99            Self::Hz15_63 => 15.63,
100            Self::Hz31_25 => 31.25,
101            Self::Hz62_50 => 62.50,
102            Self::Hz125 => 125.0,
103            Self::Hz250 => 250.0,
104            Self::Hz500 => 500.0,
105        }
106    }
107
108    /// Get the sample period in milliseconds
109    pub const fn period_ms(&self) -> f32 {
110        1000.0 / self.rate_hz()
111    }
112
113    /// Get register value for ODR configuration
114    pub const fn odr_value(&self) -> u8 {
115        *self as u8
116    }
117}
118
119/// Wake-on-Motion (`WoM`) comparison mode
120#[derive(Debug, Clone, Copy, PartialEq, Eq)]
121#[cfg_attr(feature = "defmt", derive(defmt::Format))]
122pub enum WomMode {
123    /// Compare to initial sample (more sensitive to slow changes)
124    CompareInitialSample = 0,
125    /// Compare to current sample (detects sudden motion)
126    CompareCurrentSample = 1,
127}
128
129/// Low-power mode configuration
130#[derive(Debug, Clone, Copy, PartialEq, Eq)]
131#[cfg_attr(feature = "defmt", derive(defmt::Format))]
132pub struct LowPowerConfig {
133    /// Accelerometer sample rate in low-power mode
134    pub accel_rate: LowPowerRate,
135    /// Enable wake-on-motion interrupt
136    pub enable_wake_on_motion: bool,
137    /// Wake-on-motion threshold in milligravity (mg)
138    /// Range: 0-1020 mg in 4 mg steps
139    pub wom_threshold: u16,
140    /// Wake-on-motion comparison mode
141    pub wom_mode: WomMode,
142}
143
144impl Default for LowPowerConfig {
145    fn default() -> Self {
146        Self {
147            accel_rate: LowPowerRate::Hz31_25,
148            enable_wake_on_motion: false,
149            wom_threshold: 20, // 20 mg default
150            wom_mode: WomMode::CompareCurrentSample,
151        }
152    }
153}
154
155impl LowPowerConfig {
156    /// Create a new low-power configuration
157    ///
158    /// # Arguments
159    /// * `rate` - Accelerometer sample rate
160    /// * `wom_threshold_mg` - Wake-on-motion threshold in mg (0-1020)
161    ///
162    /// # Errors
163    /// Returns `InvalidConfig` if threshold is out of range
164    pub const fn new(rate: LowPowerRate, wom_threshold_mg: u16) -> Result<Self, Error<()>> {
165        if wom_threshold_mg > 1020 {
166            return Err(Error::InvalidConfig);
167        }
168        Ok(Self {
169            accel_rate: rate,
170            enable_wake_on_motion: true,
171            wom_threshold: wom_threshold_mg,
172            wom_mode: WomMode::CompareCurrentSample,
173        })
174    }
175
176    /// Get the `WoM` threshold register value
177    ///
178    /// The register uses 4 mg steps, so we divide by 4.
179    /// If the threshold exceeds the valid range, it saturates to 255.
180    pub fn wom_threshold_register_value(&self) -> u8 {
181        u8::try_from(self.wom_threshold / 4).unwrap_or(255)
182    }
183
184    /// Create a low-power config without wake-on-motion
185    pub const fn without_wom(rate: LowPowerRate) -> Self {
186        Self {
187            accel_rate: rate,
188            enable_wake_on_motion: false,
189            wom_threshold: 0,
190            wom_mode: WomMode::CompareCurrentSample,
191        }
192    }
193}
194
195/// Cycle mode configuration for periodic sensor readings
196#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
197#[cfg_attr(feature = "defmt", derive(defmt::Format))]
198pub struct CycleConfig {
199    /// Enable accelerometer cycle mode
200    pub enable_accel_cycle: bool,
201    /// Enable gyroscope cycle mode
202    pub enable_gyro_cycle: bool,
203    /// Enable I2C master cycle mode (for magnetometer)
204    pub enable_i2c_master_cycle: bool,
205}
206
207/// Power management status
208#[derive(Debug, Clone, Copy, PartialEq, Eq)]
209#[cfg_attr(feature = "defmt", derive(defmt::Format))]
210#[allow(clippy::struct_excessive_bools)]
211pub struct PowerStatus {
212    /// Current power mode
213    pub mode: PowerMode,
214    /// Sleep enabled
215    pub sleep: bool,
216    /// Low-power mode enabled
217    pub low_power: bool,
218    /// Temperature sensor disabled
219    pub temp_disabled: bool,
220    /// Accelerometer cycle mode
221    pub accel_cycle: bool,
222    /// Gyroscope cycle mode
223    pub gyro_cycle: bool,
224    /// I2C master cycle mode
225    pub i2c_master_cycle: bool,
226}
227
228impl Default for PowerStatus {
229    fn default() -> Self {
230        Self {
231            mode: PowerMode::Normal,
232            sleep: false,
233            low_power: false,
234            temp_disabled: false,
235            accel_cycle: false,
236            gyro_cycle: false,
237            i2c_master_cycle: false,
238        }
239    }
240}
241
242/// Individual sensor power control
243#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
244#[cfg_attr(feature = "defmt", derive(defmt::Format))]
245#[allow(clippy::struct_excessive_bools)]
246pub struct SensorPowerConfig {
247    /// Disable accelerometer X-axis
248    pub disable_accel_x: bool,
249    /// Disable accelerometer Y-axis
250    pub disable_accel_y: bool,
251    /// Disable accelerometer Z-axis
252    pub disable_accel_z: bool,
253    /// Disable gyroscope X-axis
254    pub disable_gyro_x: bool,
255    /// Disable gyroscope Y-axis
256    pub disable_gyro_y: bool,
257    /// Disable gyroscope Z-axis
258    pub disable_gyro_z: bool,
259}
260
261impl SensorPowerConfig {
262    /// Enable all sensors
263    pub const fn all_enabled() -> Self {
264        Self {
265            disable_accel_x: false,
266            disable_accel_y: false,
267            disable_accel_z: false,
268            disable_gyro_x: false,
269            disable_gyro_y: false,
270            disable_gyro_z: false,
271        }
272    }
273
274    /// Disable all accelerometer axes
275    pub const fn accel_disabled() -> Self {
276        Self {
277            disable_accel_x: true,
278            disable_accel_y: true,
279            disable_accel_z: true,
280            disable_gyro_x: false,
281            disable_gyro_y: false,
282            disable_gyro_z: false,
283        }
284    }
285
286    /// Disable all gyroscope axes
287    pub const fn gyro_disabled() -> Self {
288        Self {
289            disable_accel_x: false,
290            disable_accel_y: false,
291            disable_accel_z: false,
292            disable_gyro_x: true,
293            disable_gyro_y: true,
294            disable_gyro_z: true,
295        }
296    }
297
298    /// Check if all accelerometer axes are enabled
299    pub const fn is_accel_enabled(&self) -> bool {
300        !self.disable_accel_x && !self.disable_accel_y && !self.disable_accel_z
301    }
302
303    /// Check if all gyroscope axes are enabled
304    pub const fn is_gyro_enabled(&self) -> bool {
305        !self.disable_gyro_x && !self.disable_gyro_y && !self.disable_gyro_z
306    }
307}
308
309#[cfg(test)]
310#[allow(clippy::unwrap_used)]
311mod tests {
312    use super::*;
313
314    #[test]
315    #[allow(clippy::float_cmp)]
316    fn test_low_power_rate_values() {
317        assert_eq!(LowPowerRate::Hz31_25.rate_hz(), 31.25);
318        assert_eq!(LowPowerRate::Hz125.rate_hz(), 125.0);
319        assert!(LowPowerRate::Hz31_25.period_ms() > 30.0);
320        assert!(LowPowerRate::Hz31_25.period_ms() < 33.0);
321    }
322
323    #[test]
324    fn test_low_power_config_default() {
325        let config = LowPowerConfig::default();
326        assert_eq!(config.accel_rate, LowPowerRate::Hz31_25);
327        assert!(!config.enable_wake_on_motion);
328    }
329
330    #[test]
331    fn test_low_power_config_new() {
332        let config = LowPowerConfig::new(LowPowerRate::Hz62_50, 100);
333        assert!(config.is_ok());
334        let config = config.unwrap();
335        assert_eq!(config.accel_rate, LowPowerRate::Hz62_50);
336        assert_eq!(config.wom_threshold, 100);
337        assert!(config.enable_wake_on_motion);
338    }
339
340    #[test]
341    fn test_low_power_config_invalid_threshold() {
342        let config = LowPowerConfig::new(LowPowerRate::Hz31_25, 1100);
343        assert!(config.is_err());
344    }
345
346    #[test]
347    fn test_wom_threshold_register_value() {
348        let config = LowPowerConfig {
349            wom_threshold: 100,
350            ..Default::default()
351        };
352        assert_eq!(config.wom_threshold_register_value(), 25);
353
354        let config = LowPowerConfig {
355            wom_threshold: 20,
356            ..Default::default()
357        };
358        assert_eq!(config.wom_threshold_register_value(), 5);
359    }
360
361    #[test]
362    fn test_sensor_power_config() {
363        let config = SensorPowerConfig::all_enabled();
364        assert!(config.is_accel_enabled());
365        assert!(config.is_gyro_enabled());
366
367        let config = SensorPowerConfig::accel_disabled();
368        assert!(!config.is_accel_enabled());
369        assert!(config.is_gyro_enabled());
370
371        let config = SensorPowerConfig::gyro_disabled();
372        assert!(config.is_accel_enabled());
373        assert!(!config.is_gyro_enabled());
374    }
375
376    #[test]
377    fn test_cycle_config_default() {
378        let config = CycleConfig::default();
379        assert!(!config.enable_accel_cycle);
380        assert!(!config.enable_gyro_cycle);
381        assert!(!config.enable_i2c_master_cycle);
382    }
383
384    #[test]
385    fn test_power_status_default() {
386        let status = PowerStatus::default();
387        assert_eq!(status.mode, PowerMode::Normal);
388        assert!(!status.sleep);
389        assert!(!status.low_power);
390    }
391}