/ adafruit_msa301.py
adafruit_msa301.py
  1  # The MIT License (MIT)
  2  #
  3  # Copyright (c) 2019 Bryan Siepert for Adafruit Industries
  4  #
  5  # Permission is hereby granted, free of charge, to any person obtaining a copy
  6  # of this software and associated documentation files (the "Software"), to deal
  7  # in the Software without restriction, including without limitation the rights
  8  # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  9  # copies of the Software, and to permit persons to whom the Software is
 10  # furnished to do so, subject to the following conditions:
 11  #
 12  # The above copyright notice and this permission notice shall be included in
 13  # all copies or substantial portions of the Software.
 14  #
 15  # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 16  # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 17  # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 18  # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 19  # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 20  # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 21  # THE SOFTWARE.
 22  """
 23  `MSA301`
 24  ================================================================================
 25  
 26  CircuitPython library for the MSA301 Accelerometer
 27  
 28  
 29  * Author(s): Bryan Siepert
 30  
 31  Implementation Notes
 32  --------------------
 33  
 34  **Hardware:**
 35  
 36  * Adafruit MSA301 Breakout https://www.adafruit.com/product/4344
 37  
 38  **Software and Dependencies:**
 39  
 40  * Adafruit CircuitPython firmware for the supported boards:
 41    https://github.com/adafruit/circuitpython/releases
 42  
 43  * Adafruit's Bus Device library: https://github.com/adafruit/Adafruit_CircuitPython_BusDevice
 44  * Adafruit's Register library: https://github.com/adafruit/Adafruit_CircuitPython_Register
 45  """
 46  
 47  __version__ = "0.0.0-auto.0"
 48  __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_MSA301.git"
 49  
 50  import struct
 51  
 52  from micropython import const
 53  from adafruit_register.i2c_struct import ROUnaryStruct
 54  from adafruit_register.i2c_bit import RWBit
 55  from adafruit_register.i2c_bits import RWBits, ROBits
 56  import adafruit_bus_device.i2c_device as i2cdevice
 57  
 58  _MSA301_I2CADDR_DEFAULT = const(0x26)
 59  
 60  _MSA301_REG_PARTID = const(0x01)
 61  _MSA301_REG_OUT_X_L = const(0x02)
 62  _MSA301_REG_OUT_X_H = const(0x03)
 63  _MSA301_REG_OUT_Y_L = const(0x04)
 64  _MSA301_REG_OUT_Y_H = const(0x05)
 65  _MSA301_REG_OUT_Z_L = const(0x06)
 66  _MSA301_REG_OUT_Z_H = const(0x07)
 67  _MSA301_REG_MOTIONINT = const(0x09)
 68  _MSA301_REG_DATAINT = const(0x0A)
 69  _MSA301_REG_RESRANGE = const(0x0F)
 70  _MSA301_REG_ODR = const(0x10)
 71  _MSA301_REG_POWERMODE = const(0x11)
 72  _MSA301_REG_INTSET0 = const(0x16)
 73  _MSA301_REG_INTSET1 = const(0x17)
 74  _MSA301_REG_INTMAP0 = const(0x19)
 75  _MSA301_REG_INTMAP1 = const(0x1A)
 76  _MSA301_REG_TAPDUR = const(0x2A)
 77  _MSA301_REG_TAPTH = const(0x2B)
 78  
 79  
 80  _STANDARD_GRAVITY = 9.806
 81  
 82  
 83  class Mode:  # pylint: disable=too-few-public-methods
 84      """An enum-like class representing the different modes that the MSA301 can
 85      use. The values can be referenced like ``Mode.NORMAL`` or ``Mode.SUSPEND``
 86      Possible values are
 87  
 88      - ``Mode.NORMAL``
 89      - ``Mode.LOW_POWER``
 90      - ``Mode.SUSPEND``
 91      """
 92  
 93      # pylint: disable=invalid-name
 94      NORMAL = 0b00
 95      LOWPOWER = 0b01
 96      SUSPEND = 0b010
 97  
 98  
 99  class DataRate:  # pylint: disable=too-few-public-methods
100      """An enum-like class representing the different data rates that the MSA301 can
101      use. The values can be referenced like ``DataRate.RATE_1_HZ`` or ``DataRate.RATE_1000_HZ``
102      Possible values are
103  
104      - ``DataRate.RATE_1_HZ``
105      - ``DataRate.RATE_1_95_HZ``
106      - ``DataRate.RATE_3_9_HZ``
107      - ``DataRate.RATE_7_81_HZ``
108      - ``DataRate.RATE_15_63_HZ``
109      - ``DataRate.RATE_31_25_HZ``
110      - ``DataRate.RATE_62_5_HZ``
111      - ``DataRate.RATE_125_HZ``
112      - ``DataRate.RATE_250_HZ``
113      - ``DataRate.RATE_500_HZ``
114      - ``DataRate.RATE_1000_HZ``
115      """
116  
117      RATE_1_HZ = 0b0000  # 1 Hz
118      RATE_1_95_HZ = 0b0001  # 1.95 Hz
119      RATE_3_9_HZ = 0b0010  # 3.9 Hz
120      RATE_7_81_HZ = 0b0011  # 7.81 Hz
121      RATE_15_63_HZ = 0b0100  # 15.63 Hz
122      RATE_31_25_HZ = 0b0101  # 31.25 Hz
123      RATE_62_5_HZ = 0b0110  # 62.5 Hz
124      RATE_125_HZ = 0b0111  # 125 Hz
125      RATE_250_HZ = 0b1000  # 250 Hz
126      RATE_500_HZ = 0b1001  # 500 Hz
127      RATE_1000_HZ = 0b1010  # 1000 Hz
128  
129  
130  class BandWidth:  # pylint: disable=too-few-public-methods
131      """An enum-like class representing the different bandwidths that the MSA301 can
132      use. The values can be referenced like ``BandWidth.WIDTH_1_HZ`` or ``BandWidth.RATE_500_HZ``
133      Possible values are
134  
135      - ``BandWidth.RATE_1_HZ``
136      - ``BandWidth.RATE_1_95_HZ``
137      - ``BandWidth.RATE_3_9_HZ``
138      - ``BandWidth.RATE_7_81_HZ``
139      - ``BandWidth.RATE_15_63_HZ``
140      - ``BandWidth.RATE_31_25_HZ``
141      - ``BandWidth.RATE_62_5_HZ``
142      - ``BandWidth.RATE_125_HZ``
143      - ``BandWidth.RATE_250_HZ``
144      - ``BandWidth.RATE_500_HZ``
145      - ``BandWidth.RATE_1000_HZ``
146      """
147  
148      WIDTH_1_95_HZ = 0b0000  # 1.95 Hz
149      WIDTH_3_9_HZ = 0b0011  # 3.9 Hz
150      WIDTH_7_81_HZ = 0b0100  # 7.81 Hz
151      WIDTH_15_63_HZ = 0b0101  # 15.63 Hz
152      WIDTH_31_25_HZ = 0b0110  # 31.25 Hz
153      WIDTH_62_5_HZ = 0b0111  # 62.5 Hz
154      WIDTH_125_HZ = 0b1000  # 125 Hz
155      WIDTH_250_HZ = 0b1001  # 250 Hz
156      WIDTH_500_HZ = 0b1010  # 500 Hz
157  
158  
159  class Range:  # pylint: disable=too-few-public-methods
160      """An enum-like class representing the different acceleration measurement ranges that the
161      MSA301 can use. The values can be referenced like ``Range.RANGE_2_G`` or ``Range.RANGE_16_G``
162      Possible values are
163  
164      - ``Range.RANGE_2_G``
165      - ``Range.RANGE_4_G``
166      - ``Range.RANGE_8_G``
167      - ``Range.RANGE_16_G``
168      """
169  
170      RANGE_2_G = 0b00  # +/- 2g (default value)
171      RANGE_4_G = 0b01  # +/- 4g
172      RANGE_8_G = 0b10  # +/- 8g
173      RANGE_16_G = 0b11  # +/- 16g
174  
175  
176  class Resolution:  # pylint: disable=too-few-public-methods
177      """An enum-like class representing the different measurement ranges that the MSA301 can
178      use. The values can be referenced like ``Range.RANGE_2_G`` or ``Range.RANGE_16_G``
179      Possible values are
180  
181      - ``Resolution.RESOLUTION_14_BIT``
182      - ``Resolution.RESOLUTION_12_BIT``
183      - ``Resolution.RESOLUTION_10_BIT``
184      - ``Resolution.RESOLUTION_8_BIT``
185      """
186  
187      RESOLUTION_14_BIT = 0b00
188      RESOLUTION_12_BIT = 0b01
189      RESOLUTION_10_BIT = 0b10
190      RESOLUTION_8_BIT = 0b11
191  
192  
193  class TapDuration:  # pylint: disable=too-few-public-methods,too-many-instance-attributes
194      """An enum-like class representing the options for the "double_tap_window" parameter of
195      `enable_tap_detection`"""
196  
197      DURATION_50_MS = 0b000  # < 50 millis
198      DURATION_100_MS = 0b001  # < 100 millis
199      DURATION_150_MS = 0b010  # < 150 millis
200      DURATION_200_MS = 0b011  # < 200 millis
201      DURATION_250_MS = 0b100  # < 250 millis
202      DURATION_375_MS = 0b101  # < 375 millis
203      DURATION_500_MS = 0b110  # < 500 millis
204      DURATION_700_MS = 0b111  # < 50 millis700 millis
205  
206  
207  class MSA301:  # pylint: disable=too-many-instance-attributes
208      """Driver for the MSA301 Accelerometer.
209  
210          :param ~busio.I2C i2c_bus: The I2C bus the MSA is connected to.
211      """
212  
213      _part_id = ROUnaryStruct(_MSA301_REG_PARTID, "<B")
214  
215      def __init__(self, i2c_bus):
216          self.i2c_device = i2cdevice.I2CDevice(i2c_bus, _MSA301_I2CADDR_DEFAULT)
217  
218          if self._part_id != 0x13:
219              raise AttributeError("Cannot find a MSA301")
220  
221          self._disable_x = self._disable_y = self._disable_z = False
222          self.power_mode = Mode.NORMAL
223          self.data_rate = DataRate.RATE_500_HZ
224          self.bandwidth = BandWidth.WIDTH_250_HZ
225          self.range = Range.RANGE_4_G
226          self.resolution = Resolution.RESOLUTION_14_BIT
227          self._tap_count = 0
228  
229      _disable_x = RWBit(_MSA301_REG_ODR, 7)
230      _disable_y = RWBit(_MSA301_REG_ODR, 6)
231      _disable_z = RWBit(_MSA301_REG_ODR, 5)
232  
233      _xyz_raw = ROBits(48, _MSA301_REG_OUT_X_L, 0, 6)
234  
235      # tap INT enable and status
236      _single_tap_int_en = RWBit(_MSA301_REG_INTSET0, 5)
237      _double_tap_int_en = RWBit(_MSA301_REG_INTSET0, 4)
238      _motion_int_status = ROUnaryStruct(_MSA301_REG_MOTIONINT, "B")
239  
240      # tap interrupt knobs
241      _tap_quiet = RWBit(_MSA301_REG_TAPDUR, 7)
242      _tap_shock = RWBit(_MSA301_REG_TAPDUR, 6)
243      _tap_duration = RWBits(3, _MSA301_REG_TAPDUR, 0)
244      _tap_threshold = RWBits(5, _MSA301_REG_TAPTH, 0)
245      reg_tapdur = ROUnaryStruct(_MSA301_REG_TAPDUR, "B")
246  
247      # general settings knobs
248      power_mode = RWBits(2, _MSA301_REG_POWERMODE, 6)
249      bandwidth = RWBits(4, _MSA301_REG_POWERMODE, 1)
250      data_rate = RWBits(4, _MSA301_REG_ODR, 0)
251      range = RWBits(2, _MSA301_REG_RESRANGE, 0)
252      resolution = RWBits(2, _MSA301_REG_RESRANGE, 2)
253  
254      @property
255      def acceleration(self):
256          """The x, y, z acceleration values returned in a 3-tuple and are in m / s ^ 2."""
257          # read the 6 bytes of acceleration data
258          # zh, zl, yh, yl, xh, xl
259          raw_data = self._xyz_raw
260          acc_bytes = bytearray()
261          # shift out bytes, reversing the order
262          for shift in range(6):
263              bottom_byte = raw_data >> (8 * shift) & 0xFF
264              acc_bytes.append(bottom_byte)
265  
266          # unpack three LE, signed shorts
267          x, y, z = struct.unpack_from("<hhh", acc_bytes)
268  
269          current_range = self.range
270          scale = 1.0
271          if current_range == 3:
272              scale = 512.0
273          if current_range == 2:
274              scale = 1024.0
275          if current_range == 1:
276              scale = 2048.0
277          if current_range == 0:
278              scale = 4096.0
279  
280          # shift down to the actual 14 bits and scale based on the range
281          x_acc = ((x >> 2) / scale) * _STANDARD_GRAVITY
282          y_acc = ((y >> 2) / scale) * _STANDARD_GRAVITY
283          z_acc = ((z >> 2) / scale) * _STANDARD_GRAVITY
284  
285          return (x_acc, y_acc, z_acc)
286  
287      def enable_tap_detection(
288          self,
289          *,
290          tap_count=1,
291          threshold=25,
292          long_initial_window=True,
293          long_quiet_window=True,
294          double_tap_window=TapDuration.DURATION_250_MS
295      ):
296          """
297          Enables tap detection with configurable parameters.
298  
299          :param int tap_count: 1 to detect only single taps, or 2 to detect only double taps.\
300          default is 1
301  
302          :param int threshold: A threshold for the tap detection.\
303          The higher the value the less sensitive the detection. This changes based on the\
304          accelerometer range. Default is 25.
305  
306          :param int long_initial_window: This sets the length of the window of time where a\
307          spike in acceleration must occour in before being followed by a quiet period.\
308          `True` (default) sets the value to 70ms, False to 50ms. Default is `True`
309  
310          :param int long_quiet_window: The length of the "quiet" period after an acceleration\
311          spike where no more spikes can occour for a tap to be registered.\
312          `True` (default) sets the value to 30ms, False to 20ms. Default is `True`.
313  
314          :param int double_tap_window: The length of time after an initial tap is registered\
315          in which a second tap must be detected to count as a double tap. Setting a lower\
316          value will require a faster double tap. The value must be a\
317          ``TapDuration``. Default is ``TapDuration.DURATION_250_MS``.
318  
319          If you wish to set them yourself rather than using the defaults,
320          you must use keyword arguments::
321  
322              msa.enable_tap_detection(tap_count=2,
323                                       threshold=25,
324                                       double_tap_window=TapDuration.DURATION_700_MS)
325  
326          """
327          self._tap_shock = not long_initial_window
328          self._tap_quiet = long_quiet_window
329          self._tap_threshold = threshold
330          self._tap_count = tap_count
331  
332          if double_tap_window > 7 or double_tap_window < 0:
333              raise ValueError("double_tap_window must be a TapDuration")
334          if tap_count == 1:
335              self._single_tap_int_en = True
336          elif tap_count == 2:
337              self._tap_duration = double_tap_window
338              self._double_tap_int_en = True
339          else:
340              raise ValueError("tap must be 1 for single tap, or 2 for double tap")
341  
342      @property
343      def tapped(self):
344          """`True` if a single or double tap was detected, depending on the value of the\
345             ``tap_count`` argument passed to ``enable_tap_detection``"""
346          if self._tap_count == 0:
347              return False
348  
349          motion_int_status = self._motion_int_status
350  
351          if motion_int_status == 0:  # no interrupts triggered
352              return False
353  
354          if self._tap_count == 1 and motion_int_status & 1 << 5:
355              return True
356          if self._tap_count == 2 and motion_int_status & 1 << 4:
357              return True
358  
359          return False