Beispiel #1
0
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]
Beispiel #2
0
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]
Beispiel #3
0
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
Beispiel #5
0
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
Beispiel #6
0
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
Beispiel #8
0
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
Beispiel #9
0
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
Beispiel #11
0
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]
Beispiel #12
0
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
Beispiel #13
0
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