/ adafruit_lis3dh.py
adafruit_lis3dh.py
1 # Adafruit LIS3DH Accelerometer CircuitPython Driver 2 # Based on the Arduino LIS3DH driver from: 3 # https://github.com/adafruit/Adafruit_LIS3DH/ 4 # Author: Tony DiCola 5 # License: MIT License (https://en.wikipedia.org/wiki/MIT_License) 6 """ 7 `adafruit_lis3dh` 8 ==================================================== 9 10 CircuitPython driver for the LIS3DH accelerometer. 11 12 See examples in the examples directory. 13 14 * Author(s): Tony DiCola 15 16 Implementation Notes 17 -------------------- 18 19 **Hardware:** 20 21 * `Adafruit LIS3DH Triple-Axis Accelerometer Breakout 22 <https://www.adafruit.com/product/2809>`_ 23 24 * `Circuit Playground Express <https://www.adafruit.com/product/3333>`_ 25 26 **Software and Dependencies:** 27 28 * Adafruit CircuitPython firmware for the ESP8622 and M0-based boards: 29 https://github.com/adafruit/circuitpython/releases 30 * Adafruit's Bus Device library: https://github.com/adafruit/Adafruit_CircuitPython_BusDevice 31 """ 32 33 import time 34 import math 35 from collections import namedtuple 36 import struct 37 import digitalio 38 39 from micropython import const 40 41 __version__ = "0.0.0-auto.0" 42 __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_LIS3DH.git" 43 44 # Register addresses: 45 # pylint: disable=bad-whitespace 46 _REG_OUTADC1_L = const(0x08) 47 _REG_WHOAMI = const(0x0F) 48 _REG_TEMPCFG = const(0x1F) 49 _REG_CTRL1 = const(0x20) 50 _REG_CTRL3 = const(0x22) 51 _REG_CTRL4 = const(0x23) 52 _REG_CTRL5 = const(0x24) 53 _REG_OUT_X_L = const(0x28) 54 _REG_INT1SRC = const(0x31) 55 _REG_CLICKCFG = const(0x38) 56 _REG_CLICKSRC = const(0x39) 57 _REG_CLICKTHS = const(0x3A) 58 _REG_TIMELIMIT = const(0x3B) 59 _REG_TIMELATENCY = const(0x3C) 60 _REG_TIMEWINDOW = const(0x3D) 61 62 # Register value constants: 63 RANGE_16_G = const(0b11) # +/- 16g 64 RANGE_8_G = const(0b10) # +/- 8g 65 RANGE_4_G = const(0b01) # +/- 4g 66 RANGE_2_G = const(0b00) # +/- 2g (default value) 67 DATARATE_1344_HZ = const(0b1001) # 1.344 KHz 68 DATARATE_400_HZ = const(0b0111) # 400Hz 69 DATARATE_200_HZ = const(0b0110) # 200Hz 70 DATARATE_100_HZ = const(0b0101) # 100Hz 71 DATARATE_50_HZ = const(0b0100) # 50Hz 72 DATARATE_25_HZ = const(0b0011) # 25Hz 73 DATARATE_10_HZ = const(0b0010) # 10 Hz 74 DATARATE_1_HZ = const(0b0001) # 1 Hz 75 DATARATE_POWERDOWN = const(0) 76 DATARATE_LOWPOWER_1K6HZ = const(0b1000) 77 DATARATE_LOWPOWER_5KHZ = const(0b1001) 78 79 # Other constants 80 STANDARD_GRAVITY = 9.806 81 # pylint: enable=bad-whitespace 82 83 # the named tuple returned by the class 84 AccelerationTuple = namedtuple("acceleration", ("x", "y", "z")) 85 86 87 class LIS3DH: 88 """Driver base for the LIS3DH accelerometer.""" 89 90 def __init__(self, int1=None, int2=None): 91 # Check device ID. 92 device_id = self._read_register_byte(_REG_WHOAMI) 93 if device_id != 0x33: 94 raise RuntimeError("Failed to find LIS3DH!") 95 # Reboot 96 self._write_register_byte(_REG_CTRL5, 0x80) 97 time.sleep(0.01) # takes 5ms 98 # Enable all axes, normal mode. 99 self._write_register_byte(_REG_CTRL1, 0x07) 100 # Set 400Hz data rate. 101 self.data_rate = DATARATE_400_HZ 102 # High res & BDU enabled. 103 self._write_register_byte(_REG_CTRL4, 0x88) 104 # Enable ADCs. 105 self._write_register_byte(_REG_TEMPCFG, 0x80) 106 # Latch interrupt for INT1 107 self._write_register_byte(_REG_CTRL5, 0x08) 108 109 # Initialise interrupt pins 110 self._int1 = int1 111 self._int2 = int2 112 if self._int1: 113 self._int1.direction = digitalio.Direction.INPUT 114 self._int1.pull = digitalio.Pull.UP 115 116 @property 117 def data_rate(self): 118 """The data rate of the accelerometer. Can be DATA_RATE_400_HZ, DATA_RATE_200_HZ, 119 DATA_RATE_100_HZ, DATA_RATE_50_HZ, DATA_RATE_25_HZ, DATA_RATE_10_HZ, 120 DATA_RATE_1_HZ, DATA_RATE_POWERDOWN, DATA_RATE_LOWPOWER_1K6HZ, or 121 DATA_RATE_LOWPOWER_5KHZ.""" 122 ctl1 = self._read_register_byte(_REG_CTRL1) 123 return (ctl1 >> 4) & 0x0F 124 125 @data_rate.setter 126 def data_rate(self, rate): 127 ctl1 = self._read_register_byte(_REG_CTRL1) 128 ctl1 &= ~(0xF0) 129 ctl1 |= rate << 4 130 self._write_register_byte(_REG_CTRL1, ctl1) 131 132 @property 133 def range(self): 134 """The range of the accelerometer. Can be RANGE_2_G, RANGE_4_G, RANGE_8_G, or 135 RANGE_16_G.""" 136 ctl4 = self._read_register_byte(_REG_CTRL4) 137 return (ctl4 >> 4) & 0x03 138 139 @range.setter 140 def range(self, range_value): 141 ctl4 = self._read_register_byte(_REG_CTRL4) 142 ctl4 &= ~0x30 143 ctl4 |= range_value << 4 144 self._write_register_byte(_REG_CTRL4, ctl4) 145 146 @property 147 def acceleration(self): 148 """The x, y, z acceleration values returned in a 3-tuple and are in m / s ^ 2.""" 149 divider = 1 150 accel_range = self.range 151 if accel_range == RANGE_16_G: 152 divider = 1365 153 elif accel_range == RANGE_8_G: 154 divider = 4096 155 elif accel_range == RANGE_4_G: 156 divider = 8190 157 elif accel_range == RANGE_2_G: 158 divider = 16380 159 160 x, y, z = struct.unpack("<hhh", self._read_register(_REG_OUT_X_L | 0x80, 6)) 161 162 # convert from Gs to m / s ^ 2 and adjust for the range 163 x = (x / divider) * STANDARD_GRAVITY 164 y = (y / divider) * STANDARD_GRAVITY 165 z = (z / divider) * STANDARD_GRAVITY 166 167 return AccelerationTuple(x, y, z) 168 169 def shake(self, shake_threshold=30, avg_count=10, total_delay=0.1): 170 """ 171 Detect when the accelerometer is shaken. Optional parameters: 172 173 :param shake_threshold: Increase or decrease to change shake sensitivity. This 174 requires a minimum value of 10. 10 is the total 175 acceleration if the board is not moving, therefore 176 anything less than 10 will erroneously report a constant 177 shake detected. (Default 30) 178 179 :param avg_count: The number of readings taken and used for the average 180 acceleration. (Default 10) 181 182 :param total_delay: The total time in seconds it takes to obtain avg_count 183 readings from acceleration. (Default 0.1) 184 """ 185 shake_accel = (0, 0, 0) 186 for _ in range(avg_count): 187 # shake_accel creates a list of tuples from acceleration data. 188 # zip takes multiple tuples and zips them together, as in: 189 # In : zip([-0.2, 0.0, 9.5], [37.9, 13.5, -72.8]) 190 # Out: [(-0.2, 37.9), (0.0, 13.5), (9.5, -72.8)] 191 # map applies sum to each member of this tuple, resulting in a 192 # 3-member list. tuple converts this list into a tuple which is 193 # used as shake_accel. 194 shake_accel = tuple(map(sum, zip(shake_accel, self.acceleration))) 195 time.sleep(total_delay / avg_count) 196 avg = tuple(value / avg_count for value in shake_accel) 197 total_accel = math.sqrt(sum(map(lambda x: x * x, avg))) 198 return total_accel > shake_threshold 199 200 def read_adc_raw(self, adc): 201 """Retrieve the raw analog to digital converter value. ADC must be a 202 value 1, 2, or 3. 203 """ 204 if adc < 1 or adc > 3: 205 raise ValueError("ADC must be a value 1 to 3!") 206 207 return struct.unpack( 208 "<h", self._read_register((_REG_OUTADC1_L + ((adc - 1) * 2)) | 0x80, 2)[0:2] 209 )[0] 210 211 def read_adc_mV(self, adc): # pylint: disable=invalid-name 212 """Read the specified analog to digital converter value in millivolts. 213 ADC must be a value 1, 2, or 3. NOTE the ADC can only measure voltages 214 in the range of ~900-1200mV! 215 """ 216 raw = self.read_adc_raw(adc) 217 # Interpolate between 900mV and 1800mV, see: 218 # https://learn.adafruit.com/adafruit-lis3dh-triple-axis-accelerometer-breakout/wiring-and-test#reading-the-3-adc-pins 219 # This is a simplified linear interpolation of: 220 # return y0 + (x-x0)*((y1-y0)/(x1-x0)) 221 # Where: 222 # x = ADC value 223 # x0 = -32512 224 # x1 = 32512 225 # y0 = 1800 226 # y1 = 900 227 return 1800 + (raw + 32512) * (-900 / 65024) 228 229 @property 230 def tapped(self): 231 """ 232 True if a tap was detected recently. Whether its a single tap or double tap is 233 determined by the tap param on ``set_tap``. ``tapped`` may be True over 234 multiple reads even if only a single tap or single double tap occurred if the 235 interrupt (int) pin is not specified. 236 237 The following example uses ``i2c`` and specifies the interrupt pin: 238 239 .. code-block:: python 240 241 import adafruit_lis3dh 242 import digitalio 243 244 i2c = busio.I2C(board.SCL, board.SDA) 245 int1 = digitalio.DigitalInOut(board.D11) # pin connected to interrupt 246 lis3dh = adafruit_lis3dh.LIS3DH_I2C(i2c, int1=int1) 247 lis3dh.range = adafruit_lis3dh.RANGE_8_G 248 249 """ 250 if self._int1 and not self._int1.value: 251 return False 252 raw = self._read_register_byte(_REG_CLICKSRC) 253 return raw & 0x40 > 0 254 255 def set_tap( 256 self, 257 tap, 258 threshold, 259 *, 260 time_limit=10, 261 time_latency=20, 262 time_window=255, 263 click_cfg=None 264 ): 265 """ 266 The tap detection parameters. 267 268 .. note:: Tap related registers are called ``CLICK_`` in the datasheet. 269 270 :param int tap: 0 to disable tap detection, 1 to detect only single 271 taps, and 2 to detect only double taps. 272 273 :param int threshold: A threshold for the tap detection. The higher the value 274 the less sensitive the detection. This changes based on 275 the accelerometer range. Good values are 5-10 for 16G, 276 10-20 for 8G, 20-40 for 4G, and 40-80 for 2G. 277 278 :param int time_limit: TIME_LIMIT register value (default 10). 279 :param int time_latency: TIME_LATENCY register value (default 20). 280 :param int time_window: TIME_WINDOW register value (default 255). 281 :param int click_cfg: CLICK_CFG register value. 282 """ 283 if (tap < 0 or tap > 2) and click_cfg is None: 284 raise ValueError( 285 "Tap must be 0 (disabled), 1 (single tap), or 2 (double tap)!" 286 ) 287 if threshold > 127 or threshold < 0: 288 raise ValueError("Threshold out of range (0-127)") 289 290 ctrl3 = self._read_register_byte(_REG_CTRL3) 291 if tap == 0 and click_cfg is None: 292 # Disable click interrupt. 293 self._write_register_byte(_REG_CTRL3, ctrl3 & ~(0x80)) # Turn off I1_CLICK. 294 self._write_register_byte(_REG_CLICKCFG, 0) 295 return 296 self._write_register_byte(_REG_CTRL3, ctrl3 | 0x80) # Turn on int1 click output 297 298 if click_cfg is None: 299 if tap == 1: 300 click_cfg = 0x15 # Turn on all axes & singletap. 301 if tap == 2: 302 click_cfg = 0x2A # Turn on all axes & doubletap. 303 # Or, if a custom click configuration register value specified, use it. 304 self._write_register_byte(_REG_CLICKCFG, click_cfg) 305 self._write_register_byte(_REG_CLICKTHS, 0x80 | threshold) 306 self._write_register_byte(_REG_TIMELIMIT, time_limit) 307 self._write_register_byte(_REG_TIMELATENCY, time_latency) 308 self._write_register_byte(_REG_TIMEWINDOW, time_window) 309 310 def _read_register_byte(self, register): 311 # Read a byte register value and return it. 312 return self._read_register(register, 1)[0] 313 314 def _read_register(self, register, length): 315 # Read an arbitrarily long register (specified by length number of 316 # bytes) and return a bytearray of the retrieved data. 317 # Subclasses MUST implement this! 318 raise NotImplementedError 319 320 def _write_register_byte(self, register, value): 321 # Write a single byte register at the specified register address. 322 # Subclasses MUST implement this! 323 raise NotImplementedError 324 325 326 class LIS3DH_I2C(LIS3DH): 327 """Driver for the LIS3DH accelerometer connected over I2C.""" 328 329 def __init__(self, i2c, *, address=0x18, int1=None, int2=None): 330 import adafruit_bus_device.i2c_device as i2c_device # pylint: disable=import-outside-toplevel 331 332 self._i2c = i2c_device.I2CDevice(i2c, address) 333 self._buffer = bytearray(6) 334 super().__init__(int1=int1, int2=int2) 335 336 def _read_register(self, register, length): 337 self._buffer[0] = register & 0xFF 338 with self._i2c as i2c: 339 i2c.write(self._buffer, start=0, end=1) 340 i2c.readinto(self._buffer, start=0, end=length) 341 return self._buffer 342 343 def _write_register_byte(self, register, value): 344 self._buffer[0] = register & 0xFF 345 self._buffer[1] = value & 0xFF 346 with self._i2c as i2c: 347 i2c.write(self._buffer, start=0, end=2) 348 349 350 class LIS3DH_SPI(LIS3DH): 351 """Driver for the LIS3DH accelerometer connected over SPI.""" 352 353 def __init__(self, spi, cs, *, baudrate=100000, int1=None, int2=None): 354 import adafruit_bus_device.spi_device as spi_device # pylint: disable=import-outside-toplevel 355 356 self._spi = spi_device.SPIDevice(spi, cs, baudrate=baudrate) 357 self._buffer = bytearray(6) 358 super().__init__(int1=int1, int2=int2) 359 360 def _read_register(self, register, length): 361 if length == 1: 362 self._buffer[0] = (register | 0x80) & 0xFF # Read single, bit 7 high. 363 else: 364 self._buffer[0] = (register | 0xC0) & 0xFF # Read multiple, bit 6&7 high. 365 with self._spi as spi: 366 spi.write(self._buffer, start=0, end=1) # pylint: disable=no-member 367 spi.readinto(self._buffer, start=0, end=length) # pylint: disable=no-member 368 return self._buffer 369 370 def _write_register_byte(self, register, value): 371 self._buffer[0] = register & 0x7F # Write, bit 7 low. 372 self._buffer[1] = value & 0xFF 373 with self._spi as spi: 374 spi.write(self._buffer, start=0, end=2) # pylint: disable=no-member