346 lines
11 KiB
Python
346 lines
11 KiB
Python
#!/usr/bin/env python
|
|
"""
|
|
LIS3DH Python Library for Raspberry Pi
|
|
Created by Matt Dyson (mattdyson.org)
|
|
Project hosted at https://github.com/mattdy/python-lis3dh
|
|
Version 1.0 - 10/01/16
|
|
Version 1.1 - 19/03/16 (Mal Smalley) Adding click detection
|
|
Version 1.2 - 21/01/18 (holzfigure) Using new Adafruit library Adafruit_GPIO;
|
|
Changing default busnumber (-1 -> 1);
|
|
Fixing indentation issues;
|
|
Increasing PEP8 and Python3 compatibility;
|
|
Replacing variable name "range"
|
|
(which is a keyword) with "g-range";
|
|
|
|
Requires the Adafruit-GPIO library
|
|
https://github.com/adafruit/Adafruit_Python_GPIO
|
|
|
|
Inspiration and assistance from:
|
|
- https://github.com/adafruit/Adafruit_LIS3DH
|
|
- https://www.adafruit.com/datasheets/LIS3DH.pdf
|
|
"""
|
|
|
|
from Adafruit_GPIO import I2C
|
|
import RPi.GPIO as GPIO # needed for Hardware interrupt
|
|
|
|
|
|
class LIS3DH:
|
|
# I2C
|
|
i2c = None
|
|
I2C_ADDRESS_1 = 0x18
|
|
I2C_ADDRESS_2 = 0x19
|
|
# default
|
|
I2C_DEFAULT = I2C_ADDRESS_1
|
|
|
|
# bus
|
|
BUS_NUMBER = 1 # -1
|
|
|
|
# Ranges
|
|
RANGE_2G = 0b00 # default
|
|
RANGE_4G = 0b01
|
|
RANGE_8G = 0b10
|
|
RANGE_16G = 0b11
|
|
# default
|
|
RANGE_DEFAULT = RANGE_2G
|
|
|
|
# Refresh rates
|
|
DATARATE_400HZ = 0b0111 # 400Hz # default
|
|
DATARATE_200HZ = 0b0110 # 200Hz
|
|
DATARATE_100HZ = 0b0101 # 100Hz
|
|
DATARATE_50HZ = 0b0100 # 50Hz
|
|
DATARATE_25HZ = 0b0011 # 25Hz
|
|
DATARATE_10HZ = 0b0010 # 10Hz
|
|
DATARATE_1HZ = 0b0001 # 1Hz
|
|
DATARATE_POWERDOWN = 0 # Power down
|
|
DATARATE_LOWPOWER_1K6HZ = 0b1000 # Low power mode (1.6KHz)
|
|
DATARATE_LOWPOWER_5KHZ = (
|
|
0b1001 # Low power mode (5KHz) / Normal power mode (1.25KHz)
|
|
)
|
|
# default
|
|
DATARATE_DEFAULT = DATARATE_400HZ
|
|
|
|
# Registers
|
|
REG_STATUS1 = 0x07
|
|
REG_OUTADC1_L = 0x08
|
|
REG_OUTADC1_H = 0x09
|
|
REG_OUTADC2_L = 0x0A
|
|
REG_OUTADC2_H = 0x0B
|
|
REG_OUTADC3_L = 0x0C
|
|
REG_OUTADC3_H = 0x0D
|
|
REG_INTCOUNT = 0x0E
|
|
REG_WHOAMI = 0x0F # Device identification register
|
|
REG_TEMPCFG = 0x1F
|
|
REG_CTRL1 = (
|
|
0x20 # Used for data rate selection, and enabling/disabling individual axis
|
|
)
|
|
REG_CTRL2 = 0x21
|
|
REG_CTRL3 = 0x22
|
|
REG_CTRL4 = (
|
|
0x23 # Used for BDU, scale selection, resolution selection and self-testing
|
|
)
|
|
REG_CTRL5 = 0x24
|
|
REG_CTRL6 = 0x25
|
|
REG_REFERENCE = 0x26
|
|
REG_STATUS2 = 0x27
|
|
REG_OUT_X_L = 0x28
|
|
REG_OUT_X_H = 0x29
|
|
REG_OUT_Y_L = 0x2A
|
|
REG_OUT_Y_H = 0x2B
|
|
REG_OUT_Z_L = 0x2C
|
|
REG_OUT_Z_H = 0x2D
|
|
REG_FIFOCTRL = 0x2E
|
|
REG_FIFOSRC = 0x2F
|
|
REG_INT1CFG = 0x30
|
|
REG_INT1SRC = 0x31
|
|
REG_INT1THS = 0x32
|
|
REG_INT1DUR = 0x33
|
|
REG_CLICKCFG = 0x38
|
|
REG_CLICKSRC = 0x39
|
|
REG_CLICKTHS = 0x3A
|
|
REG_TIMELIMIT = 0x3B
|
|
REG_TIMELATENCY = 0x3C
|
|
REG_TIMEWINDOW = 0x3D
|
|
|
|
# Values
|
|
DEVICE_ID = 0x33
|
|
INT_IO = 0x04 # GPIO pin for interrupt
|
|
CLK_NONE = 0x00
|
|
CLK_SINGLE = 0x01
|
|
CLK_DOUBLE = 0x02
|
|
|
|
AXIS_X = 0x00
|
|
AXIS_Y = 0x01
|
|
AXIS_Z = 0x02
|
|
|
|
# changed busnumber to 1 (from -1)
|
|
# alternative i2c address=0x19
|
|
def __init__(
|
|
self,
|
|
address=I2C_DEFAULT,
|
|
bus=BUS_NUMBER,
|
|
g_range=RANGE_DEFAULT,
|
|
datarate=DATARATE_DEFAULT,
|
|
debug=False,
|
|
):
|
|
self.isDebug = debug
|
|
self.debug("Initialising LIS3DH")
|
|
|
|
# self.i2c = Adafruit_I2C(address, busnum=bus)
|
|
self.i2c = I2C.Device(address, busnum=bus)
|
|
self.address = address
|
|
|
|
try:
|
|
val = self.i2c.readU8(self.REG_WHOAMI)
|
|
if val != self.DEVICE_ID:
|
|
# raise Exception(("Device ID incorrect - expected 0x%X, " +
|
|
# "got 0x%X at address 0x%X") % (
|
|
# self.DEVICE_ID, val, self.address))
|
|
raise Exception(
|
|
(
|
|
"Device ID incorrect - expected 0x{:x}, "
|
|
+ "got 0x{:x} at address 0x{:x}"
|
|
).format(self.DEVICE_ID, val, self.address)
|
|
)
|
|
|
|
self.debug(
|
|
("Successfully connected to LIS3DH " + "at address 0x{:x}").format(
|
|
self.address
|
|
)
|
|
)
|
|
except Exception as e:
|
|
print("Error establishing connection with LIS3DH")
|
|
print(e)
|
|
|
|
# Enable all axis
|
|
self.setAxisStatus(self.AXIS_X, True)
|
|
self.setAxisStatus(self.AXIS_Y, True)
|
|
self.setAxisStatus(self.AXIS_Z, True)
|
|
|
|
# Set refresh rate (default: 400Hz)
|
|
self.setDataRate(datarate)
|
|
|
|
self.setHighResolution()
|
|
self.setBDU()
|
|
|
|
self.setRange(g_range)
|
|
|
|
# Get reading from X axis
|
|
def getX(self):
|
|
return self.getAxis(self.AXIS_X)
|
|
|
|
# Get reading from Y axis
|
|
def getY(self):
|
|
return self.getAxis(self.AXIS_Y)
|
|
|
|
# Get reading from Z axis
|
|
def getZ(self):
|
|
return self.getAxis(self.AXIS_Z)
|
|
|
|
# Get a reading from the desired axis
|
|
def getAxis(self, axis):
|
|
# Determine which register we need to read from (2 per axis)
|
|
base = self.REG_OUT_X_L + (2 * axis)
|
|
|
|
# Read the first register (lower bits)
|
|
low = self.i2c.readU8(base)
|
|
# Read the next register (higher bits)
|
|
high = self.i2c.readU8(base + 1)
|
|
# Combine the two components
|
|
res = low | (high << 8)
|
|
# Calculate the twos compliment of the result
|
|
res = self.twosComp(res)
|
|
|
|
# Fetch the range we're set to, so we can
|
|
# accurately calculate the result
|
|
g_range = self.getRange()
|
|
divisor = 1
|
|
if g_range == self.RANGE_2G:
|
|
divisor = 16380
|
|
elif g_range == self.RANGE_4G:
|
|
divisor = 8190
|
|
elif g_range == self.RANGE_8G:
|
|
divisor = 4096
|
|
elif g_range == self.RANGE_16G:
|
|
divisor = 1365.33
|
|
|
|
return float(res) / divisor
|
|
|
|
# Get the range that the sensor is currently set to
|
|
def getRange(self):
|
|
val = self.i2c.readU8(self.REG_CTRL4) # Get value from register
|
|
val = val >> 4 # Remove lowest 4 bits
|
|
val &= 0b0011 # Mask off two highest bits
|
|
|
|
if val == self.RANGE_2G:
|
|
return self.RANGE_2G
|
|
elif val == self.RANGE_4G:
|
|
return self.RANGE_4G
|
|
elif val == self.RANGE_8G:
|
|
return self.RANGE_8G
|
|
else:
|
|
return self.RANGE_16G
|
|
|
|
# Set the range of the sensor (2G, 4G, 8G, 16G)
|
|
def setRange(self, g_range):
|
|
if g_range < 0 or g_range > 3:
|
|
raise Exception("Tried to set invalid range")
|
|
|
|
val = self.i2c.readU8(self.REG_CTRL4) # Get value from register
|
|
val &= ~(0b110000) # Mask off lowest 4 bits
|
|
val |= g_range << 4 # Write in our new range
|
|
self.writeRegister(self.REG_CTRL4, val) # Write back to register
|
|
|
|
# Enable or disable an individual axis
|
|
# Read status from CTRL_REG1, then write back with
|
|
# appropriate status bit changed
|
|
def setAxisStatus(self, axis, enable):
|
|
if axis < 0 or axis > 2:
|
|
raise Exception("Tried to modify invalid axis")
|
|
|
|
current = self.i2c.readU8(self.REG_CTRL1)
|
|
status = 1 if enable else 0
|
|
final = self.setBit(current, axis, status)
|
|
self.writeRegister(self.REG_CTRL1, final)
|
|
|
|
def setInterrupt(self, mycallback):
|
|
GPIO.setmode(GPIO.BCM)
|
|
GPIO.setup(self.INT_IO, GPIO.IN)
|
|
GPIO.add_event_detect(self.INT_IO, GPIO.RISING, callback=mycallback)
|
|
|
|
def setClick(
|
|
self,
|
|
clickmode,
|
|
clickthresh=80,
|
|
timelimit=10,
|
|
timelatency=20,
|
|
timewindow=100,
|
|
mycallback=None,
|
|
):
|
|
if clickmode == self.CLK_NONE:
|
|
val = self.i2c.readU8(self.REG_CTRL3) # Get value from register
|
|
val &= ~(0x80) # unset bit 8 to disable interrupt
|
|
self.writeRegister(self.REG_CTRL3, val) # Write back to register
|
|
self.writeRegister(self.REG_CLICKCFG, 0) # disable all interrupts
|
|
return
|
|
self.writeRegister(self.REG_CTRL3, 0x80) # turn on int1 click
|
|
self.writeRegister(self.REG_CTRL5, 0x08) # latch interrupt on int1
|
|
|
|
if clickmode == self.CLK_SINGLE:
|
|
# turn on all axes & singletap
|
|
self.writeRegister(self.REG_CLICKCFG, 0x15)
|
|
if clickmode == self.CLK_DOUBLE:
|
|
# turn on all axes & doubletap
|
|
self.writeRegister(self.REG_CLICKCFG, 0x2A)
|
|
|
|
# set timing parameters
|
|
self.writeRegister(self.REG_CLICKTHS, clickthresh)
|
|
self.writeRegister(self.REG_TIMELIMIT, timelimit)
|
|
self.writeRegister(self.REG_TIMELATENCY, timelatency)
|
|
self.writeRegister(self.REG_TIMEWINDOW, timewindow)
|
|
|
|
if mycallback is not None:
|
|
self.setInterrupt(mycallback)
|
|
|
|
def getClick(self):
|
|
reg = self.i2c.readU8(self.REG_CLICKSRC) # read click register
|
|
self.i2c.readU8(self.REG_INT1SRC) # reset interrupt flag
|
|
return reg
|
|
|
|
# Set the rate (cycles per second) at which data is gathered
|
|
def setDataRate(self, dataRate):
|
|
val = self.i2c.readU8(self.REG_CTRL1) # Get current value
|
|
val &= 0b1111 # Mask off lowest 4 bits
|
|
val |= dataRate << 4 # Write in our new data rate to highest 4 bits
|
|
self.writeRegister(self.REG_CTRL1, val) # Write back to register
|
|
|
|
# Set whether we want to use high resolution or not
|
|
def setHighResolution(self, highRes=True):
|
|
val = self.i2c.readU8(self.REG_CTRL4) # Get current value
|
|
status = 1 if highRes else 0
|
|
|
|
# High resolution is bit 4 of REG_CTRL4
|
|
final = self.setBit(val, 3, status)
|
|
self.writeRegister(self.REG_CTRL4, final)
|
|
|
|
# Set whether we want to use block data update or not
|
|
# False = output registers not updated until MSB and LSB reading
|
|
def setBDU(self, bdu=True):
|
|
val = self.i2c.readU8(self.REG_CTRL4) # Get current value
|
|
status = 1 if bdu else 0
|
|
|
|
# Block data update is bit 8 of REG_CTRL4
|
|
final = self.setBit(val, 7, status)
|
|
self.writeRegister(self.REG_CTRL4, final)
|
|
|
|
# Write the given value to the given register
|
|
def writeRegister(self, register, value):
|
|
self.debug("WRT {} to register 0x{:x}".format(bin(value), register))
|
|
self.i2c.write8(register, value)
|
|
|
|
# Set the bit at index 'bit' to 'value' on 'input' and return
|
|
def setBit(self, input, bit, value):
|
|
mask = 1 << bit
|
|
input &= ~mask
|
|
if value:
|
|
input |= mask
|
|
return input
|
|
|
|
# Return a 16-bit signed number (two's compliment)
|
|
# Thanks to http://stackoverflow.com/questions/16124059/trying-to-
|
|
# read-a-twos-complement-16bit-into-a-signed-decimal
|
|
def twosComp(self, x):
|
|
if 0x8000 & x:
|
|
x = -(0x010000 - x)
|
|
return x
|
|
|
|
# Print an output of all registers
|
|
def dumpRegisters(self):
|
|
for x in range(0x0, 0x3D):
|
|
read = self.i2c.readU8(x)
|
|
print("{:x}: {}".format(x, bin(read)))
|
|
|
|
def debug(self, message):
|
|
if not self.isDebug:
|
|
return
|
|
print(message)
|