class L3GD20_I2C(L3GD20): """ Driver for L3GD20 Gyroscope using I2C communications :param ~busio.I2C i2c: initialized busio I2C class :param int rng: the optional range value: L3DS20_RANGE_250DPS(default), L3DS20_RANGE_500DPS, or L3DS20_RANGE_2000DPS :param address: the optional device address, 0x68 is the default address """ gyro_raw = Struct(_L3GD20_REGISTER_OUT_X_L_X80, "<hhh") """Gives the raw gyro readings, in units of rad/s.""" def __init__(self, i2c, rng=L3DS20_RANGE_250DPS, address=0x6B, rate=L3DS20_RATE_100HZ): import adafruit_bus_device.i2c_device as i2c_device # pylint: disable=import-outside-toplevel self.i2c_device = i2c_device.I2CDevice(i2c, address) self.buffer = bytearray(2) super().__init__(rng, rate) def write_register(self, register, value): """ Update a register with a byte value :param int register: which device register to write :param value: a byte to write """ self.buffer[0] = register self.buffer[1] = value with self.i2c_device as i2c: i2c.write(self.buffer) def read_register(self, register): """ Returns a byte value from a register :param register: the register to read a byte """ self.buffer[0] = register with self.i2c_device as i2c: i2c.write_then_readinto(self.buffer, self.buffer, out_end=1, in_start=1) return self.buffer[1]
class L3GD20_I2C(L3GD20): """ Driver for L3GD20 Gyroscope using I2C communications :param ~busio.I2C i2c: initialized busio I2C class :param int rng: the optional range value: L3DS20_RANGE_250DPS(default), L3DS20_RANGE_500DPS, or L3DS20_RANGE_2000DPS :param address: the optional device address, 0x68 is the default address """ acceleration_raw = Struct(_L3GD20_REGISTER_OUT_X_L_X80, '<hhh') """Gives the raw acceleration readings, in units of the scaled mdps.""" def __init__(self, i2c, rng=L3DS20_RANGE_250DPS, address=0x6B): import adafruit_bus_device.i2c_device as i2c_device self.i2c_device = i2c_device.I2CDevice(i2c, address) self.buffer = bytearray(2) super().__init__(rng) def write_register(self, register, value): """ Update a register with a byte value :param int register: which device register to write :param value: a byte to write """ self.buffer[0] = register self.buffer[1] = value with self.i2c_device as i2c: i2c.write(self.buffer) def read_register(self, register): """ Returns a byte value from a register :param register: the register to read a byte """ self.buffer[0] = register with self.i2c_device as i2c: i2c.write(self.buffer, end=1, stop=False) i2c.readinto(self.buffer, start=1) return self.buffer[1]
class AS7341: # pylint:disable=too-many-instance-attributes """Library for the AS7341 Sensor :param ~busio.I2C i2c_bus: The I2C bus the AS7341 is connected to. :param address: The I2C address of the sensor """ _device_id = ROBits(6, _AS7341_WHOAMI, 2) _smux_enable_bit = RWBit(_AS7341_ENABLE, 4) _led_control_enable_bit = RWBit(_AS7341_CONFIG, 3) _color_meas_enabled = RWBit(_AS7341_ENABLE, 1) _power_enabled = RWBit(_AS7341_ENABLE, 0) _low_bank_active = RWBit(_AS7341_CFG0, 4) _smux_command = RWBits(2, _AS7341_CFG6, 3) _fd_status = UnaryStruct(_AS7341_FD_STATUS, "<B") _channel_0_data = UnaryStruct(_AS7341_CH0_DATA_L, "<H") _channel_1_data = UnaryStruct(_AS7341_CH1_DATA_L, "<H") _channel_2_data = UnaryStruct(_AS7341_CH2_DATA_L, "<H") _channel_3_data = UnaryStruct(_AS7341_CH3_DATA_L, "<H") _channel_4_data = UnaryStruct(_AS7341_CH4_DATA_L, "<H") _channel_5_data = UnaryStruct(_AS7341_CH5_DATA_L, "<H") # "Reading the ASTATUS register (0x60 or 0x94) latches # all 12 spectral data bytes to that status read." Datasheet Sec. 10.2.7 _all_channels = Struct(_AS7341_ASTATUS, "<BHHHHHH") _led_current_bits = RWBits(7, _AS7341_LED, 0) _led_enabled = RWBit(_AS7341_LED, 7) atime = UnaryStruct(_AS7341_ATIME, "<B") """The integration time step count. Total integration time will be ``(ATIME + 1) * (ASTEP + 1) * 2.78µS`` """ astep = UnaryStruct(_AS7341_ASTEP_L, "<H") """ The integration time step size in 2.78 microsecond increments""" _gain = UnaryStruct(_AS7341_CFG1, "<B") _data_ready_bit = RWBit(_AS7341_STATUS2, 6) """ * @brief * * @return true: success false: failure """ def __init__(self, i2c_bus, address=_AS7341_I2CADDR_DEFAULT): self.i2c_device = i2c_device.I2CDevice(i2c_bus, address) if not self._device_id in [_AS7341_DEVICE_ID]: raise RuntimeError( "Failed to find an AS7341 sensor - check your wiring!") self.reset() self.initialize() self._buffer = bytearray(2) self._low_channels_configured = False self._high_channels_configured = False self._flicker_detection_1k_configured = False def initialize(self): """Configure the sensors with the default settings. For use after calling `reset()`""" self._power_enabled = True self._led_control_enabled = True self.atime = 100 self.astep = 999 self.gain = Gain.GAIN_128X # pylint:disable=no-member def reset(self): """Resets the internal registers and restores the default settings""" @property def all_channels(self): """The current readings for all six ADC channels""" self._configure_f1_f4() adc_reads_f1_f4 = self._all_channels reads = adc_reads_f1_f4[1:-2] self._configure_f5_f8() adc_reads_f5_f8 = self._all_channels reads += adc_reads_f5_f8[1:-2] return reads @property def channel_415nm(self): """The current reading for the 415nm band""" self._configure_f1_f4() return self._channel_0_data @property def channel_445nm(self): """The current reading for the 445nm band""" self._configure_f1_f4() return self._channel_1_data @property def channel_480nm(self): """The current reading for the 480nm band""" self._configure_f1_f4() return self._channel_2_data @property def channel_515nm(self): """The current reading for the 515nm band""" self._configure_f1_f4() return self._channel_3_data @property def channel_555nm(self): """The current reading for the 555nm band""" self._configure_f5_f8() return self._channel_0_data @property def channel_590nm(self): """The current reading for the 590nm band""" self._configure_f5_f8() return self._channel_1_data @property def channel_630nm(self): """The current reading for the 630nm band""" self._configure_f5_f8() return self._channel_2_data @property def channel_680nm(self): """The current reading for the 680nm band""" self._configure_f5_f8() return self._channel_3_data # TODO: Add clear and NIR accessors def _wait_for_data(self, timeout=1.0): """Wait for sensor data to be ready""" start = monotonic() while not self._data_ready_bit: if monotonic() - start > timeout: raise RuntimeError("Timeout occoured waiting for sensor data") sleep(0.001) def _write_register(self, addr, data): self._buffer[0] = addr self._buffer[1] = data with self.i2c_device as i2c: i2c.write(self._buffer) def _configure_f1_f4(self): """Configure the sensor to read from elements F1-F4, Clear, and NIR""" # disable SP_EN bit while making config changes if self._low_channels_configured: return self._high_channels_configured = False self._flicker_detection_1k_configured = False self._color_meas_enabled = False # ENUM-ify self._smux_command = 2 # Write new configuration to all the 20 registers self._f1f4_clear_nir() # Start SMUX command self._smux_enabled = True # Enable SP_EN bit self._color_meas_enabled = True self._low_channels_configured = True self._wait_for_data() def _configure_f5_f8(self): """Configure the sensor to read from elements F5-F8, Clear, and NIR""" # disable SP_EN bit while making config changes if self._high_channels_configured: return self._low_channels_configured = False self._flicker_detection_1k_configured = False self._color_meas_enabled = False # ENUM-ify self._smux_command = 2 # Write new configuration to all the 20 registers self._f5f8_clear_nir() # Start SMUX command self._smux_enabled = True # Enable SP_EN bit self._color_meas_enabled = True self._high_channels_configured = True self._wait_for_data() @property def flicker_detected(self): """The flicker frequency detected in Hertz""" if not self._flicker_detection_1k_configured: AttributeError( "Flicker detection must be enabled to access `flicker_detected`" ) flicker_status = self._fd_status if flicker_status == 45: return 1000 if flicker_status == 46: return 1200 return None # if we haven't returned yet either there was an error or an unknown frequency was detected @property def flicker_detection_enabled(self): """The flicker detection status of the sensor. True if the sensor is configured\ to detect flickers. Currently only 1000Hz and 1200Hz flicker detection is supported """ return self._flicker_detection_1k_configured @flicker_detection_enabled.setter def flicker_detection_enabled(self, flicker_enable): if flicker_enable: self._configure_1k_flicker_detection() else: self._configure_f1_f4() # sane default def _f1f4_clear_nir(self): """Configure SMUX for sensors F1-F4, Clear and NIR""" self._set_smux(SMUX_IN.NC_F3L, SMUX_OUT.DISABLED, SMUX_OUT.ADC2) self._set_smux(SMUX_IN.F1L_NC, SMUX_OUT.ADC0, SMUX_OUT.DISABLED) self._set_smux(SMUX_IN.NC_NC0, SMUX_OUT.DISABLED, SMUX_OUT.DISABLED) self._set_smux(SMUX_IN.NC_F8L, SMUX_OUT.DISABLED, SMUX_OUT.DISABLED) self._set_smux(SMUX_IN.F6L_NC, SMUX_OUT.DISABLED, SMUX_OUT.DISABLED) self._set_smux(SMUX_IN.F2L_F4L, SMUX_OUT.ADC1, SMUX_OUT.ADC3) self._set_smux(SMUX_IN.NC_F5L, SMUX_OUT.DISABLED, SMUX_OUT.DISABLED) self._set_smux(SMUX_IN.F7L_NC, SMUX_OUT.DISABLED, SMUX_OUT.DISABLED) self._set_smux(SMUX_IN.NC_CL, SMUX_OUT.DISABLED, SMUX_OUT.ADC4) self._set_smux(SMUX_IN.NC_F5R, SMUX_OUT.DISABLED, SMUX_OUT.DISABLED) self._set_smux(SMUX_IN.F7R_NC, SMUX_OUT.DISABLED, SMUX_OUT.DISABLED) self._set_smux(SMUX_IN.NC_NC1, SMUX_OUT.DISABLED, SMUX_OUT.DISABLED) self._set_smux(SMUX_IN.NC_F2R, SMUX_OUT.DISABLED, SMUX_OUT.ADC1) self._set_smux(SMUX_IN.F4R_NC, SMUX_OUT.ADC3, SMUX_OUT.DISABLED) self._set_smux(SMUX_IN.F8R_F6R, SMUX_OUT.DISABLED, SMUX_OUT.DISABLED) self._set_smux(SMUX_IN.NC_F3R, SMUX_OUT.DISABLED, SMUX_OUT.ADC2) self._set_smux(SMUX_IN.F1R_EXT_GPIO, SMUX_OUT.ADC0, SMUX_OUT.DISABLED) self._set_smux(SMUX_IN.EXT_INT_CR, SMUX_OUT.DISABLED, SMUX_OUT.ADC4) self._set_smux(SMUX_IN.NC_DARK, SMUX_OUT.DISABLED, SMUX_OUT.DISABLED) self._set_smux(SMUX_IN.NIR_F, SMUX_OUT.ADC5, SMUX_OUT.DISABLED) def _f5f8_clear_nir(self): # SMUX Config for F5,F6,F7,F8,NIR,Clear self._set_smux(SMUX_IN.NC_F3L, SMUX_OUT.DISABLED, SMUX_OUT.DISABLED) self._set_smux(SMUX_IN.F1L_NC, SMUX_OUT.DISABLED, SMUX_OUT.DISABLED) self._set_smux(SMUX_IN.NC_NC0, SMUX_OUT.DISABLED, SMUX_OUT.DISABLED) self._set_smux(SMUX_IN.NC_F8L, SMUX_OUT.DISABLED, SMUX_OUT.ADC3) self._set_smux(SMUX_IN.F6L_NC, SMUX_OUT.ADC1, SMUX_OUT.DISABLED) self._set_smux(SMUX_IN.F2L_F4L, SMUX_OUT.DISABLED, SMUX_OUT.DISABLED) self._set_smux(SMUX_IN.NC_F5L, SMUX_OUT.DISABLED, SMUX_OUT.ADC0) self._set_smux(SMUX_IN.F7L_NC, SMUX_OUT.ADC2, SMUX_OUT.DISABLED) self._set_smux(SMUX_IN.NC_CL, SMUX_OUT.DISABLED, SMUX_OUT.ADC4) self._set_smux(SMUX_IN.NC_F5R, SMUX_OUT.DISABLED, SMUX_OUT.ADC0) self._set_smux(SMUX_IN.F7R_NC, SMUX_OUT.ADC2, SMUX_OUT.DISABLED) self._set_smux(SMUX_IN.NC_NC1, SMUX_OUT.DISABLED, SMUX_OUT.DISABLED) self._set_smux(SMUX_IN.NC_F2R, SMUX_OUT.DISABLED, SMUX_OUT.DISABLED) self._set_smux(SMUX_IN.F4R_NC, SMUX_OUT.DISABLED, SMUX_OUT.DISABLED) self._set_smux(SMUX_IN.F8R_F6R, SMUX_OUT.ADC3, SMUX_OUT.ADC1) self._set_smux(SMUX_IN.NC_F3R, SMUX_OUT.DISABLED, SMUX_OUT.DISABLED) self._set_smux(SMUX_IN.F1R_EXT_GPIO, SMUX_OUT.DISABLED, SMUX_OUT.DISABLED) self._set_smux(SMUX_IN.EXT_INT_CR, SMUX_OUT.DISABLED, SMUX_OUT.ADC4) self._set_smux(SMUX_IN.NC_DARK, SMUX_OUT.DISABLED, SMUX_OUT.DISABLED) self._set_smux(SMUX_IN.NIR_F, SMUX_OUT.ADC5, SMUX_OUT.DISABLED) # TODO: Convert as much of this as possible to properties or named attributes def _configure_1k_flicker_detection(self): self._low_channels_configured = False self._high_channels_configured = False # RAM_BANK 0 select which RAM bank to access in register addresses 0x00-0x7f self._write_register(_AS7341_CFG0, 0x00) # The coefficient calculated are stored into the RAM bank 0 and RAM bank 1, # they are used instead of 100Hz and 120Hz coefficients which are the default # flicker detection coefficients # write new coefficients to detect the 1000Hz and 1200Hz - part 1 self._write_register(0x04, 0x9E) self._write_register(0x05, 0x36) self._write_register(0x0E, 0x2E) self._write_register(0x0F, 0x1B) self._write_register(0x18, 0x7D) self._write_register(0x19, 0x36) self._write_register(0x22, 0x09) self._write_register(0x23, 0x1B) self._write_register(0x2C, 0x5B) self._write_register(0x2D, 0x36) self._write_register(0x36, 0xE5) self._write_register(0x37, 0x1A) self._write_register(0x40, 0x3A) self._write_register(0x41, 0x36) self._write_register(0x4A, 0xC1) self._write_register(0x4B, 0x1A) self._write_register(0x54, 0x18) self._write_register(0x55, 0x36) self._write_register(0x5E, 0x9C) self._write_register(0x5F, 0x1A) self._write_register(0x68, 0xF6) self._write_register(0x69, 0x35) self._write_register(0x72, 0x78) self._write_register(0x73, 0x1A) self._write_register(0x7C, 0x4D) self._write_register(0x7D, 0x35) # RAM_BANK 1 select which RAM bank to access in register addresses 0x00-0x7f self._write_register(_AS7341_CFG0, 0x01) # write new coefficients to detect the 1000Hz and 1200Hz - part 1 self._write_register(0x06, 0x54) self._write_register(0x07, 0x1A) self._write_register(0x10, 0xB3) self._write_register(0x11, 0x35) self._write_register(0x1A, 0x2F) self._write_register(0x1B, 0x1A) self._write_register(_AS7341_CFG0, 0x01) # select RAM coefficients for flicker detection by setting # fd_disable_constant_init to „1“ (FD_CFG0 register) in FD_CFG0 register - # 0xD7 # fd_disable_constant_init=1 # fd_samples=4 self._write_register(_AS7341_FD_CFG0, 0x60) # in FD_CFG1 register - 0xd8 fd_time(7:0) = 0x40 self._write_register(_AS7341_FD_TIME1, 0x40) # in FD_CFG2 register - 0xd9 fd_dcr_filter_size=1 fd_nr_data_sets(2:0)=5 self._write_register(0xD9, 0x25) # in FD_CFG3 register - 0xda fd_gain=9 self._write_register(_AS7341_FD_TIME2, 0x48) # in CFG9 register - 0xb2 sien_fd=1 self._write_register(_AS7341_CFG9, 0x40) # in ENABLE - 0x80 fden=1 and pon=1 are enabled self._write_register(_AS7341_ENABLE, 0x41) self._flicker_detection_1k_configured = True def _smux_template(self): # SMUX_OUT.DISABLED # SMUX_OUT.ADC0 # SMUX_OUT.ADC1 # SMUX_OUT.ADC2 # SMUX_OUT.ADC3 # SMUX_OUT.ADC4 # SMUX_OUT.ADC5 self._set_smux(SMUX_IN.NC_F3L, SMUX_OUT.DISABLED, SMUX_OUT.DISABLED) self._set_smux(SMUX_IN.F1L_NC, SMUX_OUT.DISABLED, SMUX_OUT.DISABLED) self._set_smux(SMUX_IN.NC_NC0, SMUX_OUT.DISABLED, SMUX_OUT.DISABLED) self._set_smux(SMUX_IN.NC_F8L, SMUX_OUT.DISABLED, SMUX_OUT.DISABLED) self._set_smux(SMUX_IN.F6L_NC, SMUX_OUT.DISABLED, SMUX_OUT.DISABLED) self._set_smux(SMUX_IN.F2L_F4L, SMUX_OUT.DISABLED, SMUX_OUT.DISABLED) self._set_smux(SMUX_IN.NC_F5L, SMUX_OUT.DISABLED, SMUX_OUT.DISABLED) self._set_smux(SMUX_IN.F7L_NC, SMUX_OUT.DISABLED, SMUX_OUT.DISABLED) self._set_smux(SMUX_IN.NC_CL, SMUX_OUT.DISABLED, SMUX_OUT.DISABLED) self._set_smux(SMUX_IN.NC_F5R, SMUX_OUT.DISABLED, SMUX_OUT.DISABLED) self._set_smux(SMUX_IN.F7R_NC, SMUX_OUT.DISABLED, SMUX_OUT.DISABLED) self._set_smux(SMUX_IN.NC_NC1, SMUX_OUT.DISABLED, SMUX_OUT.DISABLED) self._set_smux(SMUX_IN.NC_F2R, SMUX_OUT.DISABLED, SMUX_OUT.DISABLED) self._set_smux(SMUX_IN.F4R_NC, SMUX_OUT.DISABLED, SMUX_OUT.DISABLED) self._set_smux(SMUX_IN.F8R_F6R, SMUX_OUT.DISABLED, SMUX_OUT.DISABLED) self._set_smux(SMUX_IN.NC_F3R, SMUX_OUT.DISABLED, SMUX_OUT.DISABLED) self._set_smux(SMUX_IN.F1R_EXT_GPIO, SMUX_OUT.DISABLED, SMUX_OUT.DISABLED) self._set_smux(SMUX_IN.EXT_INT_CR, SMUX_OUT.DISABLED, SMUX_OUT.DISABLED) self._set_smux(SMUX_IN.NC_DARK, SMUX_OUT.DISABLED, SMUX_OUT.DISABLED) self._set_smux(SMUX_IN.NIR_F, SMUX_OUT.DISABLED, SMUX_OUT.DISABLED) def _set_smux(self, smux_addr, smux_out1, smux_out2): """Connect a pair of sensors to an ADC channel""" low_nibble = smux_out1 high_nibble = smux_out2 << 4 smux_byte = high_nibble | low_nibble self._write_register(smux_addr, smux_byte) @property def gain(self): """The ADC gain multiplier. Must be a valid `adafruit_as7341.Gain`""" return self._gain @gain.setter def gain(self, gain_value): if not Gain.is_valid(gain_value): raise AttributeError( "`gain` must be a valid `adafruit_as7341.Gain`") self._gain = gain_value @property def _smux_enabled(self): return self._smux_enable_bit @_smux_enabled.setter def _smux_enabled(self, enable_smux): self._low_bank_active = False self._smux_enable_bit = enable_smux while self._smux_enable_bit is True: sleep(0.001) @property @_low_bank def led_current(self): """The maximum allowed current through the attached LED in milliamps. Odd numbered values will be rounded down to the next lowest even number due to the internal configuration restrictions""" current_val = self._led_current_bits return (current_val * 2) + 4 @led_current.setter @_low_bank def led_current(self, led_curent): new_current = int((min(258, max(4, led_curent)) - 4) / 2) self._led_current_bits = new_current @property @_low_bank def led(self): """The attached LED. Set to True to turn on, False to turn off""" return self._led_enabled @led.setter @_low_bank def led(self, led_on): self._led_enabled = led_on @property @_low_bank def _led_control_enabled(self): return self._led_control_enable_bit @_led_control_enabled.setter @_low_bank def _led_control_enabled(self, enabled): self._led_control_enable_bit = enabled
class LSM6DS: # pylint: disable=too-many-instance-attributes """Driver for the LSM6DSOX 6-axis accelerometer and gyroscope. :param ~busio.I2C i2c_bus: The I2C bus the LSM6DSOX is connected to. :param int address: TThe I2C device address. Defaults to :const:`0x6A` """ # ROUnaryStructs: _chip_id = ROUnaryStruct(_LSM6DS_WHOAMI, "<b") # Structs _raw_accel_data = Struct(_LSM6DS_OUTX_L_A, "<hhh") _raw_gyro_data = Struct(_LSM6DS_OUTX_L_G, "<hhh") # RWBits: _accel_range = RWBits(2, _LSM6DS_CTRL1_XL, 2) _accel_data_rate = RWBits(4, _LSM6DS_CTRL1_XL, 4) _gyro_data_rate = RWBits(4, _LSM6DS_CTRL2_G, 4) _gyro_range = RWBits(2, _LSM6DS_CTRL2_G, 2) _gyro_range_125dps = RWBit(_LSM6DS_CTRL2_G, 1) _sw_reset = RWBit(_LSM6DS_CTRL3_C, 0) _bdu = RWBit(_LSM6DS_CTRL3_C, 6) _high_pass_filter = RWBits(2, _LSM6DS_CTRL8_XL, 5) _i3c_disable = RWBit(_LSM6DS_CTRL9_XL, 1) _pedometer_reset = RWBit(_LSM6DS_CTRL10_C, 1) _func_enable = RWBit(_LSM6DS_CTRL10_C, 2) _ped_enable = RWBit(_LSM6DS_TAP_CFG, 6) pedometer_steps = ROUnaryStruct(_LSM6DS_STEP_COUNTER, "<h") """The number of steps detected by the pedometer. You must enable with `pedometer_enable` before calling. Use `pedometer_reset` to reset the number of steps""" CHIP_ID = None def __init__(self, i2c_bus, address=LSM6DS_DEFAULT_ADDRESS): self._cached_accel_range = None self._cached_gyro_range = None self.i2c_device = i2c_device.I2CDevice(i2c_bus, address) if self.CHIP_ID is None: raise AttributeError("LSM6DS Parent Class cannot be directly instantiated") if self._chip_id != self.CHIP_ID: raise RuntimeError( "Failed to find %s - check your wiring!" % self.__class__.__name__ ) self.reset() if not hasattr(GyroRange, "string"): self._add_gyro_ranges() self._bdu = True self._add_accel_ranges() self.accelerometer_data_rate = Rate.RATE_104_HZ # pylint: disable=no-member self.gyro_data_rate = Rate.RATE_104_HZ # pylint: disable=no-member self.accelerometer_range = AccelRange.RANGE_4G # pylint: disable=no-member self.gyro_range = GyroRange.RANGE_250_DPS # pylint: disable=no-member def reset(self): "Resets the sensor's configuration into an initial state" self._sw_reset = True while self._sw_reset: sleep(0.001) @staticmethod def _add_gyro_ranges(): GyroRange.add_values( ( ("RANGE_125_DPS", 125, 125, 4.375), ("RANGE_250_DPS", 0, 250, 8.75), ("RANGE_500_DPS", 1, 500, 17.50), ("RANGE_1000_DPS", 2, 1000, 35.0), ("RANGE_2000_DPS", 3, 2000, 70.0), ) ) @staticmethod def _add_accel_ranges(): AccelRange.add_values( ( ("RANGE_2G", 0, 2, 0.061), ("RANGE_16G", 1, 16, 0.488), ("RANGE_4G", 2, 4, 0.122), ("RANGE_8G", 3, 8, 0.244), ) ) @property def acceleration(self): """The x, y, z acceleration values returned in a 3-tuple and are in m / s ^ 2.""" raw_accel_data = self._raw_accel_data x = self._scale_xl_data(raw_accel_data[0]) y = self._scale_xl_data(raw_accel_data[1]) z = self._scale_xl_data(raw_accel_data[2]) return (x, y, z) @property def gyro(self): """The x, y, z angular velocity values returned in a 3-tuple and are in radians / second""" raw_gyro_data = self._raw_gyro_data x, y, z = [radians(self._scale_gyro_data(i)) for i in raw_gyro_data] return (x, y, z) def _scale_xl_data(self, raw_measurement): return ( raw_measurement * AccelRange.lsb[self._cached_accel_range] * _MILLI_G_TO_ACCEL ) def _scale_gyro_data(self, raw_measurement): return raw_measurement * GyroRange.lsb[self._cached_gyro_range] / 1000 @property def accelerometer_range(self): """Adjusts the range of values that the sensor can measure, from +/- 2G to +/-16G Note that larger ranges will be less accurate. Must be an ``AccelRange``""" return self._cached_accel_range # pylint: disable=no-member @accelerometer_range.setter def accelerometer_range(self, value): if not AccelRange.is_valid(value): raise AttributeError("range must be an `AccelRange`") self._accel_range = value self._cached_accel_range = value sleep(0.2) # needed to let new range settle @property def gyro_range(self): """Adjusts the range of values that the sensor can measure, from 125 Degrees/s to 2000 degrees/s. Note that larger ranges will be less accurate. Must be a ``GyroRange``.""" return self._cached_gyro_range @gyro_range.setter def gyro_range(self, value): self._set_gyro_range(value) sleep(0.2) def _set_gyro_range(self, value): if not GyroRange.is_valid(value): raise AttributeError("range must be a `GyroRange`") # range uses `FS_G` enum if value <= GyroRange.RANGE_2000_DPS: # pylint: disable=no-member self._gyro_range_125dps = False self._gyro_range = value # range uses the `FS_125` bit if value is GyroRange.RANGE_125_DPS: # pylint: disable=no-member self._gyro_range_125dps = True self._cached_gyro_range = value # needed to let new range settle @property def accelerometer_data_rate(self): """Select the rate at which the accelerometer takes measurements. Must be a ``Rate``""" return self._accel_data_rate @accelerometer_data_rate.setter def accelerometer_data_rate(self, value): if not Rate.is_valid(value): raise AttributeError("accelerometer_data_rate must be a `Rate`") self._accel_data_rate = value # sleep(.2) # needed to let new range settle @property def gyro_data_rate(self): """Select the rate at which the gyro takes measurements. Must be a ``Rate``""" return self._gyro_data_rate @gyro_data_rate.setter def gyro_data_rate(self, value): if not Rate.is_valid(value): raise AttributeError("gyro_data_rate must be a `Rate`") self._gyro_data_rate = value # sleep(.2) # needed to let new range settle @property def pedometer_enable(self): """ Whether the pedometer function on the accelerometer is enabled""" return self._ped_enable and self._func_enable @pedometer_enable.setter def pedometer_enable(self, enable): self._ped_enable = enable self._func_enable = enable self._pedometer_reset = enable @property def high_pass_filter(self): """The high pass filter applied to accelerometer data""" return self._high_pass_filter @high_pass_filter.setter def high_pass_filter(self, value): if not AccelHPF.is_valid(value): raise AttributeError("range must be an `AccelHPF`") self._high_pass_filter = value
class ICM20948(ICM20X): # pylint:disable=too-many-instance-attributes """Library for the ST ICM-20948 Wide-Range 6-DoF Accelerometer and Gyro. :param ~busio.I2C i2c_bus: The I2C bus the ICM20948 is connected to. :param address: The I2C slave address of the sensor """ _slave_finished = ROBit(_ICM20X_I2C_MST_STATUS, 6) # mag data is LE _raw_mag_data = Struct(_ICM20948_EXT_SLV_SENS_DATA_00, "<hhhh") _bypass_i2c_master = RWBit(_ICM20X_REG_INT_PIN_CFG, 1) _i2c_master_control = UnaryStruct(_ICM20X_I2C_MST_CTRL, ">B") _i2c_master_enable = RWBit(_ICM20X_USER_CTRL, 5) # TODO: use this in sw reset _i2c_master_reset = RWBit(_ICM20X_USER_CTRL, 1) _slave0_addr = UnaryStruct(_ICM20X_I2C_SLV0_ADDR, ">B") _slave0_reg = UnaryStruct(_ICM20X_I2C_SLV0_REG, ">B") _slave0_ctrl = UnaryStruct(_ICM20X_I2C_SLV0_CTRL, ">B") _slave0_do = UnaryStruct(_ICM20X_I2C_SLV0_DO, ">B") _slave4_addr = UnaryStruct(_ICM20X_I2C_SLV4_ADDR, ">B") _slave4_reg = UnaryStruct(_ICM20X_I2C_SLV4_REG, ">B") _slave4_ctrl = UnaryStruct(_ICM20X_I2C_SLV4_CTRL, ">B") _slave4_do = UnaryStruct(_ICM20X_I2C_SLV4_DO, ">B") _slave4_di = UnaryStruct(_ICM20X_I2C_SLV4_DI, ">B") def __init__(self, i2c_bus, address=_ICM20948_DEFAULT_ADDRESS): AccelRange.add_values( ( ("RANGE_2G", 0, 2, 16384), ("RANGE_4G", 1, 4, 8192), ("RANGE_8G", 2, 8, 4096.0), ("RANGE_16G", 3, 16, 2048), ) ) GyroRange.add_values( ( ("RANGE_250_DPS", 0, 250, 131.0), ("RANGE_500_DPS", 1, 500, 65.5), ("RANGE_1000_DPS", 2, 1000, 32.8), ("RANGE_2000_DPS", 3, 2000, 16.4), ) ) # https://www.y-ic.es/datasheet/78/SMDSW.020-2OZ.pdf page 9 MagDataRate.add_values( ( ("SHUTDOWN", 0x0, "Shutdown", None), ("SINGLE", 0x1, "Single", None), ("RATE_10HZ", 0x2, 10, None), ("RATE_20HZ", 0x4, 20, None), ("RATE_50HZ", 0x6, 50, None), ("RATE_100HZ", 0x8, 100, None), ) ) super().__init__(i2c_bus, address) self._magnetometer_init() # A million thanks to the SparkFun folks for their library that I pillaged to write this method! # See their Python library here: # https://github.com/sparkfun/Qwiic_9DoF_IMU_ICM20948_Py @property def _mag_configured(self): success = False for _i in range(5): success = self._mag_id() is not None if success: return True self._reset_i2c_master() # i2c master stuck, try resetting return False def _reset_i2c_master(self): self._bank = 0 self._i2c_master_reset = True def _magnetometer_enable(self): self._bank = 0 sleep(0.100) self._bypass_i2c_master = False sleep(0.005) # no repeated start, i2c master clock = 345.60kHz self._bank = 3 sleep(0.100) self._i2c_master_control = 0x17 sleep(0.100) self._bank = 0 sleep(0.100) self._i2c_master_enable = True sleep(0.020) def _magnetometer_init(self): self._magnetometer_enable() self.magnetometer_data_rate = ( MagDataRate.RATE_100HZ # pylint: disable=no-member ) if not self._mag_configured: return False self._setup_mag_readout() return True # set up slave0 for reading into the bank 0 data registers def _setup_mag_readout(self): self._bank = 3 self._slave0_addr = 0x8C sleep(0.005) self._slave0_reg = 0x11 sleep(0.005) self._slave0_ctrl = 0x89 # enable sleep(0.005) def _mag_id(self): return self._read_mag_register(0x01) @property def magnetic(self): """The current magnetic field strengths onthe X, Y, and Z axes in uT (micro-teslas)""" self._bank = 0 full_data = self._raw_mag_data sleep(0.005) x = full_data[0] * _ICM20X_UT_PER_LSB y = full_data[1] * _ICM20X_UT_PER_LSB z = full_data[2] * _ICM20X_UT_PER_LSB return (x, y, z) @property def magnetometer_data_rate(self): """The rate at which the magenetometer takes measurements to update its output registers""" # read mag DR register self._read_mag_register(_AK09916_CNTL2) @magnetometer_data_rate.setter def magnetometer_data_rate(self, mag_rate): # From https://www.y-ic.es/datasheet/78/SMDSW.020-2OZ.pdf page 9 # "When user wants to change operation mode, transit to Power-down mode first and then # transit to other modes. After Power-down mode is set, at least 100 microsectons (Twait) # is needed before setting another mode" if not MagDataRate.is_valid(mag_rate): raise AttributeError("range must be an `MagDataRate`") self._write_mag_register( _AK09916_CNTL2, MagDataRate.SHUTDOWN # pylint: disable=no-member ) sleep(0.001) self._write_mag_register(_AK09916_CNTL2, mag_rate) def _read_mag_register(self, register_addr, slave_addr=0x0C): self._bank = 3 slave_addr |= 0x80 # set top bit for read self._slave4_addr = slave_addr sleep(0.005) self._slave4_reg = register_addr sleep(0.005) self._slave4_ctrl = ( 0x80 # enable, don't raise interrupt, write register value, no delay ) sleep(0.005) self._bank = 0 finished = False for _i in range(100): finished = self._slave_finished if finished: # bueno! break sleep(0.010) if not finished: return None self._bank = 3 mag_register_data = self._slave4_di sleep(0.005) return mag_register_data def _write_mag_register(self, register_addr, value, slave_addr=0x0C): self._bank = 3 self._slave4_addr = slave_addr sleep(0.005) self._slave4_reg = register_addr sleep(0.005) self._slave4_do = value sleep(0.005) self._slave4_ctrl = ( 0x80 # enable, don't raise interrupt, write register value, no delay ) sleep(0.005) self._bank = 0 finished = False for _i in range(100): finished = self._slave_finished if finished: # bueno! break sleep(0.010) return finished
class ICM20X: # pylint:disable=too-many-instance-attributes """Library for the ST ICM-20X Wide-Range 6-DoF Accelerometer and Gyro Family :param ~busio.I2C i2c_bus: The I2C bus the ICM20X is connected to. :param address: The I2C slave address of the sensor """ # Bank 0 _device_id = ROUnaryStruct(_ICM20X_WHO_AM_I, ">B") _bank_reg = UnaryStruct(_ICM20X_REG_BANK_SEL, ">B") _reset = RWBit(_ICM20X_PWR_MGMT_1, 7) _sleep_reg = RWBit(_ICM20X_PWR_MGMT_1, 6) _low_power_en = RWBit(_ICM20X_PWR_MGMT_1, 5) _clock_source = RWBits(3, _ICM20X_PWR_MGMT_1, 0) _raw_accel_data = Struct(_ICM20X_ACCEL_XOUT_H, ">hhh") # ds says LE :| _raw_gyro_data = Struct(_ICM20X_GYRO_XOUT_H, ">hhh") _lp_config_reg = UnaryStruct(_ICM20X_LP_CONFIG, ">B") _i2c_master_cycle_en = RWBit(_ICM20X_LP_CONFIG, 6) _accel_cycle_en = RWBit(_ICM20X_LP_CONFIG, 5) _gyro_cycle_en = RWBit(_ICM20X_LP_CONFIG, 4) # Bank 2 _gyro_dlpf_enable = RWBits(1, _ICM20X_GYRO_CONFIG_1, 0) _gyro_range = RWBits(2, _ICM20X_GYRO_CONFIG_1, 1) _gyro_dlpf_config = RWBits(3, _ICM20X_GYRO_CONFIG_1, 3) _accel_dlpf_enable = RWBits(1, _ICM20X_ACCEL_CONFIG_1, 0) _accel_range = RWBits(2, _ICM20X_ACCEL_CONFIG_1, 1) _accel_dlpf_config = RWBits(3, _ICM20X_ACCEL_CONFIG_1, 3) # this value is a 12-bit register spread across two bytes, big-endian first _accel_rate_divisor = UnaryStruct(_ICM20X_ACCEL_SMPLRT_DIV_1, ">H") _gyro_rate_divisor = UnaryStruct(_ICM20X_GYRO_SMPLRT_DIV, ">B") AccelDLPFFreq.add_values( ( ( "DISABLED", -1, "Disabled", None, ), # magical value that we will use do disable ("FREQ_246_0HZ_3DB", 1, 246.0, None), ("FREQ_111_4HZ_3DB", 2, 111.4, None), ("FREQ_50_4HZ_3DB", 3, 50.4, None), ("FREQ_23_9HZ_3DB", 4, 23.9, None), ("FREQ_11_5HZ_3DB", 5, 11.5, None), ("FREQ_5_7HZ_3DB", 6, 5.7, None), ("FREQ_473HZ_3DB", 7, 473, None), ) ) GyroDLPFFreq.add_values( ( ( "DISABLED", -1, "Disabled", None, ), # magical value that we will use do disable ("FREQ_196_6HZ_3DB", 0, 196.6, None), ("FREQ_151_8HZ_3DB", 1, 151.8, None), ("FREQ_119_5HZ_3DB", 2, 119.5, None), ("FREQ_51_2HZ_3DB", 3, 51.2, None), ("FREQ_23_9HZ_3DB", 4, 23.9, None), ("FREQ_11_6HZ_3DB", 5, 11.6, None), ("FREQ_5_7HZ_3DB", 6, 5.7, None), ("FREQ_361_4HZ_3DB", 7, 361.4, None), ) ) @property def _bank(self): return self._bank_reg >> 4 @_bank.setter def _bank(self, value): self._bank_reg = value << 4 def __init__(self, i2c_bus, address): self.i2c_device = i2c_device.I2CDevice(i2c_bus, address) self._bank = 0 if not self._device_id in [_ICM20649_DEVICE_ID, _ICM20948_DEVICE_ID]: raise RuntimeError("Failed to find an ICM20X sensor - check your wiring!") self.reset() self.initialize() def initialize(self): """Configure the sensors with the default settings. For use after calling `reset()`""" self._sleep = False self.accelerometer_range = AccelRange.RANGE_8G # pylint: disable=no-member self.gyro_range = GyroRange.RANGE_500_DPS # pylint: disable=no-member self.accelerometer_data_rate_divisor = 20 # ~53.57Hz self.gyro_data_rate_divisor = 10 # ~100Hz def reset(self): """Resets the internal registers and restores the default settings""" self._bank = 0 sleep(0.005) self._reset = True sleep(0.005) while self._reset: sleep(0.005) @property def _sleep(self): self._bank = 0 sleep(0.005) self._sleep_reg = False sleep(0.005) @_sleep.setter def _sleep(self, sleep_enabled): self._bank = 0 sleep(0.005) self._sleep_reg = sleep_enabled sleep(0.005) @property def acceleration(self): """The x, y, z acceleration values returned in a 3-tuple and are in m / s ^ 2.""" self._bank = 0 raw_accel_data = self._raw_accel_data sleep(0.005) x = self._scale_xl_data(raw_accel_data[0]) y = self._scale_xl_data(raw_accel_data[1]) z = self._scale_xl_data(raw_accel_data[2]) return (x, y, z) @property def gyro(self): """The x, y, z angular velocity values returned in a 3-tuple and are in degrees / second""" self._bank = 0 raw_gyro_data = self._raw_gyro_data x = self._scale_gyro_data(raw_gyro_data[0]) y = self._scale_gyro_data(raw_gyro_data[1]) z = self._scale_gyro_data(raw_gyro_data[2]) return (x, y, z) def _scale_xl_data(self, raw_measurement): sleep(0.005) return raw_measurement / AccelRange.lsb[self._cached_accel_range] * G_TO_ACCEL def _scale_gyro_data(self, raw_measurement): return ( raw_measurement / GyroRange.lsb[self._cached_gyro_range] ) * _ICM20X_RAD_PER_DEG @property def accelerometer_range(self): """Adjusts the range of values that the sensor can measure, from +/- 4G to +/-30G Note that larger ranges will be less accurate. Must be an `AccelRange`""" return self._cached_accel_range @accelerometer_range.setter def accelerometer_range(self, value): # pylint: disable=no-member if not AccelRange.is_valid(value): raise AttributeError("range must be an `AccelRange`") self._bank = 2 sleep(0.005) self._accel_range = value sleep(0.005) self._cached_accel_range = value self._bank = 0 @property def gyro_range(self): """Adjusts the range of values that the sensor can measure, from 500 Degrees/second to 4000 degrees/s. Note that larger ranges will be less accurate. Must be a `GyroRange`""" return self._cached_gyro_range @gyro_range.setter def gyro_range(self, value): if not GyroRange.is_valid(value): raise AttributeError("range must be a `GyroRange`") self._bank = 2 sleep(0.005) self._gyro_range = value sleep(0.005) self._cached_gyro_range = value self._bank = 0 sleep(0.100) # needed to let new range settle @property def accelerometer_data_rate_divisor(self): """The divisor for the rate at which accelerometer measurements are taken in Hz Note: The data rates are set indirectly by setting a rate divisor according to the following formula: ``accelerometer_data_rate = 1125/(1+divisor)`` This function sets the raw rate divisor. """ self._bank = 2 raw_rate_divisor = self._accel_rate_divisor sleep(0.005) self._bank = 0 # rate_hz = 1125/(1+raw_rate_divisor) return raw_rate_divisor @accelerometer_data_rate_divisor.setter def accelerometer_data_rate_divisor(self, value): # check that value <= 4095 self._bank = 2 sleep(0.005) self._accel_rate_divisor = value sleep(0.005) @property def gyro_data_rate_divisor(self): """The divisor for the rate at which gyro measurements are taken in Hz Note: The data rates are set indirectly by setting a rate divisor according to the following formula: ``gyro_data_rate = 1100/(1+divisor)`` This function sets the raw rate divisor. """ self._bank = 2 raw_rate_divisor = self._gyro_rate_divisor sleep(0.005) self._bank = 0 # rate_hz = 1100/(1+raw_rate_divisor) return raw_rate_divisor @gyro_data_rate_divisor.setter def gyro_data_rate_divisor(self, value): # check that value <= 255 self._bank = 2 sleep(0.005) self._gyro_rate_divisor = value sleep(0.005) def _accel_rate_calc(self, divisor): # pylint:disable=no-self-use return 1125 / (1 + divisor) def _gyro_rate_calc(self, divisor): # pylint:disable=no-self-use return 1100 / (1 + divisor) @property def accelerometer_data_rate(self): """The rate at which accelerometer measurements are taken in Hz Note: The data rates are set indirectly by setting a rate divisor according to the following formula: ``accelerometer_data_rate = 1125/(1+divisor)`` This function does the math to find the divisor from a given rate but it will not be exactly as specified. """ return self._accel_rate_calc(self.accelerometer_data_rate_divisor) @accelerometer_data_rate.setter def accelerometer_data_rate(self, value): if value < self._accel_rate_calc(4095) or value > self._accel_rate_calc(0): raise AttributeError( "Accelerometer data rate must be between 0.27 and 1125.0" ) self.accelerometer_data_rate_divisor = value @property def gyro_data_rate(self): """The rate at which gyro measurements are taken in Hz Note: The data rates are set indirectly by setting a rate divisor according to the following formula: ``gyro_data_rate = 1100/(1+divisor)`` This function does the math to find the divisor from a given rate but it will not be exactly as specified. """ return self._gyro_rate_calc(self.gyro_data_rate_divisor) @gyro_data_rate.setter def gyro_data_rate(self, value): if value < self._gyro_rate_calc(4095) or value > self._gyro_rate_calc(0): raise AttributeError("Gyro data rate must be between 4.30 and 1100.0") divisor = round(((1125.0 - value) / value)) self.gyro_data_rate_divisor = divisor @property def accel_dlpf_cutoff(self): """The cutoff frequency for the accelerometer's digital low pass filter. Signals above the given frequency will be filtered out. Must be an ``AccelDLPFCutoff``. Use AccelDLPFCutoff.DISABLED to disable the filter **Note** readings immediately following setting a cutoff frequency will be inaccurate due to the filter "warming up" """ self._bank = 2 return self._accel_dlpf_config @accel_dlpf_cutoff.setter def accel_dlpf_cutoff(self, cutoff_frequency): if not AccelDLPFFreq.is_valid(cutoff_frequency): raise AttributeError("accel_dlpf_cutoff must be an `AccelDLPFFreq`") self._bank = 2 # check for shutdown if cutoff_frequency is AccelDLPFFreq.DISABLED: # pylint: disable=no-member self._accel_dlpf_enable = False return self._accel_dlpf_enable = True self._accel_dlpf_config = cutoff_frequency @property def gyro_dlpf_cutoff(self): """The cutoff frequency for the gyro's digital low pass filter. Signals above the given frequency will be filtered out. Must be a ``GyroDLPFFreq``. Use GyroDLPFCutoff.DISABLED to disable the filter **Note** readings immediately following setting a cutoff frequency will be inaccurate due to the filter "warming up" """ self._bank = 2 return self._gyro_dlpf_config @gyro_dlpf_cutoff.setter def gyro_dlpf_cutoff(self, cutoff_frequency): if not GyroDLPFFreq.is_valid(cutoff_frequency): raise AttributeError("gyro_dlpf_cutoff must be a `GyroDLPFFreq`") self._bank = 2 # check for shutdown if cutoff_frequency is GyroDLPFFreq.DISABLED: # pylint: disable=no-member self._gyro_dlpf_enable = False return self._gyro_dlpf_enable = True self._gyro_dlpf_config = cutoff_frequency @property def _low_power(self): self._bank = 0 return self._low_power_en @_low_power.setter def _low_power(self, enabled): self._bank = 0 self._low_power_en = enabled
class LIS3MDL: """Driver for the LIS3MDL 3-axis magnetometer. :param ~busio.I2C i2c_bus: The I2C bus the LIS3MDL is connected to. :param address: The I2C device address. Defaults to :const:`0x1C` **Quickstart: Importing and using the device** Here is an example of using the :class:`LIS3MDL` class. First you will need to import the libraries to use the sensor .. code-block:: python import board import adafruit_lis3mdl Once this is done you can define your `board.I2C` object and define your sensor object .. code-block:: python i2c = board.I2C() sensor = adafruit_lis3mdl.LIS3MDL(i2c) Now you have access to the :attr:`magnetic` attribute .. code-block:: python mag_x, mag_y, mag_z = sensor.magnetic """ _chip_id = ROUnaryStruct(_LIS3MDL_WHOAMI, "<b") _perf_mode = RWBits(2, _LIS3MDL_CTRL_REG1, 5) _z_perf_mode = RWBits(2, _LIS3MDL_CTRL_REG4, 2) _operation_mode = RWBits(2, _LIS3MDL_CTRL_REG3, 0) _data_rate = RWBits(4, _LIS3MDL_CTRL_REG1, 1) _raw_mag_data = Struct(_LIS3MDL_OUT_X_L, "<hhh") _range = RWBits(2, _LIS3MDL_CTRL_REG2, 5) _reset = RWBit(_LIS3MDL_CTRL_REG2, 2) def __init__(self, i2c_bus, address=_LIS3MDL_DEFAULT_ADDRESS): # pylint: disable=no-member self.i2c_device = i2c_device.I2CDevice(i2c_bus, address) if self._chip_id != _LIS3MDL_CHIP_ID: raise RuntimeError("Failed to find LIS3MDL - check your wiring!") self.reset() self.performance_mode = PerformanceMode.MODE_ULTRA self.data_rate = Rate.RATE_155_HZ self.range = Range.RANGE_4_GAUSS self.operation_mode = OperationMode.CONTINUOUS sleep(0.010) def reset(self): # pylint: disable=no-self-use """Reset the sensor to the default state set by the library""" self._reset = True sleep(0.010) @property def magnetic(self): """The processed magnetometer sensor values. A 3-tuple of X, Y, Z axis values in microteslas that are signed floats. """ raw_mag_data = self._raw_mag_data x = self._scale_mag_data(raw_mag_data[0]) y = self._scale_mag_data(raw_mag_data[1]) z = self._scale_mag_data(raw_mag_data[2]) return (x, y, z) def _scale_mag_data(self, raw_measurement): # pylint: disable=no-self-use return (raw_measurement / Range.lsb[self.range]) * _GAUSS_TO_UT @property def range(self): """The measurement range for the magnetic sensor. Must be a ``Range``""" return self._range @range.setter def range(self, value): if not Range.is_valid(value): raise AttributeError("``range`` must be a ``Range``") self._range = value sleep(0.010) @property def data_rate(self): """The rate at which the sensor takes measurements. Must be a ``Rate``""" return self._data_rate @data_rate.setter def data_rate(self, value): # pylint: disable=no-member if value is Rate.RATE_155_HZ: self.performance_mode = PerformanceMode.MODE_ULTRA if value is Rate.RATE_300_HZ: self.performance_mode = PerformanceMode.MODE_HIGH if value is Rate.RATE_560_HZ: self.performance_mode = PerformanceMode.MODE_MEDIUM if value is Rate.RATE_1000_HZ: self.performance_mode = PerformanceMode.MODE_LOW_POWER sleep(0.010) if not Rate.is_valid(value): raise AttributeError("`data_rate` must be a `Rate`") self._data_rate = value @property def performance_mode(self): """Sets the 'performance mode' of the sensor. Must be a ``PerformanceMode``. Note that `performance_mode` affects the available data rate and will be automatically changed by setting ``data_rate`` to certain values.""" return self._perf_mode @performance_mode.setter def performance_mode(self, value): if not PerformanceMode.is_valid(value): raise AttributeError( "`performance_mode` must be a `PerformanceMode`") self._perf_mode = value self._z_perf_mode = value @property def operation_mode(self): """The operating mode for the sensor, controlling how measurements are taken. Must be an `OperationMode`. See the the `OperationMode` document for additional details """ return self._operation_mode @operation_mode.setter def operation_mode(self, value): if not OperationMode.is_valid(value): raise AttributeError("operation mode must be a OperationMode") self._operation_mode = value
class MSA301: # pylint: disable=too-many-instance-attributes """Driver for the MSA301 Accelerometer. :param ~busio.I2C i2c_bus: The I2C bus the MSA is connected to. """ _part_id = ROUnaryStruct(_MSA301_REG_PARTID, "<B") def __init__(self, i2c_bus): self.i2c_device = i2cdevice.I2CDevice(i2c_bus, _MSA301_I2CADDR_DEFAULT) if self._part_id != 0x13: raise AttributeError("Cannot find a MSA301") self._disable_x = self._disable_y = self._disable_z = False self.power_mode = Mode.NORMAL self.data_rate = DataRate.RATE_500_HZ self.bandwidth = BandWidth.WIDTH_250_HZ self.range = Range.RANGE_4_G self.resolution = Resolution.RESOLUTION_14_BIT self._tap_count = 0 _disable_x = RWBit(_MSA301_REG_ODR, 7) _disable_y = RWBit(_MSA301_REG_ODR, 6) _disable_z = RWBit(_MSA301_REG_ODR, 5) # _xyz_raw = ROBits(48, _MSA301_REG_OUT_X_L, 0, 6) _xyz_raw = Struct(_MSA301_REG_OUT_X_L, "<hhh") # tap INT enable and status _single_tap_int_en = RWBit(_MSA301_REG_INTSET0, 5) _double_tap_int_en = RWBit(_MSA301_REG_INTSET0, 4) _motion_int_status = ROUnaryStruct(_MSA301_REG_MOTIONINT, "B") # tap interrupt knobs _tap_quiet = RWBit(_MSA301_REG_TAPDUR, 7) _tap_shock = RWBit(_MSA301_REG_TAPDUR, 6) _tap_duration = RWBits(3, _MSA301_REG_TAPDUR, 0) _tap_threshold = RWBits(5, _MSA301_REG_TAPTH, 0) reg_tapdur = ROUnaryStruct(_MSA301_REG_TAPDUR, "B") # general settings knobs power_mode = RWBits(2, _MSA301_REG_POWERMODE, 6) bandwidth = RWBits(4, _MSA301_REG_POWERMODE, 1) data_rate = RWBits(4, _MSA301_REG_ODR, 0) range = RWBits(2, _MSA301_REG_RESRANGE, 0) resolution = RWBits(2, _MSA301_REG_RESRANGE, 2) @property def acceleration(self): """The x, y, z acceleration values returned in a 3-tuple and are in m / s ^ 2.""" # read the 6 bytes of acceleration data current_range = self.range scale = 1.0 if current_range == 3: scale = 512.0 if current_range == 2: scale = 1024.0 if current_range == 1: scale = 2048.0 if current_range == 0: scale = 4096.0 # shift down to the actual 14 bits and scale based on the range x, y, z = [((i >> 2) / scale) * _STANDARD_GRAVITY for i in self._xyz_raw] return (x, y, z) def enable_tap_detection( self, *, tap_count=1, threshold=25, long_initial_window=True, long_quiet_window=True, double_tap_window=TapDuration.DURATION_250_MS ): """ Enables tap detection with configurable parameters. :param int tap_count: 1 to detect only single taps, or 2 to detect only double taps.\ default is 1 :param int threshold: A threshold for the tap detection.\ The higher the value the less sensitive the detection. This changes based on the\ accelerometer range. Default is 25. :param int long_initial_window: This sets the length of the window of time where a\ spike in acceleration must occour in before being followed by a quiet period.\ `True` (default) sets the value to 70ms, False to 50ms. Default is `True` :param int long_quiet_window: The length of the "quiet" period after an acceleration\ spike where no more spikes can occour for a tap to be registered.\ `True` (default) sets the value to 30ms, False to 20ms. Default is `True`. :param int double_tap_window: The length of time after an initial tap is registered\ in which a second tap must be detected to count as a double tap. Setting a lower\ value will require a faster double tap. The value must be a\ ``TapDuration``. Default is ``TapDuration.DURATION_250_MS``. If you wish to set them yourself rather than using the defaults, you must use keyword arguments:: msa.enable_tap_detection(tap_count=2, threshold=25, double_tap_window=TapDuration.DURATION_700_MS) """ self._tap_shock = not long_initial_window self._tap_quiet = long_quiet_window self._tap_threshold = threshold self._tap_count = tap_count if double_tap_window > 7 or double_tap_window < 0: raise ValueError("double_tap_window must be a TapDuration") if tap_count == 1: self._single_tap_int_en = True elif tap_count == 2: self._tap_duration = double_tap_window self._double_tap_int_en = True else: raise ValueError("tap must be 1 for single tap, or 2 for double tap") @property def tapped(self): """`True` if a single or double tap was detected, depending on the value of the\ ``tap_count`` argument passed to ``enable_tap_detection``""" if self._tap_count == 0: return False motion_int_status = self._motion_int_status if motion_int_status == 0: # no interrupts triggered return False if self._tap_count == 1 and motion_int_status & 1 << 5: return True if self._tap_count == 2 and motion_int_status & 1 << 4: return True return False
class BMX160: """ Driver for the BMX160 accelerometer, magnetometer, gyroscope. In the buffer, bytes are allocated as follows: - mag 0-5 - rhall 6-7 (not relevant?) - gyro 8-13 - accel 14-19 - sensor time 20-22 """ # multiplicative constants # NOTE THESE FIRST TWO GET SET IN THE INIT SEQUENCE ACC_SCALAR = 1/(AccelSensitivity2Gravity * g_TO_METERS_PER_SECOND_SQUARED) # 1 m/s^2 = 0.101971621 g GYR_SCALAR = 1/GyroSensitivity2DegPerSec_values[4] MAG_SCALAR = 1/16 TEMP_SCALAR = 0.5**9 _accel = Struct(BMX160_ACCEL_DATA_ADDR, '<hhh') # this is the default scalar, but it should get reset anyhow in init _gyro = Struct(BMX160_GYRO_DATA_ADDR, '<hhh') _mag = Struct(BMX160_MAG_DATA_ADDR, '<hhh') _temp = Struct(BMX160_TEMP_DATA_ADDR, '<h') ### STATUS BITS status = ROBits(8, BMX160_STATUS_ADDR, 0) status_acc_pmu = ROBits(2, BMX160_PMU_STATUS_ADDR, 4) status_gyr_pmu = ROBits(2, BMX160_PMU_STATUS_ADDR, 2) status_mag_pmu = ROBits(2, BMX160_PMU_STATUS_ADDR, 0) cmd = RWBits(8, BMX160_COMMAND_REG_ADDR, 0) foc = RWBits(8, BMX160_FOC_CONF_ADDR, 0) # see ERR_REG in section 2.11.2 _error_status = ROBits(8, BMX160_ERROR_REG_ADDR, 0) error_code = ROBits(4, BMX160_ERROR_REG_ADDR, 1) drop_cmd_err = ROBit(BMX160_ERROR_REG_ADDR, 6) fatal_err = ROBit(BMX160_ERROR_REG_ADDR, 0) # straight from the datasheet. Need to be renamed and better explained @property def drdy_acc(self): return (self.status >> 7) & 1 @property def drdy_gyr(self): return (self.status >> 6) & 1 @property def drdy_mag(self): return (self.status >> 5) & 1 @property def nvm_rdy(self): return (self.status >> 4) & 1 @property def foc_rdy(self): return (self.status >> 3) & 1 @property def mag_man_op(self): return (self.status >> 2) & 1 @property def gyro_self_test_ok(self): return (self.status >> 1) & 1 _BUFFER = bytearray(40) _smallbuf = bytearray(6) _gyro_range = RWBits(8, BMX160_GYRO_RANGE_ADDR, 0) _accel_range = RWBits(8, BMX160_ACCEL_RANGE_ADDR, 0) # _gyro_bandwidth = NORMAL # _gyro_powermode = NORMAL _gyro_odr = 25 # Hz # _accel_bandwidth = NORMAL # _accel_powermode = NORMAL _accel_odr = 25 # Hz # _mag_bandwidth = NORMAL # _mag_powermode = NORMAL _mag_odr = 25 # Hz _mag_range = 250 # deg/sec def __init__(self): # soft reset & reboot self.cmd = BMX160_SOFT_RESET_CMD time.sleep(0.001) # Check ID registers. ID = self.read_u8(BMX160_CHIP_ID_ADDR) if ID != BMX160_CHIP_ID: raise RuntimeError('Could not find BMX160, check wiring!') # print("status:", format_binary(self.status)) # set the default settings self.init_mag() self.init_accel() self.init_gyro() # print("status:", format_binary(self.status)) ######################## SENSOR API ######################## def read_all(self): return self.read_bytes(BMX160_MAG_DATA_ADDR, 20, self._BUFFER) # synonymous # @property # def error_status(self): # return format_binary(self._error_status) @property def query_error(self): return format_binary(self._error_status) ### ACTUAL API @property def gyro(self): # deg/s return tuple(x * self.GYR_SCALAR for x in self._gyro) @property def accel(self): # m/s^2 return tuple(x * self.ACC_SCALAR for x in self._accel) @property def mag(self): # uT return tuple(x * self.MAG_SCALAR for x in self._mag) @property def temperature(self): return self._temp[0]*self.TEMP_SCALAR+23 @property def temp(self): return self._temp[0]*self.TEMP_SCALAR+23 @property def sensortime(self): tbuf = self.read_bytes(BMX160_SENSOR_TIME_ADDR, 3, self._smallbuf) t0, t1, t2 = tbuf[:3] t = (t2 << 16) | (t1 << 8) | t0 t *= 0.000039 # the time resolution is 39 microseconds return t ######################## SETTINGS RELATED ######################## ############## GYROSCOPE SETTINGS ############## # NOTE still missing BW / OSR config, but those are more complicated def init_gyro(self): # BW doesn't have an interface yet self._gyro_bwmode = BMX160_GYRO_BW_NORMAL_MODE # These rely on the setters to do their magic. self.gyro_range = BMX160_GYRO_RANGE_500_DPS # self.GYR_SCALAR = 1 # self.GYR_SCALAR = 1/GyroSensitivity2DegPerSec_values[1] self.gyro_odr = 25 self.gyro_powermode = BMX160_GYRO_NORMAL_MODE @property def gyro_range(self): return self._gyro_range @gyro_range.setter def gyro_range(self, rangeconst): """ The input is expected to be the BMX160-constant associated with the range. deg/s | bmxconst value | bmxconst_name ------------------------------------------------------ 2000 | 0 | BMX160_GYRO_RANGE_2000_DPS 1000 | 1 | BMX160_GYRO_RANGE_1000_DPS 500 | 2 | BMX160_GYRO_RANGE_500_DPS 250 | 3 | BMX160_GYRO_RANGE_250_DPS 125 | 4 | BMX160_GYRO_RANGE_125_DPS ex: bmx.gyro_range = BMX160_GYRO_RANGE_500_DPS equivalent to: bmx.gyro_range = 2 BMX160_GYRO_RANGE_VALUES = [2000, 1000, 500, 250, 125] """ if rangeconst in BMX160_GYRO_RANGE_CONSTANTS: self._gyro_range = rangeconst # read out the value to see if it changed successfully rangeconst = self._gyro_range val = BMX160_GYRO_RANGE_VALUES[rangeconst] self.GYR_SCALAR = (val / 32768.0) else: pass @property def gyro_odr(self): return self._gyro_odr @gyro_odr.setter def gyro_odr(self, odr): """ Set the output data rate of the gyroscope. The possible ODRs are 1600, 800, 400, 200, 100, 50, 25, 12.5, 6.25, 3.12, 1.56, and 0.78 Hz. Note, setting a value between the listed ones will round *downwards*. """ res = self.generic_setter(odr, BMX160_GYRO_ODR_VALUES, BMX160_GYRO_ODR_CONSTANTS, BMX160_GYRO_CONFIG_ADDR, "gyroscope odr") if res != None: self._gyro_odr = res[1] @property def gyro_powermode(self): return self._gyro_powermode @gyro_powermode.setter def gyro_powermode(self, powermode): """ Set the power mode of the gyroscope. Unlike other setters, this one has to directly take the BMX160-const associated with the power mode. The possible modes are: `BMX160_GYRO_SUSPEND_MODE` `BMX160_GYRO_NORMAL_MODE` `BMX160_GYRO_FASTSTARTUP_MODE` """ if powermode not in BMX160_GYRO_MODES: print("Unknown gyroscope powermode: " + str(powermode)) return self.write_u8(BMX160_COMMAND_REG_ADDR, powermode) if int(self.query_error) == 0: self._gyro_powermode = powermode else: settingswarning("gyroscope power mode") # NOTE: this delay is a worst case. If we need repeated switching # we can choose the delay on a case-by-case basis. time.sleep(0.0081) ############## ACCELEROMETER SETTINGS ############## def init_accel(self): # BW doesn't have an interface yet # self.write_u8(BMX160_ACCEL_CONFIG_ADDR, BMX160_ACCEL_BW_NORMAL_AVG4) # self._accel_bwmode = BMX160_ACCEL_BW_NORMAL_AVG4 # These rely on the setters to do their magic. self.accel_range = BMX160_ACCEL_RANGE_8G self.accel_odr = 25 self.accel_powermode = BMX160_ACCEL_NORMAL_MODE @property def accel_range(self): return self._accel_range @accel_range.setter def accel_range(self, rangeconst): """ The input is expected to be the BMX160-constant associated with the range. deg/s | bmxconst value | bmxconst name ------------------------------------------------------ 2 | 3 | BMX160_ACCEL_RANGE_2G 4 | 5 | BMX160_ACCEL_RANGE_4G 8 | 8 | BMX160_ACCEL_RANGE_8G 16 | 12 | BMX160_ACCEL_RANGE_16G ex: bmx.accel_range = BMX160_ACCEL_RANGE_4G equivalent to: bmx.accel_range = 5 """ if rangeconst in BMX160_ACCEL_RANGE_CONSTANTS: self._accel_range = rangeconst # read out the value to see if it changed successfully rangeconst = self._accel_range # convert to 0-3 range for indexing ind = rangeconst >> 2 val = BMX160_ACCEL_RANGE_VALUES[ind] self.ACC_SCALAR = (val / 32768.0) / g_TO_METERS_PER_SECOND_SQUARED else: pass @property def accel_odr(self): return self._accel_odr @accel_odr.setter def accel_odr(self, odr): res = self.generic_setter(odr, BMX160_ACCEL_ODR_VALUES, BMX160_ACCEL_ODR_CONSTANTS, BMX160_ACCEL_CONFIG_ADDR, "accelerometer odr") if res != None: self._accel_odr = res[1] @property def accel_powermode(self): return self._accel_powermode @accel_powermode.setter def accel_powermode(self, powermode): """ Set the power mode of the accelerometer. Unlike other setters, this one has to directly take the BMX160-const associated with the power mode. The possible modes are: `BMI160_ACCEL_NORMAL_MODE` `BMI160_ACCEL_LOWPOWER_MODE` `BMI160_ACCEL_SUSPEND_MODE` """ if powermode not in BMX160_ACCEL_MODES: print("Unknown accelerometer power mode: " + str(powermode)) return self.write_u8(BMX160_COMMAND_REG_ADDR, powermode) if int(self.query_error) == 0: self._accel_powermode = powermode else: settingswarning("accelerometer power mode") # NOTE: this delay is a worst case. If we need repeated switching # we can choose the delay on a case-by-case basis. time.sleep(0.005) ############## MAGENTOMETER SETTINGS ############## def init_mag(self): # see pg 25 of: https://ae-bst.resource.bosch.com/media/_tech/media/datasheets/BST-BMX160-DS000.pdf self.write_u8(BMX160_COMMAND_REG_ADDR, BMX160_MAG_NORMAL_MODE) time.sleep(0.00065) # datasheet says wait for 650microsec self.write_u8(BMX160_MAG_IF_0_ADDR, 0x80) # put mag into sleep mode self.write_u8(BMX160_MAG_IF_3_ADDR, 0x01) self.write_u8(BMX160_MAG_IF_2_ADDR, 0x4B) # set x-y to regular power preset self.write_u8(BMX160_MAG_IF_3_ADDR, 0x04) self.write_u8(BMX160_MAG_IF_2_ADDR, 0x51) # set z to regular preset self.write_u8(BMX160_MAG_IF_3_ADDR, 0x0E) self.write_u8(BMX160_MAG_IF_2_ADDR, 0x52) # prepare MAG_IF[1-3] for mag_if data mode self.write_u8(BMX160_MAG_IF_3_ADDR, 0x02) self.write_u8(BMX160_MAG_IF_2_ADDR, 0x4C) self.write_u8(BMX160_MAG_IF_1_ADDR, 0x42) # Set ODR to 25 Hz self.write_u8(BMX160_MAG_ODR_ADDR, BMX160_MAG_ODR_25HZ) self.write_u8(BMX160_MAG_IF_0_ADDR, 0x00) # put in low power mode. self.write_u8(BMX160_COMMAND_REG_ADDR, BMX160_MAG_LOWPOWER_MODE) time.sleep(0.1) # takes this long to warm up (empirically) @property def mag_powermode(self): return self._mag_powermode @mag_powermode.setter def mag_powermode(self, powermode): """ Set the power mode of the magnetometer. Unlike other setters, this one has to directly take the BMX160-const associated with the power mode. The possible modes are: `BMX160_` `BMX160_` `BMX160_` """ # if powermode not in BMX160_MAG_: # print("Unknown gyroscope powermode: " + str(powermode)) # return self.write_u8(BMX160_COMMAND_REG_ADDR, powermode) if int(self.query_error) == 0: self._mag_powermode = powermode else: settingswarning("mag power mode") # NOTE: this delay is a worst case. If we need repeated switching # we can choose the delay on a case-by-case basis. time.sleep(0.001) ## UTILS: def generic_setter(self, desired, possible_values, bmx_constants, config_addr, warning_interp = ""): i = find_nearest_valid(desired, possible_values) rounded = possible_values[i] bmxconst = bmx_constants[i] self.write_u8(config_addr, bmxconst) e = self.error_code if e == BMX160_OK: return (i, rounded) else: settingswarning(warning_interp)
class DeviceControl: # pylint: disable-msg=too-few-public-methods def __init__(self, i2c): self.i2c_device = i2c # self.i2c_device required by Struct class tuple_of_numbers = Struct(A_DEVICE_REGISTER, "<HH") # 2 16-bit numbers
class L3GD20_I2C(L3GD20): """ Driver for L3GD20 Gyroscope using I2C communications :param ~busio.I2C i2c: The I2C bus the device is connected to :param int rng: range value. Defaults to :const:`0x68` :param int rate: rate value. Defaults to :const:`L3DS20_RATE_100HZ` **Quickstart: Importing and using the device** Here is an example of using the :class:`L3GD20_I2C` class. First you will need to import the libraries to use the sensor .. code-block:: python import board import adafruit_l3gd20 Once this is done you can define your `board.I2C` object and define your sensor object .. code-block:: python i2c = board.I2C() # uses board.SCL and board.SDA sensor = adafruit_l3gd20.L3GD20_I2C(i2c) Now you have access to the :attr:`gyro` attribute .. code-block:: python gyro_data = sensor.gyro """ gyro_raw = Struct(_L3GD20_REGISTER_OUT_X_L_X80, "<hhh") """Gives the raw gyro readings, in units of rad/s.""" def __init__(self, i2c, rng=L3DS20_RANGE_250DPS, address=0x6B, rate=L3DS20_RATE_100HZ): from adafruit_bus_device import ( # pylint: disable=import-outside-toplevel i2c_device, ) self.i2c_device = i2c_device.I2CDevice(i2c, address) self.buffer = bytearray(2) super().__init__(rng, rate) def write_register(self, register, value): """ Update a register with a byte value :param int register: which device register to write :param value: a byte to write """ self.buffer[0] = register self.buffer[1] = value with self.i2c_device as i2c: i2c.write(self.buffer) def read_register(self, register): """ Returns a byte value from a register :param register: the register to read a byte """ self.buffer[0] = register with self.i2c_device as i2c: i2c.write_then_readinto(self.buffer, self.buffer, out_end=1, in_start=1) return self.buffer[1]
class LSM6DSOX: #pylint: disable=too-many-instance-attributes """Driver for the LSM6DSOX 6-axis accelerometer and gyroscope. :param ~busio.I2C i2c_bus: The I2C bus the LSM6DSOX is connected to. :param address: The I2C slave address of the sensor """ #ROUnaryStructs: _chip_id = ROUnaryStruct(_LSM6DSOX_WHOAMI, "<b") _temperature = ROUnaryStruct(_LSM6DSOX_OUT_TEMP_L, "<h") #RWBits: _ois_ctrl_from_ui = RWBit(_LSM6DSOX_FUNC_CFG_ACCESS, 0) _shub_reg_access = RWBit(_LSM6DSOX_FUNC_CFG_ACCESS, 6) _func_cfg_access = RWBit(_LSM6DSOX_FUNC_CFG_ACCESS, 7) _sdo_pu_en = RWBit(_LSM6DSOX_PIN_CTRL, 6) _ois_pu_dis = RWBit(_LSM6DSOX_PIN_CTRL, 7) _spi2_read_en = RWBit(_LSM6DSOX_UI_INT_OIS, 3) _den_lh_ois = RWBit(_LSM6DSOX_UI_INT_OIS, 5) _lvl2_ois = RWBit(_LSM6DSOX_UI_INT_OIS, 6) _int2_drdy_ois = RWBit(_LSM6DSOX_UI_INT_OIS, 7) _lpf_xl = RWBit(_LSM6DSOX_CTRL1_XL, 1) _accel_range = RWBits(2, _LSM6DSOX_CTRL1_XL, 2) _accel_data_rate = RWBits(4, _LSM6DSOX_CTRL1_XL, 4) _gyro_data_rate = RWBits(4, _LSM6DSOX_CTRL2_G, 4) _gyro_range = RWBits(2, _LSM6DSOX_CTRL2_G, 2) _gyro_range_125dps = RWBit(_LSM6DSOX_CTRL2_G, 1) _sw_reset = RWBit(_LSM6DSOX_CTRL3_C, 0) _if_inc = RWBit(_LSM6DSOX_CTRL3_C, 2) _sim = RWBit(_LSM6DSOX_CTRL3_C, 3) _pp_od = RWBit(_LSM6DSOX_CTRL3_C, 4) _h_lactive = RWBit(_LSM6DSOX_CTRL3_C, 5) _bdu = RWBit(_LSM6DSOX_CTRL3_C, 6) _boot = RWBit(_LSM6DSOX_CTRL3_C, 7) _st_xl = RWBits(2, _LSM6DSOX_CTRL_5_C, 0) _st_g = RWBits(2, _LSM6DSOX_CTRL_5_C, 2) _rounding_status = RWBit(_LSM6DSOX_CTRL_5_C, 4) _rounding = RWBits(2, _LSM6DSOX_CTRL_5_C, 5) _xl_ulp_en = RWBit(_LSM6DSOX_CTRL_5_C, 7) _aux_sens_on = RWBits(2, _LSM6DSOX_MASTER_CONFIG, 0) _master_on = RWBit(_LSM6DSOX_MASTER_CONFIG, 2) _shub_pu_en = RWBit(_LSM6DSOX_MASTER_CONFIG, 3) _pass_through_mode = RWBit(_LSM6DSOX_MASTER_CONFIG, 4) _start_config = RWBit(_LSM6DSOX_MASTER_CONFIG, 5) _write_once = RWBit(_LSM6DSOX_MASTER_CONFIG, 6) _rst_master_regs = RWBit(_LSM6DSOX_MASTER_CONFIG, 7) _i3c_disable = RWBit(_LSM6DSOX_CTRL9_XL, 1) _raw_temp = ROUnaryStruct(_LSM6DSOX_OUT_TEMP_L, "<h") _raw_accel_data = Struct(_LSM6DSOX_OUTX_L_A, "<hhh") _raw_gyro_data = Struct(_LSM6DSOX_OUTX_L_G, "<hhh") def __init__(self, i2c_bus, address=_LSM6DSOX_DEFAULT_ADDRESS): self.i2c_device = i2c_device.I2CDevice(i2c_bus, address) if self._chip_id not in [_LSM6DSOX_CHIP_ID, _ISM330DHCX_CHIP_ID]: raise RuntimeError( "Failed to find LSM6DSOX or ISM330DHCX - check your wiring!") self.reset() self._bdu = True self._i3c_disable = True self._if_inc = True self._accel_data_rate = Rate.RATE_104_HZ #pylint: disable=no-member self._gyro_data_rate = Rate.RATE_104_HZ #pylint: disable=no-member self._accel_range = AccelRange.RANGE_4G #pylint: disable=no-member self._cached_accel_range = self._accel_range self._gyro_range = GyroRange.RANGE_250_DPS #pylint: disable=no-member self._cached_gyro_range = self._gyro_range def reset(self): "Resets the sensor's configuration into an initial state" self._sw_reset = True while self._sw_reset: sleep(0.001) self._boot = True while self._boot: sleep(0.001) @property def is_lsm6dsox(self): """Returns `True` if the connected sensor is an LSM6DSOX, `False` if not, it's an ICM330DHCX""" return self._chip_id is _LSM6DSOX_CHIP_ID @property def acceleration(self): """The x, y, z acceleration values returned in a 3-tuple and are in m / s ^ 2.""" raw_accel_data = self._raw_accel_data x = self._scale_xl_data(raw_accel_data[0]) y = self._scale_xl_data(raw_accel_data[1]) z = self._scale_xl_data(raw_accel_data[2]) return (x, y, z) @property def gyro(self): """The x, y, z angular velocity values returned in a 3-tuple and are in degrees / second""" raw_gyro_data = self._raw_gyro_data x = self._scale_gyro_data(raw_gyro_data[0]) y = self._scale_gyro_data(raw_gyro_data[1]) z = self._scale_gyro_data(raw_gyro_data[2]) return (x, y, z) def _scale_xl_data(self, raw_measurement): return raw_measurement * AccelRange.lsb[ self._cached_accel_range] * _MILLI_G_TO_ACCEL def _scale_gyro_data(self, raw_measurement): return raw_measurement * GyroRange.lsb[self._cached_gyro_range] / 1000 @property def accelerometer_range(self): """Adjusts the range of values that the sensor can measure, from +/- 2G to +/-16G Note that larger ranges will be less accurate. Must be an `AccelRange`""" return self._cached_accel_range #pylint: disable=no-member @accelerometer_range.setter def accelerometer_range(self, value): if not AccelRange.is_valid(value): raise AttributeError("range must be an `AccelRange`") self._accel_range = value self._cached_accel_range = value sleep(.2) # needed to let new range settle @property def gyro_range(self): """Adjusts the range of values that the sensor can measure, from 125 Degrees/second to 4000 degrees/s. Note that larger ranges will be less accurate. Must be a `GyroRange`. 4000 DPS is only available for the ISM330DHCX""" return self._cached_gyro_range @gyro_range.setter def gyro_range(self, value): if not GyroRange.is_valid(value): raise AttributeError("range must be a `GyroRange`") if value is GyroRange.RANGE_4000_DPS and self.is_lsm6dsox: raise AttributeError("4000 DPS is only available for ISM330DHCX") if value is GyroRange.RANGE_125_DPS: self._gyro_range_125dps = True self._gyro_range_4000dps = False elif value is GyroRange.RANGE_4000_DPS: self._gyro_range_125dps = False self._gyro_range_4000dps = True else: self._gyro_range_125dps = False self._gyro_range_4000dps = True self._gyro_range = value self._cached_gyro_range = value sleep(.2) # needed to let new range settle #pylint: enable=no-member @property def accelerometer_data_rate(self): """Select the rate at which the accelerometer takes measurements. Must be a `Rate`""" return self._accel_data_rate @accelerometer_data_rate.setter def accelerometer_data_rate(self, value): if not Rate.is_valid(value): raise AttributeError("accelerometer_data_rate must be a `Rate`") self._accel_data_rate = value # sleep(.2) # needed to let new range settle @property def gyro_data_rate(self): """Select the rate at which the gyro takes measurements. Must be a `Rate`""" return self._gyro_data_rate @gyro_data_rate.setter def gyro_data_rate(self, value): if not Rate.is_valid(value): raise AttributeError("gyro_data_rate must be a `Rate`") self._gyro_data_rate = value
class ICM20649: # pylint:disable=too-many-instance-attributes """Library for the ST ICM-20649 Wide-Range 6-DoF Accelerometer and Gyro. :param ~busio.I2C i2c_bus: The I2C bus the ICM20649 is connected to. :param address: The I2C slave address of the sensor """ # Bank 0 _device_id = ROUnaryStruct(_ICM20649_WHO_AM_I, "<B") _bank = RWBits(2, _ICM20649_REG_BANK_SEL, 4) _reset = RWBit(_ICM20649_PWR_MGMT_1, 7) _sleep = RWBit(_ICM20649_PWR_MGMT_1, 6) _clock_source = RWBits(3, _ICM20649_PWR_MGMT_1, 0) _raw_accel_data = Struct(_ICM20649_ACCEL_XOUT_H, ">hhh") _raw_gyro_data = Struct(_ICM20649_GYRO_XOUT_H, ">hhh") # Bank 2 _gyro_range = RWBits(2, _ICM20649_GYRO_CONFIG_1, 1) _accel_dlpf_enable = RWBits(1, _ICM20649_ACCEL_CONFIG_1, 0) _accel_range = RWBits(2, _ICM20649_ACCEL_CONFIG_1, 1) _accel_dlpf_config = RWBits(3, _ICM20649_ACCEL_CONFIG_1, 3) # this value is a 12-bit register spread across two bytes, big-endian first _accel_rate_divisor = UnaryStruct(_ICM20649_ACCEL_SMPLRT_DIV_1, ">H") _gyro_rate_divisor = UnaryStruct(_ICM20649_GYRO_SMPLRT_DIV, ">B") def __init__(self, i2c_bus, address=_ICM20649_DEFAULT_ADDRESS): self.i2c_device = i2c_device.I2CDevice(i2c_bus, address) if self._device_id != _ICM20649_DEVICE_ID: raise RuntimeError("Failed to find ICM20649 - check your wiring!") self.reset() self._bank = 0 self._sleep = False self._bank = 2 self._accel_range = AccelRange.RANGE_8G # pylint: disable=no-member self._cached_accel_range = self._accel_range # TODO: CV-ify self._accel_dlpf_config = 3 # 1.125 kHz/(1+ACCEL_SMPLRT_DIV[11:0]), # 1125Hz/(1+20) = 53.57Hz self._accel_rate_divisor = 20 # writeByte(ICM20649_ADDR,GYRO_CONFIG_1, gyroConfig); self._gyro_range = GyroRange.RANGE_500_DPS # pylint: disable=no-member sleep(0.100) self._cached_gyro_range = self._gyro_range # //ORD = 1100Hz/(1+10) = 100Hz self._gyro_rate_divisor = 0x0A # //reset to register bank 0 self._bank = 0 def reset(self): """Resets the internal registers and restores the default settings""" self._reset = True while self._reset: sleep(0.001) @property def acceleration(self): """The x, y, z acceleration values returned in a 3-tuple and are in m / s ^ 2.""" raw_accel_data = self._raw_accel_data x = self._scale_xl_data(raw_accel_data[0]) y = self._scale_xl_data(raw_accel_data[1]) z = self._scale_xl_data(raw_accel_data[2]) return (x, y, z) @property def gyro(self): """The x, y, z angular velocity values returned in a 3-tuple and are in degrees / second""" raw_gyro_data = self._raw_gyro_data x = self._scale_gyro_data(raw_gyro_data[0]) y = self._scale_gyro_data(raw_gyro_data[1]) z = self._scale_gyro_data(raw_gyro_data[2]) return (x, y, z) def _scale_xl_data(self, raw_measurement): return raw_measurement / AccelRange.lsb[ self._cached_accel_range] * G_TO_ACCEL def _scale_gyro_data(self, raw_measurement): return raw_measurement / GyroRange.lsb[self._cached_gyro_range] @property def accelerometer_range(self): """Adjusts the range of values that the sensor can measure, from +/- 4G to +/-30G Note that larger ranges will be less accurate. Must be an `AccelRange`""" return self._cached_accel_range @accelerometer_range.setter def accelerometer_range(self, value): # pylint: disable=no-member if not AccelRange.is_valid(value): raise AttributeError("range must be an `AccelRange`") self._bank = 2 self._accel_range = value self._cached_accel_range = value self._bank = 0 @property def gyro_range(self): """Adjusts the range of values that the sensor can measure, from 500 Degrees/second to 4000 degrees/s. Note that larger ranges will be less accurate. Must be a `GyroRange`""" return self._cached_gyro_range @gyro_range.setter def gyro_range(self, value): if not GyroRange.is_valid(value): raise AttributeError("range must be a `GyroRange`") self._bank = 2 self._gyro_range = value self._cached_gyro_range = value self._bank = 0 sleep(0.100) # needed to let new range settle @property def accelerometer_data_rate_divisor(self): """The divisor for the rate at which accelerometer measurements are taken in Hz Note: The data rates are set indirectly by setting a rate divisor according to the following formula: ``accelerometer_data_rate = 1125/(1+divisor)`` This function sets the raw rate divisor. """ self._bank = 2 raw_rate_divisor = self._accel_rate_divisor self._bank = 0 # rate_hz = 1125/(1+raw_rate_divisor) return raw_rate_divisor @accelerometer_data_rate_divisor.setter def accelerometer_data_rate_divisor(self, value): # check that value <= 4095 self._bank = 2 self._accel_rate_divisor = value self._bank = 0 @property def gyro_data_rate_divisor(self): """The divisor for the rate at which gyro measurements are taken in Hz Note: The data rates are set indirectly by setting a rate divisor according to the following formula: ``gyro_data_rate = 1100/(1+divisor)`` This function sets the raw rate divisor. """ self._bank = 2 raw_rate_divisor = self._gyro_rate_divisor self._bank = 0 # rate_hz = 1100/(1+raw_rate_divisor) return raw_rate_divisor @gyro_data_rate_divisor.setter def gyro_data_rate_divisor(self, value): # check that value <= 255 self._bank = 2 self._gyro_rate_divisor = value self._bank = 0 def _accel_rate_calc(self, divisor): # pylint:disable=no-self-use return 1125 / (1 + divisor) def _gyro_rate_calc(self, divisor): # pylint:disable=no-self-use return 1100 / (1 + divisor) @property def accelerometer_data_rate(self): """The rate at which accelerometer measurements are taken in Hz Note: The data rates are set indirectly by setting a rate divisor according to the following formula: ``accelerometer_data_rate = 1125/(1+divisor)`` This function does the math to find the divisor from a given rate but it will not be exactly as specified. """ return self._accel_rate_calc(self.accelerometer_data_rate_divisor) @accelerometer_data_rate.setter def accelerometer_data_rate(self, value): if value < self._accel_rate_calc( 4095) or value > self._accel_rate_calc(0): raise AttributeError( "Accelerometer data rate must be between 0.27 and 1125.0") self.accelerometer_data_rate_divisor = value @property def gyro_data_rate(self): """The rate at which gyro measurements are taken in Hz Note: The data rates are set indirectly by setting a rate divisor according to the following formula: ``gyro_data_rate = 1100/(1+divisor)`` This function does the math to find the divisor from a given rate but it will not be exactly as specified. """ return self._gyro_rate_calc(self.gyro_data_rate_divisor) @gyro_data_rate.setter def gyro_data_rate(self, value): if value < self._gyro_rate_calc(4095) or value > self._gyro_rate_calc( 0): raise AttributeError( "Gyro data rate must be between 4.30 and 1100.0") divisor = round(((1125.0 - value) / value)) self.gyro_data_rate_divisor = divisor
class LSM6DS: # pylint: disable=too-many-instance-attributes """Driver for the LSM6DSOX 6-axis accelerometer and gyroscope. :param ~busio.I2C i2c_bus: The I2C bus the LSM6DSOX is connected to. :param address: The I2C slave address of the sensor """ # ROUnaryStructs: _chip_id = ROUnaryStruct(_LSM6DS_WHOAMI, "<b") # Structs _raw_accel_data = Struct(_LSM6DS_OUTX_L_A, "<hhh") _raw_gyro_data = Struct(_LSM6DS_OUTX_L_G, "<hhh") # RWBits: _accel_range = RWBits(2, _LSM6DS_CTRL1_XL, 2) _accel_data_rate = RWBits(4, _LSM6DS_CTRL1_XL, 4) _gyro_data_rate = RWBits(4, _LSM6DS_CTRL2_G, 4) _gyro_range = RWBits(2, _LSM6DS_CTRL2_G, 2) _gyro_range_125dps = RWBit(_LSM6DS_CTRL2_G, 1) _gyro_range_4000dps = RWBit(_LSM6DS_CTRL2_G, 0) _sw_reset = RWBit(_LSM6DS_CTRL3_C, 0) _bdu = RWBit(_LSM6DS_CTRL3_C, 6) _high_pass_filter = RWBits(2, _LSM6DS_CTRL8_XL, 5) _i3c_disable = RWBit(_LSM6DS_CTRL9_XL, 1) _pedometer_reset = RWBit(_LSM6DS_CTRL10_C, 1) _func_enable = RWBit(_LSM6DS_CTRL10_C, 2) _ped_enable = RWBit(_LSM6DS_TAP_CFG, 6) CHIP_ID = None def __init__(self, i2c_bus, address=_LSM6DS_DEFAULT_ADDRESS): self._cached_accel_range = None self._cached_gyro_range = None self.i2c_device = i2c_device.I2CDevice(i2c_bus, address) if self.CHIP_ID is None: raise AttributeError( "LSM6DS Parent Class cannot be directly instantiated") if self._chip_id != self.CHIP_ID: raise RuntimeError("Failed to find %s - check your wiring!" % self.__class__.__name__) self.reset() self._bdu = True self.accelerometer_data_rate = Rate.RATE_104_HZ # pylint: disable=no-member self.gyro_data_rate = Rate.RATE_104_HZ # pylint: disable=no-member self.accelerometer_range = AccelRange.RANGE_4G # pylint: disable=no-member self.gyro_range = GyroRange.RANGE_250_DPS # pylint: disable=no-member def reset(self): "Resets the sensor's configuration into an initial state" self._sw_reset = True while self._sw_reset: sleep(0.001) @property def acceleration(self): """The x, y, z acceleration values returned in a 3-tuple and are in m / s ^ 2.""" raw_accel_data = self._raw_accel_data x = self._scale_xl_data(raw_accel_data[0]) y = self._scale_xl_data(raw_accel_data[1]) z = self._scale_xl_data(raw_accel_data[2]) return (x, y, z) @property def gyro(self): """The x, y, z angular velocity values returned in a 3-tuple and are in radians / second""" raw_gyro_data = self._raw_gyro_data x, y, z = [radians(self._scale_gyro_data(i)) for i in raw_gyro_data] return (x, y, z) def _scale_xl_data(self, raw_measurement): return (raw_measurement * AccelRange.lsb[self._cached_accel_range] * _MILLI_G_TO_ACCEL) def _scale_gyro_data(self, raw_measurement): return raw_measurement * GyroRange.lsb[self._cached_gyro_range] / 1000 @property def accelerometer_range(self): """Adjusts the range of values that the sensor can measure, from +/- 2G to +/-16G Note that larger ranges will be less accurate. Must be an `AccelRange`""" return self._cached_accel_range # pylint: disable=no-member @accelerometer_range.setter def accelerometer_range(self, value): if not AccelRange.is_valid(value): raise AttributeError("range must be an `AccelRange`") self._accel_range = value self._cached_accel_range = value sleep(0.2) # needed to let new range settle @property def gyro_range(self): """Adjusts the range of values that the sensor can measure, from 125 Degrees/second to 4000 degrees/s. Note that larger ranges will be less accurate. Must be a `GyroRange`. 4000 DPS is only available for the ISM330DHCX""" return self._cached_gyro_range @gyro_range.setter def gyro_range(self, value): if not GyroRange.is_valid(value): raise AttributeError("range must be a `GyroRange`") if value is GyroRange.RANGE_4000_DPS and not isinstance( self, ISM330DHCX): raise AttributeError("4000 DPS is only available for ISM330DHCX") if value is GyroRange.RANGE_125_DPS: self._gyro_range_125dps = True self._gyro_range_4000dps = False elif value is GyroRange.RANGE_4000_DPS: self._gyro_range_125dps = False self._gyro_range_4000dps = True else: self._gyro_range_125dps = False self._gyro_range_4000dps = False self._gyro_range = value self._cached_gyro_range = value sleep(0.2) # needed to let new range settle # pylint: enable=no-member @property def accelerometer_data_rate(self): """Select the rate at which the accelerometer takes measurements. Must be a `Rate`""" return self._accel_data_rate @accelerometer_data_rate.setter def accelerometer_data_rate(self, value): if not Rate.is_valid(value): raise AttributeError("accelerometer_data_rate must be a `Rate`") self._accel_data_rate = value # sleep(.2) # needed to let new range settle @property def gyro_data_rate(self): """Select the rate at which the gyro takes measurements. Must be a `Rate`""" return self._gyro_data_rate @gyro_data_rate.setter def gyro_data_rate(self, value): if not Rate.is_valid(value): raise AttributeError("gyro_data_rate must be a `Rate`") self._gyro_data_rate = value # sleep(.2) # needed to let new range settle @property def pedometer_enable(self): """ Whether the pedometer function on the accelerometer is enabled""" return self._ped_enable and self._func_enable @pedometer_enable.setter def pedometer_enable(self, enable): self._ped_enable = enable self._func_enable = enable self._pedometer_reset = enable @property def high_pass_filter(self): """The high pass filter applied to accelerometer data""" return self._high_pass_filter @high_pass_filter.setter def high_pass_filter(self, value): if not AccelHPF.is_valid(value): raise AttributeError("range must be an `AccelHPF`") self._high_pass_filter = value