vl53l4cd_ulp/
lib.rs

1//! # VL53L4CD Ultra-Low-Power Time-of-Flight Distance Sensor Driver
2//!
3//! This crate provides a `no_std` driver for ST-Microelectronics' VL53L4CD ultra-low-power
4//! time-of-flight distance sensor in both sync and async variants.
5//!
6//! ## Basic Usage
7//!
8//! ```rust,no_run
9//! use vl53l4cd_ulp::VL53L4cd;
10//!
11//! let i2c = embedded_hal_mock::eh1::i2c::Mock::new(&[]);
12//! let delay = embedded_hal_mock::eh1::delay::NoopDelay;
13//! let mut sensor = VL53L4cd::new(i2c, delay);
14//!
15//! sensor.sensor_init().unwrap();
16//! sensor.start_ranging().unwrap();
17//!
18//! // Wait for data ready (you can connect a GPIO to the interrupt pin to detect when new data is available)
19//! if sensor.check_for_data_ready().unwrap() {
20//!     let measurement = sensor.get_estimated_measurement().unwrap();
21//!     println!("Distance: {} mm", measurement.estimated_distance_mm);
22//!     sensor.clear_interrupt().unwrap();
23//! }
24//! ```
25#![no_std]
26#![warn(missing_docs)]
27#![warn(clippy::all)]
28#![warn(clippy::pedantic)]
29
30mod fmt; // <-- must be first module!
31
32#[cfg(not(feature = "async"))]
33use embedded_hal::{delay::DelayNs, i2c::I2c};
34#[cfg(feature = "async")]
35use embedded_hal_async::{delay::DelayNs, i2c::I2c};
36
37// This is the initialization sequence for the VL53L4CD from the Ultra Low Power Driver
38const VL53L4CD_DEFAULT_CONFIGURATION: [u8; 91] = [
39    0x00, /* 0x2d */
40    0x00, /* 0x2e */
41    0x00, /* 0x2f */
42    0x11, /* 0x30 */
43    0x02, /* 0x31 */
44    0x00, /* 0x32 */
45    0x02, /* 0x33 */
46    0x08, /* 0x34 */
47    0x00, /* 0x35 */
48    0x08, /* 0x36 */
49    0x10, /* 0x37 */
50    0x01, /* 0x38 */
51    0x01, /* 0x39 */
52    0x00, /* 0x3a */
53    0x00, /* 0x3b */
54    0x00, /* 0x3c */
55    0x00, /* 0x3d */
56    0xff, /* 0x3e */
57    0x00, /* 0x3f */
58    0x0F, /* 0x40 */
59    0x00, /* 0x41 */
60    0x00, /* 0x42 */
61    0x00, /* 0x43 */
62    0x00, /* 0x44 */
63    0x00, /* 0x45 */
64    0x20, /* 0x46 */
65    0x0b, /* 0x47 */
66    0x00, /* 0x48 */
67    0x00, /* 0x49 */
68    0x02, /* 0x4a */
69    0x14, /* 0x4b */
70    0x21, /* 0x4c */
71    0x00, /* 0x4d */
72    0x00, /* 0x4e */
73    0x05, /* 0x4f */
74    0x00, /* 0x50 */
75    0x00, /* 0x51 */
76    0x00, /* 0x52 */
77    0x00, /* 0x53 */
78    0xc8, /* 0x54 */
79    0x00, /* 0x55 */
80    0x00, /* 0x56 */
81    0x38, /* 0x57 */
82    0xff, /* 0x58 */
83    0x01, /* 0x59 */
84    0x00, /* 0x5a */
85    0x08, /* 0x5b */
86    0x00, /* 0x5c */
87    0x00, /* 0x5d */
88    0x00, /* 0x5e */
89    0x01, /* 0x5f */
90    0x07, /* 0x60 */
91    0x00, /* 0x61 */
92    0x02, /* 0x62 */
93    0x05, /* 0x63 */
94    0x00, /* 0x64 */
95    0xb4, /* 0x65 */
96    0x00, /* 0x66 */
97    0xbb, /* 0x67 */
98    0x08, /* 0x68 */
99    0x38, /* 0x69 */
100    0x00, /* 0x6a */
101    0x00, /* 0x6b */
102    0x00, /* 0x6c */
103    0x00, /* 0x6d */
104    0x0f, /* 0x6e */
105    0x89, /* 0x6f */
106    0x00, /* 0x70 */
107    0x00, /* 0x71 */
108    0x00, /* 0x72 */
109    0x00, /* 0x73 */
110    0x00, /* 0x74 */
111    0x00, /* 0x75 */
112    0x00, /* 0x76 */
113    0x01, /* 0x77 */
114    0x07, /* 0x78 */
115    0x05, /* 0x79 */
116    0x06, /* 0x7a */
117    0x06, /* 0x7b */
118    0x00, /* 0x7c */
119    0x00, /* 0x7d */
120    0x02, /* 0x7e */
121    0xc7, /* 0x7f */
122    0xff, /* 0x80 */
123    0x9B, /* 0x81 */
124    0x00, /* 0x82 */
125    0x00, /* 0x83 */
126    0x00, /* 0x84 */
127    0x01, /* 0x85 */
128    0x00, /* 0x86 */
129    0x00, /* 0x87 */
130];
131
132/// Register addresses for the VL53L4CD sensor.
133#[repr(u16)]
134#[derive(Debug, Clone, Copy, PartialEq, Eq)]
135#[allow(non_camel_case_types)]
136#[cfg_attr(feature = "defmt", derive(defmt::Format))]
137pub enum Register {
138    /// I2C slave device address register (0x0001)
139    I2cSlaveDeviceAddress = 0x0001,
140    /// VHV configuration timeout macro loop bound register (0x0008)
141    VhvConfigTimeoutMacropLoopBound = 0x0008,
142    /// GPIO HV mux control register (0x0030)
143    GpioHvMuxCtrl = 0x0030,
144    /// GPIO TIO HV status register (0x0031)
145    GpioTioHvStatus = 0x0031,
146    /// System interrupt configuration register (0x0046)
147    SystemInterrupt = 0x0046,
148    /// Range configuration A register (0x005E)
149    RangeConfigA = 0x005E,
150    /// Range configuration B register (0x0061)
151    RangeConfigB = 0x0061,
152    /// Range configuration sigma threshold register (0x0064)
153    RangeConfigSigmaThresh = 0x0064,
154    /// Minimum count rate return limit MCPS register (0x0066)
155    MinCountRateRtnLimitMcps = 0x0066,
156    /// Inter-measurement period in milliseconds register (0x006C)
157    IntermeasurementMs = 0x006C,
158    /// High threshold register (0x0072)
159    ThreshHigh = 0x0072,
160    /// Low threshold register (0x0074)
161    ThreshLow = 0x0074,
162    /// Power GO1 register (0x0083)
163    PowerGo1 = 0x0083,
164    /// Firmware enable register (0x0085)
165    FirmwareEnable = 0x0085,
166    /// System interrupt clear register (0x0086)
167    SystemInterruptClear = 0x0086,
168    /// System start register (0x0087)
169    SystemStart = 0x0087,
170    /// Result range status register (0x0089)
171    ResultRangeStatus = 0x0089,
172    /// Result SPAD number register (0x008C)
173    ResultSpadNb = 0x008C,
174    /// Result signal rate register (0x008E)
175    ResultSignalRate = 0x008E,
176    /// Result ambient rate register (0x0090)
177    ResultAmbientRate = 0x0090,
178    /// Result sigma register (0x0092)
179    ResultSigma = 0x0092,
180    /// Result distance register (0x0096)
181    ResultDistance = 0x0096,
182    /// Result oscillator calibration value register (0x00DE)
183    ResultOscCalibrateVal = 0x00DE,
184    /// Firmware system status register (0x00E5)
185    FirmwareSystemStatus = 0x00E5,
186    /// Identification model ID register (0x010F)
187    IdentificationModelId = 0x010F,
188}
189
190impl From<Register> for u16 {
191    fn from(r: Register) -> Self {
192        r as u16
193    }
194}
195
196/// Interrupt configuration options for the VL53L4CD sensor.
197#[repr(u8)]
198#[derive(Debug, Clone, Copy, PartialEq, Eq)]
199#[cfg_attr(feature = "defmt", derive(defmt::Format))]
200pub enum InterruptOn {
201    /// Interrupt triggered on level low detection
202    LevelLow,
203    /// Interrupt triggered on level high detection
204    LevelHigh,
205    /// Interrupt triggered when distance is out of threshold window
206    OutOfWindow,
207    /// Interrupt triggered when distance is out of threshold window or no target detected
208    OutOfWindowOrNoTarget,
209    /// Interrupt triggered when distance is within threshold window
210    InWindow,
211    /// Interrupt triggered when new ranging data is available
212    NewSampleReady,
213    /// Custom interrupt configuration value
214    Unknown(u8),
215}
216
217impl From<InterruptOn> for u8 {
218    fn from(interrupt_on: InterruptOn) -> Self {
219        match interrupt_on {
220            InterruptOn::LevelLow => 0,
221            InterruptOn::LevelHigh => 1,
222            InterruptOn::OutOfWindow => 2,
223            InterruptOn::OutOfWindowOrNoTarget => 0x42,
224            InterruptOn::InWindow => 3,
225            InterruptOn::NewSampleReady => 0x20,
226            InterruptOn::Unknown(value) => value,
227        }
228    }
229}
230
231impl From<u8> for InterruptOn {
232    fn from(value: u8) -> Self {
233        match value {
234            0 => InterruptOn::LevelLow,
235            1 => InterruptOn::LevelHigh,
236            2 => InterruptOn::OutOfWindow,
237            0x42 => InterruptOn::OutOfWindowOrNoTarget,
238            3 => InterruptOn::InWindow,
239            0x20 => InterruptOn::NewSampleReady,
240            _ => {
241                warn!("Unknown InterruptOn value: {}", value);
242                InterruptOn::Unknown(value)
243            }
244        }
245    }
246}
247
248/// Distance measurement result from the VL53L4CD sensor.
249#[derive(Debug, Clone, Copy)]
250#[cfg_attr(feature = "defmt", derive(defmt::Format))]
251pub struct EstimatedMeasurement {
252    /// Measurement status code indicating validity and quality
253    pub measurement_status: u8,
254    /// Estimated distance in millimeters (0-4000 mm)
255    pub estimated_distance_mm: u16,
256    /// Measurement precision (1σ) in millimeters
257    pub sigma_mm: u16,
258    /// Signal rate in kilocounts per second (kcps)
259    pub signal_kcps: u16,
260    /// Ambient light rate in kilocounts per second (kcps)
261    pub ambient_kcps: u16,
262}
263
264/// VL53L4CD ultra-low-power time-of-flight distance sensor driver.
265///
266/// This struct provides an async interface to control and read data from the VL53L4CD
267/// sensor. It manages the I2C communication, sensor configuration, and ranging operations.
268///
269/// The driver is generic over the I2C and delay implementations, allowing it to work
270/// with any embedded-hal-async compatible hardware.
271pub struct VL53L4cd<I2C, D> {
272    /// I2C interface for communication with the sensor
273    i2c: I2C,
274    /// Current I2C slave address of the sensor
275    address: u8,
276    /// Delay implementation for timing operations
277    delay: D,
278}
279
280#[maybe_async_cfg::maybe(
281    sync(cfg(not(feature = "async")), keep_self),
282    async(feature = "async", keep_self)
283)]
284impl<I2C, E, D> VL53L4cd<I2C, D>
285where
286    I2C: I2c<Error = E>,
287    E: core::fmt::Debug,
288    D: DelayNs,
289{
290    /// Creates a new VL53L4CD sensor driver instance.
291    ///
292    /// This function initializes a new sensor driver with the default I2C address (0x29)
293    /// and the provided I2C and delay implementations. The sensor is not yet initialized
294    /// and must be configured using [`sensor_init`](Self::sensor_init) before use.
295    ///
296    /// # Arguments
297    ///
298    /// * `i2c` - I2C interface implementation for sensor communication
299    /// * `delay` - Delay implementation for timing operations
300    ///
301    /// # Returns
302    ///
303    /// A new `VL53L4cd` instance with default configuration
304    ///
305    /// # Examples
306    ///
307    /// ```rust,no_run
308    /// use vl53l4cd_ulp::VL53L4cd;
309    /// use embedded_hal::{i2c::I2c, delay::DelayNs};
310    ///
311    /// let i2c = embedded_hal_mock::eh1::i2c::Mock::new(&[]);
312    /// let delay = embedded_hal_mock::eh1::delay::NoopDelay;
313    ///
314    /// let mut sensor = VL53L4cd::new(i2c, delay);
315    /// ```
316    ///
317    /// # Default Configuration
318    ///
319    /// - **I2C Address**: 0x29 (default sensor address)
320    /// - **Sensor State**: Uninitialized (must call `sensor_init()`)
321    /// - **Ranging Mode**: Stopped
322    pub fn new(i2c: I2C, delay: D) -> Self {
323        Self {
324            i2c,
325            address: 0x29,
326            delay,
327        }
328    }
329
330    /// Sets the I2C address of the sensor.
331    ///
332    /// This function writes a new I2C slave address to the sensor's internal register.
333    /// The new address will take effect after the sensor is reset or reinitialized.
334    ///
335    /// **Note**: The address change only takes effect when the sensor is in a reset state.
336    /// According to the VL53L4CD Application Note, to change the I2C address, the host must:
337    /// 1. Put the device in HW standby by setting the XSHUT pin low
338    /// 2. Raise the XSHUT pin
339    /// 3. Call `set_i2c_address(new_address)` to program the new address,
340    /// 4. call `sensor_init()` to initialize the sensor on the new address
341    ///
342    /// The current driver instance will continue to use the old address until reinitialization.
343    ///
344    /// # Arguments
345    ///
346    /// * `address` - The new 7-bit I2C address
347    ///
348    /// # Returns
349    ///
350    /// * `Ok(())` - If the address was set successfully
351    ///
352    /// # Errors
353    ///
354    /// * `Err(Error::I2cError(E))` - If there was an I2C communication error
355    ///
356    /// # Examples
357    ///
358    /// ```rust,no_run
359    /// use vl53l4cd_ulp::VL53L4cd;
360    ///
361    /// let i2c = embedded_hal_mock::eh1::i2c::Mock::new(&[]);
362    /// let delay = embedded_hal_mock::eh1::delay::NoopDelay;
363    /// let mut sensor = VL53L4cd::new(i2c, delay);
364    ///
365    /// // Change sensor address to 0x30
366    /// sensor.set_i2c_address(0x30).unwrap();
367    ///
368    /// // Reinitialize to use new address
369    /// sensor.sensor_init().unwrap();
370    /// ```
371    pub async fn set_i2c_address(&mut self, address: u8) -> Result<(), Error<E>> {
372        self.write_byte(Register::I2cSlaveDeviceAddress, address)
373            .await?;
374        self.address = address;
375        Ok(())
376    }
377
378    /// Retrieves the sensor identification model ID.
379    ///
380    /// This function reads the sensor's model identification register to verify
381    /// that the correct sensor is connected and responding. The VL53L4CD should
382    /// return a specific model ID value.
383    ///
384    /// # Returns
385    ///
386    /// * `Ok(u16)` - The sensor model ID (expected value for VL53L4CD)
387    ///
388    /// # Errors
389    ///
390    /// * `Err(Error::I2cError(E))` - If there was an I2C communication error
391    ///
392    /// # Examples
393    ///
394    /// ```rust,no_run
395    /// use vl53l4cd_ulp::VL53L4cd;
396    ///
397    /// let i2c = embedded_hal_mock::eh1::i2c::Mock::new(&[]);
398    /// let delay = embedded_hal_mock::eh1::delay::NoopDelay;
399    /// let mut sensor = VL53L4cd::new(i2c, delay);
400    ///
401    /// sensor.sensor_init().unwrap();
402    /// let sensor_id = sensor.get_sensor_id().unwrap();
403    /// println!("Sensor ID: 0x{:04X}", sensor_id);
404    ///
405    /// // Verify it's the correct sensor
406    /// if sensor_id == 0xEACC { // Expected VL53L4CD model ID
407    ///     println!("VL53L4CD sensor detected");
408    /// } else {
409    ///     println!("Unexpected sensor ID: 0x{:04X}", sensor_id);
410    /// }
411    /// ```
412    pub async fn get_sensor_id(&mut self) -> Result<u16, Error<E>> {
413        let id = self.read_word(Register::IdentificationModelId).await?;
414        Ok(id)
415    }
416
417    /// Initializes the VL53L4CD sensor for operation.
418    ///
419    /// This function performs the complete sensor initialization sequence.
420    ///
421    /// **Important**: This function must be called before any ranging operations.
422    /// The sensor will not function correctly without proper initialization.
423    ///
424    /// # Returns
425    ///
426    /// * `Ok(())` - If the sensor was initialized successfully
427    ///
428    /// # Errors
429    ///
430    /// * `Err(Error::Timeout)` - If the sensor did not boot within 1 second
431    /// * `Err(Error::I2cError(E))` - If there was an I2C communication error
432    ///
433    /// # Examples
434    ///
435    /// ```rust,no_run
436    /// use vl53l4cd_ulp::VL53L4cd;
437    ///
438    /// let i2c = embedded_hal_mock::eh1::i2c::Mock::new(&[]);
439    /// let delay = embedded_hal_mock::eh1::delay::NoopDelay;
440    /// let mut sensor = VL53L4cd::new(i2c, delay);
441    ///
442    /// // Initialize the sensor
443    /// sensor.sensor_init().unwrap();
444    /// println!("Sensor initialized successfully");
445    ///
446    /// // Now the sensor is ready for ranging operations
447    /// sensor.start_ranging().unwrap();
448    /// ```
449    pub async fn sensor_init(&mut self) -> Result<(), Error<E>> {
450        const BOOT_STATUS: u8 = 0x3;
451        let mut attempts = 0u16;
452
453        info!("Waiting for sensor to boot");
454        // Wait for sensor to boot
455        loop {
456            let status = self.read_byte(Register::FirmwareSystemStatus).await?;
457
458            if status == BOOT_STATUS {
459                break Ok(());
460            }
461
462            attempts += 1;
463            if attempts >= 1000 {
464                break Err(Error::Timeout);
465            }
466
467            self.delay.delay_ms(1).await;
468        }?;
469
470        // Load default configuration
471        info!("Loading default configuration");
472        for (i, &value) in VL53L4CD_DEFAULT_CONFIGURATION.iter().enumerate() {
473            #[allow(clippy::cast_possible_truncation)]
474            self.write_byte(i as u16 + 0x2D, value).await?;
475        }
476
477        // Start VHV
478        info!("Starting VHV");
479        self.write_byte(Register::SystemStart, 0x40).await?;
480
481        // Wait for data ready
482        info!("Waiting for data ready");
483        let mut attempts = 0u16;
484        loop {
485            let status = self.check_for_data_ready().await?;
486            if status {
487                break Ok(());
488            }
489
490            attempts += 1;
491            if attempts >= 1000 {
492                break Err(Error::Timeout);
493            }
494
495            self.delay.delay_ms(1).await;
496        }?;
497
498        self.clear_interrupt().await?;
499        self.stop_ranging().await?;
500        self.write_byte(Register::VhvConfigTimeoutMacropLoopBound, 0x09)
501            .await?;
502        self.write_byte(0x0Bu16, 0x00).await?;
503        self.write_word(0x0024u16, 0x500).await?;
504        self.write_byte(0x81u16, 0b1000_1010).await?;
505        self.write_byte(0x004Bu16, 0x03).await?;
506        self.set_inter_measurement_in_ms(1000).await?;
507        Ok(())
508    }
509
510    /// Checks if new ranging data is ready for retrieval.
511    ///
512    /// This function determines whether the sensor has completed a ranging measurement
513    /// and new data is available. It checks the interrupt status by first determining
514    /// the interrupt polarity configuration, then reading the actual interrupt status.
515    ///
516    /// **Note**: This function only checks the status - it does not clear the interrupt.
517    /// Use [`clear_interrupt`](Self::clear_interrupt) after reading the data to reset
518    /// the interrupt condition.
519    ///
520    /// # Returns
521    ///
522    /// * `Ok(true)` - New ranging data is available
523    /// * `Ok(false)` - No new data available yet
524    ///
525    /// # Errors
526    ///
527    /// * `Err(Error::I2cError(E))` - If there was an I2C communication error
528    ///
529    /// # Examples
530    ///
531    /// ```rust,no_run
532    /// use vl53l4cd_ulp::VL53L4cd;
533    ///
534    /// let i2c = embedded_hal_mock::eh1::i2c::Mock::new(&[]);
535    /// let delay = embedded_hal_mock::eh1::delay::NoopDelay;
536    /// let mut sensor = VL53L4cd::new(i2c, delay);
537    ///
538    /// sensor.sensor_init().unwrap();
539    /// // Start ranging
540    /// sensor.start_ranging().unwrap();
541    ///
542    /// // Poll for data ready
543    /// loop {
544    ///     if sensor.check_for_data_ready().unwrap() {
545    ///         let measurement = sensor.get_estimated_measurement().unwrap();
546    ///         println!("Distance: {} mm", measurement.estimated_distance_mm);
547    ///         sensor.clear_interrupt().unwrap();
548    ///         break;
549    ///     }
550    /// }
551    /// ```
552    pub async fn check_for_data_ready(&mut self) -> Result<bool, Error<E>> {
553        // first check the interrupt polarity
554        let interrupt_polarity = self.read_byte(Register::GpioHvMuxCtrl).await?;
555        let interrupt_polarity = u8::from(interrupt_polarity & 0x10 == 0);
556
557        // then check the interrupt status
558        let interrupt_status = self.read_byte(Register::GpioTioHvStatus).await?;
559        if interrupt_status & 1 == interrupt_polarity {
560            Ok(true)
561        } else {
562            Ok(false)
563        }
564    }
565
566    /// Clears the sensor interrupt flag.
567    ///
568    /// This function clears the interrupt condition by writing to the interrupt clear
569    /// register. It should be called after reading measurement data to reset the
570    /// interrupt state and prepare for the next measurement.
571    ///
572    /// **Important**: Always call this function after reading data when using
573    /// interrupt-driven operation. Failure to clear the interrupt will prevent
574    /// new interrupts from being generated.
575    ///
576    /// # Returns
577    ///
578    /// * `Ok(())` - If the interrupt was cleared successfully
579    ///
580    /// # Errors
581    ///
582    /// * `Err(Error::I2cError(E))` - If there was an I2C communication error
583    ///
584    /// # Examples
585    ///
586    /// ```rust,no_run
587    /// use vl53l4cd_ulp::VL53L4cd;
588    ///
589    /// let i2c = embedded_hal_mock::eh1::i2c::Mock::new(&[]);
590    /// let delay = embedded_hal_mock::eh1::delay::NoopDelay;
591    /// let mut sensor = VL53L4cd::new(i2c, delay);
592    ///
593    /// sensor.sensor_init().unwrap();
594    /// // Check if data is ready
595    /// if sensor.check_for_data_ready().unwrap() {
596    ///     // Read the measurement data
597    ///     let measurement = sensor.get_estimated_measurement().unwrap();
598    ///     println!("Distance: {} mm", measurement.estimated_distance_mm);
599    ///     
600    ///     // Clear the interrupt to prepare for next measurement
601    ///     sensor.clear_interrupt().unwrap();
602    /// }
603    /// ```
604    pub async fn clear_interrupt(&mut self) -> Result<(), Error<E>> {
605        self.write_byte(Register::SystemInterruptClear, 0x01).await
606    }
607
608    /// Starts a single-shot ranging measurement.
609    ///
610    /// This function initiates a single ranging measurement. The sensor will perform
611    /// one complete ranging cycle and then automatically stop. This mode is useful
612    /// for applications that need occasional distance measurements without continuous
613    /// operation.
614    ///
615    /// **Note**: The sensor will automatically stop ranging after completing the measurement.
616    /// No need to call [`stop_ranging`](Self::stop_ranging) for single-shot mode.
617    ///
618    /// # Returns
619    ///
620    /// * `Ok(())` - If ranging was started successfully
621    ///
622    /// # Errors
623    ///
624    /// * `Err(Error::I2cError(E))` - If there was an I2C communication error
625    ///
626    /// # Examples
627    ///
628    /// ```rust,no_run
629    /// use vl53l4cd_ulp::VL53L4cd;
630    ///
631    /// let i2c = embedded_hal_mock::eh1::i2c::Mock::new(&[]);
632    /// let delay = embedded_hal_mock::eh1::delay::NoopDelay;
633    /// let mut sensor = VL53L4cd::new(i2c, delay);
634    ///
635    /// sensor.sensor_init().unwrap();
636    /// // Start single-shot ranging
637    /// sensor.start_ranging_single_shot().unwrap();
638    ///
639    /// // Wait for data to be ready
640    /// loop {
641    ///     if sensor.check_for_data_ready().unwrap() {
642    ///         let measurement = sensor.get_estimated_measurement().unwrap();
643    ///         println!("Single-shot distance: {} mm", measurement.estimated_distance_mm);
644    ///         sensor.clear_interrupt().unwrap();
645    ///         break;
646    ///     }
647    /// }
648    /// ```
649    pub async fn start_ranging_single_shot(&mut self) -> Result<(), Error<E>> {
650        self.write_byte(Register::SystemStart, 0x10).await
651    }
652
653    /// Starts continuous ranging measurements.
654    ///
655    /// This function initiates continuous ranging mode where the sensor performs
656    /// measurements continuously at the configured inter-measurement interval.
657    /// The sensor will continue ranging until explicitly stopped with
658    /// [`stop_ranging`](Self::stop_ranging).
659    ///
660    /// # Returns
661    ///
662    /// * `Ok(())` - If ranging was started successfully
663    ///
664    /// # Errors
665    ///
666    /// * `Err(Error::I2cError(E))` - If there was an I2C communication error
667    ///
668    /// # Examples
669    ///
670    /// ```rust,no_run
671    /// use vl53l4cd_ulp::VL53L4cd;
672    ///
673    /// let i2c = embedded_hal_mock::eh1::i2c::Mock::new(&[]);
674    /// let delay = embedded_hal_mock::eh1::delay::NoopDelay;
675    /// let mut sensor = VL53L4cd::new(i2c, delay);
676    ///
677    /// sensor.sensor_init().unwrap();
678    /// // Start continuous ranging
679    /// sensor.start_ranging().unwrap();
680    ///
681    /// // Continuous measurement loop
682    /// for _ in 0..10 {
683    ///     if sensor.check_for_data_ready().unwrap() {
684    ///         let measurement = sensor.get_estimated_measurement().unwrap();
685    ///         println!("Distance: {} mm", measurement.estimated_distance_mm);
686    ///         sensor.clear_interrupt().unwrap();
687    ///     }
688    /// }
689    ///
690    /// // Stop ranging when done
691    /// sensor.stop_ranging().unwrap();
692    /// ```
693    pub async fn start_ranging(&mut self) -> Result<(), Error<E>> {
694        self.write_byte(Register::SystemStart, 0x40).await
695    }
696
697    /// Stops ranging measurements.
698    ///
699    /// This function stops the sensor from performing ranging measurements.
700    /// It should be called when ranging is no longer needed to conserve power
701    /// and prepare the sensor for low-power modes.
702    ///
703    /// # Returns
704    ///
705    /// * `Ok(())` - If ranging was stopped successfully
706    ///
707    /// # Errors
708    ///
709    /// * `Err(Error::I2cError(E))` - If there was an I2C communication error
710    ///
711    /// # Examples
712    ///
713    /// ```rust,no_run
714    /// use vl53l4cd_ulp::VL53L4cd;
715    ///
716    /// let i2c = embedded_hal_mock::eh1::i2c::Mock::new(&[]);
717    /// let delay = embedded_hal_mock::eh1::delay::NoopDelay;
718    /// let mut sensor = VL53L4cd::new(i2c, delay);
719    ///
720    /// sensor.sensor_init().unwrap();
721    /// // Start ranging
722    /// sensor.start_ranging().unwrap();
723    ///
724    /// // Perform some measurements
725    /// for _ in 0..5 {
726    ///     if sensor.check_for_data_ready().unwrap() {
727    ///         let measurement = sensor.get_estimated_measurement().unwrap();
728    ///         println!("Distance: {} mm", measurement.estimated_distance_mm);
729    ///         sensor.clear_interrupt().unwrap();
730    ///     }
731    /// }
732    ///
733    /// // Stop ranging when done
734    /// sensor.stop_ranging().unwrap();
735    /// ```
736    pub async fn stop_ranging(&mut self) -> Result<(), Error<E>> {
737        self.write_byte(Register::SystemStart, 0x00).await
738    }
739
740    /// Get the estimated measurement from the sensor.
741    ///
742    /// This function reads all the measurement data from the sensor's result registers
743    /// and returns a comprehensive measurement result including distance, quality
744    /// indicators, and environmental data.
745    ///
746    /// **Note**: This function should only be called after confirming that new data
747    /// is available using [`check_for_data_ready`](Self::check_for_data_ready) or
748    /// using an interrupt pin to detect when new data is available.
749    ///
750    /// # Returns
751    ///
752    /// * `Ok(EstimatedMeasurement)` - The complete measurement data
753    ///
754    /// # Errors
755    ///
756    /// * `Err(Error::I2cError(E))` - If there was an I2C communication error
757    ///
758    /// # Examples
759    ///
760    /// ```rust,no_run
761    /// use vl53l4cd_ulp::VL53L4cd;
762    ///
763    /// let i2c = embedded_hal_mock::eh1::i2c::Mock::new(&[]);
764    /// let delay = embedded_hal_mock::eh1::delay::NoopDelay;
765    /// let mut sensor = VL53L4cd::new(i2c, delay);
766    ///
767    /// sensor.sensor_init().unwrap();
768    /// if sensor.check_for_data_ready().unwrap() {
769    ///     let measurement = sensor.get_estimated_measurement().unwrap();
770    ///     
771    ///     if measurement.measurement_status == 0 {
772    ///         println!("Distance: {} mm", measurement.estimated_distance_mm);
773    ///         println!("Signal strength: {} kcps", measurement.signal_kcps);
774    ///         println!("Ambient light: {} kcps", measurement.ambient_kcps);
775    ///         println!("Measurement precision: ±{} mm", measurement.sigma_mm);
776    ///     } else {
777    ///         println!("Measurement failed with status: {}", measurement.measurement_status);
778    ///     }
779    ///     
780    ///     sensor.clear_interrupt().unwrap();
781    /// }
782    /// ```
783    pub async fn get_estimated_measurement(&mut self) -> Result<EstimatedMeasurement, Error<E>> {
784        const STATUS_RTN: [u8; 24] = [
785            255, 255, 255, 5, 2, 4, 1, 7, 3, 0, 255, 255, 9, 13, 255, 255, 255, 255, 10, 6, 255,
786            255, 11, 12,
787        ];
788
789        let mut measurement_status = self.read_byte(Register::ResultRangeStatus).await? & 0x1f;
790        if measurement_status < 24 {
791            measurement_status = STATUS_RTN[measurement_status as usize];
792        }
793        let estimated_distance_mm = self.read_word(Register::ResultDistance).await?;
794        let sigma_mm = self.read_word(Register::ResultSigma).await? / 4;
795        let signal_kcps = self.read_word(Register::ResultSignalRate).await? * 8;
796        let ambient_kcps = self.read_word(Register::ResultAmbientRate).await? * 8;
797        Ok(EstimatedMeasurement {
798            measurement_status,
799            estimated_distance_mm,
800            sigma_mm,
801            signal_kcps,
802            ambient_kcps,
803        })
804    }
805
806    /// Sets the macro timing for ranging measurements.
807    ///
808    /// This function configures the timing parameters that control the duration
809    /// and precision of ranging measurements. Higher values provide better
810    /// precision but increase measurement time and power consumption.
811    ///
812    /// # Arguments
813    ///
814    /// * `macro_timing` - Macro timing value (1-255)
815    ///
816    /// # Returns
817    ///
818    /// * `Ok(())` - If the timing was set successfully
819    ///
820    /// # Errors
821    ///
822    /// * `Err(Error::InvalidArgument)` - If the value is outside valid range
823    /// * `Err(Error::I2cError(E))` - If there was an I2C communication error
824    ///
825    /// # Examples
826    ///
827    /// ```rust,no_run
828    /// use vl53l4cd_ulp::VL53L4cd;
829    ///
830    /// let i2c = embedded_hal_mock::eh1::i2c::Mock::new(&[]);
831    /// let delay = embedded_hal_mock::eh1::delay::NoopDelay;
832    /// let mut sensor = VL53L4cd::new(i2c, delay);
833    ///
834    /// sensor.sensor_init().unwrap();
835    /// // Set macro timing for high precision
836    /// sensor.set_macro_timing(200).unwrap();
837    ///
838    /// // Start ranging with new timing
839    /// sensor.start_ranging().unwrap();
840    /// ```
841    pub async fn set_macro_timing(&mut self, macro_timing: u16) -> Result<(), Error<E>> {
842        if !(1..=255).contains(&macro_timing) {
843            error!("Invalid macro timing: {}", macro_timing);
844            return Err(Error::InvalidArgument);
845        }
846        self.write_word(Register::RangeConfigA, macro_timing)
847            .await?;
848        self.write_word(Register::RangeConfigB, macro_timing + 1)
849            .await?;
850        Ok(())
851    }
852
853    /// Gets the current macro timing configuration.
854    ///
855    /// This function reads the current macro timing value from the sensor.
856    /// The macro timing controls the duration and precision of ranging
857    /// measurements, with higher values providing better precision but
858    /// longer measurement times.
859    ///
860    /// # Returns
861    ///
862    /// * `Ok(u16)` - Current macro timing value (1-255)
863    ///
864    /// # Errors
865    ///
866    /// * `Err(Error::I2cError(E))` - If there was an I2C communication error
867    ///
868    /// # Examples
869    ///
870    /// ```rust,no_run
871    /// use vl53l4cd_ulp::VL53L4cd;
872    ///
873    /// let i2c = embedded_hal_mock::eh1::i2c::Mock::new(&[]);
874    /// let delay = embedded_hal_mock::eh1::delay::NoopDelay;
875    /// let mut sensor = VL53L4cd::new(i2c, delay);
876    ///
877    /// sensor.sensor_init().unwrap();
878    /// // Get current macro timing
879    /// let current_timing = sensor.get_macro_timing().unwrap();
880    /// println!("Current macro timing: {}", current_timing);
881    ///
882    /// // Adjust timing if needed
883    /// if current_timing < 100 {
884    ///     println!("Timing is low, consider increasing for better precision");
885    ///     sensor.set_macro_timing(150).unwrap();
886    /// }
887    /// ```
888    pub async fn get_macro_timing(&mut self) -> Result<u16, Error<E>> {
889        self.read_word(Register::RangeConfigA).await
890    }
891
892    /// Sets the inter-measurement period in milliseconds.
893    ///
894    /// This function configures the time interval between consecutive ranging
895    /// measurements in continuous mode. The sensor will wait this duration
896    /// after completing one measurement before starting the next.
897    ///
898    /// **Note**: This setting only affects continuous ranging mode. Single-shot
899    /// mode ignores this setting and performs one measurement immediately.
900    ///
901    /// # Arguments
902    ///
903    /// * `inter_measurement_ms` - Time between measurements in milliseconds (1-65535)
904    ///
905    /// # Returns
906    ///
907    /// * `Ok(())` - If the interval was set successfully
908    ///
909    /// # Errors
910    ///
911    /// * `Err(Error::InvalidArgument)` - If the value is outside valid range
912    /// * `Err(Error::I2cError(E))` - If there was an I2C communication error
913    ///
914    /// # Examples
915    ///
916    /// ```rust,no_run
917    /// use vl53l4cd_ulp::VL53L4cd;
918    ///
919    /// let i2c = embedded_hal_mock::eh1::i2c::Mock::new(&[]);
920    /// let delay = embedded_hal_mock::eh1::delay::NoopDelay;
921    /// let mut sensor = VL53L4cd::new(i2c, delay);
922    ///
923    /// sensor.sensor_init().unwrap();
924    /// // Set measurement interval to 100ms for high-frequency updates
925    /// sensor.set_inter_measurement_in_ms(100).unwrap();
926    ///
927    /// // Start continuous ranging with 100ms interval
928    /// sensor.start_ranging().unwrap();
929    ///
930    /// // Now measurements will occur every 100ms
931    /// ```
932    pub async fn set_inter_measurement_in_ms(
933        &mut self,
934        inter_measurement_ms: u32,
935    ) -> Result<(), Error<E>> {
936        if !(10..=60000).contains(&inter_measurement_ms) {
937            error!("Invalid inter measurement in ms: {}", inter_measurement_ms);
938            return Err(Error::InvalidArgument);
939        }
940        let inter_measurement_factor = 1.055f32;
941        let clock_pll = self.read_word(Register::ResultOscCalibrateVal).await?;
942        let clock_pll = clock_pll & 0x3FF;
943        #[allow(
944            clippy::cast_sign_loss,
945            clippy::cast_possible_truncation,
946            clippy::cast_precision_loss
947        )]
948        let inter_measurement_factor =
949            inter_measurement_factor * inter_measurement_ms as f32 * f32::from(clock_pll);
950        #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
951        self.write_dword(
952            Register::IntermeasurementMs,
953            inter_measurement_factor as u32,
954        )
955        .await?;
956        Ok(())
957    }
958
959    /// Gets the current inter-measurement period.
960    ///
961    /// This function reads the current inter-measurement interval from the sensor.
962    /// The inter-measurement period defines the time between consecutive ranging
963    /// measurements in continuous mode, measured in milliseconds.
964    ///
965    /// # Returns
966    ///
967    /// * `Ok(u32)` - Current inter-measurement period in milliseconds
968    ///
969    /// # Errors
970    ///
971    /// * `Err(Error::I2cError(E))` - If there was an I2C communication error
972    ///
973    /// # Examples
974    ///
975    /// ```rust,no_run
976    /// use vl53l4cd_ulp::VL53L4cd;
977    ///
978    /// let i2c = embedded_hal_mock::eh1::i2c::Mock::new(&[]);
979    /// let delay = embedded_hal_mock::eh1::delay::NoopDelay;
980    /// let mut sensor = VL53L4cd::new(i2c, delay);
981    ///
982    /// sensor.sensor_init().unwrap();
983    /// // Get current measurement interval
984    /// let current_interval = sensor.get_inter_measurement_in_ms().unwrap();
985    /// println!("Current measurement interval: {} ms", current_interval);
986    ///
987    /// // Adjust interval if needed for power optimization
988    /// if current_interval < 500 {
989    ///     println!("Interval is quite short, consider increasing for battery life");
990    ///     sensor.set_inter_measurement_in_ms(1000).unwrap();
991    /// }
992    /// ```
993    pub async fn get_inter_measurement_in_ms(&mut self) -> Result<u32, Error<E>> {
994        let clock_pll_factor = 1.055f32;
995        let inter_measurement_ms = self.read_dword(Register::IntermeasurementMs).await?;
996        let clock_pll = self.read_word(Register::ResultOscCalibrateVal).await?;
997        let clock_pll = clock_pll & 0x3FF;
998        let clock_pll_factor = clock_pll_factor * f32::from(clock_pll);
999        #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
1000        let clock_pll = clock_pll_factor as u32;
1001        let inter_measurement_ms = inter_measurement_ms / clock_pll;
1002        Ok(inter_measurement_ms)
1003    }
1004
1005    /// Sets the Region of Interest (ROI) for the sensor.
1006    ///
1007    /// This function configures the number of SPADs (Single Photon Avalanche Diodes)
1008    /// used for ranging measurements. The ROI function allows some SPADs to be disabled,
1009    /// which affects both power consumption and maximum ranging distance.
1010    ///
1011    /// **Important Notes**:
1012    /// - Changing the SPAD number has **no effect** on the field of view.
1013    /// - The sensor defaults to 16x16 mode (maximum SPADs)
1014    /// - ST recommends changing SPAD number only if current consumption below 75 μA is desired
1015    /// - Using minimum ROI (4x4) typically reduces current consumption by -10 μA during ranging
1016    /// - Maximum ranging distance impact depends on reflectance, ambient light, and macroperiod
1017    /// - In some conditions, minimum ROI can reduce maximum ranging distance by up to -50%
1018    ///
1019    /// # Arguments
1020    ///
1021    /// * `roi_width` - The ROI width in pixels (4-16, where 4x4 = minimum, 16x16 = maximum)
1022    ///
1023    /// # Returns
1024    ///
1025    /// * `Ok(())` - If the ROI was set successfully
1026    ///
1027    /// # Errors
1028    ///
1029    /// * `Err(Error::I2cError(E))` - If there was an I2C communication error or invalid argument
1030    ///
1031    /// # Examples
1032    ///
1033    /// ```rust,no_run
1034    /// use vl53l4cd_ulp::VL53L4cd;
1035    ///
1036    /// let i2c = embedded_hal_mock::eh1::i2c::Mock::new(&[]);
1037    /// let delay = embedded_hal_mock::eh1::delay::NoopDelay;
1038    /// let mut sensor = VL53L4cd::new(i2c, delay);
1039    ///
1040    /// sensor.sensor_init().unwrap();
1041    /// // Set ROI to 4x4 for minimum power consumption (may reduce max range by up to 50%)
1042    /// sensor.set_roi(4).unwrap();
1043    ///
1044    /// // Or set to 8x8 for balanced power and range performance
1045    /// sensor.set_roi(8).unwrap();
1046    /// ```
1047    pub async fn set_roi(&mut self, roi_width: u8) -> Result<(), Error<E>> {
1048        if !(4..=16).contains(&roi_width) {
1049            error!("Invalid ROI width: {}", roi_width);
1050            return Err(Error::InvalidArgument);
1051        }
1052        let mut tmp = self.read_byte(0x013Eu16).await?;
1053        if roi_width > 10 {
1054            tmp = 199;
1055        }
1056        self.write_byte(0x007Fu16, tmp).await?;
1057        self.write_byte(0x0080u16, (roi_width - 1) << 4 | (roi_width - 1))
1058            .await?;
1059        Ok(())
1060    }
1061
1062    /// Gets the current Region of Interest (ROI) width setting.
1063    ///
1064    /// This function reads the current ROI width configuration from the sensor.
1065    /// The returned value represents the ROI width in pixels, which affects the
1066    /// sensor's measurement area and field of view.
1067    ///
1068    /// # Returns
1069    ///
1070    /// * `Ok(u8)` - The current ROI width in pixels (4-16)
1071    ///
1072    /// # Errors
1073    ///
1074    /// * `Err(Error::I2cError(E))` - If there was an I2C communication error
1075    ///
1076    /// # Examples
1077    ///
1078    /// ```rust,no_run
1079    /// use vl53l4cd_ulp::VL53L4cd;
1080    ///
1081    /// let i2c = embedded_hal_mock::eh1::i2c::Mock::new(&[]);
1082    /// let delay = embedded_hal_mock::eh1::delay::NoopDelay;
1083    /// let mut sensor = VL53L4cd::new(i2c, delay);
1084    ///
1085    /// sensor.sensor_init().unwrap();
1086    /// // Get current ROI width
1087    /// let roi_width = sensor.get_roi().unwrap();
1088    /// println!("Current ROI width: {} pixels", roi_width);
1089    /// ```
1090    pub async fn get_roi(&mut self) -> Result<u8, Error<E>> {
1091        let tmp = self.read_byte(0x0080u16).await?;
1092        Ok((tmp & 0x0F) + 1)
1093    }
1094
1095    /// Sets the interrupt configuration and distance thresholds.
1096    ///
1097    /// This function configures the sensor's interrupt behavior and sets distance
1098    /// thresholds that determine when interrupts are generated. The interrupt can
1099    /// be configured to trigger on new sample ready, when distance is within a
1100    /// threshold window, or when distance is outside the threshold window.
1101    ///
1102    /// **Note**: The low threshold must be less than or equal to the high threshold
1103    /// for proper operation. Invalid threshold combinations may result in unexpected
1104    /// interrupt behavior.
1105    ///
1106    /// # Arguments
1107    ///
1108    /// * `low_threshold_mm` - Lower distance threshold in millimeters (0-4000)
1109    /// * `high_threshold_mm` - Upper distance threshold in millimeters (0-4000)
1110    /// * `interrupt_on` - Interrupt trigger condition (`InterruptOn`)
1111    ///
1112    /// # Returns
1113    ///
1114    /// * `Ok(())` - If the interrupt configuration was set successfully
1115    ///
1116    /// # Errors
1117    ///
1118    /// * `Err(Error::I2cError(E))` - If there was an I2C communication error
1119    ///
1120    /// # Examples
1121    ///
1122    /// ```rust,no_run
1123    /// use vl53l4cd_ulp::{VL53L4cd, InterruptOn};
1124    ///
1125    /// let i2c = embedded_hal_mock::eh1::i2c::Mock::new(&[]);
1126    /// let delay = embedded_hal_mock::eh1::delay::NoopDelay;
1127    /// let mut sensor = VL53L4cd::new(i2c, delay);
1128    ///
1129    /// sensor.sensor_init().unwrap();
1130    /// // Configure interrupt to trigger when distance is between 100-500mm
1131    /// sensor.set_interrupt_configuration(100, 500, InterruptOn::InWindow).unwrap();
1132    /// ```
1133    pub async fn set_interrupt_configuration(
1134        &mut self,
1135        low_threshold_mm: u16,
1136        high_threshold_mm: u16,
1137        interrupt_on: InterruptOn,
1138    ) -> Result<(), Error<E>> {
1139        self.write_byte(Register::SystemInterrupt, interrupt_on.into())
1140            .await?;
1141        self.write_word(Register::ThreshHigh, high_threshold_mm)
1142            .await?;
1143        self.write_word(Register::ThreshLow, low_threshold_mm)
1144            .await?;
1145        Ok(())
1146    }
1147
1148    /// Gets the current interrupt configuration and threshold settings.
1149    ///
1150    /// This function reads the current interrupt configuration and high distance
1151    /// threshold from the sensor. It returns both the threshold value and the
1152    /// interrupt mode in a single call.
1153    ///
1154    /// # Returns
1155    ///
1156    /// * `Ok((u16, InterruptOn))` - Tuple of (`high_threshold_mm`, `interrupt_mode`)
1157    ///
1158    /// # Errors
1159    ///
1160    /// * `Err(Error::I2cError(E))` - If there was an I2C communication error
1161    ///
1162    /// # Examples
1163    ///
1164    /// ```rust,no_run
1165    /// use vl53l4cd_ulp::{VL53L4cd, InterruptOn};
1166    ///
1167    /// let i2c = embedded_hal_mock::eh1::i2c::Mock::new(&[]);
1168    /// let delay = embedded_hal_mock::eh1::delay::NoopDelay;
1169    /// let mut sensor = VL53L4cd::new(i2c, delay);
1170    ///
1171    /// sensor.sensor_init().unwrap();
1172    /// // Get current interrupt configuration and threshold
1173    /// let (threshold, mode) = sensor.get_interrupt_configuration().unwrap();
1174    ///
1175    /// println!("High threshold: {} mm", threshold);
1176    /// match mode {
1177    ///     InterruptOn::NewSampleReady => println!("Interrupt on new sample ready"),
1178    ///     InterruptOn::InWindow => println!("Interrupt when distance in threshold window"),
1179    ///     InterruptOn::OutOfWindow => println!("Interrupt when distance outside threshold window"),
1180    ///     _ => println!("Other interrupt mode: {:?}", mode),
1181    /// }
1182    /// ```
1183    pub async fn get_interrupt_configuration(&mut self) -> Result<(u16, InterruptOn), Error<E>> {
1184        let distance_threshold_mm = self.read_word(Register::ThreshHigh).await?;
1185        let interrupt_on = InterruptOn::from(self.read_byte(Register::SystemInterrupt).await?);
1186        Ok((distance_threshold_mm, interrupt_on))
1187    }
1188
1189    /// Sets the signal rate threshold for ranging measurements.
1190    ///
1191    /// This function configures the minimum signal rate required for a valid
1192    /// ranging measurement. Measurements with signal rates below this threshold
1193    /// will be considered unreliable or invalid.
1194    ///
1195    /// **Note**: The signal threshold helps filter out weak or noisy measurements,
1196    /// improving overall ranging reliability at the cost of potentially missing
1197    /// some distant or low-reflectivity targets.
1198    ///
1199    /// # Arguments
1200    ///
1201    /// * `signal_kcps` - Minimum signal rate in kilocounts per second (0-65535)
1202    ///
1203    /// # Returns
1204    ///
1205    /// * `Ok(())` - If the threshold was set successfully
1206    ///
1207    /// # Errors
1208    ///
1209    /// * `Err(Error::I2cError(E))` - If there was an I2C communication error
1210    ///
1211    /// # Examples
1212    ///
1213    /// ```rust,no_run
1214    /// use vl53l4cd_ulp::VL53L4cd;
1215    ///
1216    /// let i2c = embedded_hal_mock::eh1::i2c::Mock::new(&[]);
1217    /// let delay = embedded_hal_mock::eh1::delay::NoopDelay;
1218    /// let mut sensor = VL53L4cd::new(i2c, delay);
1219    ///
1220    /// sensor.sensor_init().unwrap();
1221    /// // Set signal threshold for reliable measurements
1222    /// sensor.set_signal_threshold(100).unwrap();  // 100 kcps minimum
1223    ///
1224    /// // Start ranging with signal threshold
1225    /// sensor.start_ranging().unwrap();
1226    /// ```
1227    pub async fn set_signal_threshold(&mut self, signal_kcps: u16) -> Result<(), Error<E>> {
1228        if !(1..=16384).contains(&signal_kcps) {
1229            error!("Invalid signal threshold: {}", signal_kcps);
1230            return Err(Error::InvalidArgument);
1231        }
1232        self.write_word(Register::MinCountRateRtnLimitMcps, signal_kcps >> 3)
1233            .await?;
1234        Ok(())
1235    }
1236
1237    /// Gets the current signal rate threshold setting.
1238    ///
1239    /// This function reads the current signal rate threshold from the sensor.
1240    /// The signal threshold defines the minimum signal rate required for valid
1241    /// ranging measurements, helping filter out weak or noisy readings.
1242    ///
1243    /// # Returns
1244    ///
1245    /// * `Ok(u16)` - Current signal threshold in kilocounts per second
1246    ///
1247    /// # Errors
1248    ///
1249    /// * `Err(Error::I2cError(E))` - If there was an I2C communication error
1250    ///
1251    /// # Examples
1252    ///
1253    /// ```rust,no_run
1254    /// use vl53l4cd_ulp::VL53L4cd;
1255    ///
1256    /// let i2c = embedded_hal_mock::eh1::i2c::Mock::new(&[]);
1257    /// let delay = embedded_hal_mock::eh1::delay::NoopDelay;
1258    /// let mut sensor = VL53L4cd::new(i2c, delay);
1259    ///
1260    /// sensor.sensor_init().unwrap();
1261    /// // Get current signal threshold
1262    /// let current_threshold = sensor.get_signal_threshold().unwrap();
1263    /// println!("Current signal threshold: {} kcps", current_threshold);
1264    ///
1265    /// // Adjust threshold if needed
1266    /// if current_threshold < 50 {
1267    ///     println!("Threshold is quite low, consider increasing for reliability");
1268    ///     sensor.set_signal_threshold(100).unwrap();
1269    /// }
1270    /// ```
1271    pub async fn get_signal_threshold(&mut self) -> Result<u16, Error<E>> {
1272        let signal_kcps = self.read_word(Register::MinCountRateRtnLimitMcps).await?;
1273        Ok(signal_kcps << 3)
1274    }
1275
1276    /// Sets the sigma threshold for ranging measurements.
1277    ///
1278    /// This function configures the maximum acceptable measurement uncertainty
1279    /// (sigma) for valid ranging results. Measurements with sigma values above
1280    /// this threshold will be considered unreliable due to poor precision.
1281    ///
1282    /// **Note**: The sigma threshold helps filter out imprecise measurements,
1283    /// improving overall ranging accuracy at the cost of potentially rejecting
1284    /// some valid but noisy measurements.
1285    ///
1286    /// # Arguments
1287    ///
1288    /// * `sigma_mm` - Maximum acceptable sigma value in millimeters (0-65535)
1289    ///
1290    /// # Returns
1291    ///
1292    /// * `Ok(())` - If the threshold was set successfully
1293    ///
1294    /// # Errors
1295    ///
1296    /// * `Err(Error::I2cError(E))` - If there was an I2C communication error
1297    ///
1298    /// # Examples
1299    ///
1300    /// ```rust,no_run
1301    /// use vl53l4cd_ulp::VL53L4cd;
1302    ///
1303    /// let i2c = embedded_hal_mock::eh1::i2c::Mock::new(&[]);
1304    /// let delay = embedded_hal_mock::eh1::delay::NoopDelay;
1305    /// let mut sensor = VL53L4cd::new(i2c, delay);
1306    ///
1307    /// sensor.sensor_init().unwrap();
1308    /// // Set sigma threshold for high-precision measurements
1309    /// sensor.set_sigma_threshold(50).unwrap();  // 50mm maximum uncertainty
1310    ///
1311    /// // Start ranging with precision threshold
1312    /// sensor.start_ranging().unwrap();
1313    /// ```
1314    pub async fn set_sigma_threshold(&mut self, sigma_mm: u16) -> Result<(), Error<E>> {
1315        if sigma_mm > 0xFFFF >> 2 {
1316            error!("Invalid sigma threshold: {}", sigma_mm);
1317            return Err(Error::InvalidArgument);
1318        }
1319        self.write_word(Register::RangeConfigSigmaThresh, sigma_mm << 2)
1320            .await?;
1321        Ok(())
1322    }
1323
1324    /// Gets the current sigma threshold setting.
1325    ///
1326    /// This function reads the current sigma threshold from the sensor.
1327    /// The sigma threshold defines the maximum acceptable measurement uncertainty
1328    /// for valid ranging results, helping filter out imprecise measurements.
1329    ///
1330    /// # Returns
1331    ///
1332    /// * `Ok(u16)` - Current sigma threshold in millimeters
1333    ///
1334    /// # Errors
1335    ///
1336    /// * `Err(Error::I2cError(E))` - If there was an I2C communication error
1337    ///
1338    /// # Examples
1339    ///
1340    /// ```rust,no_run
1341    /// use vl53l4cd_ulp::VL53L4cd;
1342    ///
1343    /// let i2c = embedded_hal_mock::eh1::i2c::Mock::new(&[]);
1344    /// let delay = embedded_hal_mock::eh1::delay::NoopDelay;
1345    /// let mut sensor = VL53L4cd::new(i2c, delay);
1346    ///
1347    /// sensor.sensor_init().unwrap();
1348    /// // Get current sigma threshold
1349    /// let current_threshold = sensor.get_sigma_threshold().unwrap();
1350    /// println!("Current sigma threshold: {} mm", current_threshold);
1351    ///
1352    /// // Adjust threshold if needed
1353    /// if current_threshold > 100 {
1354    ///     println!("Threshold is quite high, consider decreasing for precision");
1355    ///     sensor.set_sigma_threshold(50).unwrap();
1356    /// }
1357    /// ```
1358    pub async fn get_sigma_threshold(&mut self) -> Result<u16, Error<E>> {
1359        let sigma_mm = self.read_word(Register::RangeConfigSigmaThresh).await?;
1360        Ok(sigma_mm >> 2)
1361    }
1362
1363    /// Writes a single byte to a sensor register.
1364    ///
1365    /// This is a low-level function that writes an 8-bit value to a specific
1366    /// register address on the sensor. The function is generic over the register
1367    /// address type, accepting any type that can be converted to `u16`.
1368    ///
1369    /// **Note**: This function performs direct I2C communication with the sensor.
1370    /// Most applications should use the higher-level configuration functions
1371    /// instead of calling this directly.
1372    ///
1373    /// # Arguments
1374    ///
1375    /// * `register_address` - The register address to write to (implements `Into<u16>`)
1376    /// * `value` - The 8-bit value to write to the register
1377    ///
1378    /// # Returns
1379    ///
1380    /// * `Ok(())` - If the write operation was successful
1381    ///
1382    /// # Errors
1383    ///
1384    /// * `Err(Error::I2cError(E))` - If there was an I2C communication error
1385    ///
1386    /// # Examples
1387    ///
1388    /// ```rust,no_run
1389    /// use vl53l4cd_ulp::{VL53L4cd, Register};
1390    ///
1391    /// let i2c = embedded_hal_mock::eh1::i2c::Mock::new(&[]);
1392    /// let delay = embedded_hal_mock::eh1::delay::NoopDelay;
1393    /// let mut sensor = VL53L4cd::new(i2c, delay);
1394    ///
1395    /// sensor.sensor_init().unwrap();
1396    /// // Write to a specific register using the Register enum
1397    /// sensor.write_byte(Register::SystemInterrupt, 0x01).unwrap();
1398    ///
1399    /// // Or write to a hardcoded address
1400    /// sensor.write_byte(0x0046u16, 0x01).unwrap();
1401    /// ```
1402    pub async fn write_byte<R>(&mut self, register_address: R, value: u8) -> Result<(), Error<E>>
1403    where
1404        R: Into<u16>,
1405    {
1406        let reg: u16 = register_address.into();
1407        let mut buffer = [0u8; 3];
1408        buffer[0] = (reg >> 8) as u8;
1409        buffer[1] = (reg & 0xff) as u8;
1410        buffer[2] = value;
1411        self.i2c.write(self.address, &buffer).await?;
1412        Ok(())
1413    }
1414
1415    /// Reads a single byte from a sensor register.
1416    ///
1417    /// This is a low-level function that reads an 8-bit value from a specific
1418    /// register address on the sensor. The function is generic over the register
1419    /// address type, accepting any type that can be converted to `u16`.
1420    ///
1421    /// **Note**: This function performs direct I2C communication with the sensor.
1422    /// Most applications should use the higher-level data reading functions
1423    /// instead of calling this directly.
1424    ///
1425    /// # Arguments
1426    ///
1427    /// * `register_address` - The register address to read from (implements `Into<u16>`)
1428    ///
1429    /// # Returns
1430    ///
1431    /// * `Ok(u8)` - The 8-bit value read from the register
1432    ///
1433    /// # Errors
1434    ///
1435    /// * `Err(Error::I2cError(E))` - If there was an I2C communication error
1436    ///
1437    /// # Examples
1438    ///
1439    /// ```rust,no_run
1440    /// use vl53l4cd_ulp::{VL53L4cd, Register};
1441    ///
1442    /// let i2c = embedded_hal_mock::eh1::i2c::Mock::new(&[]);
1443    /// let delay = embedded_hal_mock::eh1::delay::NoopDelay;
1444    /// let mut sensor = VL53L4cd::new(i2c, delay);
1445    ///
1446    /// sensor.sensor_init().unwrap();
1447    /// // Read from a specific register using the Register enum
1448    /// let status = sensor.read_byte(Register::SystemInterrupt).unwrap();
1449    /// println!("System interrupt status: 0x{:02X}", status);
1450    ///
1451    /// // Or read from a hardcoded address
1452    /// let value = sensor.read_byte(0x0046u16).unwrap();
1453    /// ```
1454    pub async fn read_byte<R>(&mut self, register_address: R) -> Result<u8, Error<E>>
1455    where
1456        R: Into<u16>,
1457    {
1458        let reg: u16 = register_address.into();
1459        let write_buffer = reg.to_be_bytes();
1460        let mut read_buffer = [0u8; 1];
1461        self.i2c
1462            .write_read(self.address, &write_buffer, &mut read_buffer)
1463            .await?;
1464        Ok(read_buffer[0])
1465    }
1466
1467    /// Writes a 16-bit word to a sensor register.
1468    ///
1469    /// This is a low-level function that writes a 16-bit value to a specific
1470    /// register address on the sensor. The function is generic over the register
1471    /// address type, accepting any type that can be converted to `u16`.
1472    ///
1473    /// **Note**: This function performs direct I2C communication with the sensor.
1474    /// Most applications should use the higher-level configuration functions
1475    /// instead of calling this directly.
1476    ///
1477    /// # Arguments
1478    ///
1479    /// * `register_address` - The register address to write to (implements `Into<u16>`)
1480    /// * `value` - The 16-bit value to write to the register
1481    ///
1482    /// # Returns
1483    ///
1484    /// * `Ok(())` - If the write operation was successful
1485    ///
1486    /// # Errors
1487    ///
1488    /// * `Err(Error::I2cError(E))` - If there was an I2C communication error
1489    ///
1490    /// # Examples
1491    ///
1492    /// ```rust,no_run
1493    /// use vl53l4cd_ulp::{VL53L4cd, Register};
1494    ///
1495    /// let i2c = embedded_hal_mock::eh1::i2c::Mock::new(&[]);
1496    /// let delay = embedded_hal_mock::eh1::delay::NoopDelay;
1497    /// let mut sensor = VL53L4cd::new(i2c, delay);
1498    ///
1499    /// sensor.sensor_init().unwrap();
1500    /// // Write a 16-bit value to a specific register using the Register enum
1501    /// sensor.write_word(Register::RangeConfigA, 0x0123).unwrap();
1502    ///
1503    /// // Or write to a hardcoded address
1504    /// sensor.write_word(0x005Eu16, 0x0123).unwrap();
1505    /// ```
1506    pub async fn write_word<R>(&mut self, register_address: R, value: u16) -> Result<(), Error<E>>
1507    where
1508        R: Into<u16>,
1509    {
1510        let reg: u16 = register_address.into();
1511        let mut buffer = [0u8; 4];
1512        buffer[0..2].copy_from_slice(&reg.to_be_bytes());
1513        buffer[2..4].copy_from_slice(&value.to_be_bytes());
1514        self.i2c.write(self.address, &buffer).await?;
1515        Ok(())
1516    }
1517
1518    /// Reads a 16-bit word from a sensor register.
1519    ///
1520    /// This is a low-level function that reads a 16-bit value from a specific
1521    /// register address on the sensor. The function is generic over the register
1522    /// address type, accepting any type that can be converted to `u16`.
1523    ///
1524    /// **Note**: This function performs direct I2C communication with the sensor.
1525    /// Most applications should use the higher-level data reading functions
1526    /// instead of calling this directly.
1527    ///
1528    /// # Arguments
1529    ///
1530    /// * `register_address` - The register address to read from (implements `Into<u16>`)
1531    ///
1532    /// # Returns
1533    ///
1534    /// * `Ok(u16)` - The 16-bit value read from the register
1535    ///
1536    /// # Errors
1537    ///
1538    /// * `Err(Error::I2cError(E))` - If there was an I2C communication error
1539    ///
1540    /// # Examples
1541    ///
1542    /// ```rust,no_run
1543    /// use vl53l4cd_ulp::{VL53L4cd, Register};
1544    ///
1545    /// let i2c = embedded_hal_mock::eh1::i2c::Mock::new(&[]);
1546    /// let delay = embedded_hal_mock::eh1::delay::NoopDelay;
1547    /// let mut sensor = VL53L4cd::new(i2c, delay);
1548    ///
1549    /// sensor.sensor_init().unwrap();
1550    /// // Read a 16-bit value from a specific register using the Register enum
1551    /// let config = sensor.read_word(Register::RangeConfigA).unwrap();
1552    /// println!("Range config A: 0x{:04X}", config);
1553    ///
1554    /// // Or read from a hardcoded address
1555    /// let value = sensor.read_word(0x005Eu16).unwrap();
1556    /// ```
1557    pub async fn read_word<R>(&mut self, register_address: R) -> Result<u16, Error<E>>
1558    where
1559        R: Into<u16>,
1560    {
1561        let reg: u16 = register_address.into();
1562        let write_buffer = reg.to_be_bytes();
1563        let mut read_buffer = [0u8; 2];
1564        self.i2c
1565            .write_read(self.address, &write_buffer, &mut read_buffer)
1566            .await?;
1567        Ok(u16::from_be_bytes(read_buffer))
1568    }
1569
1570    /// Writes a 32-bit double word to a sensor register.
1571    ///
1572    /// This is a low-level function that writes a 32-bit value to a specific
1573    /// register address on the sensor. The function is generic over the register
1574    /// address type, accepting any type that can be converted to `u16`.
1575    ///
1576    /// **Note**: This function performs direct I2C communication with the sensor.
1577    /// Most applications should use the higher-level configuration functions
1578    /// instead of calling this directly.
1579    ///
1580    /// # Arguments
1581    ///
1582    /// * `register_address` - The register address to write to (implements `Into<u16>`)
1583    /// * `value` - The 32-bit value to write to the register
1584    ///
1585    /// # Returns
1586    ///
1587    /// * `Ok(())` - If the write operation was successful
1588    ///
1589    /// # Errors
1590    ///
1591    /// * `Err(Error::I2cError(E))` - If there was an I2C communication error
1592    ///
1593    /// # Examples
1594    ///
1595    /// ```rust,no_run
1596    /// use vl53l4cd_ulp::{VL53L4cd, Register};
1597    ///
1598    /// let i2c = embedded_hal_mock::eh1::i2c::Mock::new(&[]);
1599    /// let delay = embedded_hal_mock::eh1::delay::NoopDelay;
1600    /// let mut sensor = VL53L4cd::new(i2c, delay);
1601    ///
1602    /// sensor.sensor_init().unwrap();
1603    /// // Write a 32-bit value to a specific register using the Register enum
1604    /// sensor.write_dword(Register::IntermeasurementMs, 1000).unwrap();
1605    ///
1606    /// // Or write to a hardcoded address
1607    /// sensor.write_dword(0x006Cu16, 1000).unwrap();
1608    /// ```
1609    pub async fn write_dword<R>(&mut self, register_address: R, value: u32) -> Result<(), Error<E>>
1610    where
1611        R: Into<u16>,
1612    {
1613        let reg: u16 = register_address.into();
1614        let mut buffer = [0u8; 6];
1615        buffer[0..2].copy_from_slice(&reg.to_be_bytes());
1616        buffer[2..6].copy_from_slice(&value.to_be_bytes());
1617        self.i2c.write(self.address, &buffer).await?;
1618        Ok(())
1619    }
1620
1621    /// Reads a 32-bit double word from a sensor register.
1622    ///
1623    /// This is a low-level function that reads a 32-bit value from a specific
1624    /// register address on the sensor. The function is generic over the register
1625    /// address type, accepting any type that can be converted to `u16`.
1626    ///
1627    /// **Note**: This function performs direct I2C communication with the sensor.
1628    /// Most applications should use the higher-level data reading functions
1629    /// instead of calling this directly.
1630    ///
1631    /// # Arguments
1632    ///
1633    /// * `register_address` - The register address to read from (implements `Into<u16>`)
1634    ///
1635    /// # Returns
1636    ///
1637    /// * `Ok(u32)` - The 32-bit value read from the register
1638    ///
1639    /// # Errors
1640    ///
1641    /// * `Err(Error<E>::I2cError(e))` - If there was an I2C communication error
1642    ///
1643    /// # Examples
1644    ///
1645    /// ```rust,no_run
1646    /// use vl53l4cd_ulp::{VL53L4cd, Register};
1647    ///
1648    /// let i2c = embedded_hal_mock::eh1::i2c::Mock::new(&[]);
1649    /// let delay = embedded_hal_mock::eh1::delay::NoopDelay;
1650    /// let mut sensor = VL53L4cd::new(i2c, delay);
1651    ///
1652    /// sensor.sensor_init().unwrap();
1653    /// // Read a 32-bit value from a specific register using the Register enum
1654    /// let interval = sensor.read_dword(Register::IntermeasurementMs).unwrap();
1655    /// println!("Inter-measurement interval: {} ms", interval);
1656    ///
1657    /// // Or read from a hardcoded address
1658    /// let value = sensor.read_dword(0x006Cu16).unwrap();
1659    /// ```
1660    pub async fn read_dword<R>(&mut self, register_address: R) -> Result<u32, Error<E>>
1661    where
1662        R: Into<u16>,
1663    {
1664        let reg: u16 = register_address.into();
1665        let write_buffer = reg.to_be_bytes();
1666        let mut read_buffer = [0u8; 4];
1667        self.i2c
1668            .write_read(self.address, &write_buffer, &mut read_buffer)
1669            .await?;
1670        Ok(u32::from_be_bytes(read_buffer))
1671    }
1672}
1673
1674/// Error type for VL53L4CD sensor operations.
1675///
1676/// This enum represents all possible error conditions that can occur during
1677/// sensor initialization, configuration, and ranging operations.
1678///
1679/// # Examples
1680///
1681/// ```rust,no_run
1682/// use vl53l4cd_ulp::Error;
1683///
1684/// let i2c = embedded_hal_mock::eh1::i2c::Mock::new(&[]);
1685/// let delay = embedded_hal_mock::eh1::delay::NoopDelay;
1686/// let mut sensor = vl53l4cd_ulp::VL53L4cd::new(i2c, delay);
1687///
1688/// match sensor.sensor_init() {
1689///     Ok(()) => println!("Sensor initialized successfully"),
1690///     Err(Error::Timeout) => println!("Sensor initialization timed out"),
1691///     Err(Error::InvalidArgument) => println!("Invalid parameter provided"),
1692///     Err(Error::I2cError(e)) => println!("I2C communication error: {:?}", e),
1693/// }
1694/// ```
1695#[derive(Debug)]
1696#[cfg_attr(feature = "defmt", derive(defmt::Format))]
1697pub enum Error<E: core::fmt::Debug> {
1698    /// I2C communication error from the underlying hardware
1699    I2cError(E),
1700    /// Sensor operation timed out
1701    Timeout,
1702    /// Invalid parameter value provided
1703    InvalidArgument,
1704}
1705
1706impl<E: core::fmt::Debug> core::fmt::Display for Error<E> {
1707    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1708        write!(f, "{self:?}")
1709    }
1710}
1711
1712impl<E: core::fmt::Debug> From<E> for Error<E> {
1713    fn from(error: E) -> Self {
1714        Error::I2cError(error)
1715    }
1716}