/ 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