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}