/ 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