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(¯o_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(®.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(®.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}