1#[derive(Clone, Copy, Debug, PartialEq, Eq)]
5#[cfg_attr(feature = "defmt", derive(defmt::Format))]
6pub enum Gain {
7 X1,
9 X2,
11 Div8,
13 Div4,
15}
16
17impl Gain {
18 pub(crate) const fn bits(self) -> u16 {
19 match self {
20 Self::X1 => 0b00 << 11,
21 Self::X2 => 0b01 << 11,
22 Self::Div8 => 0b10 << 11,
23 Self::Div4 => 0b11 << 11,
24 }
25 }
26
27 pub(crate) const fn from_bits(bits: u16) -> Self {
28 match (bits >> 11) & 0b11 {
29 0b00 => Self::X1,
30 0b01 => Self::X2,
31 0b10 => Self::Div8,
32 _ => Self::Div4,
33 }
34 }
35}
36
37#[derive(Clone, Copy, Debug, PartialEq, Eq)]
39#[cfg_attr(feature = "defmt", derive(defmt::Format))]
40pub enum IntegrationTime {
41 Ms25,
43 Ms50,
45 Ms100,
47 Ms200,
49 Ms400,
51 Ms800,
53}
54
55impl IntegrationTime {
56 pub const fn milliseconds(self) -> u32 {
58 match self {
59 Self::Ms25 => 25,
60 Self::Ms50 => 50,
61 Self::Ms100 => 100,
62 Self::Ms200 => 200,
63 Self::Ms400 => 400,
64 Self::Ms800 => 800,
65 }
66 }
67
68 pub(crate) const fn bits(self) -> u16 {
69 match self {
70 Self::Ms25 => 0b1100 << 6,
71 Self::Ms50 => 0b1000 << 6,
72 Self::Ms100 => 0b0000 << 6,
73 Self::Ms200 => 0b0001 << 6,
74 Self::Ms400 => 0b0010 << 6,
75 Self::Ms800 => 0b0011 << 6,
76 }
77 }
78
79 pub(crate) const fn from_bits(bits: u16) -> Result<Self, ConfigDecodeError> {
80 match (bits >> 6) & 0b1111 {
81 0b1100 => Ok(Self::Ms25),
82 0b1000 => Ok(Self::Ms50),
83 0b0000 => Ok(Self::Ms100),
84 0b0001 => Ok(Self::Ms200),
85 0b0010 => Ok(Self::Ms400),
86 0b0011 => Ok(Self::Ms800),
87 observed => Err(ConfigDecodeError::ReservedIntegrationTime { observed }),
88 }
89 }
90}
91
92#[derive(Clone, Copy, Debug, PartialEq, Eq)]
107#[cfg_attr(feature = "defmt", derive(defmt::Format))]
108pub enum Persistence {
109 One,
111 Two,
113 Four,
115 Eight,
117}
118
119impl Persistence {
120 pub const fn count(self) -> u8 {
125 match self {
126 Self::One => 1,
127 Self::Two => 2,
128 Self::Four => 4,
129 Self::Eight => 8,
130 }
131 }
132
133 pub(crate) const fn bits(self) -> u16 {
134 match self {
135 Self::One => 0b00 << 4,
136 Self::Two => 0b01 << 4,
137 Self::Four => 0b10 << 4,
138 Self::Eight => 0b11 << 4,
139 }
140 }
141
142 pub(crate) const fn from_bits(bits: u16) -> Self {
143 match (bits >> 4) & 0b11 {
144 0b00 => Self::One,
145 0b01 => Self::Two,
146 0b10 => Self::Four,
147 _ => Self::Eight,
148 }
149 }
150}
151
152#[derive(Clone, Copy, Debug, PartialEq, Eq)]
154#[cfg_attr(feature = "defmt", derive(defmt::Format))]
155pub enum PowerState {
156 Active,
158 Shutdown,
161}
162
163impl PowerState {
164 pub(crate) const fn bit(self) -> u16 {
165 match self {
166 Self::Active => 0,
167 Self::Shutdown => 1,
168 }
169 }
170
171 pub(crate) const fn from_word(word: u16) -> Self {
172 if word & 1 == 0 {
173 Self::Active
174 } else {
175 Self::Shutdown
176 }
177 }
178}
179
180#[derive(Clone, Copy, Debug, PartialEq, Eq)]
182#[cfg_attr(feature = "defmt", derive(defmt::Format))]
183pub enum ThresholdMonitorState {
184 Disabled,
186 Enabled,
189}
190
191impl ThresholdMonitorState {
192 pub(crate) const fn bit(self) -> u16 {
193 match self {
194 Self::Disabled => 0,
195 Self::Enabled => 1 << 1,
196 }
197 }
198
199 pub(crate) const fn from_word(word: u16) -> Self {
200 if word & (1 << 1) == 0 {
201 Self::Disabled
202 } else {
203 Self::Enabled
204 }
205 }
206}
207
208#[derive(Clone, Copy, Debug, PartialEq, Eq)]
210#[cfg_attr(feature = "defmt", derive(defmt::Format))]
211pub struct MeasurementConfig {
212 gain: Gain,
213 integration_time: IntegrationTime,
214}
215
216impl MeasurementConfig {
217 pub const fn new(gain: Gain, integration_time: IntegrationTime) -> Self {
219 Self {
220 gain,
221 integration_time,
222 }
223 }
224
225 pub const fn silicon_reset_default() -> Self {
228 Self::new(Gain::X1, IntegrationTime::Ms100)
229 }
230
231 pub const fn maximum_range_start() -> Self {
233 Self::new(Gain::Div8, IntegrationTime::Ms25)
234 }
235
236 pub const fn gain(self) -> Gain {
238 self.gain
239 }
240
241 pub const fn integration_time(self) -> IntegrationTime {
243 self.integration_time
244 }
245
246 pub(crate) const fn bits(self) -> u16 {
247 self.gain.bits() | self.integration_time.bits()
248 }
249}
250
251impl Default for MeasurementConfig {
252 fn default() -> Self {
266 Self::maximum_range_start()
267 }
268}
269
270#[derive(Clone, Copy, Debug, PartialEq, Eq)]
272#[cfg_attr(feature = "defmt", derive(defmt::Format))]
273pub struct ConfigurationSnapshot {
274 pub measurement: MeasurementConfig,
276 pub persistence: Persistence,
278 pub threshold_monitor: ThresholdMonitorState,
280 pub power_state: PowerState,
282}
283
284impl ConfigurationSnapshot {
285 pub const fn silicon_reset_default() -> Self {
287 Self {
288 measurement: MeasurementConfig::silicon_reset_default(),
289 persistence: Persistence::One,
290 threshold_monitor: ThresholdMonitorState::Disabled,
291 power_state: PowerState::Shutdown,
292 }
293 }
294
295 pub(crate) const fn encode(self) -> u16 {
296 self.measurement.bits()
297 | self.persistence.bits()
298 | self.threshold_monitor.bit()
299 | self.power_state.bit()
300 }
301
302 pub(crate) const fn with_measurement(mut self, measurement: MeasurementConfig) -> Self {
303 self.measurement = measurement;
304 self
305 }
306
307 pub(crate) const fn with_persistence(mut self, persistence: Persistence) -> Self {
308 self.persistence = persistence;
309 self
310 }
311
312 pub(crate) const fn with_monitor(mut self, state: ThresholdMonitorState) -> Self {
313 self.threshold_monitor = state;
314 self
315 }
316
317 pub(crate) const fn with_power_state(mut self, state: PowerState) -> Self {
318 self.power_state = state;
319 self
320 }
321}
322
323#[derive(Clone, Copy, Debug, PartialEq, Eq)]
325#[cfg_attr(feature = "defmt", derive(defmt::Format))]
326#[non_exhaustive]
327pub enum ConfigDecodeError {
328 ReservedBits {
330 observed: u16,
332 },
333 ReservedIntegrationTime {
335 observed: u16,
337 },
338}
339
340pub(crate) struct ConfigWord(u16);
341
342impl ConfigWord {
343 pub(crate) const fn from_raw(raw: u16) -> Self {
344 Self(raw)
345 }
346
347 pub(crate) const fn from_snapshot(snapshot: ConfigurationSnapshot) -> Self {
348 Self(snapshot.encode())
349 }
350
351 pub(crate) const fn raw(self) -> u16 {
352 self.0
353 }
354
355 pub(crate) fn decode(self) -> Result<ConfigurationSnapshot, ConfigDecodeError> {
356 let reserved = self.0 & 0b1110_0100_0000_1100;
358 if reserved != 0 {
359 return Err(ConfigDecodeError::ReservedBits { observed: reserved });
360 }
361 Ok(ConfigurationSnapshot {
362 measurement: MeasurementConfig::new(
363 Gain::from_bits(self.0),
364 IntegrationTime::from_bits(self.0)?,
365 ),
366 persistence: Persistence::from_bits(self.0),
367 threshold_monitor: ThresholdMonitorState::from_word(self.0),
368 power_state: PowerState::from_word(self.0),
369 })
370 }
371}
372
373impl core::fmt::Display for ConfigDecodeError {
374 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
375 match self {
376 Self::ReservedBits { observed } => {
377 write!(f, "reserved configuration bits were set: {observed:#06x}")
378 }
379 Self::ReservedIntegrationTime { observed } => {
380 write!(f, "undocumented integration-time encoding {observed:#06b}")
381 }
382 }
383 }
384}
385
386impl core::error::Error for ConfigDecodeError {}
387
388#[cfg(test)]
389mod tests {
390 use super::*;
391
392 #[test]
393 fn reset_word_decodes() {
394 assert_eq!(
395 ConfigWord(0x0001).decode(),
396 Ok(ConfigurationSnapshot::silicon_reset_default())
397 );
398 }
399
400 #[test]
413 fn configuration_fields_occupy_the_contract_bit_positions() {
414 let base = ConfigurationSnapshot {
415 measurement: MeasurementConfig::new(Gain::X1, IntegrationTime::Ms100),
416 persistence: Persistence::One,
417 threshold_monitor: ThresholdMonitorState::Disabled,
418 power_state: PowerState::Active,
419 };
420 assert_eq!(base.encode(), 0x0000);
422
423 for (gain, bits) in [
425 (Gain::X1, 0b00_u16),
426 (Gain::X2, 0b01),
427 (Gain::Div8, 0b10),
428 (Gain::Div4, 0b11),
429 ] {
430 let word = ConfigurationSnapshot {
431 measurement: MeasurementConfig::new(gain, IntegrationTime::Ms100),
432 ..base
433 }
434 .encode();
435 assert_eq!(word, bits << 11, "gain {gain:?} must occupy bits 12:11");
436 }
437
438 for (integration_time, bits) in [
440 (IntegrationTime::Ms25, 0b1100_u16),
441 (IntegrationTime::Ms50, 0b1000),
442 (IntegrationTime::Ms100, 0b0000),
443 (IntegrationTime::Ms200, 0b0001),
444 (IntegrationTime::Ms400, 0b0010),
445 (IntegrationTime::Ms800, 0b0011),
446 ] {
447 let word = ConfigurationSnapshot {
448 measurement: MeasurementConfig::new(Gain::X1, integration_time),
449 ..base
450 }
451 .encode();
452 assert_eq!(
453 word,
454 bits << 6,
455 "integration time {integration_time:?} must occupy bits 9:6"
456 );
457 }
458
459 for (persistence, bits) in [
461 (Persistence::One, 0b00_u16),
462 (Persistence::Two, 0b01),
463 (Persistence::Four, 0b10),
464 (Persistence::Eight, 0b11),
465 ] {
466 let word = ConfigurationSnapshot {
467 persistence,
468 ..base
469 }
470 .encode();
471 assert_eq!(
472 word,
473 bits << 4,
474 "persistence {persistence:?} must occupy bits 5:4"
475 );
476 }
477
478 assert_eq!(
480 ConfigurationSnapshot {
481 threshold_monitor: ThresholdMonitorState::Enabled,
482 ..base
483 }
484 .encode(),
485 1 << 1
486 );
487 assert_eq!(
488 ConfigurationSnapshot {
489 power_state: PowerState::Shutdown,
490 ..base
491 }
492 .encode(),
493 1 << 0
494 );
495
496 let combined = (0b11 << 11) | (0b0011 << 6) | (0b11 << 4) | (1 << 1) | 1;
500 assert_eq!(combined, 0x18F3);
501 assert_eq!(
502 ConfigWord(combined).decode(),
503 Ok(ConfigurationSnapshot {
504 measurement: MeasurementConfig::new(Gain::Div4, IntegrationTime::Ms800),
505 persistence: Persistence::Eight,
506 threshold_monitor: ThresholdMonitorState::Enabled,
507 power_state: PowerState::Shutdown,
508 })
509 );
510 }
511
512 #[test]
513 fn every_documented_configuration_field_combination_round_trips() {
514 let gains = [Gain::X1, Gain::X2, Gain::Div8, Gain::Div4];
515 let times = [
516 IntegrationTime::Ms25,
517 IntegrationTime::Ms50,
518 IntegrationTime::Ms100,
519 IntegrationTime::Ms200,
520 IntegrationTime::Ms400,
521 IntegrationTime::Ms800,
522 ];
523 let persistence_values = [
524 Persistence::One,
525 Persistence::Two,
526 Persistence::Four,
527 Persistence::Eight,
528 ];
529 let monitor_states = [
530 ThresholdMonitorState::Disabled,
531 ThresholdMonitorState::Enabled,
532 ];
533 let power_states = [PowerState::Active, PowerState::Shutdown];
534
535 for gain in gains {
536 for integration_time in times {
537 for persistence in persistence_values {
538 for threshold_monitor in monitor_states {
539 for power_state in power_states {
540 let expected = ConfigurationSnapshot {
541 measurement: MeasurementConfig::new(gain, integration_time),
542 persistence,
543 threshold_monitor,
544 power_state,
545 };
546 assert_eq!(ConfigWord(expected.encode()).decode(), Ok(expected));
547 }
548 }
549 }
550 }
551 }
552 }
553
554 #[test]
555 fn every_reserved_configuration_bit_is_rejected() {
556 for bit in [2_u32, 3, 10, 13, 14, 15] {
557 let raw = 1_u16 << bit;
558 assert_eq!(
559 ConfigWord(raw).decode(),
560 Err(ConfigDecodeError::ReservedBits { observed: raw })
561 );
562 }
563 }
564
565 #[test]
566 fn every_reserved_integration_encoding_is_rejected() {
567 for observed in [4_u16, 5, 6, 7, 9, 10, 11, 13, 14, 15] {
568 assert_eq!(
569 ConfigWord(observed << 6).decode(),
570 Err(ConfigDecodeError::ReservedIntegrationTime { observed })
571 );
572 }
573 }
574
575 #[test]
576 fn public_configuration_accessors_match_the_selected_domain() {
577 let config = MeasurementConfig::new(Gain::X2, IntegrationTime::Ms800);
578 assert_eq!(config.gain(), Gain::X2);
579 assert_eq!(config.integration_time(), IntegrationTime::Ms800);
580 assert_eq!(IntegrationTime::Ms25.milliseconds(), 25);
581 assert_eq!(IntegrationTime::Ms800.milliseconds(), 800);
582 assert_eq!(Persistence::One.count(), 1);
583 assert_eq!(Persistence::Eight.count(), 8);
584 }
585}