#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum Gain {
X1,
X2,
Div8,
Div4,
}
impl Gain {
pub(crate) const fn bits(self) -> u16 {
match self {
Self::X1 => 0b00 << 11,
Self::X2 => 0b01 << 11,
Self::Div8 => 0b10 << 11,
Self::Div4 => 0b11 << 11,
}
}
pub(crate) const fn from_bits(bits: u16) -> Self {
match (bits >> 11) & 0b11 {
0b00 => Self::X1,
0b01 => Self::X2,
0b10 => Self::Div8,
_ => Self::Div4,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum IntegrationTime {
Ms25,
Ms50,
Ms100,
Ms200,
Ms400,
Ms800,
}
impl IntegrationTime {
pub const fn milliseconds(self) -> u32 {
match self {
Self::Ms25 => 25,
Self::Ms50 => 50,
Self::Ms100 => 100,
Self::Ms200 => 200,
Self::Ms400 => 400,
Self::Ms800 => 800,
}
}
pub(crate) const fn bits(self) -> u16 {
match self {
Self::Ms25 => 0b1100 << 6,
Self::Ms50 => 0b1000 << 6,
Self::Ms100 => 0b0000 << 6,
Self::Ms200 => 0b0001 << 6,
Self::Ms400 => 0b0010 << 6,
Self::Ms800 => 0b0011 << 6,
}
}
pub(crate) const fn from_bits(bits: u16) -> Result<Self, ConfigDecodeError> {
match (bits >> 6) & 0b1111 {
0b1100 => Ok(Self::Ms25),
0b1000 => Ok(Self::Ms50),
0b0000 => Ok(Self::Ms100),
0b0001 => Ok(Self::Ms200),
0b0010 => Ok(Self::Ms400),
0b0011 => Ok(Self::Ms800),
observed => Err(ConfigDecodeError::ReservedIntegrationTime { observed }),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum Persistence {
One,
Two,
Four,
Eight,
}
impl Persistence {
pub const fn count(self) -> u8 {
match self {
Self::One => 1,
Self::Two => 2,
Self::Four => 4,
Self::Eight => 8,
}
}
pub(crate) const fn bits(self) -> u16 {
match self {
Self::One => 0b00 << 4,
Self::Two => 0b01 << 4,
Self::Four => 0b10 << 4,
Self::Eight => 0b11 << 4,
}
}
pub(crate) const fn from_bits(bits: u16) -> Self {
match (bits >> 4) & 0b11 {
0b00 => Self::One,
0b01 => Self::Two,
0b10 => Self::Four,
_ => Self::Eight,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum PowerState {
Active,
Shutdown,
}
impl PowerState {
pub(crate) const fn bit(self) -> u16 {
match self {
Self::Active => 0,
Self::Shutdown => 1,
}
}
pub(crate) const fn from_word(word: u16) -> Self {
if word & 1 == 0 {
Self::Active
} else {
Self::Shutdown
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum ThresholdMonitorState {
Disabled,
Enabled,
}
impl ThresholdMonitorState {
pub(crate) const fn bit(self) -> u16 {
match self {
Self::Disabled => 0,
Self::Enabled => 1 << 1,
}
}
pub(crate) const fn from_word(word: u16) -> Self {
if word & (1 << 1) == 0 {
Self::Disabled
} else {
Self::Enabled
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct MeasurementConfig {
gain: Gain,
integration_time: IntegrationTime,
}
impl MeasurementConfig {
pub const fn new(gain: Gain, integration_time: IntegrationTime) -> Self {
Self {
gain,
integration_time,
}
}
pub const fn silicon_reset_default() -> Self {
Self::new(Gain::X1, IntegrationTime::Ms100)
}
pub const fn maximum_range_start() -> Self {
Self::new(Gain::Div8, IntegrationTime::Ms25)
}
pub const fn gain(self) -> Gain {
self.gain
}
pub const fn integration_time(self) -> IntegrationTime {
self.integration_time
}
pub(crate) const fn bits(self) -> u16 {
self.gain.bits() | self.integration_time.bits()
}
}
impl Default for MeasurementConfig {
fn default() -> Self {
Self::maximum_range_start()
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct ConfigurationSnapshot {
pub measurement: MeasurementConfig,
pub persistence: Persistence,
pub threshold_monitor: ThresholdMonitorState,
pub power_state: PowerState,
}
impl ConfigurationSnapshot {
pub const fn silicon_reset_default() -> Self {
Self {
measurement: MeasurementConfig::silicon_reset_default(),
persistence: Persistence::One,
threshold_monitor: ThresholdMonitorState::Disabled,
power_state: PowerState::Shutdown,
}
}
pub(crate) const fn encode(self) -> u16 {
self.measurement.bits()
| self.persistence.bits()
| self.threshold_monitor.bit()
| self.power_state.bit()
}
pub(crate) const fn with_measurement(mut self, measurement: MeasurementConfig) -> Self {
self.measurement = measurement;
self
}
pub(crate) const fn with_persistence(mut self, persistence: Persistence) -> Self {
self.persistence = persistence;
self
}
pub(crate) const fn with_monitor(mut self, state: ThresholdMonitorState) -> Self {
self.threshold_monitor = state;
self
}
pub(crate) const fn with_power_state(mut self, state: PowerState) -> Self {
self.power_state = state;
self
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[non_exhaustive]
pub enum ConfigDecodeError {
ReservedBits {
observed: u16,
},
ReservedIntegrationTime {
observed: u16,
},
}
pub(crate) struct ConfigWord(u16);
impl ConfigWord {
pub(crate) const fn from_raw(raw: u16) -> Self {
Self(raw)
}
pub(crate) const fn from_snapshot(snapshot: ConfigurationSnapshot) -> Self {
Self(snapshot.encode())
}
pub(crate) const fn raw(self) -> u16 {
self.0
}
pub(crate) fn decode(self) -> Result<ConfigurationSnapshot, ConfigDecodeError> {
let reserved = self.0 & 0b1110_0100_0000_1100;
if reserved != 0 {
return Err(ConfigDecodeError::ReservedBits { observed: reserved });
}
Ok(ConfigurationSnapshot {
measurement: MeasurementConfig::new(
Gain::from_bits(self.0),
IntegrationTime::from_bits(self.0)?,
),
persistence: Persistence::from_bits(self.0),
threshold_monitor: ThresholdMonitorState::from_word(self.0),
power_state: PowerState::from_word(self.0),
})
}
}
impl core::fmt::Display for ConfigDecodeError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::ReservedBits { observed } => {
write!(f, "reserved configuration bits were set: {observed:#06x}")
}
Self::ReservedIntegrationTime { observed } => {
write!(f, "undocumented integration-time encoding {observed:#06b}")
}
}
}
}
impl core::error::Error for ConfigDecodeError {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn reset_word_decodes() {
assert_eq!(
ConfigWord(0x0001).decode(),
Ok(ConfigurationSnapshot::silicon_reset_default())
);
}
#[test]
fn configuration_fields_occupy_the_contract_bit_positions() {
let base = ConfigurationSnapshot {
measurement: MeasurementConfig::new(Gain::X1, IntegrationTime::Ms100),
persistence: Persistence::One,
threshold_monitor: ThresholdMonitorState::Disabled,
power_state: PowerState::Active,
};
assert_eq!(base.encode(), 0x0000);
for (gain, bits) in [
(Gain::X1, 0b00_u16),
(Gain::X2, 0b01),
(Gain::Div8, 0b10),
(Gain::Div4, 0b11),
] {
let word = ConfigurationSnapshot {
measurement: MeasurementConfig::new(gain, IntegrationTime::Ms100),
..base
}
.encode();
assert_eq!(word, bits << 11, "gain {gain:?} must occupy bits 12:11");
}
for (integration_time, bits) in [
(IntegrationTime::Ms25, 0b1100_u16),
(IntegrationTime::Ms50, 0b1000),
(IntegrationTime::Ms100, 0b0000),
(IntegrationTime::Ms200, 0b0001),
(IntegrationTime::Ms400, 0b0010),
(IntegrationTime::Ms800, 0b0011),
] {
let word = ConfigurationSnapshot {
measurement: MeasurementConfig::new(Gain::X1, integration_time),
..base
}
.encode();
assert_eq!(
word,
bits << 6,
"integration time {integration_time:?} must occupy bits 9:6"
);
}
for (persistence, bits) in [
(Persistence::One, 0b00_u16),
(Persistence::Two, 0b01),
(Persistence::Four, 0b10),
(Persistence::Eight, 0b11),
] {
let word = ConfigurationSnapshot {
persistence,
..base
}
.encode();
assert_eq!(
word,
bits << 4,
"persistence {persistence:?} must occupy bits 5:4"
);
}
assert_eq!(
ConfigurationSnapshot {
threshold_monitor: ThresholdMonitorState::Enabled,
..base
}
.encode(),
1 << 1
);
assert_eq!(
ConfigurationSnapshot {
power_state: PowerState::Shutdown,
..base
}
.encode(),
1 << 0
);
let combined = (0b11 << 11) | (0b0011 << 6) | (0b11 << 4) | (1 << 1) | 1;
assert_eq!(combined, 0x18F3);
assert_eq!(
ConfigWord(combined).decode(),
Ok(ConfigurationSnapshot {
measurement: MeasurementConfig::new(Gain::Div4, IntegrationTime::Ms800),
persistence: Persistence::Eight,
threshold_monitor: ThresholdMonitorState::Enabled,
power_state: PowerState::Shutdown,
})
);
}
#[test]
fn every_documented_configuration_field_combination_round_trips() {
let gains = [Gain::X1, Gain::X2, Gain::Div8, Gain::Div4];
let times = [
IntegrationTime::Ms25,
IntegrationTime::Ms50,
IntegrationTime::Ms100,
IntegrationTime::Ms200,
IntegrationTime::Ms400,
IntegrationTime::Ms800,
];
let persistence_values = [
Persistence::One,
Persistence::Two,
Persistence::Four,
Persistence::Eight,
];
let monitor_states = [
ThresholdMonitorState::Disabled,
ThresholdMonitorState::Enabled,
];
let power_states = [PowerState::Active, PowerState::Shutdown];
for gain in gains {
for integration_time in times {
for persistence in persistence_values {
for threshold_monitor in monitor_states {
for power_state in power_states {
let expected = ConfigurationSnapshot {
measurement: MeasurementConfig::new(gain, integration_time),
persistence,
threshold_monitor,
power_state,
};
assert_eq!(ConfigWord(expected.encode()).decode(), Ok(expected));
}
}
}
}
}
}
#[test]
fn every_reserved_configuration_bit_is_rejected() {
for bit in [2_u32, 3, 10, 13, 14, 15] {
let raw = 1_u16 << bit;
assert_eq!(
ConfigWord(raw).decode(),
Err(ConfigDecodeError::ReservedBits { observed: raw })
);
}
}
#[test]
fn every_reserved_integration_encoding_is_rejected() {
for observed in [4_u16, 5, 6, 7, 9, 10, 11, 13, 14, 15] {
assert_eq!(
ConfigWord(observed << 6).decode(),
Err(ConfigDecodeError::ReservedIntegrationTime { observed })
);
}
}
#[test]
fn public_configuration_accessors_match_the_selected_domain() {
let config = MeasurementConfig::new(Gain::X2, IntegrationTime::Ms800);
assert_eq!(config.gain(), Gain::X2);
assert_eq!(config.integration_time(), IntegrationTime::Ms800);
assert_eq!(IntegrationTime::Ms25.milliseconds(), 25);
assert_eq!(IntegrationTime::Ms800.milliseconds(), 800);
assert_eq!(Persistence::One.count(), 1);
assert_eq!(Persistence::Eight.count(), 8);
}
}