import os
import time
from cereal import log
from openpilot . system . sensord . sensors . i2c_sensor import Sensor
class LSM6DS3_Accel ( Sensor ) :
LSM6DS3_ACCEL_I2C_REG_DRDY_CFG = 0x0B
LSM6DS3_ACCEL_I2C_REG_INT1_CTRL = 0x0D
LSM6DS3_ACCEL_I2C_REG_CTRL1_XL = 0x10
LSM6DS3_ACCEL_I2C_REG_CTRL3_C = 0x12
LSM6DS3_ACCEL_I2C_REG_CTRL5_C = 0x14
LSM6DS3_ACCEL_I2C_REG_STAT_REG = 0x1E
LSM6DS3_ACCEL_I2C_REG_OUTX_L_XL = 0x28
LSM6DS3_ACCEL_ODR_104HZ = ( 0b0100 << 4 )
LSM6DS3_ACCEL_INT1_DRDY_XL = 0b1
LSM6DS3_ACCEL_DRDY_XLDA = 0b1
LSM6DS3_ACCEL_DRDY_PULSE_MODE = ( 1 << 7 )
LSM6DS3_ACCEL_IF_INC = 0b00000100
LSM6DS3_ACCEL_ODR_52HZ = ( 0b0011 << 4 )
LSM6DS3_ACCEL_FS_4G = ( 0b10 << 2 )
LSM6DS3_ACCEL_IF_INC_BDU = 0b01000100
LSM6DS3_ACCEL_POSITIVE_TEST = 0b01
LSM6DS3_ACCEL_NEGATIVE_TEST = 0b10
LSM6DS3_ACCEL_MIN_ST_LIMIT_mg = 90.0
LSM6DS3_ACCEL_MAX_ST_LIMIT_mg = 1700.0
@property
def device_address ( self ) - > int :
return 0x6A
def reset ( self ) :
self . write ( 0x12 , 0x1 )
time . sleep ( 0.1 )
def init ( self ) :
chip_id = self . verify_chip_id ( 0x0F , [ 0x69 , 0x6A ] )
if chip_id == 0x6A :
self . source = log . SensorEventData . SensorSource . lsm6ds3trc
else :
self . source = log . SensorEventData . SensorSource . lsm6ds3
# self-test
if os . getenv ( " LSM_SELF_TEST " ) == " 1 " :
self . self_test ( self . LSM6DS3_ACCEL_POSITIVE_TEST )
self . self_test ( self . LSM6DS3_ACCEL_NEGATIVE_TEST )
# actual init
int1 = self . read ( self . LSM6DS3_ACCEL_I2C_REG_INT1_CTRL , 1 ) [ 0 ]
int1 | = self . LSM6DS3_ACCEL_INT1_DRDY_XL
self . writes ( (
# Enable continuous update and automatic address increment
( self . LSM6DS3_ACCEL_I2C_REG_CTRL3_C , self . LSM6DS3_ACCEL_IF_INC ) ,
# Set ODR to 104 Hz, FS to ±2g (default)
( self . LSM6DS3_ACCEL_I2C_REG_CTRL1_XL , self . LSM6DS3_ACCEL_ODR_104HZ ) ,
# Configure data ready signal to pulse mode
( self . LSM6DS3_ACCEL_I2C_REG_DRDY_CFG , self . LSM6DS3_ACCEL_DRDY_PULSE_MODE ) ,
# Enable data ready interrupt on INT1 without resetting existing interrupts
( self . LSM6DS3_ACCEL_I2C_REG_INT1_CTRL , int1 ) ,
) )
def get_event ( self , ts : int | None = None ) - > log . SensorEventData :
assert ts is not None # must come from the IRQ event
# Check if data is ready since IRQ is shared with gyro
status_reg = self . read ( self . LSM6DS3_ACCEL_I2C_REG_STAT_REG , 1 ) [ 0 ]
if ( status_reg & self . LSM6DS3_ACCEL_DRDY_XLDA ) == 0 :
raise self . DataNotReady
scale = 9.81 * 2.0 / ( 1 << 15 )
b = self . read ( self . LSM6DS3_ACCEL_I2C_REG_OUTX_L_XL , 6 )
x = self . parse_16bit ( b [ 0 ] , b [ 1 ] ) * scale
y = self . parse_16bit ( b [ 2 ] , b [ 3 ] ) * scale
z = self . parse_16bit ( b [ 4 ] , b [ 5 ] ) * scale
event = log . SensorEventData . new_message ( )
event . timestamp = ts
event . version = 1
event . sensor = 1 # SENSOR_ACCELEROMETER
event . type = 1 # SENSOR_TYPE_ACCELEROMETER
event . source = self . source
a = event . init ( ' acceleration ' )
a . v = [ y , - x , z ]
a . status = 1
return event
def shutdown ( self ) - > None :
# Disable data ready interrupt on INT1
value = self . read ( self . LSM6DS3_ACCEL_I2C_REG_INT1_CTRL , 1 ) [ 0 ]
value & = ~ self . LSM6DS3_ACCEL_INT1_DRDY_XL
self . write ( self . LSM6DS3_ACCEL_I2C_REG_INT1_CTRL , value )
# Power down by clearing ODR bits
value = self . read ( self . LSM6DS3_ACCEL_I2C_REG_CTRL1_XL , 1 ) [ 0 ]
value & = 0x0F
self . write ( self . LSM6DS3_ACCEL_I2C_REG_CTRL1_XL , value )
# *** self-test stuff ***
def _wait_for_data_ready ( self ) :
while True :
drdy = self . read ( self . LSM6DS3_ACCEL_I2C_REG_STAT_REG , 1 ) [ 0 ]
if drdy & self . LSM6DS3_ACCEL_DRDY_XLDA :
break
def _read_and_avg_data ( self , scaling : float ) - > list [ float ] :
out_buf = [ 0.0 , 0.0 , 0.0 ]
for _ in range ( 5 ) :
self . _wait_for_data_ready ( )
b = self . read ( self . LSM6DS3_ACCEL_I2C_REG_OUTX_L_XL , 6 )
for j in range ( 3 ) :
val = self . parse_16bit ( b [ j * 2 ] , b [ j * 2 + 1 ] ) * scaling
out_buf [ j ] + = val
return [ x / 5.0 for x in out_buf ]
def self_test ( self , test_type : int ) - > None :
# Prepare sensor for self-test
self . write ( self . LSM6DS3_ACCEL_I2C_REG_CTRL3_C , self . LSM6DS3_ACCEL_IF_INC_BDU )
# Configure ODR and full scale based on sensor type
if self . source == log . SensorEventData . SensorSource . lsm6ds3trc :
odr_fs = self . LSM6DS3_ACCEL_FS_4G | self . LSM6DS3_ACCEL_ODR_52HZ
scaling = 0.122 # mg/LSB for ±4g
else :
odr_fs = self . LSM6DS3_ACCEL_ODR_52HZ
scaling = 0.061 # mg/LSB for ±2g
self . write ( self . LSM6DS3_ACCEL_I2C_REG_CTRL1_XL , odr_fs )
# Wait for stable output
time . sleep ( 0.1 )
self . _wait_for_data_ready ( )
val_st_off = self . _read_and_avg_data ( scaling )
# Enable self-test
self . write ( self . LSM6DS3_ACCEL_I2C_REG_CTRL5_C , test_type )
# Wait for stable output
time . sleep ( 0.1 )
self . _wait_for_data_ready ( )
val_st_on = self . _read_and_avg_data ( scaling )
# Disable sensor and self-test
self . write ( self . LSM6DS3_ACCEL_I2C_REG_CTRL1_XL , 0 )
self . write ( self . LSM6DS3_ACCEL_I2C_REG_CTRL5_C , 0 )
# Calculate differences and check limits
test_val = [ abs ( on - off ) for on , off in zip ( val_st_on , val_st_off , strict = False ) ]
for val in test_val :
if val < self . LSM6DS3_ACCEL_MIN_ST_LIMIT_mg or val > self . LSM6DS3_ACCEL_MAX_ST_LIMIT_mg :
raise self . SensorException ( f " Accelerometer self-test failed for test type { test_type } " )
if __name__ == " __main__ " :
import numpy as np
s = LSM6DS3_Accel ( 1 )
s . init ( )
time . sleep ( 0.2 )
e = s . get_event ( 0 )
print ( e )
print ( np . linalg . norm ( e . acceleration . v ) )
s . shutdown ( )