Source code for lis2hh

"""LIS2HH Driver - i2c Accelerometer

This module provides high-level functions to access the basic accelerometer features,
and low-level functions to allow access to the full register set for advanced usage.

Many advanced features are available - see `datasheet <https://www.st.com/resource/en/datasheet/lis2hh12.pdf>`_
"""
import machine
import time

# Device registers
LIS_TEMP_L = 0x0b
LIS_TEMP_L = 0x0c
LIS_DEV_ID = 0x0f  # hardcoded WHO_AM_I ID
LIS_ACT_THS = 0x1e
LIS_ACT_DUR = 0x1f
LIS_CTRL_BASE = 0x1f
LIS_CTRL_REG1 = 0x20
LIS_CTRL_REG2 = 0x21
LIS_CTRL_REG3 = 0x22
LIS_CTRL_REG4 = 0x23
LIS_CTRL_REG5 = 0x24
LIS_CTRL_REG6 = 0x25
LIS_CTRL_REG7 = 0x26
LIS_STATUS_REG = 0x27
LIS_OUT_REG_MULTI = 0x28 # 3 axes XYZ, each 16bit 2's complement
LIS_FIFO_CTRL = 0x2e
LIS_FIFO_SRC = 0x2f
LIS_IG1_CFG = 0x30  # Interrupt Generator 1
LIS_IG1_SRC = 0x31
LIS_IG1_THRESH = 0x32  # IG1 thresholds
LIS_IG1_THRESH_X = 0x32
LIS_IG1_THRESH_Y = 0x33
LIS_IG1_THRESH_Z = 0x34
LIS_IG1_DURATION = 0x35  # number of ODR cycles (e.g. 1.25ms per LSB for 800Hz ODR)
LIS_IG2_CFG = 0x36  # Interrupt Generator 2
LIS_IG2_SRC = 0x37
LIS_IG2_THRESH = 0x38   # One threshold for all 3 axes
LIS_IG2_DURATION = 0x39

LIS_VFY_DEV_ID = 0x41

# Default values
LIS_DEF_CTRL1 = 0xef   # ODR=800Hz, High-Rez mode, XYZ axes enabled (180uA fangs-out mode)
LIS_DEF_CTRL2 = 0x22   # High pass filter enabled (8Hz cutoff) interrupt on IG1 (data reg not filtered)
LIS_DEF_CTRL3 = 0x08   # IG1 interrupt on INT1 pad
LIS_DEF_CTRL4 = 0x04   # +/- 2G full-scale range, address auto-increment
LIS_DEF_CTRL5 = 0x00   # No decimation, INT active high PP
LIS_DEF_CTRL6 = 0x00   # Nothing on INT2 pad
LIS_DEF_CTRL7 = 0x00   # Non latching IRQ


[docs]class LIS2HH: """Device driver for LIS2HH accelerometer, controlled via i2c bus.""" def __init__(self, addr=0x1e, int1_pin=machine.Pin("PB8", machine.Pin.IN)): self.addr = addr self.int1_pin = int1_pin self.i2c = machine.I2C(1, freq=400000) # Hardware I2C on port 1. Use 400kHz I2C freq. self.orientation = (0,0,0) self.buf1 = bytearray(1) # Pre-allocate command/resp buffer usable from IRQ self.buf2 = bytearray(2) self.reset()
[docs] def reset(self): """Init registers to default config""" self.ctrl(1, LIS_DEF_CTRL1) self.ctrl(2, LIS_DEF_CTRL2) self.ctrl(3, LIS_DEF_CTRL3) self.ctrl(4, LIS_DEF_CTRL4) self.ctrl(5, LIS_DEF_CTRL5) self.ctrl(6, LIS_DEF_CTRL6) self.ctrl(7, LIS_DEF_CTRL7)
[docs] def ctrl(self, reg, cmd): """Set control registers""" self.buf2[0] = LIS_CTRL_BASE + reg self.buf2[1] = cmd self.i2c.writeto(self.addr, self.buf2)
[docs] def set_byte(self, reg, val): """Set byte register""" self.buf2[0] = reg self.buf2[1] = val self.i2c.writeto(self.addr, self.buf2)
def get_byte(self, reg): self.buf1[0] = reg self.i2c.writeto(self.addr, self.buf1) self.i2c.readfrom_into(self.addr, self.buf1) return self.buf1[0]
[docs] def verify_id(self): """Verify device ID - return True if correct""" return self.get_byte(LIS_DEV_ID) == LIS_VFY_DEV_ID
[docs] @staticmethod def test_exceeds(a, b, delta): """Test each iterable value: a[0..n] - b[0..n] > delta""" for i,j in zip(a,b): if i - j <= delta: return False return True
[docs] def self_test(self): """Run self-test and return device to default state. Return True if passed""" result = False normal = self.read() self.ctrl(5, 0x04) # Induce positive force time.sleep_ms(200) pos_test = self.read() if self.test_exceeds(normal, pos_test, 1000): self.ctrl(5, 0x08) # Induce negative force time.sleep_ms(200) neg_test = self.read() if self.test_exceeds(neg_test, normal, 1000): result = True self.ctrl(5, LIS_DEF_CTRL5) return result
@staticmethod def signed16(buf): val = (buf[1] << 8) | buf[0] # Little endian 16-bit 2's complement value return (val & 0x8000) - (val & 0x7fff) # Convert to signed integer
[docs] def read(self): """Read current XYZ axis values and update 'self.orientation' tuple Returns: `tuple` of (x, y, z) axis values. - Default full-scale range is ±2g. - Values are signed 16-bit integer (-32767 to +32768) """ self.i2c.writeto(self.addr, bytearray((LIS_OUT_REG_MULTI,))) s = self.i2c.readfrom(self.addr, 6) x = self.signed16(s[0:2]) y = self.signed16(s[2:4]) z = self.signed16(s[4:6]) self.orientation = (x,y,z) return self.orientation
[docs] def wake(self): """Recover from sleep""" self.ctrl(1, LIS_CTRL_VAL1)
[docs] def sleep(self): """Shutdown operation - draws about 5uA""" self.ctrl(1, 0x00) # ODR=0 is power down
[docs] def dump_axes(self): """Debug - send axis values to stdout""" print("X={}, Y={}, Z={}".format(*self.read()))
def handle_irq(self, pin): # Fetch IRQ source axes and clear interrupt src = self.get_byte(LIS_IG1_SRC) axes = (bool(src & 0x02), bool(src & 0x08), bool(src & 0x20)) # (x,y,z) event occurred if self.user_int1_cb: self.user_int1_cb(axes) else: print ("INT1={}".format(axes))
[docs] def enable_int1(self, threshold=20, duration=5, cb=None): """Enable inertial interrupt on all axes. Threshold is upper byte of 16-bit value, 0 disables.""" if threshold: self.user_int1_cb = cb threshold &= 0x7f self.set_byte(LIS_IG1_CFG, 0x2a) # AND of abs(accel) > THR for all axes self.set_byte(LIS_IG1_THRESH_X, threshold) self.set_byte(LIS_IG1_THRESH_Y, threshold) self.set_byte(LIS_IG1_THRESH_Z, threshold) self.set_byte(LIS_IG1_DURATION, duration) # Enable IRQ, using "soft IRQ handler" which automatically does a micropython.schedule to defer to main context self.int1_pin.irq(self.handle_irq, trigger=machine.Pin.IRQ_RISING, hard=False) else: # Disable interrupt self.int1_pin.irq(handler=None) self.set_byte(LIS_IG1_CFG, 0x00)