/ 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))