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 INA260: """Driver for the INA260 power and current sensor. :param ~busio.I2C i2c_bus: The I2C bus the INA260 is connected to. :param address: The I2C device address for the sensor. Default is ``0x40``. """ def __init__(self, i2c_bus, address=0x40): self.i2c_device = i2cdevice.I2CDevice(i2c_bus, address) _raw_current = ROUnaryStruct(_REG_CURRENT, ">h") _raw_voltage = ROUnaryStruct(_REG_BUSVOLTAGE, ">H") _raw_power = ROUnaryStruct(_REG_POWER, ">H") _conversion_ready = ROBit(_REG_MASK_ENABLE, 3, 2, False) 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) alert_limit = RWBits(16, _REG_ALERT_LIMIT, 0, 2, False) @property def current(self): """The current (between V+ and V-) in mA""" if self.mode == Mode.TRIGGERED: while self._conversion_ready == 0: pass return self._raw_current * 1.25 @property def voltage(self): """The bus voltage in V""" if self.mode == Mode.TRIGGERED: while self._conversion_ready == 0: pass return self._raw_voltage * 0.00125 @property def power(self): """The power being delivered to the load in mW""" if self.mode == Mode.TRIGGERED: while self._conversion_ready == 0: pass return self._raw_power * 10
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 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 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 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 FDC2212(object): """ :param i2c: The `busio.I2C` object to use. This is the only required parameter. :param # Class-level buffer to reduce allocations and fragmentation. # Note this is NOT thread-safe or re-entrant by design! """ _BUFFER = bytearray(8) RESOLUTION = 2**28 # Misc Settings rst = RWBit(FDC2212_RESET_DEV, 15, 2, False) gain = RWBits(2, FDC2212_RESET_DEV, 9, 2, False) drdy0 = ROBit(FDC2212_STATUS, 6, 2, False) drdy1 = ROBit(FDC2212_STATUS, 6, 2, False) drdy01 = ROBits(2, FDC2212_STATUS, 2, 2, False) status = RWBits(16, FDC2212_STATUS, 0, 2, False) # CONFIG register settings slp = RWBit(FDC2212_CONFIG, 13, 2, False) ref_clk = RWBit(FDC2212_CONFIG, 9, 2, False) high_drv = RWBit(FDC2212_CONFIG, 6, 2, False) active_mode = RWBits(2, FDC2212_CONFIG, 14, 2, False) config_all = RWBits(16, FDC2212_CONFIG, 0, 2, False) # CLOCK DIVIDERS ch0_fsel = RWBits(2, FDC2212_CLOCK_DIVIDERS_CH0, 12, 2, False) ch0_fdiv = RWBits(10, FDC2212_CLOCK_DIVIDERS_CH0, 0, 2, False) ch0_clk = RWBits(16, FDC2212_CLOCK_DIVIDERS_CH0, 0, 2, False) ch1_fsel = RWBits(2, FDC2212_CLOCK_DIVIDERS_CH1, 12, 2, False) ch1_fdiv = RWBits(10, FDC2212_CLOCK_DIVIDERS_CH1, 0, 2, False) ch1_clk = RWBits(16, FDC2212_CLOCK_DIVIDERS_CH1, 0, 2, False) # DATA registers data_ch0a = ROBits(16, FDC2212_DATA_CH0_MSB, 0, 2, False) data_ch0b = ROBits(16, FDC2212_DATA_CH0_LSB, 0, 2, False) data_ch1a = ROBits(16, FDC2212_DATA_CH1_MSB, 0, 2, False) data_ch1b = ROBits(16, FDC2212_DATA_CH1_LSB, 0, 2, False) # MUX_CONFIG register auto_scan = RWBit(FDC2212_MUX_CONFIG, 15, 2, False) rr_seq = RWBits(2, FDC2212_MUX_CONFIG, 13, 2, False) dglitch = RWBits(3, FDC2212_MUX_CONFIG, 0, 2, False) def __init__(self, i2c, *, address=FDC2212_I2C_ADDR_0, debug=False): self.i2c_device = I2CDevice(i2c, address) self.rst = True # Check for valid chip ID if self._read16(FDC2212i2c_device_ID) not in (0x3055, 0x3054): raise RuntimeError('Failed to find FDC2212/FDC2214, check wiring!') self.debug = debug ''' CONFIG Register 00 0 [1] 0 [1] 0 [0] 1 0 [000001] CHAN SLP RES DRIVE RES CLK RES INT HDRIVE RES MUX_CONFIG 1 00 [00 0100 0001] 101 SCAN RR_SEQ RES DGLITCH ''' self._config = 0x1481 self._mux = 0x820D # Initalize varriables self._fulldrive = True self._fclk = 43.3e6 # 43.3 MHz (internal) self._L = 18e-6 # 18uH self._cap = 33e-12 # 33pf self._differential = True # differential (default) self._div = 1 self.fref = self._fclk / 1 self._Idrive = 1 self._Fsense = self._Csense = 0 self._channel = 0 self._MSB = FDC2212_DATA_CH0_MSB self._LSB = FDC2212_DATA_CH0_LSB self._write16(FDC2212_RCOUNT_CH0, 0xFFFF) self._write16(FDC2212_RCOUNT_CH1, 0xFFFF) self._write16(FDC2212_SETTLECOUNT_CH0, 0x0400) self._write16(FDC2212_SETTLECOUNT_CH1, 0x0400) self._write16(FDC2212_CLOCK_DIVIDERS_CH0, 0x1001) self._write16(FDC2212_CLOCK_DIVIDERS_CH1, 0x1001) self._write16(FDC2212_DRIVE_CH0, 0x4800) self._write16(FDC2212_DRIVE_CH1, 0x4800) self._write16(FDC2212_MUX_CONFIG, self._mux) self._write16(FDC2212_CONFIG, self._config) @property def clock(self): return self._fclk @clock.setter def clock(self, freq): self._fclk = freq if freq != 43.3e6: print('External Clock Enabled') self._config |= (1 << 9) self._write16(FDC2212_CONFIG, self._config) @property def inductance(self): return self._L @inductance.setter def inductance(self, induc): self._L = induc @property def selfcitance(self): return self._cap @selfcitance.setter def selfcitance(self, cap): self._cap = cap @property def RCOUNT(self): return self._RCOUNT @RCOUNT.setter def RCOUNT(self, rcount): self._RCOUNT = rcount self._write16(FDC2212_RCOUNT_CH0, rcount) self._write16(FDC2212_RCOUNT_CH1, rcount) @property def SETTLE(self): return self._SETTLE @SETTLE.setter def SETTLE(self, settle): ''' Settle time = (SETTLECOUNT * 16)/Fclk (0xFFFF*16)/40E6=26.2ms ''' self._SETTLE = settle self._write16(FDC2212_SETTLECOUNT_CH0, settle) self._write16(FDC2212_SETTLECOUNT_CH1, settle) @property def Idrive(self): return self._Idrive @Idrive.setter def Idrive(self, idrive): self._Idrive = idrive self._write16(FDC2212_DRIVE_CH0, idrive) self._write16(FDC2212_DRIVE_CH1, idrive) # @property # def status(self): # return self._read16(FDC2212_STATUS) @property def status_config(self): self._status_config = self._read16(FDC2212_STATUS_CONFIG) return self._status_config @status_config.setter def status_config(self, setting): self._status_config = setting self._write16(FDC2212_STATUS_CONFIG, self._status_config) @property def full_drive(self): return self._fulldrive @full_drive.setter def full_drive(self, state): # full-current drive on all channels self._fulldrive = state if state: # enabled self._config |= (1 << 11) else: # disabled self._config &= ~(1 << 11) self._write16(FDC2212_CONFIG, self._config) @property def differential(self): # Sets differential/single-ended for BOTH channels return self._differential @differential.setter def differential(self, div): self._differential = div if self._differential: # differential (default) self._write16(FDC2212_CLOCK_DIVIDERS_CH0, 0x1001) self._write16(FDC2212_CLOCK_DIVIDERS_CH1, 0x1001) self._div = 1 else: # single-ended self._write16(FDC2212_CLOCK_DIVIDERS_CH0, 0x2001) self._write16(FDC2212_CLOCK_DIVIDERS_CH1, 0x2001) self._div = 2 @property def sleep(self): return self._sleep @sleep.setter def sleep(self, value): if value: self._config |= (1 << 13) else: self._config &= ~(1 << 13) self._write16(FDC2212_CONFIG, self._config) @property def scan(self): return bool(self.auto_scan) @scan.setter def scan(self, value): self.slp = True if value: self.auto_scan = True self.rr_seq = 0x00 #CH0 & CH1 else: self.auto_scan = False self.slp = False @property def deglitch(self): return self._deglitch @deglitch.setter def deglitch(self, value): ''' Input deglitch filter bandwidth. Select the lowest setting that exceeds the oscillation tank oscillation frequency. 1MHz,3.3MHz,10MHz,33MHz ''' if value not in (1, 4, 5, 7): raise ValueError( "Unsupported deglitch setting. Valid values are: 1(1MHz),4(3.3MHz),5(10MHz),7(33MHz)" ) self.dglitch = value if self.debug: print(hex(self._mux)) @property def channel(self): return self._channel @channel.setter def channel(self, channel): if channel not in (0, 1): raise ValueError("Unsupported channel.") if channel == 0: self._config &= ~(1 << 14) self._write16(FDC2212_CONFIG, self._config) self._MSB = FDC2212_DATA_CH0_MSB self._LSB = FDC2212_DATA_CH0_LSB if channel == 1: self._config |= (1 << 14) self._write16(FDC2212_CONFIG, self._config) self._MSB = FDC2212_DATA_CH1_MSB self._LSB = FDC2212_DATA_CH1_LSB self._channel = channel def _read_into(self, address, buf, count=None): # Read bytes from the specified address into the provided buffer. # If count is not specified (the default) the entire buffer is filled, # otherwise only count bytes are copied in. assert len(buf) > 0 if count is None: count = len(buf) with self.i2c_device as i2c: i2c.write_then_readinto(bytes([address & 0xFF]), buf, in_end=count, stop=False) # [print(hex(i),'\t',end='') for i in self._BUFFER] # print('') def _read16(self, address): # Read a 16-bit unsigned value for from the specified address. self._read_into(address, self._BUFFER, count=2) _raw = self._BUFFER[0] << 8 | self._BUFFER[1] return _raw def _write16(self, address, value): # Write a 16-bit unsigned value to the specified address. self._BUFFER[0] = address & 0xFF self._BUFFER[1] = (value >> 8) & 0xFF self._BUFFER[2] = (value) & 0xff with self.i2c_device as i2c: i2c.write(self._BUFFER, end=3) def _read_raw(self): _reading = (self._read16(self._MSB) & FDC2212_DATA_CHx_MASK_DATA) << 16 _reading |= self._read16(self._LSB) return _reading def burst(self, cnt=10): _burst = [] _buff = [] test = bytearray(2) t1 = time.monotonic_ns() with self.i2c_device as i2c: for _ in range(cnt): i2c.write_then_readinto(bytes([0x01]), test, stop=False) # [print(hex(i),'\t',end='') for i in test] # print('') _buff.append((test[0], test[1])) t1 = time.monotonic_ns() - t1 # print(_buff) print('Freq:{} Hz'.format(cnt / (t1 * 1e-9))) for item in _buff: _dat = 0x1ed0000 _dat |= ((item[0] << 8) | item[1]) self._Fsense = (self._div * _dat * self._fclk / self.RESOLUTION) _burst.append( (1e12) * ((1 / (self._L * (2 * pi * self._Fsense)**2)) - self._cap)) return _burst def read(self): _reading = self._read_raw() try: # calculate fsensor (40MHz external ref) self._Fsense = (self._div * _reading * self._fclk / self.RESOLUTION) # calculate Csensor (18uH and 33pF LC tank) self._Csense = (1e12) * ( (1 / (self._L * (2 * pi * self._Fsense)**2)) - self._cap) except Exception as e: if self.debug: print('Error on read:', e) pass return self._Csense def read_both(self, errchck=False): ''' Reads CH0 and CH1 data channels and returns a tuple: (CH0_DATA,CH1_DATA,CH0_ERR_AW,CH0_ERR_WD,CH1_ERR_AW,CH1_ERR_WD) ''' output = [] err = 0 s = time.monotonic() while self.drdy01 != 3: if time.monotonic() - s > 10: return (0, 0, 0, 0, 0, 0) pass _reading1 = self.data_ch0a if errchck: output.append(_reading1 & FDC2212_DATA_CHx_MASK_ERRAW) #ch0 err_aw output.append(_reading1 & FDC2212_DATA_CHx_MASK_ERRWD) #ch0 err_wdt _reading1 = (_reading1 & FDC2212_DATA_CHx_MASK_DATA) << 16 _reading1 |= self.data_ch0b _reading2 = self.data_ch1a if errchck: output.append(_reading2 & FDC2212_DATA_CHx_MASK_ERRAW) #ch1 err_aw output.append(_reading2 & FDC2212_DATA_CHx_MASK_ERRWD) #ch1 err_wdt _reading2 = (_reading2 & FDC2212_DATA_CHx_MASK_DATA) << 16 _reading2 |= self.data_ch1b try: for i in _reading1, _reading2: # calculate fsensor (40MHz external ref) # self._Fsense=(self._div*i*self._fclk/self.RESOLUTION) # self._Fsense=self.ch_sel*self.fref*((i/self.RESOLUTION)) self._Fsense = 1 * self.fref * ((i / self.RESOLUTION)) # calculate Csensor (18uF and 33pF LC tank) in pF self._Csense = (1e12) * ( (1 / (self._L * (2 * pi * self._Fsense)**2)) - self._cap) output.append(self._Csense) except Exception as e: if self.debug: print('Error on read:', e) return output.append((0, 0)) return output
class BMX160: """ Driver for the BMX160 accelerometer, magnetometer, gyroscope. In the buffer, bytes are allocated as follows: - mag 0-5 - rhall 6-7 (not relevant?) - gyro 8-13 - accel 14-19 - sensor time 20-22 """ # multiplicative constants # NOTE THESE FIRST TWO GET SET IN THE INIT SEQUENCE ACC_SCALAR = 1/(AccelSensitivity2Gravity * g_TO_METERS_PER_SECOND_SQUARED) # 1 m/s^2 = 0.101971621 g GYR_SCALAR = 1/GyroSensitivity2DegPerSec_values[4] MAG_SCALAR = 1/16 TEMP_SCALAR = 0.5**9 _accel = Struct(BMX160_ACCEL_DATA_ADDR, '<hhh') # this is the default scalar, but it should get reset anyhow in init _gyro = Struct(BMX160_GYRO_DATA_ADDR, '<hhh') _mag = Struct(BMX160_MAG_DATA_ADDR, '<hhh') _temp = Struct(BMX160_TEMP_DATA_ADDR, '<h') ### STATUS BITS status = ROBits(8, BMX160_STATUS_ADDR, 0) status_acc_pmu = ROBits(2, BMX160_PMU_STATUS_ADDR, 4) status_gyr_pmu = ROBits(2, BMX160_PMU_STATUS_ADDR, 2) status_mag_pmu = ROBits(2, BMX160_PMU_STATUS_ADDR, 0) cmd = RWBits(8, BMX160_COMMAND_REG_ADDR, 0) foc = RWBits(8, BMX160_FOC_CONF_ADDR, 0) # see ERR_REG in section 2.11.2 _error_status = ROBits(8, BMX160_ERROR_REG_ADDR, 0) error_code = ROBits(4, BMX160_ERROR_REG_ADDR, 1) drop_cmd_err = ROBit(BMX160_ERROR_REG_ADDR, 6) fatal_err = ROBit(BMX160_ERROR_REG_ADDR, 0) # straight from the datasheet. Need to be renamed and better explained @property def drdy_acc(self): return (self.status >> 7) & 1 @property def drdy_gyr(self): return (self.status >> 6) & 1 @property def drdy_mag(self): return (self.status >> 5) & 1 @property def nvm_rdy(self): return (self.status >> 4) & 1 @property def foc_rdy(self): return (self.status >> 3) & 1 @property def mag_man_op(self): return (self.status >> 2) & 1 @property def gyro_self_test_ok(self): return (self.status >> 1) & 1 _BUFFER = bytearray(40) _smallbuf = bytearray(6) _gyro_range = RWBits(8, BMX160_GYRO_RANGE_ADDR, 0) _accel_range = RWBits(8, BMX160_ACCEL_RANGE_ADDR, 0) # _gyro_bandwidth = NORMAL # _gyro_powermode = NORMAL _gyro_odr = 25 # Hz # _accel_bandwidth = NORMAL # _accel_powermode = NORMAL _accel_odr = 25 # Hz # _mag_bandwidth = NORMAL # _mag_powermode = NORMAL _mag_odr = 25 # Hz _mag_range = 250 # deg/sec def __init__(self): # soft reset & reboot self.cmd = BMX160_SOFT_RESET_CMD time.sleep(0.001) # Check ID registers. ID = self.read_u8(BMX160_CHIP_ID_ADDR) if ID != BMX160_CHIP_ID: raise RuntimeError('Could not find BMX160, check wiring!') # print("status:", format_binary(self.status)) # set the default settings self.init_mag() self.init_accel() self.init_gyro() # print("status:", format_binary(self.status)) ######################## SENSOR API ######################## def read_all(self): return self.read_bytes(BMX160_MAG_DATA_ADDR, 20, self._BUFFER) # synonymous # @property # def error_status(self): # return format_binary(self._error_status) @property def query_error(self): return format_binary(self._error_status) ### ACTUAL API @property def gyro(self): # deg/s return tuple(x * self.GYR_SCALAR for x in self._gyro) @property def accel(self): # m/s^2 return tuple(x * self.ACC_SCALAR for x in self._accel) @property def mag(self): # uT return tuple(x * self.MAG_SCALAR for x in self._mag) @property def temperature(self): return self._temp[0]*self.TEMP_SCALAR+23 @property def temp(self): return self._temp[0]*self.TEMP_SCALAR+23 @property def sensortime(self): tbuf = self.read_bytes(BMX160_SENSOR_TIME_ADDR, 3, self._smallbuf) t0, t1, t2 = tbuf[:3] t = (t2 << 16) | (t1 << 8) | t0 t *= 0.000039 # the time resolution is 39 microseconds return t ######################## SETTINGS RELATED ######################## ############## GYROSCOPE SETTINGS ############## # NOTE still missing BW / OSR config, but those are more complicated def init_gyro(self): # BW doesn't have an interface yet self._gyro_bwmode = BMX160_GYRO_BW_NORMAL_MODE # These rely on the setters to do their magic. self.gyro_range = BMX160_GYRO_RANGE_500_DPS # self.GYR_SCALAR = 1 # self.GYR_SCALAR = 1/GyroSensitivity2DegPerSec_values[1] self.gyro_odr = 25 self.gyro_powermode = BMX160_GYRO_NORMAL_MODE @property def gyro_range(self): return self._gyro_range @gyro_range.setter def gyro_range(self, rangeconst): """ The input is expected to be the BMX160-constant associated with the range. deg/s | bmxconst value | bmxconst_name ------------------------------------------------------ 2000 | 0 | BMX160_GYRO_RANGE_2000_DPS 1000 | 1 | BMX160_GYRO_RANGE_1000_DPS 500 | 2 | BMX160_GYRO_RANGE_500_DPS 250 | 3 | BMX160_GYRO_RANGE_250_DPS 125 | 4 | BMX160_GYRO_RANGE_125_DPS ex: bmx.gyro_range = BMX160_GYRO_RANGE_500_DPS equivalent to: bmx.gyro_range = 2 BMX160_GYRO_RANGE_VALUES = [2000, 1000, 500, 250, 125] """ if rangeconst in BMX160_GYRO_RANGE_CONSTANTS: self._gyro_range = rangeconst # read out the value to see if it changed successfully rangeconst = self._gyro_range val = BMX160_GYRO_RANGE_VALUES[rangeconst] self.GYR_SCALAR = (val / 32768.0) else: pass @property def gyro_odr(self): return self._gyro_odr @gyro_odr.setter def gyro_odr(self, odr): """ Set the output data rate of the gyroscope. The possible ODRs are 1600, 800, 400, 200, 100, 50, 25, 12.5, 6.25, 3.12, 1.56, and 0.78 Hz. Note, setting a value between the listed ones will round *downwards*. """ res = self.generic_setter(odr, BMX160_GYRO_ODR_VALUES, BMX160_GYRO_ODR_CONSTANTS, BMX160_GYRO_CONFIG_ADDR, "gyroscope odr") if res != None: self._gyro_odr = res[1] @property def gyro_powermode(self): return self._gyro_powermode @gyro_powermode.setter def gyro_powermode(self, powermode): """ Set the power mode of the gyroscope. Unlike other setters, this one has to directly take the BMX160-const associated with the power mode. The possible modes are: `BMX160_GYRO_SUSPEND_MODE` `BMX160_GYRO_NORMAL_MODE` `BMX160_GYRO_FASTSTARTUP_MODE` """ if powermode not in BMX160_GYRO_MODES: print("Unknown gyroscope powermode: " + str(powermode)) return self.write_u8(BMX160_COMMAND_REG_ADDR, powermode) if int(self.query_error) == 0: self._gyro_powermode = powermode else: settingswarning("gyroscope power mode") # NOTE: this delay is a worst case. If we need repeated switching # we can choose the delay on a case-by-case basis. time.sleep(0.0081) ############## ACCELEROMETER SETTINGS ############## def init_accel(self): # BW doesn't have an interface yet # self.write_u8(BMX160_ACCEL_CONFIG_ADDR, BMX160_ACCEL_BW_NORMAL_AVG4) # self._accel_bwmode = BMX160_ACCEL_BW_NORMAL_AVG4 # These rely on the setters to do their magic. self.accel_range = BMX160_ACCEL_RANGE_8G self.accel_odr = 25 self.accel_powermode = BMX160_ACCEL_NORMAL_MODE @property def accel_range(self): return self._accel_range @accel_range.setter def accel_range(self, rangeconst): """ The input is expected to be the BMX160-constant associated with the range. deg/s | bmxconst value | bmxconst name ------------------------------------------------------ 2 | 3 | BMX160_ACCEL_RANGE_2G 4 | 5 | BMX160_ACCEL_RANGE_4G 8 | 8 | BMX160_ACCEL_RANGE_8G 16 | 12 | BMX160_ACCEL_RANGE_16G ex: bmx.accel_range = BMX160_ACCEL_RANGE_4G equivalent to: bmx.accel_range = 5 """ if rangeconst in BMX160_ACCEL_RANGE_CONSTANTS: self._accel_range = rangeconst # read out the value to see if it changed successfully rangeconst = self._accel_range # convert to 0-3 range for indexing ind = rangeconst >> 2 val = BMX160_ACCEL_RANGE_VALUES[ind] self.ACC_SCALAR = (val / 32768.0) / g_TO_METERS_PER_SECOND_SQUARED else: pass @property def accel_odr(self): return self._accel_odr @accel_odr.setter def accel_odr(self, odr): res = self.generic_setter(odr, BMX160_ACCEL_ODR_VALUES, BMX160_ACCEL_ODR_CONSTANTS, BMX160_ACCEL_CONFIG_ADDR, "accelerometer odr") if res != None: self._accel_odr = res[1] @property def accel_powermode(self): return self._accel_powermode @accel_powermode.setter def accel_powermode(self, powermode): """ Set the power mode of the accelerometer. Unlike other setters, this one has to directly take the BMX160-const associated with the power mode. The possible modes are: `BMI160_ACCEL_NORMAL_MODE` `BMI160_ACCEL_LOWPOWER_MODE` `BMI160_ACCEL_SUSPEND_MODE` """ if powermode not in BMX160_ACCEL_MODES: print("Unknown accelerometer power mode: " + str(powermode)) return self.write_u8(BMX160_COMMAND_REG_ADDR, powermode) if int(self.query_error) == 0: self._accel_powermode = powermode else: settingswarning("accelerometer power mode") # NOTE: this delay is a worst case. If we need repeated switching # we can choose the delay on a case-by-case basis. time.sleep(0.005) ############## MAGENTOMETER SETTINGS ############## def init_mag(self): # see pg 25 of: https://ae-bst.resource.bosch.com/media/_tech/media/datasheets/BST-BMX160-DS000.pdf self.write_u8(BMX160_COMMAND_REG_ADDR, BMX160_MAG_NORMAL_MODE) time.sleep(0.00065) # datasheet says wait for 650microsec self.write_u8(BMX160_MAG_IF_0_ADDR, 0x80) # put mag into sleep mode self.write_u8(BMX160_MAG_IF_3_ADDR, 0x01) self.write_u8(BMX160_MAG_IF_2_ADDR, 0x4B) # set x-y to regular power preset self.write_u8(BMX160_MAG_IF_3_ADDR, 0x04) self.write_u8(BMX160_MAG_IF_2_ADDR, 0x51) # set z to regular preset self.write_u8(BMX160_MAG_IF_3_ADDR, 0x0E) self.write_u8(BMX160_MAG_IF_2_ADDR, 0x52) # prepare MAG_IF[1-3] for mag_if data mode self.write_u8(BMX160_MAG_IF_3_ADDR, 0x02) self.write_u8(BMX160_MAG_IF_2_ADDR, 0x4C) self.write_u8(BMX160_MAG_IF_1_ADDR, 0x42) # Set ODR to 25 Hz self.write_u8(BMX160_MAG_ODR_ADDR, BMX160_MAG_ODR_25HZ) self.write_u8(BMX160_MAG_IF_0_ADDR, 0x00) # put in low power mode. self.write_u8(BMX160_COMMAND_REG_ADDR, BMX160_MAG_LOWPOWER_MODE) time.sleep(0.1) # takes this long to warm up (empirically) @property def mag_powermode(self): return self._mag_powermode @mag_powermode.setter def mag_powermode(self, powermode): """ Set the power mode of the magnetometer. Unlike other setters, this one has to directly take the BMX160-const associated with the power mode. The possible modes are: `BMX160_` `BMX160_` `BMX160_` """ # if powermode not in BMX160_MAG_: # print("Unknown gyroscope powermode: " + str(powermode)) # return self.write_u8(BMX160_COMMAND_REG_ADDR, powermode) if int(self.query_error) == 0: self._mag_powermode = powermode else: settingswarning("mag power mode") # NOTE: this delay is a worst case. If we need repeated switching # we can choose the delay on a case-by-case basis. time.sleep(0.001) ## UTILS: def generic_setter(self, desired, possible_values, bmx_constants, config_addr, warning_interp = ""): i = find_nearest_valid(desired, possible_values) rounded = possible_values[i] bmxconst = bmx_constants[i] self.write_u8(config_addr, bmxconst) e = self.error_code if e == BMX160_OK: return (i, rounded) else: settingswarning(warning_interp)
class ADT7410: """Interface to the Analog Devices ADT7410 temperature sensor.""" # many modes can be set with register objects for simplicity ready = ROBit(_ADT7410_STATUS, 7) ctpin_polarity = RWBit(_ADT7410_CONFIG, 2) intpin_polarity = RWBit(_ADT7410_CONFIG, 3) comparator_mode = RWBit(_ADT7410_CONFIG, 4) high_resolution = RWBit(_ADT7410_CONFIG, 7) def __init__(self, i2c_bus, address=0x48): self.i2c_device = I2CDevice(i2c_bus, address) self._buf = bytearray(3) # Verify the manufacturer and device ids to ensure we are talking to # what we expect. _id = (self._read_register(_ADT7410_ID)[0]) & 0xF8 if _id != 0xC8: raise ValueError("Unable to find ADT7410 at i2c address " + str(hex(address))) # Perform a software reset self._write_register(_ADT7410_SWRST) time.sleep(0.01) @property def temperature(self): """The temperature in celsius""" while not self.ready: pass temp = self._read_register(_ADT7410_TEMPMSB, 2) return struct.unpack('>h', temp)[0] / 128 @property def status(self): """The ADT7410 status registers current value""" return self._read_register(_ADT7410_STATUS)[0] @property def configuration(self): """The ADT7410 configuration register""" return self._read_register(_ADT7410_CONFIG)[0] @configuration.setter def configuration(self, val): return self._write_register(_ADT7410_CONFIG, val) def _read_register(self, addr, num=1): self._buf[0] = addr with self.i2c_device as i2c: i2c.write_then_readinto(self._buf, self._buf, out_end=1, in_start=1, in_end=num + 1, stop=False) return self._buf[1:num + 1] def _write_register(self, addr, data=None): self._buf[0] = addr end = 1 if data: self._buf[1] = data end = 2 with self.i2c_device as i2c: i2c.write(self._buf, end=end)
class LSM303_Accel: #pylint:disable=too-many-instance-attributes """Driver for the LSM303's accelerometer.""" # Class-level buffer for reading and writing data with the sensor. # This reduces memory allocations but means the code is not re-entrant or # thread safe! _chip_id = UnaryStruct(_REG_ACCEL_WHO_AM_I, "B") _int2_int1_enable = RWBit(_REG_ACCEL_CTRL_REG6_A, 6) _int2_int2_enable = RWBit(_REG_ACCEL_CTRL_REG6_A, 5) _int1_latching = RWBit(_REG_ACCEL_CTRL_REG5_A, 3) _int2_latching = RWBit(_REG_ACCEL_CTRL_REG5_A, 1) _bdu = RWBit(_REG_ACCEL_CTRL_REG4_A, 7) _int2_activity_enable = RWBit(_REG_ACCEL_CTRL_REG6_A, 3) _int_pin_active_low = RWBit(_REG_ACCEL_CTRL_REG6_A, 1) _act_threshold = UnaryStruct(_REG_ACCEL_ACT_THS_A, "B") _act_duration = UnaryStruct(_REG_ACCEL_ACT_DUR_A, "B") """ .. code-block:: python import board i2c = board.I2C() import adafruit_lsm303_accel accel = adafruit_lsm303_accel.LSM303_Accel(i2c) accel._act_threshold = 20 accel._act_duration = 1 accel._int2_activity_enable = True # toggle pins, defaults to False accel._int_pin_active_low = True """ _data_rate = RWBits(4, _REG_ACCEL_CTRL_REG1_A, 4) _enable_xyz = RWBits(3, _REG_ACCEL_CTRL_REG1_A, 0) _raw_accel_data = StructArray(_REG_ACCEL_OUT_X_L_A, "<h", 3) _low_power = RWBit(_REG_ACCEL_CTRL_REG1_A, 3) _high_resolution = RWBit(_REG_ACCEL_CTRL_REG4_A, 3) _range = RWBits(2, _REG_ACCEL_CTRL_REG4_A, 4) _int1_src = UnaryStruct(_REG_ACCEL_INT1_SOURCE_A, "B") _tap_src = UnaryStruct(_REG_ACCEL_CLICK_SRC_A, "B") _tap_interrupt_enable = RWBit(_REG_ACCEL_CTRL_REG3_A, 7, 1) _tap_config = UnaryStruct(_REG_ACCEL_CLICK_CFG_A, "B") _tap_interrupt_active = ROBit(_REG_ACCEL_CLICK_SRC_A, 6, 1) _tap_threshold = UnaryStruct(_REG_ACCEL_CLICK_THS_A, "B") _tap_time_limit = UnaryStruct(_REG_ACCEL_TIME_LIMIT_A, "B") _tap_time_latency = UnaryStruct(_REG_ACCEL_TIME_LATENCY_A, "B") _tap_time_window = UnaryStruct(_REG_ACCEL_TIME_WINDOW_A, "B") _BUFFER = bytearray(6) def __init__(self, i2c): self._accel_device = I2CDevice(i2c, _ADDRESS_ACCEL) self.i2c_device = self._accel_device self._data_rate = 2 self._enable_xyz = 0b111 self._int1_latching = True self._int2_latching = True self._bdu = True # self._write_register_byte(_REG_CTRL5, 0x80) # time.sleep(0.01) # takes 5ms self._cached_mode = 0 self._cached_range = 0 def set_tap(self, tap, threshold, *, time_limit=10, time_latency=20, time_window=255, tap_cfg=None): """ The tap detection parameters. :param int tap: 0 to disable tap detection, 1 to detect only single taps, and 2 to detect \ only double taps. :param int threshold: A threshold for the tap detection. The higher the value the less\ sensitive the detection. This changes based on the accelerometer range. Good values\ are 5-10 for 16G, 10-20 for 8G, 20-40 for 4G, and 40-80 for 2G. :param int time_limit: TIME_LIMIT register value (default 10). :param int time_latency: TIME_LATENCY register value (default 20). :param int time_window: TIME_WINDOW register value (default 255). :param int click_cfg: CLICK_CFG register value. """ if (tap < 0 or tap > 2) and tap_cfg is None: raise ValueError( 'Tap must be 0 (disabled), 1 (single tap), or 2 (double tap)!') if threshold > 127 or threshold < 0: raise ValueError('Threshold out of range (0-127)') if tap == 0 and tap_cfg is None: # Disable click interrupt. self._tap_interrupt_enable = False self._tap_config = 0 return self._tap_interrupt_enable = True if tap_cfg is None: if tap == 1: tap_cfg = 0x15 # Turn on all axes & singletap. if tap == 2: tap_cfg = 0x2A # Turn on all axes & doubletap. # Or, if a custom tap configuration register value specified, use it. self._tap_config = tap_cfg self._tap_threshold = threshold # why and? self._tap_time_limit = time_limit self._tap_time_latency = time_latency self._tap_time_window = time_window @property def tapped(self): """ True if a tap was detected recently. Whether its a single tap or double tap is determined by the tap param on ``set_tap``. ``tapped`` may be True over multiple reads even if only a single tap or single double tap occurred. """ tap_src = self._tap_src return tap_src & 0b1000000 > 0 @property def _raw_acceleration(self): self._read_bytes(self._accel_device, _REG_ACCEL_OUT_X_L_A | 0x80, 6, self._BUFFER) return struct.unpack_from('<hhh', self._BUFFER[0:6]) @property def acceleration(self): """The measured accelerometer sensor values. A 3-tuple of X, Y, Z axis values in m/s^2 squared that are signed floats. """ raw_accel_data = self._raw_acceleration x = self._scale_data(raw_accel_data[0]) y = self._scale_data(raw_accel_data[1]) z = self._scale_data(raw_accel_data[2]) return (x, y, z) def _scale_data(self, raw_measurement): lsb, shift = self._lsb_shift() return (raw_measurement >> shift) * lsb * _SMOLLER_GRAVITY def _lsb_shift(self): #pylint:disable=too-many-branches # the bit depth of the data depends on the mode, and the lsb value # depends on the mode and range lsb = -1 # the default, normal mode @ 2G if self._cached_mode is Mode.MODE_HIGH_RESOLUTION: # 12-bit shift = 4 if self._cached_range is Range.RANGE_2G: lsb = 0.98 elif self._cached_range is Range.RANGE_4G: lsb = 1.95 elif self._cached_range is Range.RANGE_8G: lsb = 3.9 elif self._cached_range is Range.RANGE_16G: lsb = 11.72 elif self._cached_mode is Mode.MODE_NORMAL: # 10-bit shift = 6 if self._cached_range is Range.RANGE_2G: lsb = 3.9 elif self._cached_range is Range.RANGE_4G: lsb = 7.82 elif self._cached_range is Range.RANGE_8G: lsb = 15.63 elif self._cached_range is Range.RANGE_16G: lsb = 46.9 elif self._cached_mode is Mode.MODE_LOW_POWER: # 8-bit shift = 8 if self._cached_range is Range.RANGE_2G: lsb = 15.63 elif self._cached_range is Range.RANGE_4G: lsb = 31.26 elif self._cached_range is Range.RANGE_8G: lsb = 62.52 elif self._cached_range is Range.RANGE_16G: lsb = 187.58 if lsb is -1: raise AttributeError( "'impossible' range or mode detected: range: %d mode: %d" % (self._cached_range, self._cached_mode)) return (lsb, shift) @property def data_rate(self): """Select the rate at which the sensor takes measurements. Must be a `Rate`""" return self._data_rate @data_rate.setter def data_rate(self, value): if value < 0 or value > 9: raise AttributeError("data_rate must be a `Rate`") self._data_rate = value @property def range(self): """Adjusts the range of values that the sensor can measure, from +- 2G to +-16G Note that larger ranges will be less accurate. Must be a `Range`""" return self._cached_range @range.setter def range(self, value): if value < 0 or value > 3: raise AttributeError("range must be a `Range`") self._range = value self._cached_range = value @property def mode(self): """Sets the power mode of the sensor. The mode must be a `Mode`. Note that the mode and range will both affect the accuracy of the sensor""" return self._cached_mode @mode.setter def mode(self, value): if value < 0 or value > 2: raise AttributeError("mode must be a `Mode`") self._high_resolution = value & 0b01 self._low_power = (value & 0b10) >> 1 self._cached_mode = value def _read_u8(self, device, address): with device as i2c: self._BUFFER[0] = address & 0xFF i2c.write_then_readinto(self._BUFFER, self._BUFFER, out_end=1, in_end=1) return self._BUFFER[0] def _write_u8(self, device, address, val): with device as i2c: self._BUFFER[0] = address & 0xFF self._BUFFER[1] = val & 0xFF i2c.write(self._BUFFER, end=2) @staticmethod def _read_bytes(device, address, count, buf): with device as i2c: buf[0] = address & 0xFF i2c.write_then_readinto(buf, buf, out_end=1, in_end=count)
class MMC5603NJ: """ Driver for the MMC5603NJ magnetometer :param busio.I2C i2c_bus: The I2C bus the mag is connected to """ _BUFFER = bytearray(6) # _device_id = ROUnaryStruct(Product_ID, "B") # B = unsigned char/integer _device_id = ROBits(8, Product_ID, 0) # 0b 0001 0000 # _raw_x0 = ROUnaryStruct(Xout0, "<h") # < = little-endian, h = short/integer # _raw_y0 = ROUnaryStruct(Yout0, "<h") # _raw_z0 = ROUnaryStruct(Zout0, "<h") _raw_x0 = ROBits(8, Xout0, 0) _raw_x1 = ROBits(8, Xout1, 0) _raw_x2 = ROBits(4, Xout2, 4) _raw_y0 = ROBits(8, Yout0, 0) _raw_y1 = ROBits(8, Yout1, 0) _raw_y2 = ROBits(4, Yout2, 4) _raw_z0 = ROBits(8, Zout0, 0) _raw_z1 = ROBits(8, Zout1, 0) _raw_z2 = ROBits(4, Zout2, 4) _raw_t = ROBits(8, Tout, 0) _status = ROUnaryStruct(Status1, "B") _sat_sensor = ROBit(Status1, 5) _meas_m_done = ROBit(Status1, 6) _meas_t_done = ROBit(Status1, 7) _odr = UnaryStruct(ODR, "B") _contr_reg_0 = RWBits(8, Internal_control_0, 0) _do_reset = RWBit(Internal_control_0, 4) _auto_sr_en = RWBit(Internal_control_0, 5) _cmm_freq_en = RWBit(Internal_control_0, 7) _bw = RWBits(2, Internal_control_1, 0) _cmm_en = RWBit(Internal_control_2, 4) def __init__(self, i2c): self.i2c_device = I2CDevice(i2c, _ADDRESS_MAG) if self._device_id != 0x10: raise AttributeError("Cannot find an MMC5603NJ") def reset(self): """Do RESET to 'condition the AMR sensors for optimum performance """ self._do_reset = 1 sleep(3) @property def magnetic_cmm(self): """The processed magnetometer sensor values. Print out measurements periodically :param float delay: sleep delay in seconds """ delay = 3.0 self.reset() # set bw? self._bw = 0b00 # set automatic set/reset self._auto_sr_en = 1 # write the desired number into output data rate self._odr = 1 # is this 75 Hz? with auto set/reset # set cmm_freq_en to 1 to calculate target number for the counter self._cmm_freq_en = 1 # set cmm_en to 1 to start continous mode, internal counter starts self._cmm_en = 1 sleep(1) # need to add a way to stop continuous measurements # press a button? while True: magx, magy, magz = self.magnetic_get_processed_mag_data() print("X:{0:7.2f}, Y:{1:7.2f}, Z:{2:7.2f} ".format(magx, magy, magz)) sleep(delay) return() @property def magnetic_single(self): """The processed magnetometer sensor values. A tuple of X, Y, Z axis values. """ # TM_M and Auto_SR_en high self._contr_reg_0 = 0b00100001 # check Meas_M_Done bit while self._meas_m_done != 1: pass return self.magnetic_get_processed_mag_data() def magnetic_get_processed_mag_data(self): """ Returns ------- TYPE DESCRIPTION. TYPE DESCRIPTION. TYPE DESCRIPTION. """ # Need to check math on this # 20 bit xval = (self._raw_x0<<12) | (self._raw_x1<<4) | (self._raw_x2) yval = (self._raw_y0<<12) | (self._raw_y1<<4) | (self._raw_y2) zval = (self._raw_z0<<12) | (self._raw_z1<<4) | (self._raw_z2) mask = 524288 xval = -(xval & mask) + (xval & ~mask) yval = -(yval & mask) + (yval & ~mask) zval = -(zval & mask) + (zval & ~mask) return ( xval*0.0000625, yval*0.0000625, zval*0.0000625 ) @property def magnetic_raw(self): """The raw magnetometer sensor values. """ # TM_M and Auto_SR_en high self._contr_reg_0 = 0b00100001 # check Meas_M_Done bit while self._meas_m_done != 1: pass return self.magnetic_get_raw_mag_data() def magnetic_get_raw_mag_data(self): """ Returns ------- TYPE DESCRIPTION. TYPE DESCRIPTION. TYPE DESCRIPTION. TYPE DESCRIPTION. TYPE DESCRIPTION. TYPE DESCRIPTION. TYPE DESCRIPTION. TYPE DESCRIPTION. TYPE DESCRIPTION. """ return ( self._raw_x0, self._raw_x1, self._raw_x2, self._raw_y0, self._raw_y1, self._raw_y2, self._raw_z0, self._raw_z1, self._raw_z2, ) @property def temperature(self): """The processed temperature output value in deg C. (Unsigned format, range is -75 to 125 deg C, 00000000 stands for -75 deg C) """ self._contr_reg_0 = 0b00100010 while self._meas_t_done != 1: pass return (self._raw_t * (200/255)) - 75
class 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) # 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.""" # INT_FLAG - PS interrupt flag proximity_high_interrupt = ROBit(0x0B, 9, register_width=2) """If interrupt is set to ``PS_INT_CLOSE`` or ``PS_INT_CLOSE_AWAY``, trigger event when proximity rises above high threshold interrupt.""" proximity_low_interrupt = ROBit(0x0B, 8, register_width=2) """If interrupt is set to ``PS_INT_AWAY`` or ``PS_INT_CLOSE_AWAY``, trigger event when proximity drops below low threshold.""" 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.""" # ALS_Data_LM - ALS output data light = ROUnaryStruct(0x09, "<H") """Ambient light data. This example prints the ambient light data. Cover the sensor to see the values change. .. code-block:: python import time import board import busio import adafruit_vcnl4040 i2c = busio.I2C(board.SCL, board.SDA) sensor = adafruit_vcnl4040.VCNL4040(i2c) while True: print("Ambient light:", sensor.light) time.sleep(0.1) """ # 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) """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) time.sleep(0.1) """ 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 light_high_interrupt = ROBit(0x0B, 12, register_width=2) """High interrupt event. Triggered when ambient light value exceeds high threshold.""" light_low_interrupt = ROBit(0x0B, 13, register_width=2) """Low interrupt event. Triggered when ambient light value drops below low threshold.""" # White_Data_LM - White output data white = ROUnaryStruct(0x0A, "<H") """White light data. This example prints the white light data. Cover the sensor to see the values change. .. code-block:: python import time import board import busio import adafruit_vcnl4040 i2c = busio.I2C(board.SCL, board.SDA) sensor = adafruit_vcnl4040.VCNL4040(i2c) while True: print("White light:", sensor.white) time.sleep(0.1) """ # 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_enable = RWBit(0x04, 15, register_width=2) """White data enable. ``True`` when enabled.""" def __init__(self, i2c, address=0x60, interrupt_pin=None): self.i2c_device = i2cdevice.I2CDevice(i2c, address) if self._device_id != 0x186: raise RuntimeError("Failed to find VCNL4040 - check wiring!") self._interrupt_pin = interrupt_pin if self._interrupt_pin: self._interrupt_pin.switch_to_input(pull=digitalio.Pull.UP) self.proximity_shutdown = False self.light_shutdown = False self.white_enable = True
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 ADT7410: """Interface to the Analog Devices ADT7410 temperature sensor. :param ~busio.I2C i2c_bus: The I2C bus the ADT7410 is connected to. :param int address: The I2C device address. Default is :const:`0x48` **Quickstart: Importing and using the ADT7410 temperature sensor** Here is an example of using the :class:`ADT7410` class. First you will need to import the libraries to use the sensor .. code-block:: python import board import adafruit_adt7410 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 adt = adafruit_adt7410.ADT7410(i2c_bus, address=0x48) Now you have access to the temperature using :attr:`temperature`. .. code-block:: python temperature = adt.temperature """ # many modes can be set with register objects for simplicity ready = ROBit(_ADT7410_STATUS, 7) ctpin_polarity = RWBit(_ADT7410_CONFIG, 2) intpin_polarity = RWBit(_ADT7410_CONFIG, 3) comparator_mode = RWBit(_ADT7410_CONFIG, 4) high_resolution = RWBit(_ADT7410_CONFIG, 7) def __init__(self, i2c_bus, address=0x48): self.i2c_device = I2CDevice(i2c_bus, address) self._buf = bytearray(3) # Verify the manufacturer and device ids to ensure we are talking to # what we expect. _id = (self._read_register(_ADT7410_ID)[0]) & 0xF8 if _id != 0xC8: raise ValueError("Unable to find ADT7410 at i2c address " + str(hex(address))) self.reset() @property def temperature(self): """The temperature in Celsius""" temp = self._read_register(_ADT7410_TEMPMSB, 2) return struct.unpack(">h", temp)[0] / 128 @property def status(self): """The ADT7410 status registers current value""" return self._read_register(_ADT7410_STATUS)[0] @property def configuration(self): """The ADT7410 configuration register""" return self._read_register(_ADT7410_CONFIG)[0] @configuration.setter def configuration(self, val): return self._write_register(_ADT7410_CONFIG, val) def reset(self): """Perform a software reset""" self._write_register(_ADT7410_SWRST) time.sleep(0.5) def _read_register(self, addr, num=1): self._buf[0] = addr with self.i2c_device as i2c: i2c.write_then_readinto(self._buf, self._buf, out_end=1, in_start=1, in_end=num + 1) return self._buf[1:num + 1] def _write_register(self, addr, data=None): self._buf[0] = addr end = 1 if data: self._buf[1] = data end = 2 with self.i2c_device as i2c: i2c.write(self._buf, end=end)
class LTR390: # pylint:disable=too-many-instance-attributes """Class to use the LTR390 Ambient Light and UV sensor :param ~busio.I2C i2c: The I2C bus the LTR390 is connected to. :param int address: The I2C device address. Defaults to :const:`0x53` **Quickstart: Importing and using the LTR390** Here is an example of using the :class:`LTR390` class. First you will need to import the libraries to use the sensor .. code-block:: python import board import adafruit_ltr390 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 ltr = adafruit_ltr390.LTR390(i2c) Now you have access to the :attr:`lux` and :attr:`light` attributes .. code-block:: python lux = ltr.lux light = ltr.light """ _reset_bit = RWBit(_CTRL, 4) _enable_bit = RWBit(_CTRL, 1) _mode_bit = RWBit(_CTRL, 3) _int_enable_bit = RWBit(_INT_CFG, 2) _gain_bits = RWBits(3, _GAIN, 0) _resolution_bits = RWBits(3, _MEAS_RATE, 4) _measurement_delay_bits = RWBits(3, _MEAS_RATE, 0) _rate_bits = RWBits(3, _MEAS_RATE, 4) _int_src_bits = RWBits(2, _INT_CFG, 4) _int_persistance_bits = RWBits(4, _INT_PST, 4) _id_reg = ROUnaryStruct(_PART_ID, "<B") _uvs_data_reg = UnalignedStruct(_UVSDATA, "<I", 24, 3) _als_data_reg = UnalignedStruct(_ALSDATA, "<I", 24, 3) data_ready = ROBit(_STATUS, 3) """Ask the sensor if new data is available""" high_threshold = UnalignedStruct(_THRESH_UP, "<I", 24, 3) """When the measured value is more than the low_threshold, the sensor will raise an alert""" low_threshold = UnalignedStruct(_THRESH_LOW, "<I", 24, 3) """When the measured value is less than the low_threshold, the sensor will raise an alert""" threshold_passed = ROBit(_STATUS, 4) """The status of any configured alert. If True, a threshold has been passed. Once read, this property will be False until it is updated in the next measurement cycle""" def __init__(self, i2c, address=_DEFAULT_I2C_ADDR): self.i2c_device = i2c_device.I2CDevice(i2c, address) if self._id_reg != 0xB2: raise RuntimeError("Unable to find LTR390; check your wiring") self._mode_cache = None self.initialize() def initialize(self): """Reset the sensor to it's initial unconfigured state and configure it with sensible defaults so it can be used""" self._reset() self._enable_bit = True if not self._enable_bit: raise RuntimeError("Unable to enable sensor") self._mode = UV self.gain = Gain.GAIN_3X # pylint:disable=no-member self.resolution = Resolution.RESOLUTION_16BIT # pylint:disable=no-member self._window_factor = 1 # default window transmission factor # ltr.setThresholds(100, 1000); # self.low_threshold = 100 # self.high_threshold = 1000 # ltr.configInterrupt(true, LTR390_MODE_UVS); def _reset(self): try: self._reset_bit = True except OSError: # The write to the reset bit will fail because it seems to not ACK before it resets pass sleep(0.1) # check that reset is complete w/ the bit unset if self._reset_bit: raise RuntimeError("Unable to reset sensor") @property def _mode(self): return self._mode_bit @_mode.setter def _mode(self, value): if not value in [ALS, UV]: raise AttributeError("Mode must be ALS or UV") if self._mode_cache != value: self._mode_bit = value self._mode_cache = value sleep(0.030) # something is wrong here; I had to add a sleep to the loop to get both to update correctly @property def uvs(self): """The calculated UV value""" self._mode = UV while not self.data_ready: sleep(0.010) return self._uvs_data_reg @property def light(self): """The currently measured ambient light level""" self._mode = ALS while not self.data_ready: sleep(0.010) return self._als_data_reg @property def gain(self): """The amount of gain the raw measurements are multiplied by""" return self._gain_bits @gain.setter def gain(self, value): if not Gain.is_valid(value): raise AttributeError("gain must be a Gain") self._gain_bits = value @property def resolution(self): """Set the precision of the internal ADC used to read the light measurements""" return self._resolution_bits @resolution.setter def resolution(self, value): if not Resolution.is_valid(value): raise AttributeError("resolution must be a Resolution") self._resolution_bits = value def enable_alerts(self, enable, source, persistance): """The configuration of alerts raised by the sensor :param enable: Whether the interrupt output is enabled :param source: Whether to use the ALS or UVS data register to compare :param persistance: The number of consecutive out-of-range readings before """ self._int_enable_bit = enable if not enable: return if source == ALS: self._int_src_bits = 1 elif source == UV: self._int_src_bits = 3 else: raise AttributeError("interrupt source must be UV or ALS") self._int_persistance_bits = persistance @property def measurement_delay(self): """The delay between measurements. This can be used to set the measurement rate which affects the sensor power usage.""" return self._measurement_delay_bits @measurement_delay.setter def measurement_delay(self, value): if not MeasurementDelay.is_valid(value): raise AttributeError("measurement_delay must be a MeasurementDelay") self._measurement_delay_bits = value @property def uvi(self): """Read UV count and return calculated UV Index (UVI) value based upon the rated sensitivity of 1 UVI per 2300 counts at 18X gain factor and 20-bit resolution.""" return ( self.uvs / ( (Gain.factor[self.gain] / 18) * (2 ** Resolution.factor[self.resolution]) / (2 ** 20) * 2300 ) * self._window_factor ) @property def lux(self): """Read light level and return calculated Lux value.""" return ( (self.light * 0.6) / (Gain.factor[self.gain] * Resolution.integration[self.resolution]) ) * self._window_factor @property def window_factor(self): """Window transmission factor (Wfac) for UVI and Lux calculations. A factor of 1 (default) represents no window or clear glass; > 1 for a tinted window. Factor of > 1 requires an empirical calibration with a reference light source.""" return self._window_factor @window_factor.setter def window_factor(self, factor=1): if factor < 1: raise ValueError( "window transmission factor must be a value of 1.0 or greater" ) self._window_factor = factor
class DPS310: #pylint: disable=too-many-instance-attributes """Library for the DPS310 Precision Barometric Pressure Sensor. :param ~busio.I2C i2c_bus: The I2C bus the DPS310 is connected to. :param address: The I2C slave address of the sensor """ # Register definitions _device_id = ROUnaryStruct(_DPS310_PRODREVID, ">B") _reset_register = UnaryStruct(_DPS310_RESET, ">B") _mode_bits = RWBits(3, _DPS310_MEASCFG, 0) _pressure_ratebits = RWBits(3, _DPS310_PRSCFG, 4) _pressure_osbits = RWBits(4, _DPS310_PRSCFG, 0) _temp_ratebits = RWBits(3, _DPS310_TMPCFG, 4) _temp_osbits = RWBits(4, _DPS310_TMPCFG, 0) _temp_measurement_src_bit = RWBit(_DPS310_TMPCFG, 7) _pressure_shiftbit = RWBit(_DPS310_CFGREG, 2) _temp_shiftbit = RWBit(_DPS310_CFGREG, 3) _coefficients_ready = RWBit(_DPS310_MEASCFG, 7) _sensor_ready = RWBit(_DPS310_MEASCFG, 6) _temp_ready = RWBit(_DPS310_MEASCFG, 5) _pressure_ready = RWBit(_DPS310_MEASCFG, 4) _raw_pressure = ROBits(24, _DPS310_PRSB2, 0, 3, lsb_first=False) _raw_temperature = ROBits(24, _DPS310_TMPB2, 0, 3, lsb_first=False) _calib_coeff_temp_src_bit = ROBit(_DPS310_TMPCOEFSRCE, 7) def __init__(self, i2c_bus, address=_DPS310_DEFAULT_ADDRESS): self.i2c_device = i2c_device.I2CDevice(i2c_bus, address) if self._device_id != _DPS310_DEVICE_ID: raise RuntimeError("Failed to find DPS310 - check your wiring!") self._pressure_scale = None self._temp_scale = None self._c0 = None self._c1 = None self._c00 = None self._c00 = None self._c10 = None self._c10 = None self._c01 = None self._c11 = None self._c20 = None self._c21 = None self._c30 = None self._oversample_scalefactor = (524288, 1572864, 3670016, 7864320, 253952, 516096, 1040384, 2088960) self.initialize() def initialize(self): """Reset the sensor to the default state""" self._reset() self._read_calibration() # make sure we're using the temperature source used for calibration self._temp_measurement_src_bit = self._calib_coeff_temp_src_bit self.pressure_rate = Rate.RATE_64_HZ self.pressure_oversample_count = SampleCount.COUNT_64 self.temperature_rate = Rate.RATE_64_HZ self.temperature_oversample_count = SampleCount.COUNT_64 self.mode = Mode.CONT_PRESTEMP # wait until we have at least one good measurement while (self._temp_ready is False) or (self._pressure_ready is False): sleep(0.001) def _reset(self): """Perform a soft-reset on the sensor""" self._reset_register = 0x89 # wait for hardware reset to finish sleep(0.010) while not self._sensor_ready: sleep(0.001) @property def pressure(self): """Returns the current pressure reading in kPA""" temp_reading = self._raw_temperature raw_temperature = self._twos_complement(temp_reading, 24) pressure_reading = self._raw_pressure raw_pressure = self._twos_complement(pressure_reading, 24) _scaled_rawtemp = raw_temperature / self._temp_scale _temperature = _scaled_rawtemp * self._c1 + self._c0 / 2.0 p_red = raw_pressure / self._pressure_scale pres_calc = (self._c00 + p_red * (self._c10 + p_red * (self._c20 + p_red * self._c30)) + _scaled_rawtemp * (self._c01 + p_red * (self._c11 + p_red * self._c21))) final_pressure = pres_calc / 100 return final_pressure @property def temperature(self): """The current temperature reading in degrees C""" _scaled_rawtemp = self._raw_temperature / self._temp_scale _temperature = _scaled_rawtemp * self._c1 + self._c0 / 2.0 return _temperature @property def temperature_ready(self): """Returns true if there is a temperature reading ready""" return self._temp_ready @property def pressure_ready(self): """Returns true if pressure readings are ready""" return self._pressure_ready @property def mode(self): """The measurement mode. Must be a `Mode`. See the `Mode` documentation for details""" return self._mode_bits @mode.setter def mode(self, value): if not Mode.is_valid(value): raise AttributeError("mode must be an `Mode`") self._mode_bits = value @property def pressure_rate(self): """Configure the pressure measurement rate. Must be a `Rate`""" return self._pressure_ratebits @pressure_rate.setter def pressure_rate(self, value): if not Rate.is_valid(value): raise AttributeError("pressure_rate must be a Rate") self._pressure_ratebits = value @property def pressure_oversample_count(self): """The number of samples taken per pressure measurement. Must be a `SampleCount`""" return self._pressure_osbits @pressure_oversample_count.setter def pressure_oversample_count(self, value): if not SampleCount.is_valid(value): raise AttributeError("pressure_oversample_count must be a SampleCount") self._pressure_osbits = value self._pressure_shiftbit = (value > SampleCount.COUNT_8) self._pressure_scale = self._oversample_scalefactor[value] @property def temperature_rate(self): """Configure the temperature measurement rate. Must be a `Rate`""" return self._temp_ratebits @temperature_rate.setter def temperature_rate(self, value): if not Rate.is_valid(value): raise AttributeError("temperature_rate must be a Rate") self._temp_ratebits = value @property def temperature_oversample_count(self): """The number of samples taken per temperature measurement. Must be a `SampleCount`""" return self._temp_osbits @temperature_oversample_count.setter def temperature_oversample_count(self, value): if not SampleCount.is_valid(value): raise AttributeError("temperature_oversample_count must be a SampleCount") self._temp_osbits = value self._temp_scale = self._oversample_scalefactor[value] self._temp_shiftbit = (value > SampleCount.COUNT_8) @staticmethod def _twos_complement(val, bits): if val & (1 << (bits - 1)): val -= (1 << bits) return val def _read_calibration(self): while not self._coefficients_ready: sleep(0.001) buffer = bytearray(19) coeffs = [None]*18 for offset in range(18): buffer = bytearray(2) buffer[0] = 0x10 + offset with self.i2c_device as i2c: i2c.write_then_readinto(buffer, buffer, out_end=1, in_start=1) coeffs[offset] = buffer[1] self._c0 = (coeffs[0] << 4) | ((coeffs[1] >> 4) & 0x0F) self._c0 = self._twos_complement(self._c0, 12) self._c1 = self._twos_complement(((coeffs[1] & 0x0F) << 8) | coeffs[2], 12) self._c00 = (coeffs[3] << 12) | (coeffs[4] << 4) | ((coeffs[5] >> 4) & 0x0F) self._c00 = self._twos_complement(self._c00, 20) self._c10 = ((coeffs[5] & 0x0F) << 16) | (coeffs[6] << 8) |coeffs[7] self._c10 = self._twos_complement(self._c10, 20) self._c01 = self._twos_complement((coeffs[8] << 8) | coeffs[9], 16) self._c11 = self._twos_complement((coeffs[10] << 8) | coeffs[11], 16) self._c20 = self._twos_complement((coeffs[12] << 8) | coeffs[13], 16) self._c21 = self._twos_complement((coeffs[14] << 8) | coeffs[15], 16) self._c30 = self._twos_complement((coeffs[16] << 8) | coeffs[17], 16)
class VEML7700: """Driver for the VEML7700 ambient light sensor. :param busio.I2C i2c_bus: The I2C bus the VEML7700 is connected to. """ # Ambient light sensor gain settings ALS_GAIN_1 = const(0x0) ALS_GAIN_2 = const(0x1) ALS_GAIN_1_8 = const(0x2) ALS_GAIN_1_4 = const(0x3) # Ambient light integration time settings ALS_25MS = const(0xC) ALS_50MS = const(0x8) ALS_100MS = const(0x0) ALS_200MS = const(0x1) ALS_400MS = const(0x2) ALS_800MS = const(0x3) # Gain value integers gain_values = { ALS_GAIN_2: 2, ALS_GAIN_1: 1, ALS_GAIN_1_4: 0.25, ALS_GAIN_1_8: 0.125, } # Integration time value integers integration_time_values = { ALS_25MS: 25, ALS_50MS: 50, ALS_100MS: 100, ALS_200MS: 200, ALS_400MS: 400, ALS_800MS: 800, } # ALS - Ambient light sensor high resolution output data light = ROUnaryStruct(0x04, "<H") """Ambient light data. This example prints the ambient light data. Cover the sensor to see the values change. .. code-block:: python import time import board import busio import adafruit_veml7700 i2c = busio.I2C(board.SCL, board.SDA) veml7700 = adafruit_veml7700.VEML7700(i2c) while True: print("Ambient light:", veml7700.light) time.sleep(0.1) """ # WHITE - White channel output data white = ROUnaryStruct(0x05, "<H") """White light data. This example prints the white light data. Cover the sensor to see the values change. .. code-block:: python import time import board import busio import adafruit_veml7700 i2c = busio.I2C(board.SCL, board.SDA) veml7700 = adafruit_veml7700.VEML7700(i2c) while True: print("White light:", veml7700.white) time.sleep(0.1) """ # ALS_CONF_0 - ALS gain, integration time, interrupt and shutdown. light_shutdown = RWBit(0x00, 0, register_width=2) """Ambient light sensor shutdown. When ``True``, ambient light sensor is disabled.""" light_interrupt = RWBit(0x00, 1, register_width=2) """Enable interrupt. ``True`` to enable, ``False`` to disable.""" light_gain = RWBits(2, 0x00, 11, register_width=2) """Ambient light gain setting. Gain settings are 2, 1, 1/4 and 1/8. Settings options are: ALS_GAIN_2, ALS_GAIN_1, ALS_GAIN_1_4, ALS_GAIN_1_8. This example sets the ambient light gain to 2 and prints the ambient light sensor data. .. code-block:: python import time import board import busio import adafruit_veml7700 i2c = busio.I2C(board.SCL, board.SDA) veml7700 = adafruit_vcnl4040.VCNL4040(i2c) veml7700.light_gain = veml7700.ALS_GAIN_2 while True: print("Ambient light:", veml7700.light) time.sleep(0.1) """ light_integration_time = RWBits(4, 0x00, 6, register_width=2) """Ambient light integration time setting. Longer time has higher sensitivity. Can be: ALS_25MS, ALS_50MS, ALS_100MS, ALS_200MS, ALS_400MS, ALS_800MS. This example sets the ambient light integration time to 400ms and prints the ambient light sensor data. .. code-block:: python import time import board import busio import adafruit_veml7700 i2c = busio.I2C(board.SCL, board.SDA) veml7700 = adafruit_vcnl4040.VCNL4040(i2c) veml7700.light_integration_time = veml7700.ALS_400MS while True: print("Ambient light:", veml7700.light) time.sleep(0.1) """ # ALS_WH - ALS high threshold window setting light_high_threshold = UnaryStruct(0x01, "<H") """Ambient light sensor interrupt high threshold setting.""" # ALS_WL - ALS low threshold window setting light_low_threshold = UnaryStruct(0x02, "<H") """Ambient light sensor interrupt low threshold setting.""" # ALS_INT - ALS INT trigger event light_interrupt_high = ROBit(0x06, 14, register_width=2) """Ambient light high threshold interrupt flag. Triggered when high threshold exceeded.""" light_interrupt_low = ROBit(0x06, 15, register_width=2) """Ambient light low threshold interrupt flag. Triggered when low threshold exceeded.""" def __init__(self, i2c_bus, address=0x10): self.i2c_device = i2cdevice.I2CDevice(i2c_bus, address) self.light_shutdown = False # Enable the ambient light sensor def integration_time_value(self): """Integration time value in integer form. Used for calculating ``resolution``.""" integration_time = self.light_integration_time return self.integration_time_values[integration_time] def gain_value(self): """Gain value in integer form. Used for calculating ``resolution``.""" gain = self.light_gain return self.gain_values[gain] def resolution(self): """Calculate the ``resolution`` necessary to calculate lux. Based on integration time and gain settings.""" resolution_at_max = 0.0036 gain_max = 2 integration_time_max = 800 if (self.gain_value() == gain_max and self.integration_time_value() == integration_time_max): return resolution_at_max return (resolution_at_max * (integration_time_max / self.integration_time_value()) * (gain_max / self.gain_value())) @property def lux(self): """Light value in lux. This example prints the light data in lux. Cover the sensor to see the values change. .. code-block:: python import time import board import busio import adafruit_veml7700 i2c = busio.I2C(board.SCL, board.SDA) veml7700 = adafruit_veml7700.VEML7700(i2c) while True: print("Lux:", veml7700.lux) time.sleep(0.1) """ return self.resolution() * self.light
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 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 DPS310: # pylint: disable=too-many-instance-attributes """Library for the DPS310 Precision Barometric Pressure Sensor. :param ~busio.I2C i2c_bus: The I2C bus the DPS310 is connected to. :param address: The I2C slave address of the sensor """ # Register definitions _device_id = ROUnaryStruct(_DPS310_PRODREVID, ">B") _reset_register = UnaryStruct(_DPS310_RESET, ">B") _mode_bits = RWBits(3, _DPS310_MEASCFG, 0) _pressure_ratebits = RWBits(3, _DPS310_PRSCFG, 4) _pressure_osbits = RWBits(4, _DPS310_PRSCFG, 0) _temp_ratebits = RWBits(3, _DPS310_TMPCFG, 4) _temp_osbits = RWBits(4, _DPS310_TMPCFG, 0) _temp_measurement_src_bit = RWBit(_DPS310_TMPCFG, 7) _pressure_shiftbit = RWBit(_DPS310_CFGREG, 2) _temp_shiftbit = RWBit(_DPS310_CFGREG, 3) _coefficients_ready = RWBit(_DPS310_MEASCFG, 7) _sensor_ready = RWBit(_DPS310_MEASCFG, 6) _temp_ready = RWBit(_DPS310_MEASCFG, 5) _pressure_ready = RWBit(_DPS310_MEASCFG, 4) _raw_pressure = ROBits(24, _DPS310_PRSB2, 0, 3, lsb_first=False) _raw_temperature = ROBits(24, _DPS310_TMPB2, 0, 3, lsb_first=False) _calib_coeff_temp_src_bit = ROBit(_DPS310_TMPCOEFSRCE, 7) _reg0e = RWBits(8, 0x0E, 0) _reg0f = RWBits(8, 0x0F, 0) _reg62 = RWBits(8, 0x62, 0) def __init__(self, i2c_bus, address=_DPS310_DEFAULT_ADDRESS): self.i2c_device = i2c_device.I2CDevice(i2c_bus, address) if self._device_id != _DPS310_DEVICE_ID: raise RuntimeError("Failed to find DPS310 - check your wiring!") self._pressure_scale = None self._temp_scale = None self._c0 = None self._c1 = None self._c00 = None self._c00 = None self._c10 = None self._c10 = None self._c01 = None self._c11 = None self._c20 = None self._c21 = None self._c30 = None self._oversample_scalefactor = ( 524288, 1572864, 3670016, 7864320, 253952, 516096, 1040384, 2088960, ) self.sea_level_pressure = 1013.25 """Pressure in hectoPascals at sea level. Used to calibrate `altitude`.""" self.initialize() def initialize(self): """Initialize the sensor to continuous measurement""" self.reset() self.pressure_rate = Rate.RATE_64_HZ self.pressure_oversample_count = SampleCount.COUNT_64 self.temperature_rate = Rate.RATE_64_HZ self.temperature_oversample_count = SampleCount.COUNT_64 self.mode = Mode.CONT_PRESTEMP # wait until we have at least one good measurement self.wait_temperature_ready() self.wait_pressure_ready() # (https://github.com/Infineon/DPS310-Pressure-Sensor#temperature-measurement-issue) # similar to DpsClass::correctTemp(void) from infineon's c++ library def _correct_temp(self): """Correct temperature readings on ICs with a fuse bit problem""" self._reg0e = 0xA5 self._reg0f = 0x96 self._reg62 = 0x02 self._reg0e = 0 self._reg0f = 0 # perform a temperature measurement # the most recent temperature will be saved internally # and used for compensation when calculating pressure _unused = self._raw_temperature def reset(self): """Reset the sensor""" self._reset_register = 0x89 # wait for hardware reset to finish sleep(0.010) while not self._sensor_ready: sleep(0.001) self._correct_temp() self._read_calibration() # make sure we're using the temperature source used for calibration self._temp_measurement_src_bit = self._calib_coeff_temp_src_bit @property def pressure(self): """Returns the current pressure reading in kPA""" temp_reading = self._raw_temperature raw_temperature = self._twos_complement(temp_reading, 24) pressure_reading = self._raw_pressure raw_pressure = self._twos_complement(pressure_reading, 24) _scaled_rawtemp = raw_temperature / self._temp_scale _temperature = _scaled_rawtemp * self._c1 + self._c0 / 2.0 p_red = raw_pressure / self._pressure_scale pres_calc = (self._c00 + p_red * (self._c10 + p_red * (self._c20 + p_red * self._c30)) + _scaled_rawtemp * (self._c01 + p_red * (self._c11 + p_red * self._c21))) final_pressure = pres_calc / 100 return final_pressure @property def altitude(self): """The altitude based on the sea level pressure (`sea_level_pressure`) - which you must enter ahead of time)""" return 44330 * ( 1.0 - math.pow(self.pressure / self.sea_level_pressure, 0.1903)) @property def temperature(self): """The current temperature reading in degrees C""" _scaled_rawtemp = self._raw_temperature / self._temp_scale _temperature = _scaled_rawtemp * self._c1 + self._c0 / 2.0 return _temperature @property def temperature_ready(self): """Returns true if there is a temperature reading ready""" return self._temp_ready def wait_temperature_ready(self): """Wait until a temperature measurement is available. To avoid waiting indefinitely this function raises an error if the sensor isn't configured for temperate measurements, ie. ``Mode.ONE_TEMPERATURE``, ``Mode.CONT_TEMP`` or ``Mode.CONT_PRESTEMP``. See the `Mode` documentation for details. """ if (self._mode_bits == Mode.IDLE or self._mode_bits == Mode.ONE_PRESSURE or self._mode_bits == Mode.CONT_PRESSURE): raise RuntimeError( "Sensor mode is set to idle or pressure measurement,\ can't wait for a temperature measurement") while self._temp_ready is False: sleep(0.001) @property def pressure_ready(self): """Returns true if pressure readings are ready""" return self._pressure_ready def wait_pressure_ready(self): """Wait until a pressure measurement is available To avoid waiting indefinitely this function raises an error if the sensor isn't configured for pressure measurements, ie. ``Mode.ONE_PRESSURE``, ``Mode.CONT_PRESSURE`` or ``Mode.CONT_PRESTEMP`` See the `Mode` documentation for details. """ if (self._mode_bits == Mode.IDLE or self._mode_bits == Mode.ONE_TEMPERATURE or self._mode_bits == Mode.CONT_TEMP): raise RuntimeError( "Sensor mode is set to idle or temperature measurement,\ can't wait for a pressure measurement") while self._pressure_ready is False: sleep(0.001) @property def mode(self): """The measurement mode. Must be a `Mode`. See the `Mode` documentation for details""" return self._mode_bits @mode.setter def mode(self, value): if not Mode.is_valid(value): raise AttributeError("mode must be an `Mode`") self._mode_bits = value @property def pressure_rate(self): """Configure the pressure measurement rate. Must be a `Rate`""" return self._pressure_ratebits @pressure_rate.setter def pressure_rate(self, value): if not Rate.is_valid(value): raise AttributeError("pressure_rate must be a Rate") self._pressure_ratebits = value @property def pressure_oversample_count(self): """The number of samples taken per pressure measurement. Must be a `SampleCount`""" return self._pressure_osbits @pressure_oversample_count.setter def pressure_oversample_count(self, value): if not SampleCount.is_valid(value): raise AttributeError( "pressure_oversample_count must be a SampleCount") self._pressure_osbits = value self._pressure_shiftbit = value > SampleCount.COUNT_8 self._pressure_scale = self._oversample_scalefactor[value] @property def temperature_rate(self): """Configure the temperature measurement rate. Must be a `Rate`""" return self._temp_ratebits @temperature_rate.setter def temperature_rate(self, value): if not Rate.is_valid(value): raise AttributeError("temperature_rate must be a Rate") self._temp_ratebits = value @property def temperature_oversample_count(self): """The number of samples taken per temperature measurement. Must be a `SampleCount`""" return self._temp_osbits @temperature_oversample_count.setter def temperature_oversample_count(self, value): if not SampleCount.is_valid(value): raise AttributeError( "temperature_oversample_count must be a SampleCount") self._temp_osbits = value self._temp_scale = self._oversample_scalefactor[value] self._temp_shiftbit = value > SampleCount.COUNT_8 @staticmethod def _twos_complement(val, bits): if val & (1 << (bits - 1)): val -= 1 << bits return val def _read_calibration(self): while not self._coefficients_ready: sleep(0.001) buffer = bytearray(19) coeffs = [None] * 18 for offset in range(18): buffer = bytearray(2) buffer[0] = 0x10 + offset with self.i2c_device as i2c: i2c.write_then_readinto(buffer, buffer, out_end=1, in_start=1) coeffs[offset] = buffer[1] self._c0 = (coeffs[0] << 4) | ((coeffs[1] >> 4) & 0x0F) self._c0 = self._twos_complement(self._c0, 12) self._c1 = self._twos_complement(((coeffs[1] & 0x0F) << 8) | coeffs[2], 12) self._c00 = (coeffs[3] << 12) | (coeffs[4] << 4) | ( (coeffs[5] >> 4) & 0x0F) self._c00 = self._twos_complement(self._c00, 20) self._c10 = ((coeffs[5] & 0x0F) << 16) | (coeffs[6] << 8) | coeffs[7] self._c10 = self._twos_complement(self._c10, 20) self._c01 = self._twos_complement((coeffs[8] << 8) | coeffs[9], 16) self._c11 = self._twos_complement((coeffs[10] << 8) | coeffs[11], 16) self._c20 = self._twos_complement((coeffs[12] << 8) | coeffs[13], 16) self._c21 = self._twos_complement((coeffs[14] << 8) | coeffs[15], 16) self._c30 = self._twos_complement((coeffs[16] << 8) | coeffs[17], 16)
class TMP117: """Library for the TI TMP117 high-accuracy temperature sensor""" _part_id = ROUnaryStruct(_DEVICE_ID, ">H") _raw_temperature = ROUnaryStruct(_TEMP_RESULT, ">h") _raw_high_limit = UnaryStruct(_T_HIGH_LIMIT, ">h") _raw_low_limit = UnaryStruct(_T_LOW_LIMIT, ">h") _raw_temperature_offset = UnaryStruct(_TEMP_OFFSET, ">h") # these three bits will clear on read in some configurations, so we read them together _alert_status_data_ready = ROBits(3, _CONFIGURATION, 13, 2, False) _eeprom_busy = ROBit(_CONFIGURATION, 12, 2, False) _mode = RWBits(2, _CONFIGURATION, 10, 2, False) _raw_measurement_delay = RWBits(3, _CONFIGURATION, 7, 2, False) _raw_averaged_measurements = RWBits(2, _CONFIGURATION, 5, 2, False) _raw_alert_mode = RWBit(_CONFIGURATION, 4, 2, False) # T/nA bits in the datasheet _int_active_high = RWBit(_CONFIGURATION, 3, 2, False) _data_ready_int_en = RWBit(_CONFIGURATION, 2, 2, False) _soft_reset = RWBit(_CONFIGURATION, 1, 2, False) def __init__(self, i2c_bus, address=_I2C_ADDR): self.i2c_device = i2c_device.I2CDevice(i2c_bus, address) if self._part_id != _DEVICE_ID_VALUE: raise AttributeError("Cannot find a TMP117") # currently set when `alert_status` is read, but not exposed self.reset() self.initialize() def reset(self): """Reset the sensor to its unconfigured power-on state""" self._soft_reset = True def initialize(self): """Configure the sensor with sensible defaults. `initialize` is primarily provided to be called after `reset`, however it can also be used to easily set the sensor to a known configuration""" # Datasheet specifies that reset will finish in 2ms however by default the first # conversion will be averaged 8x and take 1s # TODO: sleep depending on current averaging config self._set_mode_and_wait_for_measurement( _CONTINUOUS_CONVERSION_MODE) # one shot time.sleep(1) @property def temperature(self): """The current measured temperature in degrees celcius""" return self._read_temperature() @property def temperature_offset(self): """User defined temperature offset to be added to measurements from `temperature` .. code-block::python3 # SPDX-FileCopyrightText: 2020 Bryan Siepert, written for Adafruit Industries # # SPDX-License-Identifier: Unlicense import time import board import busio import adafruit_tmp117 i2c = busio.I2C(board.SCL, board.SDA) tmp117 = adafruit_tmp117.TMP117(i2c) print("Temperature without offset: %.2f degrees C" % tmp117.temperature) tmp117.temperature_offset = 10.0 while True: print("Temperature w/ offset: %.2f degrees C" % tmp117.temperature) time.sleep(1) """ return self._raw_temperature_offset * _TMP117_RESOLUTION @temperature_offset.setter def temperature_offset(self, value): if value > 256 or value < -256: raise AttributeError("temperature_offset must be ") scaled_offset = int(value / _TMP117_RESOLUTION) self._raw_temperature_offset = scaled_offset @property def high_limit(self): """The high temperature limit in degrees celcius. When the measured temperature exceeds this value, the `high_alert` attribute of the `alert_status` property will be True. See the documentation for `alert_status` for more information""" return self._raw_high_limit * _TMP117_RESOLUTION @high_limit.setter def high_limit(self, value): if value > 256 or value < -256: raise AttributeError("high_limit must be from 255 to -256") scaled_limit = int(value / _TMP117_RESOLUTION) self._raw_high_limit = scaled_limit @property def low_limit(self): """The low temperature limit in degrees celcius. When the measured temperature goes below this value, the `low_alert` attribute of the `alert_status` property will be True. See the documentation for `alert_status` for more information""" return self._raw_low_limit * _TMP117_RESOLUTION @low_limit.setter def low_limit(self, value): if value > 256 or value < -256: raise AttributeError("low_limit must be from 255 to -256") scaled_limit = int(value / _TMP117_RESOLUTION) self._raw_low_limit = scaled_limit @property def alert_status(self): """The current triggered status of the high and low temperature alerts as a AlertStatus named tuple with attributes for the triggered status of each alert. .. code-block :: python3 import board import busio import adafruit_tmp117 i2c = busio.I2C(board.SCL, board.SDA) tmp117 = adafruit_tmp117.TMP117(i2c) tmp117.high_limit = 25 tmp117.low_limit = 10 print("High limit", tmp117.high_limit) print("Low limit", tmp117.low_limit) # Try changing `alert_mode` to see how it modifies the behavior of the alerts. # tmp117.alert_mode = AlertMode.WINDOW #default # tmp117.alert_mode = AlertMode.HYSTERESIS print("Alert mode:", AlertMode.string[tmp117.alert_mode]) print("") print("") while True: print("Temperature: %.2f degrees C" % tmp117.temperature) alert_status = tmp117.alert_status print("High alert:", alert_status.high_alert) print("Low alert:", alert_status.low_alert) print("") time.sleep(1) """ high_alert, low_alert, *_ = self._read_status() return AlertStatus(high_alert=high_alert, low_alert=low_alert) @property def averaged_measurements(self): """The number of measurements that are taken and averaged before updating the temperature measurement register. A larger number will reduce measurement noise but may also affect the rate at which measurements are updated, depending on the value of `measurement_delay` Note that each averaged measurement takes 15.5ms which means that larger numbers of averaged measurements may make the delay between new reported measurements to exceed the delay set by `measurement_delay` .. code-block::python3 import time import board import busio from adafruit_tmp117 import TMP117, AverageCount i2c = busio.I2C(board.SCL, board.SDA) tmp117 = TMP117(i2c) # uncomment different options below to see how it affects the reported temperature # tmp117.averaged_measurements = AverageCount.AVERAGE_1X # tmp117.averaged_measurements = AverageCount.AVERAGE_8X # tmp117.averaged_measurements = AverageCount.AVERAGE_32X # tmp117.averaged_measurements = AverageCount.AVERAGE_64X print( "Number of averaged samples per measurement:", AverageCount.string[tmp117.averaged_measurements], ) print("") while True: print("Temperature:", tmp117.temperature) time.sleep(0.1) """ return self._raw_averaged_measurements @averaged_measurements.setter def averaged_measurements(self, value): if not AverageCount.is_valid(value): raise AttributeError( "averaged_measurements must be an `AverageCount`") self._raw_averaged_measurements = value @property def measurement_mode(self): """Sets the measurement mode, specifying the behavior of how often measurements are taken. `measurement_mode` must be one of: +----------------------------------------+------------------------------------------------------+ | Mode | Behavior | +========================================+======================================================+ | :py:const:`MeasurementMode.CONTINUOUS` | Measurements are made at the interval determined by | | | | | | `averaged_measurements` and `measurement_delay`. | | | | | | `temperature` returns the most recent measurement | +----------------------------------------+------------------------------------------------------+ | :py:const:`MeasurementMode.ONE_SHOT` | Take a single measurement with the current number of | | | | | | `averaged_measurements` and switch to | | | :py:const:`SHUTDOWN` when | | | | | | finished. | | | | | | | | | `temperature` will return the new measurement until | | | | | | `measurement_mode` is set to :py:const:`CONTINUOUS` | | | or :py:const:`ONE_SHOT` is | | | | | | set again. | +----------------------------------------+------------------------------------------------------+ | :py:const:`MeasurementMode.SHUTDOWN` | The sensor is put into a low power state and no new | | | | | | measurements are taken. | | | | | | `temperature` will return the last measurement until | | | | | | a new `measurement_mode` is selected. | +----------------------------------------+------------------------------------------------------+ """ return self._mode @measurement_mode.setter def measurement_mode(self, value): if not MeasurementMode.is_valid(value): raise AttributeError( "measurement_mode must be a `MeasurementMode` ") self._set_mode_and_wait_for_measurement(value) @property def measurement_delay(self): """The minimum amount of time between measurements in seconds. Must be a `MeasurementDelay`. The specified amount may be exceeded depending on the current setting off `averaged_measurements` which determines the minimum time needed between reported measurements. .. code-block::python3 import time import board import busio from adafruit_tmp117 import TMP117, AverageCount, MeasurementDelay i2c = busio.I2C(board.SCL, board.SDA) tmp117 = TMP117(i2c) # uncomment different options below to see how it affects the reported temperature # tmp117.measurement_delay = MeasurementDelay.DELAY_0_0015_S # tmp117.measurement_delay = MeasurementDelay.DELAY_0_125_S # tmp117.measurement_delay = MeasurementDelay.DELAY_0_250_S # tmp117.measurement_delay = MeasurementDelay.DELAY_0_500_S # tmp117.measurement_delay = MeasurementDelay.DELAY_1_S # tmp117.measurement_delay = MeasurementDelay.DELAY_4_S # tmp117.measurement_delay = MeasurementDelay.DELAY_8_S # tmp117.measurement_delay = MeasurementDelay.DELAY_16_S print("Minimum time between measurements:", MeasurementDelay.string[tmp117.measurement_delay], "seconds") print("") while True: print("Temperature:", tmp117.temperature) time.sleep(0.01) """ return self._raw_measurement_delay @measurement_delay.setter def measurement_delay(self, value): if not MeasurementDelay.is_valid(value): raise AttributeError( "measurement_delay must be a `MeasurementDelay`") self._raw_measurement_delay = value def take_single_measurememt(self): """Perform a single measurement cycle respecting the value of `averaged_measurements`, returning the measurement once complete. Once finished the sensor is placed into a low power state until :py:meth:`take_single_measurement` or `temperature` are read. **Note:** if `averaged_measurements` is set to a high value there will be a notable delay before the temperature measurement is returned while the sensor takes the required number of measurements """ return self._set_mode_and_wait_for_measurement( _ONE_SHOT_MODE) # one shot @property def alert_mode(self): """Sets the behavior of the `low_limit`, `high_limit`, and `alert_status` properties. When set to :py:const:`AlertMode.WINDOW`, the `high_limit` property will unset when the measured temperature goes below `high_limit`. Similarly `low_limit` will be True or False depending on if the measured temperature is below (`False`) or above(`True`) `low_limit`. When set to :py:const:`AlertMode.HYSTERESIS`, the `high_limit` property will be set to `False` when the measured temperature goes below `low_limit`. In this mode, the `low_limit` property of `alert_status` will not be set. The default is :py:const:`AlertMode.WINDOW`""" return self._raw_alert_mode @alert_mode.setter def alert_mode(self, value): if not AlertMode.is_valid(value): raise AttributeError("alert_mode must be an `AlertMode`") self._raw_alert_mode = value @property def serial_number(self): """A 48-bit, factory-set unique identifier for the device.""" eeprom1_data = bytearray(2) eeprom2_data = bytearray(2) eeprom3_data = bytearray(2) # Fetch EEPROM registers with self.i2c_device as i2c: i2c.write_then_readinto(bytearray([_EEPROM1]), eeprom1_data) i2c.write_then_readinto(bytearray([_EEPROM2]), eeprom2_data) i2c.write_then_readinto(bytearray([_EEPROM3]), eeprom3_data) # Combine the 2-byte portions combined_id = bytearray([ eeprom1_data[0], eeprom1_data[1], eeprom2_data[0], eeprom2_data[1], eeprom3_data[0], eeprom3_data[1], ]) # Convert to an integer return _convert_to_integer(combined_id) def _set_mode_and_wait_for_measurement(self, mode): self._mode = mode # poll for data ready while not self._read_status()[2]: time.sleep(0.001) return self._read_temperature() # eeprom write enable to set defaults for limits and config # requires context manager or something to perform a general call reset def _read_status(self): # 3 bits: high_alert, low_alert, data_ready status_flags = self._alert_status_data_ready high_alert = 0b100 & status_flags > 0 low_alert = 0b010 & status_flags > 0 data_ready = 0b001 & status_flags > 0 return (high_alert, low_alert, data_ready) def _read_temperature(self): return self._raw_temperature * _TMP117_RESOLUTION
class HTS221: # pylint: disable=too-many-instance-attributes """Library for the ST HTS221 Humidity and Temperature Sensor :param ~busio.I2C i2c_bus: The I2C bus the HTS221HB is connected to. """ _chip_id = ROUnaryStruct(_WHO_AM_I, "<B") _boot_bit = RWBit(_CTRL_REG2, 7) enabled = RWBit(_CTRL_REG1, 7) """Controls the power down state of the sensor. Setting to `False` will shut the sensor down""" _data_rate = RWBits(2, _CTRL_REG1, 0) _one_shot_bit = RWBit(_CTRL_REG2, 0) _temperature_status_bit = ROBit(_STATUS_REG, 0) _humidity_status_bit = ROBit(_STATUS_REG, 1) _raw_temperature = ROUnaryStruct(_TEMP_OUT_L, "<h") _raw_humidity = ROUnaryStruct(_HUMIDITY_OUT_L, "<h") # humidity calibration consts _t0_deg_c_x8_lsbyte = ROBits(8, _T0_DEGC_X8, 0) _t1_deg_c_x8_lsbyte = ROBits(8, _T1_DEGC_X8, 0) _t1_t0_deg_c_x8_msbits = ROBits(4, _T1_T0_MSB, 0) _t0_out = ROUnaryStruct(_T0_OUT, "<h") _t1_out = ROUnaryStruct(_T1_OUT, "<h") _h0_rh_x2 = ROUnaryStruct(_H0_RH_X2, "<B") _h1_rh_x2 = ROUnaryStruct(_H1_RH_X2, "<B") _h0_t0_out = ROUnaryStruct(_H0_T0_OUT, "<h") _h1_t0_out = ROUnaryStruct(_H1_T1_OUT, "<h") def __init__(self, i2c_bus): self.i2c_device = i2cdevice.I2CDevice(i2c_bus, _HTS221_DEFAULT_ADDRESS) if not self._chip_id in [_HTS221_CHIP_ID]: raise RuntimeError("Failed to find HTS221HB! Found chip ID 0x%x" % self._chip_id) self._boot() self.enabled = True self.data_rate = Rate.RATE_12_5_HZ # pylint:disable=no-member t1_t0_msbs = self._t1_t0_deg_c_x8_msbits self.calib_temp_value_0 = self._t0_deg_c_x8_lsbyte self.calib_temp_value_0 |= (t1_t0_msbs & 0b0011) << 8 self.calibrated_value_1 = self._t1_deg_c_x8_lsbyte self.calibrated_value_1 |= (t1_t0_msbs & 0b1100) << 6 self.calib_temp_value_0 >>= 3 # divide by 8 to remove x8 self.calibrated_value_1 >>= 3 # divide by 8 to remove x8 self.calib_temp_meas_0 = self._t0_out self.calib_temp_meas_1 = self._t1_out self.calib_hum_value_0 = self._h0_rh_x2 self.calib_hum_value_0 >>= 1 # divide by 2 to remove x2 self.calib_hum_value_1 = self._h1_rh_x2 self.calib_hum_value_1 >>= 1 # divide by 2 to remove x2 self.calib_hum_meas_0 = self._h0_t0_out self.calib_hum_meas_1 = self._h1_t0_out # This is the closest thing to a software reset. It re-loads the calibration values from flash def _boot(self): self._boot_bit = True # wait for the reset to finish while self._boot_bit: pass @property def relative_humidity(self): """The current relative humidity measurement in %rH""" calibrated_value_delta = self.calib_hum_value_1 - self.calib_hum_value_0 calibrated_measurement_delta = self.calib_hum_meas_1 - self.calib_hum_meas_0 calibration_value_offset = self.calib_hum_value_0 calibrated_measurement_offset = self.calib_hum_meas_0 zeroed_measured_humidity = self._raw_humidity - calibrated_measurement_offset correction_factor = calibrated_value_delta / calibrated_measurement_delta adjusted_humidity = (zeroed_measured_humidity * correction_factor + calibration_value_offset) return adjusted_humidity @property def temperature(self): """The current temperature measurement in degrees C""" calibrated_value_delta = self.calibrated_value_1 - self.calib_temp_value_0 calibrated_measurement_delta = self.calib_temp_meas_1 - self.calib_temp_meas_0 calibration_value_offset = self.calib_temp_value_0 calibrated_measurement_offset = self.calib_temp_meas_0 zeroed_measured_temp = self._raw_temperature - calibrated_measurement_offset correction_factor = calibrated_value_delta / calibrated_measurement_delta adjusted_temp = (zeroed_measured_temp * correction_factor) + calibration_value_offset return adjusted_temp @property def data_rate(self): """The rate at which the sensor measures ``relative_humidity`` and ``temperature``. ``data_rate`` should be set to one of the values of ``adafruit_hts221.Rate``. Note that setting ``data_rate`` to ``Rate.ONE_SHOT`` will cause ``relative_humidity`` and ``temperature`` measurements to only update when ``take_measurements`` is called.""" return self._data_rate @data_rate.setter def data_rate(self, value): if not Rate.is_valid(value): raise AttributeError("data_rate must be a `Rate`") self._data_rate = value @property def humidity_data_ready(self): """Returns true if a new relative humidity measurement is available to be read""" return self._humidity_status_bit @property def temperature_data_ready(self): """Returns true if a new temperature measurement is available to be read""" return self._temperature_status_bit def take_measurements(self): """Update the value of ``relative_humidity`` and ``temperature`` by taking a single measurement. Only meaningful if ``data_rate`` is set to ``ONE_SHOT``""" self._one_shot_bit = True while self._one_shot_bit: pass
class INA219: """Driver for the INA219 current sensor""" # Basic API: # INA219( i2c_bus, addr) Create instance of INA219 sensor # :param i2c_bus The I2C bus the INA219is connected to # :param addr (0x40) Address of the INA219 on the bus (default 0x40) # shunt_voltage RO : shunt voltage scaled to Volts # bus_voltage RO : bus voltage (V- to GND) scaled to volts (==load voltage) # current RO : current through shunt, scaled to mA # power RO : power consumption of the load, scaled to Watt # set_calibration_32V_2A() Initialize chip for 32V max and up to 2A (default) # set_calibration_32V_1A() Initialize chip for 32V max and up to 1A # set_calibration_16V_400mA() Initialize chip for 16V max and up to 400mA # Advanced API: # config register break-up # reset WO : Write Reset.RESET to reset the chip (must recalibrate) # bus_voltage_range RW : Bus Voltage Range field (use BusVoltageRange.XXX constants) # gain RW : Programmable Gain field (use Gain.XXX constants) # bus_adc_resolution RW : Bus ADC resolution and averaging modes (ADCResolution.XXX) # shunt_adc_resolution RW : Shunt ADC resolution and averaging modes (ADCResolution.XXX) # mode RW : operating modes in config register (use Mode.XXX constants) # raw_shunt_voltage RO : Shunt Voltage register (not scaled) # raw_bus_voltage RO : Bus Voltage field in Bus Voltage register (not scaled) # conversion_ready RO : Conversion Ready bit in Bus Voltage register # overflow RO : Math Overflow bit in Bus Voltage register # raw_power RO : Power register (not scaled) # raw_current RO : Current register (not scaled) # calibration RW : calibration register (note: value is cached) def __init__(self, i2c_bus, addr=0x40): self.i2c_device = I2CDevice(i2c_bus, addr) self.i2c_addr = addr # Set chip to known config values to start self._cal_value = 0 self._current_lsb = 0 self._power_lsb = 0 self.set_calibration_32V_2A() # config register break-up reset = RWBits(1, _REG_CONFIG, 15, 2, False) bus_voltage_range = RWBits(1, _REG_CONFIG, 13, 2, False) gain = RWBits(2, _REG_CONFIG, 11, 2, False) bus_adc_resolution = RWBits(4, _REG_CONFIG, 7, 2, False) shunt_adc_resolution = RWBits(4, _REG_CONFIG, 3, 2, False) mode = RWBits(3, _REG_CONFIG, 0, 2, False) # shunt voltage register raw_shunt_voltage = ROUnaryStruct(_REG_SHUNTVOLTAGE, ">h") # bus voltage register raw_bus_voltage = ROBits(13, _REG_BUSVOLTAGE, 3, 2, False) conversion_ready = ROBit(_REG_BUSVOLTAGE, 1, 2, False) overflow = ROBit(_REG_BUSVOLTAGE, 0, 2, False) # power and current registers raw_power = ROUnaryStruct(_REG_POWER, ">H") raw_current = ROUnaryStruct(_REG_CURRENT, ">h") # calibration register _raw_calibration = UnaryStruct(_REG_CALIBRATION, ">H") @property def calibration(self): """Calibration register (cached value)""" return self._cal_value # return cached value @calibration.setter def calibration(self, cal_value): self._cal_value = ( cal_value # value is cached for ``current`` and ``power`` properties ) self._raw_calibration = self._cal_value @property def shunt_voltage(self): """The shunt voltage (between V+ and V-) in Volts (so +-.327V)""" # The least signficant bit is 10uV which is 0.00001 volts return self.raw_shunt_voltage * 0.00001 @property def bus_voltage(self): """The bus voltage (between V- and GND) in Volts""" # Shift to the right 3 to drop CNVR and OVF and multiply by LSB # Each least signficant bit is 4mV return self.raw_bus_voltage * 0.004 @property def current(self): """The current through the shunt resistor in milliamps.""" # Sometimes a sharp load will reset the INA219, which will # reset the cal register, meaning CURRENT and POWER will # not be available ... always setting a cal # value even if it's an unfortunate extra step self._raw_calibration = self._cal_value # Now we can safely read the CURRENT register! return self.raw_current * self._current_lsb @property def power(self): """The power through the load in Watt.""" # Sometimes a sharp load will reset the INA219, which will # reset the cal register, meaning CURRENT and POWER will # not be available ... always setting a cal # value even if it's an unfortunate extra step self._raw_calibration = self._cal_value # Now we can safely read the CURRENT register! return self.raw_power * self._power_lsb def set_calibration_32V_2A(self): # pylint: disable=invalid-name """Configures to INA219 to be able to measure up to 32V and 2A of current. Counter overflow occurs at 3.2A. ..note :: These calculations assume a 0.1 shunt ohm resistor is present """ # By default we use a pretty huge range for the input voltage, # which probably isn't the most appropriate choice for system # that don't use a lot of power. But all of the calculations # are shown below if you want to change the settings. You will # also need to change any relevant register settings, such as # setting the VBUS_MAX to 16V instead of 32V, etc. # VBUS_MAX = 32V (Assumes 32V, can also be set to 16V) # VSHUNT_MAX = 0.32 (Assumes Gain 8, 320mV, can also be 0.16, 0.08, 0.04) # RSHUNT = 0.1 (Resistor value in ohms) # 1. Determine max possible current # MaxPossible_I = VSHUNT_MAX / RSHUNT # MaxPossible_I = 3.2A # 2. Determine max expected current # MaxExpected_I = 2.0A # 3. Calculate possible range of LSBs (Min = 15-bit, Max = 12-bit) # MinimumLSB = MaxExpected_I/32767 # MinimumLSB = 0.000061 (61uA per bit) # MaximumLSB = MaxExpected_I/4096 # MaximumLSB = 0,000488 (488uA per bit) # 4. Choose an LSB between the min and max values # (Preferrably a roundish number close to MinLSB) # CurrentLSB = 0.0001 (100uA per bit) self._current_lsb = 0.1 # Current LSB = 100uA per bit # 5. Compute the calibration register # Cal = trunc (0.04096 / (Current_LSB * RSHUNT)) # Cal = 4096 (0x1000) self._cal_value = 4096 # 6. Calculate the power LSB # PowerLSB = 20 * CurrentLSB # PowerLSB = 0.002 (2mW per bit) self._power_lsb = 0.002 # Power LSB = 2mW per bit # 7. Compute the maximum current and shunt voltage values before overflow # # Max_Current = Current_LSB * 32767 # Max_Current = 3.2767A before overflow # # If Max_Current > Max_Possible_I then # Max_Current_Before_Overflow = MaxPossible_I # Else # Max_Current_Before_Overflow = Max_Current # End If # # Max_ShuntVoltage = Max_Current_Before_Overflow * RSHUNT # Max_ShuntVoltage = 0.32V # # If Max_ShuntVoltage >= VSHUNT_MAX # Max_ShuntVoltage_Before_Overflow = VSHUNT_MAX # Else # Max_ShuntVoltage_Before_Overflow = Max_ShuntVoltage # End If # 8. Compute the Maximum Power # MaximumPower = Max_Current_Before_Overflow * VBUS_MAX # MaximumPower = 3.2 * 32V # MaximumPower = 102.4W # Set Calibration register to 'Cal' calculated above self._raw_calibration = self._cal_value # Set Config register to take into account the settings above self.bus_voltage_range = BusVoltageRange.RANGE_32V self.gain = Gain.DIV_8_320MV self.bus_adc_resolution = ADCResolution.ADCRES_12BIT_1S self.shunt_adc_resolution = ADCResolution.ADCRES_12BIT_1S self.mode = Mode.SANDBVOLT_CONTINUOUS def set_calibration_32V_1A(self): # pylint: disable=invalid-name """Configures to INA219 to be able to measure up to 32V and 1A of current. Counter overflow occurs at 1.3A. .. note:: These calculations assume a 0.1 ohm shunt resistor is present""" # By default we use a pretty huge range for the input voltage, # which probably isn't the most appropriate choice for system # that don't use a lot of power. But all of the calculations # are shown below if you want to change the settings. You will # also need to change any relevant register settings, such as # setting the VBUS_MAX to 16V instead of 32V, etc. # VBUS_MAX = 32V (Assumes 32V, can also be set to 16V) # VSHUNT_MAX = 0.32 (Assumes Gain 8, 320mV, can also be 0.16, 0.08, 0.04) # RSHUNT = 0.1 (Resistor value in ohms) # 1. Determine max possible current # MaxPossible_I = VSHUNT_MAX / RSHUNT # MaxPossible_I = 3.2A # 2. Determine max expected current # MaxExpected_I = 1.0A # 3. Calculate possible range of LSBs (Min = 15-bit, Max = 12-bit) # MinimumLSB = MaxExpected_I/32767 # MinimumLSB = 0.0000305 (30.5uA per bit) # MaximumLSB = MaxExpected_I/4096 # MaximumLSB = 0.000244 (244uA per bit) # 4. Choose an LSB between the min and max values # (Preferrably a roundish number close to MinLSB) # CurrentLSB = 0.0000400 (40uA per bit) self._current_lsb = 0.04 # In milliamps # 5. Compute the calibration register # Cal = trunc (0.04096 / (Current_LSB * RSHUNT)) # Cal = 10240 (0x2800) self._cal_value = 10240 # 6. Calculate the power LSB # PowerLSB = 20 * CurrentLSB # PowerLSB = 0.0008 (800uW per bit) self._power_lsb = 0.0008 # 7. Compute the maximum current and shunt voltage values before overflow # # Max_Current = Current_LSB * 32767 # Max_Current = 1.31068A before overflow # # If Max_Current > Max_Possible_I then # Max_Current_Before_Overflow = MaxPossible_I # Else # Max_Current_Before_Overflow = Max_Current # End If # # ... In this case, we're good though since Max_Current is less than MaxPossible_I # # Max_ShuntVoltage = Max_Current_Before_Overflow * RSHUNT # Max_ShuntVoltage = 0.131068V # # If Max_ShuntVoltage >= VSHUNT_MAX # Max_ShuntVoltage_Before_Overflow = VSHUNT_MAX # Else # Max_ShuntVoltage_Before_Overflow = Max_ShuntVoltage # End If # 8. Compute the Maximum Power # MaximumPower = Max_Current_Before_Overflow * VBUS_MAX # MaximumPower = 1.31068 * 32V # MaximumPower = 41.94176W # Set Calibration register to 'Cal' calculated above self._raw_calibration = self._cal_value # Set Config register to take into account the settings above self.bus_voltage_range = BusVoltageRange.RANGE_32V self.gain = Gain.DIV_8_320MV self.bus_adc_resolution = ADCResolution.ADCRES_12BIT_1S self.shunt_adc_resolution = ADCResolution.ADCRES_12BIT_1S self.mode = Mode.SANDBVOLT_CONTINUOUS def set_calibration_16V_400mA(self): # pylint: disable=invalid-name """Configures to INA219 to be able to measure up to 16V and 400mA of current. Counter overflow occurs at 1.6A. .. note:: These calculations assume a 0.1 ohm shunt resistor is present""" # Calibration which uses the highest precision for # current measurement (0.1mA), at the expense of # only supporting 16V at 400mA max. # VBUS_MAX = 16V # VSHUNT_MAX = 0.04 (Assumes Gain 1, 40mV) # RSHUNT = 0.1 (Resistor value in ohms) # 1. Determine max possible current # MaxPossible_I = VSHUNT_MAX / RSHUNT # MaxPossible_I = 0.4A # 2. Determine max expected current # MaxExpected_I = 0.4A # 3. Calculate possible range of LSBs (Min = 15-bit, Max = 12-bit) # MinimumLSB = MaxExpected_I/32767 # MinimumLSB = 0.0000122 (12uA per bit) # MaximumLSB = MaxExpected_I/4096 # MaximumLSB = 0.0000977 (98uA per bit) # 4. Choose an LSB between the min and max values # (Preferrably a roundish number close to MinLSB) # CurrentLSB = 0.00005 (50uA per bit) self._current_lsb = 0.05 # in milliamps # 5. Compute the calibration register # Cal = trunc (0.04096 / (Current_LSB * RSHUNT)) # Cal = 8192 (0x2000) self._cal_value = 8192 # 6. Calculate the power LSB # PowerLSB = 20 * CurrentLSB # PowerLSB = 0.001 (1mW per bit) self._power_lsb = 0.001 # 7. Compute the maximum current and shunt voltage values before overflow # # Max_Current = Current_LSB * 32767 # Max_Current = 1.63835A before overflow # # If Max_Current > Max_Possible_I then # Max_Current_Before_Overflow = MaxPossible_I # Else # Max_Current_Before_Overflow = Max_Current # End If # # Max_Current_Before_Overflow = MaxPossible_I # Max_Current_Before_Overflow = 0.4 # # Max_ShuntVoltage = Max_Current_Before_Overflow * RSHUNT # Max_ShuntVoltage = 0.04V # # If Max_ShuntVoltage >= VSHUNT_MAX # Max_ShuntVoltage_Before_Overflow = VSHUNT_MAX # Else # Max_ShuntVoltage_Before_Overflow = Max_ShuntVoltage # End If # # Max_ShuntVoltage_Before_Overflow = VSHUNT_MAX # Max_ShuntVoltage_Before_Overflow = 0.04V # 8. Compute the Maximum Power # MaximumPower = Max_Current_Before_Overflow * VBUS_MAX # MaximumPower = 0.4 * 16V # MaximumPower = 6.4W # Set Calibration register to 'Cal' calculated above self._raw_calibration = self._cal_value # Set Config register to take into account the settings above self.bus_voltage_range = BusVoltageRange.RANGE_16V self.gain = Gain.DIV_1_40MV self.bus_adc_resolution = ADCResolution.ADCRES_12BIT_1S self.shunt_adc_resolution = ADCResolution.ADCRES_12BIT_1S self.mode = Mode.SANDBVOLT_CONTINUOUS def set_calibration_16V_5A(self): # pylint: disable=invalid-name """Configures to INA219 to be able to measure up to 16V and 5000mA of current. Counter overflow occurs at 8.0A. .. note:: These calculations assume a 0.02 ohm shunt resistor is present""" # Calibration which uses the highest precision for # current measurement (0.1mA), at the expense of # only supporting 16V at 5000mA max. # VBUS_MAX = 16V # VSHUNT_MAX = 0.16 (Assumes Gain 3, 160mV) # RSHUNT = 0.02 (Resistor value in ohms) # 1. Determine max possible current # MaxPossible_I = VSHUNT_MAX / RSHUNT # MaxPossible_I = 8.0A # 2. Determine max expected current # MaxExpected_I = 5.0A # 3. Calculate possible range of LSBs (Min = 15-bit, Max = 12-bit) # MinimumLSB = MaxExpected_I/32767 # MinimumLSB = 0.0001529 (uA per bit) # MaximumLSB = MaxExpected_I/4096 # MaximumLSB = 0.0012207 (uA per bit) # 4. Choose an LSB between the min and max values # (Preferrably a roundish number close to MinLSB) # CurrentLSB = 0.00016 (uA per bit) self._current_lsb = 0.1524 # in milliamps # 5. Compute the calibration register # Cal = trunc (0.04096 / (Current_LSB * RSHUNT)) # Cal = 13434 (0x347a) self._cal_value = 13434 # 6. Calculate the power LSB # PowerLSB = 20 * CurrentLSB # PowerLSB = 0.003 (3.048mW per bit) self._power_lsb = 0.003048 # 7. Compute the maximum current and shunt voltage values before overflow # # 8. Compute the Maximum Power # # Set Calibration register to 'Cal' calcutated above self._raw_calibration = self._cal_value # Set Config register to take into account the settings above self.bus_voltage_range = BusVoltageRange.RANGE_16V self.gain = Gain.DIV_4_160MV self.bus_adc_resolution = ADCResolution.ADCRES_12BIT_1S self.shunt_adc_resolution = ADCResolution.ADCRES_12BIT_1S self.mode = Mode.SANDBVOLT_CONTINUOUS
class NAU7802: """The primary NAU7802 class.""" # pylint: disable=too-many-instance-attributes def __init__(self, i2c_bus, address=0x2A, active_channels=1): """Instantiate NAU7802; LDO 3v0 volts, gain 128, 10 samples per second conversion rate, disabled ADC chopper clock, low ESR caps, and PGA output stabilizer cap if in single channel mode. Returns True if successful.""" self.i2c_device = I2CDevice(i2c_bus, address) if not self.reset(): raise RuntimeError("NAU7802 device could not be reset") if not self.enable(True): raise RuntimeError("NAU7802 device could not be enabled") self.ldo_voltage = "3V0" # 3.0-volt internal analog power (AVDD) self._pu_ldo_source = True # Internal analog power (AVDD) self.gain = 128 # X128 self._c2_conv_rate = ConversionRate.RATE_10SPS # 10 SPS; default self._adc_chop_clock = 0x3 # 0x3 = Disable ADC chopper clock self._pga_ldo_mode = 0x0 # 0x0 = Use low ESR capacitors self._act_channels = active_channels # 0x1 = Enable PGA out stabilizer cap for single channel use self._pc_cap_enable = 0x1 if self._act_channels == 2: # 0x0 = Disable PGA out stabilizer cap for dual channel use self._pc_cap_enable = 0x0 self._calib_mode = None # Initialize for later use self._adc_out = None # Initialize for later use # DEFINE I2C DEVICE BITS, NYBBLES, BYTES, AND REGISTERS # Chip Revision R- _rev_id = ROBits(4, _REV_ID, 0, 1, False) # Register Reset (RR) RW _pu_reg_reset = RWBit(_PU_CTRL, 0, 1, False) # Power-Up Digital Circuit (PUD) RW _pu_digital = RWBit(_PU_CTRL, 1, 1, False) # Power-Up Analog Circuit (PUA) RW _pu_analog = RWBit(_PU_CTRL, 2, 1, False) # Power-Up Ready Status (PUR) R- _pu_ready = ROBit(_PU_CTRL, 3, 1, False) # Power-Up Conversion Cycle Start (CS) RW _pu_cycle_start = RWBit(_PU_CTRL, 4, 1, False) # Power-Up Cycle Ready (CR) R- _pu_cycle_ready = ROBit(_PU_CTRL, 5, 1, False) # Power-Up AVDD Source ADDS) RW _pu_ldo_source = RWBit(_PU_CTRL, 7, 1, False) # Control_1 Gain (GAINS) RW _c1_gains = RWBits(3, _CTRL1, 0, 1, False) # Control_1 LDO Voltage (VLDO) RW _c1_vldo_volts = RWBits(3, _CTRL1, 3, 1, False) # Control_2 Calibration Mode (CALMOD) RW _c2_cal_mode = RWBits(2, _CTRL2, 0, 1, False) # Control_2 Calibration Start (CALS) RW _c2_cal_start = RWBit(_CTRL2, 2, 1, False) # Control_2 Calibration Error (CAL_ERR) RW _c2_cal_error = RWBit(_CTRL2, 3, 1, False) # Control_2 Conversion Rate (CRS) RW _c2_conv_rate = RWBits(3, _CTRL2, 4, 1, False) # Control_2 Channel Select (CHS) RW _c2_chan_select = RWBit(_CTRL2, 7, 1, False) # ADC Result Output MSByte R- _adc_out_2 = ROUnaryStruct(_ADCO_B2, ">B") # ADC Result Output MidSByte R- _adc_out_1 = ROUnaryStruct(_ADCO_B1, ">B") # ADC Result Output LSByte R- _adc_out_0 = ROUnaryStruct(_ADCO_B0, ">B") # ADC Chopper Clock Frequency Select -W _adc_chop_clock = RWBits(2, _ADC, 4, 1, False) # PGA Stability/Accuracy Mode (LDOMODE) RW _pga_ldo_mode = RWBit(_PGA, 6, 1, False) # Power_Ctrl PGA Capacitor (PGA_CAP_EN) RW _pc_cap_enable = RWBit(_PWR_CTRL, 7, 1, False) @property def chip_revision(self): """The chip revision code.""" return self._rev_id @property def channel(self): "Selected channel number (1 or 2)." return self._c2_chan_select + 1 @channel.setter def channel(self, chan=1): """Select the active channel. Valid channel numbers are 1 and 2. Analog multiplexer settling time was emperically determined to be approximately 400ms at 10SPS, 200ms at 20SPS, 100ms at 40SPS, 50ms at 80SPS, and 20ms at 320SPS.""" if chan == 1: self._c2_chan_select = 0x0 time.sleep(0.400) # 400ms settling time for 10SPS elif chan == 2 and self._act_channels == 2: self._c2_chan_select = 0x1 time.sleep(0.400) # 400ms settling time for 10SPS else: raise ValueError("Invalid Channel Number") @property def ldo_voltage(self): """Representation of the LDO voltage value.""" return self._ldo_voltage @ldo_voltage.setter def ldo_voltage(self, voltage="EXTERNAL"): """Select the LDO Voltage. Valid voltages are '2V4', '2V7', '3V0'.""" if not "LDO_" + voltage in dir(LDOVoltage): raise ValueError("Invalid LDO Voltage") self._ldo_voltage = voltage if self._ldo_voltage == "2V4": self._c1_vldo_volts = LDOVoltage.LDO_2V4 elif self._ldo_voltage == "2V7": self._c1_vldo_volts = LDOVoltage.LDO_2V7 elif self._ldo_voltage == "3V0": self._c1_vldo_volts = LDOVoltage.LDO_3V0 @property def gain(self): """The programmable amplifier (PGA) gain factor.""" return self._gain @gain.setter def gain(self, factor=1): """Select PGA gain factor. Valid values are '1, 2, 4, 8, 16, 32, 64, and 128.""" if not "GAIN_X" + str(factor) in dir(Gain): raise ValueError("Invalid Gain Factor") self._gain = factor if self._gain == 1: self._c1_gains = Gain.GAIN_X1 elif self._gain == 2: self._c1_gains = Gain.GAIN_X2 elif self._gain == 4: self._c1_gains = Gain.GAIN_X4 elif self._gain == 8: self._c1_gains = Gain.GAIN_X8 elif self._gain == 16: self._c1_gains = Gain.GAIN_X16 elif self._gain == 32: self._c1_gains = Gain.GAIN_X32 elif self._gain == 64: self._c1_gains = Gain.GAIN_X64 elif self._gain == 128: self._c1_gains = Gain.GAIN_X128 def enable(self, power=True): """Enable(start) or disable(stop) the internal analog and digital systems power. Enable = True; Disable (low power) = False. Returns True when enabled; False when disabled.""" self._enable = power if self._enable: self._pu_analog = True self._pu_digital = True time.sleep(0.750) # Wait 750ms; minimum 400ms self._pu_start = True # Start acquisition system cycling return self._pu_ready self._pu_analog = False self._pu_digital = False time.sleep(0.010) # Wait 10ms (200us minimum) return False def available(self): """Read the ADC data-ready status. True when data is available; False when ADC data is unavailable.""" return self._pu_cycle_ready def read(self): """Reads the 24-bit ADC data. Returns a signed integer value with 24-bit resolution. Assumes that the ADC data-ready bit was checked to be True.""" adc = self._adc_out_2 << 24 # [31:24] << MSByte adc = adc | (self._adc_out_1 << 16) # [23:16] << MidSByte adc = adc | (self._adc_out_0 << 8) # [15: 8] << LSByte adc = adc.to_bytes(4, "big") # Pack to 4-byte (32-bit) structure value = struct.unpack(">i", adc)[0] # Unpack as 4-byte signed integer self._adc_out = value / 128 # Restore to 24-bit signed integer value return self._adc_out def reset(self): """Resets all device registers and enables digital system power. Returns the power ready status bit value: True when system is ready; False when system not ready for use.""" self._pu_reg_reset = True # Reset all registers) time.sleep(0.100) # Wait 100ms; 10ms minimum self._pu_reg_reset = False self._pu_digital = True time.sleep(0.750) # Wait 750ms; 400ms minimum return self._pu_ready def calibrate(self, mode="INTERNAL"): """Perform the calibration procedure. Valid calibration modes are 'INTERNAL', 'OFFSET', and 'GAIN'. True if successful.""" if not mode in dir(CalibrationMode): raise ValueError("Invalid Calibration Mode") self._calib_mode = mode if self._calib_mode == "INTERNAL": # Internal PGA offset (zero setting) self._c2_cal_mode = CalibrationMode.INTERNAL elif self._calib_mode == "OFFSET": # External PGA offset (zero setting) self._c2_cal_mode = CalibrationMode.OFFSET elif self._calib_mode == "GAIN": # External PGA full-scale gain setting self._c2_cal_mode = CalibrationMode.GAIN self._c2_cal_start = True while self._c2_cal_start: time.sleep(0.010) # 10ms return not self._c2_cal_error