Skip to main content

pamoja_sensors/
opt3001.rs

1//! Texas Instruments OPT3001 ambient light sensor.
2//!
3//! The OPT3001 is a single-chip lux meter with an optical filter matched to the
4//! photopic response of the human eye. It reports illuminance as a 16-bit word holding
5//! a 4-bit exponent and a 12-bit mantissa, across twelve binary-weighted full-scale
6//! ranges the part can select on its own; the low- and high-limit registers that drive
7//! its interrupt pin share that encoding. This module decodes the word, encodes a lux
8//! threshold back into it, and builds and parses the configuration register field by
9//! field, following the datasheet's register tables and its table of worked decoding
10//! examples.
11//!
12//! Illuminance is returned in integer milli-lux, which holds every register value
13//! exactly since the smallest LSB is 0.01 lux; an `f32` lux convenience sits beside it.
14
15/// The I2C address with the ADDR pin tied to GND.
16pub const I2C_ADDRESS_GND: u8 = 0x44;
17/// The I2C address with the ADDR pin tied to VDD.
18pub const I2C_ADDRESS_VDD: u8 = 0x45;
19/// The I2C address with the ADDR pin tied to SDA.
20pub const I2C_ADDRESS_SDA: u8 = 0x46;
21/// The I2C address with the ADDR pin tied to SCL.
22pub const I2C_ADDRESS_SCL: u8 = 0x47;
23
24/// The value the manufacturer ID register returns: `0x5449`, the ASCII bytes "TI".
25pub const MANUFACTURER_ID: u16 = 0x5449;
26/// The value the device ID register returns for an OPT3001.
27pub const DEVICE_ID: u16 = 0x3001;
28
29/// The OPT3001 register addresses. Every register is 16 bits, sent most significant
30/// byte first.
31pub mod register {
32    /// Result register: the exponent and mantissa of the latest conversion.
33    pub const RESULT: u8 = 0x00;
34    /// Configuration register: range, conversion time, mode, status flags, and the
35    /// interrupt reporting settings.
36    pub const CONFIGURATION: u8 = 0x01;
37    /// Low-limit register, in the result register's encoding.
38    pub const LOW_LIMIT: u8 = 0x02;
39    /// High-limit register, in the result register's encoding.
40    pub const HIGH_LIMIT: u8 = 0x03;
41    /// Manufacturer ID register; reads [`super::MANUFACTURER_ID`].
42    pub const MANUFACTURER_ID: u8 = 0x7E;
43    /// Device ID register; reads [`super::DEVICE_ID`] for an OPT3001.
44    pub const DEVICE_ID: u8 = 0x7F;
45}
46
47/// The power-on value of the configuration register (0xC810): automatic full-scale
48/// range, 800 ms conversion time, shutdown mode, latched window-style comparison, an
49/// active-low INT pin, the exponent not masked, and a fault count of one.
50pub const CONFIGURATION_RESET: u16 = 0xC810;
51/// The power-on value of the low-limit register: exponent 0, mantissa 0, or 0 lux.
52pub const LOW_LIMIT_RESET: u16 = 0x0000;
53/// The power-on value of the high-limit register: exponent 11, mantissa 0xFFF, the
54/// largest encodable threshold of 83865.60 lux.
55pub const HIGH_LIMIT_RESET: u16 = 0xBFFF;
56/// The low-limit register value that selects end-of-conversion mode, where the INT
57/// pin and the flags report every completed conversion: the exponent's two most
58/// significant bits set to `11b`.
59pub const LOW_LIMIT_END_OF_CONVERSION: u16 = 0xC000;
60
61/// The range number that selects automatic full-scale setting, `1100b`. Range numbers
62/// `0` to `11` select one of the fixed full-scale ranges; `13` to `15` are reserved.
63pub const RANGE_AUTOMATIC: u8 = 0b1100;
64/// The largest range number that selects a fixed full-scale range.
65pub const RANGE_MAX: u8 = 11;
66
67/// Returns the LSB size, in milli-lux, of a result or limit register at an exponent.
68///
69/// The datasheet's `LSB_Size = 0.01 lux * 2^E`, so 10 milli-lux doubled per step.
70///
71/// # Arguments
72///
73/// * `exponent` - the 4-bit exponent, a range number from `0` to `11`.
74///
75/// # Returns
76///
77/// The LSB size in milli-lux, or `None` if `exponent` is above [`RANGE_MAX`].
78pub fn lsb_milli_lux(exponent: u8) -> Option<u32> {
79    (exponent <= RANGE_MAX).then(|| 10u32 << exponent)
80}
81
82/// Returns the full-scale illuminance, in milli-lux, of a range number.
83///
84/// Full scale is the 12-bit mantissa's maximum, 4095, at that range's LSB size, so
85/// 40.95 lux at range `0` up to 83865.60 lux at range `11`.
86///
87/// # Arguments
88///
89/// * `range_number` - the range number, `0` to `11`.
90///
91/// # Returns
92///
93/// The full-scale illuminance in milli-lux, or `None` for the automatic and reserved
94/// range numbers, which have no single full scale.
95pub fn full_scale_milli_lux(range_number: u8) -> Option<u32> {
96    lsb_milli_lux(range_number).map(|lsb| lsb * 0xFFF)
97}
98
99/// Decodes a result or limit register word to milli-lux.
100///
101/// The datasheet's `lux = 0.01 * 2^E[3:0] * R[11:0]`, exact in integer milli-lux for
102/// every possible word.
103///
104/// # Arguments
105///
106/// * `raw` - the 16-bit register word, exponent in bits 15:12 and mantissa in 11:0.
107///
108/// # Returns
109///
110/// The illuminance in milli-lux.
111pub fn milli_lux(raw: u16) -> u32 {
112    let exponent = raw >> 12;
113    let mantissa = u32::from(raw & 0x0FFF);
114    (10u32 << exponent) * mantissa
115}
116
117/// Decodes a result or limit register word to lux.
118///
119/// # Arguments
120///
121/// * `raw` - the 16-bit register word.
122///
123/// # Returns
124///
125/// The illuminance in lux.
126pub fn lux(raw: u16) -> f32 {
127    milli_lux(raw) as f32 / 1000.0
128}
129
130/// Encodes an illuminance into the result and limit registers' exponent-and-mantissa
131/// word, at the smallest exponent that holds the value.
132///
133/// The inverse of [`milli_lux`]. The smallest exponent gives the finest LSB, so the
134/// encoded threshold is the closest one the part can compare against; the mantissa is
135/// truncated to that LSB. An illuminance above the largest full scale, 83865.60 lux,
136/// saturates to [`HIGH_LIMIT_RESET`], the largest encodable word.
137///
138/// # Arguments
139///
140/// * `milli_lux` - the illuminance in milli-lux.
141///
142/// # Returns
143///
144/// The 16-bit register word.
145pub fn raw_from_milli_lux(milli_lux: u32) -> u16 {
146    for exponent in 0..=RANGE_MAX {
147        let mantissa = milli_lux / (10u32 << exponent);
148        if mantissa <= 0x0FFF {
149            return (u16::from(exponent) << 12) | mantissa as u16;
150        }
151    }
152    HIGH_LIMIT_RESET
153}
154
155/// Assembles a register word from the two bytes the part sends, most significant
156/// byte first.
157///
158/// # Arguments
159///
160/// * `bytes` - the two data bytes of a register read.
161///
162/// # Returns
163///
164/// The 16-bit register word.
165pub fn word_from_bytes(bytes: [u8; 2]) -> u16 {
166    u16::from_be_bytes(bytes)
167}
168
169/// Splits a register word into the two bytes written to the part, most significant
170/// byte first.
171///
172/// # Arguments
173///
174/// * `word` - the 16-bit register word.
175///
176/// # Returns
177///
178/// The two data bytes of a register write.
179pub fn word_to_bytes(word: u16) -> [u8; 2] {
180    word.to_be_bytes()
181}
182
183/// The conversion time (configuration bit 11).
184#[derive(Clone, Copy, Debug, PartialEq, Eq)]
185pub enum ConversionTime {
186    /// 100 ms; on ranges `0` to `5` this drops one to three bits of resolution.
187    Ms100,
188    /// 800 ms, the full specified resolution on every range (the default).
189    Ms800,
190}
191
192impl ConversionTime {
193    /// Returns the conversion time in milliseconds.
194    pub fn millis(self) -> u16 {
195        match self {
196            ConversionTime::Ms100 => 100,
197            ConversionTime::Ms800 => 800,
198        }
199    }
200}
201
202/// The mode of conversion operation (configuration bits 10:9).
203#[derive(Clone, Copy, Debug, PartialEq, Eq)]
204pub enum Mode {
205    /// Shutdown; the flags and INT pin keep their last state (the default).
206    Shutdown,
207    /// One conversion, after which the field reads back as shutdown.
208    SingleShot,
209    /// Continuous conversions.
210    Continuous,
211}
212
213impl Mode {
214    /// Returns the 2-bit field code for this mode.
215    pub fn code(self) -> u8 {
216        match self {
217            Mode::Shutdown => 0b00,
218            Mode::SingleShot => 0b01,
219            Mode::Continuous => 0b10,
220        }
221    }
222
223    /// Builds a mode from a 2-bit field code.
224    ///
225    /// Codes `10` and `11` both select continuous conversion and map to
226    /// [`Mode::Continuous`].
227    pub fn from_code(code: u8) -> Mode {
228        match code & 0b11 {
229            0b00 => Mode::Shutdown,
230            0b01 => Mode::SingleShot,
231            _ => Mode::Continuous,
232        }
233    }
234}
235
236/// The interrupt reporting style, the latch field (configuration bit 4).
237#[derive(Clone, Copy, Debug, PartialEq, Eq)]
238pub enum Latch {
239    /// Transparent hysteresis-style comparison: the INT pin and flags follow the
240    /// comparison directly, with no clearing event.
241    TransparentHysteresis,
242    /// Latched window-style comparison: the INT pin and flags hold until the
243    /// configuration register is read (the default).
244    LatchedWindow,
245}
246
247/// The INT pin polarity (configuration bit 3).
248#[derive(Clone, Copy, Debug, PartialEq, Eq)]
249pub enum Polarity {
250    /// INT pulls low on an interrupt event (the default).
251    ActiveLow,
252    /// INT goes high impedance on an interrupt event, to be pulled high.
253    ActiveHigh,
254}
255
256/// The number of consecutive fault events that trigger a report (configuration
257/// bits 1:0).
258#[derive(Clone, Copy, Debug, PartialEq, Eq)]
259pub enum FaultCount {
260    /// One fault (the default).
261    One,
262    /// Two consecutive faults.
263    Two,
264    /// Four consecutive faults.
265    Four,
266    /// Eight consecutive faults.
267    Eight,
268}
269
270impl FaultCount {
271    /// Returns the 2-bit field code for this fault count.
272    pub fn code(self) -> u8 {
273        match self {
274            FaultCount::One => 0b00,
275            FaultCount::Two => 0b01,
276            FaultCount::Four => 0b10,
277            FaultCount::Eight => 0b11,
278        }
279    }
280
281    /// Builds a fault count from a 2-bit field code.
282    pub fn from_code(code: u8) -> FaultCount {
283        match code & 0b11 {
284            0b00 => FaultCount::One,
285            0b01 => FaultCount::Two,
286            0b10 => FaultCount::Four,
287            _ => FaultCount::Eight,
288        }
289    }
290
291    /// Returns the number of consecutive faults this setting requires.
292    pub fn count(self) -> u8 {
293        match self {
294            FaultCount::One => 1,
295            FaultCount::Two => 2,
296            FaultCount::Four => 4,
297            FaultCount::Eight => 8,
298        }
299    }
300}
301
302/// A decoded OPT3001 configuration register.
303///
304/// Build one, set the fields, and turn it into the 16-bit register value with
305/// [`bits`](Configuration::bits); or parse a register read with
306/// [`from_bits`](Configuration::from_bits). [`Configuration::default`] is the power-on
307/// state, [`CONFIGURATION_RESET`]. The four status fields are read-only on the part
308/// and are ignored by [`bits`](Configuration::bits).
309///
310/// # Examples
311///
312/// ```
313/// use pamoja_sensors::opt3001::{Configuration, Mode};
314///
315/// // Convert continuously with the range set automatically, everything else default.
316/// let config = Configuration {
317///     mode: Mode::Continuous,
318///     ..Configuration::default()
319/// };
320/// assert_eq!(config.bits(), 0xCC10);
321/// assert_eq!(Configuration::from_bits(0xCC10), config);
322/// ```
323#[derive(Clone, Copy, Debug, PartialEq, Eq)]
324pub struct Configuration {
325    /// The range number (bits 15:12): `0` to `11` for a fixed full-scale range, or
326    /// [`RANGE_AUTOMATIC`] to let the part choose and report its choice in the
327    /// result's exponent.
328    pub range_number: u8,
329    /// The conversion time.
330    pub conversion_time: ConversionTime,
331    /// The mode of conversion operation.
332    pub mode: Mode,
333    /// Read-only: the last conversion overflowed its full-scale range.
334    pub overflow: bool,
335    /// Read-only: a conversion has completed since the register was last read or
336    /// written with a non-shutdown mode.
337    pub conversion_ready: bool,
338    /// Read-only: the result exceeded the high limit for the fault count.
339    pub flag_high: bool,
340    /// Read-only: the result fell below the low limit for the fault count.
341    pub flag_low: bool,
342    /// The interrupt reporting style.
343    pub latch: Latch,
344    /// The INT pin polarity.
345    pub polarity: Polarity,
346    /// Force the result's exponent to zero on a fixed range, so the mantissa alone
347    /// is the reading at that range's LSB.
348    pub mask_exponent: bool,
349    /// The consecutive faults required to trigger a report.
350    pub fault_count: FaultCount,
351}
352
353impl Default for Configuration {
354    fn default() -> Self {
355        Configuration {
356            range_number: RANGE_AUTOMATIC,
357            conversion_time: ConversionTime::Ms800,
358            mode: Mode::Shutdown,
359            overflow: false,
360            conversion_ready: false,
361            flag_high: false,
362            flag_low: false,
363            latch: Latch::LatchedWindow,
364            polarity: Polarity::ActiveLow,
365            mask_exponent: false,
366            fault_count: FaultCount::One,
367        }
368    }
369}
370
371impl Configuration {
372    /// Assembles the 16-bit configuration register value.
373    ///
374    /// The read-only status bits are written as zero, and the range number is
375    /// truncated to its four bits.
376    ///
377    /// # Returns
378    ///
379    /// The register value to write, most significant byte first.
380    pub fn bits(self) -> u16 {
381        let mut bits = 0u16;
382        bits |= u16::from(self.range_number & 0x0F) << 12;
383        bits |= u16::from(matches!(self.conversion_time, ConversionTime::Ms800)) << 11;
384        bits |= u16::from(self.mode.code()) << 9;
385        bits |= u16::from(matches!(self.latch, Latch::LatchedWindow)) << 4;
386        bits |= u16::from(matches!(self.polarity, Polarity::ActiveHigh)) << 3;
387        bits |= u16::from(self.mask_exponent) << 2;
388        bits |= u16::from(self.fault_count.code());
389        bits
390    }
391
392    /// Parses a 16-bit configuration register value.
393    ///
394    /// # Arguments
395    ///
396    /// * `bits` - the register value, as read from the device.
397    ///
398    /// # Returns
399    ///
400    /// The decoded configuration, status flags included.
401    pub fn from_bits(bits: u16) -> Configuration {
402        Configuration {
403            range_number: (bits >> 12) as u8,
404            conversion_time: if bits & (1 << 11) != 0 {
405                ConversionTime::Ms800
406            } else {
407                ConversionTime::Ms100
408            },
409            mode: Mode::from_code((bits >> 9) as u8),
410            overflow: bits & (1 << 8) != 0,
411            conversion_ready: bits & (1 << 7) != 0,
412            flag_high: bits & (1 << 6) != 0,
413            flag_low: bits & (1 << 5) != 0,
414            latch: if bits & (1 << 4) != 0 {
415                Latch::LatchedWindow
416            } else {
417                Latch::TransparentHysteresis
418            },
419            polarity: if bits & (1 << 3) != 0 {
420                Polarity::ActiveHigh
421            } else {
422                Polarity::ActiveLow
423            },
424            mask_exponent: bits & (1 << 2) != 0,
425            fault_count: FaultCount::from_code(bits as u8),
426        }
427    }
428
429    /// Returns whether the range number selects automatic full-scale setting.
430    pub fn is_automatic_range(&self) -> bool {
431        self.range_number == RANGE_AUTOMATIC
432    }
433}
434
435#[cfg(test)]
436mod tests {
437    use super::*;
438
439    #[test]
440    fn addresses_follow_the_addr_pin_table() {
441        // Table 1: 1000100b with ADDR to GND, then VDD, SDA, SCL.
442        assert_eq!(I2C_ADDRESS_GND, 0b100_0100);
443        assert_eq!(I2C_ADDRESS_VDD, 0b100_0101);
444        assert_eq!(I2C_ADDRESS_SDA, 0b100_0110);
445        assert_eq!(I2C_ADDRESS_SCL, 0b100_0111);
446    }
447
448    #[test]
449    fn register_map_and_ids_match_the_datasheet() {
450        // Table 6 register map; Tables 14 and 15 for the ID values.
451        assert_eq!(register::RESULT, 0x00);
452        assert_eq!(register::CONFIGURATION, 0x01);
453        assert_eq!(register::LOW_LIMIT, 0x02);
454        assert_eq!(register::HIGH_LIMIT, 0x03);
455        assert_eq!(register::MANUFACTURER_ID, 0x7E);
456        assert_eq!(register::DEVICE_ID, 0x7F);
457        assert_eq!(MANUFACTURER_ID, 0x5449);
458        assert_eq!(MANUFACTURER_ID.to_be_bytes(), *b"TI");
459        assert_eq!(DEVICE_ID, 0x3001);
460    }
461
462    #[test]
463    fn result_register_decodes_per_the_datasheet_examples() {
464        // Table 9, every row: register word, LSB weight, and resulting lux.
465        let rows: [(u16, u32, u32); 10] = [
466            (0x0001, 10, 10),
467            (0x0FFF, 10, 40_950),
468            (0x3456, 80, 88_800),
469            (0x789A, 1_280, 2_818_560),
470            (0x8800, 2_560, 5_242_880),
471            (0x9400, 5_120, 5_242_880),
472            (0xA200, 10_240, 5_242_880),
473            (0xB100, 20_480, 5_242_880),
474            (0xB001, 20_480, 20_480),
475            (0xBFFF, 20_480, 83_865_600),
476        ];
477        for (raw, lsb, expected) in rows {
478            assert_eq!(
479                lsb_milli_lux((raw >> 12) as u8),
480                Some(lsb),
481                "lsb {raw:#06x}"
482            );
483            assert_eq!(milli_lux(raw), expected, "milli-lux {raw:#06x}");
484            assert!(
485                (lux(raw) - expected as f32 / 1000.0).abs() < 0.001,
486                "lux {raw:#06x}"
487            );
488        }
489    }
490
491    #[test]
492    fn full_scale_table_matches_each_range_number() {
493        // Table 8: full-scale range and LSB size for exponents 0000b to 1011b.
494        let rows: [(u8, u32, u32); 12] = [
495            (0, 40_950, 10),
496            (1, 81_900, 20),
497            (2, 163_800, 40),
498            (3, 327_600, 80),
499            (4, 655_200, 160),
500            (5, 1_310_400, 320),
501            (6, 2_620_800, 640),
502            (7, 5_241_600, 1_280),
503            (8, 10_483_200, 2_560),
504            (9, 20_966_400, 5_120),
505            (10, 41_932_800, 10_240),
506            (11, 83_865_600, 20_480),
507        ];
508        for (range, full_scale, lsb) in rows {
509            assert_eq!(
510                full_scale_milli_lux(range),
511                Some(full_scale),
512                "range {range}"
513            );
514            assert_eq!(lsb_milli_lux(range), Some(lsb), "range {range}");
515            assert_eq!(milli_lux((u16::from(range) << 12) | 0x0FFF), full_scale);
516        }
517        // The automatic and reserved range numbers have no full scale of their own.
518        for range in RANGE_AUTOMATIC..=0x0F {
519            assert_eq!(full_scale_milli_lux(range), None);
520            assert_eq!(lsb_milli_lux(range), None);
521        }
522        // Electrical Characteristics: 0.01 lux resolution, 83865.6 lux full scale.
523        assert_eq!(lsb_milli_lux(0), Some(10));
524        assert_eq!(full_scale_milli_lux(RANGE_MAX), Some(83_865_600));
525    }
526
527    #[test]
528    fn encoder_picks_the_smallest_exponent_that_holds_the_value() {
529        // Table 9 lists four words for 5242.88 lux; 08h/800h is the smallest exponent.
530        assert_eq!(raw_from_milli_lux(5_242_880), 0x8800);
531        // 88.80 lux fits a 0.04 lux LSB, one step finer than Table 9's 03h/456h.
532        assert_eq!(raw_from_milli_lux(88_800), 0x28AC);
533        assert_eq!(milli_lux(0x28AC), 88_800);
534        assert_eq!(raw_from_milli_lux(10), 0x0001);
535        assert_eq!(raw_from_milli_lux(40_950), 0x0FFF);
536        assert_eq!(raw_from_milli_lux(40_960), 0x1800);
537        assert_eq!(raw_from_milli_lux(0), 0x0000);
538        // The largest encodable threshold, and saturation above it.
539        assert_eq!(raw_from_milli_lux(83_865_600), HIGH_LIMIT_RESET);
540        assert_eq!(raw_from_milli_lux(83_865_601), HIGH_LIMIT_RESET);
541        assert_eq!(raw_from_milli_lux(u32::MAX), HIGH_LIMIT_RESET);
542        // A value between two LSB steps truncates to the step below.
543        assert_eq!(raw_from_milli_lux(15), 0x0001);
544    }
545
546    #[test]
547    fn every_result_word_survives_a_round_trip_through_the_encoder() {
548        for raw in 0..=HIGH_LIMIT_RESET {
549            let value = milli_lux(raw);
550            let encoded = raw_from_milli_lux(value);
551            assert_eq!(milli_lux(encoded), value, "word {raw:#06x}");
552            assert!(
553                encoded >> 12 <= raw >> 12,
554                "word {raw:#06x} re-encoded {encoded:#06x}"
555            );
556        }
557    }
558
559    #[test]
560    fn integer_decode_tracks_the_floating_point_reference() {
561        // Equation 3, transcribed: lux = 0.01 * 2^E[3:0] * R[11:0].
562        for raw in 0..=HIGH_LIMIT_RESET {
563            let exponent = f64::from(raw >> 12);
564            let mantissa = f64::from(raw & 0x0FFF);
565            let reference = 0.01 * exponent.exp2() * mantissa;
566            let integer = f64::from(milli_lux(raw)) / 1000.0;
567            assert!(
568                (integer - reference).abs() < 1e-6,
569                "word {raw:#06x}: {integer} vs {reference}"
570            );
571        }
572    }
573
574    #[test]
575    fn register_words_travel_most_significant_byte_first() {
576        // Figures 20 and 21: data MSByte then data LSByte.
577        assert_eq!(word_from_bytes([0x34, 0x56]), 0x3456);
578        assert_eq!(word_to_bytes(0x3456), [0x34, 0x56]);
579        assert_eq!(milli_lux(word_from_bytes([0x78, 0x9A])), 2_818_560);
580        assert_eq!(word_from_bytes(word_to_bytes(0xC810)), 0xC810);
581    }
582
583    #[test]
584    fn default_configuration_is_the_datasheet_reset_value() {
585        // Section 7.6.1.1.2: configuration register reset C810h, Table 10 per field.
586        assert_eq!(Configuration::default().bits(), CONFIGURATION_RESET);
587        assert_eq!(
588            Configuration::from_bits(CONFIGURATION_RESET),
589            Configuration::default()
590        );
591        let reset = Configuration::default();
592        assert_eq!(reset.range_number, 0b1100);
593        assert!(reset.is_automatic_range());
594        assert_eq!(reset.conversion_time, ConversionTime::Ms800);
595        assert_eq!(reset.mode, Mode::Shutdown);
596        assert_eq!(reset.latch, Latch::LatchedWindow);
597        assert_eq!(reset.polarity, Polarity::ActiveLow);
598        assert!(!reset.mask_exponent);
599        assert_eq!(reset.fault_count, FaultCount::One);
600    }
601
602    #[test]
603    fn limit_registers_reset_per_the_datasheet() {
604        // Table 11: LE = 0h, TL = 000h. Table 13: HE = Bh, TH = FFFh.
605        assert_eq!(LOW_LIMIT_RESET, 0x0000);
606        assert_eq!(milli_lux(LOW_LIMIT_RESET), 0);
607        assert_eq!(HIGH_LIMIT_RESET, 0xBFFF);
608        assert_eq!(milli_lux(HIGH_LIMIT_RESET), 83_865_600);
609        // Section 7.4.2.3: end-of-conversion mode is LE[3:2] = 11b.
610        assert_eq!(LOW_LIMIT_END_OF_CONVERSION >> 14, 0b11);
611    }
612
613    #[test]
614    fn configuration_field_codes_match_the_datasheet() {
615        // Table 10: CT 1 = 800 ms; M 01 = single-shot, 10 and 11 = continuous;
616        // FC 00, 01, 10, 11 = one, two, four, eight faults.
617        assert_eq!(ConversionTime::Ms100.millis(), 100);
618        assert_eq!(ConversionTime::Ms800.millis(), 800);
619        assert_eq!(Mode::SingleShot.code(), 0b01);
620        assert_eq!(Mode::from_code(0b10), Mode::Continuous);
621        assert_eq!(Mode::from_code(0b11), Mode::Continuous);
622        assert_eq!(FaultCount::Eight.code(), 0b11);
623        assert_eq!(FaultCount::from_code(0b10).count(), 4);
624        // Continuous conversion on the automatic range is the reset word with M = 10b.
625        let continuous = Configuration {
626            mode: Mode::Continuous,
627            ..Configuration::default()
628        };
629        assert_eq!(continuous.bits(), 0xCC10);
630        // A read with OVF, CRF, and FH set: the flags decode and are not written back.
631        let status = Configuration::from_bits(0xCDD0);
632        assert!(status.overflow);
633        assert!(status.conversion_ready);
634        assert!(status.flag_high);
635        assert!(!status.flag_low);
636        assert_eq!(status.bits(), 0xCC10);
637    }
638
639    #[test]
640    fn configuration_round_trips_through_bits() {
641        let config = Configuration {
642            range_number: 6,
643            conversion_time: ConversionTime::Ms100,
644            mode: Mode::SingleShot,
645            latch: Latch::TransparentHysteresis,
646            polarity: Polarity::ActiveHigh,
647            mask_exponent: true,
648            fault_count: FaultCount::Four,
649            ..Configuration::default()
650        };
651        assert_eq!(config.bits(), 0x620E);
652        assert_eq!(Configuration::from_bits(config.bits()), config);
653        assert!(!config.is_automatic_range());
654        assert_eq!(full_scale_milli_lux(config.range_number), Some(2_620_800));
655    }
656}