예제 #1
0
class DeviceControl:  # pylint: disable-msg=too-few-public-methods
    """General LTR559 control registers"""

    def __init__(self, i2c):
        self.i2c_device = i2c  # self.i2c_device required by RWBit class

    sw_reset = RWBit(_LTR559_REG_ALS_CONTROL, 1)
    part_number = ROBits(4, _LTR559_REG_PART_ID, 4)
    revision = ROBits(4, _LTR559_REG_PART_ID, 0)
    manufacturer_id = ROBits(8, _LTR559_REG_MANUFACTURER_ID, 0)

    led_pulse_freq_khz = RWBits(3, _LTR559_REG_PS_LED, 5)
    led_duty_cycle = RWBits(2, _LTR559_REG_PS_LED, 3)
    led_current_ma = RWBits(3, _LTR559_REG_PS_LED, 0)
    led_pulse_count = RWBits(4, _LTR559_REG_PS_N_PULSES, 0)

    interrupt_polarity = RWBit(_LTR559_REG_INTERRUPT, 2)
    interrupt_mode = RWBits(2, _LTR559_REG_INTERRUPT, 0)
예제 #2
0
class BQ25883:
    _pn = ROBits(4, _PART_INFO, 3, 1, False)
    _fault_status = ROBits(8, _FAULT_STAT, 0, 1, False)
    _chrgr_status1 = ROBits(8, _CHRG_STAT1, 0, 1, False)
    _chrgr_status2 = ROBits(8, _CHRG_STAT2, 0, 1, False)
    _chrg_status = ROBits(3, _CHRG_STAT1, 0, 1, False)
    _otg_ctrl = ROBits(8, _OTG_CTRL, 0, 1, False)
    _chrg_ctrl2 = ROBits(8, _CHRGR_CRTL2, 0, 1, False)
    _wdt = RWBits(2, _CHRGR_CRTL1, 4, 1, False)
    _ntc_stat = RWBits(3, _NTC_STAT, 0, 1, False)
    _pfm_dis = RWBit(_CHRGR_CRTL3, 7, 1, False)
    _en_chrg = RWBit(_CHRGR_CRTL2, 3, 1, False)
    _reg_rst = RWBit(_PART_INFO, 7, 1, False)
    _stat_dis = RWBit(_CHRGR_CRTL1, 6, 1, False)
    _inlim = RWBit(_CHRGI_LIM, 6, 1, False)

    def __init__(self, i2c_bus, addr=0x6B):
        self.i2c_device = I2CDevice(i2c_bus, addr)
        self.i2c_addr = addr
        assert self._pn == 3, "Unable to find BQ25883"

    @property
    def status(self):
        print('Fault:', bin(self._fault_status))
        print('Charger Status 1:', bin(self._chrgr_status1))
        print('Charger Status 2:', bin(self._chrgr_status2))
        print('Charge Status:', bin(self._chrg_status))
        print('Charge Control2:', bin(self._chrg_ctrl2))
        print('NTC Status:', bin(self._ntc_stat))
        print('OTG:', hex(self._otg_ctrl))

    @property
    def charging(self):
        print('Charge Control2:', bin(self._chrg_ctrl2))

    @charging.setter
    def charging(self, value):
        assert type(value) == bool
        self._en_chrg = value

    @property
    def wdt(self):
        print('Watchdog Timer:', bin(self._wdt))

    @wdt.setter
    def wdt(self, value):
        if not value:
            self._wdt = 0
        else:
            self._wdt = value

    @property
    def led(self):
        print('Status LED:', bin(self._stat_dis))

    @led.setter
    def led(self, value):
        assert type(value) == bool
        self._stat_dis = not value
예제 #3
0
class ALSControl:  # pylint: disable-msg=too-few-public-methods
    """Control registers for the LTR559 light sensor"""

    def __init__(self, i2c):
        self.i2c_device = i2c  # self.i2c_device required by RWBit class

    gain = RWBits(3, _LTR559_REG_ALS_CONTROL, 2)
    mode = RWBit(_LTR559_REG_ALS_CONTROL, 0)
    integration_time_ms = RWBits(3, _LTR559_REG_ALS_MEAS_RATE, 3)
    repeat_rate_ms = RWBits(3, _LTR559_REG_ALS_MEAS_RATE, 0)

    data = ROBits(32, _LTR559_REG_ALS_DATA_CH1, 0, register_width=4)

    threshold_lower = RWBits(16, _LTR559_REG_ALS_THRESHOLD_LOWER, 0, register_width=2)
    threshold_upper = RWBits(16, _LTR559_REG_ALS_THRESHOLD_UPPER, 0, register_width=2)

    interrupt_persist = RWBits(4, _LTR559_REG_INTERRUPT_PERSIST, 4)

    data_valid = ROBit(_LTR559_REG_ALS_PS_STATUS, 7)
    data_gain = ROBits(3, _LTR559_REG_ALS_PS_STATUS, 4)

    new_data = ROBit(_LTR559_REG_ALS_PS_STATUS, 2)
    interrupt_active = ROBit(_LTR559_REG_ALS_PS_STATUS, 3)
예제 #4
0
class PSControl:  # pylint: disable-msg=too-few-public-methods
    """Control registers for the LTR559 proximity sensor"""

    def __init__(self, i2c):
        self.i2c_device = i2c  # self.i2c_device required by RWBit class

    saturation_indicator_enable = RWBit(_LTR559_REG_PS_CONTROL, 5)
    active = RWBits(2, _LTR559_REG_PS_CONTROL, 0)
    rate_ms = RWBits(4, _LTR559_REG_PS_MEAS_RATE, 0)

    data_ch0 = ROBits(16, _LTR559_REG_PS_DATA_CH0, 0, register_width=2)
    saturation = RWBit(_LTR559_REG_PS_DATA_SAT, 7)

    threshold_lower = RWBits(16, _LTR559_REG_PS_THRESHOLD_LOWER, 0, register_width=2)
    threshold_upper = RWBits(16, _LTR559_REG_PS_THRESHOLD_UPPER, 0, register_width=2)

    offset = RWBits(10, _LTR559_REG_PS_OFFSET, 0, register_width=2)

    interrupt_persist = RWBits(4, _LTR559_REG_INTERRUPT_PERSIST, 0)

    new_data = ROBit(_LTR559_REG_ALS_PS_STATUS, 0)
    interrupt_active = ROBit(_LTR559_REG_ALS_PS_STATUS, 1)
예제 #5
0
class TempSensor:
    def __init__(self, i2c):
        self.i2c_device = i2c

    alert_output_mode = RWBit(DEVICE_CONFIG_REG, 0, 2)
    alert_output_polarity = RWBit(DEVICE_CONFIG_REG, 1, 2)
    alert_output_select = RWBit(DEVICE_CONFIG_REG, 2, 2)
    alert_output_control = RWBit(DEVICE_CONFIG_REG, 3, 2)
    alert_output_status = RWBit(DEVICE_CONFIG_REG, 4, 2)
    interrupt_clear = RWBit(DEVICE_CONFIG_REG, 5, 2)
    win_lock = RWBit(DEVICE_CONFIG_REG, 6, 2)
    crit_loc = RWBit(DEVICE_CONFIG_REG, 7, 2)

    temp_value = ROBits(12, DEVICE_TEMP_REG, 0, 2)
    temp_crit_flag = ROBit(DEVICE_TEMP_REG, 15, 2)
    temp_upper_flag = ROBit(DEVICE_TEMP_REG, 14, 2)
    temp_lower_flag = ROBit(DEVICE_TEMP_REG, 13, 2)

    temp_upper_value = RWBits(11, DEVICE_UPPER_TEMP_REG, 2, 2)

    temp_lower_value = RWBits(11, DEVICE_LOWER_TEMP_REG, 2, 2)

    temp_crit_value = RWBits(11, DEVICE_CRIT_TEMP_REG, 2, 2)
class MSA301:#pylint: disable=too-many-instance-attributes
    """Driver for the MSA301 Accelerometer.

        :param ~busio.I2C i2c_bus: The I2C bus the MSA is connected to.
    """
    _part_id = ROUnaryStruct(_MSA301_REG_PARTID, "<B")

    def __init__(self, i2c_bus):
        self.i2c_device = i2cdevice.I2CDevice(i2c_bus, _MSA301_I2CADDR_DEFAULT)

        if self._part_id != 0x13:
            raise AttributeError("Cannot find a MSA301")


        self._disable_x = self._disable_y = self._disable_z = False
        self.power_mode = Mode.NORMAL
        self.data_rate = DataRate.RATE_500_HZ
        self.bandwidth = BandWidth.WIDTH_250_HZ
        self.range = Range.RANGE_4_G
        self.resolution = Resolution.RESOLUTION_14_BIT
        self._tap_count = 0

    _disable_x = RWBit(_MSA301_REG_ODR, 7)
    _disable_y = RWBit(_MSA301_REG_ODR, 6)
    _disable_z = RWBit(_MSA301_REG_ODR, 5)

    _xyz_raw = ROBits(48, _MSA301_REG_OUT_X_L, 0, 6)

    # tap INT enable and status
    _single_tap_int_en = RWBit(_MSA301_REG_INTSET0, 5)
    _double_tap_int_en = RWBit(_MSA301_REG_INTSET0, 4)
    _motion_int_status = ROUnaryStruct(_MSA301_REG_MOTIONINT, "B")

    # tap interrupt knobs
    _tap_quiet = RWBit(_MSA301_REG_TAPDUR, 7)
    _tap_shock = RWBit(_MSA301_REG_TAPDUR, 6)
    _tap_duration = RWBits(3, _MSA301_REG_TAPDUR, 0)
    _tap_threshold = RWBits(5, _MSA301_REG_TAPTH, 0)
    reg_tapdur = ROUnaryStruct(_MSA301_REG_TAPDUR, "B")

    # general settings knobs
    power_mode = RWBits(2, _MSA301_REG_POWERMODE, 6)
    bandwidth = RWBits(4, _MSA301_REG_POWERMODE, 1)
    data_rate = RWBits(4, _MSA301_REG_ODR, 0)
    range = RWBits(2, _MSA301_REG_RESRANGE, 0)
    resolution = RWBits(2, _MSA301_REG_RESRANGE, 2)

    @property
    def acceleration(self):
        """The x, y, z acceleration values returned in a 3-tuple and are in m / s ^ 2."""
        # read the 6 bytes of acceleration data
        # zh, zl, yh, yl, xh, xl
        raw_data = self._xyz_raw
        acc_bytes = bytearray()
        # shift out bytes, reversing the order
        for shift in range(6):
            bottom_byte = (raw_data >>(8*shift) & 0xFF)
            acc_bytes.append(bottom_byte)

        # unpack three LE, signed shorts
        x, y, z = struct.unpack_from("<hhh", acc_bytes)

        current_range = self.range
        scale = 1.0
        if current_range == 3:
            scale = 512.0
        if current_range == 2:
            scale = 1024.0
        if current_range == 1:
            scale = 2048.0
        if current_range == 0:
            scale = 4096.0

        # shift down to the actual 14 bits and scale based on the range
        x_acc = ((x>>2) / scale) * _STANDARD_GRAVITY
        y_acc = ((y>>2) / scale) * _STANDARD_GRAVITY
        z_acc = ((z>>2) / scale) * _STANDARD_GRAVITY

        return (x_acc, y_acc, z_acc)

    def enable_tap_detection(self, *,
                             tap_count=1,
                             threshold=25,
                             long_initial_window=True,
                             long_quiet_window=True,
                             double_tap_window=TapDuration.DURATION_250_MS):
        """
        Enables tap detection with configurable parameters.

        :param int tap_count: 1 to detect only single taps, or 2 to detect only double taps.\
        default is 1

        :param int threshold: A threshold for the tap detection.\
        The higher the value the less sensitive the detection. This changes based on the\
        accelerometer range. Default is 25.

        :param int long_initial_window: This sets the length of the window of time where a\
        spike in acceleration must occour in before being followed by a quiet period.\
        `True` (default) sets the value to 70ms, False to 50ms. Default is `True`

        :param int long_quiet_window: The length of the "quiet" period after an acceleration\
        spike where no more spikes can occour for a tap to be registered.\
        `True` (default) sets the value to 30ms, False to 20ms. Default is `True`.

        :param int double_tap_window: The length of time after an initial tap is registered\
        in which a second tap must be detected to count as a double tap. Setting a lower\
        value will require a faster double tap. The value must be a\
        ``TapDuration``. Default is ``TapDuration.DURATION_250_MS``.

        If you wish to set them yourself rather than using the defaults,
        you must use keyword arguments::

            msa.enable_tap_detection(tap_count=2,
                                     threshold=25,
                                     double_tap_window=TapDuration.DURATION_700_MS)

        """
        self._tap_shock = not long_initial_window
        self._tap_quiet = long_quiet_window
        self._tap_threshold = threshold
        self._tap_count = tap_count

        if double_tap_window > 7 or double_tap_window < 0:
            raise ValueError("double_tap_window must be a TapDuration")
        if tap_count == 1:
            self._single_tap_int_en = True
        elif tap_count == 2:
            self._tap_duration = double_tap_window
            self._double_tap_int_en = True
        else:
            raise ValueError("tap must be 1 for single tap, or 2 for double tap")

    @property
    def tapped(self):
        """`True` if a single or double tap was detected, depending on the value of the\
           ``tap_count`` argument passed to ``enable_tap_detection``"""
        if self._tap_count == 0:
            return False

        motion_int_status = self._motion_int_status

        if motion_int_status == 0: # no interrupts triggered
            return False

        if self._tap_count == 1 and motion_int_status & 1<<5:
            return True
        if self._tap_count == 2 and motion_int_status & 1<<4:
            return True

        return False
예제 #7
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
class LPS2X:  # pylint: disable=too-many-instance-attributes
    """Library for the ST LPS2x family of pressure sensors

        :param ~busio.I2C i2c_bus: The I2C bus the LPS25HB 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.

    """

    _chip_id = ROUnaryStruct(_WHO_AM_I, "<B")
    _reset = RWBit(_CTRL_REG2, 2)
    enabled = RWBit(_CTRL_REG1, 7)
    """Controls the power down state of the sensor. Setting to `False` will shut the sensor down"""
    _data_rate = RWBits(3, _CTRL_REG1, 4)
    _raw_temperature = ROUnaryStruct(_TEMP_OUT_L, "<h")
    _raw_pressure = ROBits(24, _PRESS_OUT_XL, 0, 3)

    def __init__(self, i2c_bus, address=_LPS25_DEFAULT_ADDRESS):
        self.i2c_device = i2cdevice.I2CDevice(i2c_bus, address)
        if not self._chip_id in [_LPS25_CHIP_ID]:
            raise RuntimeError("Failed to find LPS25HB! Found chip ID 0x%x" %
                               self._chip_id)

        self.reset()
        self.enabled = True
        self.data_rate = Rate.RATE_25_HZ  # pylint:disable=no-member

    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

    @property
    def pressure(self):
        """The current pressure measurement in hPa"""
        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"""
        raw_temperature = self._raw_temperature
        return (raw_temperature / 480) + 42.5

    @property
    def data_rate(self):
        """The rate at which the sensor measures ``pressure`` and ``temperature``. ``data_rate``
        shouldbe set to one of the values of ``adafruit_lps2x.DataRate``. Note that setting
        ``data_rate``to ``Rate.ONE_SHOT`` places the sensor into a low-power shutdown mode where
        measurements toupdate ``pressure`` and ``temperature`` are only taken when
        ``take_measurement`` is called."""
        return self._data_rate

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

        self._data_rate = value
class INA260:
    """Driver for the INA260 power and current sensor.

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

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

        if self._manufacturer_id != self.TEXAS_INSTRUMENT_ID:
            raise RuntimeError(
                "Failed to find Texas Instrument ID, read " +
                f"{self._manufacturer_id} while expected {self.TEXAS_INSTRUMENT_ID}"
                " - check your wiring!")

        if self._device_id != self.INA260_ID:
            raise RuntimeError(
                "Failed to find INA260 ID, read {self._device_id} while expected {self.INA260_ID}"
                " - check your wiring!")

    _raw_current = ROUnaryStruct(_REG_CURRENT, ">h")
    _raw_voltage = ROUnaryStruct(_REG_BUSVOLTAGE, ">H")
    _raw_power = ROUnaryStruct(_REG_POWER, ">H")

    # MASK_ENABLE fields
    overcurrent_limit = RWBit(_REG_MASK_ENABLE, 15, 2, False)
    """Setting this bit high configures the ALERT pin to be asserted if the current measurement
       following a conversion exceeds the value programmed in the Alert Limit Register.
    """
    under_current_limit = RWBit(_REG_MASK_ENABLE, 14, 2, False)
    """Setting this bit high configures the ALERT pin to be asserted if the current measurement
       following a conversion drops below the value programmed in the Alert Limit Register.
    """
    bus_voltage_over_voltage = RWBit(_REG_MASK_ENABLE, 13, 2, False)
    """Setting this bit high configures the ALERT pin to be asserted if the bus voltage measurement
       following a conversion exceeds the value programmed in the Alert Limit Register.
    """
    bus_voltage_under_voltage = RWBit(_REG_MASK_ENABLE, 12, 2, False)
    """Setting this bit high configures the ALERT pin to be asserted if the bus voltage measurement
       following a conversion drops below the value programmed in the Alert Limit Register.
    """
    power_over_limit = RWBit(_REG_MASK_ENABLE, 11, 2, False)
    """Setting this bit high configures the ALERT pin to be asserted if the Power calculation
       made following a bus voltage measurement exceeds the value programmed in the
       Alert Limit Register.
    """
    conversion_ready = RWBit(_REG_MASK_ENABLE, 10, 2, False)
    """Setting this bit high configures the ALERT pin to be asserted when the Conversion Ready Flag,
        Bit 3, is asserted indicating that the device is ready for the next conversion.
    """
    # from 5 to 9 are not used
    alert_function_flag = ROBit(_REG_MASK_ENABLE, 4, 2, False)
    """While only one Alert Function can be monitored at the ALERT pin at time, the
       Conversion Ready can also be enabled to assert the ALERT pin.
       Reading the Alert Function Flag following an alert allows the user to determine if the Alert
       Function was the source of the Alert.

       When the Alert Latch Enable bit is set to Latch mode, the Alert Function Flag bit
       clears only when the Mask/Enable Register is read.
       When the Alert Latch Enable bit is set to Transparent mode, the Alert Function Flag bit
       is cleared following the next conversion that does not result in an Alert condition.
    """
    _conversion_ready_flag = ROBit(_REG_MASK_ENABLE, 3, 2, False)
    """Bit to help coordinate one-shot or triggered conversion. This bit is set after all
        conversion, averaging, and multiplication are complete.
        Conversion Ready flag bit clears when writing the configuration register or
        reading the Mask/Enable register.
    """
    math_overflow_flag = ROBit(_REG_MASK_ENABLE, 2, 2, False)
    """This bit is set to 1 if an arithmetic operation resulted in an overflow error.
    """
    alert_polarity_bit = RWBit(_REG_MASK_ENABLE, 1, 2, False)
    """Active-high open collector when True, Active-low open collector when false (default).
    """
    alert_latch_enable = RWBit(_REG_MASK_ENABLE, 0, 2, False)
    """Configures the latching feature of the ALERT pin and Alert Flag Bits.
    """

    reset_bit = RWBit(_REG_CONFIG, 15, 2, False)
    """Setting this bit t 1 generates a system reset. Reset all registers to default values."""
    averaging_count = RWBits(3, _REG_CONFIG, 9, 2, False)
    """The window size of the rolling average used in continuous mode"""
    voltage_conversion_time = RWBits(3, _REG_CONFIG, 6, 2, False)
    """The conversion time taken for the bus voltage measurement"""
    current_conversion_time = RWBits(3, _REG_CONFIG, 3, 2, False)
    """The conversion time taken for the current measurement"""

    mode = RWBits(3, _REG_CONFIG, 0, 2, False)
    """The mode that the INA260 is operating in. Must be one of
    ``Mode.CONTINUOUS``, ``Mode.TRIGGERED``, or ``Mode.SHUTDOWN``
    """

    mask_enable = RWBits(16, _REG_MASK_ENABLE, 0, 2, False)
    """The Mask/Enable Register selects the function that is enabled to control the ALERT pin as
        well as how that pin functions.
        If multiple functions are enabled, the highest significant bit
        position Alert Function (D15-D11) takes priority and responds to the Alert Limit Register.
    """
    alert_limit = RWBits(16, _REG_ALERT_LIMIT, 0, 2, False)
    """The Alert Limit Register contains the value used to compare to the register selected in the
        Mask/Enable Register to determine if a limit has been exceeded.
        The format for this register will match the format of the register that is selected for
        comparison.
    """

    TEXAS_INSTRUMENT_ID = const(0x5449)
    INA260_ID = const(0x227)
    _manufacturer_id = ROUnaryStruct(_REG_MFG_UID, ">H")
    """Manufacturer identification bits"""
    _device_id = ROBits(12, _REG_DIE_UID, 4, 2, False)
    """Device identification bits"""
    revision_id = ROBits(4, _REG_DIE_UID, 0, 2, False)
    """Device revision identification bits"""

    @property
    def current(self) -> float:
        """The current (between V+ and V-) in mA"""
        if self.mode == Mode.TRIGGERED:
            while self._conversion_ready_flag == 0:
                pass
        return self._raw_current * 1.25

    @property
    def voltage(self) -> float:
        """The bus voltage in V"""
        if self.mode == Mode.TRIGGERED:
            while self._conversion_ready_flag == 0:
                pass
        return self._raw_voltage * 0.00125

    @property
    def power(self) -> int:
        """The power being delivered to the load in mW"""
        if self.mode == Mode.TRIGGERED:
            while self._conversion_ready_flag == 0:
                pass
        return self._raw_power * 10
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)
예제 #11
0
class BMX160:
    """
    Driver for the BMX160 accelerometer, magnetometer, gyroscope.

    In the buffer, bytes are allocated as follows:
        - mag 0-5
        - rhall 6-7 (not relevant?)
        - gyro 8-13
        - accel 14-19
        - sensor time 20-22
    """

    # multiplicative constants

    # NOTE THESE FIRST TWO GET SET IN THE INIT SEQUENCE
    ACC_SCALAR = 1/(AccelSensitivity2Gravity * g_TO_METERS_PER_SECOND_SQUARED) # 1 m/s^2 = 0.101971621 g
    GYR_SCALAR = 1/GyroSensitivity2DegPerSec_values[4]
    MAG_SCALAR = 1/16
    TEMP_SCALAR = 0.5**9

    _accel = Struct(BMX160_ACCEL_DATA_ADDR, '<hhh') # this is the default scalar, but it should get reset anyhow in init
    _gyro  = Struct(BMX160_GYRO_DATA_ADDR, '<hhh')
    _mag   = Struct(BMX160_MAG_DATA_ADDR, '<hhh')
    _temp  = Struct(BMX160_TEMP_DATA_ADDR, '<h')


    ### STATUS BITS
    status         = ROBits(8, BMX160_STATUS_ADDR, 0)
    status_acc_pmu = ROBits(2, BMX160_PMU_STATUS_ADDR, 4)
    status_gyr_pmu = ROBits(2, BMX160_PMU_STATUS_ADDR, 2)
    status_mag_pmu = ROBits(2, BMX160_PMU_STATUS_ADDR, 0)
    cmd = RWBits(8, BMX160_COMMAND_REG_ADDR, 0)
    foc = RWBits(8, BMX160_FOC_CONF_ADDR, 0)

    # see ERR_REG in section 2.11.2
    _error_status = ROBits(8, BMX160_ERROR_REG_ADDR, 0)
    error_code    = ROBits(4, BMX160_ERROR_REG_ADDR, 1)
    drop_cmd_err  = ROBit(BMX160_ERROR_REG_ADDR, 6)
    fatal_err     = ROBit(BMX160_ERROR_REG_ADDR, 0)

    # straight from the datasheet. Need to be renamed and better explained
    @property
    def drdy_acc(self): return (self.status >> 7) & 1
    @property
    def drdy_gyr(self): return (self.status >> 6) & 1
    @property
    def drdy_mag(self): return (self.status >> 5) & 1
    @property
    def nvm_rdy(self): return (self.status >> 4) & 1
    @property
    def foc_rdy(self): return (self.status >> 3) & 1
    @property
    def mag_man_op(self): return (self.status >> 2) & 1
    @property
    def gyro_self_test_ok(self): return (self.status >> 1)  & 1

    _BUFFER = bytearray(40)
    _smallbuf = bytearray(6)

    _gyro_range = RWBits(8, BMX160_GYRO_RANGE_ADDR, 0)
    _accel_range = RWBits(8, BMX160_ACCEL_RANGE_ADDR, 0)

    # _gyro_bandwidth = NORMAL
    # _gyro_powermode = NORMAL
    _gyro_odr = 25    # Hz

    # _accel_bandwidth = NORMAL
    # _accel_powermode = NORMAL
    _accel_odr = 25  # Hz

    # _mag_bandwidth = NORMAL
    # _mag_powermode = NORMAL
    _mag_odr = 25    # Hz
    _mag_range = 250 # deg/sec


    def __init__(self):
        # soft reset & reboot
        self.cmd = BMX160_SOFT_RESET_CMD
        time.sleep(0.001)
        # Check ID registers.
        ID = self.read_u8(BMX160_CHIP_ID_ADDR)
        if ID != BMX160_CHIP_ID:
            raise RuntimeError('Could not find BMX160, check wiring!')

        # print("status:", format_binary(self.status))
        # set the default settings
        self.init_mag()
        self.init_accel()
        self.init_gyro()
        # print("status:", format_binary(self.status))

    ######################## SENSOR API ########################

    def read_all(self):
        return self.read_bytes(BMX160_MAG_DATA_ADDR, 20, self._BUFFER)

    # synonymous
    # @property
    # def error_status(self):
    #     return format_binary(self._error_status)
    @property
    def query_error(self):
        return format_binary(self._error_status)

    ### ACTUAL API
    @property
    def gyro(self):
        # deg/s
        return tuple(x * self.GYR_SCALAR for x in self._gyro)

    @property
    def accel(self):
        # m/s^2
        return tuple(x * self.ACC_SCALAR for x in self._accel)

    @property
    def mag(self):
        # uT
        return tuple(x * self.MAG_SCALAR for x in self._mag)

    @property
    def temperature(self):
        return self._temp[0]*self.TEMP_SCALAR+23
    @property
    def temp(self):
        return self._temp[0]*self.TEMP_SCALAR+23

    @property
    def sensortime(self):
        tbuf = self.read_bytes(BMX160_SENSOR_TIME_ADDR, 3, self._smallbuf)
        t0, t1, t2 = tbuf[:3]
        t = (t2 << 16) | (t1 << 8) | t0
        t *= 0.000039 # the time resolution is 39 microseconds
        return t

    ######################## SETTINGS RELATED ########################

    ############## GYROSCOPE SETTINGS  ##############
    # NOTE still missing BW / OSR config, but those are more complicated

    def init_gyro(self):
        # BW doesn't have an interface yet
        self._gyro_bwmode = BMX160_GYRO_BW_NORMAL_MODE
        # These rely on the setters to do their magic.
        self.gyro_range = BMX160_GYRO_RANGE_500_DPS
        # self.GYR_SCALAR = 1
        # self.GYR_SCALAR = 1/GyroSensitivity2DegPerSec_values[1]

        self.gyro_odr = 25
        self.gyro_powermode = BMX160_GYRO_NORMAL_MODE

    @property
    def gyro_range(self):
        return self._gyro_range

    @gyro_range.setter
    def gyro_range(self, rangeconst):
        """
        The input is expected to be the BMX160-constant associated with the range.

        deg/s | bmxconst value | bmxconst_name
        ------------------------------------------------------
        2000  |   0            |  BMX160_GYRO_RANGE_2000_DPS
        1000  |   1            |  BMX160_GYRO_RANGE_1000_DPS
        500   |   2            |  BMX160_GYRO_RANGE_500_DPS
        250   |   3            |  BMX160_GYRO_RANGE_250_DPS
        125   |   4            |  BMX160_GYRO_RANGE_125_DPS

        ex: bmx.gyro_range = BMX160_GYRO_RANGE_500_DPS
        equivalent to: bmx.gyro_range = 2
        BMX160_GYRO_RANGE_VALUES = [2000, 1000, 500, 250, 125]
        """

        if rangeconst in BMX160_GYRO_RANGE_CONSTANTS:
            self._gyro_range = rangeconst
            # read out the value to see if it changed successfully
            rangeconst = self._gyro_range
            val = BMX160_GYRO_RANGE_VALUES[rangeconst]
            self.GYR_SCALAR = (val / 32768.0)
        else:
            pass


    @property
    def gyro_odr(self):
        return self._gyro_odr

    @gyro_odr.setter
    def gyro_odr(self, odr):
        """
        Set the output data rate of the gyroscope. The possible ODRs are 1600, 800, 400, 200, 100,
        50, 25, 12.5, 6.25, 3.12, 1.56, and 0.78 Hz. Note, setting a value between the listed ones
        will round *downwards*.
        """
        res = self.generic_setter(odr, BMX160_GYRO_ODR_VALUES,
                                  BMX160_GYRO_ODR_CONSTANTS,
                                  BMX160_GYRO_CONFIG_ADDR,
                                  "gyroscope odr")
        if res != None:
            self._gyro_odr = res[1]

    @property
    def gyro_powermode(self):
        return self._gyro_powermode

    @gyro_powermode.setter
    def gyro_powermode(self, powermode):
        """
        Set the power mode of the gyroscope. Unlike other setters, this one has to directly take the
        BMX160-const associated with the power mode. The possible modes are:
        `BMX160_GYRO_SUSPEND_MODE`
        `BMX160_GYRO_NORMAL_MODE`
        `BMX160_GYRO_FASTSTARTUP_MODE`
        """
        if powermode not in BMX160_GYRO_MODES:
            print("Unknown gyroscope powermode: " + str(powermode))
            return

        self.write_u8(BMX160_COMMAND_REG_ADDR, powermode)
        if int(self.query_error) == 0:
            self._gyro_powermode = powermode
        else:
            settingswarning("gyroscope power mode")

        # NOTE: this delay is a worst case. If we need repeated switching
        # we can choose the delay on a case-by-case basis.
        time.sleep(0.0081)


    ############## ACCELEROMETER SETTINGS  ##############

    def init_accel(self):
        # BW doesn't have an interface yet
        # self.write_u8(BMX160_ACCEL_CONFIG_ADDR, BMX160_ACCEL_BW_NORMAL_AVG4)
        # self._accel_bwmode = BMX160_ACCEL_BW_NORMAL_AVG4
        # These rely on the setters to do their magic.
        self.accel_range = BMX160_ACCEL_RANGE_8G
        self.accel_odr = 25
        self.accel_powermode = BMX160_ACCEL_NORMAL_MODE

    @property
    def accel_range(self):
        return self._accel_range

    @accel_range.setter
    def accel_range(self, rangeconst):
        """
        The input is expected to be the BMX160-constant associated with the range.

        deg/s | bmxconst value | bmxconst name
        ------------------------------------------------------
        2     |   3            | BMX160_ACCEL_RANGE_2G
        4     |   5            | BMX160_ACCEL_RANGE_4G
        8     |   8            | BMX160_ACCEL_RANGE_8G
        16    |   12           | BMX160_ACCEL_RANGE_16G

        ex: bmx.accel_range = BMX160_ACCEL_RANGE_4G
        equivalent to: bmx.accel_range = 5
        """

        if rangeconst in BMX160_ACCEL_RANGE_CONSTANTS:
            self._accel_range = rangeconst
            # read out the value to see if it changed successfully
            rangeconst = self._accel_range
            # convert to 0-3 range for indexing
            ind = rangeconst >> 2
            val = BMX160_ACCEL_RANGE_VALUES[ind]
            self.ACC_SCALAR = (val / 32768.0) / g_TO_METERS_PER_SECOND_SQUARED
        else:
            pass

    @property
    def accel_odr(self):
        return self._accel_odr

    @accel_odr.setter
    def accel_odr(self, odr):
        res = self.generic_setter(odr, BMX160_ACCEL_ODR_VALUES,
                                  BMX160_ACCEL_ODR_CONSTANTS,
                                  BMX160_ACCEL_CONFIG_ADDR,
                                  "accelerometer odr")
        if res != None:
            self._accel_odr = res[1]

    @property
    def accel_powermode(self):
        return self._accel_powermode

    @accel_powermode.setter
    def accel_powermode(self, powermode):
        """
        Set the power mode of the accelerometer. Unlike other setters, this one has to directly take the
        BMX160-const associated with the power mode. The possible modes are:
        `BMI160_ACCEL_NORMAL_MODE`
        `BMI160_ACCEL_LOWPOWER_MODE`
        `BMI160_ACCEL_SUSPEND_MODE`
        """
        if powermode not in BMX160_ACCEL_MODES:
            print("Unknown accelerometer power mode: " + str(powermode))
            return

        self.write_u8(BMX160_COMMAND_REG_ADDR, powermode)
        if int(self.query_error) == 0:
            self._accel_powermode = powermode
        else:
            settingswarning("accelerometer power mode")

        # NOTE: this delay is a worst case. If we need repeated switching
        # we can choose the delay on a case-by-case basis.
        time.sleep(0.005)

    ############## MAGENTOMETER SETTINGS  ##############

    def init_mag(self):
        # see pg 25 of: https://ae-bst.resource.bosch.com/media/_tech/media/datasheets/BST-BMX160-DS000.pdf
        self.write_u8(BMX160_COMMAND_REG_ADDR, BMX160_MAG_NORMAL_MODE)
        time.sleep(0.00065) # datasheet says wait for 650microsec
        self.write_u8(BMX160_MAG_IF_0_ADDR, 0x80)
        # put mag into sleep mode
        self.write_u8(BMX160_MAG_IF_3_ADDR, 0x01)
        self.write_u8(BMX160_MAG_IF_2_ADDR, 0x4B)
        # set x-y to regular power preset
        self.write_u8(BMX160_MAG_IF_3_ADDR, 0x04)
        self.write_u8(BMX160_MAG_IF_2_ADDR, 0x51)
        # set z to regular preset
        self.write_u8(BMX160_MAG_IF_3_ADDR, 0x0E)
        self.write_u8(BMX160_MAG_IF_2_ADDR, 0x52)
        # prepare MAG_IF[1-3] for mag_if data mode
        self.write_u8(BMX160_MAG_IF_3_ADDR, 0x02)
        self.write_u8(BMX160_MAG_IF_2_ADDR, 0x4C)
        self.write_u8(BMX160_MAG_IF_1_ADDR, 0x42)
        # Set ODR to 25 Hz
        self.write_u8(BMX160_MAG_ODR_ADDR, BMX160_MAG_ODR_25HZ)
        self.write_u8(BMX160_MAG_IF_0_ADDR, 0x00)
        # put in low power mode.
        self.write_u8(BMX160_COMMAND_REG_ADDR, BMX160_MAG_LOWPOWER_MODE)
        time.sleep(0.1) # takes this long to warm up (empirically)

    @property
    def mag_powermode(self):
        return self._mag_powermode

    @mag_powermode.setter
    def mag_powermode(self, powermode):
        """
        Set the power mode of the magnetometer. Unlike other setters, this one has to directly take the
        BMX160-const associated with the power mode. The possible modes are:
        `BMX160_`
        `BMX160_`
        `BMX160_`
        """
        # if powermode not in BMX160_MAG_:
        #     print("Unknown gyroscope powermode: " + str(powermode))
        #     return

        self.write_u8(BMX160_COMMAND_REG_ADDR, powermode)
        if int(self.query_error) == 0:
            self._mag_powermode = powermode
        else:
            settingswarning("mag power mode")

        # NOTE: this delay is a worst case. If we need repeated switching
        # we can choose the delay on a case-by-case basis.
        time.sleep(0.001)

    ## UTILS:
    def generic_setter(self, desired, possible_values, bmx_constants, config_addr, warning_interp = ""):
        i = find_nearest_valid(desired, possible_values)
        rounded = possible_values[i]
        bmxconst = bmx_constants[i]
        self.write_u8(config_addr, bmxconst)
        e = self.error_code

        if e == BMX160_OK:
            return (i, rounded)
        else:
            settingswarning(warning_interp)
예제 #12
0
class LPS2X:  # pylint: disable=too-many-instance-attributes
    """Base class ST LPS2x family of pressure sensors

        :param ~busio.I2C i2c_bus: The I2C bus the sensor 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.

    """

    _chip_id = ROUnaryStruct(_LPS2X_WHO_AM_I, "<B")
    _raw_temperature = ROUnaryStruct(_LPS2X_TEMP_OUT_L, "<h")
    _raw_pressure = ROBits(24, _LPS2X_PRESS_OUT_XL, 0, 3)

    def __init__(self, i2c_bus, address=_LPS2X_DEFAULT_ADDRESS, chip_id=None):
        self.i2c_device = i2cdevice.I2CDevice(i2c_bus, address)
        if not self._chip_id in [chip_id]:
            raise RuntimeError("Failed to find LPS2X! Found chip ID 0x%x" %
                               self._chip_id)
        self.reset()
        self.initialize()
        sleep(0.010)  # delay 10ms for first reading

    def initialize(self):  # pylint: disable=no-self-use
        """Configure the sensor with the default settings. For use after calling `reset()`"""
        raise RuntimeError(
            "LPS2X Base class cannot be instantiated directly. Use LPS22 or LPS25 instead"
        )  # override in subclass

    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

    @property
    def pressure(self):
        """The current pressure measurement in hPa"""
        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"""

        raw_temperature = self._raw_temperature
        return (raw_temperature / self._temp_scaling  # pylint:disable=no-member
                ) + self._temp_offset  # pylint:disable=no-member

    @property
    def data_rate(self):
        """The rate at which the sensor measures ``pressure`` and ``temperature``. ``data_rate``
        shouldbe set to one of the values of ``adafruit_lps2x.Rate``."""
        return self._data_rate

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

        self._data_rate = value
예제 #13
0
class MMC5603NJ: 
    """
    Driver for the MMC5603NJ magnetometer
    
    :param busio.I2C i2c_bus: The I2C bus the mag is connected to
    """

    _BUFFER = bytearray(6)

    # _device_id = ROUnaryStruct(Product_ID, "B") # B = unsigned char/integer
    _device_id = ROBits(8, Product_ID, 0)
    # 0b 0001 0000

    # _raw_x0 = ROUnaryStruct(Xout0, "<h") # < = little-endian, h = short/integer
    # _raw_y0 = ROUnaryStruct(Yout0, "<h")
    # _raw_z0 = ROUnaryStruct(Zout0, "<h")
    _raw_x0 = ROBits(8, Xout0, 0)
    _raw_x1 = ROBits(8, Xout1, 0)
    _raw_x2 = ROBits(4, Xout2, 4)
    _raw_y0 = ROBits(8, Yout0, 0)
    _raw_y1 = ROBits(8, Yout1, 0)
    _raw_y2 = ROBits(4, Yout2, 4)
    _raw_z0 = ROBits(8, Zout0, 0)
    _raw_z1 = ROBits(8, Zout1, 0)
    _raw_z2 = ROBits(4, Zout2, 4)
    
    _raw_t = ROBits(8, Tout, 0)
    
    _status = ROUnaryStruct(Status1, "B")
    _sat_sensor = ROBit(Status1, 5)
    _meas_m_done = ROBit(Status1, 6)
    _meas_t_done = ROBit(Status1, 7)
    
    _odr = UnaryStruct(ODR, "B")
    
    _contr_reg_0 = RWBits(8, Internal_control_0, 0)
    _do_reset = RWBit(Internal_control_0, 4)
    _auto_sr_en = RWBit(Internal_control_0, 5)
    _cmm_freq_en = RWBit(Internal_control_0, 7)
    
    _bw = RWBits(2, Internal_control_1, 0)
    
    _cmm_en = RWBit(Internal_control_2, 4)    
    

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

        if self._device_id != 0x10:
            raise AttributeError("Cannot find an MMC5603NJ")
            
    def reset(self):
        """Do RESET to 'condition the AMR sensors for optimum performance  
        """
        self._do_reset = 1
        sleep(3)

    @property
    def magnetic_cmm(self):
        """The processed magnetometer sensor values.
        Print out measurements periodically
        
        :param float delay: sleep delay in seconds
        """
        
        delay = 3.0
        
        self.reset()
        
        # set bw?
        self._bw = 0b00
        
        # set automatic set/reset
        self._auto_sr_en = 1
        
        # write the desired number into output data rate
        self._odr = 1 # is this 75 Hz? with auto set/reset
        
        # set cmm_freq_en to 1 to calculate target number for the counter
        self._cmm_freq_en = 1
        
        # set cmm_en to 1 to start continous mode, internal counter starts
        self._cmm_en = 1
        
        sleep(1)
        
        # need to add a way to stop continuous measurements
        # press a button?
        while True:
            magx, magy, magz = self.magnetic_get_processed_mag_data()
            print("X:{0:7.2f}, Y:{1:7.2f}, Z:{2:7.2f} ".format(magx, magy, magz))
            sleep(delay)
            
        return()

    @property
    def magnetic_single(self):
        """The processed magnetometer sensor values.
        A tuple of X, Y, Z axis values.
        """
        # TM_M and Auto_SR_en high
        self._contr_reg_0 = 0b00100001
        
        # check Meas_M_Done bit
        while self._meas_m_done != 1:
            pass
               
        return self.magnetic_get_processed_mag_data()
    
    def magnetic_get_processed_mag_data(self):
        """
        Returns
        -------
        TYPE
            DESCRIPTION.
        TYPE
            DESCRIPTION.
        TYPE
            DESCRIPTION.
        """
        # Need to check math on this
        # 20 bit
        xval = (self._raw_x0<<12) | (self._raw_x1<<4) | (self._raw_x2)
        yval = (self._raw_y0<<12) | (self._raw_y1<<4) | (self._raw_y2)
        zval = (self._raw_z0<<12) | (self._raw_z1<<4) | (self._raw_z2)
        mask = 524288 
        xval = -(xval & mask) + (xval & ~mask)
        yval = -(yval & mask) + (yval & ~mask)
        zval = -(zval & mask) + (zval & ~mask)
        
        return (
            xval*0.0000625, yval*0.0000625, zval*0.0000625
        )

    @property
    def magnetic_raw(self):
        """The raw magnetometer sensor values.
        """
        # TM_M and Auto_SR_en high
        self._contr_reg_0 = 0b00100001
        
        # check Meas_M_Done bit
        while self._meas_m_done != 1:
            pass
        
        return self.magnetic_get_raw_mag_data()
    
    def magnetic_get_raw_mag_data(self):
        """
        Returns
        -------
        TYPE
            DESCRIPTION.
        TYPE
            DESCRIPTION.
        TYPE
            DESCRIPTION.
        TYPE
            DESCRIPTION.
        TYPE
            DESCRIPTION.
        TYPE
            DESCRIPTION.
        TYPE
            DESCRIPTION.
        TYPE
            DESCRIPTION.
        TYPE
            DESCRIPTION.

        """
        return (
            self._raw_x0, self._raw_x1, self._raw_x2,
            self._raw_y0, self._raw_y1, self._raw_y2,
            self._raw_z0, self._raw_z1, self._raw_z2,
        )

    @property
    def temperature(self):
        """The processed temperature output value in deg C. 
        (Unsigned format, range is -75 to 125 deg C, 00000000 stands for -75 deg C)
        """
        self._contr_reg_0 = 0b00100010
        
        while self._meas_t_done != 1:
            pass
        
        return (self._raw_t * (200/255)) - 75
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
예제 #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 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
예제 #16
0
class FDC2212(object):
    """
    :param i2c: The `busio.I2C` object to use. This is the only required parameter.
    :param 
    # Class-level buffer to reduce allocations and fragmentation.
    # Note this is NOT thread-safe or re-entrant by design!   
    """
    _BUFFER = bytearray(8)
    RESOLUTION = 2**28

    # Misc Settings
    rst = RWBit(FDC2212_RESET_DEV, 15, 2, False)
    gain = RWBits(2, FDC2212_RESET_DEV, 9, 2, False)
    drdy0 = ROBit(FDC2212_STATUS, 6, 2, False)
    drdy1 = ROBit(FDC2212_STATUS, 6, 2, False)
    drdy01 = ROBits(2, FDC2212_STATUS, 2, 2, False)
    status = RWBits(16, FDC2212_STATUS, 0, 2, False)

    # CONFIG register settings
    slp = RWBit(FDC2212_CONFIG, 13, 2, False)
    ref_clk = RWBit(FDC2212_CONFIG, 9, 2, False)
    high_drv = RWBit(FDC2212_CONFIG, 6, 2, False)
    active_mode = RWBits(2, FDC2212_CONFIG, 14, 2, False)
    config_all = RWBits(16, FDC2212_CONFIG, 0, 2, False)

    # CLOCK DIVIDERS
    ch0_fsel = RWBits(2, FDC2212_CLOCK_DIVIDERS_CH0, 12, 2, False)
    ch0_fdiv = RWBits(10, FDC2212_CLOCK_DIVIDERS_CH0, 0, 2, False)
    ch0_clk = RWBits(16, FDC2212_CLOCK_DIVIDERS_CH0, 0, 2, False)
    ch1_fsel = RWBits(2, FDC2212_CLOCK_DIVIDERS_CH1, 12, 2, False)
    ch1_fdiv = RWBits(10, FDC2212_CLOCK_DIVIDERS_CH1, 0, 2, False)
    ch1_clk = RWBits(16, FDC2212_CLOCK_DIVIDERS_CH1, 0, 2, False)

    # DATA registers
    data_ch0a = ROBits(16, FDC2212_DATA_CH0_MSB, 0, 2, False)
    data_ch0b = ROBits(16, FDC2212_DATA_CH0_LSB, 0, 2, False)
    data_ch1a = ROBits(16, FDC2212_DATA_CH1_MSB, 0, 2, False)
    data_ch1b = ROBits(16, FDC2212_DATA_CH1_LSB, 0, 2, False)

    # MUX_CONFIG register
    auto_scan = RWBit(FDC2212_MUX_CONFIG, 15, 2, False)
    rr_seq = RWBits(2, FDC2212_MUX_CONFIG, 13, 2, False)
    dglitch = RWBits(3, FDC2212_MUX_CONFIG, 0, 2, False)

    def __init__(self, i2c, *, address=FDC2212_I2C_ADDR_0, debug=False):
        self.i2c_device = I2CDevice(i2c, address)
        self.rst = True
        # Check for valid chip ID
        if self._read16(FDC2212i2c_device_ID) not in (0x3055, 0x3054):
            raise RuntimeError('Failed to find FDC2212/FDC2214, check wiring!')
        self.debug = debug
        '''
        CONFIG Register
         00     0   [1]   0   [1]  0  [0]  1    0    [000001]
        CHAN   SLP  RES DRIVE RES CLK RES INT HDRIVE RES

        MUX_CONFIG
         1       00    [00 0100 0001]    101
        SCAN   RR_SEQ      RES         DGLITCH        
        '''
        self._config = 0x1481
        self._mux = 0x820D

        # Initalize varriables
        self._fulldrive = True
        self._fclk = 43.3e6  # 43.3 MHz (internal)
        self._L = 18e-6  # 18uH
        self._cap = 33e-12  # 33pf
        self._differential = True  # differential (default)
        self._div = 1
        self.fref = self._fclk / 1
        self._Idrive = 1
        self._Fsense = self._Csense = 0
        self._channel = 0
        self._MSB = FDC2212_DATA_CH0_MSB
        self._LSB = FDC2212_DATA_CH0_LSB

        self._write16(FDC2212_RCOUNT_CH0, 0xFFFF)
        self._write16(FDC2212_RCOUNT_CH1, 0xFFFF)
        self._write16(FDC2212_SETTLECOUNT_CH0, 0x0400)
        self._write16(FDC2212_SETTLECOUNT_CH1, 0x0400)
        self._write16(FDC2212_CLOCK_DIVIDERS_CH0, 0x1001)
        self._write16(FDC2212_CLOCK_DIVIDERS_CH1, 0x1001)
        self._write16(FDC2212_DRIVE_CH0, 0x4800)
        self._write16(FDC2212_DRIVE_CH1, 0x4800)
        self._write16(FDC2212_MUX_CONFIG, self._mux)
        self._write16(FDC2212_CONFIG, self._config)

    @property
    def clock(self):
        return self._fclk

    @clock.setter
    def clock(self, freq):
        self._fclk = freq
        if freq != 43.3e6:
            print('External Clock Enabled')
            self._config |= (1 << 9)
            self._write16(FDC2212_CONFIG, self._config)

    @property
    def inductance(self):
        return self._L

    @inductance.setter
    def inductance(self, induc):
        self._L = induc

    @property
    def selfcitance(self):
        return self._cap

    @selfcitance.setter
    def selfcitance(self, cap):
        self._cap = cap

    @property
    def RCOUNT(self):
        return self._RCOUNT

    @RCOUNT.setter
    def RCOUNT(self, rcount):
        self._RCOUNT = rcount
        self._write16(FDC2212_RCOUNT_CH0, rcount)
        self._write16(FDC2212_RCOUNT_CH1, rcount)

    @property
    def SETTLE(self):
        return self._SETTLE

    @SETTLE.setter
    def SETTLE(self, settle):
        '''
        Settle time = (SETTLECOUNT * 16)/Fclk
        (0xFFFF*16)/40E6=26.2ms
        '''
        self._SETTLE = settle
        self._write16(FDC2212_SETTLECOUNT_CH0, settle)
        self._write16(FDC2212_SETTLECOUNT_CH1, settle)

    @property
    def Idrive(self):
        return self._Idrive

    @Idrive.setter
    def Idrive(self, idrive):
        self._Idrive = idrive
        self._write16(FDC2212_DRIVE_CH0, idrive)
        self._write16(FDC2212_DRIVE_CH1, idrive)

    # @property
    # def status(self):
    #     return self._read16(FDC2212_STATUS)

    @property
    def status_config(self):
        self._status_config = self._read16(FDC2212_STATUS_CONFIG)
        return self._status_config

    @status_config.setter
    def status_config(self, setting):
        self._status_config = setting
        self._write16(FDC2212_STATUS_CONFIG, self._status_config)

    @property
    def full_drive(self):
        return self._fulldrive

    @full_drive.setter
    def full_drive(self, state):
        # full-current drive on all channels
        self._fulldrive = state
        if state:  # enabled
            self._config |= (1 << 11)
        else:  # disabled
            self._config &= ~(1 << 11)
        self._write16(FDC2212_CONFIG, self._config)

    @property
    def differential(self):
        # Sets differential/single-ended for BOTH channels
        return self._differential

    @differential.setter
    def differential(self, div):
        self._differential = div
        if self._differential:
            # differential (default)
            self._write16(FDC2212_CLOCK_DIVIDERS_CH0, 0x1001)
            self._write16(FDC2212_CLOCK_DIVIDERS_CH1, 0x1001)
            self._div = 1
        else:
            # single-ended
            self._write16(FDC2212_CLOCK_DIVIDERS_CH0, 0x2001)
            self._write16(FDC2212_CLOCK_DIVIDERS_CH1, 0x2001)
            self._div = 2

    @property
    def sleep(self):
        return self._sleep

    @sleep.setter
    def sleep(self, value):
        if value:
            self._config |= (1 << 13)
        else:
            self._config &= ~(1 << 13)
        self._write16(FDC2212_CONFIG, self._config)

    @property
    def scan(self):
        return bool(self.auto_scan)

    @scan.setter
    def scan(self, value):
        self.slp = True
        if value:
            self.auto_scan = True
            self.rr_seq = 0x00  #CH0 & CH1
        else:
            self.auto_scan = False
        self.slp = False

    @property
    def deglitch(self):
        return self._deglitch

    @deglitch.setter
    def deglitch(self, value):
        '''
        Input deglitch filter bandwidth.
        Select the lowest setting that exceeds the oscillation tank
        oscillation frequency.
        1MHz,3.3MHz,10MHz,33MHz
        '''
        if value not in (1, 4, 5, 7):
            raise ValueError(
                "Unsupported deglitch setting. Valid values are: 1(1MHz),4(3.3MHz),5(10MHz),7(33MHz)"
            )
        self.dglitch = value
        if self.debug: print(hex(self._mux))

    @property
    def channel(self):
        return self._channel

    @channel.setter
    def channel(self, channel):
        if channel not in (0, 1):
            raise ValueError("Unsupported channel.")
        if channel == 0:
            self._config &= ~(1 << 14)
            self._write16(FDC2212_CONFIG, self._config)
            self._MSB = FDC2212_DATA_CH0_MSB
            self._LSB = FDC2212_DATA_CH0_LSB
        if channel == 1:
            self._config |= (1 << 14)
            self._write16(FDC2212_CONFIG, self._config)
            self._MSB = FDC2212_DATA_CH1_MSB
            self._LSB = FDC2212_DATA_CH1_LSB
        self._channel = channel

    def _read_into(self, address, buf, count=None):
        # Read bytes from the specified address into the provided buffer.
        # If count is not specified (the default) the entire buffer is filled,
        # otherwise only count bytes are copied in.
        assert len(buf) > 0
        if count is None:
            count = len(buf)
        with self.i2c_device as i2c:
            i2c.write_then_readinto(bytes([address & 0xFF]),
                                    buf,
                                    in_end=count,
                                    stop=False)
        # [print(hex(i),'\t',end='') for i in self._BUFFER]
        # print('')

    def _read16(self, address):
        # Read a 16-bit unsigned value for from the specified address.
        self._read_into(address, self._BUFFER, count=2)
        _raw = self._BUFFER[0] << 8 | self._BUFFER[1]
        return _raw

    def _write16(self, address, value):
        # Write a 16-bit unsigned value to the specified address.
        self._BUFFER[0] = address & 0xFF
        self._BUFFER[1] = (value >> 8) & 0xFF
        self._BUFFER[2] = (value) & 0xff
        with self.i2c_device as i2c:
            i2c.write(self._BUFFER, end=3)

    def _read_raw(self):
        _reading = (self._read16(self._MSB) & FDC2212_DATA_CHx_MASK_DATA) << 16
        _reading |= self._read16(self._LSB)
        return _reading

    def burst(self, cnt=10):
        _burst = []
        _buff = []
        test = bytearray(2)
        t1 = time.monotonic_ns()
        with self.i2c_device as i2c:
            for _ in range(cnt):
                i2c.write_then_readinto(bytes([0x01]), test, stop=False)
                # [print(hex(i),'\t',end='') for i in test]
                # print('')
                _buff.append((test[0], test[1]))
        t1 = time.monotonic_ns() - t1
        # print(_buff)
        print('Freq:{} Hz'.format(cnt / (t1 * 1e-9)))
        for item in _buff:
            _dat = 0x1ed0000
            _dat |= ((item[0] << 8) | item[1])
            self._Fsense = (self._div * _dat * self._fclk / self.RESOLUTION)
            _burst.append(
                (1e12) * ((1 / (self._L *
                                (2 * pi * self._Fsense)**2)) - self._cap))
        return _burst

    def read(self):
        _reading = self._read_raw()
        try:
            # calculate fsensor (40MHz external ref)
            self._Fsense = (self._div * _reading * self._fclk /
                            self.RESOLUTION)
            # calculate Csensor (18uH and 33pF LC tank)
            self._Csense = (1e12) * (
                (1 / (self._L * (2 * pi * self._Fsense)**2)) - self._cap)
        except Exception as e:
            if self.debug: print('Error on read:', e)
            pass
        return self._Csense

    def read_both(self, errchck=False):
        '''
        Reads CH0 and CH1 data channels and returns a tuple:
        (CH0_DATA,CH1_DATA,CH0_ERR_AW,CH0_ERR_WD,CH1_ERR_AW,CH1_ERR_WD)
        '''
        output = []
        err = 0
        s = time.monotonic()
        while self.drdy01 != 3:
            if time.monotonic() - s > 10:
                return (0, 0, 0, 0, 0, 0)
            pass
        _reading1 = self.data_ch0a
        if errchck:
            output.append(_reading1 & FDC2212_DATA_CHx_MASK_ERRAW)  #ch0 err_aw
            output.append(_reading1
                          & FDC2212_DATA_CHx_MASK_ERRWD)  #ch0 err_wdt
        _reading1 = (_reading1 & FDC2212_DATA_CHx_MASK_DATA) << 16
        _reading1 |= self.data_ch0b

        _reading2 = self.data_ch1a
        if errchck:
            output.append(_reading2 & FDC2212_DATA_CHx_MASK_ERRAW)  #ch1 err_aw
            output.append(_reading2
                          & FDC2212_DATA_CHx_MASK_ERRWD)  #ch1 err_wdt
        _reading2 = (_reading2 & FDC2212_DATA_CHx_MASK_DATA) << 16
        _reading2 |= self.data_ch1b
        try:
            for i in _reading1, _reading2:
                # calculate fsensor (40MHz external ref)
                # self._Fsense=(self._div*i*self._fclk/self.RESOLUTION)
                # self._Fsense=self.ch_sel*self.fref*((i/self.RESOLUTION))
                self._Fsense = 1 * self.fref * ((i / self.RESOLUTION))

                # calculate Csensor (18uF and 33pF LC tank) in pF
                self._Csense = (1e12) * (
                    (1 / (self._L * (2 * pi * self._Fsense)**2)) - self._cap)
                output.append(self._Csense)
        except Exception as e:
            if self.debug: print('Error on read:', e)
            return output.append((0, 0))
        return output
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
예제 #18
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)
예제 #19
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
예제 #20
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)

    _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
        """Pressure in hectoPascals at sea level. Used to calibrate `altitude`."""
        self.initialize()

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

        self.reset()

        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
        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 (`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 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

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

        To avoid waiting indefinitely this function raises an
        error if the sensor isn't configured for temperate measurements,
        ie. ``Mode.ONE_TEMPERATURE``, ``Mode.CONT_TEMP`` or ``Mode.CONT_PRESTEMP``.
        See the `Mode` documentation for details.
        """
        if (self._mode_bits == Mode.IDLE
                or self._mode_bits == Mode.ONE_PRESSURE
                or self._mode_bits == Mode.CONT_PRESSURE):
            raise RuntimeError(
                "Sensor mode is set to idle or pressure measurement,\
                    can't wait for a temperature measurement")
        while self._temp_ready is False:
            sleep(0.001)

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

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

        To avoid waiting indefinitely this function raises an
        error if the sensor isn't configured for pressure measurements,
        ie.  ``Mode.ONE_PRESSURE``, ``Mode.CONT_PRESSURE`` or ``Mode.CONT_PRESTEMP``
        See the `Mode` documentation for details.
        """
        if (self._mode_bits == Mode.IDLE
                or self._mode_bits == Mode.ONE_TEMPERATURE
                or self._mode_bits == Mode.CONT_TEMP):
            raise RuntimeError(
                "Sensor mode is set to idle or temperature measurement,\
                    can't wait for a pressure measurement")
        while self._pressure_ready is False:
            sleep(0.001)

    @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)
예제 #21
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
class HTS221:  # pylint: disable=too-many-instance-attributes
    """Library for the ST HTS221 Humidity and Temperature Sensor

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

    """

    _chip_id = ROUnaryStruct(_WHO_AM_I, "<B")
    _boot_bit = RWBit(_CTRL_REG2, 7)
    enabled = RWBit(_CTRL_REG1, 7)
    """Controls the power down state of the sensor. Setting to `False` will shut the sensor down"""
    _data_rate = RWBits(2, _CTRL_REG1, 0)
    _one_shot_bit = RWBit(_CTRL_REG2, 0)
    _temperature_status_bit = ROBit(_STATUS_REG, 0)
    _humidity_status_bit = ROBit(_STATUS_REG, 1)
    _raw_temperature = ROUnaryStruct(_TEMP_OUT_L, "<h")
    _raw_humidity = ROUnaryStruct(_HUMIDITY_OUT_L, "<h")

    # humidity calibration consts
    _t0_deg_c_x8_lsbyte = ROBits(8, _T0_DEGC_X8, 0)
    _t1_deg_c_x8_lsbyte = ROBits(8, _T1_DEGC_X8, 0)
    _t1_t0_deg_c_x8_msbits = ROBits(4, _T1_T0_MSB, 0)

    _t0_out = ROUnaryStruct(_T0_OUT, "<h")
    _t1_out = ROUnaryStruct(_T1_OUT, "<h")

    _h0_rh_x2 = ROUnaryStruct(_H0_RH_X2, "<B")
    _h1_rh_x2 = ROUnaryStruct(_H1_RH_X2, "<B")

    _h0_t0_out = ROUnaryStruct(_H0_T0_OUT, "<h")
    _h1_t0_out = ROUnaryStruct(_H1_T1_OUT, "<h")

    def __init__(self, i2c_bus):
        self.i2c_device = i2cdevice.I2CDevice(i2c_bus, _HTS221_DEFAULT_ADDRESS)
        if not self._chip_id in [_HTS221_CHIP_ID]:
            raise RuntimeError("Failed to find HTS221HB! Found chip ID 0x%x" %
                               self._chip_id)
        self._boot()
        self.enabled = True
        self.data_rate = Rate.RATE_12_5_HZ  # pylint:disable=no-member

        t1_t0_msbs = self._t1_t0_deg_c_x8_msbits
        self.calib_temp_value_0 = self._t0_deg_c_x8_lsbyte
        self.calib_temp_value_0 |= (t1_t0_msbs & 0b0011) << 8

        self.calibrated_value_1 = self._t1_deg_c_x8_lsbyte
        self.calibrated_value_1 |= (t1_t0_msbs & 0b1100) << 6

        self.calib_temp_value_0 >>= 3  # divide by 8 to remove x8
        self.calibrated_value_1 >>= 3  # divide by 8 to remove x8

        self.calib_temp_meas_0 = self._t0_out
        self.calib_temp_meas_1 = self._t1_out

        self.calib_hum_value_0 = self._h0_rh_x2
        self.calib_hum_value_0 >>= 1  # divide by 2 to remove x2

        self.calib_hum_value_1 = self._h1_rh_x2
        self.calib_hum_value_1 >>= 1  # divide by 2 to remove x2

        self.calib_hum_meas_0 = self._h0_t0_out
        self.calib_hum_meas_1 = self._h1_t0_out

    # This is the closest thing to a software reset. It re-loads the calibration values from flash
    def _boot(self):
        self._boot_bit = True
        # wait for the reset to finish
        while self._boot_bit:
            pass

    @property
    def relative_humidity(self):
        """The current relative humidity measurement in %rH"""
        calibrated_value_delta = self.calib_hum_value_1 - self.calib_hum_value_0
        calibrated_measurement_delta = self.calib_hum_meas_1 - self.calib_hum_meas_0

        calibration_value_offset = self.calib_hum_value_0
        calibrated_measurement_offset = self.calib_hum_meas_0
        zeroed_measured_humidity = self._raw_humidity - calibrated_measurement_offset

        correction_factor = calibrated_value_delta / calibrated_measurement_delta

        adjusted_humidity = (zeroed_measured_humidity * correction_factor +
                             calibration_value_offset)

        return adjusted_humidity

    @property
    def temperature(self):
        """The current temperature measurement in degrees C"""

        calibrated_value_delta = self.calibrated_value_1 - self.calib_temp_value_0
        calibrated_measurement_delta = self.calib_temp_meas_1 - self.calib_temp_meas_0

        calibration_value_offset = self.calib_temp_value_0
        calibrated_measurement_offset = self.calib_temp_meas_0
        zeroed_measured_temp = self._raw_temperature - calibrated_measurement_offset

        correction_factor = calibrated_value_delta / calibrated_measurement_delta

        adjusted_temp = (zeroed_measured_temp *
                         correction_factor) + calibration_value_offset

        return adjusted_temp

    @property
    def data_rate(self):
        """The rate at which the sensor measures ``relative_humidity`` and ``temperature``.
        ``data_rate`` should be set to one of the values of ``adafruit_hts221.Rate``. Note that
        setting ``data_rate`` to ``Rate.ONE_SHOT`` will cause  ``relative_humidity`` and
        ``temperature`` measurements to only update when ``take_measurements`` is called."""
        return self._data_rate

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

        self._data_rate = value

    @property
    def humidity_data_ready(self):
        """Returns true if a new relative humidity measurement is available to be read"""
        return self._humidity_status_bit

    @property
    def temperature_data_ready(self):
        """Returns true if a new temperature measurement is available to be read"""
        return self._temperature_status_bit

    def take_measurements(self):
        """Update the value of ``relative_humidity`` and ``temperature`` by taking a single
        measurement. Only meaningful if ``data_rate`` is set to ``ONE_SHOT``"""
        self._one_shot_bit = True
        while self._one_shot_bit:
            pass
예제 #23
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
예제 #24
0
class NAU7802:
    """The primary NAU7802 class."""

    # pylint: disable=too-many-instance-attributes
    def __init__(self, i2c_bus, address=0x2A, active_channels=1):
        """Instantiate NAU7802; LDO 3v0 volts, gain 128, 10 samples per second
        conversion rate, disabled ADC chopper clock, low ESR caps, and PGA output
        stabilizer cap if in single channel mode. Returns True if successful."""
        self.i2c_device = I2CDevice(i2c_bus, address)
        if not self.reset():
            raise RuntimeError("NAU7802 device could not be reset")
        if not self.enable(True):
            raise RuntimeError("NAU7802 device could not be enabled")
        self.ldo_voltage = "3V0"  # 3.0-volt internal analog power (AVDD)
        self._pu_ldo_source = True  # Internal analog power (AVDD)
        self.gain = 128  # X128
        self._c2_conv_rate = ConversionRate.RATE_10SPS  # 10 SPS; default
        self._adc_chop_clock = 0x3  # 0x3 = Disable ADC chopper clock
        self._pga_ldo_mode = 0x0  # 0x0 = Use low ESR capacitors
        self._act_channels = active_channels
        # 0x1 = Enable PGA out stabilizer cap for single channel use
        self._pc_cap_enable = 0x1
        if self._act_channels == 2:
            # 0x0 = Disable PGA out stabilizer cap for dual channel use
            self._pc_cap_enable = 0x0
        self._calib_mode = None  # Initialize for later use
        self._adc_out = None  # Initialize for later use

    # DEFINE I2C DEVICE BITS, NYBBLES, BYTES, AND REGISTERS
    # Chip Revision  R-
    _rev_id = ROBits(4, _REV_ID, 0, 1, False)
    # Register Reset  (RR)  RW
    _pu_reg_reset = RWBit(_PU_CTRL, 0, 1, False)
    # Power-Up Digital Circuit  (PUD) RW
    _pu_digital = RWBit(_PU_CTRL, 1, 1, False)
    # Power-Up Analog Circuit  (PUA) RW
    _pu_analog = RWBit(_PU_CTRL, 2, 1, False)
    # Power-Up Ready Status  (PUR) R-
    _pu_ready = ROBit(_PU_CTRL, 3, 1, False)
    # Power-Up Conversion Cycle Start  (CS) RW
    _pu_cycle_start = RWBit(_PU_CTRL, 4, 1, False)
    # Power-Up Cycle Ready  (CR) R-
    _pu_cycle_ready = ROBit(_PU_CTRL, 5, 1, False)
    # Power-Up AVDD Source  ADDS) RW
    _pu_ldo_source = RWBit(_PU_CTRL, 7, 1, False)
    # Control_1 Gain  (GAINS) RW
    _c1_gains = RWBits(3, _CTRL1, 0, 1, False)
    # Control_1 LDO Voltage  (VLDO) RW
    _c1_vldo_volts = RWBits(3, _CTRL1, 3, 1, False)
    # Control_2 Calibration Mode  (CALMOD) RW
    _c2_cal_mode = RWBits(2, _CTRL2, 0, 1, False)
    # Control_2 Calibration Start  (CALS) RW
    _c2_cal_start = RWBit(_CTRL2, 2, 1, False)
    # Control_2 Calibration Error (CAL_ERR) RW
    _c2_cal_error = RWBit(_CTRL2, 3, 1, False)
    # Control_2 Conversion Rate  (CRS) RW
    _c2_conv_rate = RWBits(3, _CTRL2, 4, 1, False)
    # Control_2 Channel Select  (CHS) RW
    _c2_chan_select = RWBit(_CTRL2, 7, 1, False)
    # ADC Result Output  MSByte R-
    _adc_out_2 = ROUnaryStruct(_ADCO_B2, ">B")
    # ADC Result Output  MidSByte R-
    _adc_out_1 = ROUnaryStruct(_ADCO_B1, ">B")
    # ADC Result Output  LSByte R-
    _adc_out_0 = ROUnaryStruct(_ADCO_B0, ">B")
    # ADC Chopper Clock Frequency Select  -W
    _adc_chop_clock = RWBits(2, _ADC, 4, 1, False)
    # PGA Stability/Accuracy Mode (LDOMODE) RW
    _pga_ldo_mode = RWBit(_PGA, 6, 1, False)
    # Power_Ctrl PGA Capacitor (PGA_CAP_EN) RW
    _pc_cap_enable = RWBit(_PWR_CTRL, 7, 1, False)

    @property
    def chip_revision(self):
        """The chip revision code."""
        return self._rev_id

    @property
    def channel(self):
        "Selected channel number (1 or 2)."
        return self._c2_chan_select + 1

    @channel.setter
    def channel(self, chan=1):
        """Select the active channel. Valid channel numbers are 1 and 2.
        Analog multiplexer settling time was emperically determined to be
        approximately 400ms at 10SPS, 200ms at 20SPS, 100ms at 40SPS,
        50ms at 80SPS, and 20ms at 320SPS."""
        if chan == 1:
            self._c2_chan_select = 0x0
            time.sleep(0.400)  # 400ms settling time for 10SPS
        elif chan == 2 and self._act_channels == 2:
            self._c2_chan_select = 0x1
            time.sleep(0.400)  # 400ms settling time for 10SPS
        else:
            raise ValueError("Invalid Channel Number")

    @property
    def ldo_voltage(self):
        """Representation of the LDO voltage value."""
        return self._ldo_voltage

    @ldo_voltage.setter
    def ldo_voltage(self, voltage="EXTERNAL"):
        """Select the LDO Voltage. Valid voltages are '2V4', '2V7', '3V0'."""
        if not "LDO_" + voltage in dir(LDOVoltage):
            raise ValueError("Invalid LDO Voltage")
        self._ldo_voltage = voltage
        if self._ldo_voltage == "2V4":
            self._c1_vldo_volts = LDOVoltage.LDO_2V4
        elif self._ldo_voltage == "2V7":
            self._c1_vldo_volts = LDOVoltage.LDO_2V7
        elif self._ldo_voltage == "3V0":
            self._c1_vldo_volts = LDOVoltage.LDO_3V0

    @property
    def gain(self):
        """The programmable amplifier (PGA) gain factor."""
        return self._gain

    @gain.setter
    def gain(self, factor=1):
        """Select PGA gain factor. Valid values are '1, 2, 4, 8, 16, 32, 64,
        and 128."""
        if not "GAIN_X" + str(factor) in dir(Gain):
            raise ValueError("Invalid Gain Factor")
        self._gain = factor
        if self._gain == 1:
            self._c1_gains = Gain.GAIN_X1
        elif self._gain == 2:
            self._c1_gains = Gain.GAIN_X2
        elif self._gain == 4:
            self._c1_gains = Gain.GAIN_X4
        elif self._gain == 8:
            self._c1_gains = Gain.GAIN_X8
        elif self._gain == 16:
            self._c1_gains = Gain.GAIN_X16
        elif self._gain == 32:
            self._c1_gains = Gain.GAIN_X32
        elif self._gain == 64:
            self._c1_gains = Gain.GAIN_X64
        elif self._gain == 128:
            self._c1_gains = Gain.GAIN_X128

    def enable(self, power=True):
        """Enable(start) or disable(stop) the internal analog and digital
        systems power. Enable = True; Disable (low power) = False. Returns
        True when enabled; False when disabled."""
        self._enable = power
        if self._enable:
            self._pu_analog = True
            self._pu_digital = True
            time.sleep(0.750)  # Wait 750ms; minimum 400ms
            self._pu_start = True  # Start acquisition system cycling
            return self._pu_ready
        self._pu_analog = False
        self._pu_digital = False
        time.sleep(0.010)  # Wait 10ms (200us minimum)
        return False

    def available(self):
        """Read the ADC data-ready status. True when data is available; False when
        ADC data is unavailable."""
        return self._pu_cycle_ready

    def read(self):
        """Reads the 24-bit ADC data. Returns a signed integer value with
        24-bit resolution. Assumes that the ADC data-ready bit was checked
        to be True."""
        adc = self._adc_out_2 << 24  # [31:24] << MSByte
        adc = adc | (self._adc_out_1 << 16)  # [23:16] << MidSByte
        adc = adc | (self._adc_out_0 << 8)  # [15: 8] << LSByte
        adc = adc.to_bytes(4, "big")  # Pack to 4-byte (32-bit) structure
        value = struct.unpack(">i", adc)[0]  # Unpack as 4-byte signed integer
        self._adc_out = value / 128  # Restore to 24-bit signed integer value
        return self._adc_out

    def reset(self):
        """Resets all device registers and enables digital system power.
        Returns the power ready status bit value: True when system is ready;
        False when system not ready for use."""
        self._pu_reg_reset = True  # Reset all registers)
        time.sleep(0.100)  # Wait 100ms; 10ms minimum
        self._pu_reg_reset = False
        self._pu_digital = True
        time.sleep(0.750)  # Wait 750ms; 400ms minimum
        return self._pu_ready

    def calibrate(self, mode="INTERNAL"):
        """Perform the calibration procedure. Valid calibration modes
        are 'INTERNAL', 'OFFSET', and 'GAIN'. True if successful."""
        if not mode in dir(CalibrationMode):
            raise ValueError("Invalid Calibration Mode")
        self._calib_mode = mode
        if self._calib_mode == "INTERNAL":  # Internal PGA offset (zero setting)
            self._c2_cal_mode = CalibrationMode.INTERNAL
        elif self._calib_mode == "OFFSET":  # External PGA offset (zero setting)
            self._c2_cal_mode = CalibrationMode.OFFSET
        elif self._calib_mode == "GAIN":  # External PGA full-scale gain setting
            self._c2_cal_mode = CalibrationMode.GAIN
        self._c2_cal_start = True
        while self._c2_cal_start:
            time.sleep(0.010)  # 10ms
        return not self._c2_cal_error