class PCA9685:
    """
    Initialise the PCA9685 chip at ``address`` on ``i2c_bus``.

    The internal reference clock is 25mhz but may vary slightly with environmental conditions and
    manufacturing variances. Providing a more precise ``reference_clock_speed`` can improve the
    accuracy of the frequency and duty_cycle computations. See the ``calibration.py`` example for
    how to derive this value by measuring the resulting pulse widths.

    :param ~busio.I2C i2c_bus: The I2C bus which the PCA9685 is connected to.
    :param int address: The I2C address of the PCA9685.
    :param int reference_clock_speed: The frequency of the internal reference clock in Hertz.
    """
    # Registers:
    mode1_reg = UnaryStruct(0x00, '<B')
    prescale_reg = UnaryStruct(0xFE, '<B')
    pwm_regs = StructArray(0x06, '<HH', 16)

    def __init__(self,
                 i2c_bus,
                 *,
                 address=0x40,
                 reference_clock_speed=25000000):
        self.i2c_device = i2c_device.I2CDevice(i2c_bus, address)
        self.channels = PCAChannels(self)
        """Sequence of 16 `PWMChannel` objects. One for each channel."""
        self.reference_clock_speed = reference_clock_speed
        """The reference clock speed in Hz."""
        self.reset()

    def reset(self):
        """Reset the chip."""
        self.mode1_reg = 0x00  # Mode1

    @property
    def frequency(self):
        """The overall PWM frequency in Hertz."""
        return self.reference_clock_speed / 4096 / self.prescale_reg

    @frequency.setter
    def frequency(self, freq):
        prescale = int(self.reference_clock_speed / 4096.0 / freq + 0.5)
        if prescale < 3:
            raise ValueError("PCA9685 cannot output at the given frequency")
        old_mode = self.mode1_reg  # Mode 1
        self.mode1_reg = (old_mode & 0x7F) | 0x10  # Mode 1, sleep
        self.prescale_reg = prescale  # Prescale
        self.mode1_reg = old_mode  # Mode 1
        time.sleep(0.005)
        self.mode1_reg = old_mode | 0xa1  # Mode 1, autoincrement on

    def __enter__(self):
        return self

    def __exit__(self, exception_type, exception_value, traceback):
        self.deinit()

    def deinit(self):
        """Stop using the pca9685."""
        self.reset()
Example #2
0
class DS3502:
    """Driver for the DS3502 I2C Digital Potentiometer.

    :param ~busio.I2C i2c_bus: The I2C bus the DS3502 is connected to.
    :param address: The I2C device address for the sensor. Default is ``0x40``.
    """

    def __init__(self, i2c_bus, address=0x28):
        self.i2c_device = i2cdevice.I2CDevice(i2c_bus, address)

        # set to mode 1 on init to not write to the IVR every time you set
        self._write_only_to_wiper = True

    _wiper = UnaryStruct(_REG_WIPER, ">B")
    _write_only_to_wiper = RWBit(_REG_CONTROL, 7)

    @property
    def wiper(self):
        """The value of the potentionmeter's wiper.

        :param wiper_value: The value from 0-127 to set the wiper to.
        """
        return self._wiper

    @wiper.setter
    def wiper(self, value):
        if value < 0 or value > 127:
            raise ValueError("wiper must be from 0-127")
        self._wiper = value

    def set_default(self, default):
        """Sets the wiper's default value and current value to the given value

        :param new_default: The value from 0-127 to set as the wiper's default.
        """
        self._write_only_to_wiper = False
        self.wiper = default
        sleep(0.1)  # wait for write to eeprom to finish
        self._write_only_to_wiper = True
Example #3
0
class VEML7700:
    """Driver for the VEML7700 ambient light sensor.

    :param busio.I2C i2c_bus: The I2C bus the VEML7700 is connected to.

    """

    # Ambient light sensor gain settings
    ALS_GAIN_1 = const(0x0)
    ALS_GAIN_2 = const(0x1)
    ALS_GAIN_1_8 = const(0x2)
    ALS_GAIN_1_4 = const(0x3)

    # Ambient light integration time settings

    ALS_25MS = const(0xC)
    ALS_50MS = const(0x8)
    ALS_100MS = const(0x0)
    ALS_200MS = const(0x1)
    ALS_400MS = const(0x2)
    ALS_800MS = const(0x3)

    # Gain value integers
    gain_values = {
        ALS_GAIN_2: 2,
        ALS_GAIN_1: 1,
        ALS_GAIN_1_4: 0.25,
        ALS_GAIN_1_8: 0.125,
    }

    # Integration time value integers
    integration_time_values = {
        ALS_25MS: 25,
        ALS_50MS: 50,
        ALS_100MS: 100,
        ALS_200MS: 200,
        ALS_400MS: 400,
        ALS_800MS: 800,
    }

    # ALS - Ambient light sensor high resolution output data
    light = ROUnaryStruct(0x04, "<H")
    """Ambient light data.

    This example prints the ambient light data. Cover the sensor to see the values change.

    .. code-block:: python

        import time
        import board
        import busio
        import adafruit_veml7700

        i2c = busio.I2C(board.SCL, board.SDA)
        veml7700 = adafruit_veml7700.VEML7700(i2c)

        while True:
            print("Ambient light:", veml7700.light)
            time.sleep(0.1)
    """

    # WHITE - White channel output data
    white = ROUnaryStruct(0x05, "<H")
    """White light data.

    This example prints the white light data. Cover the sensor to see the values change.

    .. code-block:: python

        import time
        import board
        import busio
        import adafruit_veml7700

        i2c = busio.I2C(board.SCL, board.SDA)
        veml7700 = adafruit_veml7700.VEML7700(i2c)

        while True:
            print("White light:", veml7700.white)
            time.sleep(0.1)
    """

    # ALS_CONF_0 - ALS gain, integration time, interrupt and shutdown.
    light_shutdown = RWBit(0x00, 0, register_width=2)
    """Ambient light sensor shutdown. When ``True``, ambient light sensor is disabled."""
    light_interrupt = RWBit(0x00, 1, register_width=2)
    """Enable interrupt. ``True`` to enable, ``False`` to disable."""
    light_gain = RWBits(2, 0x00, 11, register_width=2)
    """Ambient light gain setting. Gain settings are 2, 1, 1/4 and 1/8. Settings options are:
    ALS_GAIN_2, ALS_GAIN_1, ALS_GAIN_1_4, ALS_GAIN_1_8.

    This example sets the ambient light gain to 2 and prints the ambient light sensor data.

    .. code-block:: python

        import time
        import board
        import busio
        import adafruit_veml7700

        i2c = busio.I2C(board.SCL, board.SDA)
        veml7700 = adafruit_vcnl4040.VCNL4040(i2c)

        veml7700.light_gain = veml7700.ALS_GAIN_2

        while True:
            print("Ambient light:", veml7700.light)
            time.sleep(0.1)

    """
    light_integration_time = RWBits(4, 0x00, 6, register_width=2)
    """Ambient light integration time setting. Longer time has higher sensitivity. Can be:
    ALS_25MS, ALS_50MS, ALS_100MS, ALS_200MS, ALS_400MS, ALS_800MS.

    This example sets the ambient light integration time to 400ms and prints the ambient light
    sensor data.

    .. code-block:: python

        import time
        import board
        import busio
        import adafruit_veml7700

        i2c = busio.I2C(board.SCL, board.SDA)
        veml7700 = adafruit_vcnl4040.VCNL4040(i2c)

        veml7700.light_integration_time = veml7700.ALS_400MS

        while True:
            print("Ambient light:", veml7700.light)
            time.sleep(0.1)

    """

    # ALS_WH - ALS high threshold window setting
    light_high_threshold = UnaryStruct(0x01, "<H")
    """Ambient light sensor interrupt high threshold setting."""
    # ALS_WL - ALS low threshold window setting
    light_low_threshold = UnaryStruct(0x02, "<H")
    """Ambient light sensor interrupt low threshold setting."""
    # ALS_INT - ALS INT trigger event
    light_interrupt_high = ROBit(0x06, 14, register_width=2)
    """Ambient light high threshold interrupt flag. Triggered when high threshold exceeded."""
    light_interrupt_low = ROBit(0x06, 15, register_width=2)
    """Ambient light low threshold interrupt flag. Triggered when low threshold exceeded."""
    def __init__(self, i2c_bus, address=0x10):
        self.i2c_device = i2cdevice.I2CDevice(i2c_bus, address)
        self.light_shutdown = False  # Enable the ambient light sensor

    def integration_time_value(self):
        """Integration time value in integer form. Used for calculating ``resolution``."""
        integration_time = self.light_integration_time
        return self.integration_time_values[integration_time]

    def gain_value(self):
        """Gain value in integer form. Used for calculating ``resolution``."""
        gain = self.light_gain
        return self.gain_values[gain]

    def resolution(self):
        """Calculate the ``resolution`` necessary to calculate lux. Based on integration time and
        gain settings."""
        resolution_at_max = 0.0036
        gain_max = 2
        integration_time_max = 800

        if (self.gain_value() == gain_max
                and self.integration_time_value() == integration_time_max):
            return resolution_at_max
        return (resolution_at_max *
                (integration_time_max / self.integration_time_value()) *
                (gain_max / self.gain_value()))

    @property
    def lux(self):
        """Light value in lux.

        This example prints the light data in lux. Cover the sensor to see the values change.

        .. code-block:: python

            import time
            import board
            import busio
            import adafruit_veml7700

            i2c = busio.I2C(board.SCL, board.SDA)
            veml7700 = adafruit_veml7700.VEML7700(i2c)

            while True:
                print("Lux:", veml7700.lux)
                time.sleep(0.1)
        """
        return self.resolution() * self.light
Example #4
0
class LIS331:
    # pylint:disable=too-many-instance-attributes
    """Base class for the LIS331 family of 3-axis accelerometers.
    **Cannot be instantiated directly**

        :param ~busio.I2C i2c_bus: The I2C bus the LIS331 is connected to.
        :param address: The I2C slave address of the sensor

    """

    _chip_id = ROUnaryStruct(_LIS331_REG_WHOAMI, "<B")
    _mode_and_odr_bits = RWBits(5, _LIS331_REG_CTRL1, 3)
    _power_mode_bits = RWBits(3, _LIS331_REG_CTRL1, 5)
    _data_rate_lpf_bits = RWBits(2, _LIS331_REG_CTRL1, 3)
    _range_bits = RWBits(2, _LIS331_REG_CTRL4, 4)
    _raw_acceleration = ROByteArray((_LIS331_REG_OUT_X_L | 0x80), "<hhh", 6)

    _reference_value = UnaryStruct(_LIS331_REG_REFERENCE, "<b")
    _zero_hpf = ROUnaryStruct(_LIS331_REG_HP_FILTER_RESET, "<b")
    _hpf_mode_bits = RWBit(_LIS331_REG_CTRL2, 5)
    _hpf_enable_bit = RWBit(_LIS331_REG_CTRL2, 4)
    _hpf_cutoff = RWBits(2, _LIS331_REG_CTRL2, 0)

    def __init__(self, i2c_bus, address=_LIS331_DEFAULT_ADDRESS):
        if (not isinstance(self, LIS331HH)) and (not isinstance(
                self, H3LIS331)):
            raise RuntimeError(
                "Base class LIS331 cannot be instantiated directly. Use LIS331HH or H3LIS331"
            )
        self.i2c_device = i2c_device.I2CDevice(i2c_bus, address)
        if self._chip_id != _LIS331_CHIP_ID:
            raise RuntimeError("Failed to find %s - check your wiring!" %
                               self.__class__.__name__)
        self._range_class = None
        self.enable_hpf(False)

    @property
    def lpf_cutoff(self):
        """The frequency above which signals will be filtered out"""
        if self.mode == Mode.NORMAL:  # pylint: disable=no-member
            raise RuntimeError(
                "lpf_cuttoff cannot be read while a NORMAL data rate is in use"
            )
        return self._data_rate_lpf_bits

    @lpf_cutoff.setter
    def lpf_cutoff(self, cutoff_freq):
        if not Frequency.is_valid(cutoff_freq):
            raise AttributeError("lpf_cutoff must be a `Frequency`")

        if self.mode == Mode.NORMAL:  # pylint: disable=no-member
            raise RuntimeError(
                "lpf_cuttoff cannot be set while a NORMAL data rate is in use")

        self._data_rate_lpf_bits = cutoff_freq

    @property
    def hpf_reference(self):
        """The reference value to offset measurements when using the High-pass filter. To use,
        ``use_reference`` must be set to true when enabling the high-pass filter. The value
        is a signed 8-bit number from -128 to 127. The value of each increment of 1 depends on the
        currently set measurement range and is approximate:
        #pylint: disable=line-too-long
        +-------------------------------------------------------------+-------------------------------+
        | Range                                                       | Incremental value (LSB value) |
        +-------------------------------------------------------------+-------------------------------+
        | ``LIS331HHRange.RANGE_6G`` or ``H3LIS331Range.RANGE_100G``  | ~16mg                         |
        +-------------------------------------------------------------+-------------------------------+
        | ``LIS331HHRange.RANGE_12G`` or ``H3LIS331Range.RANGE_200G`` | ~31mg                         |
        +-------------------------------------------------------------+-------------------------------+
        | ``LIS331HHRange.RANGE_24G`` or ``H3LIS331Range.RANGE_400G`` | ~63mg                         |
        +-------------------------------------------------------------+-------------------------------+
        #pylint: enable=line-too-long
        """

        return self._reference_value

    @hpf_reference.setter
    def hpf_reference(self, reference_value):
        if reference_value < -128 or reference_value > 127:
            raise AttributeError("`hpf_reference` must be from -128 to 127")
        self._reference_value = reference_value

    def zero_hpf(self):
        """When the high-pass filter is enabled  with ``use_reference=False``, calling ``zero_hpf``
        will set all measurements to zero immediately, avoiding the normal settling time seen when
        using the high-pass filter without a ``hpf_reference``
        """
        self._zero_hpf  # pylint: disable=pointless-statement

    def enable_hpf(self,
                   enabled=True,
                   cutoff=RateDivisor.ODR_DIV_50,
                   use_reference=False):  # pylint: disable=no-member
        """Enable or disable the high-pass filter.

        :param enabled: Enable or disable the filter. Default is `True` to enable
        :param ~RateDivisor cutoff: A `RateDivisor` to set the high-pass cutoff frequency. Default\
        is ``RateDivisor.ODR_DIV_50``. See ``RateDivisor`` for more information
        :param use_reference: Determines if the filtered measurements are offset by a reference\
        value. Default is false.

    See section **4** of the LIS331DLH application note for more information `LIS331DLH application\
        note for more information <https://www.st.com/content/ccc/resource/technical/document/\
        application_note/b5/8e/58/69/cb/87/45/55/CD00215823.pdf/files/CD00215823.pdf/jcr:content/\
        translations/en.CD00215823.pdf>`_

        """
        self._hpf_mode_bits = use_reference
        self._hpf_cutoff = cutoff
        self._hpf_enable_bit = enabled

    @property
    def data_rate(self):
        """Select the rate at which the accelerometer takes measurements. Must be a `Rate`"""
        return self._cached_data_rate

    @data_rate.setter
    def data_rate(self, new_rate_bits):
        if not Rate.is_valid(new_rate_bits):
            raise AttributeError("data_rate must be a `Rate`")

        # to determine what to be set we'll look at the mode to so we don't overwrite the filter
        new_mode = self._mode_and_rate(new_rate_bits)[0]
        if new_mode == Mode.NORMAL:  # pylint: disable=no-member
            self._mode_and_odr_bits = new_rate_bits
        else:
            self._power_mode_bits = new_mode

        self._cached_data_rate = new_mode << 2 | new_rate_bits

    @property
    def mode(self):
        """The `Mode` power mode that the sensor is set to, as determined by the current
        `data_rate`. To set the mode, use `data_rate` and the approprite `Rate`"""
        mode_bits = self._mode_and_rate()[0]
        return mode_bits

    def _mode_and_rate(self, data_rate=None):
        if data_rate is None:
            data_rate = self._cached_data_rate

        pm_value = (data_rate & 0x1C) >> 2
        dr_value = data_rate & 0x3
        if pm_value is Mode.LOW_POWER:  # pylint: disable=no-member
            dr_value = 0
        return (pm_value, dr_value)

    @property
    def range(self):
        """Adjusts the range of values that the sensor can measure, Note that larger ranges will be
        less accurate. Must be a `H3LIS331Range` or `LIS331HHRange`"""
        return self._range_bits

    @range.setter
    def range(self, new_range):
        if not self._range_class.is_valid(new_range):  # pylint: disable=no-member
            raise AttributeError("range must be a `%s`" %
                                 self._range_class.__qualname__)
        self._range_bits = new_range
        self._cached_accel_range = new_range
        sleep(0.010)  # give time for the new rate to settle

    @property
    def acceleration(self):
        """The x, y, z acceleration values returned in a 3-tuple and are in m / s ^ 2."""

        raw_acceleration_bytes = self._raw_acceleration

        return (
            self._scale_acceleration(raw_acceleration_bytes[0]),
            self._scale_acceleration(raw_acceleration_bytes[1]),
            self._scale_acceleration(raw_acceleration_bytes[2]),
        )

    def _scale_acceleration(self, value):
        # The measurements are 12 bits left justified to preserve the sign bit
        # so we'll shift them back to get the real value
        right_justified = value >> 4
        lsb_value = self._range_class.lsb[self._cached_accel_range]
        return right_justified * lsb_value
Example #5
0
class BD3491FS:  # pylint: disable=too-many-instance-attributes
    """Driver for the Rohm BD3491FS audio processor

    :param ~busio.I2C i2c_bus: The I2C bus the BD3491FS is connected to.
    """

    _input_selector = UnaryStruct(_INPUT_SELECTOR, "<B")
    _input_gain = UnaryStruct(_INPUT_GAIN, "<B")
    _ch1_attenuation = UnaryStruct(_VOLUME_GAIN_CH1, "<B")
    _ch2_attenuation = UnaryStruct(_VOLUME_GAIN_CH2, "<B")
    _system_reset = UnaryStruct(_SYSTEM_RESET, "<B")

    def __init__(self, i2c_bus):
        self.i2c_device = i2cdevice.I2CDevice(i2c_bus, 0x41)
        self._current_active_input = 7  # mute
        self._current_input_gain = 0  # 0dB
        self._current_ch1_attenuation = 255  # muted
        self._current_ch2_attenuation = 255  # muted
        self.reset()

    def reset(self):
        """Reset the sensor, muting the input, reducting input gain to 0dB, and the output channnel
        attenuation to maximum"""
        self._reset = 0x81

    @property
    def active_input(self):
        """The currently selected input. Must be an ``Input``

        This example sets A1 and A2 to the active input pair.
        .. code-block:: python
        bd3491fs.active_input = adafruit_bd3491fs.Input.A
        """
        return self._current_active_input

    @active_input.setter
    def active_input(self, value):
        self._input_selector = value
        self._current_active_input = value

    @property
    def input_gain(self):
        """The gain applied to all inputs equally"
        This example sets the input gain to 10dB.
        .. code-block:: python
        bd3491fs.input_gain = adafruit_bd3491fs.Level.10_DB""
        """
        return self._current_input_gain

    @input_gain.setter
    def input_gain(self, value):
        allowed_gains = [0, 1, 2, 3, 4, 6, 8, 10]
        if not value in allowed_gains:
            raise ValueError(
                "input gain must be one of 0, 2, 4, 6, 8, 12, 16, 20 dB")
        self._input_gain = value
        self._current_input_gain = value

    @property
    def channel_1_attenuation(self):
        """The attenuation applied to channel 1 of the currently selected input pair in -dB.
        Maximum is -87dB. To mute set to 255
        This example sets the attenuation for input channel 1 to -10dB.
        .. code-block:: python
        bd3491fs.channel_1_attenuation = 10""
        """
        return self._current_ch1_attenuation

    @channel_1_attenuation.setter
    def channel_1_attenuation(self, value):
        if (value < 0) or ((value > 87) and (value != 255)):
            raise ValueError("channel 1 attenuation must be from 0-87db")
        self._ch1_attenuation = value
        self._current_ch1_attenuation = value

    @property
    def channel_2_attenuation(self):
        """The attenuation applied to channel 2 of the currently selected input pair in -dB.
        Maximum is -87dB. To mute set to 255
        This example sets the attenuation for input channel 2 to -10dB.
        .. code-block:: python
        bd3491fs.channel_2_attenuation = 10""
        """
        return self._current_ch2_attenuation

    @channel_2_attenuation.setter
    def channel_2_attenuation(self, value):
        if (value < 0) or ((value > 87) and (value != 255)):
            raise ValueError("channel 2 attenuation must be from 0-87db")
        self._ch2_attenuation = value
        self._current_ch2_attenuation = value
Example #6
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
class Edubit:
    I2C_ADDRESS = 0x08

    NEOPIXEL_PIN = board.P13

    RED_LED_PIN = board.P14
    YELLOW_LED_PIN = board.P15
    GREEN_LED_PIN = board.P16

    SOUND_BIT_PIN = board.P1
    POTENTIO_BIT_PIN = board.P2
    IR_BIT_PIN = board.P8

    REG_ADD_REVISION = 0
    REG_ADD_SERVO_1 = 1
    REG_ADD_SERVO_2 = 2
    REG_ADD_SERVO_3 = 3
    REG_ADD_M1A = 4
    REG_ADD_M1B = 5
    REG_ADD_M2A = 6
    REG_ADD_M2B = 7
    REG_ADD_LB_UTH = 8
    REG_ADD_LB_LTH = 9
    REG_ADD_OV_TH = 10
    REG_ADD_VIN = 11
    REG_ADD_PWR_STATE = 12
    REG_ADD_LB_STATE = 13
    REG_ADD_OV_STATE = 14

    ### These all depend on self.i2c_device being set
    revision_reg = ROUnaryStruct(REG_ADD_REVISION, "<B")

    servo_1_reg = UnaryStruct(REG_ADD_SERVO_1, "<B")
    servo_2_reg = UnaryStruct(REG_ADD_SERVO_2, "<B")
    servo_3_reg = UnaryStruct(REG_ADD_SERVO_3, "<B")
    m1a_reg = UnaryStruct(REG_ADD_M1A, "<B")
    m1b_reg = UnaryStruct(REG_ADD_M1B, "<B")
    m2a_reg = UnaryStruct(REG_ADD_M2A, "<B")
    m2b_reg = UnaryStruct(REG_ADD_M2B, "<B")
    lb_uth_reg = UnaryStruct(REG_ADD_LB_UTH, "<B")
    lb_lth_reg = UnaryStruct(REG_ADD_LB_LTH, "<B")
    ov_th_reg = UnaryStruct(REG_ADD_OV_TH, "<B")

    vin_reg = ROUnaryStruct(REG_ADD_VIN, "<B")
    pwr_state_reg = ROUnaryStruct(REG_ADD_PWR_STATE, "<B")
    lb_state_reg = ROUnaryStruct(REG_ADD_LB_STATE, "<B")
    ov_state_reg = ROUnaryStruct(REG_ADD_OV_STATE, "<B")

    S1 = REG_ADD_SERVO_1
    S2 = REG_ADD_SERVO_2
    S3 = REG_ADD_SERVO_3

    M1 = 0
    M2 = 1
    ALL = 1000

    FORWARD = 0
    BACKWARD = 1

    RED = 0
    YELLOW = 1
    GREEN = 2

    def __init__(self, i2c=None, device_address=I2C_ADDRESS):
        self._i2c = board.I2C() if i2c is None else i2c
        self.i2c_device = i2c_device.I2CDevice(i2c, device_address)

        if self.is_power_on():
            self.init_motor_servos()
        else:
            raise RuntimeError("Edu:bit is not responding to i2c"
                               " - powered on?")

        try:
            self.pixels = neopixel.NeoPixel(self.NEOPIXEL_PIN, 4)
        except ValueError:
            raise RuntimeError("NeoPixel pin already in use"
                               " - Edubit object already instantiated?")

        self.pixels.fill((0, 0, 0))

        self._red_led = digitalio.DigitalInOut(self.RED_LED_PIN)
        self._red_led.switch_to_output(False)
        self._yellow_led = digitalio.DigitalInOut(self.YELLOW_LED_PIN)
        self._yellow_led.switch_to_output(False)
        self._green_led = digitalio.DigitalInOut(self.GREEN_LED_PIN)
        self._green_led.switch_to_output(False)

        self._sound = analogio.AnalogIn(self.SOUND_BIT_PIN)
        self._potentio = analogio.AnalogIn(self.POTENTIO_BIT_PIN)
        self._ir = digitalio.DigitalInOut(self.IR_BIT_PIN)
        self._ir.pull = digitalio.Pull.DOWN

        time.sleep(0.2)  ### 200ms pause - the MicroPython library does this

    def init_motor_servos(self):
        self.brake_motor(self.ALL)
        self.disable_servo(self.ALL)

    def _set_motor_speed(self, motorChannel, fwd_speed, rev_speed):
        if fwd_speed > 0 and rev_speed > 0:
            raise ValueError("At least one speed arg must be 0")

        if motorChannel in (self.M1, self.ALL):
            self.m1a_reg = fwd_speed
            self.m1b_reg = rev_speed

        if motorChannel in (self.M2, self.ALL):
            self.m2a_reg = fwd_speed
            self.m2b_reg = rev_speed

    def brake_motor(self, motorChannel):
        self._set_motor_speed(motorChannel, 0, 0)

    def run_motor(self, motorChannel, direction, speed):
        speed = constrain(speed, 0, 255)
        forward = speed if direction == self.FORWARD else 0
        backward = speed if direction == self.BACKWARD else 0
        self._set_motor_speed(motorChannel, forward, backward)

    def _set_servo_value(self, servo, value):
        if servo in (self.S1, self.ALL):
            self.servo_1_reg = value
        if servo in (self.S2, self.ALL):
            self.servo_2_reg = value
        if servo in (self.S3, self.ALL):
            self.servo_3_reg = value

    def disable_servo(self, servo):
        self._set_servo_value(servo, 0)

    def set_servo_position(self, servo, position):
        position = constrain(position, 0, 180)

        ### This formula comes from MicroPython library
        pulseWidth = int(position * 20 / 18 + 50)
        self._set_servo_value(servo, pulseWidth)

    def is_power_on(self):
        return self.pwr_state_reg != 0

    def is_low_batt(self):
        return self.lb_state_reg != 0

    def is_overvoltage(self):
        return self.ov_state_reg != 0

    def read_Vin(self):
        return self.vin_reg / 10.0

    def set_led(self, color, state):
        if color in (self.RED, self.ALL):
            self._red_led.value = bool(state)
        if color in (self.YELLOW, self.ALL):
            self._yellow_led.value = bool(state)
        if color in (self.GREEN, self.ALL):
            self._green_led.value = bool(state)

    def read_sound_sensor(self):
        return self._sound.value

    def read_pot_value(self):
        return self._potentio.value

    def read_IR_sensor(self):
        return self._ir.value

    def is_IR_sensor_triggered(self):
        return not self._ir.value
Example #8
0
class TMP117:
    """Library for the TI TMP117 high-accuracy temperature sensor"""

    _part_id = ROUnaryStruct(_DEVICE_ID, ">H")
    _raw_temperature = ROUnaryStruct(_TEMP_RESULT, ">h")
    _raw_high_limit = UnaryStruct(_T_HIGH_LIMIT, ">h")
    _raw_low_limit = UnaryStruct(_T_LOW_LIMIT, ">h")
    _raw_temperature_offset = UnaryStruct(_TEMP_OFFSET, ">h")

    # these three bits will clear on read in some configurations, so we read them together
    _alert_status_data_ready = ROBits(3, _CONFIGURATION, 13, 2, False)
    _eeprom_busy = ROBit(_CONFIGURATION, 12, 2, False)
    _mode = RWBits(2, _CONFIGURATION, 10, 2, False)

    _raw_measurement_delay = RWBits(3, _CONFIGURATION, 7, 2, False)
    _raw_averaged_measurements = RWBits(2, _CONFIGURATION, 5, 2, False)

    _raw_alert_mode = RWBit(_CONFIGURATION, 4, 2,
                            False)  # T/nA bits in the datasheet
    _int_active_high = RWBit(_CONFIGURATION, 3, 2, False)
    _data_ready_int_en = RWBit(_CONFIGURATION, 2, 2, False)
    _soft_reset = RWBit(_CONFIGURATION, 1, 2, False)

    def __init__(self, i2c_bus, address=_I2C_ADDR):

        self.i2c_device = i2c_device.I2CDevice(i2c_bus, address)
        if self._part_id != _DEVICE_ID_VALUE:
            raise AttributeError("Cannot find a TMP117")
        # currently set when `alert_status` is read, but not exposed
        self.reset()
        self.initialize()

    def reset(self):
        """Reset the sensor to its unconfigured power-on state"""
        self._soft_reset = True

    def initialize(self):
        """Configure the sensor with sensible defaults. `initialize` is primarily provided to be
        called after `reset`, however it can also be used to easily set the sensor to a known
        configuration"""
        # Datasheet specifies that reset will finish in 2ms however by default the first
        # conversion will be averaged 8x and take 1s
        # TODO: sleep depending on current averaging config
        self._set_mode_and_wait_for_measurement(
            _CONTINUOUS_CONVERSION_MODE)  # one shot
        time.sleep(1)

    @property
    def temperature(self):
        """The current measured temperature in degrees celcius"""

        return self._read_temperature()

    @property
    def temperature_offset(self):
        """User defined temperature offset to be added to measurements from `temperature`

        .. code-block::python3

            # SPDX-FileCopyrightText: 2020 Bryan Siepert, written for Adafruit Industries
            #
            # SPDX-License-Identifier: Unlicense
            import time
            import board
            import busio
            import adafruit_tmp117

            i2c = busio.I2C(board.SCL, board.SDA)

            tmp117 = adafruit_tmp117.TMP117(i2c)

            print("Temperature without offset: %.2f degrees C" % tmp117.temperature)
            tmp117.temperature_offset = 10.0
            while True:
                print("Temperature w/ offset: %.2f degrees C" % tmp117.temperature)
                time.sleep(1)

        """
        return self._raw_temperature_offset * _TMP117_RESOLUTION

    @temperature_offset.setter
    def temperature_offset(self, value):
        if value > 256 or value < -256:
            raise AttributeError("temperature_offset must be ")
        scaled_offset = int(value / _TMP117_RESOLUTION)
        self._raw_temperature_offset = scaled_offset

    @property
    def high_limit(self):
        """The high temperature limit in degrees celcius. When the measured temperature exceeds this
        value, the `high_alert` attribute of the `alert_status` property will be True. See the
        documentation for `alert_status` for more information"""

        return self._raw_high_limit * _TMP117_RESOLUTION

    @high_limit.setter
    def high_limit(self, value):
        if value > 256 or value < -256:
            raise AttributeError("high_limit must be from 255 to -256")
        scaled_limit = int(value / _TMP117_RESOLUTION)
        self._raw_high_limit = scaled_limit

    @property
    def low_limit(self):
        """The low  temperature limit in degrees celcius. When the measured temperature goes below
        this value, the `low_alert` attribute of the `alert_status` property will be True. See the
        documentation for `alert_status` for more information"""

        return self._raw_low_limit * _TMP117_RESOLUTION

    @low_limit.setter
    def low_limit(self, value):
        if value > 256 or value < -256:
            raise AttributeError("low_limit must be from 255 to -256")
        scaled_limit = int(value / _TMP117_RESOLUTION)
        self._raw_low_limit = scaled_limit

    @property
    def alert_status(self):
        """The current triggered status of the high and low temperature alerts as a AlertStatus
        named tuple with attributes for the triggered status of each alert.

        .. code-block :: python3

            import board
            import busio
            import adafruit_tmp117
            i2c = busio.I2C(board.SCL, board.SDA)

            tmp117 = adafruit_tmp117.TMP117(i2c)

            tmp117.high_limit = 25
            tmp117.low_limit = 10

            print("High limit", tmp117.high_limit)
            print("Low limit", tmp117.low_limit)

            # Try changing `alert_mode`  to see how it modifies the behavior of the alerts.
            # tmp117.alert_mode = AlertMode.WINDOW #default
            # tmp117.alert_mode = AlertMode.HYSTERESIS

            print("Alert mode:", AlertMode.string[tmp117.alert_mode])
            print("")
            print("")
            while True:
                print("Temperature: %.2f degrees C" % tmp117.temperature)
                alert_status = tmp117.alert_status
                print("High alert:", alert_status.high_alert)
                print("Low alert:", alert_status.low_alert)
                print("")
                time.sleep(1)

        """
        high_alert, low_alert, *_ = self._read_status()
        return AlertStatus(high_alert=high_alert, low_alert=low_alert)

    @property
    def averaged_measurements(self):
        """The number of measurements that are taken and averaged before updating the temperature
        measurement register. A larger number will reduce measurement noise but may also affect
        the rate at which measurements are updated, depending on the value of `measurement_delay`

        Note that each averaged measurement takes 15.5ms which means that larger numbers of averaged
        measurements may make the delay between new reported measurements to exceed the delay set
        by `measurement_delay`

        .. code-block::python3

            import time
            import board
            import busio
            from adafruit_tmp117 import TMP117, AverageCount

            i2c = busio.I2C(board.SCL, board.SDA)

            tmp117 = TMP117(i2c)

            # uncomment different options below to see how it affects the reported temperature
            # tmp117.averaged_measurements = AverageCount.AVERAGE_1X
            # tmp117.averaged_measurements = AverageCount.AVERAGE_8X
            # tmp117.averaged_measurements = AverageCount.AVERAGE_32X
            # tmp117.averaged_measurements = AverageCount.AVERAGE_64X

            print(
                "Number of averaged samples per measurement:",
                AverageCount.string[tmp117.averaged_measurements],
            )
            print("")

            while True:
                print("Temperature:", tmp117.temperature)
                time.sleep(0.1)

        """
        return self._raw_averaged_measurements

    @averaged_measurements.setter
    def averaged_measurements(self, value):
        if not AverageCount.is_valid(value):
            raise AttributeError(
                "averaged_measurements must be an `AverageCount`")
        self._raw_averaged_measurements = value

    @property
    def measurement_mode(self):
        """Sets the measurement mode, specifying the behavior of how often measurements are taken.
        `measurement_mode` must be one of:

+----------------------------------------+------------------------------------------------------+
| Mode                                   | Behavior                                             |
+========================================+======================================================+
| :py:const:`MeasurementMode.CONTINUOUS` | Measurements are made at the interval determined by  |
|                                        |                                                      |
|                                        | `averaged_measurements` and `measurement_delay`.     |
|                                        |                                                      |
|                                        | `temperature` returns the most recent measurement    |
+----------------------------------------+------------------------------------------------------+
| :py:const:`MeasurementMode.ONE_SHOT`   | Take a single measurement with the current number of |
|                                        |                                                      |
|                                        | `averaged_measurements` and switch to                |
|                                        | :py:const:`SHUTDOWN` when                            |
|                                        |                                                      |
|                                        | finished.                                            |
|                                        |                                                      |
|                                        |                                                      |
|                                        | `temperature` will return the new measurement until  |
|                                        |                                                      |
|                                        | `measurement_mode` is set to :py:const:`CONTINUOUS`  |
|                                        | or :py:const:`ONE_SHOT` is                           |
|                                        |                                                      |
|                                        | set again.                                           |
+----------------------------------------+------------------------------------------------------+
| :py:const:`MeasurementMode.SHUTDOWN`   | The sensor is put into a low power state and no new  |
|                                        |                                                      |
|                                        | measurements are taken.                              |
|                                        |                                                      |
|                                        | `temperature` will return the last measurement until |
|                                        |                                                      |
|                                        | a new `measurement_mode` is selected.                |
+----------------------------------------+------------------------------------------------------+

        """
        return self._mode

    @measurement_mode.setter
    def measurement_mode(self, value):
        if not MeasurementMode.is_valid(value):
            raise AttributeError(
                "measurement_mode must be a `MeasurementMode` ")

        self._set_mode_and_wait_for_measurement(value)

    @property
    def measurement_delay(self):
        """The minimum amount of time between measurements in seconds. Must be a
        `MeasurementDelay`. The specified amount may be exceeded depending on the
        current setting off `averaged_measurements` which determines the minimum
        time needed between reported measurements.

        .. code-block::python3

            import time
            import board
            import busio
            from adafruit_tmp117 import TMP117, AverageCount, MeasurementDelay

            i2c = busio.I2C(board.SCL, board.SDA)

            tmp117 = TMP117(i2c)

            # uncomment different options below to see how it affects the reported temperature

            # tmp117.measurement_delay = MeasurementDelay.DELAY_0_0015_S
            # tmp117.measurement_delay = MeasurementDelay.DELAY_0_125_S
            # tmp117.measurement_delay = MeasurementDelay.DELAY_0_250_S
            # tmp117.measurement_delay = MeasurementDelay.DELAY_0_500_S
            # tmp117.measurement_delay = MeasurementDelay.DELAY_1_S
            # tmp117.measurement_delay = MeasurementDelay.DELAY_4_S
            # tmp117.measurement_delay = MeasurementDelay.DELAY_8_S
            # tmp117.measurement_delay = MeasurementDelay.DELAY_16_S

            print("Minimum time between measurements:",
            MeasurementDelay.string[tmp117.measurement_delay], "seconds")

            print("")

            while True:
                print("Temperature:", tmp117.temperature)
                time.sleep(0.01)

        """

        return self._raw_measurement_delay

    @measurement_delay.setter
    def measurement_delay(self, value):
        if not MeasurementDelay.is_valid(value):
            raise AttributeError(
                "measurement_delay must be a `MeasurementDelay`")
        self._raw_measurement_delay = value

    def take_single_measurememt(self):
        """Perform a single measurement cycle respecting the value of `averaged_measurements`,
        returning the measurement once complete. Once finished the sensor is placed into a low power
        state until :py:meth:`take_single_measurement` or `temperature` are read.

        **Note:** if `averaged_measurements` is set to a high value there will be a notable
        delay before the temperature measurement is returned while the sensor takes the required
        number of measurements
        """

        return self._set_mode_and_wait_for_measurement(
            _ONE_SHOT_MODE)  # one shot

    @property
    def alert_mode(self):
        """Sets the behavior of the `low_limit`, `high_limit`, and `alert_status` properties.

        When set to :py:const:`AlertMode.WINDOW`, the `high_limit` property will unset when the
        measured temperature goes below `high_limit`. Similarly `low_limit` will be True or False
        depending on if the measured temperature is below (`False`) or above(`True`) `low_limit`.

        When set to :py:const:`AlertMode.HYSTERESIS`, the `high_limit` property will be set to
        `False` when the measured temperature goes below `low_limit`. In this mode, the `low_limit`
        property of `alert_status` will not be set.

        The default is :py:const:`AlertMode.WINDOW`"""

        return self._raw_alert_mode

    @alert_mode.setter
    def alert_mode(self, value):
        if not AlertMode.is_valid(value):
            raise AttributeError("alert_mode must be an `AlertMode`")
        self._raw_alert_mode = value

    @property
    def serial_number(self):
        """A 48-bit, factory-set unique identifier for the device."""
        eeprom1_data = bytearray(2)
        eeprom2_data = bytearray(2)
        eeprom3_data = bytearray(2)
        # Fetch EEPROM registers
        with self.i2c_device as i2c:
            i2c.write_then_readinto(bytearray([_EEPROM1]), eeprom1_data)
            i2c.write_then_readinto(bytearray([_EEPROM2]), eeprom2_data)
            i2c.write_then_readinto(bytearray([_EEPROM3]), eeprom3_data)
        # Combine the 2-byte portions
        combined_id = bytearray([
            eeprom1_data[0],
            eeprom1_data[1],
            eeprom2_data[0],
            eeprom2_data[1],
            eeprom3_data[0],
            eeprom3_data[1],
        ])
        # Convert to an integer
        return _convert_to_integer(combined_id)

    def _set_mode_and_wait_for_measurement(self, mode):

        self._mode = mode
        # poll for data ready
        while not self._read_status()[2]:
            time.sleep(0.001)

        return self._read_temperature()

    # eeprom write enable to set defaults for limits and config
    # requires context manager or something to perform a general call reset

    def _read_status(self):
        # 3 bits: high_alert, low_alert, data_ready
        status_flags = self._alert_status_data_ready

        high_alert = 0b100 & status_flags > 0
        low_alert = 0b010 & status_flags > 0
        data_ready = 0b001 & status_flags > 0

        return (high_alert, low_alert, data_ready)

    def _read_temperature(self):
        return self._raw_temperature * _TMP117_RESOLUTION
Example #9
0
class AW9523:
    """CircuitPython helper class for using the AW9523 GPIO expander"""

    _chip_id = ROUnaryStruct(_AW9523_REG_CHIPID, "<B")
    _reset_reg = UnaryStruct(_AW9523_REG_SOFTRESET, "<B")

    # Set all 16 gpio outputs
    outputs = UnaryStruct(_AW9523_REG_OUTPUT0, "<H")
    # Read all 16 gpio inputs
    inputs = UnaryStruct(_AW9523_REG_INPUT0, "<H")
    # Set all 16 gpio interrupt enable
    _interrupt_enables = UnaryStruct(_AW9523_REG_INTENABLE0, "<H")
    # Set all 16 gpio directions
    _directions = UnaryStruct(_AW9523_REG_CONFIG0, "<H")
    # Set all 16 gpio LED modes
    _LED_modes = UnaryStruct(_AW9523_REG_LEDMODE, "<H")

    # Whether port 0 is push-pull
    port0_push_pull = RWBit(_AW9523_REG_GCR, 4)

    def __init__(self, i2c_bus, address=_AW9523_DEFAULT_ADDR, reset=True):
        self.i2c_device = i2c_device.I2CDevice(i2c_bus, address)
        self._buffer = bytearray(2)
        if self._chip_id != 0x23:
            raise AttributeError("Cannot find a AW9523")
        if reset:
            self.reset()
            self.port0_push_pull = True  # pushpull output
            self.interrupt_enables = 0x0000  # no IRQ
            self.directions = 0x0000  # all inputs!

    def reset(self):
        """Perform a soft reset, check datasheets for post-reset defaults!"""
        self._reset_reg = 0

    def set_constant_current(self, pin, value):
        """
        Set the constant current drain for an AW9523 pin
        :param int pin: pin to set constant current, 0..15
        :param int value: the value ranging from 0 (off) to 255 (max current)
        """
        # See Table 13. 256 step dimming control register
        if 0 <= pin <= 7:
            self._buffer[0] = 0x24 + pin
        elif 8 <= pin <= 11:
            self._buffer[0] = 0x20 + pin - 8
        elif 12 <= pin <= 15:
            self._buffer[0] = 0x2C + pin - 12
        else:
            raise ValueError("Pin must be 0 to 15")

        # set value
        if not 0 <= value <= 255:
            raise ValueError("Value must be 0 to 255")
        self._buffer[1] = value
        with self.i2c_device as i2c:
            i2c.write(self._buffer)

    def get_pin(self, pin):
        """Convenience function to create an instance of the DigitalInOut class
        pointing at the specified pin of this AW9523 device.
        :param int pin: pin to use for digital IO, 0 to 15
        """
        assert 0 <= pin <= 15
        return DigitalInOut(pin, self)

    @property
    def interrupt_enables(self):
        """Enables interrupt for input pin change if bit mask is 1"""
        return ~self._interrupt_enables & 0xFFFF

    @interrupt_enables.setter
    def interrupt_enables(self, enables):
        self._interrupt_enables = ~enables & 0xFFFF

    @property
    def directions(self):
        """Direction is output if bit mask is 1, input if bit is 0"""
        return ~self._directions & 0xFFFF

    @directions.setter
    def directions(self, dirs):
        self._directions = (~dirs) & 0xFFFF

    @property
    def LED_modes(self):
        """Pin is set up for constant current mode if bit mask is 1"""
        return ~self._LED_modes & 0xFFFF

    @LED_modes.setter
    def LED_modes(self, modes):
        self._LED_modes = ~modes & 0xFFFF
Example #10
0
class LIS2MDL:  # pylint: disable=too-many-instance-attributes
    """
    Driver for the LIS2MDL 3-axis magnetometer.

    :param busio.I2C i2c_bus: The I2C bus the LIS2MDL is connected to.

    """

    _BUFFER = bytearray(6)

    _device_id = ROUnaryStruct(WHO_AM_I, "B")
    _int_control = UnaryStruct(INT_CRTL_REG, "B")
    _mode = RWBits(2, CFG_REG_A, 0, 1)
    _data_rate = RWBits(2, CFG_REG_A, 2, 1)
    _temp_comp = RWBit(CFG_REG_A, 7, 1)
    _reboot = RWBit(CFG_REG_A, 6, 1)
    _soft_reset = RWBit(CFG_REG_A, 5, 1)
    _bdu = RWBit(CFG_REG_C, 4, 1)

    _int_iron_off = RWBit(CFG_REG_B, 3, 1)

    _x_offset = UnaryStruct(OFFSET_X_REG_L, "<h")
    _y_offset = UnaryStruct(OFFSET_Y_REG_L, "<h")
    _z_offset = UnaryStruct(OFFSET_Z_REG_L, "<h")

    _interrupt_pin_putput = RWBit(CFG_REG_C, 6, 1)
    _interrupt_threshold = UnaryStruct(INT_THS_L_REG, "<h")

    _x_int_enable = RWBit(INT_CRTL_REG, 7, 1)
    _y_int_enable = RWBit(INT_CRTL_REG, 6, 1)
    _z_int_enable = RWBit(INT_CRTL_REG, 5, 1)
    _int_reg_polarity = RWBit(INT_CRTL_REG, 2, 1)
    _int_latched = RWBit(INT_CRTL_REG, 1, 1)
    _int_enable = RWBit(INT_CRTL_REG, 0, 1)

    _int_source = ROUnaryStruct(INT_SOURCE_REG, "B")

    low_power = RWBit(CFG_REG_A, 4, 1)
    """Enables and disables low power mode"""

    _raw_x = ROUnaryStruct(OUTX_L_REG, "<h")
    _raw_y = ROUnaryStruct(OUTY_L_REG, "<h")
    _raw_z = ROUnaryStruct(OUTZ_L_REG, "<h")

    _x_offset = UnaryStruct(OFFSET_X_REG_L, "<h")
    _y_offset = UnaryStruct(OFFSET_Y_REG_L, "<h")
    _z_offset = UnaryStruct(OFFSET_Z_REG_L, "<h")

    def __init__(self, i2c):
        self.i2c_device = I2CDevice(i2c, _ADDRESS_MAG)

        if self._device_id != 0x40:
            raise AttributeError("Cannot find an LIS2MDL")

        self.reset()

    def reset(self):
        """Reset the sensor to the default state set by the library"""
        self._soft_reset = True
        sleep(0.100)
        self._reboot = True
        sleep(0.100)
        self._mode = 0x00
        self._bdu = True  # Make sure high and low bytes are set together
        self._int_latched = True
        self._int_reg_polarity = True
        self._int_iron_off = False
        self._interrupt_pin_putput = True
        self._temp_comp = True

        sleep(0.030)  # sleep 20ms to allow measurements to stabilize

    @property
    def magnetic(self):
        """The processed magnetometer sensor values.
        A 3-tuple of X, Y, Z axis values in microteslas that are signed floats.
        """

        return (
            self._raw_x * _MAG_SCALE,
            self._raw_y * _MAG_SCALE,
            self._raw_z * _MAG_SCALE,
        )

    @property
    def data_rate(self):
        """The magnetometer update rate."""
        return self._data_rate

    @data_rate.setter
    def data_rate(self, value):
        if not value in (
                DataRate.Rate_10_HZ,
                DataRate.Rate_20_HZ,
                DataRate.Rate_50_HZ,
                DataRate.Rate_100_HZ,
        ):
            raise ValueError("data_rate must be a `DataRate`")
        self._data_rate = value

    @property
    def interrupt_threshold(self):
        """The threshold (in microteslas) for magnetometer interrupt generation. Given value is
        compared against all axes in both the positive and negative direction"""
        return self._interrupt_threshold * _MAG_SCALE

    @interrupt_threshold.setter
    def interrupt_threshold(self, value):
        if value < 0:
            value = -value
        self._interrupt_threshold = int(value / _MAG_SCALE)

    @property
    def interrupt_enabled(self):
        """Enable or disable the magnetometer interrupt"""
        return self._int_enable

    @interrupt_enabled.setter
    def interrupt_enabled(self, val):
        self._x_int_enable = val
        self._y_int_enable = val
        self._z_int_enable = val
        self._int_enable = val

    @property
    def faults(self):
        """A tuple representing interrupts on each axis in a positive and negative direction
        ``(x_hi, y_hi, z_hi, x_low, y_low, z_low, int_triggered)``"""
        int_status = self._int_source
        x_hi = (int_status & 0b10000000) > 0
        y_hi = int_status & 0b01000000 > 0
        z_hi = int_status & 0b00100000 > 0

        x_low = int_status & 0b00010000 > 0
        y_low = int_status & 0b00001000 > 0
        z_low = int_status & 0b00000100 > 0
        int_triggered = int_status & 0b1 > 0
        return (x_hi, y_hi, z_hi, x_low, y_low, z_low, int_triggered)

    @property
    def x_offset(self):
        """An offset for the X-Axis to subtract from the measured value to correct
        for magnetic interference"""
        return self._x_offset * _MAG_SCALE

    @x_offset.setter
    def x_offset(self, value):
        self._x_offset = int(value / _MAG_SCALE)

    @property
    def y_offset(self):
        """An offset for the Y-Axis to subtract from the measured value to correct
        for magnetic interference"""
        return self._y_offset * _MAG_SCALE

    @y_offset.setter
    def y_offset(self, value):
        self._y_offset = int(value / _MAG_SCALE)

    @property
    def z_offset(self):
        """An offset for the Z-Axis to subtract from the measured value to correct
        for magnetic interference"""
        return self._z_offset * _MAG_SCALE

    @z_offset.setter
    def z_offset(self, value):
        self._z_offset = int(value / _MAG_SCALE)
Example #11
0
class MPU6050:
    """
	Init the MPU chip at ``address`` on ``i2c_bus``
	
	:param i2c object bus 	-> i2c_bus: The i2c bus to use for the communication 
	:param int type 	-> address: The MPU I2C address
	"""
    """ 
	Class General Variables
	"""
    GRAVITIY_MS2 = 9.80665

    #Registers:
    #General Registers
    PWR_MGMT_1 = UnaryStruct(0x6B, ">B")  # 0x6B
    WHO_AM_I = ROUnaryStruct(0x75, ">B")  # 0X75
    FSYC_DLP_CONFIG = UnaryStruct(0x1A, ">B")
    #Temp Sensor Registers
    TEMP_OUTH = ROUnaryStruct(0x41, ">b")  # 0x41
    TEMP_OUTL = ROUnaryStruct(0x42, ">b")  # 0x41
    #Accelerometer Registers
    ACCEL_CONFIG = UnaryStruct(0x1C, ">B")  # 0x1C
    ACCEL_XOUTH = ROUnaryStruct(0x3B, ">B")  # 0x3B
    ACCEL_XOUTL = ROUnaryStruct(0x3C, ">B")  # 0x3B
    ACCEL_YOUTH = ROUnaryStruct(0x3D, ">B")  # 0x3D
    ACCEL_YOUTL = ROUnaryStruct(0x3E, ">B")  # 0x3D
    ACCEL_ZOUTH = ROUnaryStruct(0x3F, ">B")  # 0x3F
    ACCEL_ZOUTL = ROUnaryStruct(0x40, ">B")  # 0x3F
    #Gyroscope Registers
    GYRO_CONFIG = UnaryStruct(0x1B, ">B")  # 0x1B
    GYRO_XOUTH = ROUnaryStruct(0x43, ">B")  # 0x43
    GYRO_XOUTL = ROUnaryStruct(0x44, ">B")  # 0x44
    GYRO_YOUTH = ROUnaryStruct(0x45, ">B")  # 0x45
    GYRO_YOUTL = ROUnaryStruct(0x46, ">B")  # 0x46
    GYRO_ZOUTH = ROUnaryStruct(0x47, ">B")  # 0x47
    GYRO_ZOUTL = ROUnaryStruct(0x48, ">B")  # 0x48

    #Full scale Range ACCEL:
    """ from register 1C bit 4 to 3"""
    ACCEL_RANGE_2G = 0x00
    ACCEL_RANGE_4G = 0x08
    ACCEL_RANGE_8G = 0x10
    ACCEL_RANGE_16G = 0x18

    # LSB Sensitivity
    ACCEL_LSB_SENS_2G = 16384.0
    ACCEL_LSB_SENS_4G = 8192.0
    ACCEL_LSB_SENS_8G = 4096.0
    ACCEL_LSB_SENS_16G = 2048.0

    #Full scale Range GYRO:
    """ from register 1B bit 4 to 3"""
    GYRO_RANGE_250DEG = 0x00
    GYRO_RANGE_500DEG = 0x08
    GYRO_RANGE_1000DEG = 0x10
    GYRO_RANGE_2000DEG = 0x18

    #LSB Sensitivity
    GYRO_LSB_SENS_250DEG = 131.0
    GYRO_LSB_SENS_500DEG = 65.5
    GYRO_LSB_SENS_1000DEG = 32.8
    GYRO_LSB_SENS_2000DEG = 16.4

    #MPU Address
    MPU_ADDRESS = 0x68
    """ 
	Constructor
	"""
    def __init__(self, i2c_bus=None, address=MPU_ADDRESS):
        """ Create an i2c device from the MPU6050"""
        self.i2c = i2c_bus

        if self.i2c == None:
            import busio
            from board import SDA, SCL
            self.i2c = busio.I2C(SCL, SDA)

        self.i2c_device = I2CDevice(self.i2c, address)
        """ Wake up the MPU-6050 since it starts in sleep mode """
        self.wakeup()
        print('MPU already awaked')
        """ verify the accel range and get the accel scale modifier"""
        print('accelerometer range set: {} g'.format(self.read_accel_range()))
        self.accel_scale_modifier = self.get_accel_scale_modifier()
        """ verify the gyro range and get the gyro scale modifier"""
        print('gyroscope range set: {} °/s'.format(self.read_gyro_range()))
        self.gyro_scale_modifier = self.get_gyro_scale_modifier()
        """ configuring the Digital Low Pass Filter """
        #by default:
        # Bandwith of 21Hz and delay of 8.5ms, Sampling Freq 1KHz -> Accelerometer
        # Bandwith of 20Hz and delay of 8.3ms, Sampling Freq 1KHz -> Gyroscope
        self.filter_sensor = 0x04
        print('digital filter configure to be: {}'.format(self.filter_sensor))

    """ 
		Class Properties
	"""

    @property
    def whoami(self):
        """ MPU6050 I2C Address """
        return self.WHO_AM_I

    @property
    def get_temp(self):
        """ MPU6050 Temperature """
        H = self.TEMP_OUTH
        L = self.TEMP_OUTL

        raw_temp = self.raw_data_format((H << 8) + L)

        # Get the actual temperature using the formule given in the
        # MPU-6050 Register Map and Descriptions revision 4.2, page 30
        actual_temp = (raw_temp / 340.0) + 36.53
        return actual_temp

    @property
    def filter_sensor(self):
        """
		Return the filter sensor
		"""
        return self.FSYC_DLP_CONFIG

    """ Class Setter """

    @filter_sensor.setter
    def filter_sensor(self, filter_value=0x04):
        """
		Configuration Register

		In case FYNC pin is used, the bit of the sampling will be reported at 
		any of the following registers. For more information consult MPU-6000-Register
		Map at page 13. 

		EXT_SYNC_SET, bit 5:3, Values:
		000: Input Disable
		001: Temp_Out_L
		010: GYRO_XOUT_L[0] 
		011: GYRO_YOUT_L[0] 
		100: GYRO_ZOUT_L[0]
		101: ACCEL_XOUT_L[0]
		110: ACCEL_YOUT_L[0]
		111: ACCEL_ZOUT_L[0]

		In case to set accelerometer and gyroscope are filtered according to the following
		table. 
		DLPF_CFG, bit 2:0, Values:
					accelerometer 				gyroscope
				Bandwidth	Delays	Fs
		000:	260 Hz 		0	ms 	1KHz 	256 Hz 		0.98ms 	8KHz
		001:	184 Hz 		2	ms 	1KHz 	188 Hz 		1.9	ms 	1KHz
		010:	94	Hz 		3	ms 	1KHz 	98	Hz 		2.8	ms 	1KHz
		011:	44	Hz  	4.9	ms 	1KHz 	42	Hz  	4.8	ms 	1KHz
		100:	21	Hz  	8.5	ms 	1KHz 	20	Hz  	8.3	ms 	1KHz
		101:	10	Hz  	13.8ms	1KHz 	10	Hz  	13.4ms	1KHz
		110:	5	Hz  	19	ms 	1KHz    5	Hz  	18.6ms 	1KHz
		111: 	RESERVED  	   RESERVED     RESERVED   RESERVED 8KHz
		"""
        if (filter_value < 0x00 and filter_value > 0x07):
            filter_value = 0x04

        self.FSYC_DLP_CONFIG = filter_value

    """
		function members
	"""

    def wakeup(self):
        """ waking up the MPU-6050 by writing at the POWER MANAGEMENT REGISTER 1

		Device_Reset, bit 7, values:
		0: Nothing
		1: the device will reset all internal registers to their default values.

		Sleep, bit 6, values:
		0: disables the sleep mode
		1: enables the sleep mode

		Cycle, bit 5 values:
		0: Nothing
		1: The device will cycle between sleep mode and waking up to take a single sample of data
		from active sensors at a rate determined by LP_WAKE_CTRL (register 108)

		Reserverd bit 4, value = 0

		Temp_dis, bit 3, values
		0: Nothing
		1: Disables the temperature sensor

		ClkSel, bit 2:0, values:
		000: Internal 8MHz oscillator 
		001: PLL with X axis gyroscope reference 
		010: PLL with y axis gyroscope reference
		011: PLL with z axis gyroscope reference
		100: PLL with external 32.768kHz reference
		101: PLL with external 19.2MHz reference 
		110: Reserverd
		111: Stops the clock and keeps the timing generator in reset 

		"""
        self.PWR_MGMT_1 = 0x00  # BINARY 01001111

    def sleep(self):
        """ entenring into sleep mode
		Deactivate the internal clock generator and enter into sleep mode
		"""
        self.PWR_MGMT_1 = 0x4F  # BINARY 01001111

    def deinit(self):
        """ stop using the MPU-6050 """
        self.sleep()

    def raw_data_format(self, raw_data):
        """ formating data that comes from the I2C bus"""
        """ This helps to provide the results between -1 and 1 along with the accel or gyro modifier"""
        if (raw_data >= 0x8000):
            raw_data = -((65535 - raw_data) + 1)
        return raw_data

    """ Accelerometer """

    def read_accel_range(self, raw=False):
        """Reads the range the accelerometer is set to.

		If raw is True, it will return the raw value from the ACCEL_CONFIG
		register
		If raw is False, it will return an integer: -1, 2, 4, 8 or 16. When it
		returns -1 something went wrong.
		"""
        raw_data = self.ACCEL_CONFIG

        if raw is True:
            return raw_data
        elif raw is False:
            if raw_data == self.ACCEL_RANGE_2G:
                return 2
            elif raw_data == self.ACCEL_RANGE_4G:
                return 4
            elif raw_data == self.ACCEL_RANGE_8G:
                return 8
            elif raw_data == self.ACCEL_RANGE_16G:
                return 16
        else:
            return -1

    def set_accel_range(self, value):
        """
		set accel range
		"""
        cond = (self.ACCEL_RANGE_2G == value) or (self.ACCEL_RANGE_4G == value) \
            (self.ACCEL_RANGE_8G == value) or (self.ACCEL_RANGE_16G)

        if cond == False:
            value = self.ACCEL_RANGE_2G

        self.ACCEL_CONFIG = value
        self.accel_scale_modifier = self.get_accel_scale_modifier()

    def get_accel_scale_modifier(self):

        accel_range = self.read_accel_range(True)

        if accel_range == self.ACCEL_RANGE_2G:
            accel_scale_modifier = self.ACCEL_LSB_SENS_2G
        elif accel_range == self.ACCEL_RANGE_4G:
            accel_scale_modifier = self.ACCEL_LSB_SENS_4G
        elif accel_range == self.ACCEL_RANGE_8G:
            accel_scale_modifier = self.ACCEL_LSB_SENS_8G
        elif accel_range == self.ACCEL_RANGE_16G:
            accel_scale_modifier = self.ACCEL_LSB_SENS_16G
        else:
            print(
                "Unkown range - accel_scale_modifier set to self.ACCEL_SCALE_MODIFIER_2G"
            )
            accel_scale_modifier = self.ACCEL_LSB_SENS_2G

        return accel_scale_modifier

    def get_accel_data(self, g=False):
        """Gets and returns the X, Y and Z values from the accelerometer.
		If g is True, it will return the data in g
		If g is False, it will return the data in m/s^2
		Returns a dictionary with the measurement results.
		"""
        XH = self.ACCEL_XOUTH
        XL = self.ACCEL_XOUTL
        YH = self.ACCEL_YOUTH
        YL = self.ACCEL_YOUTL
        ZH = self.ACCEL_ZOUTH
        ZL = self.ACCEL_ZOUTL

        x = self.raw_data_format((XH << 8) + XL)
        y = self.raw_data_format((YH << 8) + YL)
        z = self.raw_data_format((ZH << 8) + ZL)

        x = x / self.accel_scale_modifier
        y = y / self.accel_scale_modifier
        z = z / self.accel_scale_modifier

        if g is False:
            x = x * self.GRAVITIY_MS2
            y = y * self.GRAVITIY_MS2
            z = z * self.GRAVITIY_MS2

        return {'x': x, 'y': y, 'z': z}

    """ Gyroscope """

    def read_gyro_range(self, raw=False):
        """Reads the range the gyroscope is set to.
	    If raw is True, it will return the raw value from the GYRO_CONFIG
	    register.
	    If raw is False, it will return 250, 500, 1000, 2000 or -1. If the
	    returned value is equal to -1 something went wrong.
	    """
        raw_data = self.GYRO_CONFIG

        if raw is True:
            return raw_data
        elif raw is False:
            if raw_data == self.GYRO_RANGE_250DEG:
                return 250
            elif raw_data == self.GYRO_RANGE_500DEG:
                return 500
            elif raw_data == self.GYRO_RANGE_1000DEG:
                return 1000
            elif raw_data == self.GYRO_RANGE_2000DEG:
                return 2000
            else:
                return -1

    def set_gyro_range(self, value):
        """
		set gyro range
		"""
        cond = (self.GYRO_RANGE_250DEG == value) or (self.GYRO_RANGE_500DEG == value) \
            (self.GYRO_RANGE_1000DEG == value) or (self.GYRO_RANGE_2000DEG)

        if cond == False:
            value = self.GYRO_RANGE_250DEG

        self.GYRO_CONFIG = value
        self.gyro_scale_modifier = self.get_gyro_scale_modifier()

    def get_gyro_scale_modifier(self):
        """
		Get gyro scale modifier from reading the gyro range
		"""
        gyro_range = self.read_gyro_range(True)

        if gyro_range == self.GYRO_RANGE_250DEG:
            gyro_scale_modifier = self.GYRO_LSB_SENS_250DEG
        elif gyro_range == self.GYRO_RANGE_500DEG:
            gyro_scale_modifier = self.GYRO_LSB_SENS_500DEG
        elif gyro_range == self.GYRO_RANGE_1000DEG:
            gyro_scale_modifier = self.GYRO_LSB_SENS_1000DEG
        elif gyro_range == self.GYRO_RANGE_2000DEG:
            gyro_scale_modifier = self.GYRO_LSB_SENS_2000DEG
        else:
            print(
                "Unkown range - gyro_scale_modifier set to self.GYRO_LSB_SENS_250DEG"
            )
            gyro_scale_modifier = self.GYRO_LSB_SENS_250DEG

        return gyro_scale_modifier

    def get_gyro_data(self):
        """
		Gets and returns the X, Y and Z values from the gyroscope.
		"""
        XH = self.GYRO_XOUTH
        XL = self.GYRO_XOUTL
        YH = self.GYRO_YOUTH
        YL = self.GYRO_YOUTL
        ZH = self.GYRO_ZOUTH
        ZL = self.GYRO_ZOUTL

        x = self.raw_data_format((XH << 8) + XL)
        y = self.raw_data_format((YH << 8) + YL)
        z = self.raw_data_format((ZH << 8) + ZL)

        x = x / self.gyro_scale_modifier
        y = y / self.gyro_scale_modifier
        z = z / self.gyro_scale_modifier

        return {'x': x, 'y': y, 'z': z}

    """
		magic methos helps to make easy the use of with
	"""

    def __enter__(self):
        """ to make easy the use of with """
        return self

    def __exit__(self, exceptio_typ, exception_value, traceback):
        """ to make easy the use of with """
        self.deinit()
Example #12
0
class DeviceControl:  # pylint: disable-msg=too-few-public-methods
    def __init__(self, i2c):
        self.i2c_device = i2c  # self.i2c_device required by UnaryStruct class

    register1 = UnaryStruct(A_DEVICE_REGISTER_1, "<B")  # 8-bit number
    register2 = UnaryStruct(A_DEVICE_REGISTER_2, "<B")  # 8-bit number
Example #13
0
class DPS310:
    #pylint: disable=too-many-instance-attributes
    """Library for the DPS310 Precision Barometric Pressure Sensor.

        :param ~busio.I2C i2c_bus: The I2C bus the DPS310 is connected to.
        :param address: The I2C slave address of the sensor

    """
    # Register definitions
    _device_id = ROUnaryStruct(_DPS310_PRODREVID, ">B")
    _reset_register = UnaryStruct(_DPS310_RESET, ">B")
    _mode_bits = RWBits(3, _DPS310_MEASCFG, 0)

    _pressure_ratebits = RWBits(3, _DPS310_PRSCFG, 4)
    _pressure_osbits = RWBits(4, _DPS310_PRSCFG, 0)

    _temp_ratebits = RWBits(3, _DPS310_TMPCFG, 4)
    _temp_osbits = RWBits(4, _DPS310_TMPCFG, 0)

    _temp_measurement_src_bit = RWBit(_DPS310_TMPCFG, 7)

    _pressure_shiftbit = RWBit(_DPS310_CFGREG, 2)
    _temp_shiftbit = RWBit(_DPS310_CFGREG, 3)

    _coefficients_ready = RWBit(_DPS310_MEASCFG, 7)
    _sensor_ready = RWBit(_DPS310_MEASCFG, 6)
    _temp_ready = RWBit(_DPS310_MEASCFG, 5)
    _pressure_ready = RWBit(_DPS310_MEASCFG, 4)

    _raw_pressure = ROBits(24, _DPS310_PRSB2, 0, 3, lsb_first=False)
    _raw_temperature = ROBits(24, _DPS310_TMPB2, 0, 3, lsb_first=False)

    _calib_coeff_temp_src_bit = ROBit(_DPS310_TMPCOEFSRCE, 7)

    def __init__(self, i2c_bus, address=_DPS310_DEFAULT_ADDRESS):
        self.i2c_device = i2c_device.I2CDevice(i2c_bus, address)

        if self._device_id != _DPS310_DEVICE_ID:
            raise RuntimeError("Failed to find DPS310 - check your wiring!")
        self._pressure_scale = None
        self._temp_scale = None
        self._c0 = None
        self._c1 = None
        self._c00 = None
        self._c00 = None
        self._c10 = None
        self._c10 = None
        self._c01 = None
        self._c11 = None
        self._c20 = None
        self._c21 = None
        self._c30 = None
        self._oversample_scalefactor = (524288, 1572864, 3670016, 7864320, 253952,
                                        516096, 1040384, 2088960)
        self.initialize()

    def initialize(self):
        """Reset the sensor to the default state"""

        self._reset()
        self._read_calibration()

        # make sure we're using the temperature source used for calibration
        self._temp_measurement_src_bit = self._calib_coeff_temp_src_bit

        self.pressure_rate = Rate.RATE_64_HZ
        self.pressure_oversample_count = SampleCount.COUNT_64
        self.temperature_rate = Rate.RATE_64_HZ
        self.temperature_oversample_count = SampleCount.COUNT_64
        self.mode = Mode.CONT_PRESTEMP

        # wait until we have at least one good measurement

        while (self._temp_ready is False) or (self._pressure_ready is False):
            sleep(0.001)

    def _reset(self):
        """Perform a soft-reset on the sensor"""
        self._reset_register = 0x89
        # wait for hardware reset to finish
        sleep(0.010)
        while not self._sensor_ready:
            sleep(0.001)

    @property
    def pressure(self):
        """Returns the current pressure reading in kPA"""

        temp_reading = self._raw_temperature
        raw_temperature = self._twos_complement(temp_reading, 24)
        pressure_reading = self._raw_pressure
        raw_pressure = self._twos_complement(pressure_reading, 24)
        _scaled_rawtemp = raw_temperature / self._temp_scale

        _temperature = _scaled_rawtemp * self._c1 + self._c0 / 2.0

        p_red = raw_pressure / self._pressure_scale


        pres_calc = (self._c00 + p_red * (self._c10 + p_red * (self._c20 + p_red * self._c30)) +
                     _scaled_rawtemp * (self._c01 + p_red * (self._c11 + p_red * self._c21)))

        final_pressure = pres_calc / 100
        return final_pressure

    @property
    def temperature(self):
        """The current temperature reading in degrees C"""
        _scaled_rawtemp = self._raw_temperature / self._temp_scale
        _temperature = _scaled_rawtemp * self._c1 + self._c0 / 2.0
        return _temperature

    @property
    def temperature_ready(self):
        """Returns true if there is a temperature reading ready"""
        return self._temp_ready

    @property
    def pressure_ready(self):
        """Returns true if pressure readings are ready"""
        return self._pressure_ready

    @property
    def mode(self):
        """The measurement mode. Must be a `Mode`. See the `Mode` documentation for details"""
        return self._mode_bits

    @mode.setter
    def mode(self, value):
        if not Mode.is_valid(value):
            raise AttributeError("mode must be an `Mode`")

        self._mode_bits = value

    @property
    def pressure_rate(self):
        """Configure the pressure measurement rate. Must be a `Rate`"""
        return self._pressure_ratebits

    @pressure_rate.setter
    def pressure_rate(self, value):
        if not Rate.is_valid(value):
            raise AttributeError("pressure_rate must be a Rate")
        self._pressure_ratebits = value

    @property
    def pressure_oversample_count(self):
        """The number of samples taken per pressure measurement. Must be a `SampleCount`"""
        return self._pressure_osbits

    @pressure_oversample_count.setter
    def pressure_oversample_count(self, value):
        if not SampleCount.is_valid(value):
            raise AttributeError("pressure_oversample_count must be a SampleCount")

        self._pressure_osbits = value
        self._pressure_shiftbit = (value > SampleCount.COUNT_8)
        self._pressure_scale = self._oversample_scalefactor[value]

    @property
    def temperature_rate(self):
        """Configure the temperature measurement rate. Must be a `Rate`"""
        return self._temp_ratebits

    @temperature_rate.setter
    def temperature_rate(self, value):
        if not Rate.is_valid(value):
            raise AttributeError("temperature_rate must be a Rate")
        self._temp_ratebits = value

    @property
    def temperature_oversample_count(self):
        """The number of samples taken per temperature measurement. Must be a `SampleCount`"""
        return self._temp_osbits

    @temperature_oversample_count.setter
    def temperature_oversample_count(self, value):
        if not SampleCount.is_valid(value):
            raise AttributeError("temperature_oversample_count must be a SampleCount")

        self._temp_osbits = value
        self._temp_scale = self._oversample_scalefactor[value]
        self._temp_shiftbit = (value > SampleCount.COUNT_8)

    @staticmethod
    def _twos_complement(val, bits):
        if val & (1 << (bits - 1)):
            val -= (1 << bits)

        return val

    def _read_calibration(self):

        while not self._coefficients_ready:
            sleep(0.001)

        buffer = bytearray(19)
        coeffs = [None]*18
        for offset in range(18):
            buffer = bytearray(2)
            buffer[0] = 0x10 + offset

            with self.i2c_device as i2c:

                i2c.write_then_readinto(buffer, buffer, out_end=1, in_start=1)

                coeffs[offset] = buffer[1]

        self._c0 = (coeffs[0] << 4) | ((coeffs[1] >> 4) & 0x0F)
        self._c0 = self._twos_complement(self._c0, 12)

        self._c1 = self._twos_complement(((coeffs[1] & 0x0F) << 8) | coeffs[2], 12)

        self._c00 = (coeffs[3] << 12) | (coeffs[4] << 4) | ((coeffs[5] >> 4) & 0x0F)
        self._c00 = self._twos_complement(self._c00, 20)

        self._c10 = ((coeffs[5] & 0x0F) << 16) | (coeffs[6] << 8) |coeffs[7]
        self._c10 = self._twos_complement(self._c10, 20)

        self._c01 = self._twos_complement((coeffs[8] << 8) | coeffs[9], 16)
        self._c11 = self._twos_complement((coeffs[10] << 8) | coeffs[11], 16)
        self._c20 = self._twos_complement((coeffs[12] << 8) | coeffs[13], 16)
        self._c21 = self._twos_complement((coeffs[14] << 8) | coeffs[15], 16)
        self._c30 = self._twos_complement((coeffs[16] << 8) | coeffs[17], 16)
Example #14
0
class INA226:
    """Driver for the INA226 current sensor"""

    # Basic API:

    # INA226( i2c_bus, i2c_addr)  Create instance of INA226 sensor
    #    :param i2c_bus          The I2C bus the INA226 is connected to
    #    :param i2c_addr (0x40)  Address of the INA226 on the bus (default 0x40)

    # shunt_voltage               RO : shunt voltage scaled to Volts
    # bus_voltage                 RO : bus voltage (V- to GND) scaled to volts (==load voltage)
    # current                     RO : current through shunt, scaled to mA
    # power                       RO : power consumption of the load, scaled to Watt

    # raw_shunt_voltage           RO : Shunt Voltage register (not scaled)
    # raw_bus_voltage             RO : Bus Voltage field in Bus Voltage register (not scaled)
    # conversion_ready            RO : Conversion Ready bit in Bus Voltage register
    # overflow                    RO : Math Overflow bit in Bus Voltage register
    # raw_power                   RO : Power register (not scaled)
    # raw_current                 RO : Current register (not scaled)
    # calibration                 RW : calibration register (note: value is cached)

    def __init__(self, i2c_bus, addr=0x40):
        self.i2c_device = I2CDevice(i2c_bus, addr)
        self.i2c_addr = addr

        # Set chip to known config values to start
        self._cal_value = 0
        self._current_lsb = 0
        self._power_lsb = 0
        self.num_averages = AVG_256
        self.bus_conv_time = VBUSCT_2MS
        self.shunt_conv_time = VSHCT_2MS

    # config register break-up
    reset = RWBits(1, _REG_CONFIG, 15, 2, False)
    num_averages = RWBits(3, _REG_CONFIG, 9, 2, False)
    bus_conv_time = RWBits(3, _REG_CONFIG, 6, 2, False)
    shunt_conv_time = RWBits(3, _REG_CONFIG, 3, 2, False)
    mode = RWBits(3, _REG_CONFIG, 0, 2, False)

    # shunt voltage register
    raw_shunt_voltage = ROUnaryStruct(_REG_SHUNTVOLTAGE, ">h")

    #bus voltage register
    raw_bus_voltage = ROUnaryStruct(_REG_BUSVOLTAGE, ">h")

    # power and current registers
    raw_power = ROUnaryStruct(_REG_POWER, ">H")
    raw_current = ROUnaryStruct(_REG_CURRENT, ">h")

    # calibration register
    _raw_calibration = UnaryStruct(_REG_CALIBRATION, ">H")

    @property
    def calibration(self):
        """Calibration register (cached value)"""
        return self._cal_value  # return cached value

    @calibration.setter
    def calibration(self, cal_value):
        self._cal_value = cal_value  # value is cached for ``current`` and ``power`` properties
        self._raw_calibration = self._cal_value

    @property
    def shunt_voltage(self):
        """The shunt voltage (between V+ and V-) in Volts (so +-.327V)"""
        # The least signficant bit is 2.5uV which is 0.0000025 volts
        return self.raw_shunt_voltage * 0.0000025

    @property
    def bus_voltage(self):
        """The bus voltage (between V- and GND) in Volts"""
        # Shift to the right 3 to drop CNVR and OVF and multiply by LSB
        # Each least signficant bit is 4mV
        return self.raw_bus_voltage * 0.00125

    @property
    def current(self):
        """The current through the shunt resistor in milliamps."""
        # Sometimes a sharp load will reset the INA226, which will
        # reset the cal register, meaning CURRENT and POWER will
        # not be available ... always setting a cal
        # value even if it's an unfortunate extra step
        self._raw_calibration = self._cal_value
        # Now we can safely read the CURRENT register!
        return self.raw_current * self._current_lsb

    @property
    def power(self):
        """The power through the load in Watt."""
        # Sometimes a sharp load will reset the INA226, which will
        # reset the cal register, meaning CURRENT and POWER will
        # not be available ... always setting a cal
        # value even if it's an unfortunate extra step
        self._raw_calibration = self._cal_value
        # Now we can safely read the CURRENT register!
        return self.raw_power * self._power_lsb
Example #15
0
class LPS35HW:  # pylint: disable=too-many-instance-attributes
    """Driver for the ST LPS35HW MEMS pressure sensor

    :param ~busio.I2C i2c_bus: The I2C bus the LPS35HW is connected to.
    :param int address: The I2C device address for the sensor. Default is :const:`0x5d`
                        but will accept :const:`0x5c` when the ``SDO`` pin is connected to Ground.


    **Quickstart: Importing and using the LPS35HW**

        Here is an example of using the :class:`LPS35HW` class.
        First you will need to import the libraries to use the sensor

        .. code-block:: python

            import board
            import adafruit_lps35hw

        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
            lps = adafruit_lps35hw.LPS35HW(i2c)

        Now you have access to the :attr:`temperature` and :attr:`pressure` attributes.
        Temperature is in Celsius and Pressure is in hPa.

        .. code-block:: python

            temperature = lps.temperature
            pressure = lps.pressure


    """

    data_rate = RWBits(3, _CTRL_REG1, 4)
    """The rate at which the sensor measures :attr:`pressure` and :attr:`temperature`.
     ``data_rate`` should be set to one of the values of :meth:`adafruit_lps35hw.DataRate`.

    .. note::
        Setting ``data_rate`` to :const:`DataRate.ONE_SHOT` places the sensor
        into a low-power shutdown         mode where measurements to update
        :attr:`pressure` and :attr:`temperature` are only taken
        when :meth:`take_measurement` is called.

    """

    low_pass_enabled = RWBit(_CTRL_REG1, 3)
    """True if the low pass filter is enabled. Setting to `True` will reduce the sensor bandwidth
    from :math:`data_rate/2` to :math:`data_rate/9`, filtering out high-frequency noise."""

    _raw_temperature = UnaryStruct(_TEMP_OUT_L, "<h")
    _raw_pressure = ROBits(24, _PRESS_OUT_XL, 0, 3)

    _block_updates = RWBit(_CTRL_REG1, 1)
    _reset = RWBit(_CTRL_REG2, 2)
    _one_shot = RWBit(_CTRL_REG2, 0)

    # INT status registers
    _pressure_low = RWBit(_INT_SOURCE, 1)
    _pressure_high = RWBit(_INT_SOURCE, 0)

    _auto_zero = RWBit(_INTERRUPT_CFG, 5)
    _reset_zero = RWBit(_INTERRUPT_CFG, 4)

    _interrupts_enabled = RWBit(_INTERRUPT_CFG, 3)
    _interrupt_latch = RWBit(_INTERRUPT_CFG, 2)
    _interrupt_low = RWBit(_INTERRUPT_CFG, 1)
    _interrupt_high = RWBit(_INTERRUPT_CFG, 0)

    _reset_filter = ROBits(8, _LPFP_RES, 0, 1)

    _chip_id = UnaryStruct(_WHO_AM_I, "<B")
    _pressure_threshold = UnaryStruct(_THS_P_L, "<H")

    def __init__(self, i2c_bus, address=0x5D):
        self.i2c_device = i2cdevice.I2CDevice(i2c_bus, address)
        if self._chip_id != 0xB1:
            raise RuntimeError("Failed to find LPS35HW! Chip ID 0x%x" %
                               self._chip_id)

        self.reset()

        # set data_rate to put the sensor in continuous mode
        self.data_rate = DataRate.RATE_10_HZ

        self._block_updates = True
        self._interrupt_latch = True

    @property
    def pressure(self):
        """The current pressure measurement in hPa"""
        # reset the filter to prevent spurious readings
        self._reset_filter  # pylint: disable=pointless-statement

        # check for negative and convert
        raw = self._raw_pressure
        if raw & (1 << 23) != 0:
            raw = raw - (1 << 24)
        return raw / 4096.0

    @property
    def temperature(self):
        """The current temperature measurement in degrees Celsius"""
        return self._raw_temperature / 100.0

    def reset(self):
        """Reset the sensor, restoring all configuration registers to their defaults"""
        self._reset = True
        # wait for the reset to finish
        while self._reset:
            pass

    def take_measurement(self):
        """Update the value of :attr:`pressure` and :attr:`temperature`
        by taking a single measurement. Only meaningful if ``data_rate``
        is set to ``ONE_SHOT``"""
        self._one_shot = True
        while self._one_shot:
            pass

    def zero_pressure(self):
        """Set the current pressure as zero and report the :attr:`pressure` relative to it"""
        self._auto_zero = True
        while self._auto_zero:
            pass

    def reset_pressure(self):
        """Reset :attr:`pressure` to be reported as the measured absolute value"""
        self._reset_zero = True

    @property
    def pressure_threshold(self):
        """The high pressure threshold. Use :attr:`high_threshold_enabled`
        or :attr:`high_threshold_enabled` to use it"""
        return self._pressure_threshold / 16

    @pressure_threshold.setter
    def pressure_threshold(self, value):
        """The high value threshold"""
        self._pressure_threshold = value * 16

    @property
    def high_threshold_enabled(self):
        """Set to `True` or `False` to enable or disable the high pressure threshold"""
        return self._interrupts_enabled and self._interrupt_high

    @high_threshold_enabled.setter
    def high_threshold_enabled(self, value):
        self._interrupts_enabled = value
        self._interrupt_high = value

    @property
    def low_threshold_enabled(self):
        """Set to `True` or `False` to enable or disable the low pressure threshold.

        .. note::
            The low pressure threshold only works in relative mode

        """
        return self._interrupts_enabled and self._interrupt_low

    @low_threshold_enabled.setter
    def low_threshold_enabled(self, value):
        self._interrupts_enabled = value
        self._interrupt_low = value

    @property
    def high_threshold_exceeded(self):
        """Returns `True` if the pressure high threshold has been exceeded.
        Must be enabled by setting :attr:`high_threshold_enabled` to `True`
        and setting a :attr:`pressure_threshold`."""
        return self._pressure_high

    @property
    def low_threshold_exceeded(self):
        """Returns `True` if the pressure low threshold has been exceeded.
        Must be enabled by setting :attr:`high_threshold_enabled`
        to `True` and setting a :attr:`pressure_threshold`."""
        return self._pressure_low
Example #16
0
class MCP9600:
    """Interface to the MCP9600 thermocouple amplifier breakout"""

    # Shutdown mode options
    NORMAL = 0b00
    SHUTDOWN = 0b01
    BURST = 0b10

    # Burst mode sample options
    BURST_SAMPLES_1 = 0b000
    BURST_SAMPLES_2 = 0b001
    BURST_SAMPLES_4 = 0b010
    BURST_SAMPLES_8 = 0b011
    BURST_SAMPLES_16 = 0b100
    BURST_SAMPLES_32 = 0b101
    BURST_SAMPLES_64 = 0b110
    BURST_SAMPLES_128 = 0b111

    # Alert temperature monitor options
    AMBIENT = 1
    THERMOCOUPLE = 0

    # Temperature change type to trigger alert. Rising is heating up. Falling is cooling down.
    RISING = 1
    FALLING = 0

    # Alert output options
    ACTIVE_HIGH = 1
    ACTIVE_LOW = 0

    # Alert mode options
    INTERRUPT = 1  # Interrupt clear option must be set when using this mode!
    COMPARATOR = 0

    # Ambient (cold-junction) temperature sensor resolution options
    AMBIENT_RESOLUTION_0_0625 = 0  # 0.0625 degrees Celsius
    AMBIENT_RESOLUTION_0_25 = 1  # 0.25 degrees Celsius

    # STATUS - 0x4
    burst_complete = RWBit(0x4, 7)
    """Burst complete."""
    temperature_update = RWBit(0x4, 6)
    """Temperature update."""
    input_range = ROBit(0x4, 4)
    """Input range."""
    alert_1 = ROBit(0x4, 0)
    """Alert 1 status."""
    alert_2 = ROBit(0x4, 1)
    """Alert 2 status."""
    alert_3 = ROBit(0x4, 2)
    """Alert 3 status."""
    alert_4 = ROBit(0x4, 3)
    """Alert 4 status."""
    # Device Configuration - 0x6
    ambient_resolution = RWBit(0x6, 7)
    """Ambient (cold-junction) temperature resolution. Options are ``AMBIENT_RESOLUTION_0_0625``
    (0.0625 degrees Celsius) or ``AMBIENT_RESOLUTION_0_25`` (0.25 degrees Celsius)."""
    burst_mode_samples = RWBits(3, 0x6, 2)
    """The number of samples taken during a burst in burst mode. Options are ``BURST_SAMPLES_1``,
    ``BURST_SAMPLES_2``, ``BURST_SAMPLES_4``, ``BURST_SAMPLES_8``, ``BURST_SAMPLES_16``,
    ``BURST_SAMPLES_32``, ``BURST_SAMPLES_64``, ``BURST_SAMPLES_128``."""
    shutdown_mode = RWBits(2, 0x6, 0)
    """Shutdown modes. Options are ``NORMAL``, ``SHUTDOWN``, and ``BURST``."""
    # Alert 1 Configuration - 0x8
    _alert_1_interrupt_clear = RWBit(0x8, 7)
    _alert_1_monitor = RWBit(0x8, 4)
    _alert_1_temp_direction = RWBit(0x8, 3)
    _alert_1_state = RWBit(0x8, 2)
    _alert_1_mode = RWBit(0x8, 1)
    _alert_1_enable = RWBit(0x8, 0)
    # Alert 2 Configuration - 0x9
    _alert_2_interrupt_clear = RWBit(0x9, 7)
    _alert_2_monitor = RWBit(0x9, 4)
    _alert_2_temp_direction = RWBit(0x9, 3)
    _alert_2_state = RWBit(0x9, 2)
    _alert_2_mode = RWBit(0x9, 1)
    _alert_2_enable = RWBit(0x9, 0)
    # Alert 3 Configuration - 0xa
    _alert_3_interrupt_clear = RWBit(0xA, 7)
    _alert_3_monitor = RWBit(0xA, 4)
    _alert_3_temp_direction = RWBit(0xA, 3)
    _alert_3_state = RWBit(0xA, 2)
    _alert_3_mode = RWBit(0xA, 1)
    _alert_3_enable = RWBit(0xA, 0)
    # Alert 4 Configuration - 0xb
    _alert_4_interrupt_clear = RWBit(0xB, 7)
    _alert_4_monitor = RWBit(0xB, 4)
    _alert_4_temp_direction = RWBit(0xB, 3)
    _alert_4_state = RWBit(0xB, 2)
    _alert_4_mode = RWBit(0xB, 1)
    _alert_4_enable = RWBit(0xB, 0)
    # Alert 1 Hysteresis - 0xc
    _alert_1_hysteresis = UnaryStruct(0xC, ">H")
    # Alert 2 Hysteresis - 0xd
    _alert_2_hysteresis = UnaryStruct(0xD, ">H")
    # Alert 3 Hysteresis - 0xe
    _alert_3_hysteresis = UnaryStruct(0xE, ">H")
    # Alert 4 Hysteresis - 0xf
    _alert_4_hysteresis = UnaryStruct(0xF, ">H")
    # Alert 1 Limit - 0x10
    _alert_1_temperature_limit = UnaryStruct(0x10, ">H")
    # Alert 2 Limit - 0x11
    _alert_2_limit = UnaryStruct(0x11, ">H")
    # Alert 3 Limit - 0x12
    _alert_3_limit = UnaryStruct(0x12, ">H")
    # Alert 4 Limit - 0x13
    _alert_4_limit = UnaryStruct(0x13, ">H")
    # Device ID/Revision - 0x20
    _device_id = ROBits(8, 0x20, 8, register_width=2, lsb_first=False)
    _revision_id = ROBits(8, 0x20, 0, register_width=2)

    types = ("K", "J", "T", "N", "S", "E", "B", "R")

    def __init__(self, i2c, address=_DEFAULT_ADDRESS, tctype="K", tcfilter=0):
        self.buf = bytearray(3)
        self.i2c_device = I2CDevice(i2c, address)
        self.type = tctype
        # is this a valid thermocouple type?
        if tctype not in MCP9600.types:
            raise Exception("invalid thermocouple type ({})".format(tctype))
        # filter is from 0 (none) to 7 (max), can limit spikes in
        # temperature readings
        tcfilter = min(7, max(0, tcfilter))
        ttype = MCP9600.types.index(tctype)

        self.buf[0] = _REGISTER_THERM_CFG
        self.buf[1] = tcfilter | (ttype << 4)
        with self.i2c_device as tci2c:
            tci2c.write(self.buf, end=2)
        if self._device_id != 0x40:
            raise RuntimeError("Failed to find MCP9600 - check wiring!")

    def alert_config(self, *, alert_number, alert_temp_source,
                     alert_temp_limit, alert_hysteresis, alert_temp_direction,
                     alert_mode, alert_state):
        """Configure a specified alert pin. Alert is enabled by default when alert is configured.
        To disable an alert pin, use ``alert_disable``.

        :param int alert_number: The alert pin number. Must be 1-4.
        :param alert_temp_source: The temperature source to monitor for the alert. Options are:
                                  ``THERMOCOUPLE`` (hot-junction) or ``AMBIENT`` (cold-junction).
                                  Temperatures are in Celsius.
        :param float alert_temp_limit: The temperature in degrees Celsius at which the alert should
                                       trigger. For rising temperatures, the alert will trigger when
                                       the temperature rises above this limit. For falling
                                       temperatures, the alert will trigger when the temperature
                                       falls below this limit.
        :param float alert_hysteresis: The alert hysteresis range. Must be 0-255 degrees Celsius.
                                       For rising temperatures, the hysteresis is below alert limit.
                                       For falling temperatures, the hysteresis is above alert
                                       limit. See data-sheet for further information.
        :param alert_temp_direction: The direction the temperature must change to trigger the alert.
                                     Options are ``RISING`` (heating up) or ``FALLING`` (cooling
                                     down).
        :param alert_mode: The alert mode. Options are ``COMPARATOR`` or ``INTERRUPT``. In
                           comparator mode, the pin will follow the alert, so if the temperature
                           drops, for example, the alert pin will go back low. In interrupt mode,
                           by comparison, once the alert goes off, you must manually clear it. If
                           setting mode to ``INTERRUPT``, use ``alert_interrupt_clear`` to clear the
                           interrupt flag.
        :param alert_state: Alert pin output state. Options are ``ACTIVE_HIGH`` or ``ACTIVE_LOW``.


        For example, to configure alert 1:

        .. code-block:: python

            import board
            import busio
            import digitalio
            import adafruit_mcp9600

            i2c = busio.I2C(board.SCL, board.SDA, frequency=100000)
            mcp = adafruit_mcp9600.MCP9600(i2c)
            alert_1 = digitalio.DigitalInOut(board.D5)
            alert_1.switch_to_input()

            mcp.alert_config(alert_number=1, alert_temp_source=mcp.THERMOCOUPLE,
                             alert_temp_limit=25, alert_hysteresis=0,
                             alert_temp_direction=mcp.RISING, alert_mode=mcp.COMPARATOR,
                             alert_state=mcp.ACTIVE_LOW)

        """
        if alert_number not in (1, 2, 3, 4):
            raise ValueError("Alert pin number must be 1-4.")
        if not 0 <= alert_hysteresis < 256:
            raise ValueError("Hysteresis value must be 0-255.")
        setattr(self, "_alert_%d_monitor" % alert_number, alert_temp_source)
        setattr(
            self,
            "_alert_%d_temperature_limit" % alert_number,
            int(alert_temp_limit / 0.0625),
        )
        setattr(self, "_alert_%d_hysteresis" % alert_number, alert_hysteresis)
        setattr(self, "_alert_%d_temp_direction" % alert_number,
                alert_temp_direction)
        setattr(self, "_alert_%d_mode" % alert_number, alert_mode)
        setattr(self, "_alert_%d_state" % alert_number, alert_state)
        setattr(self, "_alert_%d_enable" % alert_number, True)

    def alert_disable(self, alert_number):
        """Configuring an alert using ``alert_config()`` enables the specified alert by default.
        Use ``alert_disable`` to disable an alert pin.

        :param int alert_number: The alert pin number. Must be 1-4.

        """
        if alert_number not in (1, 2, 3, 4):
            raise ValueError("Alert pin number must be 1-4.")
        setattr(self, "_alert_%d_enable" % alert_number, False)

    def alert_interrupt_clear(self, alert_number, interrupt_clear=True):
        """Turns off the alert flag in the MCP9600, and clears the pin state (not used if the alert
        is in comparator mode). Required when ``alert_mode`` is ``INTERRUPT``.

        :param int alert_number: The alert pin number. Must be 1-4.
        :param bool interrupt_clear: The bit to write the interrupt state flag

        """
        if alert_number not in (1, 2, 3, 4):
            raise ValueError("Alert pin number must be 1-4.")
        setattr(self, "_alert_%d_interrupt_clear" % alert_number,
                interrupt_clear)

    @property
    def version(self):
        """ MCP9600 chip version """
        data = self._read_register(_REGISTER_VERSION, 2)
        return unpack(">xH", data)[0]

    @property
    def ambient_temperature(self):
        """ Cold junction/ambient/room temperature in Celsius """
        data = self._read_register(_REGISTER_COLD_JUNCTION, 2)
        value = unpack(">xH", data)[0] * 0.0625
        if data[1] & 0x80:
            value -= 4096
        return value

    @property
    def temperature(self):
        """ Hot junction temperature in Celsius """
        data = self._read_register(_REGISTER_HOT_JUNCTION, 2)
        value = unpack(">xH", data)[0] * 0.0625
        if data[1] & 0x80:
            value -= 4096
        return value

    @property
    def delta_temperature(self):
        """ Delta temperature in Celsius """
        data = self._read_register(_REGISTER_DELTA_TEMP, 2)
        value = unpack(">xH", data)[0] * 0.0625
        if data[1] & 0x80:
            value -= 4096
        return value

    def _read_register(self, reg, count=1):
        self.buf[0] = reg
        with self.i2c_device as i2c:
            i2c.write_then_readinto(self.buf,
                                    self.buf,
                                    out_end=count,
                                    in_start=1)
        return self.buf
Example #17
0
class LPS35HW:  # pylint: disable=too-many-instance-attributes
    """Driver for the ST LPS35HW MEMS pressure sensor

        :param ~busio.I2C i2c_bus: The I2C bus the LPS34HW is connected to.
        :param address: The I2C device address for the sensor. Default is ``0x5d`` but will accept
            ``0x5c`` when the ``SDO`` pin is connected to Ground.

    """

    data_rate = RWBits(3, _CTRL_REG1, 4)
    """The rate at which the sensor measures ``pressure`` and ``temperature``. ``data_rate`` should
    be set to one of the values of ``adafruit_lps35hw.DataRate``. Note that setting ``data_rate``
    to ``DataRate.ONE_SHOT`` places the sensor into a low-power shutdown mode where measurements to
    update ``pressure`` and ``temperature`` are only taken when ``take_measurement`` is called."""

    low_pass_enabled = RWBit(_CTRL_REG1, 3)
    """True if the low pass filter is enabled. Setting to `True` will reduce the sensor bandwidth
    from ``data_rate/2`` to ``data_rate/9``, filtering out high-frequency noise."""

    _raw_temperature = ROBits(16, _TEMP_OUT_L, 0, 2)
    _raw_pressure = ROBits(24, _PRESS_OUT_XL, 0, 3)
    _reference_pressure = RWBits(24, _REF_P_XL, 0, 3)
    _pressure_offset = RWBits(16, _RPDS_L, 0, 2)

    _block_updates = RWBit(_CTRL_REG1, 1)

    _reset = RWBit(_CTRL_REG2, 2)
    _one_shot = RWBit(_CTRL_REG2, 0)

    # registers for configuring INT pin behavior
    _interrupt_cfg = UnaryStruct(_CTRL_REG3,
                                 "<B")  # to read all values for latching?

    # INT status registers
    _interrupt_active = RWBit(_INT_SOURCE, 2)
    _pressure_low = RWBit(_INT_SOURCE, 1)
    _pressure_high = RWBit(_INT_SOURCE, 0)

    _auto_zero = RWBit(_INTERRUPT_CFG, 5)
    _reset_zero = RWBit(_INTERRUPT_CFG, 4)

    _interrupts_enabled = RWBit(_INTERRUPT_CFG, 3)
    _interrupt_latch = RWBit(_INTERRUPT_CFG, 2)
    _interrupt_low = RWBit(_INTERRUPT_CFG, 1)
    _interrupt_high = RWBit(_INTERRUPT_CFG, 0)

    _reset_filter = ROBits(8, _LPFP_RES, 0, 1)

    _chip_id = UnaryStruct(_WHO_AM_I, "<B")
    _pressure_threshold = UnaryStruct(_THS_P_L, "<H")

    def __init__(self, i2c_bus, address=0x5D):
        self.i2c_device = i2cdevice.I2CDevice(i2c_bus, address)
        if self._chip_id != 0xB1:
            raise RuntimeError("Failed to find LPS35HW! Chip ID 0x%x" %
                               self._chip_id)

        self.reset()

        # set data_rate to put the sensor in continuous mode
        self.data_rate = DataRate.RATE_10_HZ

        self._block_updates = True
        self._interrupt_latch = True

    @property
    def pressure(self):
        """The current pressure measurement in hPa"""
        # reset the filter to prevent spurious readings
        self._reset_filter  # pylint: disable=pointless-statement

        # check for negative and convert
        raw = self._raw_pressure
        if raw & (1 << 23) != 0:
            raw = raw - (1 << 24)
        return raw / 4096.0

    @property
    def temperature(self):
        """The current temperature measurement in degrees C"""
        return self._raw_temperature / 100.0

    def reset(self):
        """Reset the sensor, restoring all configuration registers to their defaults"""
        self._reset = True
        # wait for the reset to finish
        while self._reset:
            pass

    def take_measurement(self):
        """Update the value of ``pressure`` and ``temperature`` by taking a single measurement.
            Only meaningful if ``data_rate`` is set to ``ONE_SHOT``"""
        self._one_shot = True
        while self._one_shot:
            pass

    def zero_pressure(self):
        """Set the current pressure as zero and report the ``pressure`` relative to it"""
        self._auto_zero = True
        while self._auto_zero:
            pass

    def reset_pressure(self):
        """Reset ``pressure`` to be reported as the measured absolute value"""
        self._reset_zero = True

    @property
    def pressure_threshold(self):
        """The high presure threshold. Use ``high_threshold_enabled`` or  ``high_threshold_enabled``
        to use it"""
        return self._pressure_threshold / 16

    @pressure_threshold.setter
    def pressure_threshold(self, value):
        """The high value threshold"""
        self._pressure_threshold = value * 16

    @property
    def high_threshold_enabled(self):
        """Set to `True` or `False` to enable or disable the high pressure threshold"""
        return self._interrupts_enabled and self._interrupt_high

    @high_threshold_enabled.setter
    def high_threshold_enabled(self, value):
        self._interrupts_enabled = value
        self._interrupt_high = value

    @property
    def low_threshold_enabled(self):
        """Set to `True` or `False` to enable or disable the low pressure threshold. **Note the
        low pressure threshold only works in relative mode**"""
        return self._interrupts_enabled and self._interrupt_low

    @low_threshold_enabled.setter
    def low_threshold_enabled(self, value):
        self._interrupts_enabled = value
        self._interrupt_low = value

    @property
    def high_threshold_exceeded(self):
        """Returns `True` if the pressure high threshold has been exceeded. Must be enabled by
        setting ``high_threshold_enabled`` to `True` and setting a ``pressure_threshold``."""
        return self._pressure_high

    @property
    def low_threshold_exceeded(self):
        """Returns `True` if the pressure low threshold has been exceeded. Must be enabled by
        setting ``high_threshold_enabled`` to `True` and setting a ``pressure_threshold``."""
        return self._pressure_low
Example #18
0
class MLX90395:
    """Class for interfacing with the MLX90395 3-axis magnetometer"""

    _gain = RWBits(4, _REG_0, 4, 2, False)

    _resolution = RWBits(2, _REG_2, 5, 2, False)
    _filter = RWBits(3, _REG_2, 2, 2, False)
    _osr = RWBits(2, _REG_2, 0, 2, False)
    _reg0 = UnaryStruct(
        _REG_0,
        ">H",
    )
    _reg2 = UnaryStruct(_REG_2, ">H")

    def __init__(self, i2c_bus, address=_DEFAULT_ADDR):
        self.i2c_device = i2c_device.I2CDevice(i2c_bus, address)
        self._ut_lsb = None
        self._gain_val = 0
        self._buffer = bytearray(12)

        self.reset()
        self.initialize()

    def reset(self):
        """Reset the sensor to it's power-on state"""

        self._command(_REG_EX)
        self._command(_REG_EX)

        sleep(0.10)
        if self._command(_REG_RT) != _STATUS_RESET:
            raise RuntimeError("Unable to reset!")

        sleep(0.10)

    def initialize(self):
        """Configure the sensor for use"""
        self._gain_val = self.gain
        if self._gain_val == 8:  # default high field gain
            self._ut_lsb = 7.14
        else:
            self._ut_lsb = 2.5  # medium field gain

    @property
    def resolution(self):
        """The current resolution setting for the magnetometer"""
        return self._resolution

    @resolution.setter
    def resolution(self, value):
        if not Resolution.is_valid(value):
            raise AttributeError("resolution must be a Resolution")
        self._resolution = value

    @property
    def gain(self):
        """The gain applied to the magnetometer's ADC."""
        return self._gain

    @gain.setter
    def gain(self, value):
        if not Gain.is_valid(value):
            raise AttributeError("gain must be a valid value")
        self._gain = value
        self._gain_val = value

    def _command(self, command_id):

        buffer = bytearray([0x80, command_id])
        with self.i2c_device as i2c:
            i2c.write_then_readinto(buffer, buffer, in_end=1)
        return buffer[0]

    @property
    def magnetic(self):
        """The processed magnetometer sensor values.
        A 3-tuple of X, Y, Z axis values in microteslas that are signed floats.
        """
        if self._command(_REG_SM | 0x0F) != _STATUS_SMMODE:
            raise RuntimeError("Unable to initiate a single reading")
        res = self._read_measurement()
        while res is None:
            sleep(0.001)
            res = self._read_measurement()

        return res

    def _read_measurement(self):

        # clear the buffer
        for i in range(len(self._buffer)):  # pylint: disable=consider-using-enumerate
            self._buffer[i] = 0
        self._buffer[0] = 0x80  # read memory command

        with self.i2c_device as i2c:
            i2c.write_then_readinto(self._buffer, self._buffer, out_end=1)

        if self._buffer[0] != _STATUS_DRDY:
            return None

        x_raw, y_raw, z_raw = unpack_from(">hhh", self._buffer, offset=2)

        scalar = GAIN_AMOUNT[self._gain_val] * self._ut_lsb
        return (x_raw * scalar, y_raw * scalar, z_raw * scalar)

    @property
    def oversample_rate(self):
        """The number of times that the measurements are re-sampled and averaged to reduce noise"""
        return self._osr

    @oversample_rate.setter
    def oversample_rate(self, value):
        if not OSR.is_valid(value):
            raise AttributeError("oversample_rate must be an OSR")
        self._osr = value
class VCNL4040:  # pylint: disable=too-few-public-methods
    """Driver for the VCNL4040 proximity and ambient light sensor.

    :param busio.I2C i2c_bus: The I2C bus the VCNL4040 is connected to.

    """

    # Ambient light sensor integration times
    ALS_80MS = const(0x0)
    ALS_160MS = const(0x1)
    ALS_320MS = const(0x2)
    ALS_640MS = const(0x3)

    # Proximity sensor integration times
    PS_1T = const(0x0)
    PS_1_5T = const(0x1)
    PS_2T = const(0x2)
    PS_2_5T = const(0x3)
    PS_3T = const(0x4)
    PS_3_5T = const(0x5)
    PS_4T = const(0x6)
    PS_8T = const(0x7)

    # LED current settings
    LED_50MA = const(0x0)
    LED_75MA = const(0x1)
    LED_100MA = const(0x2)
    LED_120MA = const(0x3)
    LED_140MA = const(0x4)
    LED_160MA = const(0x5)
    LED_180MA = const(0x6)
    LED_200MA = const(0x7)

    # LED duty cycle settings
    LED_1_40 = const(0x0)
    LED_1_80 = const(0x1)
    LED_1_160 = const(0x2)
    LED_1_320 = const(0x3)

    # Proximity sensor interrupt enable/disable options
    PS_INT_DISABLE = const(0x0)
    PS_INT_CLOSE = const(0x1)
    PS_INT_AWAY = const(0x2)
    PS_INT_CLOSE_AWAY = const(0x3)

    # Offsets into interrupt status register for different types
    ALS_IF_L = const(0x0D)
    ALS_IF_H = const(0x0C)
    PS_IF_CLOSE = const(0x09)
    PS_IF_AWAY = const(0x08)

    # ID_LM - Device ID, address
    _device_id = UnaryStruct(0x0C, "<H")
    """The device ID."""

    # PS_Data_LM - PS output data
    proximity = ROUnaryStruct(0x08, "<H")
    """Proximity data.

    This example prints the proximity data. Move your hand towards the sensor to see the values
    change.

    .. code-block:: python

        import time
        import board
        import busio
        import adafruit_vcnl4040

        i2c = busio.I2C(board.SCL, board.SDA)
        sensor = adafruit_vcnl4040.VCNL4040(i2c)

        while True:
            print("Proximity:", sensor.proximity)
            time.sleep(0.1)
    """

    # PS_CONF1 - PS duty ratio, integration time, persistence, enable/disable
    # PS_CONF2 - PS output resolution selection, interrupt trigger method
    # PS_CONF3 - PS smart persistence, active force mode
    proximity_shutdown = RWBit(0x03, 0, register_width=2)
    """Proximity sensor shutdown. When ``True``, proximity data is disabled."""
    proximity_integration_time = RWBits(3, 0x03, 1, register_width=2)
    """Proximity sensor integration time setting. Integration times are 1T, 1.5T, 2T, 2.5T, 3T,
    3.5T, 4T, and 8T. Options are: PS_1T, PS_1_5T, PS_2T, PS_2_5T, PS_3T, PS_3_5T, PS_4T, PS_8T.
    """
    proximity_interrupt = RWBits(2, 0x03, 8, register_width=2)
    """Interrupt enable. Interrupt setting are close, away, close and away, or disabled. Options
    are: PS_INT_DISABLE, PS_INT_CLOSE, PS_INT_AWAY, PS_INT_CLOSE_AWAY."""
    proximity_bits = RWBit(0x03, 11, register_width=2)
    """Proximity data output setting. ``0`` when proximity sensor output is 12 bits, ``1`` when
    proximity sensor output is 16 bits."""

    # PS_THDL_LM - PS low interrupt threshold setting
    proximity_low_threshold = UnaryStruct(0x06, "<H")
    """Proximity sensor interrupt low threshold setting."""
    # PS_THDH_LM - PS high interrupt threshold setting
    proximity_high_threshold = UnaryStruct(0x07, "<H")
    """Proximity sensor interrupt high threshold setting."""

    interrupt_state = ROUnaryStruct(0x0B, "<H")

    # INT_FLAG - PS interrupt flag
    @property
    def proximity_high_interrupt(self):
        """If interrupt is set to ``PS_INT_CLOSE`` or ``PS_INT_CLOSE_AWAY``, trigger event when
        proximity rises above high threshold interrupt."""
        return self._get_and_clear_cached_interrupt_state(self.PS_IF_CLOSE)

    @property
    def proximity_low_interrupt(self):
        """If interrupt is set to ``PS_INT_AWAY`` or ``PS_INT_CLOSE_AWAY``, trigger event when
        proximity drops below low threshold."""
        return self._get_and_clear_cached_interrupt_state(self.PS_IF_AWAY)

    led_current = RWBits(3, 0x04, 8, register_width=2)
    """LED current selection setting, in mA. Options are LED_50MA, LED_75MA, LED_100MA, LED_120MA,
    LED_140MA, LED_160MA, LED_180MA, LED_200MA."""

    led_duty_cycle = RWBits(2, 0x03, 6, register_width=2)
    """Proximity sensor LED duty ratio setting. Ratios are 1/40, 1/80, 1/160, and 1/320. Options
    are: LED_1_40, LED_1_80, LED_1_160, LED_1_320."""

    light = ROUnaryStruct(0x09, "<H")
    """Raw ambient light data. The raw ambient light data which will change with integration time
    and gain settings changes. Use ``lux`` to get the correctly scaled value for the current
    integration time and gain settings
    """

    @property
    def lux(self):
        """Ambient light data in lux. Represents the raw sensor data scaled according to the current
        integration time and gain settings.

        This example prints the ambient light data. Cover the sensor to see the values change.

        .. code-block:: python

            import time
            import board
            import busio
            import adafruit_vcnl4040

            i2c = busio.I2C(board.SCL, board.SDA)
            sensor = adafruit_vcnl4040.VCNL4040(i2c)

            while True:
                print("Ambient light: %.2f lux"%sensor.lux)
                time.sleep(0.1)
        """
        return self.light * (0.1 / (1 << self.light_integration_time))

    # ALS_CONF - ALS integration time, persistence, interrupt, function enable/disable
    light_shutdown = RWBit(0x00, 0, register_width=2)
    """Ambient light sensor shutdown. When ``True``, ambient light data is disabled."""

    _light_integration_time = RWBits(2, 0x00, 6, register_width=2)

    @property
    def light_integration_time(self):
        """Ambient light sensor integration time setting. Longer time has higher sensitivity.
        Can be: ALS_80MS, ALS_160MS, ALS_320MS or ALS_640MS.

        This example sets the ambient light integration time to 640ms and prints the ambient light
        sensor data.

        .. code-block:: python

            import time
            import board
            import busio
            import adafruit_vcnl4040

            i2c = busio.I2C(board.SCL, board.SDA)
            sensor = adafruit_vcnl4040.VCNL4040(i2c)

            sensor.light_integration_time = sensor.ALS_640MS

            while True:
                print("Ambient light:", sensor.light)
        """
        return self._light_integration_time

    @light_integration_time.setter
    def light_integration_time(self, new_it):
        from time import sleep  # pylint: disable=import-outside-toplevel

        # IT values are in 0-3 -> 80-640ms
        old_it_ms = (8 << self._light_integration_time) * 10
        new_it_ms = (8 << new_it) * 10
        it_delay_seconds = (old_it_ms + new_it_ms + 1) * 0.001

        self._light_integration_time = new_it
        sleep(it_delay_seconds)

    light_interrupt = RWBit(0x00, 1, register_width=2)
    """Ambient light sensor interrupt enable. ``True`` to enable, and ``False`` to disable."""

    # ALS_THDL_LM - ALS low interrupt threshold setting
    light_low_threshold = UnaryStruct(0x02, "<H")
    """Ambient light interrupt low threshold."""
    # ALS_THDH_LM - ALS high interrupt threshold setting
    light_high_threshold = UnaryStruct(0x01, "<H")
    """Ambient light interrupt high threshold."""
    # INT_FLAG - ALS interrupt flag

    @property
    def light_high_interrupt(self):
        """High interrupt event. Triggered when ambient light value exceeds high threshold."""
        return self._get_and_clear_cached_interrupt_state(self.ALS_IF_H)

    @property
    def light_low_interrupt(self):
        """Low interrupt event. Triggered when ambient light value drops below low threshold."""
        return self._get_and_clear_cached_interrupt_state(self.ALS_IF_L)

    _raw_white = ROUnaryStruct(0x0A, "<H")

    @property
    def white(self):
        """White light data scaled according to the current integration time and gain settings.

        This example prints the white light data. Cover the sensor to see the values change.

        .. code-block:: python

            import time
            import board
            import busio
            import adafruit_vcnl4040

            i2c = busio.I2C(board.SCL, board.SDA)
            sensor = adafruit_vcnl4040.VCNL4040(i2c)

            while True:
                print("White light:", sensor.white)
                time.sleep(0.1)
        """
        return self._raw_white * (0.1 / (1 << self.light_integration_time))

    # PS_MS - White channel enable/disable, PS mode, PS protection setting, LED current
    # White_EN - PS_MS_H, 7th bit - White channel enable/disable
    white_shutdown = RWBit(0x04, 15, register_width=2)
    """White light channel shutdown. When ``True``, white light data is disabled."""

    def __init__(self, i2c, address=0x60):
        self.i2c_device = i2cdevice.I2CDevice(i2c, address)
        if self._device_id != 0x186:
            raise RuntimeError("Failed to find VCNL4040 - check wiring!")

        self.cached_interrupt_state = {
            self.ALS_IF_L: False,
            self.ALS_IF_H: False,
            self.PS_IF_CLOSE: False,
            self.PS_IF_AWAY: False,
        }

        self.proximity_shutdown = False
        self.light_shutdown = False
        self.white_shutdown = False

    def _update_interrupt_state(self):
        interrupts = [
            self.PS_IF_AWAY, self.PS_IF_CLOSE, self.ALS_IF_H, self.ALS_IF_L
        ]
        new_interrupt_state = self.interrupt_state
        for interrupt in interrupts:
            new_state = new_interrupt_state & (1 << interrupt) > 0
            if new_state:
                self.cached_interrupt_state[interrupt] = new_state

    def _get_and_clear_cached_interrupt_state(self, interrupt_offset):
        self._update_interrupt_state()
        new_interrupt_state = self.cached_interrupt_state[interrupt_offset]
        self.cached_interrupt_state[interrupt_offset] = False

        return new_interrupt_state
class APDS9500:
    """Library for the APDS9500 Gesture Sensor.

        :param ~busio.I2C i2c_bus: The I2C bus the APDS9500 is connected to.
        :param address: The I2C slave address of the sensor

    """

    # # endian/format helper
    # # b signed, 1 byte :: B unsigned 1 byte
    # # h signed, 2 bytes :: H unsigned 2 bytes
    # _raw_example_data = Struct(_APDS9500_EXAMPLE_XOUT_H, ">hhh")
    # _bank = RWBits(2, _APDS9500_REG_BANK_SEL, 4)
    # _reset = RWBit(_APDS9500_PWR_MGMT_1, 7)

    reg_bank_set = UnaryStruct(APDS9500_R_RegBankSet, ">B")
    cursor_clamp_left = UnaryStruct(APDS9500_R_CursorClampLeft, ">B")
    cursor_clamp_right = UnaryStruct(APDS9500_R_CursorClampRight, ">B")
    cursor_clamp_up = UnaryStruct(APDS9500_R_CursorClampUp, ">B")
    int2_en = UnaryStruct(APDS9500_R_Int2_En, ">B")
    ae_led_off_ub = UnaryStruct(APDS9500_R_AELedOff_UB, ">B")
    ae_led_off_lb = UnaryStruct(APDS9500_R_AELedOff_LB, ">B")
    ae_exposure_ub_l = UnaryStruct(APDS9500_R_AE_Exposure_UB_L, ">B")
    ae_exposure_ub_h = UnaryStruct(APDS9500_R_AE_Exposure_UB_H, ">B")
    ae_exposure_lb_l = UnaryStruct(APDS9500_R_AE_Exposure_LB_L, ">B")
    ae_gain_lb = UnaryStruct(APDS9500_R_AE_Gain_LB, ">B")
    manual = UnaryStruct(APDS9500_R_Manual, ">B")
    unknown_1 = UnaryStruct(APDS9500_unknown_1, ">B")
    unknown_2 = UnaryStruct(APDS9500_unknown_2, ">B")
    apds9500_input_mode_gpio_0_1 = UnaryStruct(APDS9500_InputMode_GPIO_0_1,
                                               ">B")
    apds9500_input_mode_gpio_2_3 = UnaryStruct(APDS9500_InputMode_GPIO_2_3,
                                               ">B")
    apds9500_input_mode_int = UnaryStruct(APDS9500_InputMode_INT, ">B")
    cursor_object_size_th = UnaryStruct(APDS9500_R_Cursor_ObjectSizeTh, ">B")
    no_motion_count_thd = UnaryStruct(APDS9500_R_NoMotionCountThd, ">B")
    z_direction_thd = UnaryStruct(APDS9500_R_ZDirectionThd, ">B")
    z_direction_xy_thd = UnaryStruct(APDS9500_R_ZDirectionXYThd, ">B")
    z_direction_angle_thd = UnaryStruct(APDS9500_R_ZDirectionAngleThd, ">B")
    rotate_xy_thd = UnaryStruct(APDS9500_R_RotateXYThd, ">B")
    filter = UnaryStruct(APDS9500_R_Filter, ">B")
    filter_image = UnaryStruct(APDS9500_R_FilterImage, ">B")
    yto_z_sum = UnaryStruct(APDS9500_R_YtoZSum, ">B")
    yto_z_factor = UnaryStruct(APDS9500_R_YtoZFactor, ">B")
    filter_length = UnaryStruct(APDS9500_R_FilterLength, ">B")
    wave_thd = UnaryStruct(APDS9500_R_WaveThd, ">B")
    abort_count_thd = UnaryStruct(APDS9500_R_AbortCountThd, ">B")
    reg_bank_set = UnaryStruct(APDS9500_R_RegBankSet, ">B")

    cmd_h_start = UnaryStruct(APDS9500_Cmd_HStart, ">B")
    cmd_v_start = UnaryStruct(APDS9500_Cmd_VStart, ">B")
    cmd_hv = UnaryStruct(APDS9500_Cmd_HV, ">B")
    lens_shading_comp_en_h = UnaryStruct(APDS9500_R_LensShadingComp_EnH, ">B")
    offest_y = UnaryStruct(APDS9500_R_Offest_Y, ">B")
    lsc = UnaryStruct(APDS9500_R_LSC, ">B")
    lsft = UnaryStruct(APDS9500_R_LSFT, ">B")
    cursor_clamp_center_y_h = UnaryStruct(APDS9500_R_CursorClampCenterY_H,
                                          ">B")
    unknown_1 = UnaryStruct(APDS9500_unknown_1, ">B")
    idle_time_l = UnaryStruct(APDS9500_R_IDLE_TIME_L, ">B")
    idle_time_sleep_1_l = UnaryStruct(APDS9500_R_IDLE_TIME_SLEEP_1_L, ">B")
    idle_time_sleep_2_l = UnaryStruct(APDS9500_R_IDLE_TIME_SLEEP_2_L, ">B")
    idle_time_sleep_2_h = UnaryStruct(APDS9500_R_IDLE_TIME_SLEEP_2_H, ">B")
    object_time_2_l = UnaryStruct(APDS9500_R_Object_TIME_2_L, ">B")
    object_time_2_h = UnaryStruct(APDS9500_R_Object_TIME_2_H, ">B")
    tg_en_h = UnaryStruct(APDS9500_R_TG_EnH, ">B")
    auto_sleep_mode = UnaryStruct(APDS9500_R_Auto_SLEEP_Mode, ">B")
    wake_up_sig_sel = UnaryStruct(APDS9500_R_Wake_Up_Sig_Sel, ">B")
    sram_read_en_h = UnaryStruct(APDS9500_R_SRAM_Read_EnH, ">B")

    # readers
    gestures_enabled = UnaryStruct(APDS9500_R_GestureDetEn, ">B")
    gesture_result_register = UnaryStruct(APDS9500_GestureResult, ">B")
    gesture_result = ROBits(4, APDS9500_GestureResult, 0)

    # 0 GestureResult 0xB6 3:0 - R Gesture result

    int_flag_1 = UnaryStruct(APDS9500_Int_Flag_1, ">B")
    int_flag_2 = UnaryStruct(APDS9500_Int_Flag_2, ">B")

    def __init__(self, i2c_bus, address=APDS9500_DEFAULT_ADDRESS):
        self.i2c_device = i2c_device.I2CDevice(i2c_bus, address)

        self.reg_bank_set = 0x00
        self.reg_bank_set = 0x00
        self.reg_bank_set = 0x0

        self.cursor_clamp_left = 0x7
        self.cursor_clamp_right = 0x17
        self.cursor_clamp_up = 0x6

        self.int2_en = 0x1

        self.ae_led_off_ub = 0x2D
        self.ae_led_off_lb = 0xF
        self.ae_exposure_ub_l = 0x3C
        self.ae_exposure_ub_h = 0x0
        self.ae_exposure_lb_l = 0x1E
        self.ae_gain_lb = 0x20

        self.manual = 0x10
        self.unkown_1 = 0x10
        self.unknown_2 = 0x27

        self.apds9500_input_mode_gpio_0_1 = 0x42
        self.apds9500_input_mode_gpio_2_3 = 0x44
        self.apds9500_input_mode_int = 0x4

        self.cursor_object_size_th = 0x1

        self.no_motion_count_thd = 0x6

        self.z_direction_thd = 0xA
        self.z_direction_xy_thd = 0xC
        self.z_direction_angle_thd = 0x5

        self.rotate_xy_thd = 0x14

        self.filter = 0x3F
        self.filter_image = 0x19

        self.yto_z_sum = 0x19
        self.yto_z_factor = 0xB

        self.filter_length = 0x3

        self.wave_thd = 0x64
        self.abort_count_thd = 0x21
        self.reg_bank_set = 0x1

        self.cmd_h_start = 0xF
        self.cmd_v_start = 0x10
        self.cmd_hv = 0x2

        self.lens_shading_comp_en_h = 0x1
        self.offest_y = 0x39
        self.lsc = 0x7F
        self.lsft = 0x8
        self.cursor_clamp_center_y_h = 0xFF
        self.unknown_1 = 0x3D

        self.idle_time_l = 0x96
        self.idle_time_sleep_1_l = 0x97
        self.idle_time_sleep_2_l = 0xCD
        self.idle_time_sleep_2_h = 0x1

        self.object_time_2_l = 0x2C
        self.object_time_2_h = 0x1

        self.tg_en_h = 0x1
        self.auto_sleep_mode = 0x35
        self.wake_up_sig_sel = 0x0
        self.sram_read_en_h = 0x1

        self.reg_bank_set = 0x00

    @property
    def gestures(self):
        """Returns a list of gestures that were detected. Results are `Gesture` types"""
        # pylint:disable=no-member
        detected_gestures = []
        gesture_flag = self.int_flag_1

        for g_flag in Gesture.string:
            if gesture_flag & g_flag:
                detected_gestures.append(g_flag)

        return detected_gestures
Example #21
0
class PCT2075:
    """Driver for the PCT2075 Digital Temperature Sensor and Thermal Watchdog.

    :param ~busio.I2C i2c_bus: The I2C bus the PCT2075 is connected to.
    :param int address: The I2C device address. Default is :const:`0x37`

    **Quickstart: Importing and using the PCT2075 temperature sensor**

        Here is an example of using the :class:`PCT2075` class.
        First you will need to import the libraries to use the sensor

        .. code-block:: python

            import board
            import adafruit_pct2075

        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
            pct = adafruit_pct2075.PCT2075(i2c)

        Now you have access to the temperature using the attribute :attr:`temperature`.

        .. code-block:: python

            temperature = pct.temperature

    """
    def __init__(self,
                 i2c_bus: I2C,
                 address: int = PCT2075_DEFAULT_ADDRESS) -> None:
        self.i2c_device = i2cdevice.I2CDevice(i2c_bus, address)

    _temperature = ROUnaryStruct(PCT2075_REGISTER_TEMP, ">h")
    mode = RWBit(PCT2075_REGISTER_CONFIG, 1, register_width=1)
    """Sets the alert mode. In comparator mode, the sensor acts like a thermostat and will activate
    the INT pin according to `high_temp_active_high` when an alert is triggered. The INT pin will be
    deactivated when the temperature falls below :attr:`temperature_hysteresis`.
    In interrupt mode the INT pin is activated once when a temperature fault
    is detected, and once more when the     temperature falls below
    :attr:`temperature_hysteresis`. In interrupt mode, the alert is cleared by
    reading a property"""

    shutdown = RWBit(PCT2075_REGISTER_CONFIG, 0, 1)
    """Set to True to turn off the temperature measurement circuitry in the sensor. While shut down
    the configurations properties can still be read or written but the temperature will not be
    measured"""
    _fault_queue_length = RWBits(2,
                                 PCT2075_REGISTER_CONFIG,
                                 3,
                                 register_width=1)
    _high_temperature_threshold = UnaryStruct(PCT2075_REGISTER_TOS, ">h")
    _temp_hysteresis = UnaryStruct(PCT2075_REGISTER_THYST, ">h")
    _idle_time = RWBits(5, PCT2075_REGISTER_TIDLE, 0, register_width=1)
    high_temp_active_high = RWBit(PCT2075_REGISTER_CONFIG, 2, register_width=1)
    """Sets the alert polarity. When False the INT pin will be tied to ground when an alert is
    triggered. If set to True it will be disconnected from ground when an alert is triggered."""

    @property
    def temperature(self) -> float:
        """Returns the current temperature in degrees Celsius.
        Resolution is 0.125 degrees Celsius"""
        return (self._temperature >> 5) * 0.125

    @property
    def high_temperature_threshold(self) -> float:
        """The temperature in degrees celsius that will trigger an alert on the INT pin if it is
        exceeded. Resolution is 0.5 degrees Celsius"""
        return (self._high_temperature_threshold >> 7) * 0.5

    @high_temperature_threshold.setter
    def high_temperature_threshold(self, value: float) -> None:
        self._high_temperature_threshold = int(value * 2) << 7

    @property
    def temperature_hysteresis(self) -> float:
        """The temperature hysteresis value defines the bottom
        of the temperature range in degrees Celsius in which
        the temperature is still considered high.
        :attr:`temperature_hysteresis` must be lower than
        :attr:`high_temperature_threshold`.
        Resolution is 0.5 degrees Celsius
        """
        return (self._temp_hysteresis >> 7) * 0.5

    @temperature_hysteresis.setter
    def temperature_hysteresis(self, value: float) -> None:
        if value >= self.high_temperature_threshold:
            raise ValueError(
                "temperature_hysteresis must be less than high_temperature_threshold"
            )
        self._temp_hysteresis = int(value * 2) << 7

    @property
    def faults_to_alert(self) -> int:
        """The number of consecutive high temperature faults required to raise an alert. An fault
        is tripped each time the sensor measures the temperature to be greater than
        :attr:`high_temperature_threshold`. The rate at which the sensor measures the temperature
        is defined by :attr:`delay_between_measurements`.
        """

        return self._fault_queue_length

    @faults_to_alert.setter
    def faults_to_alert(self, value: int) -> None:
        if value > 4 or value < 1:
            raise ValueError(
                "faults_to_alert must be an adafruit_pct2075.FaultCount")
        self._fault_queue_length = value

    @property
    def delay_between_measurements(self) -> int:
        """The amount of time between measurements made by the sensor in milliseconds. The value
        must be between 100 and 3100 and a multiple of 100"""
        return self._idle_time * 100

    @delay_between_measurements.setter
    def delay_between_measurements(self, value: int) -> None:
        if value > 3100 or value < 100 or value % 100 > 0:
            raise AttributeError(
                """"delay_between_measurements must be >= 100 or <= 3100\
            and a multiple of 100""")
        self._idle_time = int(value / 100)
Example #22
0
class PCT2075:
    """Driver for the PCT2075 Digital Temperature Sensor and Thermal Watchdog.
    :param ~busio.I2C i2c_bus: The I2C bus the PCT2075 is connected to.
    :param address: The I2C device address for the sensor. Default is ``0x37``.
    """
    def __init__(self, i2c_bus, address=PCT2075_DEFAULT_ADDRESS):
        self.i2c_device = i2cdevice.I2CDevice(i2c_bus, address)

    _temperature = ROUnaryStruct(PCT2075_REGISTER_TEMP, ">h")
    mode = RWBit(PCT2075_REGISTER_CONFIG, 1, register_width=1)
    """Sets the alert mode. In comparitor mode, the sensor acts like a thermostat and will activate
    the INT pin according to `high_temp_active_high` when an alert is triggered. The INT pin will be
    deactiveated when the temperature falls below `temperature_hysteresis`. In interrupt mode the
    INT pin is activated once when a temperature fault is detected, and once more when the
    temperature falls below `temperature_hysteresis`. In interrupt mode, the alert is cleared by
    reading a property"""

    shutdown = RWBit(PCT2075_REGISTER_CONFIG, 0, 1)
    """Set to True to turn off the temperature measurement circuitry in the sensor. While shut down
    the configurations properties can still be read or written but the temperature will not be
    measured"""
    _fault_queue_length = RWBits(2,
                                 PCT2075_REGISTER_CONFIG,
                                 3,
                                 register_width=1)
    _high_temperature_threshold = UnaryStruct(PCT2075_REGISTER_TOS, ">h")
    _temp_hysteresis = UnaryStruct(PCT2075_REGISTER_THYST, ">h")
    _idle_time = RWBits(5, PCT2075_REGISTER_TIDLE, 0, register_width=1)
    high_temp_active_high = RWBit(PCT2075_REGISTER_CONFIG, 2, register_width=1)
    """Sets the alert polarity. When False the INT pin will be tied to ground when an alert is
    triggered. If set to True it will be disconnected from ground when an alert is triggered."""

    @property
    def temperature(self):
        """Returns the current temperature in degress celsius. Resolution is 0.125 degrees C"""
        return (self._temperature >> 5) * 0.125

    @property
    def high_temperature_threshold(self):
        """The temperature in degrees celsius that will trigger an alert on the INT pin if it is
        exceeded. Resolution is 0.5 degrees C."""
        return (self._high_temperature_threshold >> 7) * 0.5

    @high_temperature_threshold.setter
    def high_temperature_threshold(self, value):
        self._high_temperature_threshold = int(value * 2) << 7

    @property
    def temperature_hysteresis(self):
        """The temperature hysteresis value defines the bottom of the temperature range in degrees
        C in which the temperature is still considered high". `temperature_hysteresis` must be
        lower than `high_temperature_threshold`. Resolution is 0.5 degrees C.
        """
        return (self._temp_hysteresis >> 7) * 0.5

    @temperature_hysteresis.setter
    def temperature_hysteresis(self, value):
        if value >= self.high_temperature_threshold:
            raise ValueError(
                "temperature_hysteresis must be less than high_temperature_threshold"
            )
        self._temp_hysteresis = int(value * 2) << 7

    @property
    def faults_to_alert(self):
        """The number of consecutive high temperature faults required to raise an alert. An fault
        is tripped each time the sensor measures the temperature to be greater than
        `high_temperature_threshold`. The rate at which the sensor measures the temperature
        is defined by `delay_between_measurements`.
        """

        return self._fault_queue_length

    @faults_to_alert.setter
    def faults_to_alert(self, value):
        if value > 4 or value < 1:
            raise ValueError(
                "faults_to_alert must be an adafruit_pct2075.FaultCount")
        self._fault_queue_length = value

    @property
    def delay_between_measurements(self):
        """The amount of time between measurements made by the sensor in milliseconds. The value
        must be between 100 and 3100 and a multiple of 100"""
        return self._idle_time * 100

    @delay_between_measurements.setter
    def delay_between_measurements(self, value):
        if value > 3100 or value < 100 or value % 100 > 0:
            raise AttributeError(
                """"delay_between_measurements must be >= 100 or <= 3100\
            and a multiple of 100""")
        self._idle_time = int(value / 100)
Example #23
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
Example #24
0
class Trackball(object):
    """
    Initialise the Trackball chip at ``address`` on ``i2c_bus``.
    :param ~busio.I2C i2c_bus: The I2C bus which the Trackball is connected to.
    :param int address: The I2C address of the Trackball.

    Usage:

        import time
        from board import I2C
        from pimoroni_trackball import Trackball

        i2c = I2C() 

        trackball = Trackball( i2c )

        trackball.set_rgbw(0, 127, 127, 0)

        r, g, b, w = trackball.rgbw
        print("Red: {:02d} Green: {:02d} Blue: {:02d} White: {:02d}".format(r,g,b,w))
        while True:
            up, down, left, right, switch, state = trackball.read()
            print("r: {:02d} u: {:02d} d: {:02d} l: {:02d} switch: {:03d} state: {}".format(right, up, down, left, switch, state))
            time.sleep(0.200)

    """
    CHIP_ID = 0xBA11

    # Registers:
    # LEDs
    REG_LED_RED = UnaryStruct(0x00, "<B")
    REG_LED_GRN = UnaryStruct(0x01, "<B")
    REG_LED_BLU = UnaryStruct(0x02, "<B")
    REG_LED_WHT = UnaryStruct(0x03, "<B")

    # Directions
    REG_LEFT = UnaryStruct(0x04, "<B")
    REG_RIGHT = UnaryStruct(0x05, "<B")
    REG_UP = UnaryStruct(0x06, "<B")
    REG_DOWN = UnaryStruct(0x07, "<B")
    REG_SWITCH = UnaryStruct(0x08, "<B")

    # Misc
    REG_INT = UnaryStruct(0xF9, "<B")
    # TODO: maybe use CTRL reg + MSK_CTRL_RESET to reset the device on teardown?
    REG_CHIP_ID_L = UnaryStruct(0xFA, "<B")
    REG_CHIP_ID_H = UnaryStruct(0xFB, "<B")

    # Bit Masks:
    MSK_SWITCH_STATE = 0b10000000
    MSK_INT_TRIGGERED = 0b00000001
    MSK_INT_OUT_EN = 0b00000010
    MSK_CTRL_SLEEP = 0b00000001
    MSK_CTRL_RESET = 0b00000010
    MSK_CTRL_FREAD = 0b00000100
    MSK_CTRL_FWRITE = 0b00001000

    def __init__(self, i2c_bus, address=0x0A, timeout=5):
        self.i2c_device = i2c_device.I2CDevice(i2c_bus, address)
        self._timeout = timeout

        chip_id = (self.REG_CHIP_ID_H << 8) + self.REG_CHIP_ID_L
        if chip_id != self.CHIP_ID:
            raise Exception(
                "Invalid chip ID: 0x {: 04X}, expected 0x {: 04X}".format(
                    chip_id, self.CHIP_ID))

        self.enable_interrupt()

    def enable_interrupt(self, interrupt=True):
        value = self.REG_INT
        value = value & (0xFF ^ self.MSK_INT_OUT_EN)
        if interrupt:
            value = value | self.MSK_INT_OUT_EN
        self.REG_INT = value

    def get_interrupt(self):
        """Get the trackball interrupt status."""
        # Only support the software interrupt version
        data = self.REG_INT
        return (data & self.MSK_INT_TRIGGERED) == self.MSK_INT_TRIGGERED

    def set_red(self, value):
        """Set brightness of trackball red LED."""
        self.REG_LED_RED = value & 0xff

    def set_green(self, value):
        """Set brightness of trackball green LED."""
        self.REG_LED_GRN = value & 0xff

    def set_blue(self, value):
        """Set brightness of trackball blue LED."""
        self.REG_LED_BLU = value & 0xff

    def set_white(self, value):
        """Set brightness of trackball white LED."""
        self.REG_LED_WHT = value & 0xff

    @property
    def rgbw(self):
        return self.REG_LED_RED, self.REG_LED_GRN, self.REG_LED_BLU, self.REG_LED_WHT

    def set_rgbw(self, r, g, b, w):
        """Set all LED brightness as RGBW."""
        self.set_red(r)
        self.set_green(g)
        self.set_blue(b)
        self.set_white(w)

    def read(self):
        """Read up, down, left, right and switch data from trackball."""
        switch, switch_state = self.REG_SWITCH & (
            0xFF ^ self.MSK_SWITCH_STATE), (
                self.REG_SWITCH
                & self.MSK_SWITCH_STATE) == self.MSK_SWITCH_STATE
        return self.REG_UP, self.REG_DOWN, self.REG_LEFT, self.REG_RIGHT, switch, switch_state

    def reset(self):
        """Reset the chip."""
        pass

    def __enter__(self):
        return self

    def __exit__(self, exception_type, exception_value, traceback):
        self.deinit()

    def deinit(self):
        """Stop using the trackball."""
        self.reset()
class INA219:
    """Driver for the INA219 current sensor"""

    # Basic API:

    # INA219( i2c_bus, addr)  Create instance of INA219 sensor
    #    :param i2c_bus          The I2C bus the INA219is connected to
    #    :param addr (0x40)      Address of the INA219 on the bus (default 0x40)

    # shunt_voltage               RO : shunt voltage scaled to Volts
    # bus_voltage                 RO : bus voltage (V- to GND) scaled to volts (==load voltage)
    # current                     RO : current through shunt, scaled to mA
    # power                       RO : power consumption of the load, scaled to Watt
    # set_calibration_32V_2A()    Initialize chip for 32V max and up to 2A (default)
    # set_calibration_32V_1A()    Initialize chip for 32V max and up to 1A
    # set_calibration_16V_400mA() Initialize chip for 16V max and up to 400mA

    # Advanced API:
    # config register break-up
    #   reset                     WO : Write Reset.RESET to reset the chip (must recalibrate)
    #   bus_voltage_range         RW : Bus Voltage Range field (use BusVoltageRange.XXX constants)
    #   gain                      RW : Programmable Gain field (use Gain.XXX constants)
    #   bus_adc_resolution        RW : Bus ADC resolution and averaging modes (ADCResolution.XXX)
    #   shunt_adc_resolution      RW : Shunt ADC resolution and averaging modes (ADCResolution.XXX)
    #   mode                      RW : operating modes in config register (use Mode.XXX constants)

    # raw_shunt_voltage           RO : Shunt Voltage register (not scaled)
    # raw_bus_voltage             RO : Bus Voltage field in Bus Voltage register (not scaled)
    # conversion_ready            RO : Conversion Ready bit in Bus Voltage register
    # overflow                    RO : Math Overflow bit in Bus Voltage register
    # raw_power                   RO : Power register (not scaled)
    # raw_current                 RO : Current register (not scaled)
    # calibration                 RW : calibration register (note: value is cached)

    def __init__(self, i2c_bus, addr=0x40):
        self.i2c_device = I2CDevice(i2c_bus, addr)
        self.i2c_addr = addr

        # Set chip to known config values to start
        self._cal_value = 0
        self._current_lsb = 0
        self._power_lsb = 0
        self.set_calibration_32V_2A()

    # config register break-up
    reset = RWBits(1, _REG_CONFIG, 15, 2, False)
    bus_voltage_range = RWBits(1, _REG_CONFIG, 13, 2, False)
    gain = RWBits(2, _REG_CONFIG, 11, 2, False)
    bus_adc_resolution = RWBits(4, _REG_CONFIG, 7, 2, False)
    shunt_adc_resolution = RWBits(4, _REG_CONFIG, 3, 2, False)
    mode = RWBits(3, _REG_CONFIG, 0, 2, False)

    # shunt voltage register
    raw_shunt_voltage = ROUnaryStruct(_REG_SHUNTVOLTAGE, ">h")

    # bus voltage register
    raw_bus_voltage = ROBits(13, _REG_BUSVOLTAGE, 3, 2, False)
    conversion_ready = ROBit(_REG_BUSVOLTAGE, 1, 2, False)
    overflow = ROBit(_REG_BUSVOLTAGE, 0, 2, False)

    # power and current registers
    raw_power = ROUnaryStruct(_REG_POWER, ">H")
    raw_current = ROUnaryStruct(_REG_CURRENT, ">h")

    # calibration register
    _raw_calibration = UnaryStruct(_REG_CALIBRATION, ">H")

    @property
    def calibration(self):
        """Calibration register (cached value)"""
        return self._cal_value  # return cached value

    @calibration.setter
    def calibration(self, cal_value):
        self._cal_value = (
            cal_value  # value is cached for ``current`` and ``power`` properties
        )
        self._raw_calibration = self._cal_value

    @property
    def shunt_voltage(self):
        """The shunt voltage (between V+ and V-) in Volts (so +-.327V)"""
        # The least signficant bit is 10uV which is 0.00001 volts
        return self.raw_shunt_voltage * 0.00001

    @property
    def bus_voltage(self):
        """The bus voltage (between V- and GND) in Volts"""
        # Shift to the right 3 to drop CNVR and OVF and multiply by LSB
        # Each least signficant bit is 4mV
        return self.raw_bus_voltage * 0.004

    @property
    def current(self):
        """The current through the shunt resistor in milliamps."""
        # Sometimes a sharp load will reset the INA219, which will
        # reset the cal register, meaning CURRENT and POWER will
        # not be available ... always setting a cal
        # value even if it's an unfortunate extra step
        self._raw_calibration = self._cal_value
        # Now we can safely read the CURRENT register!
        return self.raw_current * self._current_lsb

    @property
    def power(self):
        """The power through the load in Watt."""
        # Sometimes a sharp load will reset the INA219, which will
        # reset the cal register, meaning CURRENT and POWER will
        # not be available ... always setting a cal
        # value even if it's an unfortunate extra step
        self._raw_calibration = self._cal_value
        # Now we can safely read the CURRENT register!
        return self.raw_power * self._power_lsb

    def set_calibration_32V_2A(self):  # pylint: disable=invalid-name
        """Configures to INA219 to be able to measure up to 32V and 2A of current. Counter
        overflow occurs at 3.2A.

        ..note :: These calculations assume a 0.1 shunt ohm resistor is present
        """
        # By default we use a pretty huge range for the input voltage,
        # which probably isn't the most appropriate choice for system
        # that don't use a lot of power.  But all of the calculations
        # are shown below if you want to change the settings.  You will
        # also need to change any relevant register settings, such as
        # setting the VBUS_MAX to 16V instead of 32V, etc.

        # VBUS_MAX = 32V             (Assumes 32V, can also be set to 16V)
        # VSHUNT_MAX = 0.32          (Assumes Gain 8, 320mV, can also be 0.16, 0.08, 0.04)
        # RSHUNT = 0.1               (Resistor value in ohms)

        # 1. Determine max possible current
        # MaxPossible_I = VSHUNT_MAX / RSHUNT
        # MaxPossible_I = 3.2A

        # 2. Determine max expected current
        # MaxExpected_I = 2.0A

        # 3. Calculate possible range of LSBs (Min = 15-bit, Max = 12-bit)
        # MinimumLSB = MaxExpected_I/32767
        # MinimumLSB = 0.000061              (61uA per bit)
        # MaximumLSB = MaxExpected_I/4096
        # MaximumLSB = 0,000488              (488uA per bit)

        # 4. Choose an LSB between the min and max values
        #    (Preferrably a roundish number close to MinLSB)
        # CurrentLSB = 0.0001 (100uA per bit)
        self._current_lsb = 0.1  # Current LSB = 100uA per bit

        # 5. Compute the calibration register
        # Cal = trunc (0.04096 / (Current_LSB * RSHUNT))
        # Cal = 4096 (0x1000)

        self._cal_value = 4096

        # 6. Calculate the power LSB
        # PowerLSB = 20 * CurrentLSB
        # PowerLSB = 0.002 (2mW per bit)
        self._power_lsb = 0.002  # Power LSB = 2mW per bit

        # 7. Compute the maximum current and shunt voltage values before overflow
        #
        # Max_Current = Current_LSB * 32767
        # Max_Current = 3.2767A before overflow
        #
        # If Max_Current > Max_Possible_I then
        #    Max_Current_Before_Overflow = MaxPossible_I
        # Else
        #    Max_Current_Before_Overflow = Max_Current
        # End If
        #
        # Max_ShuntVoltage = Max_Current_Before_Overflow * RSHUNT
        # Max_ShuntVoltage = 0.32V
        #
        # If Max_ShuntVoltage >= VSHUNT_MAX
        #    Max_ShuntVoltage_Before_Overflow = VSHUNT_MAX
        # Else
        #    Max_ShuntVoltage_Before_Overflow = Max_ShuntVoltage
        # End If

        # 8. Compute the Maximum Power
        # MaximumPower = Max_Current_Before_Overflow * VBUS_MAX
        # MaximumPower = 3.2 * 32V
        # MaximumPower = 102.4W

        # Set Calibration register to 'Cal' calculated above
        self._raw_calibration = self._cal_value

        # Set Config register to take into account the settings above
        self.bus_voltage_range = BusVoltageRange.RANGE_32V
        self.gain = Gain.DIV_8_320MV
        self.bus_adc_resolution = ADCResolution.ADCRES_12BIT_1S
        self.shunt_adc_resolution = ADCResolution.ADCRES_12BIT_1S
        self.mode = Mode.SANDBVOLT_CONTINUOUS

    def set_calibration_32V_1A(self):  # pylint: disable=invalid-name
        """Configures to INA219 to be able to measure up to 32V and 1A of current. Counter overflow
        occurs at 1.3A.

        .. note:: These calculations assume a 0.1 ohm shunt resistor is present"""
        # By default we use a pretty huge range for the input voltage,
        # which probably isn't the most appropriate choice for system
        # that don't use a lot of power.  But all of the calculations
        # are shown below if you want to change the settings.  You will
        # also need to change any relevant register settings, such as
        # setting the VBUS_MAX to 16V instead of 32V, etc.

        # VBUS_MAX = 32V        (Assumes 32V, can also be set to 16V)
        # VSHUNT_MAX = 0.32    (Assumes Gain 8, 320mV, can also be 0.16, 0.08, 0.04)
        # RSHUNT = 0.1            (Resistor value in ohms)

        # 1. Determine max possible current
        # MaxPossible_I = VSHUNT_MAX / RSHUNT
        # MaxPossible_I = 3.2A

        # 2. Determine max expected current
        # MaxExpected_I = 1.0A

        # 3. Calculate possible range of LSBs (Min = 15-bit, Max = 12-bit)
        # MinimumLSB = MaxExpected_I/32767
        # MinimumLSB = 0.0000305             (30.5uA per bit)
        # MaximumLSB = MaxExpected_I/4096
        # MaximumLSB = 0.000244              (244uA per bit)

        # 4. Choose an LSB between the min and max values
        #    (Preferrably a roundish number close to MinLSB)
        # CurrentLSB = 0.0000400 (40uA per bit)
        self._current_lsb = 0.04  # In milliamps

        # 5. Compute the calibration register
        # Cal = trunc (0.04096 / (Current_LSB * RSHUNT))
        # Cal = 10240 (0x2800)

        self._cal_value = 10240

        # 6. Calculate the power LSB
        # PowerLSB = 20 * CurrentLSB
        # PowerLSB = 0.0008 (800uW per bit)
        self._power_lsb = 0.0008

        # 7. Compute the maximum current and shunt voltage values before overflow
        #
        # Max_Current = Current_LSB * 32767
        # Max_Current = 1.31068A before overflow
        #
        # If Max_Current > Max_Possible_I then
        #    Max_Current_Before_Overflow = MaxPossible_I
        # Else
        #    Max_Current_Before_Overflow = Max_Current
        # End If
        #
        # ... In this case, we're good though since Max_Current is less than MaxPossible_I
        #
        # Max_ShuntVoltage = Max_Current_Before_Overflow * RSHUNT
        # Max_ShuntVoltage = 0.131068V
        #
        # If Max_ShuntVoltage >= VSHUNT_MAX
        #    Max_ShuntVoltage_Before_Overflow = VSHUNT_MAX
        # Else
        #    Max_ShuntVoltage_Before_Overflow = Max_ShuntVoltage
        # End If

        # 8. Compute the Maximum Power
        # MaximumPower = Max_Current_Before_Overflow * VBUS_MAX
        # MaximumPower = 1.31068 * 32V
        # MaximumPower = 41.94176W

        # Set Calibration register to 'Cal' calculated above
        self._raw_calibration = self._cal_value

        # Set Config register to take into account the settings above
        self.bus_voltage_range = BusVoltageRange.RANGE_32V
        self.gain = Gain.DIV_8_320MV
        self.bus_adc_resolution = ADCResolution.ADCRES_12BIT_1S
        self.shunt_adc_resolution = ADCResolution.ADCRES_12BIT_1S
        self.mode = Mode.SANDBVOLT_CONTINUOUS

    def set_calibration_16V_400mA(self):  # pylint: disable=invalid-name
        """Configures to INA219 to be able to measure up to 16V and 400mA of current. Counter
        overflow occurs at 1.6A.

        .. note:: These calculations assume a 0.1 ohm shunt resistor is present"""
        # Calibration which uses the highest precision for
        # current measurement (0.1mA), at the expense of
        # only supporting 16V at 400mA max.

        # VBUS_MAX = 16V
        # VSHUNT_MAX = 0.04          (Assumes Gain 1, 40mV)
        # RSHUNT = 0.1               (Resistor value in ohms)

        # 1. Determine max possible current
        # MaxPossible_I = VSHUNT_MAX / RSHUNT
        # MaxPossible_I = 0.4A

        # 2. Determine max expected current
        # MaxExpected_I = 0.4A

        # 3. Calculate possible range of LSBs (Min = 15-bit, Max = 12-bit)
        # MinimumLSB = MaxExpected_I/32767
        # MinimumLSB = 0.0000122              (12uA per bit)
        # MaximumLSB = MaxExpected_I/4096
        # MaximumLSB = 0.0000977              (98uA per bit)

        # 4. Choose an LSB between the min and max values
        #    (Preferrably a roundish number close to MinLSB)
        # CurrentLSB = 0.00005 (50uA per bit)
        self._current_lsb = 0.05  # in milliamps

        # 5. Compute the calibration register
        # Cal = trunc (0.04096 / (Current_LSB * RSHUNT))
        # Cal = 8192 (0x2000)

        self._cal_value = 8192

        # 6. Calculate the power LSB
        # PowerLSB = 20 * CurrentLSB
        # PowerLSB = 0.001 (1mW per bit)
        self._power_lsb = 0.001

        # 7. Compute the maximum current and shunt voltage values before overflow
        #
        # Max_Current = Current_LSB * 32767
        # Max_Current = 1.63835A before overflow
        #
        # If Max_Current > Max_Possible_I then
        #    Max_Current_Before_Overflow = MaxPossible_I
        # Else
        #    Max_Current_Before_Overflow = Max_Current
        # End If
        #
        # Max_Current_Before_Overflow = MaxPossible_I
        # Max_Current_Before_Overflow = 0.4
        #
        # Max_ShuntVoltage = Max_Current_Before_Overflow * RSHUNT
        # Max_ShuntVoltage = 0.04V
        #
        # If Max_ShuntVoltage >= VSHUNT_MAX
        #    Max_ShuntVoltage_Before_Overflow = VSHUNT_MAX
        # Else
        #    Max_ShuntVoltage_Before_Overflow = Max_ShuntVoltage
        # End If
        #
        # Max_ShuntVoltage_Before_Overflow = VSHUNT_MAX
        # Max_ShuntVoltage_Before_Overflow = 0.04V

        # 8. Compute the Maximum Power
        # MaximumPower = Max_Current_Before_Overflow * VBUS_MAX
        # MaximumPower = 0.4 * 16V
        # MaximumPower = 6.4W

        # Set Calibration register to 'Cal' calculated above
        self._raw_calibration = self._cal_value

        # Set Config register to take into account the settings above
        self.bus_voltage_range = BusVoltageRange.RANGE_16V
        self.gain = Gain.DIV_1_40MV
        self.bus_adc_resolution = ADCResolution.ADCRES_12BIT_1S
        self.shunt_adc_resolution = ADCResolution.ADCRES_12BIT_1S
        self.mode = Mode.SANDBVOLT_CONTINUOUS

    def set_calibration_16V_5A(self):  # pylint: disable=invalid-name
        """Configures to INA219 to be able to measure up to 16V and 5000mA of current. Counter
        overflow occurs at 8.0A.

        .. note:: These calculations assume a 0.02 ohm shunt resistor is present"""
        # Calibration which uses the highest precision for
        # current measurement (0.1mA), at the expense of
        # only supporting 16V at 5000mA max.

        # VBUS_MAX = 16V
        # VSHUNT_MAX = 0.16          (Assumes Gain 3, 160mV)
        # RSHUNT = 0.02              (Resistor value in ohms)

        # 1. Determine max possible current
        # MaxPossible_I = VSHUNT_MAX / RSHUNT
        # MaxPossible_I = 8.0A

        # 2. Determine max expected current
        # MaxExpected_I = 5.0A

        # 3. Calculate possible range of LSBs (Min = 15-bit, Max = 12-bit)
        # MinimumLSB = MaxExpected_I/32767
        # MinimumLSB = 0.0001529              (uA per bit)
        # MaximumLSB = MaxExpected_I/4096
        # MaximumLSB = 0.0012207              (uA per bit)

        # 4. Choose an LSB between the min and max values
        #    (Preferrably a roundish number close to MinLSB)
        # CurrentLSB = 0.00016 (uA per bit)
        self._current_lsb = 0.1524  # in milliamps

        # 5. Compute the calibration register
        # Cal = trunc (0.04096 / (Current_LSB * RSHUNT))
        # Cal = 13434 (0x347a)

        self._cal_value = 13434

        # 6. Calculate the power LSB
        # PowerLSB = 20 * CurrentLSB
        # PowerLSB = 0.003 (3.048mW per bit)
        self._power_lsb = 0.003048

        # 7. Compute the maximum current and shunt voltage values before overflow
        #
        # 8. Compute the Maximum Power
        #

        # Set Calibration register to 'Cal' calcutated above
        self._raw_calibration = self._cal_value

        # Set Config register to take into account the settings above
        self.bus_voltage_range = BusVoltageRange.RANGE_16V
        self.gain = Gain.DIV_4_160MV
        self.bus_adc_resolution = ADCResolution.ADCRES_12BIT_1S
        self.shunt_adc_resolution = ADCResolution.ADCRES_12BIT_1S
        self.mode = Mode.SANDBVOLT_CONTINUOUS
Example #26
0
class LSM303_Accel:  #pylint:disable=too-many-instance-attributes
    """Driver for the LSM303's accelerometer."""

    # Class-level buffer for reading and writing data with the sensor.
    # This reduces memory allocations but means the code is not re-entrant or
    # thread safe!
    _chip_id = UnaryStruct(_REG_ACCEL_WHO_AM_I, "B")
    _int2_int1_enable = RWBit(_REG_ACCEL_CTRL_REG6_A, 6)
    _int2_int2_enable = RWBit(_REG_ACCEL_CTRL_REG6_A, 5)
    _int1_latching = RWBit(_REG_ACCEL_CTRL_REG5_A, 3)
    _int2_latching = RWBit(_REG_ACCEL_CTRL_REG5_A, 1)
    _bdu = RWBit(_REG_ACCEL_CTRL_REG4_A, 7)

    _int2_activity_enable = RWBit(_REG_ACCEL_CTRL_REG6_A, 3)
    _int_pin_active_low = RWBit(_REG_ACCEL_CTRL_REG6_A, 1)

    _act_threshold = UnaryStruct(_REG_ACCEL_ACT_THS_A, "B")
    _act_duration = UnaryStruct(_REG_ACCEL_ACT_DUR_A, "B")
    """
    .. code-block:: python

        import board
        i2c = board.I2C()

        import adafruit_lsm303_accel
        accel = adafruit_lsm303_accel.LSM303_Accel(i2c)

        accel._act_threshold = 20
        accel._act_duration = 1
        accel._int2_activity_enable = True

        # toggle pins, defaults to False
        accel._int_pin_active_low = True
    """
    _data_rate = RWBits(4, _REG_ACCEL_CTRL_REG1_A, 4)
    _enable_xyz = RWBits(3, _REG_ACCEL_CTRL_REG1_A, 0)
    _raw_accel_data = StructArray(_REG_ACCEL_OUT_X_L_A, "<h", 3)

    _low_power = RWBit(_REG_ACCEL_CTRL_REG1_A, 3)
    _high_resolution = RWBit(_REG_ACCEL_CTRL_REG4_A, 3)

    _range = RWBits(2, _REG_ACCEL_CTRL_REG4_A, 4)

    _int1_src = UnaryStruct(_REG_ACCEL_INT1_SOURCE_A, "B")
    _tap_src = UnaryStruct(_REG_ACCEL_CLICK_SRC_A, "B")

    _tap_interrupt_enable = RWBit(_REG_ACCEL_CTRL_REG3_A, 7, 1)
    _tap_config = UnaryStruct(_REG_ACCEL_CLICK_CFG_A, "B")
    _tap_interrupt_active = ROBit(_REG_ACCEL_CLICK_SRC_A, 6, 1)
    _tap_threshold = UnaryStruct(_REG_ACCEL_CLICK_THS_A, "B")
    _tap_time_limit = UnaryStruct(_REG_ACCEL_TIME_LIMIT_A, "B")
    _tap_time_latency = UnaryStruct(_REG_ACCEL_TIME_LATENCY_A, "B")
    _tap_time_window = UnaryStruct(_REG_ACCEL_TIME_WINDOW_A, "B")

    _BUFFER = bytearray(6)

    def __init__(self, i2c):
        self._accel_device = I2CDevice(i2c, _ADDRESS_ACCEL)
        self.i2c_device = self._accel_device
        self._data_rate = 2
        self._enable_xyz = 0b111
        self._int1_latching = True
        self._int2_latching = True
        self._bdu = True

        # self._write_register_byte(_REG_CTRL5, 0x80)
        # time.sleep(0.01)  # takes 5ms
        self._cached_mode = 0
        self._cached_range = 0

    def set_tap(self,
                tap,
                threshold,
                *,
                time_limit=10,
                time_latency=20,
                time_window=255,
                tap_cfg=None):
        """
        The tap detection parameters.

        :param int tap: 0 to disable tap detection, 1 to detect only single taps, and 2 to detect \
            only double taps.
        :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.  Good values\
            are 5-10 for 16G, 10-20 for 8G, 20-40 for 4G, and 40-80 for 2G.
        :param int time_limit: TIME_LIMIT register value (default 10).
        :param int time_latency: TIME_LATENCY register value (default 20).
        :param int time_window: TIME_WINDOW register value (default 255).
        :param int click_cfg: CLICK_CFG register value.

        """

        if (tap < 0 or tap > 2) and tap_cfg is None:
            raise ValueError(
                'Tap must be 0 (disabled), 1 (single tap), or 2 (double tap)!')
        if threshold > 127 or threshold < 0:
            raise ValueError('Threshold out of range (0-127)')

        if tap == 0 and tap_cfg is None:
            # Disable click interrupt.
            self._tap_interrupt_enable = False
            self._tap_config = 0
            return

        self._tap_interrupt_enable = True

        if tap_cfg is None:
            if tap == 1:
                tap_cfg = 0x15  # Turn on all axes & singletap.
            if tap == 2:
                tap_cfg = 0x2A  # Turn on all axes & doubletap.
        # Or, if a custom tap configuration register value specified, use it.
        self._tap_config = tap_cfg

        self._tap_threshold = threshold  # why and?
        self._tap_time_limit = time_limit
        self._tap_time_latency = time_latency
        self._tap_time_window = time_window

    @property
    def tapped(self):
        """
        True if a tap was detected recently. Whether its a single tap or double tap is
        determined by the tap param on ``set_tap``. ``tapped`` may be True over
        multiple reads even if only a single tap or single double tap occurred.
        """
        tap_src = self._tap_src
        return tap_src & 0b1000000 > 0

    @property
    def _raw_acceleration(self):
        self._read_bytes(self._accel_device, _REG_ACCEL_OUT_X_L_A | 0x80, 6,
                         self._BUFFER)
        return struct.unpack_from('<hhh', self._BUFFER[0:6])

    @property
    def acceleration(self):
        """The measured accelerometer sensor values.
        A 3-tuple of X, Y, Z axis values in m/s^2 squared that are signed floats.
        """

        raw_accel_data = self._raw_acceleration

        x = self._scale_data(raw_accel_data[0])
        y = self._scale_data(raw_accel_data[1])
        z = self._scale_data(raw_accel_data[2])

        return (x, y, z)

    def _scale_data(self, raw_measurement):
        lsb, shift = self._lsb_shift()

        return (raw_measurement >> shift) * lsb * _SMOLLER_GRAVITY

    def _lsb_shift(self):  #pylint:disable=too-many-branches
        # the bit depth of the data depends on the mode, and the lsb value
        # depends on the mode and range
        lsb = -1  # the default, normal mode @ 2G

        if self._cached_mode is Mode.MODE_HIGH_RESOLUTION:  # 12-bit
            shift = 4
            if self._cached_range is Range.RANGE_2G:
                lsb = 0.98
            elif self._cached_range is Range.RANGE_4G:
                lsb = 1.95
            elif self._cached_range is Range.RANGE_8G:
                lsb = 3.9
            elif self._cached_range is Range.RANGE_16G:
                lsb = 11.72
        elif self._cached_mode is Mode.MODE_NORMAL:  # 10-bit
            shift = 6
            if self._cached_range is Range.RANGE_2G:
                lsb = 3.9
            elif self._cached_range is Range.RANGE_4G:
                lsb = 7.82
            elif self._cached_range is Range.RANGE_8G:
                lsb = 15.63
            elif self._cached_range is Range.RANGE_16G:
                lsb = 46.9

        elif self._cached_mode is Mode.MODE_LOW_POWER:  # 8-bit
            shift = 8
            if self._cached_range is Range.RANGE_2G:
                lsb = 15.63
            elif self._cached_range is Range.RANGE_4G:
                lsb = 31.26
            elif self._cached_range is Range.RANGE_8G:
                lsb = 62.52
            elif self._cached_range is Range.RANGE_16G:
                lsb = 187.58

        if lsb is -1:
            raise AttributeError(
                "'impossible' range or mode detected: range: %d mode: %d" %
                (self._cached_range, self._cached_mode))
        return (lsb, shift)

    @property
    def data_rate(self):
        """Select the rate at which the sensor takes measurements. Must be a `Rate`"""
        return self._data_rate

    @data_rate.setter
    def data_rate(self, value):
        if value < 0 or value > 9:
            raise AttributeError("data_rate must be a `Rate`")

        self._data_rate = value

    @property
    def 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 a `Range`"""
        return self._cached_range

    @range.setter
    def range(self, value):
        if value < 0 or value > 3:
            raise AttributeError("range must be a `Range`")
        self._range = value
        self._cached_range = value

    @property
    def mode(self):
        """Sets the power mode of the sensor. The mode must be a `Mode`. Note that the
        mode and range will both affect the accuracy of the sensor"""
        return self._cached_mode

    @mode.setter
    def mode(self, value):
        if value < 0 or value > 2:
            raise AttributeError("mode must be a `Mode`")
        self._high_resolution = value & 0b01
        self._low_power = (value & 0b10) >> 1
        self._cached_mode = value

    def _read_u8(self, device, address):
        with device as i2c:
            self._BUFFER[0] = address & 0xFF
            i2c.write_then_readinto(self._BUFFER,
                                    self._BUFFER,
                                    out_end=1,
                                    in_end=1)
        return self._BUFFER[0]

    def _write_u8(self, device, address, val):
        with device as i2c:
            self._BUFFER[0] = address & 0xFF
            self._BUFFER[1] = val & 0xFF
            i2c.write(self._BUFFER, end=2)

    @staticmethod
    def _read_bytes(device, address, count, buf):
        with device as i2c:
            buf[0] = address & 0xFF
            i2c.write_then_readinto(buf, buf, out_end=1, in_end=count)
class MPU6050:
    """Driver for the MPU6050 6-DoF accelerometer and gyroscope.

        :param ~busio.I2C i2c_bus: The I2C bus the MPU6050 is connected to.
        :param address: The I2C slave address of the sensor

    """
    def __init__(self, i2c_bus, address=_MPU6050_DEFAULT_ADDRESS):
        self.i2c_device = i2c_device.I2CDevice(i2c_bus, address)

        if self._device_id != _MPU6050_DEVICE_ID:
            raise RuntimeError("Failed to find MPU6050 - check your wiring!")

        self.reset()

        self._sample_rate_divisor = 0
        self._filter_bandwidth = Bandwidth.BAND_260_HZ
        self._gyro_range = GyroRange.RANGE_500_DPS
        self._accel_range = Range.RANGE_2_G
        sleep(0.100)
        self._clock_source = 1  # set to use gyro x-axis as reference
        sleep(0.100)
        self.sleep = False
        sleep(0.010)

    def reset(self):
        """Reinitialize the sensor"""
        self._reset = True
        while self._reset is True:
            sleep(0.001)
        sleep(0.100)

        _signal_path_reset = 0b111  # reset all sensors
        sleep(0.100)

    _clock_source = RWBits(3, _MPU6050_PWR_MGMT_1, 0)
    _device_id = ROUnaryStruct(_MPU6050_WHO_AM_I, ">B")

    _reset = RWBit(_MPU6050_PWR_MGMT_1, 7, 1)
    _signal_path_reset = RWBits(3, _MPU6050_SIG_PATH_RESET, 3)

    _gyro_range = RWBits(2, _MPU6050_GYRO_CONFIG, 3)
    _accel_range = RWBits(2, _MPU6050_ACCEL_CONFIG, 3)

    _filter_bandwidth = RWBits(2, _MPU6050_CONFIG, 3)

    _raw_accel_data = StructArray(_MPU6050_ACCEL_OUT, ">h", 3)
    _raw_gyro_data = StructArray(_MPU6050_GYRO_OUT, ">h", 3)
    _raw_temp_data = ROUnaryStruct(_MPU6050_TEMP_OUT, ">h")

    _cycle = RWBit(_MPU6050_PWR_MGMT_1, 5)
    _cycle_rate = RWBits(2, _MPU6050_PWR_MGMT_2, 6, 1)

    sleep = RWBit(_MPU6050_PWR_MGMT_1, 6, 1)
    """Shuts down the accelerometers and gyroscopes, saving power. No new data will
    be recorded until the sensor is taken out of sleep by setting to `False`"""
    sample_rate_divisor = UnaryStruct(_MPU6050_SMPLRT_DIV, ">B")
    """The sample rate divisor. See the datasheet for additional detail"""

    @property
    def temperature(self):
        """The current temperature in  º C"""
        raw_temperature = self._raw_temp_data
        temp = (raw_temperature / 340.0) + 36.53
        return temp

    @property
    def acceleration(self):
        """Acceleration X, Y, and Z axis data in m/s^2"""
        raw_data = self._raw_accel_data
        raw_x = raw_data[0][0]
        raw_y = raw_data[1][0]
        raw_z = raw_data[2][0]

        accel_range = self._accel_range
        accel_scale = 1
        if accel_range == Range.RANGE_16_G:
            accel_scale = 2048
        if accel_range == Range.RANGE_8_G:
            accel_scale = 4096
        if accel_range == Range.RANGE_4_G:
            accel_scale = 8192
        if accel_range == Range.RANGE_2_G:
            accel_scale = 16384

        # setup range dependant scaling
        accel_x = (raw_x / accel_scale) * STANDARD_GRAVITY
        accel_y = (raw_y / accel_scale) * STANDARD_GRAVITY
        accel_z = (raw_z / accel_scale) * STANDARD_GRAVITY

        return (accel_x, accel_y, accel_z)

    @property
    def gyro(self):
        """Gyroscope X, Y, and Z axis data in º/s"""
        raw_data = self._raw_gyro_data
        raw_x = raw_data[0][0]
        raw_y = raw_data[1][0]
        raw_z = raw_data[2][0]

        gyro_scale = 1
        gyro_range = self._gyro_range
        if gyro_range == GyroRange.RANGE_250_DPS:
            gyro_scale = 131
        if gyro_range == GyroRange.RANGE_500_DPS:
            gyro_scale = 65.5
        if gyro_range == GyroRange.RANGE_1000_DPS:
            gyro_scale = 32.8
        if gyro_range == GyroRange.RANGE_2000_DPS:
            gyro_scale = 16.4

        # setup range dependant scaling
        gyro_x = (raw_x / gyro_scale)
        gyro_y = (raw_y / gyro_scale)
        gyro_z = (raw_z / gyro_scale)

        return (gyro_x, gyro_y, gyro_z)

    @property
    def cycle(self):
        """Enable or disable perodic measurement at a rate set by `cycle_rate`.
        If the sensor was in sleep mode, it will be waken up to cycle"""
        return self._cycle

    @cycle.setter
    def cycle(self, value):
        self.sleep = not value
        self._cycle = value

    @property
    def gyro_range(self):
        """The measurement range of all gyroscope axes. Must be a `GyroRange`"""
        return self._gyro_range

    @gyro_range.setter
    def gyro_range(self, value):
        if (value < 0) or (value > 3):
            raise ValueError("gyro_range must be a GyroRange")
        self._gyro_range = value
        sleep(0.01)

    @property
    def accelerometer_range(self):
        """The measurement range of all accelerometer axes. Must be a `Range`"""
        return self._accel_range

    @accelerometer_range.setter
    def accelerometer_range(self, value):
        if (value < 0) or (value > 3):
            raise ValueError("accelerometer_range must be a Range")
        self._accel_range = value
        sleep(0.01)

    @property
    def filter_bandwidth(self):
        """The bandwidth of the gyroscope Digital Low Pass Filter. Must be a `GyroRange`"""
        return self._filter_bandwidth

    @filter_bandwidth.setter
    def filter_bandwidth(self, value):
        if (value < 0) or (value > 6):
            raise ValueError("filter_bandwidth must be a Bandwidth")
        self._filter_bandwidth = value
        sleep(0.01)

    @property
    def cycle_rate(self):
        """The rate that measurements are taken while in `cycle` mode. Must be a `Rate`"""
        return self._cycle_rate

    @cycle_rate.setter
    def cycle_rate(self, value):
        if (value < 0) or (value > 3):
            raise ValueError("cycle_rate must be a Rate")
        self._cycle_rate = value
        sleep(0.01)
class DPS310:
    # pylint: disable=too-many-instance-attributes
    """Library for the DPS310 Precision Barometric Pressure Sensor.
    Depending on your board memory availability you could use DPS310_Advanced.

    :param ~busio.I2C i2c_bus: The I2C bus the DPS310 is connected to.
    :param int address: The I2C device address. Defaults to :const:`0x77`

    **Quickstart: Importing and using the DPS310**

        Here is an example of using the :class:`DPS310` class.
        First you will need to import the libraries to use the sensor

        .. code-block:: python

            import board
            from adafruit_dps310.basic import DPS310

        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
            dps310 = DPS310(i2c)

        Now you have access to the :attr:`temperature` and :attr:`pressure` attributes.

        .. code-block:: python

            temperature = dps310.temperature
            pressure = dps310.pressure

    """
    # Register definitions
    _device_id = ROUnaryStruct(_DPS310_PRODREVID, ">B")
    _reset_register = UnaryStruct(_DPS310_RESET, ">B")
    _mode_bits = RWBits(3, _DPS310_MEASCFG, 0)  #

    _pressure_osbits = RWBits(4, _DPS310_PRSCFG, 0)

    _temp_osbits = RWBits(4, _DPS310_TMPCFG, 0)

    _temp_measurement_src_bit = RWBit(_DPS310_TMPCFG, 7)

    _pressure_shiftbit = RWBit(_DPS310_CFGREG, 2)
    _temp_shiftbit = RWBit(_DPS310_CFGREG, 3)

    _coefficients_ready = RWBit(_DPS310_MEASCFG, 7)
    _sensor_ready = RWBit(_DPS310_MEASCFG, 6)
    _temp_ready = RWBit(_DPS310_MEASCFG, 5)
    _pressure_ready = RWBit(_DPS310_MEASCFG, 4)

    _raw_pressure = ROBits(24, _DPS310_PRSB2, 0, 3, lsb_first=False)
    _raw_temperature = ROBits(24, _DPS310_TMPB2, 0, 3, lsb_first=False)

    _calib_coeff_temp_src_bit = ROBit(_DPS310_TMPCOEFSRCE, 7)

    _reg0e = RWBits(8, 0x0E, 0)
    _reg0f = RWBits(8, 0x0F, 0)
    _reg62 = RWBits(8, 0x62, 0)

    def __init__(self, i2c_bus, address=_DPS310_DEFAULT_ADDRESS):
        self.i2c_device = i2c_device.I2CDevice(i2c_bus, address)

        if self._device_id != _DPS310_DEVICE_ID:
            raise RuntimeError("Failed to find DPS310 - check your wiring!")
        self._pressure_scale = None
        self._temp_scale = None
        self._c0 = None
        self._c1 = None
        self._c00 = None
        self._c00 = None
        self._c10 = None
        self._c10 = None
        self._c01 = None
        self._c11 = None
        self._c20 = None
        self._c21 = None
        self._c30 = None
        self._oversample_scalefactor = (
            524288,
            1572864,
            3670016,
            7864320,
            253952,
            516096,
            1040384,
            2088960,
        )
        self._sea_level_pressure = 1013.25

        self.initialize()

    def initialize(self):
        """Initialize the sensor to continuous measurement"""

        self.reset()

        self._pressure_osbits = 6
        self._pressure_shiftbit = True
        self._pressure_scale = self._oversample_scalefactor[6]

        self._temp_osbits = 6
        self._temp_scale = self._oversample_scalefactor[6]
        self._temp_shiftbit = True

        self._mode_bits = 7

        # wait until we have at least one good measurement
        self.wait_temperature_ready()
        self.wait_pressure_ready()

    # (https://github.com/Infineon/DPS310-Pressure-Sensor#temperature-measurement-issue)
    # similar to DpsClass::correctTemp(void) from infineon's c++ library
    def _correct_temp(self):
        """Correct temperature readings on ICs with a fuse bit problem"""
        self._reg0e = 0xA5
        self._reg0f = 0x96
        self._reg62 = 0x02
        self._reg0e = 0
        self._reg0f = 0

        # perform a temperature measurement
        # the most recent temperature will be saved internally
        # and used for compensation when calculating pressure
        _unused = self._raw_temperature

    def reset(self):
        """Reset the sensor"""
        self._reset_register = 0x89
        # wait for hardware reset to finish
        sleep(0.010)
        while not self._sensor_ready:
            sleep(0.001)
        self._correct_temp()
        self._read_calibration()
        # make sure we're using the temperature source used for calibration
        self._temp_measurement_src_bit = self._calib_coeff_temp_src_bit

    @property
    def pressure(self):
        """Returns the current pressure reading in kPA"""

        temp_reading = self._raw_temperature
        raw_temperature = self._twos_complement(temp_reading, 24)
        pressure_reading = self._raw_pressure
        raw_pressure = self._twos_complement(pressure_reading, 24)
        _scaled_rawtemp = raw_temperature / self._temp_scale

        _temperature = _scaled_rawtemp * self._c1 + self._c0 / 2.0

        p_red = raw_pressure / self._pressure_scale

        pres_calc = (self._c00 + p_red * (self._c10 + p_red *
                                          (self._c20 + p_red * self._c30)) +
                     _scaled_rawtemp * (self._c01 + p_red *
                                        (self._c11 + p_red * self._c21)))

        final_pressure = pres_calc / 100
        return final_pressure

    @property
    def altitude(self):
        """The altitude based on the sea level pressure (:attr:`sea_level_pressure`) -
        which you must enter ahead of time)"""
        return 44330 * (
            1.0 - math.pow(self.pressure / self._sea_level_pressure, 0.1903))

    @property
    def temperature(self):
        """The current temperature reading in degrees Celsius"""
        _scaled_rawtemp = self._raw_temperature / self._temp_scale
        _temperature = _scaled_rawtemp * self._c1 + self._c0 / 2.0
        return _temperature

    @property
    def sea_level_pressure(self):
        """The local sea level pressure in hectoPascals (aka millibars). This is used
        for calculation of :attr:`altitude`. Values are typically in the range
        980 - 1030."""
        return self._sea_level_pressure

    @sea_level_pressure.setter
    def sea_level_pressure(self, value):
        self._sea_level_pressure = value

    def wait_temperature_ready(self):
        """Wait until a temperature measurement is available."""

        while self._temp_ready is False:
            sleep(0.001)

    def wait_pressure_ready(self):
        """Wait until a pressure measurement is available"""

        while self._pressure_ready is False:
            sleep(0.001)

    @staticmethod
    def _twos_complement(val, bits):
        if val & (1 << (bits - 1)):
            val -= 1 << bits

        return val

    def _read_calibration(self):

        while not self._coefficients_ready:
            sleep(0.001)

        buffer = bytearray(19)
        coeffs = [None] * 18
        for offset in range(18):
            buffer = bytearray(2)
            buffer[0] = 0x10 + offset

            with self.i2c_device as i2c:

                i2c.write_then_readinto(buffer, buffer, out_end=1, in_start=1)

                coeffs[offset] = buffer[1]

        self._c0 = (coeffs[0] << 4) | ((coeffs[1] >> 4) & 0x0F)
        self._c0 = self._twos_complement(self._c0, 12)

        self._c1 = self._twos_complement(((coeffs[1] & 0x0F) << 8) | coeffs[2],
                                         12)

        self._c00 = (coeffs[3] << 12) | (coeffs[4] << 4) | (
            (coeffs[5] >> 4) & 0x0F)
        self._c00 = self._twos_complement(self._c00, 20)

        self._c10 = ((coeffs[5] & 0x0F) << 16) | (coeffs[6] << 8) | coeffs[7]
        self._c10 = self._twos_complement(self._c10, 20)

        self._c01 = self._twos_complement((coeffs[8] << 8) | coeffs[9], 16)
        self._c11 = self._twos_complement((coeffs[10] << 8) | coeffs[11], 16)
        self._c20 = self._twos_complement((coeffs[12] << 8) | coeffs[13], 16)
        self._c21 = self._twos_complement((coeffs[14] << 8) | coeffs[15], 16)
        self._c30 = self._twos_complement((coeffs[16] << 8) | coeffs[17], 16)
Example #29
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
Example #30
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