#![warn(missing_docs)]
pub use i2cdev;
use tokio::time::sleep;
use std::time::Duration;
use i2cdev::{
core::I2CDevice,
linux::{LinuxI2CDevice, LinuxI2CError},
};
#[cfg(feature = "tracing")]
use tracing::{debug, instrument, trace};
const DEFAULT_CONFIG: &[u8] = &[
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, ];
const VHV_CONFIG_TIMEOUT_MACROP_LOOP_BOUND: u16 = 0x0008;
const SYSTEM_START: u16 = 0x0087;
const GPIO_HV_MUX_CTRL: u16 = 0x0030;
const GPIO_TIO_HV_STATUS: u16 = 0x0031;
const RANGE_CONFIG_A: u16 = 0x005e;
const RANGE_CONFIG_B: u16 = 0x0061;
const INTERMEASUREMENT_MS: u16 = 0x006c;
const SYSTEM_INTERRUPT_CLEAR: u16 = 0x0086;
const RESULT_RANGE_STATUS: u16 = 0x0089;
const RESULT_NUM_SPADS: u16 = 0x008c;
const RESULT_SIGNAL_RATE: u16 = 0x008e;
const RESULT_AMBIENT_RATE: u16 = 0x0090;
const RESULT_SIGMA: u16 = 0x0092;
const RESULT_DISTANCE: u16 = 0x0096;
const IDENTIFICATION_MODEL_ID: u16 = 0x010f;
pub const PERIPHERAL_ADDR: u16 = 0x29;
const DATA_POLL_INTERVAL: Duration = Duration::from_millis(1);
const BOOT_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 {
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 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> {
self.i2c.write(&[])?;
let id = self.device_id()?;
assert_eq!(id, 0xebaa, "strange id ({:x})", id);
#[cfg(feature = "tracing")]
debug!("waiting for boot");
while self.read_byte(0xE5)? != 0x3 {
sleep(BOOT_POLL_INTERVAL).await; }
#[cfg(feature = "tracing")]
debug!("booted");
self.write_bytes(0x2d, DEFAULT_CONFIG)?;
self.start_ranging().await?;
self.stop_ranging()?;
self.write_byte(VHV_CONFIG_TIMEOUT_MACROP_LOOP_BOUND, 0x09)?;
self.write_byte(0x0b, 0)?;
self.write_word(0x0024, 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(0x0006)?);
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(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(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(RANGE_CONFIG_B, ms_byte)?;
Ok(())
}
pub async fn measure(&mut self) -> Result<Measurement, LinuxI2CError> {
while !self.has_measurement()? {
sleep(DATA_POLL_INTERVAL).await;
}
let measurement = self.read_measurement()?;
self.clear_interrupt()?;
Ok(measurement)
}
#[inline]
fn has_measurement(&mut self) -> Result<bool, LinuxI2CError> {
let ctrl = self.read_byte(GPIO_HV_MUX_CTRL)?;
let status = self.read_byte(GPIO_TIO_HV_STATUS)?;
Ok(status & 1 != ctrl >> 4 & 1)
}
#[inline]
fn read_measurement(&mut self) -> Result<Measurement, LinuxI2CError> {
let status = self.read_byte(RESULT_RANGE_STATUS)? & 0x1f;
Ok(Measurement {
status: Status::from_rtn(status),
distance: self.read_word(RESULT_DISTANCE)?,
spads_enabled: self.read_word(RESULT_NUM_SPADS)? / 256,
ambient_rate: self.read_word(RESULT_AMBIENT_RATE)? * 8,
signal_rate: self.read_word(RESULT_SIGNAL_RATE)? * 8,
sigma: self.read_word(RESULT_SIGMA)? / 4,
})
}
#[inline]
fn clear_interrupt(&mut self) -> Result<(), LinuxI2CError> {
self.write_byte(SYSTEM_INTERRUPT_CLEAR, 0x01)
}
#[inline]
pub async fn start_ranging(&mut self) -> Result<(), LinuxI2CError> {
if self.read_dword(INTERMEASUREMENT_MS)? == 0 {
self.write_byte(SYSTEM_START, 0x21)
} else {
self.write_byte(SYSTEM_START, 0x40)
}
}
#[inline]
pub fn stop_ranging(&mut self) -> Result<(), LinuxI2CError> {
self.write_byte(SYSTEM_START, 0x00)
}
fn device_id(&mut self) -> Result<u16, LinuxI2CError> {
self.read_word(IDENTIFICATION_MODEL_ID)
}
#[cfg_attr(feature = "tracing", instrument(level = "trace", skip(self, buf), fields(len = %buf.len())))]
fn read_bytes(&mut self, reg: u16, buf: &mut [u8]) -> Result<(), LinuxI2CError> {
#[cfg(feature = "tracing")]
trace!("write {:x?}", reg.to_be_bytes());
self.i2c.write(®.to_be_bytes())?;
#[cfg(feature = "tracing")]
trace!("read {}", buf.len());
self.i2c.read(buf)
}
fn read_byte(&mut self, reg: u16) -> 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: u16) -> 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: u16) -> Result<u32, LinuxI2CError> {
let mut buf = [0; 4];
self.read_bytes(reg, &mut buf)?;
Ok(u32::from_be_bytes(buf))
}
#[cfg_attr(feature = "tracing", instrument(level = "trace", skip(self, data)))]
fn write_bytes(&mut self, reg: u16, data: &[u8]) -> Result<(), LinuxI2CError> {
let data = [®.to_be_bytes(), data].concat();
#[cfg(feature = "tracing")]
trace!("write {:x?}", data);
self.i2c.write(&data)
}
fn write_byte(&mut self, reg: u16, data: u8) -> Result<(), LinuxI2CError> {
self.write_bytes(reg, &[data])
}
fn write_word(&mut self, reg: u16, data: u16) -> Result<(), LinuxI2CError> {
self.write_bytes(reg, &data.to_be_bytes())
}
fn write_dword(&mut self, reg: u16, data: u32) -> Result<(), LinuxI2CError> {
self.write_bytes(reg, &data.to_be_bytes())
}
}