#![no_std]
#![warn(missing_docs)]
#![warn(clippy::all)]
#![warn(clippy::pedantic)]
mod fmt;
#[cfg(not(feature = "async"))]
use embedded_hal::{delay::DelayNs, i2c::I2c};
#[cfg(feature = "async")]
use embedded_hal_async::{delay::DelayNs, i2c::I2c};
const VL53L4CD_DEFAULT_CONFIGURATION: [u8; 91] = [
0x00,
0x00,
0x00,
0x11,
0x02,
0x00,
0x02,
0x08,
0x00,
0x08,
0x10,
0x01,
0x01,
0x00,
0x00,
0x00,
0x00,
0xff,
0x00,
0x0F,
0x00,
0x00,
0x00,
0x00,
0x00,
0x20,
0x0b,
0x00,
0x00,
0x02,
0x14,
0x21,
0x00,
0x00,
0x05,
0x00,
0x00,
0x00,
0x00,
0xc8,
0x00,
0x00,
0x38,
0xff,
0x01,
0x00,
0x08,
0x00,
0x00,
0x00,
0x01,
0x07,
0x00,
0x02,
0x05,
0x00,
0xb4,
0x00,
0xbb,
0x08,
0x38,
0x00,
0x00,
0x00,
0x00,
0x0f,
0x89,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x01,
0x07,
0x05,
0x06,
0x06,
0x00,
0x00,
0x02,
0xc7,
0xff,
0x9B,
0x00,
0x00,
0x00,
0x01,
0x00,
0x00,
];
#[repr(u16)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[allow(non_camel_case_types)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum Register {
I2cSlaveDeviceAddress = 0x0001,
VhvConfigTimeoutMacropLoopBound = 0x0008,
GpioHvMuxCtrl = 0x0030,
GpioTioHvStatus = 0x0031,
SystemInterrupt = 0x0046,
RangeConfigA = 0x005E,
RangeConfigB = 0x0061,
RangeConfigSigmaThresh = 0x0064,
MinCountRateRtnLimitMcps = 0x0066,
IntermeasurementMs = 0x006C,
ThreshHigh = 0x0072,
ThreshLow = 0x0074,
PowerGo1 = 0x0083,
FirmwareEnable = 0x0085,
SystemInterruptClear = 0x0086,
SystemStart = 0x0087,
ResultRangeStatus = 0x0089,
ResultSpadNb = 0x008C,
ResultSignalRate = 0x008E,
ResultAmbientRate = 0x0090,
ResultSigma = 0x0092,
ResultDistance = 0x0096,
ResultOscCalibrateVal = 0x00DE,
FirmwareSystemStatus = 0x00E5,
IdentificationModelId = 0x010F,
}
impl From<Register> for u16 {
fn from(r: Register) -> Self {
r as u16
}
}
#[repr(u8)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum InterruptOn {
LevelLow,
LevelHigh,
OutOfWindow,
OutOfWindowOrNoTarget,
InWindow,
NewSampleReady,
Unknown(u8),
}
impl From<InterruptOn> for u8 {
fn from(interrupt_on: InterruptOn) -> Self {
match interrupt_on {
InterruptOn::LevelLow => 0,
InterruptOn::LevelHigh => 1,
InterruptOn::OutOfWindow => 2,
InterruptOn::OutOfWindowOrNoTarget => 0x42,
InterruptOn::InWindow => 3,
InterruptOn::NewSampleReady => 0x20,
InterruptOn::Unknown(value) => value,
}
}
}
impl From<u8> for InterruptOn {
fn from(value: u8) -> Self {
match value {
0 => InterruptOn::LevelLow,
1 => InterruptOn::LevelHigh,
2 => InterruptOn::OutOfWindow,
0x42 => InterruptOn::OutOfWindowOrNoTarget,
3 => InterruptOn::InWindow,
0x20 => InterruptOn::NewSampleReady,
_ => {
warn!("Unknown InterruptOn value: {}", value);
InterruptOn::Unknown(value)
}
}
}
}
#[derive(Debug, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct EstimatedMeasurement {
pub measurement_status: u8,
pub estimated_distance_mm: u16,
pub sigma_mm: u16,
pub signal_kcps: u16,
pub ambient_kcps: u16,
}
pub struct VL53L4cd<I2C, D> {
i2c: I2C,
address: u8,
delay: D,
}
#[maybe_async_cfg::maybe(
sync(cfg(not(feature = "async")), keep_self),
async(feature = "async", keep_self)
)]
impl<I2C, E, D> VL53L4cd<I2C, D>
where
I2C: I2c<Error = E>,
E: core::fmt::Debug,
D: DelayNs,
{
pub fn new(i2c: I2C, delay: D) -> Self {
Self {
i2c,
address: 0x29,
delay,
}
}
pub async fn set_i2c_address(&mut self, address: u8) -> Result<(), Error<E>> {
self.write_byte(Register::I2cSlaveDeviceAddress, address)
.await?;
self.address = address;
Ok(())
}
pub async fn get_sensor_id(&mut self) -> Result<u16, Error<E>> {
let id = self.read_word(Register::IdentificationModelId).await?;
Ok(id)
}
pub async fn sensor_init(&mut self) -> Result<(), Error<E>> {
const BOOT_STATUS: u8 = 0x3;
let mut attempts = 0u16;
info!("Waiting for sensor to boot");
loop {
let status = self.read_byte(Register::FirmwareSystemStatus).await?;
if status == BOOT_STATUS {
break Ok(());
}
attempts += 1;
if attempts >= 1000 {
break Err(Error::Timeout);
}
self.delay.delay_ms(1).await;
}?;
info!("Loading default configuration");
for (i, &value) in VL53L4CD_DEFAULT_CONFIGURATION.iter().enumerate() {
#[allow(clippy::cast_possible_truncation)]
self.write_byte(i as u16 + 0x2D, value).await?;
}
info!("Starting VHV");
self.write_byte(Register::SystemStart, 0x40).await?;
info!("Waiting for data ready");
let mut attempts = 0u16;
loop {
let status = self.check_for_data_ready().await?;
if status {
break Ok(());
}
attempts += 1;
if attempts >= 1000 {
break Err(Error::Timeout);
}
self.delay.delay_ms(1).await;
}?;
self.clear_interrupt().await?;
self.stop_ranging().await?;
self.write_byte(Register::VhvConfigTimeoutMacropLoopBound, 0x09)
.await?;
self.write_byte(0x0Bu16, 0x00).await?;
self.write_word(0x0024u16, 0x500).await?;
self.write_byte(0x81u16, 0b1000_1010).await?;
self.write_byte(0x004Bu16, 0x03).await?;
self.set_inter_measurement_in_ms(1000).await?;
Ok(())
}
pub async fn check_for_data_ready(&mut self) -> Result<bool, Error<E>> {
let interrupt_polarity = self.read_byte(Register::GpioHvMuxCtrl).await?;
let interrupt_polarity = u8::from(interrupt_polarity & 0x10 == 0);
let interrupt_status = self.read_byte(Register::GpioTioHvStatus).await?;
if interrupt_status & 1 == interrupt_polarity {
Ok(true)
} else {
Ok(false)
}
}
pub async fn clear_interrupt(&mut self) -> Result<(), Error<E>> {
self.write_byte(Register::SystemInterruptClear, 0x01).await
}
pub async fn start_ranging_single_shot(&mut self) -> Result<(), Error<E>> {
self.write_byte(Register::SystemStart, 0x10).await
}
pub async fn start_ranging(&mut self) -> Result<(), Error<E>> {
self.write_byte(Register::SystemStart, 0x40).await
}
pub async fn stop_ranging(&mut self) -> Result<(), Error<E>> {
self.write_byte(Register::SystemStart, 0x00).await
}
pub async fn get_estimated_measurement(&mut self) -> Result<EstimatedMeasurement, Error<E>> {
const STATUS_RTN: [u8; 24] = [
255, 255, 255, 5, 2, 4, 1, 7, 3, 0, 255, 255, 9, 13, 255, 255, 255, 255, 10, 6, 255,
255, 11, 12,
];
let mut measurement_status = self.read_byte(Register::ResultRangeStatus).await? & 0x1f;
if measurement_status < 24 {
measurement_status = STATUS_RTN[measurement_status as usize];
}
let estimated_distance_mm = self.read_word(Register::ResultDistance).await?;
let sigma_mm = self.read_word(Register::ResultSigma).await? / 4;
let signal_kcps = self.read_word(Register::ResultSignalRate).await? * 8;
let ambient_kcps = self.read_word(Register::ResultAmbientRate).await? * 8;
Ok(EstimatedMeasurement {
measurement_status,
estimated_distance_mm,
sigma_mm,
signal_kcps,
ambient_kcps,
})
}
pub async fn set_macro_timing(&mut self, macro_timing: u16) -> Result<(), Error<E>> {
if !(1..=255).contains(¯o_timing) {
error!("Invalid macro timing: {}", macro_timing);
return Err(Error::InvalidArgument);
}
self.write_word(Register::RangeConfigA, macro_timing)
.await?;
self.write_word(Register::RangeConfigB, macro_timing + 1)
.await?;
Ok(())
}
pub async fn get_macro_timing(&mut self) -> Result<u16, Error<E>> {
self.read_word(Register::RangeConfigA).await
}
pub async fn set_inter_measurement_in_ms(
&mut self,
inter_measurement_ms: u32,
) -> Result<(), Error<E>> {
if !(10..=60000).contains(&inter_measurement_ms) {
error!("Invalid inter measurement in ms: {}", inter_measurement_ms);
return Err(Error::InvalidArgument);
}
let inter_measurement_factor = 1.055f32;
let clock_pll = self.read_word(Register::ResultOscCalibrateVal).await?;
let clock_pll = clock_pll & 0x3FF;
#[allow(
clippy::cast_sign_loss,
clippy::cast_possible_truncation,
clippy::cast_precision_loss
)]
let inter_measurement_factor =
inter_measurement_factor * inter_measurement_ms as f32 * f32::from(clock_pll);
#[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
self.write_dword(
Register::IntermeasurementMs,
inter_measurement_factor as u32,
)
.await?;
Ok(())
}
pub async fn get_inter_measurement_in_ms(&mut self) -> Result<u32, Error<E>> {
let clock_pll_factor = 1.055f32;
let inter_measurement_ms = self.read_dword(Register::IntermeasurementMs).await?;
let clock_pll = self.read_word(Register::ResultOscCalibrateVal).await?;
let clock_pll = clock_pll & 0x3FF;
let clock_pll_factor = clock_pll_factor * f32::from(clock_pll);
#[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
let clock_pll = clock_pll_factor as u32;
let inter_measurement_ms = inter_measurement_ms / clock_pll;
Ok(inter_measurement_ms)
}
pub async fn set_roi(&mut self, roi_width: u8) -> Result<(), Error<E>> {
if !(4..=16).contains(&roi_width) {
error!("Invalid ROI width: {}", roi_width);
return Err(Error::InvalidArgument);
}
let mut tmp = self.read_byte(0x013Eu16).await?;
if roi_width > 10 {
tmp = 199;
}
self.write_byte(0x007Fu16, tmp).await?;
self.write_byte(0x0080u16, (roi_width - 1) << 4 | (roi_width - 1))
.await?;
Ok(())
}
pub async fn get_roi(&mut self) -> Result<u8, Error<E>> {
let tmp = self.read_byte(0x0080u16).await?;
Ok((tmp & 0x0F) + 1)
}
pub async fn set_interrupt_configuration(
&mut self,
low_threshold_mm: u16,
high_threshold_mm: u16,
interrupt_on: InterruptOn,
) -> Result<(), Error<E>> {
self.write_byte(Register::SystemInterrupt, interrupt_on.into())
.await?;
self.write_word(Register::ThreshHigh, high_threshold_mm)
.await?;
self.write_word(Register::ThreshLow, low_threshold_mm)
.await?;
Ok(())
}
pub async fn get_interrupt_configuration(&mut self) -> Result<(u16, InterruptOn), Error<E>> {
let distance_threshold_mm = self.read_word(Register::ThreshHigh).await?;
let interrupt_on = InterruptOn::from(self.read_byte(Register::SystemInterrupt).await?);
Ok((distance_threshold_mm, interrupt_on))
}
pub async fn set_signal_threshold(&mut self, signal_kcps: u16) -> Result<(), Error<E>> {
if !(1..=16384).contains(&signal_kcps) {
error!("Invalid signal threshold: {}", signal_kcps);
return Err(Error::InvalidArgument);
}
self.write_word(Register::MinCountRateRtnLimitMcps, signal_kcps >> 3)
.await?;
Ok(())
}
pub async fn get_signal_threshold(&mut self) -> Result<u16, Error<E>> {
let signal_kcps = self.read_word(Register::MinCountRateRtnLimitMcps).await?;
Ok(signal_kcps << 3)
}
pub async fn set_sigma_threshold(&mut self, sigma_mm: u16) -> Result<(), Error<E>> {
if sigma_mm > 0xFFFF >> 2 {
error!("Invalid sigma threshold: {}", sigma_mm);
return Err(Error::InvalidArgument);
}
self.write_word(Register::RangeConfigSigmaThresh, sigma_mm << 2)
.await?;
Ok(())
}
pub async fn get_sigma_threshold(&mut self) -> Result<u16, Error<E>> {
let sigma_mm = self.read_word(Register::RangeConfigSigmaThresh).await?;
Ok(sigma_mm >> 2)
}
pub async fn write_byte<R>(&mut self, register_address: R, value: u8) -> Result<(), Error<E>>
where
R: Into<u16>,
{
let reg: u16 = register_address.into();
let mut buffer = [0u8; 3];
buffer[0] = (reg >> 8) as u8;
buffer[1] = (reg & 0xff) as u8;
buffer[2] = value;
self.i2c.write(self.address, &buffer).await?;
Ok(())
}
pub async fn read_byte<R>(&mut self, register_address: R) -> Result<u8, Error<E>>
where
R: Into<u16>,
{
let reg: u16 = register_address.into();
let write_buffer = reg.to_be_bytes();
let mut read_buffer = [0u8; 1];
self.i2c
.write_read(self.address, &write_buffer, &mut read_buffer)
.await?;
Ok(read_buffer[0])
}
pub async fn write_word<R>(&mut self, register_address: R, value: u16) -> Result<(), Error<E>>
where
R: Into<u16>,
{
let reg: u16 = register_address.into();
let mut buffer = [0u8; 4];
buffer[0..2].copy_from_slice(®.to_be_bytes());
buffer[2..4].copy_from_slice(&value.to_be_bytes());
self.i2c.write(self.address, &buffer).await?;
Ok(())
}
pub async fn read_word<R>(&mut self, register_address: R) -> Result<u16, Error<E>>
where
R: Into<u16>,
{
let reg: u16 = register_address.into();
let write_buffer = reg.to_be_bytes();
let mut read_buffer = [0u8; 2];
self.i2c
.write_read(self.address, &write_buffer, &mut read_buffer)
.await?;
Ok(u16::from_be_bytes(read_buffer))
}
pub async fn write_dword<R>(&mut self, register_address: R, value: u32) -> Result<(), Error<E>>
where
R: Into<u16>,
{
let reg: u16 = register_address.into();
let mut buffer = [0u8; 6];
buffer[0..2].copy_from_slice(®.to_be_bytes());
buffer[2..6].copy_from_slice(&value.to_be_bytes());
self.i2c.write(self.address, &buffer).await?;
Ok(())
}
pub async fn read_dword<R>(&mut self, register_address: R) -> Result<u32, Error<E>>
where
R: Into<u16>,
{
let reg: u16 = register_address.into();
let write_buffer = reg.to_be_bytes();
let mut read_buffer = [0u8; 4];
self.i2c
.write_read(self.address, &write_buffer, &mut read_buffer)
.await?;
Ok(u32::from_be_bytes(read_buffer))
}
}
#[derive(Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum Error<E: core::fmt::Debug> {
I2cError(E),
Timeout,
InvalidArgument,
}
impl<E: core::fmt::Debug> core::fmt::Display for Error<E> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{self:?}")
}
}
impl<E: core::fmt::Debug> From<E> for Error<E> {
fn from(error: E) -> Self {
Error::I2cError(error)
}
}