class DeviceControl: # pylint: disable-msg=too-few-public-methods def __init__(self, i2c): self.i2c_device = i2c # self.i2c_device required by RWBit class flag1 = RWBit(A_DEVICE_REGISTER, 0) # bit 0 of the control register flag2 = RWBit(A_DEVICE_REGISTER, 1) # bit 1 flag3 = RWBit(A_DEVICE_REGISTER, 7) # bit 7
class LPS25(LPS2X): """Library for the ST LPS25 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. """ enabled = RWBit(_LPS25_CTRL_REG1, 7) """Controls the power down state of the sensor. Setting to `False` will shut the sensor down""" _reset = RWBit(_LPS25_CTRL_REG2, 2) _data_rate = RWBits(3, _LPS25_CTRL_REG1, 4) def __init__(self, i2c_bus, address=_LPS2X_DEFAULT_ADDRESS): Rate.add_values(( ("LPS25_RATE_ONE_SHOT", 0, 0, None), ("LPS25_RATE_1_HZ", 1, 1, None), ("LPS25_RATE_7_HZ", 2, 7, None), ("LPS25_RATE_12_5_HZ", 3, 12.5, None), ("LPS25_RATE_25_HZ", 4, 25, None), )) super().__init__(i2c_bus, address, chip_id=_LPS25HB_CHIP_ID) self._temp_scaling = 480 self._temp_offset = 42.5 # self._inc_spi_flag = 0x40 def initialize(self): """Configure the sensor with the default settings. For use after calling `reset()`""" self.enabled = True self.data_rate = Rate.LPS25_RATE_25_HZ # pylint:disable=no-member
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
class MPU9250: """Driver for the MPU9250 9-DoF IMU. :param ~busio.I2C i2c_bus: The I2C bus the MPU9250 is connected to. :param address: The I2C slave address of the sensor """ def __init__(self, i2c_bus, mpu_addr=_MPU6500_DEFAULT_ADDRESS, akm_addr=_AK8963_DEFAULT_ADDRESS): self.i2c_device = i2c_device.I2CDevice(i2c_bus, mpu_addr) if self._device_id != _MPU9250_DEVICE_ID: raise RuntimeError("Failed to find MPU9250 - check your wiring!") self._mpu = MPU6500(i2c_bus, mpu_addr) self._bypass = 1 self._ready = 1 sleep(0.100) self._akm = AK8963(i2c_bus, akm_addr) def reset(self): """Reinitialize the sensor""" self._mpu.reset() self._akm.reset() _device_id = ROUnaryStruct(_MPU9250_WHO_AM_I, ">B") _bypass = RWBit(_MPU9250_INT_PIN_CFG, 1, 1) _ready = RWBit(_MPU9250_INT_ENABLE, 0, 1) @property def temperature(self): """The current temperature in º C""" return self._mpu.temperature @property def acceleration(self): """Acceleration X, Y, and Z axis data in m/s^2""" return self._mpu.acceleration @property def gyro(self): """Gyroscope X, Y, and Z axis data in º/s""" return self._mpu.gyro @property def magnetic(self): """Magnetometer X, Y and Z asix data in micro-Tesla (uT)""" return self._akm.magnetic def cal_mag(self): return self._akm.calibrate()
class LPS22(LPS2X): """Library for the ST LPS22 pressure sensors :param ~busio.I2C i2c_bus: The I2C bus the LPS22HB 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. """ _reset = RWBit(_LPS22_CTRL_REG2, 2) _data_rate = RWBits(3, _LPS22_CTRL_REG1, 4) def __init__(self, i2c_bus, address=_LPS2X_DEFAULT_ADDRESS): # Only adding Class-appropriate rates Rate.add_values(( ("LPS22_RATE_ONE_SHOT", 0, 0, None), ("LPS22_RATE_1_HZ", 1, 1, None), ("LPS22_RATE_10_HZ", 2, 10, None), ("LPS22_RATE_25_HZ", 3, 25, None), ("LPS22_RATE_50_HZ", 4, 50, None), ("LPS22_RATE_75_HZ", 5, 75, None), )) super().__init__(i2c_bus, address, chip_id=_LPS22HB_CHIP_ID) self._temp_scaling = 100 self._temp_offset = 0 def initialize(self): """Configure the sensor with the default settings. For use after calling `reset()`""" self.data_rate = Rate.LPS22_RATE_75_HZ # pylint:disable=no-member
class TC74: """ Driver for the Microchip TC74 Digital Temperature Sensor. :param i2c_bus: The I2C bus the TC74 is connected to. :param address: The I2C device address for the sensor. Default is ``0x48``. """ def __init__(self, i2c_bus, address=TC74_DEFAULT_ADDRESS): self.i2c_device = i2cdevice.I2CDevice(i2c_bus, address) _temperature = ROUnaryStruct(TC74_REGISTER_TEMP, "b") shutdown = RWBit(TC74_REGISTER_CONFIG, TC74_SHUTDOWN_BIT, lsb_first=True) """Set to True to turn off the temperature measurement circuitry in the sensor. While shut down the configurations properties can still be read or written but the temperature will not be measured.""" data_ready = ROBit(TC74_REGISTER_CONFIG, TC74_DATA_READY_BIT, lsb_first=True) """Read-only bit that indicates when the temperature register is ready to be read from, especially after power-on or when switching from the shutdown to the normal state.""" @property def temperature(self): """ Returns the current temperature in degrees celsius. Resolution is 1 degrees C. """ return self._temperature
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)
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)
class MMC5603: # pylint: disable=too-many-instance-attributes _enable = RWBit(Inter_control, 0, 1) _raw_x_0 = ROUnaryStruct(OUTX_L_REG, "<B") _raw_x_1 = ROUnaryStruct(OUTX_H_REG, "<B") # _raw_y = ROUnaryStruct(OUTY_L_REG, "<h") # _raw_z = ROUnaryStruct(OUTZ_L_REG, "<h") def __init__(self, i2c): self.i2c_device = I2CDevice(i2c, _ADDRESS_MAG) # # if self._device_id != 0x40: # raise AttributeError("Cannot find an LIS2MDL") # self.reset() def reset(self): """Reset the sensor to the default state set by the library""" self._soft_reset = True sleep(0.100) self._reboot = True sleep(0.100) self._mode = 0x00 self._bdu = True # Make sure high and low bytes are set together self._int_latched = True self._int_reg_polarity = True self._int_iron_off = False self._interrupt_pin_putput = True self._temp_comp = True sleep(0.030) # sleep 20ms to allow measurements to stabilize @property def magnetic(self): """The processed magnetometer sensor values. A 3-tuple of X, Y, Z axis values in microteslas that are signed floats. """ self._enable = 1 return ( self._raw_x_0, self._raw_x_1, )
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 DS3502: """Driver for the DS3502 I2C Digital Potentiometer. :param ~busio.I2C i2c_bus: The I2C bus the DS3502 is connected to. :param address: The I2C device address for the sensor. Default is ``0x40``. """ def __init__(self, i2c_bus, address=0x28): self.i2c_device = i2cdevice.I2CDevice(i2c_bus, address) # set to mode 1 on init to not write to the IVR every time you set self._write_only_to_wiper = True _wiper = UnaryStruct(_REG_WIPER, ">B") _write_only_to_wiper = RWBit(_REG_CONTROL, 7) @property def wiper(self): """The value of the potentionmeter's wiper. :param wiper_value: The value from 0-127 to set the wiper to. """ return self._wiper @wiper.setter def wiper(self, value): if value < 0 or value > 127: raise ValueError("wiper must be from 0-127") self._wiper = value def set_default(self, default): """Sets the wiper's default value and current value to the given value :param new_default: The value from 0-127 to set as the wiper's default. """ self._write_only_to_wiper = False self.wiper = default sleep(0.1) # wait for write to eeprom to finish self._write_only_to_wiper = True
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)
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
class ICM20X: # pylint:disable=too-many-instance-attributes """Library for the ST ICM-20X Wide-Range 6-DoF Accelerometer and Gyro Family :param ~busio.I2C i2c_bus: The I2C bus the ICM20X is connected to. :param address: The I2C slave address of the sensor """ # Bank 0 _device_id = ROUnaryStruct(_ICM20X_WHO_AM_I, ">B") _bank_reg = UnaryStruct(_ICM20X_REG_BANK_SEL, ">B") _reset = RWBit(_ICM20X_PWR_MGMT_1, 7) _sleep_reg = RWBit(_ICM20X_PWR_MGMT_1, 6) _low_power_en = RWBit(_ICM20X_PWR_MGMT_1, 5) _clock_source = RWBits(3, _ICM20X_PWR_MGMT_1, 0) _raw_accel_data = Struct(_ICM20X_ACCEL_XOUT_H, ">hhh") # ds says LE :| _raw_gyro_data = Struct(_ICM20X_GYRO_XOUT_H, ">hhh") _lp_config_reg = UnaryStruct(_ICM20X_LP_CONFIG, ">B") _i2c_master_cycle_en = RWBit(_ICM20X_LP_CONFIG, 6) _accel_cycle_en = RWBit(_ICM20X_LP_CONFIG, 5) _gyro_cycle_en = RWBit(_ICM20X_LP_CONFIG, 4) # Bank 2 _gyro_dlpf_enable = RWBits(1, _ICM20X_GYRO_CONFIG_1, 0) _gyro_range = RWBits(2, _ICM20X_GYRO_CONFIG_1, 1) _gyro_dlpf_config = RWBits(3, _ICM20X_GYRO_CONFIG_1, 3) _accel_dlpf_enable = RWBits(1, _ICM20X_ACCEL_CONFIG_1, 0) _accel_range = RWBits(2, _ICM20X_ACCEL_CONFIG_1, 1) _accel_dlpf_config = RWBits(3, _ICM20X_ACCEL_CONFIG_1, 3) # this value is a 12-bit register spread across two bytes, big-endian first _accel_rate_divisor = UnaryStruct(_ICM20X_ACCEL_SMPLRT_DIV_1, ">H") _gyro_rate_divisor = UnaryStruct(_ICM20X_GYRO_SMPLRT_DIV, ">B") AccelDLPFFreq.add_values( ( ( "DISABLED", -1, "Disabled", None, ), # magical value that we will use do disable ("FREQ_246_0HZ_3DB", 1, 246.0, None), ("FREQ_111_4HZ_3DB", 2, 111.4, None), ("FREQ_50_4HZ_3DB", 3, 50.4, None), ("FREQ_23_9HZ_3DB", 4, 23.9, None), ("FREQ_11_5HZ_3DB", 5, 11.5, None), ("FREQ_5_7HZ_3DB", 6, 5.7, None), ("FREQ_473HZ_3DB", 7, 473, None), ) ) GyroDLPFFreq.add_values( ( ( "DISABLED", -1, "Disabled", None, ), # magical value that we will use do disable ("FREQ_196_6HZ_3DB", 0, 196.6, None), ("FREQ_151_8HZ_3DB", 1, 151.8, None), ("FREQ_119_5HZ_3DB", 2, 119.5, None), ("FREQ_51_2HZ_3DB", 3, 51.2, None), ("FREQ_23_9HZ_3DB", 4, 23.9, None), ("FREQ_11_6HZ_3DB", 5, 11.6, None), ("FREQ_5_7HZ_3DB", 6, 5.7, None), ("FREQ_361_4HZ_3DB", 7, 361.4, None), ) ) @property def _bank(self): return self._bank_reg >> 4 @_bank.setter def _bank(self, value): self._bank_reg = value << 4 def __init__(self, i2c_bus, address): self.i2c_device = i2c_device.I2CDevice(i2c_bus, address) self._bank = 0 if not self._device_id in [_ICM20649_DEVICE_ID, _ICM20948_DEVICE_ID]: raise RuntimeError("Failed to find an ICM20X sensor - check your wiring!") self.reset() self.initialize() def initialize(self): """Configure the sensors with the default settings. For use after calling `reset()`""" self._sleep = False self.accelerometer_range = AccelRange.RANGE_8G # pylint: disable=no-member self.gyro_range = GyroRange.RANGE_500_DPS # pylint: disable=no-member self.accelerometer_data_rate_divisor = 20 # ~53.57Hz self.gyro_data_rate_divisor = 10 # ~100Hz def reset(self): """Resets the internal registers and restores the default settings""" self._bank = 0 sleep(0.005) self._reset = True sleep(0.005) while self._reset: sleep(0.005) @property def _sleep(self): self._bank = 0 sleep(0.005) self._sleep_reg = False sleep(0.005) @_sleep.setter def _sleep(self, sleep_enabled): self._bank = 0 sleep(0.005) self._sleep_reg = sleep_enabled sleep(0.005) @property def acceleration(self): """The x, y, z acceleration values returned in a 3-tuple and are in m / s ^ 2.""" self._bank = 0 raw_accel_data = self._raw_accel_data sleep(0.005) x = self._scale_xl_data(raw_accel_data[0]) y = self._scale_xl_data(raw_accel_data[1]) z = self._scale_xl_data(raw_accel_data[2]) return (x, y, z) @property def gyro(self): """The x, y, z angular velocity values returned in a 3-tuple and are in degrees / second""" self._bank = 0 raw_gyro_data = self._raw_gyro_data x = self._scale_gyro_data(raw_gyro_data[0]) y = self._scale_gyro_data(raw_gyro_data[1]) z = self._scale_gyro_data(raw_gyro_data[2]) return (x, y, z) def _scale_xl_data(self, raw_measurement): sleep(0.005) return raw_measurement / AccelRange.lsb[self._cached_accel_range] * G_TO_ACCEL def _scale_gyro_data(self, raw_measurement): return ( raw_measurement / GyroRange.lsb[self._cached_gyro_range] ) * _ICM20X_RAD_PER_DEG @property def accelerometer_range(self): """Adjusts the range of values that the sensor can measure, from +/- 4G to +/-30G Note that larger ranges will be less accurate. Must be an `AccelRange`""" return self._cached_accel_range @accelerometer_range.setter def accelerometer_range(self, value): # pylint: disable=no-member if not AccelRange.is_valid(value): raise AttributeError("range must be an `AccelRange`") self._bank = 2 sleep(0.005) self._accel_range = value sleep(0.005) self._cached_accel_range = value self._bank = 0 @property def gyro_range(self): """Adjusts the range of values that the sensor can measure, from 500 Degrees/second to 4000 degrees/s. Note that larger ranges will be less accurate. Must be a `GyroRange`""" return self._cached_gyro_range @gyro_range.setter def gyro_range(self, value): if not GyroRange.is_valid(value): raise AttributeError("range must be a `GyroRange`") self._bank = 2 sleep(0.005) self._gyro_range = value sleep(0.005) self._cached_gyro_range = value self._bank = 0 sleep(0.100) # needed to let new range settle @property def accelerometer_data_rate_divisor(self): """The divisor for the rate at which accelerometer measurements are taken in Hz Note: The data rates are set indirectly by setting a rate divisor according to the following formula: ``accelerometer_data_rate = 1125/(1+divisor)`` This function sets the raw rate divisor. """ self._bank = 2 raw_rate_divisor = self._accel_rate_divisor sleep(0.005) self._bank = 0 # rate_hz = 1125/(1+raw_rate_divisor) return raw_rate_divisor @accelerometer_data_rate_divisor.setter def accelerometer_data_rate_divisor(self, value): # check that value <= 4095 self._bank = 2 sleep(0.005) self._accel_rate_divisor = value sleep(0.005) @property def gyro_data_rate_divisor(self): """The divisor for the rate at which gyro measurements are taken in Hz Note: The data rates are set indirectly by setting a rate divisor according to the following formula: ``gyro_data_rate = 1100/(1+divisor)`` This function sets the raw rate divisor. """ self._bank = 2 raw_rate_divisor = self._gyro_rate_divisor sleep(0.005) self._bank = 0 # rate_hz = 1100/(1+raw_rate_divisor) return raw_rate_divisor @gyro_data_rate_divisor.setter def gyro_data_rate_divisor(self, value): # check that value <= 255 self._bank = 2 sleep(0.005) self._gyro_rate_divisor = value sleep(0.005) def _accel_rate_calc(self, divisor): # pylint:disable=no-self-use return 1125 / (1 + divisor) def _gyro_rate_calc(self, divisor): # pylint:disable=no-self-use return 1100 / (1 + divisor) @property def accelerometer_data_rate(self): """The rate at which accelerometer measurements are taken in Hz Note: The data rates are set indirectly by setting a rate divisor according to the following formula: ``accelerometer_data_rate = 1125/(1+divisor)`` This function does the math to find the divisor from a given rate but it will not be exactly as specified. """ return self._accel_rate_calc(self.accelerometer_data_rate_divisor) @accelerometer_data_rate.setter def accelerometer_data_rate(self, value): if value < self._accel_rate_calc(4095) or value > self._accel_rate_calc(0): raise AttributeError( "Accelerometer data rate must be between 0.27 and 1125.0" ) self.accelerometer_data_rate_divisor = value @property def gyro_data_rate(self): """The rate at which gyro measurements are taken in Hz Note: The data rates are set indirectly by setting a rate divisor according to the following formula: ``gyro_data_rate = 1100/(1+divisor)`` This function does the math to find the divisor from a given rate but it will not be exactly as specified. """ return self._gyro_rate_calc(self.gyro_data_rate_divisor) @gyro_data_rate.setter def gyro_data_rate(self, value): if value < self._gyro_rate_calc(4095) or value > self._gyro_rate_calc(0): raise AttributeError("Gyro data rate must be between 4.30 and 1100.0") divisor = round(((1125.0 - value) / value)) self.gyro_data_rate_divisor = divisor @property def accel_dlpf_cutoff(self): """The cutoff frequency for the accelerometer's digital low pass filter. Signals above the given frequency will be filtered out. Must be an ``AccelDLPFCutoff``. Use AccelDLPFCutoff.DISABLED to disable the filter **Note** readings immediately following setting a cutoff frequency will be inaccurate due to the filter "warming up" """ self._bank = 2 return self._accel_dlpf_config @accel_dlpf_cutoff.setter def accel_dlpf_cutoff(self, cutoff_frequency): if not AccelDLPFFreq.is_valid(cutoff_frequency): raise AttributeError("accel_dlpf_cutoff must be an `AccelDLPFFreq`") self._bank = 2 # check for shutdown if cutoff_frequency is AccelDLPFFreq.DISABLED: # pylint: disable=no-member self._accel_dlpf_enable = False return self._accel_dlpf_enable = True self._accel_dlpf_config = cutoff_frequency @property def gyro_dlpf_cutoff(self): """The cutoff frequency for the gyro's digital low pass filter. Signals above the given frequency will be filtered out. Must be a ``GyroDLPFFreq``. Use GyroDLPFCutoff.DISABLED to disable the filter **Note** readings immediately following setting a cutoff frequency will be inaccurate due to the filter "warming up" """ self._bank = 2 return self._gyro_dlpf_config @gyro_dlpf_cutoff.setter def gyro_dlpf_cutoff(self, cutoff_frequency): if not GyroDLPFFreq.is_valid(cutoff_frequency): raise AttributeError("gyro_dlpf_cutoff must be a `GyroDLPFFreq`") self._bank = 2 # check for shutdown if cutoff_frequency is GyroDLPFFreq.DISABLED: # pylint: disable=no-member self._gyro_dlpf_enable = False return self._gyro_dlpf_enable = True self._gyro_dlpf_config = cutoff_frequency @property def _low_power(self): self._bank = 0 return self._low_power_en @_low_power.setter def _low_power(self, enabled): self._bank = 0 self._low_power_en = enabled
class ICM20948(ICM20X): # pylint:disable=too-many-instance-attributes """Library for the ST ICM-20948 Wide-Range 6-DoF Accelerometer and Gyro. :param ~busio.I2C i2c_bus: The I2C bus the ICM20948 is connected to. :param address: The I2C slave address of the sensor """ _slave_finished = ROBit(_ICM20X_I2C_MST_STATUS, 6) # mag data is LE _raw_mag_data = Struct(_ICM20948_EXT_SLV_SENS_DATA_00, "<hhhh") _bypass_i2c_master = RWBit(_ICM20X_REG_INT_PIN_CFG, 1) _i2c_master_control = UnaryStruct(_ICM20X_I2C_MST_CTRL, ">B") _i2c_master_enable = RWBit(_ICM20X_USER_CTRL, 5) # TODO: use this in sw reset _i2c_master_reset = RWBit(_ICM20X_USER_CTRL, 1) _slave0_addr = UnaryStruct(_ICM20X_I2C_SLV0_ADDR, ">B") _slave0_reg = UnaryStruct(_ICM20X_I2C_SLV0_REG, ">B") _slave0_ctrl = UnaryStruct(_ICM20X_I2C_SLV0_CTRL, ">B") _slave0_do = UnaryStruct(_ICM20X_I2C_SLV0_DO, ">B") _slave4_addr = UnaryStruct(_ICM20X_I2C_SLV4_ADDR, ">B") _slave4_reg = UnaryStruct(_ICM20X_I2C_SLV4_REG, ">B") _slave4_ctrl = UnaryStruct(_ICM20X_I2C_SLV4_CTRL, ">B") _slave4_do = UnaryStruct(_ICM20X_I2C_SLV4_DO, ">B") _slave4_di = UnaryStruct(_ICM20X_I2C_SLV4_DI, ">B") def __init__(self, i2c_bus, address=_ICM20948_DEFAULT_ADDRESS): AccelRange.add_values( ( ("RANGE_2G", 0, 2, 16384), ("RANGE_4G", 1, 4, 8192), ("RANGE_8G", 2, 8, 4096.0), ("RANGE_16G", 3, 16, 2048), ) ) GyroRange.add_values( ( ("RANGE_250_DPS", 0, 250, 131.0), ("RANGE_500_DPS", 1, 500, 65.5), ("RANGE_1000_DPS", 2, 1000, 32.8), ("RANGE_2000_DPS", 3, 2000, 16.4), ) ) # https://www.y-ic.es/datasheet/78/SMDSW.020-2OZ.pdf page 9 MagDataRate.add_values( ( ("SHUTDOWN", 0x0, "Shutdown", None), ("SINGLE", 0x1, "Single", None), ("RATE_10HZ", 0x2, 10, None), ("RATE_20HZ", 0x4, 20, None), ("RATE_50HZ", 0x6, 50, None), ("RATE_100HZ", 0x8, 100, None), ) ) super().__init__(i2c_bus, address) self._magnetometer_init() # A million thanks to the SparkFun folks for their library that I pillaged to write this method! # See their Python library here: # https://github.com/sparkfun/Qwiic_9DoF_IMU_ICM20948_Py @property def _mag_configured(self): success = False for _i in range(5): success = self._mag_id() is not None if success: return True self._reset_i2c_master() # i2c master stuck, try resetting return False def _reset_i2c_master(self): self._bank = 0 self._i2c_master_reset = True def _magnetometer_enable(self): self._bank = 0 sleep(0.100) self._bypass_i2c_master = False sleep(0.005) # no repeated start, i2c master clock = 345.60kHz self._bank = 3 sleep(0.100) self._i2c_master_control = 0x17 sleep(0.100) self._bank = 0 sleep(0.100) self._i2c_master_enable = True sleep(0.020) def _magnetometer_init(self): self._magnetometer_enable() self.magnetometer_data_rate = ( MagDataRate.RATE_100HZ # pylint: disable=no-member ) if not self._mag_configured: return False self._setup_mag_readout() return True # set up slave0 for reading into the bank 0 data registers def _setup_mag_readout(self): self._bank = 3 self._slave0_addr = 0x8C sleep(0.005) self._slave0_reg = 0x11 sleep(0.005) self._slave0_ctrl = 0x89 # enable sleep(0.005) def _mag_id(self): return self._read_mag_register(0x01) @property def magnetic(self): """The current magnetic field strengths onthe X, Y, and Z axes in uT (micro-teslas)""" self._bank = 0 full_data = self._raw_mag_data sleep(0.005) x = full_data[0] * _ICM20X_UT_PER_LSB y = full_data[1] * _ICM20X_UT_PER_LSB z = full_data[2] * _ICM20X_UT_PER_LSB return (x, y, z) @property def magnetometer_data_rate(self): """The rate at which the magenetometer takes measurements to update its output registers""" # read mag DR register self._read_mag_register(_AK09916_CNTL2) @magnetometer_data_rate.setter def magnetometer_data_rate(self, mag_rate): # From https://www.y-ic.es/datasheet/78/SMDSW.020-2OZ.pdf page 9 # "When user wants to change operation mode, transit to Power-down mode first and then # transit to other modes. After Power-down mode is set, at least 100 microsectons (Twait) # is needed before setting another mode" if not MagDataRate.is_valid(mag_rate): raise AttributeError("range must be an `MagDataRate`") self._write_mag_register( _AK09916_CNTL2, MagDataRate.SHUTDOWN # pylint: disable=no-member ) sleep(0.001) self._write_mag_register(_AK09916_CNTL2, mag_rate) def _read_mag_register(self, register_addr, slave_addr=0x0C): self._bank = 3 slave_addr |= 0x80 # set top bit for read self._slave4_addr = slave_addr sleep(0.005) self._slave4_reg = register_addr sleep(0.005) self._slave4_ctrl = ( 0x80 # enable, don't raise interrupt, write register value, no delay ) sleep(0.005) self._bank = 0 finished = False for _i in range(100): finished = self._slave_finished if finished: # bueno! break sleep(0.010) if not finished: return None self._bank = 3 mag_register_data = self._slave4_di sleep(0.005) return mag_register_data def _write_mag_register(self, register_addr, value, slave_addr=0x0C): self._bank = 3 self._slave4_addr = slave_addr sleep(0.005) self._slave4_reg = register_addr sleep(0.005) self._slave4_do = value sleep(0.005) self._slave4_ctrl = ( 0x80 # enable, don't raise interrupt, write register value, no delay ) sleep(0.005) self._bank = 0 finished = False for _i in range(100): finished = self._slave_finished if finished: # bueno! break sleep(0.010) return finished
class 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 PCT2075: """Driver for the PCT2075 Digital Temperature Sensor and Thermal Watchdog. :param ~busio.I2C i2c_bus: The I2C bus the PCT2075 is connected to. :param int address: The I2C device address. Default is :const:`0x37` **Quickstart: Importing and using the PCT2075 temperature sensor** Here is an example of using the :class:`PCT2075` class. First you will need to import the libraries to use the sensor .. code-block:: python import board import adafruit_pct2075 Once this is done you can define your `board.I2C` object and define your sensor object .. code-block:: python i2c = board.I2C() # uses board.SCL and board.SDA pct = adafruit_pct2075.PCT2075(i2c) Now you have access to the temperature using the attribute :attr:`temperature`. .. code-block:: python temperature = pct.temperature """ def __init__(self, i2c_bus: I2C, address: int = PCT2075_DEFAULT_ADDRESS) -> None: self.i2c_device = i2cdevice.I2CDevice(i2c_bus, address) _temperature = ROUnaryStruct(PCT2075_REGISTER_TEMP, ">h") mode = RWBit(PCT2075_REGISTER_CONFIG, 1, register_width=1) """Sets the alert mode. In comparator mode, the sensor acts like a thermostat and will activate the INT pin according to `high_temp_active_high` when an alert is triggered. The INT pin will be deactivated when the temperature falls below :attr:`temperature_hysteresis`. In interrupt mode the INT pin is activated once when a temperature fault is detected, and once more when the temperature falls below :attr:`temperature_hysteresis`. In interrupt mode, the alert is cleared by reading a property""" shutdown = RWBit(PCT2075_REGISTER_CONFIG, 0, 1) """Set to True to turn off the temperature measurement circuitry in the sensor. While shut down the configurations properties can still be read or written but the temperature will not be measured""" _fault_queue_length = RWBits(2, PCT2075_REGISTER_CONFIG, 3, register_width=1) _high_temperature_threshold = UnaryStruct(PCT2075_REGISTER_TOS, ">h") _temp_hysteresis = UnaryStruct(PCT2075_REGISTER_THYST, ">h") _idle_time = RWBits(5, PCT2075_REGISTER_TIDLE, 0, register_width=1) high_temp_active_high = RWBit(PCT2075_REGISTER_CONFIG, 2, register_width=1) """Sets the alert polarity. When False the INT pin will be tied to ground when an alert is triggered. If set to True it will be disconnected from ground when an alert is triggered.""" @property def temperature(self) -> float: """Returns the current temperature in degrees Celsius. Resolution is 0.125 degrees Celsius""" return (self._temperature >> 5) * 0.125 @property def high_temperature_threshold(self) -> float: """The temperature in degrees celsius that will trigger an alert on the INT pin if it is exceeded. Resolution is 0.5 degrees Celsius""" return (self._high_temperature_threshold >> 7) * 0.5 @high_temperature_threshold.setter def high_temperature_threshold(self, value: float) -> None: self._high_temperature_threshold = int(value * 2) << 7 @property def temperature_hysteresis(self) -> float: """The temperature hysteresis value defines the bottom of the temperature range in degrees Celsius in which the temperature is still considered high. :attr:`temperature_hysteresis` must be lower than :attr:`high_temperature_threshold`. Resolution is 0.5 degrees Celsius """ return (self._temp_hysteresis >> 7) * 0.5 @temperature_hysteresis.setter def temperature_hysteresis(self, value: float) -> None: if value >= self.high_temperature_threshold: raise ValueError( "temperature_hysteresis must be less than high_temperature_threshold" ) self._temp_hysteresis = int(value * 2) << 7 @property def faults_to_alert(self) -> int: """The number of consecutive high temperature faults required to raise an alert. An fault is tripped each time the sensor measures the temperature to be greater than :attr:`high_temperature_threshold`. The rate at which the sensor measures the temperature is defined by :attr:`delay_between_measurements`. """ return self._fault_queue_length @faults_to_alert.setter def faults_to_alert(self, value: int) -> None: if value > 4 or value < 1: raise ValueError( "faults_to_alert must be an adafruit_pct2075.FaultCount") self._fault_queue_length = value @property def delay_between_measurements(self) -> int: """The amount of time between measurements made by the sensor in milliseconds. The value must be between 100 and 3100 and a multiple of 100""" return self._idle_time * 100 @delay_between_measurements.setter def delay_between_measurements(self, value: int) -> None: if value > 3100 or value < 100 or value % 100 > 0: raise AttributeError( """"delay_between_measurements must be >= 100 or <= 3100\ and a multiple of 100""") self._idle_time = int(value / 100)
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)
class VCNL4040: # pylint: disable=too-few-public-methods """Driver for the VCNL4040 proximity and ambient light sensor. :param busio.I2C i2c_bus: The I2C bus the VCNL4040 is connected to. """ # Ambient light sensor integration times ALS_80MS = const(0x0) ALS_160MS = const(0x1) ALS_320MS = const(0x2) ALS_640MS = const(0x3) # Proximity sensor integration times PS_1T = const(0x0) PS_1_5T = const(0x1) PS_2T = const(0x2) PS_2_5T = const(0x3) PS_3T = const(0x4) PS_3_5T = const(0x5) PS_4T = const(0x6) PS_8T = const(0x7) # LED current settings LED_50MA = const(0x0) LED_75MA = const(0x1) LED_100MA = const(0x2) LED_120MA = const(0x3) LED_140MA = const(0x4) LED_160MA = const(0x5) LED_180MA = const(0x6) LED_200MA = const(0x7) # LED duty cycle settings LED_1_40 = const(0x0) LED_1_80 = const(0x1) LED_1_160 = const(0x2) LED_1_320 = const(0x3) # Proximity sensor interrupt enable/disable options PS_INT_DISABLE = const(0x0) PS_INT_CLOSE = const(0x1) PS_INT_AWAY = const(0x2) PS_INT_CLOSE_AWAY = const(0x3) # Offsets into interrupt status register for different types ALS_IF_L = const(0x0D) ALS_IF_H = const(0x0C) PS_IF_CLOSE = const(0x09) PS_IF_AWAY = const(0x08) # ID_LM - Device ID, address _device_id = UnaryStruct(0x0C, "<H") """The device ID.""" # PS_Data_LM - PS output data proximity = ROUnaryStruct(0x08, "<H") """Proximity data. This example prints the proximity data. Move your hand towards the sensor to see the values change. .. code-block:: python import time import board import busio import adafruit_vcnl4040 i2c = busio.I2C(board.SCL, board.SDA) sensor = adafruit_vcnl4040.VCNL4040(i2c) while True: print("Proximity:", sensor.proximity) time.sleep(0.1) """ # PS_CONF1 - PS duty ratio, integration time, persistence, enable/disable # PS_CONF2 - PS output resolution selection, interrupt trigger method # PS_CONF3 - PS smart persistence, active force mode proximity_shutdown = RWBit(0x03, 0, register_width=2) """Proximity sensor shutdown. When ``True``, proximity data is disabled.""" proximity_integration_time = RWBits(3, 0x03, 1, register_width=2) """Proximity sensor integration time setting. Integration times are 1T, 1.5T, 2T, 2.5T, 3T, 3.5T, 4T, and 8T. Options are: PS_1T, PS_1_5T, PS_2T, PS_2_5T, PS_3T, PS_3_5T, PS_4T, PS_8T. """ proximity_interrupt = RWBits(2, 0x03, 8, register_width=2) """Interrupt enable. Interrupt setting are close, away, close and away, or disabled. Options are: PS_INT_DISABLE, PS_INT_CLOSE, PS_INT_AWAY, PS_INT_CLOSE_AWAY.""" proximity_bits = RWBit(0x03, 11, register_width=2) """Proximity data output setting. ``0`` when proximity sensor output is 12 bits, ``1`` when proximity sensor output is 16 bits.""" # PS_THDL_LM - PS low interrupt threshold setting proximity_low_threshold = UnaryStruct(0x06, "<H") """Proximity sensor interrupt low threshold setting.""" # PS_THDH_LM - PS high interrupt threshold setting proximity_high_threshold = UnaryStruct(0x07, "<H") """Proximity sensor interrupt high threshold setting.""" interrupt_state = ROUnaryStruct(0x0B, "<H") # INT_FLAG - PS interrupt flag @property def proximity_high_interrupt(self): """If interrupt is set to ``PS_INT_CLOSE`` or ``PS_INT_CLOSE_AWAY``, trigger event when proximity rises above high threshold interrupt.""" return self._get_and_clear_cached_interrupt_state(self.PS_IF_CLOSE) @property def proximity_low_interrupt(self): """If interrupt is set to ``PS_INT_AWAY`` or ``PS_INT_CLOSE_AWAY``, trigger event when proximity drops below low threshold.""" return self._get_and_clear_cached_interrupt_state(self.PS_IF_AWAY) led_current = RWBits(3, 0x04, 8, register_width=2) """LED current selection setting, in mA. Options are LED_50MA, LED_75MA, LED_100MA, LED_120MA, LED_140MA, LED_160MA, LED_180MA, LED_200MA.""" led_duty_cycle = RWBits(2, 0x03, 6, register_width=2) """Proximity sensor LED duty ratio setting. Ratios are 1/40, 1/80, 1/160, and 1/320. Options are: LED_1_40, LED_1_80, LED_1_160, LED_1_320.""" light = ROUnaryStruct(0x09, "<H") """Raw ambient light data. The raw ambient light data which will change with integration time and gain settings changes. Use ``lux`` to get the correctly scaled value for the current integration time and gain settings """ @property def lux(self): """Ambient light data in lux. Represents the raw sensor data scaled according to the current integration time and gain settings. This example prints the ambient light data. Cover the sensor to see the values change. .. code-block:: python import time import board import busio import adafruit_vcnl4040 i2c = busio.I2C(board.SCL, board.SDA) sensor = adafruit_vcnl4040.VCNL4040(i2c) while True: print("Ambient light: %.2f lux"%sensor.lux) time.sleep(0.1) """ return self.light * (0.1 / (1 << self.light_integration_time)) # ALS_CONF - ALS integration time, persistence, interrupt, function enable/disable light_shutdown = RWBit(0x00, 0, register_width=2) """Ambient light sensor shutdown. When ``True``, ambient light data is disabled.""" _light_integration_time = RWBits(2, 0x00, 6, register_width=2) @property def light_integration_time(self): """Ambient light sensor integration time setting. Longer time has higher sensitivity. Can be: ALS_80MS, ALS_160MS, ALS_320MS or ALS_640MS. This example sets the ambient light integration time to 640ms and prints the ambient light sensor data. .. code-block:: python import time import board import busio import adafruit_vcnl4040 i2c = busio.I2C(board.SCL, board.SDA) sensor = adafruit_vcnl4040.VCNL4040(i2c) sensor.light_integration_time = sensor.ALS_640MS while True: print("Ambient light:", sensor.light) """ return self._light_integration_time @light_integration_time.setter def light_integration_time(self, new_it): from time import sleep # pylint: disable=import-outside-toplevel # IT values are in 0-3 -> 80-640ms old_it_ms = (8 << self._light_integration_time) * 10 new_it_ms = (8 << new_it) * 10 it_delay_seconds = (old_it_ms + new_it_ms + 1) * 0.001 self._light_integration_time = new_it sleep(it_delay_seconds) light_interrupt = RWBit(0x00, 1, register_width=2) """Ambient light sensor interrupt enable. ``True`` to enable, and ``False`` to disable.""" # ALS_THDL_LM - ALS low interrupt threshold setting light_low_threshold = UnaryStruct(0x02, "<H") """Ambient light interrupt low threshold.""" # ALS_THDH_LM - ALS high interrupt threshold setting light_high_threshold = UnaryStruct(0x01, "<H") """Ambient light interrupt high threshold.""" # INT_FLAG - ALS interrupt flag @property def light_high_interrupt(self): """High interrupt event. Triggered when ambient light value exceeds high threshold.""" return self._get_and_clear_cached_interrupt_state(self.ALS_IF_H) @property def light_low_interrupt(self): """Low interrupt event. Triggered when ambient light value drops below low threshold.""" return self._get_and_clear_cached_interrupt_state(self.ALS_IF_L) _raw_white = ROUnaryStruct(0x0A, "<H") @property def white(self): """White light data scaled according to the current integration time and gain settings. This example prints the white light data. Cover the sensor to see the values change. .. code-block:: python import time import board import busio import adafruit_vcnl4040 i2c = busio.I2C(board.SCL, board.SDA) sensor = adafruit_vcnl4040.VCNL4040(i2c) while True: print("White light:", sensor.white) time.sleep(0.1) """ return self._raw_white * (0.1 / (1 << self.light_integration_time)) # PS_MS - White channel enable/disable, PS mode, PS protection setting, LED current # White_EN - PS_MS_H, 7th bit - White channel enable/disable white_shutdown = RWBit(0x04, 15, register_width=2) """White light channel shutdown. When ``True``, white light data is disabled.""" def __init__(self, i2c, address=0x60): self.i2c_device = i2cdevice.I2CDevice(i2c, address) if self._device_id != 0x186: raise RuntimeError("Failed to find VCNL4040 - check wiring!") self.cached_interrupt_state = { self.ALS_IF_L: False, self.ALS_IF_H: False, self.PS_IF_CLOSE: False, self.PS_IF_AWAY: False, } self.proximity_shutdown = False self.light_shutdown = False self.white_shutdown = False def _update_interrupt_state(self): interrupts = [ self.PS_IF_AWAY, self.PS_IF_CLOSE, self.ALS_IF_H, self.ALS_IF_L ] new_interrupt_state = self.interrupt_state for interrupt in interrupts: new_state = new_interrupt_state & (1 << interrupt) > 0 if new_state: self.cached_interrupt_state[interrupt] = new_state def _get_and_clear_cached_interrupt_state(self, interrupt_offset): self._update_interrupt_state() new_interrupt_state = self.cached_interrupt_state[interrupt_offset] self.cached_interrupt_state[interrupt_offset] = False return new_interrupt_state
class MPU6050: """Driver for the MPU6050 6-DoF accelerometer and gyroscope. :param ~busio.I2C i2c_bus: The I2C bus the MPU6050 is connected to. :param address: The I2C slave address of the sensor """ def __init__(self, i2c_bus, address=_MPU6050_DEFAULT_ADDRESS): self.i2c_device = i2c_device.I2CDevice(i2c_bus, address) if self._device_id != _MPU6050_DEVICE_ID: raise RuntimeError("Failed to find MPU6050 - check your wiring!") self.reset() self._sample_rate_divisor = 0 self._filter_bandwidth = Bandwidth.BAND_260_HZ self._gyro_range = GyroRange.RANGE_500_DPS self._accel_range = Range.RANGE_2_G sleep(0.100) self._clock_source = 1 # set to use gyro x-axis as reference sleep(0.100) self.sleep = False sleep(0.010) def reset(self): """Reinitialize the sensor""" self._reset = True while self._reset is True: sleep(0.001) sleep(0.100) _signal_path_reset = 0b111 # reset all sensors sleep(0.100) _clock_source = RWBits(3, _MPU6050_PWR_MGMT_1, 0) _device_id = ROUnaryStruct(_MPU6050_WHO_AM_I, ">B") _reset = RWBit(_MPU6050_PWR_MGMT_1, 7, 1) _signal_path_reset = RWBits(3, _MPU6050_SIG_PATH_RESET, 3) _gyro_range = RWBits(2, _MPU6050_GYRO_CONFIG, 3) _accel_range = RWBits(2, _MPU6050_ACCEL_CONFIG, 3) _filter_bandwidth = RWBits(2, _MPU6050_CONFIG, 3) _raw_accel_data = StructArray(_MPU6050_ACCEL_OUT, ">h", 3) _raw_gyro_data = StructArray(_MPU6050_GYRO_OUT, ">h", 3) _raw_temp_data = ROUnaryStruct(_MPU6050_TEMP_OUT, ">h") _cycle = RWBit(_MPU6050_PWR_MGMT_1, 5) _cycle_rate = RWBits(2, _MPU6050_PWR_MGMT_2, 6, 1) sleep = RWBit(_MPU6050_PWR_MGMT_1, 6, 1) """Shuts down the accelerometers and gyroscopes, saving power. No new data will be recorded until the sensor is taken out of sleep by setting to `False`""" sample_rate_divisor = UnaryStruct(_MPU6050_SMPLRT_DIV, ">B") """The sample rate divisor. See the datasheet for additional detail""" @property def temperature(self): """The current temperature in º C""" raw_temperature = self._raw_temp_data temp = (raw_temperature / 340.0) + 36.53 return temp @property def acceleration(self): """Acceleration X, Y, and Z axis data in m/s^2""" raw_data = self._raw_accel_data raw_x = raw_data[0][0] raw_y = raw_data[1][0] raw_z = raw_data[2][0] accel_range = self._accel_range accel_scale = 1 if accel_range == Range.RANGE_16_G: accel_scale = 2048 if accel_range == Range.RANGE_8_G: accel_scale = 4096 if accel_range == Range.RANGE_4_G: accel_scale = 8192 if accel_range == Range.RANGE_2_G: accel_scale = 16384 # setup range dependant scaling accel_x = (raw_x / accel_scale) * STANDARD_GRAVITY accel_y = (raw_y / accel_scale) * STANDARD_GRAVITY accel_z = (raw_z / accel_scale) * STANDARD_GRAVITY return (accel_x, accel_y, accel_z) @property def gyro(self): """Gyroscope X, Y, and Z axis data in º/s""" raw_data = self._raw_gyro_data raw_x = raw_data[0][0] raw_y = raw_data[1][0] raw_z = raw_data[2][0] gyro_scale = 1 gyro_range = self._gyro_range if gyro_range == GyroRange.RANGE_250_DPS: gyro_scale = 131 if gyro_range == GyroRange.RANGE_500_DPS: gyro_scale = 65.5 if gyro_range == GyroRange.RANGE_1000_DPS: gyro_scale = 32.8 if gyro_range == GyroRange.RANGE_2000_DPS: gyro_scale = 16.4 # setup range dependant scaling gyro_x = (raw_x / gyro_scale) gyro_y = (raw_y / gyro_scale) gyro_z = (raw_z / gyro_scale) return (gyro_x, gyro_y, gyro_z) @property def cycle(self): """Enable or disable perodic measurement at a rate set by `cycle_rate`. If the sensor was in sleep mode, it will be waken up to cycle""" return self._cycle @cycle.setter def cycle(self, value): self.sleep = not value self._cycle = value @property def gyro_range(self): """The measurement range of all gyroscope axes. Must be a `GyroRange`""" return self._gyro_range @gyro_range.setter def gyro_range(self, value): if (value < 0) or (value > 3): raise ValueError("gyro_range must be a GyroRange") self._gyro_range = value sleep(0.01) @property def accelerometer_range(self): """The measurement range of all accelerometer axes. Must be a `Range`""" return self._accel_range @accelerometer_range.setter def accelerometer_range(self, value): if (value < 0) or (value > 3): raise ValueError("accelerometer_range must be a Range") self._accel_range = value sleep(0.01) @property def filter_bandwidth(self): """The bandwidth of the gyroscope Digital Low Pass Filter. Must be a `GyroRange`""" return self._filter_bandwidth @filter_bandwidth.setter def filter_bandwidth(self, value): if (value < 0) or (value > 6): raise ValueError("filter_bandwidth must be a Bandwidth") self._filter_bandwidth = value sleep(0.01) @property def cycle_rate(self): """The rate that measurements are taken while in `cycle` mode. Must be a `Rate`""" return self._cycle_rate @cycle_rate.setter def cycle_rate(self, value): if (value < 0) or (value > 3): raise ValueError("cycle_rate must be a Rate") self._cycle_rate = value sleep(0.01)
class LIS331: # pylint:disable=too-many-instance-attributes """Base class for the LIS331 family of 3-axis accelerometers. **Cannot be instantiated directly** :param ~busio.I2C i2c_bus: The I2C bus the LIS331 is connected to. :param address: The I2C slave address of the sensor """ _chip_id = ROUnaryStruct(_LIS331_REG_WHOAMI, "<B") _mode_and_odr_bits = RWBits(5, _LIS331_REG_CTRL1, 3) _power_mode_bits = RWBits(3, _LIS331_REG_CTRL1, 5) _data_rate_lpf_bits = RWBits(2, _LIS331_REG_CTRL1, 3) _range_bits = RWBits(2, _LIS331_REG_CTRL4, 4) _raw_acceleration = ROByteArray((_LIS331_REG_OUT_X_L | 0x80), "<hhh", 6) _reference_value = UnaryStruct(_LIS331_REG_REFERENCE, "<b") _zero_hpf = ROUnaryStruct(_LIS331_REG_HP_FILTER_RESET, "<b") _hpf_mode_bits = RWBit(_LIS331_REG_CTRL2, 5) _hpf_enable_bit = RWBit(_LIS331_REG_CTRL2, 4) _hpf_cutoff = RWBits(2, _LIS331_REG_CTRL2, 0) def __init__(self, i2c_bus, address=_LIS331_DEFAULT_ADDRESS): if (not isinstance(self, LIS331HH)) and (not isinstance( self, H3LIS331)): raise RuntimeError( "Base class LIS331 cannot be instantiated directly. Use LIS331HH or H3LIS331" ) self.i2c_device = i2c_device.I2CDevice(i2c_bus, address) if self._chip_id != _LIS331_CHIP_ID: raise RuntimeError("Failed to find %s - check your wiring!" % self.__class__.__name__) self._range_class = None self.enable_hpf(False) @property def lpf_cutoff(self): """The frequency above which signals will be filtered out""" if self.mode == Mode.NORMAL: # pylint: disable=no-member raise RuntimeError( "lpf_cuttoff cannot be read while a NORMAL data rate is in use" ) return self._data_rate_lpf_bits @lpf_cutoff.setter def lpf_cutoff(self, cutoff_freq): if not Frequency.is_valid(cutoff_freq): raise AttributeError("lpf_cutoff must be a `Frequency`") if self.mode == Mode.NORMAL: # pylint: disable=no-member raise RuntimeError( "lpf_cuttoff cannot be set while a NORMAL data rate is in use") self._data_rate_lpf_bits = cutoff_freq @property def hpf_reference(self): """The reference value to offset measurements when using the High-pass filter. To use, ``use_reference`` must be set to true when enabling the high-pass filter. The value is a signed 8-bit number from -128 to 127. The value of each increment of 1 depends on the currently set measurement range and is approximate: #pylint: disable=line-too-long +-------------------------------------------------------------+-------------------------------+ | Range | Incremental value (LSB value) | +-------------------------------------------------------------+-------------------------------+ | ``LIS331HHRange.RANGE_6G`` or ``H3LIS331Range.RANGE_100G`` | ~16mg | +-------------------------------------------------------------+-------------------------------+ | ``LIS331HHRange.RANGE_12G`` or ``H3LIS331Range.RANGE_200G`` | ~31mg | +-------------------------------------------------------------+-------------------------------+ | ``LIS331HHRange.RANGE_24G`` or ``H3LIS331Range.RANGE_400G`` | ~63mg | +-------------------------------------------------------------+-------------------------------+ #pylint: enable=line-too-long """ return self._reference_value @hpf_reference.setter def hpf_reference(self, reference_value): if reference_value < -128 or reference_value > 127: raise AttributeError("`hpf_reference` must be from -128 to 127") self._reference_value = reference_value def zero_hpf(self): """When the high-pass filter is enabled with ``use_reference=False``, calling ``zero_hpf`` will set all measurements to zero immediately, avoiding the normal settling time seen when using the high-pass filter without a ``hpf_reference`` """ self._zero_hpf # pylint: disable=pointless-statement def enable_hpf(self, enabled=True, cutoff=RateDivisor.ODR_DIV_50, use_reference=False): # pylint: disable=no-member """Enable or disable the high-pass filter. :param enabled: Enable or disable the filter. Default is `True` to enable :param ~RateDivisor cutoff: A `RateDivisor` to set the high-pass cutoff frequency. Default\ is ``RateDivisor.ODR_DIV_50``. See ``RateDivisor`` for more information :param use_reference: Determines if the filtered measurements are offset by a reference\ value. Default is false. See section **4** of the LIS331DLH application note for more information `LIS331DLH application\ note for more information <https://www.st.com/content/ccc/resource/technical/document/\ application_note/b5/8e/58/69/cb/87/45/55/CD00215823.pdf/files/CD00215823.pdf/jcr:content/\ translations/en.CD00215823.pdf>`_ """ self._hpf_mode_bits = use_reference self._hpf_cutoff = cutoff self._hpf_enable_bit = enabled @property def data_rate(self): """Select the rate at which the accelerometer takes measurements. Must be a `Rate`""" return self._cached_data_rate @data_rate.setter def data_rate(self, new_rate_bits): if not Rate.is_valid(new_rate_bits): raise AttributeError("data_rate must be a `Rate`") # to determine what to be set we'll look at the mode to so we don't overwrite the filter new_mode = self._mode_and_rate(new_rate_bits)[0] if new_mode == Mode.NORMAL: # pylint: disable=no-member self._mode_and_odr_bits = new_rate_bits else: self._power_mode_bits = new_mode self._cached_data_rate = new_mode << 2 | new_rate_bits @property def mode(self): """The `Mode` power mode that the sensor is set to, as determined by the current `data_rate`. To set the mode, use `data_rate` and the approprite `Rate`""" mode_bits = self._mode_and_rate()[0] return mode_bits def _mode_and_rate(self, data_rate=None): if data_rate is None: data_rate = self._cached_data_rate pm_value = (data_rate & 0x1C) >> 2 dr_value = data_rate & 0x3 if pm_value is Mode.LOW_POWER: # pylint: disable=no-member dr_value = 0 return (pm_value, dr_value) @property def range(self): """Adjusts the range of values that the sensor can measure, Note that larger ranges will be less accurate. Must be a `H3LIS331Range` or `LIS331HHRange`""" return self._range_bits @range.setter def range(self, new_range): if not self._range_class.is_valid(new_range): # pylint: disable=no-member raise AttributeError("range must be a `%s`" % self._range_class.__qualname__) self._range_bits = new_range self._cached_accel_range = new_range sleep(0.010) # give time for the new rate to settle @property def acceleration(self): """The x, y, z acceleration values returned in a 3-tuple and are in m / s ^ 2.""" raw_acceleration_bytes = self._raw_acceleration return ( self._scale_acceleration(raw_acceleration_bytes[0]), self._scale_acceleration(raw_acceleration_bytes[1]), self._scale_acceleration(raw_acceleration_bytes[2]), ) def _scale_acceleration(self, value): # The measurements are 12 bits left justified to preserve the sign bit # so we'll shift them back to get the real value right_justified = value >> 4 lsb_value = self._range_class.lsb[self._cached_accel_range] return right_justified * lsb_value
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 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 APDS9960: """ APDS9900 provide basic driver services for the ASDS9960 breakout board """ _gesture_enable = RWBit(APDS9960_ENABLE, 6) _gesture_valid = RWBit(APDS9960_GSTATUS, 0) _gesture_mode = RWBit(APDS9960_GCONF4, 0) _proximity_persistance = RWBits(4, APDS9960_PERS, 4) def __init__(self, i2c, *, interrupt_pin=None, address=0x39, integration_time=0x01, gain=0x01): self.buf129 = None self.buf2 = bytearray(2) self.i2c_device = I2CDevice(i2c, address) self._interrupt_pin = interrupt_pin if interrupt_pin: self._interrupt_pin.switch_to_input(pull=digitalio.Pull.UP) if self._read8(APDS9960_ID) != 0xAB: raise RuntimeError() self.enable_gesture = False self.enable_proximity = False self.enable_color = False self.enable_proximity_interrupt = False self.clear_interrupt() self.enable = False time.sleep(0.010) self.enable = True time.sleep(0.010) self.color_gain = gain self.integration_time = integration_time self.gesture_dimensions = 0x00 # all self.gesture_fifo_threshold = 0x01 # fifo 4 self.gesture_gain = 0x02 # gain 4 self.gesture_proximity_threshold = 50 self._reset_counts() # gesture pulse length=0x2 pulse count=0x3 self._write8(APDS9960_GPULSE, (0x2 << 6) | 0x3) ## BOARD def _reset_counts(self): """Gesture detection internal counts""" self._saw_down_start = 0 self._saw_up_start = 0 self._saw_left_start = 0 self._saw_right_start = 0 enable = RWBit(APDS9960_ENABLE, 0) """Board enable. True to enable, False to disable""" enable_color = RWBit(APDS9960_ENABLE, 1) """Color detection enable flag. True when color detection is enabled, else False""" enable_proximity = RWBit(APDS9960_ENABLE, 2) """Enable of proximity mode""" gesture_fifo_threshold = RWBits(2, APDS9960_GCONF1, 6) """Gesture fifo threshold value: range 0-3""" gesture_gain = RWBits(2, APDS9960_GCONF2, 5) """Gesture gain value: range 0-3""" color_gain = RWBits(2, APDS9960_CONTROL, 0) """Color gain value""" enable_proximity_interrupt = RWBit(APDS9960_ENABLE, 5) """Proximity interrupt enable flag. True if enabled, False to disable""" ## GESTURE DETECTION @property def enable_gesture(self): """Gesture detection enable flag. True to enable, False to disable. Note that when disabled, gesture mode is turned off""" return self._gesture_enable @enable_gesture.setter def enable_gesture(self, enable_flag): if not enable_flag: self._gesture_mode = False self._gesture_enable = enable_flag def gesture(self): #pylint: disable-msg=too-many-branches """Returns gesture code if detected. =0 if no gesture detected =1 if an UP, =2 if a DOWN, =3 if an LEFT, =4 if a RIGHT """ # buffer to read of contents of device FIFO buffer if not self.buf129: self.buf129 = bytearray(129) buffer = self.buf129 buffer[0] = APDS9960_GFIFO_U if not self._gesture_valid: return 0 time_mark = 0 gesture_received = 0 while True: up_down_diff = 0 left_right_diff = 0 gesture_received = 0 time.sleep(0.030) # 30 ms n_recs = self._read8(APDS9960_GFLVL) if n_recs: with self.i2c_device as i2c: i2c.write(buffer, end=1, stop=False) i2c.readinto(buffer, start=1, end=min(129, 1 + n_recs * 4)) upp, down, left, right = buffer[1:5] if abs(upp - down) > 13: up_down_diff = upp - down if abs(left - right) > 13: left_right_diff = left - right if up_down_diff != 0: if up_down_diff < 0: # either leading edge of down movement # or trailing edge of up movement if self._saw_up_start: gesture_received = 0x01 # up else: self._saw_down_start += 1 elif up_down_diff > 0: # either leading edge of up movement # or trailing edge of down movement if self._saw_down_start: gesture_received = 0x02 # down else: self._saw_up_start += 1 if left_right_diff != 0: if left_right_diff < 0: # either leading edge of right movement # trailing edge of left movement if self._saw_left_start: gesture_received = 0x03 # left else: self._saw_right_start += 1 elif left_right_diff > 0: # either leading edge of left movement # trailing edge of right movement if self._saw_right_start: gesture_received = 0x04 #right else: self._saw_left_start += 1 # saw a leading or trailing edge; start timer if up_down_diff or left_right_diff: time_mark = time.monotonic() # finished when a gesture is detected or ran out of time (300ms) if gesture_received or time.monotonic() - time_mark > 0.300: self._reset_counts() break return gesture_received @property def gesture_dimensions(self): """Gesture dimension value: range 0-3""" return self._read8(APDS9960_GCONF3) @gesture_dimensions.setter def gesture_dimensions(self, dims): self._write8(APDS9960_GCONF3, dims & 0x03) @property def color_data_ready(self): """Color data ready flag. zero if not ready, 1 is ready""" return self._read8(APDS9960_STATUS) & 0x01 @property def color_data(self): """Tuple containing r, g, b, c values""" return self._color_data16(APDS9960_CDATAL + 2), \ self._color_data16(APDS9960_CDATAL + 4), \ self._color_data16(APDS9960_CDATAL + 6), \ self._color_data16(APDS9960_CDATAL) ### PROXIMITY @property def proximity_interrupt_threshold(self): """Tuple containing low and high threshold followed by the proximity interrupt persistance. When setting the proximity interrupt threshold values using a tuple of zero to three values: low threshold, high threshold, persistance. persistance defaults to 4 if not provided""" return self._read8(APDS9960_PILT), \ self._read8(APDS9960_PIHT), \ self._proximity_persistance @proximity_interrupt_threshold.setter def proximity_interrupt_threshold(self, setting_tuple): if setting_tuple: self._write8(APDS9960_PILT, setting_tuple[0]) if len(setting_tuple) > 1: self._write8(APDS9960_PIHT, setting_tuple[1]) persist = 4 # default 4 if len(setting_tuple) > 2: persist = min(setting_tuple[2], 7) self._proximity_persistance = persist @property def gesture_proximity_threshold(self): """Proximity threshold value: range 0-255""" return self._read8(APDS9960_GPENTH) @gesture_proximity_threshold.setter def gesture_proximity_threshold(self, thresh): self._write8(APDS9960_GPENTH, thresh & 0xff) def proximity(self): """proximity value: range 0-255""" return self._read8(APDS9960_PDATA) def clear_interrupt(self): """Clear all interrupts""" self._writecmdonly(APDS9960_AICLEAR) @property def integration_time(self): """Proximity integration time: range 0-255""" return self._read8(APDS9960_ATIME) @integration_time.setter def integration_time(self, int_time): self._write8(APDS9960_ATIME, int_time & 0xff) # method for reading and writing to I2C def _write8(self, command, abyte): """Write a command and 1 byte of data to the I2C device""" buf = self.buf2 buf[0] = command buf[1] = abyte with self.i2c_device as i2c: i2c.write(buf) def _writecmdonly(self, command): """Writes a command and 0 bytes of data to the I2C device""" buf = self.buf2 buf[0] = command with self.i2c_device as i2c: i2c.write(buf, end=1) def _read8(self, command): """Sends a command and reads 1 byte of data from the I2C device""" buf = self.buf2 buf[0] = command with self.i2c_device as i2c: i2c.write(buf, end=1) i2c.readinto(buf, end=1) return buf[0] def _color_data16(self, command): """Sends a command and reads 2 bytes of data from the I2C device The returned data is low byte first followed by high byte""" buf = self.buf2 buf[0] = command with self.i2c_device as i2c: i2c.write(buf, end=1, stop=False) i2c.readinto(buf) return buf[1] << 8 | buf[0]
class LIS3MDL: """Driver for the LIS3MDL 3-axis magnetometer. :param ~busio.I2C i2c_bus: The I2C bus the LIS3MDL is connected to. :param address: The I2C device address. Defaults to :const:`0x1C` **Quickstart: Importing and using the device** Here is an example of using the :class:`LIS3MDL` class. First you will need to import the libraries to use the sensor .. code-block:: python import board import adafruit_lis3mdl Once this is done you can define your `board.I2C` object and define your sensor object .. code-block:: python i2c = board.I2C() sensor = adafruit_lis3mdl.LIS3MDL(i2c) Now you have access to the :attr:`magnetic` attribute .. code-block:: python mag_x, mag_y, mag_z = sensor.magnetic """ _chip_id = ROUnaryStruct(_LIS3MDL_WHOAMI, "<b") _perf_mode = RWBits(2, _LIS3MDL_CTRL_REG1, 5) _z_perf_mode = RWBits(2, _LIS3MDL_CTRL_REG4, 2) _operation_mode = RWBits(2, _LIS3MDL_CTRL_REG3, 0) _data_rate = RWBits(4, _LIS3MDL_CTRL_REG1, 1) _raw_mag_data = Struct(_LIS3MDL_OUT_X_L, "<hhh") _range = RWBits(2, _LIS3MDL_CTRL_REG2, 5) _reset = RWBit(_LIS3MDL_CTRL_REG2, 2) def __init__(self, i2c_bus, address=_LIS3MDL_DEFAULT_ADDRESS): # pylint: disable=no-member self.i2c_device = i2c_device.I2CDevice(i2c_bus, address) if self._chip_id != _LIS3MDL_CHIP_ID: raise RuntimeError("Failed to find LIS3MDL - check your wiring!") self.reset() self.performance_mode = PerformanceMode.MODE_ULTRA self.data_rate = Rate.RATE_155_HZ self.range = Range.RANGE_4_GAUSS self.operation_mode = OperationMode.CONTINUOUS sleep(0.010) def reset(self): # pylint: disable=no-self-use """Reset the sensor to the default state set by the library""" self._reset = True sleep(0.010) @property def magnetic(self): """The processed magnetometer sensor values. A 3-tuple of X, Y, Z axis values in microteslas that are signed floats. """ raw_mag_data = self._raw_mag_data x = self._scale_mag_data(raw_mag_data[0]) y = self._scale_mag_data(raw_mag_data[1]) z = self._scale_mag_data(raw_mag_data[2]) return (x, y, z) def _scale_mag_data(self, raw_measurement): # pylint: disable=no-self-use return (raw_measurement / Range.lsb[self.range]) * _GAUSS_TO_UT @property def range(self): """The measurement range for the magnetic sensor. Must be a ``Range``""" return self._range @range.setter def range(self, value): if not Range.is_valid(value): raise AttributeError("``range`` must be a ``Range``") self._range = value sleep(0.010) @property def data_rate(self): """The rate at which the sensor takes measurements. Must be a ``Rate``""" return self._data_rate @data_rate.setter def data_rate(self, value): # pylint: disable=no-member if value is Rate.RATE_155_HZ: self.performance_mode = PerformanceMode.MODE_ULTRA if value is Rate.RATE_300_HZ: self.performance_mode = PerformanceMode.MODE_HIGH if value is Rate.RATE_560_HZ: self.performance_mode = PerformanceMode.MODE_MEDIUM if value is Rate.RATE_1000_HZ: self.performance_mode = PerformanceMode.MODE_LOW_POWER sleep(0.010) if not Rate.is_valid(value): raise AttributeError("`data_rate` must be a `Rate`") self._data_rate = value @property def performance_mode(self): """Sets the 'performance mode' of the sensor. Must be a ``PerformanceMode``. Note that `performance_mode` affects the available data rate and will be automatically changed by setting ``data_rate`` to certain values.""" return self._perf_mode @performance_mode.setter def performance_mode(self, value): if not PerformanceMode.is_valid(value): raise AttributeError( "`performance_mode` must be a `PerformanceMode`") self._perf_mode = value self._z_perf_mode = value @property def operation_mode(self): """The operating mode for the sensor, controlling how measurements are taken. Must be an `OperationMode`. See the the `OperationMode` document for additional details """ return self._operation_mode @operation_mode.setter def operation_mode(self, value): if not OperationMode.is_valid(value): raise AttributeError("operation mode must be a OperationMode") self._operation_mode = value
class 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 LSM6DS: # pylint: disable=too-many-instance-attributes """Driver for the LSM6DSOX 6-axis accelerometer and gyroscope. :param ~busio.I2C i2c_bus: The I2C bus the LSM6DSOX is connected to. :param int address: TThe I2C device address. Defaults to :const:`0x6A` """ # ROUnaryStructs: _chip_id = ROUnaryStruct(_LSM6DS_WHOAMI, "<b") # Structs _raw_accel_data = Struct(_LSM6DS_OUTX_L_A, "<hhh") _raw_gyro_data = Struct(_LSM6DS_OUTX_L_G, "<hhh") # RWBits: _accel_range = RWBits(2, _LSM6DS_CTRL1_XL, 2) _accel_data_rate = RWBits(4, _LSM6DS_CTRL1_XL, 4) _gyro_data_rate = RWBits(4, _LSM6DS_CTRL2_G, 4) _gyro_range = RWBits(2, _LSM6DS_CTRL2_G, 2) _gyro_range_125dps = RWBit(_LSM6DS_CTRL2_G, 1) _sw_reset = RWBit(_LSM6DS_CTRL3_C, 0) _bdu = RWBit(_LSM6DS_CTRL3_C, 6) _high_pass_filter = RWBits(2, _LSM6DS_CTRL8_XL, 5) _i3c_disable = RWBit(_LSM6DS_CTRL9_XL, 1) _pedometer_reset = RWBit(_LSM6DS_CTRL10_C, 1) _func_enable = RWBit(_LSM6DS_CTRL10_C, 2) _ped_enable = RWBit(_LSM6DS_TAP_CFG, 6) pedometer_steps = ROUnaryStruct(_LSM6DS_STEP_COUNTER, "<h") """The number of steps detected by the pedometer. You must enable with `pedometer_enable` before calling. Use `pedometer_reset` to reset the number of steps""" CHIP_ID = None def __init__(self, i2c_bus, address=LSM6DS_DEFAULT_ADDRESS): self._cached_accel_range = None self._cached_gyro_range = None self.i2c_device = i2c_device.I2CDevice(i2c_bus, address) if self.CHIP_ID is None: raise AttributeError("LSM6DS Parent Class cannot be directly instantiated") if self._chip_id != self.CHIP_ID: raise RuntimeError( "Failed to find %s - check your wiring!" % self.__class__.__name__ ) self.reset() if not hasattr(GyroRange, "string"): self._add_gyro_ranges() self._bdu = True self._add_accel_ranges() self.accelerometer_data_rate = Rate.RATE_104_HZ # pylint: disable=no-member self.gyro_data_rate = Rate.RATE_104_HZ # pylint: disable=no-member self.accelerometer_range = AccelRange.RANGE_4G # pylint: disable=no-member self.gyro_range = GyroRange.RANGE_250_DPS # pylint: disable=no-member def reset(self): "Resets the sensor's configuration into an initial state" self._sw_reset = True while self._sw_reset: sleep(0.001) @staticmethod def _add_gyro_ranges(): GyroRange.add_values( ( ("RANGE_125_DPS", 125, 125, 4.375), ("RANGE_250_DPS", 0, 250, 8.75), ("RANGE_500_DPS", 1, 500, 17.50), ("RANGE_1000_DPS", 2, 1000, 35.0), ("RANGE_2000_DPS", 3, 2000, 70.0), ) ) @staticmethod def _add_accel_ranges(): AccelRange.add_values( ( ("RANGE_2G", 0, 2, 0.061), ("RANGE_16G", 1, 16, 0.488), ("RANGE_4G", 2, 4, 0.122), ("RANGE_8G", 3, 8, 0.244), ) ) @property def acceleration(self): """The x, y, z acceleration values returned in a 3-tuple and are in m / s ^ 2.""" raw_accel_data = self._raw_accel_data x = self._scale_xl_data(raw_accel_data[0]) y = self._scale_xl_data(raw_accel_data[1]) z = self._scale_xl_data(raw_accel_data[2]) return (x, y, z) @property def gyro(self): """The x, y, z angular velocity values returned in a 3-tuple and are in radians / second""" raw_gyro_data = self._raw_gyro_data x, y, z = [radians(self._scale_gyro_data(i)) for i in raw_gyro_data] return (x, y, z) def _scale_xl_data(self, raw_measurement): return ( raw_measurement * AccelRange.lsb[self._cached_accel_range] * _MILLI_G_TO_ACCEL ) def _scale_gyro_data(self, raw_measurement): return raw_measurement * GyroRange.lsb[self._cached_gyro_range] / 1000 @property def accelerometer_range(self): """Adjusts the range of values that the sensor can measure, from +/- 2G to +/-16G Note that larger ranges will be less accurate. Must be an ``AccelRange``""" return self._cached_accel_range # pylint: disable=no-member @accelerometer_range.setter def accelerometer_range(self, value): if not AccelRange.is_valid(value): raise AttributeError("range must be an `AccelRange`") self._accel_range = value self._cached_accel_range = value sleep(0.2) # needed to let new range settle @property def gyro_range(self): """Adjusts the range of values that the sensor can measure, from 125 Degrees/s to 2000 degrees/s. Note that larger ranges will be less accurate. Must be a ``GyroRange``.""" return self._cached_gyro_range @gyro_range.setter def gyro_range(self, value): self._set_gyro_range(value) sleep(0.2) def _set_gyro_range(self, value): if not GyroRange.is_valid(value): raise AttributeError("range must be a `GyroRange`") # range uses `FS_G` enum if value <= GyroRange.RANGE_2000_DPS: # pylint: disable=no-member self._gyro_range_125dps = False self._gyro_range = value # range uses the `FS_125` bit if value is GyroRange.RANGE_125_DPS: # pylint: disable=no-member self._gyro_range_125dps = True self._cached_gyro_range = value # needed to let new range settle @property def accelerometer_data_rate(self): """Select the rate at which the accelerometer takes measurements. Must be a ``Rate``""" return self._accel_data_rate @accelerometer_data_rate.setter def accelerometer_data_rate(self, value): if not Rate.is_valid(value): raise AttributeError("accelerometer_data_rate must be a `Rate`") self._accel_data_rate = value # sleep(.2) # needed to let new range settle @property def gyro_data_rate(self): """Select the rate at which the gyro takes measurements. Must be a ``Rate``""" return self._gyro_data_rate @gyro_data_rate.setter def gyro_data_rate(self, value): if not Rate.is_valid(value): raise AttributeError("gyro_data_rate must be a `Rate`") self._gyro_data_rate = value # sleep(.2) # needed to let new range settle @property def pedometer_enable(self): """ Whether the pedometer function on the accelerometer is enabled""" return self._ped_enable and self._func_enable @pedometer_enable.setter def pedometer_enable(self, enable): self._ped_enable = enable self._func_enable = enable self._pedometer_reset = enable @property def high_pass_filter(self): """The high pass filter applied to accelerometer data""" return self._high_pass_filter @high_pass_filter.setter def high_pass_filter(self, value): if not AccelHPF.is_valid(value): raise AttributeError("range must be an `AccelHPF`") self._high_pass_filter = value
class 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
class AS7341: # pylint:disable=too-many-instance-attributes """Library for the AS7341 Sensor :param ~busio.I2C i2c_bus: The I2C bus the AS7341 is connected to. :param address: The I2C address of the sensor """ _device_id = ROBits(6, _AS7341_WHOAMI, 2) _smux_enable_bit = RWBit(_AS7341_ENABLE, 4) _led_control_enable_bit = RWBit(_AS7341_CONFIG, 3) _color_meas_enabled = RWBit(_AS7341_ENABLE, 1) _power_enabled = RWBit(_AS7341_ENABLE, 0) _low_bank_active = RWBit(_AS7341_CFG0, 4) _smux_command = RWBits(2, _AS7341_CFG6, 3) _fd_status = UnaryStruct(_AS7341_FD_STATUS, "<B") _channel_0_data = UnaryStruct(_AS7341_CH0_DATA_L, "<H") _channel_1_data = UnaryStruct(_AS7341_CH1_DATA_L, "<H") _channel_2_data = UnaryStruct(_AS7341_CH2_DATA_L, "<H") _channel_3_data = UnaryStruct(_AS7341_CH3_DATA_L, "<H") _channel_4_data = UnaryStruct(_AS7341_CH4_DATA_L, "<H") _channel_5_data = UnaryStruct(_AS7341_CH5_DATA_L, "<H") # "Reading the ASTATUS register (0x60 or 0x94) latches # all 12 spectral data bytes to that status read." Datasheet Sec. 10.2.7 _all_channels = Struct(_AS7341_ASTATUS, "<BHHHHHH") _led_current_bits = RWBits(7, _AS7341_LED, 0) _led_enabled = RWBit(_AS7341_LED, 7) atime = UnaryStruct(_AS7341_ATIME, "<B") """The integration time step count. Total integration time will be ``(ATIME + 1) * (ASTEP + 1) * 2.78µS`` """ astep = UnaryStruct(_AS7341_ASTEP_L, "<H") """ The integration time step size in 2.78 microsecond increments""" _gain = UnaryStruct(_AS7341_CFG1, "<B") _data_ready_bit = RWBit(_AS7341_STATUS2, 6) """ * @brief * * @return true: success false: failure """ def __init__(self, i2c_bus, address=_AS7341_I2CADDR_DEFAULT): self.i2c_device = i2c_device.I2CDevice(i2c_bus, address) if not self._device_id in [_AS7341_DEVICE_ID]: raise RuntimeError( "Failed to find an AS7341 sensor - check your wiring!") self.reset() self.initialize() self._buffer = bytearray(2) self._low_channels_configured = False self._high_channels_configured = False self._flicker_detection_1k_configured = False def initialize(self): """Configure the sensors with the default settings. For use after calling `reset()`""" self._power_enabled = True self._led_control_enabled = True self.atime = 100 self.astep = 999 self.gain = Gain.GAIN_128X # pylint:disable=no-member def reset(self): """Resets the internal registers and restores the default settings""" @property def all_channels(self): """The current readings for all six ADC channels""" self._configure_f1_f4() adc_reads_f1_f4 = self._all_channels reads = adc_reads_f1_f4[1:-2] self._configure_f5_f8() adc_reads_f5_f8 = self._all_channels reads += adc_reads_f5_f8[1:-2] return reads @property def channel_415nm(self): """The current reading for the 415nm band""" self._configure_f1_f4() return self._channel_0_data @property def channel_445nm(self): """The current reading for the 445nm band""" self._configure_f1_f4() return self._channel_1_data @property def channel_480nm(self): """The current reading for the 480nm band""" self._configure_f1_f4() return self._channel_2_data @property def channel_515nm(self): """The current reading for the 515nm band""" self._configure_f1_f4() return self._channel_3_data @property def channel_555nm(self): """The current reading for the 555nm band""" self._configure_f5_f8() return self._channel_0_data @property def channel_590nm(self): """The current reading for the 590nm band""" self._configure_f5_f8() return self._channel_1_data @property def channel_630nm(self): """The current reading for the 630nm band""" self._configure_f5_f8() return self._channel_2_data @property def channel_680nm(self): """The current reading for the 680nm band""" self._configure_f5_f8() return self._channel_3_data # TODO: Add clear and NIR accessors def _wait_for_data(self, timeout=1.0): """Wait for sensor data to be ready""" start = monotonic() while not self._data_ready_bit: if monotonic() - start > timeout: raise RuntimeError("Timeout occoured waiting for sensor data") sleep(0.001) def _write_register(self, addr, data): self._buffer[0] = addr self._buffer[1] = data with self.i2c_device as i2c: i2c.write(self._buffer) def _configure_f1_f4(self): """Configure the sensor to read from elements F1-F4, Clear, and NIR""" # disable SP_EN bit while making config changes if self._low_channels_configured: return self._high_channels_configured = False self._flicker_detection_1k_configured = False self._color_meas_enabled = False # ENUM-ify self._smux_command = 2 # Write new configuration to all the 20 registers self._f1f4_clear_nir() # Start SMUX command self._smux_enabled = True # Enable SP_EN bit self._color_meas_enabled = True self._low_channels_configured = True self._wait_for_data() def _configure_f5_f8(self): """Configure the sensor to read from elements F5-F8, Clear, and NIR""" # disable SP_EN bit while making config changes if self._high_channels_configured: return self._low_channels_configured = False self._flicker_detection_1k_configured = False self._color_meas_enabled = False # ENUM-ify self._smux_command = 2 # Write new configuration to all the 20 registers self._f5f8_clear_nir() # Start SMUX command self._smux_enabled = True # Enable SP_EN bit self._color_meas_enabled = True self._high_channels_configured = True self._wait_for_data() @property def flicker_detected(self): """The flicker frequency detected in Hertz""" if not self._flicker_detection_1k_configured: AttributeError( "Flicker detection must be enabled to access `flicker_detected`" ) flicker_status = self._fd_status if flicker_status == 45: return 1000 if flicker_status == 46: return 1200 return None # if we haven't returned yet either there was an error or an unknown frequency was detected @property def flicker_detection_enabled(self): """The flicker detection status of the sensor. True if the sensor is configured\ to detect flickers. Currently only 1000Hz and 1200Hz flicker detection is supported """ return self._flicker_detection_1k_configured @flicker_detection_enabled.setter def flicker_detection_enabled(self, flicker_enable): if flicker_enable: self._configure_1k_flicker_detection() else: self._configure_f1_f4() # sane default def _f1f4_clear_nir(self): """Configure SMUX for sensors F1-F4, Clear and NIR""" self._set_smux(SMUX_IN.NC_F3L, SMUX_OUT.DISABLED, SMUX_OUT.ADC2) self._set_smux(SMUX_IN.F1L_NC, SMUX_OUT.ADC0, SMUX_OUT.DISABLED) self._set_smux(SMUX_IN.NC_NC0, SMUX_OUT.DISABLED, SMUX_OUT.DISABLED) self._set_smux(SMUX_IN.NC_F8L, SMUX_OUT.DISABLED, SMUX_OUT.DISABLED) self._set_smux(SMUX_IN.F6L_NC, SMUX_OUT.DISABLED, SMUX_OUT.DISABLED) self._set_smux(SMUX_IN.F2L_F4L, SMUX_OUT.ADC1, SMUX_OUT.ADC3) self._set_smux(SMUX_IN.NC_F5L, SMUX_OUT.DISABLED, SMUX_OUT.DISABLED) self._set_smux(SMUX_IN.F7L_NC, SMUX_OUT.DISABLED, SMUX_OUT.DISABLED) self._set_smux(SMUX_IN.NC_CL, SMUX_OUT.DISABLED, SMUX_OUT.ADC4) self._set_smux(SMUX_IN.NC_F5R, SMUX_OUT.DISABLED, SMUX_OUT.DISABLED) self._set_smux(SMUX_IN.F7R_NC, SMUX_OUT.DISABLED, SMUX_OUT.DISABLED) self._set_smux(SMUX_IN.NC_NC1, SMUX_OUT.DISABLED, SMUX_OUT.DISABLED) self._set_smux(SMUX_IN.NC_F2R, SMUX_OUT.DISABLED, SMUX_OUT.ADC1) self._set_smux(SMUX_IN.F4R_NC, SMUX_OUT.ADC3, SMUX_OUT.DISABLED) self._set_smux(SMUX_IN.F8R_F6R, SMUX_OUT.DISABLED, SMUX_OUT.DISABLED) self._set_smux(SMUX_IN.NC_F3R, SMUX_OUT.DISABLED, SMUX_OUT.ADC2) self._set_smux(SMUX_IN.F1R_EXT_GPIO, SMUX_OUT.ADC0, SMUX_OUT.DISABLED) self._set_smux(SMUX_IN.EXT_INT_CR, SMUX_OUT.DISABLED, SMUX_OUT.ADC4) self._set_smux(SMUX_IN.NC_DARK, SMUX_OUT.DISABLED, SMUX_OUT.DISABLED) self._set_smux(SMUX_IN.NIR_F, SMUX_OUT.ADC5, SMUX_OUT.DISABLED) def _f5f8_clear_nir(self): # SMUX Config for F5,F6,F7,F8,NIR,Clear self._set_smux(SMUX_IN.NC_F3L, SMUX_OUT.DISABLED, SMUX_OUT.DISABLED) self._set_smux(SMUX_IN.F1L_NC, SMUX_OUT.DISABLED, SMUX_OUT.DISABLED) self._set_smux(SMUX_IN.NC_NC0, SMUX_OUT.DISABLED, SMUX_OUT.DISABLED) self._set_smux(SMUX_IN.NC_F8L, SMUX_OUT.DISABLED, SMUX_OUT.ADC3) self._set_smux(SMUX_IN.F6L_NC, SMUX_OUT.ADC1, SMUX_OUT.DISABLED) self._set_smux(SMUX_IN.F2L_F4L, SMUX_OUT.DISABLED, SMUX_OUT.DISABLED) self._set_smux(SMUX_IN.NC_F5L, SMUX_OUT.DISABLED, SMUX_OUT.ADC0) self._set_smux(SMUX_IN.F7L_NC, SMUX_OUT.ADC2, SMUX_OUT.DISABLED) self._set_smux(SMUX_IN.NC_CL, SMUX_OUT.DISABLED, SMUX_OUT.ADC4) self._set_smux(SMUX_IN.NC_F5R, SMUX_OUT.DISABLED, SMUX_OUT.ADC0) self._set_smux(SMUX_IN.F7R_NC, SMUX_OUT.ADC2, SMUX_OUT.DISABLED) self._set_smux(SMUX_IN.NC_NC1, SMUX_OUT.DISABLED, SMUX_OUT.DISABLED) self._set_smux(SMUX_IN.NC_F2R, SMUX_OUT.DISABLED, SMUX_OUT.DISABLED) self._set_smux(SMUX_IN.F4R_NC, SMUX_OUT.DISABLED, SMUX_OUT.DISABLED) self._set_smux(SMUX_IN.F8R_F6R, SMUX_OUT.ADC3, SMUX_OUT.ADC1) self._set_smux(SMUX_IN.NC_F3R, SMUX_OUT.DISABLED, SMUX_OUT.DISABLED) self._set_smux(SMUX_IN.F1R_EXT_GPIO, SMUX_OUT.DISABLED, SMUX_OUT.DISABLED) self._set_smux(SMUX_IN.EXT_INT_CR, SMUX_OUT.DISABLED, SMUX_OUT.ADC4) self._set_smux(SMUX_IN.NC_DARK, SMUX_OUT.DISABLED, SMUX_OUT.DISABLED) self._set_smux(SMUX_IN.NIR_F, SMUX_OUT.ADC5, SMUX_OUT.DISABLED) # TODO: Convert as much of this as possible to properties or named attributes def _configure_1k_flicker_detection(self): self._low_channels_configured = False self._high_channels_configured = False # RAM_BANK 0 select which RAM bank to access in register addresses 0x00-0x7f self._write_register(_AS7341_CFG0, 0x00) # The coefficient calculated are stored into the RAM bank 0 and RAM bank 1, # they are used instead of 100Hz and 120Hz coefficients which are the default # flicker detection coefficients # write new coefficients to detect the 1000Hz and 1200Hz - part 1 self._write_register(0x04, 0x9E) self._write_register(0x05, 0x36) self._write_register(0x0E, 0x2E) self._write_register(0x0F, 0x1B) self._write_register(0x18, 0x7D) self._write_register(0x19, 0x36) self._write_register(0x22, 0x09) self._write_register(0x23, 0x1B) self._write_register(0x2C, 0x5B) self._write_register(0x2D, 0x36) self._write_register(0x36, 0xE5) self._write_register(0x37, 0x1A) self._write_register(0x40, 0x3A) self._write_register(0x41, 0x36) self._write_register(0x4A, 0xC1) self._write_register(0x4B, 0x1A) self._write_register(0x54, 0x18) self._write_register(0x55, 0x36) self._write_register(0x5E, 0x9C) self._write_register(0x5F, 0x1A) self._write_register(0x68, 0xF6) self._write_register(0x69, 0x35) self._write_register(0x72, 0x78) self._write_register(0x73, 0x1A) self._write_register(0x7C, 0x4D) self._write_register(0x7D, 0x35) # RAM_BANK 1 select which RAM bank to access in register addresses 0x00-0x7f self._write_register(_AS7341_CFG0, 0x01) # write new coefficients to detect the 1000Hz and 1200Hz - part 1 self._write_register(0x06, 0x54) self._write_register(0x07, 0x1A) self._write_register(0x10, 0xB3) self._write_register(0x11, 0x35) self._write_register(0x1A, 0x2F) self._write_register(0x1B, 0x1A) self._write_register(_AS7341_CFG0, 0x01) # select RAM coefficients for flicker detection by setting # fd_disable_constant_init to „1“ (FD_CFG0 register) in FD_CFG0 register - # 0xD7 # fd_disable_constant_init=1 # fd_samples=4 self._write_register(_AS7341_FD_CFG0, 0x60) # in FD_CFG1 register - 0xd8 fd_time(7:0) = 0x40 self._write_register(_AS7341_FD_TIME1, 0x40) # in FD_CFG2 register - 0xd9 fd_dcr_filter_size=1 fd_nr_data_sets(2:0)=5 self._write_register(0xD9, 0x25) # in FD_CFG3 register - 0xda fd_gain=9 self._write_register(_AS7341_FD_TIME2, 0x48) # in CFG9 register - 0xb2 sien_fd=1 self._write_register(_AS7341_CFG9, 0x40) # in ENABLE - 0x80 fden=1 and pon=1 are enabled self._write_register(_AS7341_ENABLE, 0x41) self._flicker_detection_1k_configured = True def _smux_template(self): # SMUX_OUT.DISABLED # SMUX_OUT.ADC0 # SMUX_OUT.ADC1 # SMUX_OUT.ADC2 # SMUX_OUT.ADC3 # SMUX_OUT.ADC4 # SMUX_OUT.ADC5 self._set_smux(SMUX_IN.NC_F3L, SMUX_OUT.DISABLED, SMUX_OUT.DISABLED) self._set_smux(SMUX_IN.F1L_NC, SMUX_OUT.DISABLED, SMUX_OUT.DISABLED) self._set_smux(SMUX_IN.NC_NC0, SMUX_OUT.DISABLED, SMUX_OUT.DISABLED) self._set_smux(SMUX_IN.NC_F8L, SMUX_OUT.DISABLED, SMUX_OUT.DISABLED) self._set_smux(SMUX_IN.F6L_NC, SMUX_OUT.DISABLED, SMUX_OUT.DISABLED) self._set_smux(SMUX_IN.F2L_F4L, SMUX_OUT.DISABLED, SMUX_OUT.DISABLED) self._set_smux(SMUX_IN.NC_F5L, SMUX_OUT.DISABLED, SMUX_OUT.DISABLED) self._set_smux(SMUX_IN.F7L_NC, SMUX_OUT.DISABLED, SMUX_OUT.DISABLED) self._set_smux(SMUX_IN.NC_CL, SMUX_OUT.DISABLED, SMUX_OUT.DISABLED) self._set_smux(SMUX_IN.NC_F5R, SMUX_OUT.DISABLED, SMUX_OUT.DISABLED) self._set_smux(SMUX_IN.F7R_NC, SMUX_OUT.DISABLED, SMUX_OUT.DISABLED) self._set_smux(SMUX_IN.NC_NC1, SMUX_OUT.DISABLED, SMUX_OUT.DISABLED) self._set_smux(SMUX_IN.NC_F2R, SMUX_OUT.DISABLED, SMUX_OUT.DISABLED) self._set_smux(SMUX_IN.F4R_NC, SMUX_OUT.DISABLED, SMUX_OUT.DISABLED) self._set_smux(SMUX_IN.F8R_F6R, SMUX_OUT.DISABLED, SMUX_OUT.DISABLED) self._set_smux(SMUX_IN.NC_F3R, SMUX_OUT.DISABLED, SMUX_OUT.DISABLED) self._set_smux(SMUX_IN.F1R_EXT_GPIO, SMUX_OUT.DISABLED, SMUX_OUT.DISABLED) self._set_smux(SMUX_IN.EXT_INT_CR, SMUX_OUT.DISABLED, SMUX_OUT.DISABLED) self._set_smux(SMUX_IN.NC_DARK, SMUX_OUT.DISABLED, SMUX_OUT.DISABLED) self._set_smux(SMUX_IN.NIR_F, SMUX_OUT.DISABLED, SMUX_OUT.DISABLED) def _set_smux(self, smux_addr, smux_out1, smux_out2): """Connect a pair of sensors to an ADC channel""" low_nibble = smux_out1 high_nibble = smux_out2 << 4 smux_byte = high_nibble | low_nibble self._write_register(smux_addr, smux_byte) @property def gain(self): """The ADC gain multiplier. Must be a valid `adafruit_as7341.Gain`""" return self._gain @gain.setter def gain(self, gain_value): if not Gain.is_valid(gain_value): raise AttributeError( "`gain` must be a valid `adafruit_as7341.Gain`") self._gain = gain_value @property def _smux_enabled(self): return self._smux_enable_bit @_smux_enabled.setter def _smux_enabled(self, enable_smux): self._low_bank_active = False self._smux_enable_bit = enable_smux while self._smux_enable_bit is True: sleep(0.001) @property @_low_bank def led_current(self): """The maximum allowed current through the attached LED in milliamps. Odd numbered values will be rounded down to the next lowest even number due to the internal configuration restrictions""" current_val = self._led_current_bits return (current_val * 2) + 4 @led_current.setter @_low_bank def led_current(self, led_curent): new_current = int((min(258, max(4, led_curent)) - 4) / 2) self._led_current_bits = new_current @property @_low_bank def led(self): """The attached LED. Set to True to turn on, False to turn off""" return self._led_enabled @led.setter @_low_bank def led(self, led_on): self._led_enabled = led_on @property @_low_bank def _led_control_enabled(self): return self._led_control_enable_bit @_led_control_enabled.setter @_low_bank def _led_control_enabled(self, enabled): self._led_control_enable_bit = enabled
class AW9523: """CircuitPython helper class for using the AW9523 GPIO expander""" _chip_id = ROUnaryStruct(_AW9523_REG_CHIPID, "<B") _reset_reg = UnaryStruct(_AW9523_REG_SOFTRESET, "<B") # Set all 16 gpio outputs outputs = UnaryStruct(_AW9523_REG_OUTPUT0, "<H") # Read all 16 gpio inputs inputs = UnaryStruct(_AW9523_REG_INPUT0, "<H") # Set all 16 gpio interrupt enable _interrupt_enables = UnaryStruct(_AW9523_REG_INTENABLE0, "<H") # Set all 16 gpio directions _directions = UnaryStruct(_AW9523_REG_CONFIG0, "<H") # Set all 16 gpio LED modes _LED_modes = UnaryStruct(_AW9523_REG_LEDMODE, "<H") # Whether port 0 is push-pull port0_push_pull = RWBit(_AW9523_REG_GCR, 4) def __init__(self, i2c_bus, address=_AW9523_DEFAULT_ADDR, reset=True): self.i2c_device = i2c_device.I2CDevice(i2c_bus, address) self._buffer = bytearray(2) if self._chip_id != 0x23: raise AttributeError("Cannot find a AW9523") if reset: self.reset() self.port0_push_pull = True # pushpull output self.interrupt_enables = 0x0000 # no IRQ self.directions = 0x0000 # all inputs! def reset(self): """Perform a soft reset, check datasheets for post-reset defaults!""" self._reset_reg = 0 def set_constant_current(self, pin, value): """ Set the constant current drain for an AW9523 pin :param int pin: pin to set constant current, 0..15 :param int value: the value ranging from 0 (off) to 255 (max current) """ # See Table 13. 256 step dimming control register if 0 <= pin <= 7: self._buffer[0] = 0x24 + pin elif 8 <= pin <= 11: self._buffer[0] = 0x20 + pin - 8 elif 12 <= pin <= 15: self._buffer[0] = 0x2C + pin - 12 else: raise ValueError("Pin must be 0 to 15") # set value if not 0 <= value <= 255: raise ValueError("Value must be 0 to 255") self._buffer[1] = value with self.i2c_device as i2c: i2c.write(self._buffer) def get_pin(self, pin): """Convenience function to create an instance of the DigitalInOut class pointing at the specified pin of this AW9523 device. :param int pin: pin to use for digital IO, 0 to 15 """ assert 0 <= pin <= 15 return DigitalInOut(pin, self) @property def interrupt_enables(self): """Enables interrupt for input pin change if bit mask is 1""" return ~self._interrupt_enables & 0xFFFF @interrupt_enables.setter def interrupt_enables(self, enables): self._interrupt_enables = ~enables & 0xFFFF @property def directions(self): """Direction is output if bit mask is 1, input if bit is 0""" return ~self._directions & 0xFFFF @directions.setter def directions(self, dirs): self._directions = (~dirs) & 0xFFFF @property def LED_modes(self): """Pin is set up for constant current mode if bit mask is 1""" return ~self._LED_modes & 0xFFFF @LED_modes.setter def LED_modes(self, modes): self._LED_modes = ~modes & 0xFFFF