#![warn(missing_docs)]
#![no_std]
pub use i2cdev;
use core::time::Duration;
use i2cdev::{
core::I2CDevice,
linux::{LinuxI2CDevice, LinuxI2CError},
};
#[cfg(feature = "tracing")]
use tracing::{debug, instrument, trace};
const DEFAULT_CONFIG_MSG: &[u8] = &[
0x00, 0x2d, 0x12, 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, 0x01, 0xCC, 0x07, 0x01, 0xF1, 0x05, 0x00, 0xA0, 0x00, 0x80, 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, ];
#[derive(Debug, Clone, Copy)]
#[allow(non_camel_case_types, dead_code)]
enum Register {
OSC_FREQ = 0x0006,
VHV_CONFIG_TIMEOUT_MACROP_LOOP_BOUND = 0x0008,
MYSTERY_1 = 0x000b,
MYSTERY_2 = 0x0024,
SYSTEM_START = 0x0087,
GPIO_HV_MUX_CTRL = 0x0030,
GPIO_TIO_HV_STATUS = 0x0031,
RANGE_CONFIG_A = 0x005e,
RANGE_CONFIG_B = 0x0061,
INTERMEASUREMENT_MS = 0x006c,
SYSTEM_INTERRUPT_CLEAR = 0x0086,
RESULT_RANGE_STATUS = 0x0089,
RESULT_NUM_SPADS = 0x008c,
RESULT_SIGNAL_RATE = 0x008e,
RESULT_AMBIENT_RATE = 0x0090,
RESULT_SIGMA = 0x0092,
RESULT_DISTANCE = 0x0096,
SYSTEM_STATUS = 0x00e5,
IDENTIFICATION_MODEL_ID = 0x010f,
}
impl Register {
const fn addr(&self) -> u16 {
*self as u16
}
const fn as_bytes(&self) -> [u8; 2] {
self.addr().to_be_bytes()
}
}
pub const PERIPHERAL_ADDR: u16 = 0x29;
pub const DATA_POLL_INTERVAL: Duration = Duration::from_millis(1);
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[repr(u8)]
pub enum Status {
Valid = 0,
SigmaAboveThreshold,
SigmaBelowThreshold,
DistanceBelowDetectionThreshold,
InvalidPhase,
HardwareFail,
NoWrapAroundCheck,
WrappedTargetPhaseMismatch,
ProcessingFail,
XTalkFail,
InterruptError,
MergedTarget,
SignalTooWeak,
Other = 255,
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum Severity {
None,
Warning,
Error,
}
impl Status {
const fn from_rtn(rtn: u8) -> Self {
assert!(rtn < 24);
match rtn {
3 => Self::HardwareFail,
4 | 5 => Self::SigmaBelowThreshold,
6 => Self::SigmaAboveThreshold,
7 => Self::WrappedTargetPhaseMismatch,
8 => Self::DistanceBelowDetectionThreshold,
9 => Self::Valid,
12 => Self::XTalkFail,
13 => Self::InterruptError,
18 => Self::InterruptError,
19 => Self::NoWrapAroundCheck,
22 => Self::MergedTarget,
23 => Self::SignalTooWeak,
_ => Self::Other,
}
}
pub const fn severity(&self) -> Severity {
match self {
Status::Valid => Severity::None,
Status::SigmaAboveThreshold => Severity::Warning,
Status::SigmaBelowThreshold => Severity::Warning,
Status::DistanceBelowDetectionThreshold => Severity::Error,
Status::InvalidPhase => Severity::Error,
Status::HardwareFail => Severity::Error,
Status::NoWrapAroundCheck => Severity::Warning,
Status::WrappedTargetPhaseMismatch => Severity::Error,
Status::ProcessingFail => Severity::Error,
Status::XTalkFail => Severity::Error,
Status::InterruptError => Severity::Error,
Status::MergedTarget => Severity::Error,
Status::SignalTooWeak => Severity::Error,
Status::Other => Severity::Error,
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct Measurement {
pub status: Status,
pub distance: u16,
pub ambient_rate: u16,
pub signal_rate: u16,
pub spads_enabled: u16,
pub sigma: u16,
}
impl Measurement {
#[inline]
pub fn is_valid(&self) -> bool {
self.status == Status::Valid
}
}
pub struct Vl53l4cd {
i2c: LinuxI2CDevice,
}
impl Vl53l4cd {
pub fn new(i2c: LinuxI2CDevice) -> Self {
Self { i2c }
}
#[cfg_attr(feature = "tracing", instrument(err, skip(self)))]
pub async fn init(&mut self) -> Result<(), LinuxI2CError> {
let id = self.read_word(Register::IDENTIFICATION_MODEL_ID)?;
assert_eq!(id, 0xebaa, "strange device id ({:x})", id);
#[cfg(feature = "tracing")]
debug!("waiting for boot");
while self.read_byte(Register::SYSTEM_STATUS)? != 0x3 {
tokio::time::sleep(Duration::from_millis(1)).await; }
#[cfg(feature = "tracing")]
debug!("booted");
self.i2c.write(DEFAULT_CONFIG_MSG)?;
self.start_ranging()?;
self.stop_ranging()?;
self.write_byte(Register::VHV_CONFIG_TIMEOUT_MACROP_LOOP_BOUND, 0x09)?;
self.write_byte(Register::MYSTERY_1, 0)?;
self.write_word(Register::MYSTERY_2, 0x500)?;
self.set_range_timing(50, 0)?;
Ok(())
}
pub fn set_range_timing(
&mut self,
timing_budget_ms: u32,
inter_measurement_ms: u32,
) -> Result<(), LinuxI2CError> {
assert!(
(10..=200).contains(&timing_budget_ms),
"timing budget must be in range [10, 200]"
);
let osc_freq = u32::from(self.read_word(Register::OSC_FREQ)?);
assert_ne!(osc_freq, 0, "oscillation frequency is zero");
let mut timing_budget_us = timing_budget_ms * 1000;
let macro_period_us = (2304 * (0x40000000 / osc_freq)) >> 6;
if inter_measurement_ms == 0 {
self.write_dword(Register::INTERMEASUREMENT_MS, 0)?;
timing_budget_us -= 2500;
} else if inter_measurement_ms > timing_budget_ms {
timing_budget_us -= 4300;
timing_budget_us /= 2;
} else {
panic!("timing budget must not be less than inter-measurement");
}
let mut ms_byte = 0;
timing_budget_us <<= 12;
let mut tmp = macro_period_us * 16;
let mut ls_byte = ((timing_budget_us + ((tmp >> 6) >> 1)) / (tmp >> 6)) - 1;
while (ls_byte & 0xFFFFFF00) > 0 {
ls_byte >>= 1;
ms_byte += 1;
}
ms_byte <<= 8 + (ls_byte * 0xff) as u16;
self.write_word(Register::RANGE_CONFIG_A, ms_byte)?;
ms_byte = 0;
tmp = macro_period_us * 12;
let mut ls_byte = ((timing_budget_us + ((tmp >> 6) >> 1)) / (tmp >> 6)) - 1;
while (ls_byte & 0xFFFFFF00) > 0 {
ls_byte >>= 1;
ms_byte += 1;
}
ms_byte = (ms_byte << 8) + (ls_byte & 0xFF) as u16;
self.write_word(Register::RANGE_CONFIG_B, ms_byte)?;
Ok(())
}
pub async fn measure(&mut self) -> Result<Measurement, LinuxI2CError> {
while !self.has_measurement()? {
tokio::time::sleep(DATA_POLL_INTERVAL).await;
}
let measurement = self.read_measurement()?;
self.clear_interrupt()?;
Ok(measurement)
}
#[inline]
pub fn has_measurement(&mut self) -> Result<bool, LinuxI2CError> {
let ctrl = self.read_byte(Register::GPIO_HV_MUX_CTRL)?;
let status = self.read_byte(Register::GPIO_TIO_HV_STATUS)?;
Ok(status & 1 != ctrl >> 4 & 1)
}
#[inline]
pub fn read_measurement(&mut self) -> Result<Measurement, LinuxI2CError> {
let status = self.read_byte(Register::RESULT_RANGE_STATUS)? & 0x1f;
Ok(Measurement {
status: Status::from_rtn(status),
distance: self.read_word(Register::RESULT_DISTANCE)?,
spads_enabled: self.read_word(Register::RESULT_NUM_SPADS)? / 256,
ambient_rate: self.read_word(Register::RESULT_AMBIENT_RATE)? * 8,
signal_rate: self.read_word(Register::RESULT_SIGNAL_RATE)? * 8,
sigma: self.read_word(Register::RESULT_SIGMA)? / 4,
})
}
#[inline]
pub fn clear_interrupt(&mut self) -> Result<(), LinuxI2CError> {
self.write_byte(Register::SYSTEM_INTERRUPT_CLEAR, 0x01)
}
#[inline]
pub fn start_ranging(&mut self) -> Result<(), LinuxI2CError> {
if self.read_dword(Register::INTERMEASUREMENT_MS)? == 0 {
self.write_byte(Register::SYSTEM_START, 0x21)
} else {
self.write_byte(Register::SYSTEM_START, 0x40)
}
}
#[inline]
pub fn stop_ranging(&mut self) -> Result<(), LinuxI2CError> {
self.write_byte(Register::SYSTEM_START, 0x00)
}
#[cfg_attr(feature = "tracing", instrument(level = "trace", skip(self, buf), fields(len = %buf.len())))]
fn read_bytes(&mut self, reg: Register, buf: &mut [u8]) -> Result<(), LinuxI2CError> {
#[cfg(feature = "tracing")]
trace!("write {:x?}", reg.as_bytes());
self.i2c.write(®.as_bytes())?;
#[cfg(feature = "tracing")]
trace!("read {}", buf.len());
self.i2c.read(buf)
}
fn read_byte(&mut self, reg: Register) -> Result<u8, LinuxI2CError> {
let mut buf = [0];
self.read_bytes(reg, &mut buf)?;
Ok(u8::from_be_bytes(buf))
}
fn read_word(&mut self, reg: Register) -> Result<u16, LinuxI2CError> {
let mut buf = [0; 2];
self.read_bytes(reg, &mut buf)?;
Ok(u16::from_be_bytes(buf))
}
fn read_dword(&mut self, reg: Register) -> Result<u32, LinuxI2CError> {
let mut buf = [0; 4];
self.read_bytes(reg, &mut buf)?;
Ok(u32::from_be_bytes(buf))
}
fn write_byte(&mut self, reg: Register, data: u8) -> Result<(), LinuxI2CError> {
let mut msg = [0, 0, data];
msg[..2].copy_from_slice(®.as_bytes());
self.i2c.write(&msg)
}
fn write_word(&mut self, reg: Register, data: u16) -> Result<(), LinuxI2CError> {
let mut msg = [0; 4];
msg[..2].copy_from_slice(®.as_bytes());
msg[2..].copy_from_slice(&data.to_be_bytes());
self.i2c.write(&msg)
}
fn write_dword(&mut self, reg: Register, data: u32) -> Result<(), LinuxI2CError> {
let mut msg = [0; 6];
msg[..2].copy_from_slice(®.as_bytes());
msg[2..].copy_from_slice(&data.to_be_bytes());
self.i2c.write(&msg)
}
}