/ adafruit_lsm9ds0.py
adafruit_lsm9ds0.py
  1  # The MIT License (MIT)
  2  #
  3  # Copyright (c) 2017 Tony DiCola 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  `adafruit_lsm9ds0`
 24  ====================================================
 25  
 26  CircuitPython module for the LSM9DS0 accelerometer, magnetometer, gyroscope.
 27  Based on the driver from:
 28  https://github.com/adafruit/Adafruit_LSM9DS0
 29  
 30  See examples/simpletest.py for a demo of the usage.
 31  
 32  * Author(s): Tony DiCola
 33  
 34  Implementation Notes
 35  --------------------
 36  
 37  **Hardware:**
 38  
 39  * Adafruit `9-DOF Accel/Mag/Gyro+Temp Breakout Board - LSM9DS0
 40    <https://www.adafruit.com/product/2021>`_ (Product ID: 2021)
 41  
 42  * FLORA `9-DOF Accelerometer/Gyroscope/Magnetometer - LSM9DS0
 43    <https://www.adafruit.com/product/2020>`_ (Product ID: 2020)
 44  
 45  **Software and Dependencies:**
 46  
 47  * Adafruit CircuitPython firmware for the ESP8622 and M0-based boards:
 48    https://github.com/adafruit/circuitpython/releases
 49  * Adafruit's Bus Device library: https://github.com/adafruit/Adafruit_CircuitPython_BusDevice
 50  """
 51  try:
 52      import struct
 53  except ImportError:
 54      import ustruct as struct
 55  
 56  import adafruit_bus_device.i2c_device as i2c_device
 57  import adafruit_bus_device.spi_device as spi_device
 58  from digitalio import Direction
 59  
 60  from micropython import const
 61  
 62  __version__ = "0.0.0-auto.0"
 63  __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_LSM9DS0.git"
 64  
 65  # Internal constants and register values:
 66  # pylint: disable=bad-whitespace
 67  _LSM9DS0_ADDRESS_ACCELMAG = const(0x1D)  # 3B >> 1 = 7bit default
 68  _LSM9DS0_ADDRESS_GYRO = const(0x6B)  # D6 >> 1 = 7bit default
 69  _LSM9DS0_XM_ID = const(0b01001001)
 70  _LSM9DS0_G_ID = const(0b11010100)
 71  _LSM9DS0_ACCEL_MG_LSB_2G = 0.061
 72  _LSM9DS0_ACCEL_MG_LSB_4G = 0.122
 73  _LSM9DS0_ACCEL_MG_LSB_6G = 0.183
 74  _LSM9DS0_ACCEL_MG_LSB_8G = 0.244
 75  _LSM9DS0_ACCEL_MG_LSB_16G = 0.732  # Is this right? Was expecting 0.488
 76  _LSM9DS0_MAG_MGAUSS_2GAUSS = 0.08
 77  _LSM9DS0_MAG_MGAUSS_4GAUSS = 0.16
 78  _LSM9DS0_MAG_MGAUSS_8GAUSS = 0.32
 79  _LSM9DS0_MAG_MGAUSS_12GAUSS = 0.48
 80  _LSM9DS0_GYRO_DPS_DIGIT_245DPS = 0.00875
 81  _LSM9DS0_GYRO_DPS_DIGIT_500DPS = 0.01750
 82  _LSM9DS0_GYRO_DPS_DIGIT_2000DPS = 0.07000
 83  _LSM9DS0_TEMP_LSB_DEGREE_CELSIUS = 8  # 1°C = 8, 25° = 200, etc.
 84  _LSM9DS0_REGISTER_WHO_AM_I_G = const(0x0F)
 85  _LSM9DS0_REGISTER_CTRL_REG1_G = const(0x20)
 86  _LSM9DS0_REGISTER_CTRL_REG3_G = const(0x22)
 87  _LSM9DS0_REGISTER_CTRL_REG4_G = const(0x23)
 88  _LSM9DS0_REGISTER_OUT_X_L_G = const(0x28)
 89  _LSM9DS0_REGISTER_OUT_X_H_G = const(0x29)
 90  _LSM9DS0_REGISTER_OUT_Y_L_G = const(0x2A)
 91  _LSM9DS0_REGISTER_OUT_Y_H_G = const(0x2B)
 92  _LSM9DS0_REGISTER_OUT_Z_L_G = const(0x2C)
 93  _LSM9DS0_REGISTER_OUT_Z_H_G = const(0x2D)
 94  _LSM9DS0_REGISTER_TEMP_OUT_L_XM = const(0x05)
 95  _LSM9DS0_REGISTER_TEMP_OUT_H_XM = const(0x06)
 96  _LSM9DS0_REGISTER_STATUS_REG_M = const(0x07)
 97  _LSM9DS0_REGISTER_OUT_X_L_M = const(0x08)
 98  _LSM9DS0_REGISTER_OUT_X_H_M = const(0x09)
 99  _LSM9DS0_REGISTER_OUT_Y_L_M = const(0x0A)
100  _LSM9DS0_REGISTER_OUT_Y_H_M = const(0x0B)
101  _LSM9DS0_REGISTER_OUT_Z_L_M = const(0x0C)
102  _LSM9DS0_REGISTER_OUT_Z_H_M = const(0x0D)
103  _LSM9DS0_REGISTER_WHO_AM_I_XM = const(0x0F)
104  _LSM9DS0_REGISTER_INT_CTRL_REG_M = const(0x12)
105  _LSM9DS0_REGISTER_INT_SRC_REG_M = const(0x13)
106  _LSM9DS0_REGISTER_CTRL_REG1_XM = const(0x20)
107  _LSM9DS0_REGISTER_CTRL_REG2_XM = const(0x21)
108  _LSM9DS0_REGISTER_CTRL_REG5_XM = const(0x24)
109  _LSM9DS0_REGISTER_CTRL_REG6_XM = const(0x25)
110  _LSM9DS0_REGISTER_CTRL_REG7_XM = const(0x26)
111  _LSM9DS0_REGISTER_OUT_X_L_A = const(0x28)
112  _LSM9DS0_REGISTER_OUT_X_H_A = const(0x29)
113  _LSM9DS0_REGISTER_OUT_Y_L_A = const(0x2A)
114  _LSM9DS0_REGISTER_OUT_Y_H_A = const(0x2B)
115  _LSM9DS0_REGISTER_OUT_Z_L_A = const(0x2C)
116  _LSM9DS0_REGISTER_OUT_Z_H_A = const(0x2D)
117  _GYROTYPE = True
118  _XMTYPE = False
119  _SENSORS_GRAVITY_STANDARD = 9.80665
120  
121  # User facing constants/module globals.
122  ACCELRANGE_2G = 0b000 << 3
123  ACCELRANGE_4G = 0b001 << 3
124  ACCELRANGE_6G = 0b010 << 3
125  ACCELRANGE_8G = 0b011 << 3
126  ACCELRANGE_16G = 0b100 << 3
127  MAGGAIN_2GAUSS = 0b00 << 5  # +/- 2 gauss
128  MAGGAIN_4GAUSS = 0b01 << 5  # +/- 4 gauss
129  MAGGAIN_8GAUSS = 0b10 << 5  # +/- 8 gauss
130  MAGGAIN_12GAUSS = 0b11 << 5  # +/- 12 gauss
131  GYROSCALE_245DPS = 0b00 << 4  # +/- 245 degrees per second rotation
132  GYROSCALE_500DPS = 0b01 << 4  # +/- 500 degrees per second rotation
133  GYROSCALE_2000DPS = 0b10 << 4  # +/- 2000 degrees per second rotation
134  # pylint: enable=bad-whitespace
135  
136  
137  def _twos_comp(val, bits):
138      # Convert an unsigned integer in 2's compliment form of the specified bit
139      # length to its signed integer value and return it.
140      if val & (1 << (bits - 1)) != 0:
141          return val - (1 << bits)
142      return val
143  
144  
145  class LSM9DS0:
146      """Driver for the LSM9DS0 accelerometer, magnetometer, gyroscope."""
147  
148      # Class-level buffer for reading and writing data with the sensor.
149      # This reduces memory allocations but means the code is not re-entrant or
150      # thread safe!
151      _BUFFER = bytearray(6)
152  
153      def __init__(self):
154          # Check ID registers.
155          if (
156              self._read_u8(_XMTYPE, _LSM9DS0_REGISTER_WHO_AM_I_XM) != _LSM9DS0_XM_ID
157              or self._read_u8(_GYROTYPE, _LSM9DS0_REGISTER_WHO_AM_I_G) != _LSM9DS0_G_ID
158          ):
159              raise RuntimeError("Could not find LSM9DS0, check wiring!")
160          # Enable the accelerometer continous
161          self._write_u8(_XMTYPE, _LSM9DS0_REGISTER_CTRL_REG1_XM, 0x67)
162          self._write_u8(_XMTYPE, _LSM9DS0_REGISTER_CTRL_REG5_XM, 0b11110000)
163          # enable mag continuous
164          self._write_u8(_XMTYPE, _LSM9DS0_REGISTER_CTRL_REG7_XM, 0b00000000)
165          # enable gyro continuous
166          self._write_u8(_GYROTYPE, _LSM9DS0_REGISTER_CTRL_REG1_G, 0x0F)
167          # enable the temperature sensor (output rate same as the mag sensor)
168          temp_reg = self._read_u8(_XMTYPE, _LSM9DS0_REGISTER_CTRL_REG5_XM)
169          self._write_u8(_XMTYPE, _LSM9DS0_REGISTER_CTRL_REG5_XM, temp_reg | (1 << 7))
170          # Set default ranges for the various sensors
171          self._accel_mg_lsb = None
172          self._mag_mgauss_lsb = None
173          self._gyro_dps_digit = None
174          self.accel_range = ACCELRANGE_2G
175          self.mag_gain = MAGGAIN_2GAUSS
176          self.gyro_scale = GYROSCALE_245DPS
177  
178      @property
179      def accel_range(self):
180          """The accelerometer range.  Must be a value of:
181            - ACCELRANGE_2G
182            - ACCELRANGE_4G
183            - ACCELRANGE_6G
184            - ACCELRANGE_8G
185            - ACCELRANGE_16G
186          """
187          reg = self._read_u8(_XMTYPE, _LSM9DS0_REGISTER_CTRL_REG2_XM)
188          return (reg & 0b00111000) & 0xFF
189  
190      @accel_range.setter
191      def accel_range(self, val):
192          assert val in (
193              ACCELRANGE_2G,
194              ACCELRANGE_4G,
195              ACCELRANGE_6G,
196              ACCELRANGE_8G,
197              ACCELRANGE_16G,
198          )
199          reg = self._read_u8(_XMTYPE, _LSM9DS0_REGISTER_CTRL_REG2_XM)
200          reg = (reg & ~(0b00111000)) & 0xFF
201          reg |= val
202          self._write_u8(_XMTYPE, _LSM9DS0_REGISTER_CTRL_REG2_XM, reg)
203          if val == ACCELRANGE_2G:
204              self._accel_mg_lsb = _LSM9DS0_ACCEL_MG_LSB_2G
205          elif val == ACCELRANGE_4G:
206              self._accel_mg_lsb = _LSM9DS0_ACCEL_MG_LSB_4G
207          elif val == ACCELRANGE_6G:
208              self._accel_mg_lsb = _LSM9DS0_ACCEL_MG_LSB_6G
209          elif val == ACCELRANGE_8G:
210              self._accel_mg_lsb = _LSM9DS0_ACCEL_MG_LSB_8G
211          elif val == ACCELRANGE_16G:
212              self._accel_mg_lsb = _LSM9DS0_ACCEL_MG_LSB_16G
213  
214      @property
215      def mag_gain(self):
216          """The magnetometer gain.  Must be a value of:
217            - MAGGAIN_2GAUSS
218            - MAGGAIN_4GAUSS
219            - MAGGAIN_8GAUSS
220            - MAGGAIN_12GAUSS
221          """
222          reg = self._read_u8(_XMTYPE, _LSM9DS0_REGISTER_CTRL_REG6_XM)
223          return (reg & 0b01100000) & 0xFF
224  
225      @mag_gain.setter
226      def mag_gain(self, val):
227          assert val in (MAGGAIN_2GAUSS, MAGGAIN_4GAUSS, MAGGAIN_8GAUSS, MAGGAIN_12GAUSS)
228          reg = self._read_u8(_XMTYPE, _LSM9DS0_REGISTER_CTRL_REG6_XM)
229          reg = (reg & ~(0b01100000)) & 0xFF
230          reg |= val
231          self._write_u8(_XMTYPE, _LSM9DS0_REGISTER_CTRL_REG6_XM, reg)
232          if val == MAGGAIN_2GAUSS:
233              self._mag_mgauss_lsb = _LSM9DS0_MAG_MGAUSS_2GAUSS
234          elif val == MAGGAIN_4GAUSS:
235              self._mag_mgauss_lsb = _LSM9DS0_MAG_MGAUSS_4GAUSS
236          elif val == MAGGAIN_8GAUSS:
237              self._mag_mgauss_lsb = _LSM9DS0_MAG_MGAUSS_8GAUSS
238          elif val == MAGGAIN_12GAUSS:
239              self._mag_mgauss_lsb = _LSM9DS0_MAG_MGAUSS_12GAUSS
240  
241      @property
242      def gyro_scale(self):
243          """The gyroscope scale.  Must be a value of:
244            - GYROSCALE_245DPS
245            - GYROSCALE_500DPS
246            - GYROSCALE_2000DPS
247          """
248          reg = self._read_u8(_GYROTYPE, _LSM9DS0_REGISTER_CTRL_REG4_G)
249          return (reg & 0b00110000) & 0xFF
250  
251      @gyro_scale.setter
252      def gyro_scale(self, val):
253          assert val in (GYROSCALE_245DPS, GYROSCALE_500DPS, GYROSCALE_2000DPS)
254          reg = self._read_u8(_GYROTYPE, _LSM9DS0_REGISTER_CTRL_REG4_G)
255          reg = (reg & ~(0b00110000)) & 0xFF
256          reg |= val
257          self._write_u8(_GYROTYPE, _LSM9DS0_REGISTER_CTRL_REG4_G, reg)
258          if val == GYROSCALE_245DPS:
259              self._gyro_dps_digit = _LSM9DS0_GYRO_DPS_DIGIT_245DPS
260          elif val == GYROSCALE_500DPS:
261              self._gyro_dps_digit = _LSM9DS0_GYRO_DPS_DIGIT_500DPS
262          elif val == GYROSCALE_2000DPS:
263              self._gyro_dps_digit = _LSM9DS0_GYRO_DPS_DIGIT_2000DPS
264  
265      def read_accel_raw(self):
266          """Read the raw accelerometer sensor values and return it as a
267          3-tuple of X, Y, Z axis values that are 16-bit unsigned values.  If you
268          want the acceleration in nice units you probably want to use the
269          accelerometer property!
270          """
271          # Read the accelerometer
272          self._read_bytes(_XMTYPE, 0x80 | _LSM9DS0_REGISTER_OUT_X_L_A, 6, self._BUFFER)
273          raw_x, raw_y, raw_z = struct.unpack_from("<hhh", self._BUFFER[0:6])
274          return (raw_x, raw_y, raw_z)
275  
276      @property
277      def acceleration(self):
278          """The accelerometer X, Y, Z axis values as a 3-tuple of
279          m/s^2 values.
280          """
281          raw = self.read_accel_raw()
282          return (
283              x * self._accel_mg_lsb / 1000.0 * _SENSORS_GRAVITY_STANDARD for x in raw
284          )
285  
286      def read_mag_raw(self):
287          """Read the raw magnetometer sensor values and return it as a
288          3-tuple of X, Y, Z axis values that are 16-bit unsigned values.  If you
289          want the magnetometer in nice units you probably want to use the
290          magnetometer property!
291          """
292          # Read the magnetometer
293          self._read_bytes(_XMTYPE, 0x80 | _LSM9DS0_REGISTER_OUT_X_L_M, 6, self._BUFFER)
294          raw_x, raw_y, raw_z = struct.unpack_from("<hhh", self._BUFFER[0:6])
295          return (raw_x, raw_y, raw_z)
296  
297      @property
298      def magnetic(self):
299          """The magnetometer X, Y, Z axis values as a 3-tuple of
300          gauss values.
301          """
302          raw = self.read_mag_raw()
303          return (x * self._mag_mgauss_lsb / 1000.0 for x in raw)
304  
305      def read_gyro_raw(self):
306          """Read the raw gyroscope sensor values and return it as a
307          3-tuple of X, Y, Z axis values that are 16-bit unsigned values.  If you
308          want the gyroscope in nice units you probably want to use the
309          gyroscope property!
310          """
311          # Read the gyroscope
312          self._read_bytes(_GYROTYPE, 0x80 | _LSM9DS0_REGISTER_OUT_X_L_G, 6, self._BUFFER)
313          raw_x, raw_y, raw_z = struct.unpack_from("<hhh", self._BUFFER[0:6])
314          return (raw_x, raw_y, raw_z)
315  
316      @property
317      def gyro(self):
318          """The gyroscope X, Y, Z axis values as a 3-tuple of
319          degrees/second values.
320          """
321          raw = self.read_gyro_raw()
322          return (x * self._gyro_dps_digit for x in raw)
323  
324      def read_temp_raw(self):
325          """Read the raw temperature sensor value and return it as a 16-bit
326          unsigned value.  If you want the temperature in nice units you probably
327          want to use the temperature property!
328          """
329          # Read temp sensor
330          self._read_bytes(
331              _XMTYPE, 0x80 | _LSM9DS0_REGISTER_TEMP_OUT_L_XM, 2, self._BUFFER
332          )
333          temp = ((self._BUFFER[1] << 8) | self._BUFFER[0]) >> 4
334          return _twos_comp(temp, 12)
335  
336      @property
337      def temperature(self):
338          """The temperature of the sensor in degrees Celsius."""
339          # This is just a guess since the starting point (21C here) isn't documented :(
340          temp = self.read_temp_raw()
341          temp = 21.0 + temp / 8
342          return temp
343  
344      def _read_u8(self, sensor_type, address):
345          # Read an 8-bit unsigned value from the specified 8-bit address.
346          # The sensor_type boolean should be _MAGTYPE when talking to the
347          # magnetometer, or _XGTYPE when talking to the accel or gyro.
348          # MUST be implemented by subclasses!
349          raise NotImplementedError()
350  
351      def _read_bytes(self, sensor_type, address, count, buf):
352          # Read a count number of bytes into buffer from the provided 8-bit
353          # register address.  The sensor_type boolean should be _MAGTYPE when
354          # talking to the magnetometer, or _XGTYPE when talking to the accel or
355          # gyro.  MUST be implemented by subclasses!
356          raise NotImplementedError()
357  
358      def _write_u8(self, sensor_type, address, val):
359          # Write an 8-bit unsigned value to the specified 8-bit address.
360          # The sensor_type boolean should be _MAGTYPE when talking to the
361          # magnetometer, or _XGTYPE when talking to the accel or gyro.
362          # MUST be implemented by subclasses!
363          raise NotImplementedError()
364  
365  
366  class LSM9DS0_I2C(LSM9DS0):
367      """Driver for the LSM9DS0 connected over I2C."""
368  
369      def __init__(self, i2c):
370          self._gyro_device = i2c_device.I2CDevice(i2c, _LSM9DS0_ADDRESS_GYRO)
371          self._xm_device = i2c_device.I2CDevice(i2c, _LSM9DS0_ADDRESS_ACCELMAG)
372          super().__init__()
373  
374      def _read_u8(self, sensor_type, address):
375          self._read_bytes(sensor_type, address, 1, self._BUFFER)
376          return self._BUFFER[0]
377  
378      def _read_bytes(self, sensor_type, address, count, buf):
379          if sensor_type == _GYROTYPE:
380              device = self._gyro_device
381          else:
382              device = self._xm_device
383          with device as i2c:
384              buf[0] = address & 0xFF
385              i2c.write(buf, end=1)
386              i2c.readinto(buf, end=count)
387          # print("read from %02x: %s" % (address, [hex(i) for i in buf[:count]]))
388  
389      def _write_u8(self, sensor_type, address, val):
390          if sensor_type == _GYROTYPE:
391              device = self._gyro_device
392          else:
393              device = self._xm_device
394          with device as i2c:
395              self._BUFFER[0] = address & 0xFF
396              self._BUFFER[1] = val & 0xFF
397              i2c.write(self._BUFFER, end=2)
398          # print("write to %02x: %02x" % (address, val))
399  
400  
401  class LSM9DS0_SPI(LSM9DS0):
402      """Driver for the LSM9DS0 connected over SPI."""
403  
404      # pylint: disable=no-member
405      def __init__(self, spi, xmcs, gcs):
406          gcs.direction = Direction.OUTPUT
407          gcs.value = True
408          xmcs.direction = Direction.OUTPUT
409          xmcs.value = True
410          self._gyro_device = spi_device.SPIDevice(spi, gcs)
411          self._xm_device = spi_device.SPIDevice(spi, xmcs)
412          super().__init__()
413  
414      def _read_u8(self, sensor_type, address):
415          self._read_bytes(sensor_type, address, 1, self._BUFFER)
416          return self._BUFFER[0]
417  
418      def _read_bytes(self, sensor_type, address, count, buf):
419          if sensor_type == _GYROTYPE:
420              device = self._gyro_device
421          else:
422              device = self._xm_device
423          with device as spi:
424              buf[0] = (address | 0x80 | 0x40) & 0xFF
425              spi.write(buf, end=1)
426              spi.readinto(buf, end=count)
427          # print("read from %02x: %s" % (address, [hex(i) for i in buf[:count]]))
428  
429      def _write_u8(self, sensor_type, address, val):
430          if sensor_type == _GYROTYPE:
431              device = self._gyro_device
432          else:
433              device = self._xm_device
434          with device as spi:
435              self._BUFFER[0] = (address & 0x7F) & 0xFF
436              self._BUFFER[1] = val & 0xFF
437              spi.write(self._BUFFER, end=2)
438          # print("write to %02x: %02x" % (address, val))