platypush/platypush/plugins/sensor/lis3dh/lib/LIS3DH.py

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)