|
| 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 | +import ustruct |
| 7 | + |
| 8 | +import adafruit_bus_device.i2c_device as i2c_device |
| 9 | +import adafruit_bus_device.spi_device as spi_device |
| 10 | + |
| 11 | + |
| 12 | +# Register addresses: |
| 13 | +REG_OUTADC1_L = const(0x08) |
| 14 | +REG_WHOAMI = const(0x0F) |
| 15 | +REG_TEMPCFG = const(0x1F) |
| 16 | +REG_CTRL1 = const(0x20) |
| 17 | +REG_CTRL3 = const(0x22) |
| 18 | +REG_CTRL4 = const(0x23) |
| 19 | +REG_CTRL5 = const(0x24) |
| 20 | +REG_OUT_X_L = const(0x28) |
| 21 | +REG_CLICKCFG = const(0x38) |
| 22 | +REG_CLICKSRC = const(0x39) |
| 23 | +REG_CLICKTHS = const(0x3A) |
| 24 | +REG_TIMELIMIT = const(0x3B) |
| 25 | +REG_TIMELATENCY = const(0x3C) |
| 26 | +REG_TIMEWINDOW = const(0x3D) |
| 27 | + |
| 28 | +# Register value constants: |
| 29 | +RANGE_16_G = const(0b11) # +/- 16g |
| 30 | +RANGE_8_G = const(0b10) # +/- 8g |
| 31 | +RANGE_4_G = const(0b01) # +/- 4g |
| 32 | +RANGE_2_G = const(0b00) # +/- 2g (default value) |
| 33 | +DATARATE_400_HZ = const(0b0111) # 400Hz |
| 34 | +DATARATE_200_HZ = const(0b0110) # 200Hz |
| 35 | +DATARATE_100_HZ = const(0b0101) # 100Hz |
| 36 | +DATARATE_50_HZ = const(0b0100) # 50Hz |
| 37 | +DATARATE_25_HZ = const(0b0011) # 25Hz |
| 38 | +DATARATE_10_HZ = const(0b0010) # 10 Hz |
| 39 | +DATARATE_1_HZ = const(0b0001) # 1 Hz |
| 40 | +DATARATE_POWERDOWN = const(0) |
| 41 | +DATARATE_LOWPOWER_1K6HZ = const(0b1000) |
| 42 | +DATARATE_LOWPOWER_5KHZ = const(0b1001) |
| 43 | + |
| 44 | + |
| 45 | +class LIS3DH: |
| 46 | + |
| 47 | + def __init__(self): |
| 48 | + # Check device ID. |
| 49 | + device_id = self._read_register_byte(REG_WHOAMI) |
| 50 | + if device_id != 0x33: |
| 51 | + raise RuntimeError('Failed to find LIS3DH!') |
| 52 | + # Enable all axes, normal mode. |
| 53 | + self._write_register_byte(REG_CTRL1, 0x07) |
| 54 | + # Set 400Hz data rate. |
| 55 | + self.data_rate = DATARATE_400_HZ |
| 56 | + # High res & BDU enabled. |
| 57 | + self._write_register_byte(REG_CTRL4, 0x88) |
| 58 | + # DRDY on INT1. |
| 59 | + self._write_register_byte(REG_CTRL3, 0x10) |
| 60 | + # Enable ADCs. |
| 61 | + self._write_register_byte(REG_TEMPCFG, 0x80) |
| 62 | + |
| 63 | + @property |
| 64 | + def data_rate(self): |
| 65 | + """Get/set the data rate of the accelerometer. Can be DATA_RATE_400_HZ, |
| 66 | + DATA_RATE_200_HZ, DATA_RATE_100_HZ, DATA_RATE_50_HZ, DATA_RATE_25_HZ, |
| 67 | + DATA_RATE_10_HZ, DATA_RATE_1_HZ, DATA_RATE_POWERDOWN, DATA_RATE_LOWPOWER_1K6HZ, |
| 68 | + or DATA_RATE_LOWPOWER_5KHZ. |
| 69 | + """ |
| 70 | + ctl1 = self._read_register_byte(REG_CTRL1) |
| 71 | + return (ctl1 >> 4) & 0x0F |
| 72 | + |
| 73 | + @data_rate.setter |
| 74 | + def data_rate(self, rate): |
| 75 | + ctl1 = self._read_register_byte(REG_CTRL1) |
| 76 | + ctl1 &= ~(0xF0) |
| 77 | + ctl1 |= rate << 4 |
| 78 | + self._write_register_byte(REG_CTRL1, ctl1) |
| 79 | + |
| 80 | + @property |
| 81 | + def range(self): |
| 82 | + """Get/set the range of the accelerometer. Can be RANGE_2_G, RANGE_4_G, |
| 83 | + RANGE_8_G, or RANGE_16_G. |
| 84 | + """ |
| 85 | + ctl4 = self._read_register_byte(REG_CTRL4) |
| 86 | + return (ctl4 >> 4) & 0x03 |
| 87 | + |
| 88 | + @range.setter |
| 89 | + def range(self, range_value): |
| 90 | + ctl4 = self._read_register_byte(REG_CTRL4) |
| 91 | + ctl4 &= ~(0x30) |
| 92 | + ctl4 |= range_value << 4 |
| 93 | + self._write_register_byte(REG_CTRL4, ctl4) |
| 94 | + |
| 95 | + def read_accel_g(self): |
| 96 | + """Read the x, y, z acceleration values. These values are returned in |
| 97 | + a 3-tuple and are in gravities (G). |
| 98 | + """ |
| 99 | + data = self._read_register(REG_OUT_X_L | 0x80, 6) |
| 100 | + x = ustruct.unpack('<h', data[0:2])[0] |
| 101 | + y = ustruct.unpack('<h', data[2:4])[0] |
| 102 | + z = ustruct.unpack('<h', data[4:6])[0] |
| 103 | + divider = 1 |
| 104 | + accel_range = self.range |
| 105 | + if accel_range == RANGE_16_G: |
| 106 | + divider = 1365 |
| 107 | + elif accel_range == RANGE_8_G: |
| 108 | + divider = 4096 |
| 109 | + elif accel_range == RANGE_4_G: |
| 110 | + divider = 8190 |
| 111 | + elif accel_range == RANGE_2_G: |
| 112 | + divider = 16380 |
| 113 | + return (x / divider, y / divider, z / divider) |
| 114 | + |
| 115 | + def read_adc_raw(self, adc): |
| 116 | + """Retrieve the raw analog to digital converter value. ADC must be a |
| 117 | + value 1, 2, or 3. |
| 118 | + """ |
| 119 | + if adc < 1 or adc > 3: |
| 120 | + raise ValueError('ADC must be a value 1 to 3!') |
| 121 | + data = self._read_register((REG_OUTADC1_L+((adc-1)*2)) | 0x80, 2) |
| 122 | + return ustruct.unpack('<h', data[0:2])[0] |
| 123 | + |
| 124 | + def read_adc_mV(self, adc): |
| 125 | + """Read the specified analog to digital converter value in millivolts. |
| 126 | + ADC must be a value 1, 2, or 3. NOTE the ADC can only measure voltages |
| 127 | + in the range of ~900-1200mV! |
| 128 | + """ |
| 129 | + raw = self.read_adc_raw(adc) |
| 130 | + # Interpolate between 900mV and 1800mV, see: |
| 131 | + # https://learn.adafruit.com/adafruit-lis3dh-triple-axis-accelerometer-breakout/wiring-and-test#reading-the-3-adc-pins |
| 132 | + # This is a simplified linear interpolation of: |
| 133 | + # return y0 + (x-x0)*((y1-y0)/(x1-x0)) |
| 134 | + # Where: |
| 135 | + # x = ADC value |
| 136 | + # x0 = -32512 |
| 137 | + # x1 = 32512 |
| 138 | + # y0 = 1800 |
| 139 | + # y1 = 900 |
| 140 | + return 1800+(raw+32512)*(-900/65024) |
| 141 | + |
| 142 | + def read_click_raw(self): |
| 143 | + """Read the raw click register byte value.""" |
| 144 | + return self._read_register_byte(REG_CLICKSRC) |
| 145 | + |
| 146 | + def read_click(self): |
| 147 | + """Read a 2-tuple of bools where the first value is True if a single |
| 148 | + click was detected and the second value is True if a double click was |
| 149 | + detected. |
| 150 | + """ |
| 151 | + raw = self.read_click_raw() |
| 152 | + return (raw & 0x10 > 0, raw & 0x20 > 0) |
| 153 | + |
| 154 | + def set_click(self, click, threshold, time_limit=10, time_latency=20, time_window=255): |
| 155 | + """Set the click detection parameters. Must specify at least: |
| 156 | + click - Set to 0 to disable click detection, 1 to detect only single |
| 157 | + clicks, and 2 to detect single & double clicks. |
| 158 | + threshold - A threshold for the click detection. The higher the value |
| 159 | + the less sensitive the detection. This changes based on |
| 160 | + the accelerometer range. Good values are 5-10 for 16G, |
| 161 | + 10-20 for 8G, 20-40 for 4G, and 40-80 for 2G. |
| 162 | + Optionally specify (see datasheet for meaning of these): |
| 163 | + time_limit - Time limit register value (default 10). |
| 164 | + time_latency - Time latency register value (default 20). |
| 165 | + time_window - Time window register value (default 255). |
| 166 | + """ |
| 167 | + if click < 0 or click > 2: |
| 168 | + raise ValueError('Click must be 0 (disabled), 1 (single click), or 2 (double click)!') |
| 169 | + if click == 0: |
| 170 | + # Disable click interrupt. |
| 171 | + r = self._read_register_byte(REG_CTRL3) |
| 172 | + r &= ~(0x80) # Turn off I1_CLICK. |
| 173 | + self._write_register_byte(REG_CTRL3, r) |
| 174 | + self._write_register_byte(REG_CLICKCFG, 0) |
| 175 | + return |
| 176 | + # Else enable click with specified parameters. |
| 177 | + self._write_register_byte(REG_CTRL3, 0x80) # Turn on int1 click. |
| 178 | + self._write_register_byte(REG_CTRL5, 0x08) # Latch interrupt on int1. |
| 179 | + if click == 1: |
| 180 | + self._write_register_byte(REG_CLICKCFG, 0x15) # Turn on all axes & singletap. |
| 181 | + elif click == 2: |
| 182 | + self._write_register_byte(REG_CLICKCFG, 0x2A) # Turn on all axes & doubletap. |
| 183 | + self._write_register_byte(REG_CLICKTHS, threshold) |
| 184 | + self._write_register_byte(REG_TIMELIMIT, time_limit) |
| 185 | + self._write_register_byte(REG_TIMELATENCY, time_latency) |
| 186 | + self._write_register_byte(REG_TIMEWINDOW, time_window) |
| 187 | + |
| 188 | + def _read_register_byte(self, register): |
| 189 | + # Read a byte register value and return it. |
| 190 | + return self._read_register(register, 1)[0] |
| 191 | + |
| 192 | + def _read_register(self, register, length): |
| 193 | + # Read an arbitrarily long register (specified by length number of |
| 194 | + # bytes) and return a bytearray of the retrieved data. |
| 195 | + # Subclasses MUST implement this! |
| 196 | + raise NotImplementedError |
| 197 | + |
| 198 | + def _write_register_byte(self, register, value): |
| 199 | + # Write a single byte register at the specified register address. |
| 200 | + # Subclasses MUST implement this! |
| 201 | + raise NotImplementedError |
| 202 | + |
| 203 | + |
| 204 | +class LIS3DH_I2C(LIS3DH): |
| 205 | + |
| 206 | + def __init__(self, i2c, address=0x18): |
| 207 | + self._i2c = i2c_device.I2CDevice(i2c, address) |
| 208 | + self._buffer = bytearray(6) |
| 209 | + super().__init__() |
| 210 | + |
| 211 | + def _read_register(self, register, length): |
| 212 | + self._buffer[0] = register & 0xFF |
| 213 | + with self._i2c as i2c: |
| 214 | + i2c.writeto(self._buffer, start=0, end=1) |
| 215 | + i2c.readfrom_into(self._buffer, start=0, end=length) |
| 216 | + return self._buffer |
| 217 | + |
| 218 | + def _write_register_byte(self, register, value): |
| 219 | + self._buffer[0] = register & 0xFF |
| 220 | + self._buffer[1] = value & 0xFF |
| 221 | + with self._i2c as i2c: |
| 222 | + i2c.writeto(self._buffer, start=0, end=2) |
| 223 | + |
| 224 | + |
| 225 | +class LIS3DH_SPI(LIS3DH): |
| 226 | + |
| 227 | + def __init__(self, spi, cs, baudrate=500000): |
| 228 | + self._spi = spi_device.SPIDevice(spi, cs, baudrate=baudrate) |
| 229 | + super().__init__() |
| 230 | + |
| 231 | + def _read_register(self, register, length): |
| 232 | + # TODO: Once https://github.com/adafruit/circuitpython/issues/108 is |
| 233 | + # resolved switch away from dynamic buffers to a single static one. |
| 234 | + request = bytearray(1) |
| 235 | + request[0] = (register | 0x80) & 0xFF # Read, bit 7 high. |
| 236 | + result = bytearray(length) |
| 237 | + with self._spi as spi: |
| 238 | + spi.write(request) |
| 239 | + spi.readinto(result) |
| 240 | + return result |
| 241 | + |
| 242 | + def _write_register_byte(self, register, value): |
| 243 | + # TODO: Once https://github.com/adafruit/circuitpython/issues/108 is |
| 244 | + # resolved switch away from dynamic buffers to a single static one. |
| 245 | + request = bytearray(2) |
| 246 | + request[0] = (register & ~0x80) & 0xFF # Write, bit 7 low. |
| 247 | + request[1] = value & 0xFF |
| 248 | + with self._spi as spi: |
| 249 | + spi.write(request) |
0 commit comments