|
|
|
@ -15,7 +15,7 @@ Implementation Notes
|
|
|
|
|
|
|
|
|
|
|
|
**Hardware:**
|
|
|
|
**Hardware:**
|
|
|
|
|
|
|
|
|
|
|
|
* `Adafruit INA228 High Side Current and Power Monitor <https://www.adafruit.com/product/5832>`_
|
|
|
|
* `Adafruit INA228 High or Low Side Current and Power Monitor <https://www.adafruit.com/product/5832>`_
|
|
|
|
|
|
|
|
|
|
|
|
**Software and Dependencies:**
|
|
|
|
**Software and Dependencies:**
|
|
|
|
|
|
|
|
|
|
|
|
@ -28,9 +28,9 @@ Implementation Notes
|
|
|
|
import time
|
|
|
|
import time
|
|
|
|
|
|
|
|
|
|
|
|
from adafruit_bus_device.i2c_device import I2CDevice
|
|
|
|
from adafruit_bus_device.i2c_device import I2CDevice
|
|
|
|
from adafruit_register.i2c_bit import RWBit
|
|
|
|
from adafruit_register.i2c_bit import ROBit, RWBit
|
|
|
|
from adafruit_register.i2c_bits import RWBits
|
|
|
|
from adafruit_register.i2c_bits import ROBits, RWBits
|
|
|
|
from adafruit_register.i2c_struct import UnaryStruct
|
|
|
|
from adafruit_register.i2c_struct import ROUnaryStruct, UnaryStruct
|
|
|
|
from micropython import const
|
|
|
|
from micropython import const
|
|
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
try:
|
|
|
|
@ -43,96 +43,370 @@ except ImportError:
|
|
|
|
__version__ = "0.0.0+auto.0"
|
|
|
|
__version__ = "0.0.0+auto.0"
|
|
|
|
__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_INA228.git"
|
|
|
|
__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_INA228.git"
|
|
|
|
|
|
|
|
|
|
|
|
# Register addresses
|
|
|
|
# Common register addresses for INA2XX family
|
|
|
|
_CONFIG = const(0x00) # Configuration Register
|
|
|
|
_CONFIG = const(0x00)
|
|
|
|
_ADC_CONFIG = const(0x01) # ADC Configuration Register
|
|
|
|
_ADCCFG = const(0x01)
|
|
|
|
_SHUNT_CAL = const(0x02) # Shunt Calibration Register
|
|
|
|
_SHUNTCAL = const(0x02)
|
|
|
|
_SHUNT_TEMPCO = const(0x03) # Shunt Temperature Coefficient Register
|
|
|
|
_VSHUNT = const(0x04)
|
|
|
|
_VSHUNT = const(0x04) # Shunt Voltage Measurement
|
|
|
|
_VBUS = const(0x05)
|
|
|
|
_VBUS = const(0x05) # Bus Voltage Measurement
|
|
|
|
_DIETEMP = const(0x06)
|
|
|
|
_DIETEMP = const(0x06) # Temperature Measurement
|
|
|
|
_CURRENT = const(0x07)
|
|
|
|
_CURRENT = const(0x07) # Current Result
|
|
|
|
_POWER = const(0x08)
|
|
|
|
_POWER = const(0x08) # Power Result
|
|
|
|
_DIAGALRT = const(0x0B)
|
|
|
|
_ENERGY = const(0x09) # Energy Result
|
|
|
|
_MFG_UID = const(0x3E)
|
|
|
|
_CHARGE = const(0x0A) # Charge Result
|
|
|
|
_DVC_UID = const(0x3F)
|
|
|
|
_DIAG_ALRT = const(0x0B) # Diagnostic Flags and Alert
|
|
|
|
|
|
|
|
_SOVL = const(0x0C) # Shunt Overvoltage Threshold
|
|
|
|
# INA228-specific registers
|
|
|
|
_SUVL = const(0x0D) # Shunt Undervoltage Threshold
|
|
|
|
_SHUNT_TEMPCO = const(0x03)
|
|
|
|
_BOVL = const(0x0E) # Bus Overvoltage Threshold
|
|
|
|
_ENERGY = const(0x09)
|
|
|
|
_BUVL = const(0x0F) # Bus Undervoltage Threshold
|
|
|
|
_CHARGE = const(0x0A)
|
|
|
|
_TEMP_LIMIT = const(0x10) # Temperature Over-Limit Threshold
|
|
|
|
_SOVL = const(0x0C)
|
|
|
|
_PWR_LIMIT = const(0x11) # Power Over-Limit Threshold
|
|
|
|
_SUVL = const(0x0D)
|
|
|
|
_MFG_ID = const(0x3E) # Manufacturer ID
|
|
|
|
_BOVL = const(0x0E)
|
|
|
|
_DEVICE_ID = const(0x3F) # Device ID
|
|
|
|
_BUVL = const(0x0F)
|
|
|
|
|
|
|
|
_TEMP_LIMIT = const(0x10)
|
|
|
|
|
|
|
|
_PWR_LIMIT = const(0x11)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Constants
|
|
|
|
|
|
|
|
_INA2XX_DEFAULT_ADDR = const(0x40)
|
|
|
|
|
|
|
|
_TEXAS_INSTRUMENTS_ID = const(0x5449)
|
|
|
|
|
|
|
|
_INA228_DEVICE_ID = const(0x228)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Mode:
|
|
|
|
class Mode:
|
|
|
|
"""Constants for operating modes"""
|
|
|
|
"""Operating mode constants for INA2XX"""
|
|
|
|
|
|
|
|
|
|
|
|
SHUTDOWN = 0x00
|
|
|
|
SHUTDOWN = const(0x00)
|
|
|
|
TRIGGERED_BUS = 0x01
|
|
|
|
TRIG_BUS = const(0x01)
|
|
|
|
TRIGGERED_SHUNT = 0x02
|
|
|
|
TRIG_SHUNT = const(0x02)
|
|
|
|
TRIGGERED_BUS_SHUNT = 0x03
|
|
|
|
TRIG_BUS_SHUNT = const(0x03)
|
|
|
|
TRIGGERED_TEMP = 0x04
|
|
|
|
TRIG_TEMP = const(0x04)
|
|
|
|
TRIGGERED_TEMP_BUS = 0x05
|
|
|
|
TRIG_TEMP_BUS = const(0x05)
|
|
|
|
TRIGGERED_TEMP_SHUNT = 0x06
|
|
|
|
TRIG_TEMP_SHUNT = const(0x06)
|
|
|
|
TRIGGERED_ALL = 0x07
|
|
|
|
TRIG_TEMP_BUS_SHUNT = const(0x07)
|
|
|
|
SHUTDOWN2 = 0x08
|
|
|
|
CONT_BUS = const(0x09)
|
|
|
|
CONTINUOUS_BUS = 0x09
|
|
|
|
CONT_SHUNT = const(0x0A)
|
|
|
|
CONTINUOUS_SHUNT = 0x0A
|
|
|
|
CONT_BUS_SHUNT = const(0x0B)
|
|
|
|
CONTINUOUS_BUS_SHUNT = 0x0B
|
|
|
|
CONT_TEMP = const(0x0C)
|
|
|
|
CONTINUOUS_TEMP = 0x0C
|
|
|
|
CONT_TEMP_BUS = const(0x0D)
|
|
|
|
CONTINUOUS_TEMP_BUS = 0x0D
|
|
|
|
CONT_TEMP_SHUNT = const(0x0E)
|
|
|
|
CONTINUOUS_TEMP_SHUNT = 0x0E
|
|
|
|
CONT_TEMP_BUS_SHUNT = const(0x0F)
|
|
|
|
CONTINUOUS_ALL = 0x0F
|
|
|
|
|
|
|
|
|
|
|
|
# Convenience aliases
|
|
|
|
|
|
|
|
TRIGGERED = TRIG_TEMP_BUS_SHUNT
|
|
|
|
|
|
|
|
CONTINUOUS = CONT_TEMP_BUS_SHUNT
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Valid modes set for validation
|
|
|
|
|
|
|
|
_VALID_MODES = {
|
|
|
|
|
|
|
|
SHUTDOWN,
|
|
|
|
|
|
|
|
TRIG_BUS,
|
|
|
|
|
|
|
|
TRIG_SHUNT,
|
|
|
|
|
|
|
|
TRIG_BUS_SHUNT,
|
|
|
|
|
|
|
|
TRIG_TEMP,
|
|
|
|
|
|
|
|
TRIG_TEMP_BUS,
|
|
|
|
|
|
|
|
TRIG_TEMP_SHUNT,
|
|
|
|
|
|
|
|
TRIG_TEMP_BUS_SHUNT,
|
|
|
|
|
|
|
|
CONT_BUS,
|
|
|
|
|
|
|
|
CONT_SHUNT,
|
|
|
|
|
|
|
|
CONT_BUS_SHUNT,
|
|
|
|
|
|
|
|
CONT_TEMP,
|
|
|
|
|
|
|
|
CONT_TEMP_BUS,
|
|
|
|
|
|
|
|
CONT_TEMP_SHUNT,
|
|
|
|
|
|
|
|
CONT_TEMP_BUS_SHUNT,
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class INA228: # noqa: PLR0904
|
|
|
|
class ConversionTime:
|
|
|
|
"""Driver for the INA228 power and current sensor"""
|
|
|
|
"""Conversion time constants for INA2XX"""
|
|
|
|
|
|
|
|
|
|
|
|
_config = UnaryStruct(_CONFIG, ">H")
|
|
|
|
TIME_50_US = const(0)
|
|
|
|
_adc_config = UnaryStruct(_ADC_CONFIG, ">H")
|
|
|
|
TIME_84_US = const(1)
|
|
|
|
_shunt_cal = UnaryStruct(_SHUNT_CAL, ">H")
|
|
|
|
TIME_150_US = const(2)
|
|
|
|
_diag_alrt = UnaryStruct(_DIAG_ALRT, ">H")
|
|
|
|
TIME_280_US = const(3)
|
|
|
|
_adc_range = RWBit(_CONFIG, 4, register_width=2)
|
|
|
|
TIME_540_US = const(4)
|
|
|
|
"""Operating mode"""
|
|
|
|
TIME_1052_US = const(5)
|
|
|
|
mode = RWBits(4, _ADC_CONFIG, 12, register_width=2)
|
|
|
|
TIME_2074_US = const(6)
|
|
|
|
_vbus_ct = RWBits(3, _ADC_CONFIG, 9, register_width=2)
|
|
|
|
TIME_4120_US = const(7)
|
|
|
|
_vshunt_ct = RWBits(3, _ADC_CONFIG, 6, register_width=2)
|
|
|
|
|
|
|
|
_temper_ct = RWBits(3, _ADC_CONFIG, 3, register_width=2)
|
|
|
|
|
|
|
|
_avg_count = RWBits(3, _ADC_CONFIG, 0, register_width=2)
|
|
|
|
|
|
|
|
_device_id = UnaryStruct(_DEVICE_ID, ">H")
|
|
|
|
|
|
|
|
_temperature = UnaryStruct(_DIETEMP, ">h")
|
|
|
|
|
|
|
|
_sovl = UnaryStruct(_SOVL, ">H") # Shunt overvoltage
|
|
|
|
|
|
|
|
_suvl = UnaryStruct(_SUVL, ">H") # Shunt undervoltage
|
|
|
|
|
|
|
|
_bovl = UnaryStruct(_BOVL, ">H") # Bus overvoltage
|
|
|
|
|
|
|
|
_buvl = UnaryStruct(_BUVL, ">H") # Bus undervoltage
|
|
|
|
|
|
|
|
_temp_limit = UnaryStruct(_TEMP_LIMIT, ">H") # Temperature limit
|
|
|
|
|
|
|
|
_pwr_limit = UnaryStruct(_PWR_LIMIT, ">H") # Power limit
|
|
|
|
|
|
|
|
_shunt_tempco = UnaryStruct(_SHUNT_TEMPCO, ">H")
|
|
|
|
|
|
|
|
"""Manufacturer ID"""
|
|
|
|
|
|
|
|
manufacturer_id = UnaryStruct(_MFG_ID, ">H")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def __init__(self, i2c_bus, addr=0x40):
|
|
|
|
_VALID_TIMES = {
|
|
|
|
self.i2c_device = I2CDevice(i2c_bus, addr)
|
|
|
|
TIME_50_US,
|
|
|
|
self.buf3 = bytearray(3) # Buffer for 24-bit registers
|
|
|
|
TIME_84_US,
|
|
|
|
self.buf5 = bytearray(5) # Buffer for 40-bit registers
|
|
|
|
TIME_150_US,
|
|
|
|
# Verify device ID
|
|
|
|
TIME_280_US,
|
|
|
|
dev_id = (self._device_id >> 4) & 0xFFF # Get 12-bit device ID
|
|
|
|
TIME_540_US,
|
|
|
|
if dev_id != 0x228:
|
|
|
|
TIME_1052_US,
|
|
|
|
raise RuntimeError(f"Failed to find INA228 - check your wiring! (Got ID: 0x{dev_id:X})")
|
|
|
|
TIME_2074_US,
|
|
|
|
self._current_lsb = 0
|
|
|
|
TIME_4120_US,
|
|
|
|
self._shunt_res = 0
|
|
|
|
}
|
|
|
|
self.reset()
|
|
|
|
|
|
|
|
self.mode = Mode.CONTINUOUS_ALL
|
|
|
|
# Microsecond values for each setting
|
|
|
|
self.set_shunt(0.1, 2.0)
|
|
|
|
_VALUES = [50, 84, 150, 280, 540, 1052, 2074, 4120]
|
|
|
|
self.conversion_time_bus = 150
|
|
|
|
|
|
|
|
self.conversion_time_shunt = 280
|
|
|
|
|
|
|
|
self.averaging_count = 16
|
|
|
|
class AveragingCount:
|
|
|
|
|
|
|
|
"""Averaging count constants for INA2XX"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
COUNT_1 = const(0)
|
|
|
|
|
|
|
|
COUNT_4 = const(1)
|
|
|
|
|
|
|
|
COUNT_16 = const(2)
|
|
|
|
|
|
|
|
COUNT_64 = const(3)
|
|
|
|
|
|
|
|
COUNT_128 = const(4)
|
|
|
|
|
|
|
|
COUNT_256 = const(5)
|
|
|
|
|
|
|
|
COUNT_512 = const(6)
|
|
|
|
|
|
|
|
COUNT_1024 = const(7)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
_VALID_COUNTS = {
|
|
|
|
|
|
|
|
COUNT_1,
|
|
|
|
|
|
|
|
COUNT_4,
|
|
|
|
|
|
|
|
COUNT_16,
|
|
|
|
|
|
|
|
COUNT_64,
|
|
|
|
|
|
|
|
COUNT_128,
|
|
|
|
|
|
|
|
COUNT_256,
|
|
|
|
|
|
|
|
COUNT_512,
|
|
|
|
|
|
|
|
COUNT_1024,
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Actual count values for each setting
|
|
|
|
|
|
|
|
_VALUES = [1, 4, 16, 64, 128, 256, 512, 1024]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class AlertType:
|
|
|
|
|
|
|
|
"""Alert type constants for INA2XX"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
NONE = const(0x0)
|
|
|
|
|
|
|
|
CONVERSION_READY = const(0x1)
|
|
|
|
|
|
|
|
OVERPOWER = const(0x2)
|
|
|
|
|
|
|
|
UNDERVOLTAGE = const(0x4)
|
|
|
|
|
|
|
|
OVERVOLTAGE = const(0x8)
|
|
|
|
|
|
|
|
UNDERCURRENT = const(0x10)
|
|
|
|
|
|
|
|
OVERCURRENT = const(0x20)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# INA237/238 specific
|
|
|
|
|
|
|
|
OVERTEMPERATURE = const(0x2)
|
|
|
|
|
|
|
|
UNDERSHUNT = const(0x20)
|
|
|
|
|
|
|
|
OVERSHUNT = const(0x40)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
_VALID_TYPES = {
|
|
|
|
|
|
|
|
NONE,
|
|
|
|
|
|
|
|
CONVERSION_READY,
|
|
|
|
|
|
|
|
OVERPOWER,
|
|
|
|
|
|
|
|
UNDERVOLTAGE,
|
|
|
|
|
|
|
|
OVERVOLTAGE,
|
|
|
|
|
|
|
|
UNDERCURRENT,
|
|
|
|
|
|
|
|
OVERCURRENT,
|
|
|
|
|
|
|
|
OVERTEMPERATURE,
|
|
|
|
|
|
|
|
UNDERSHUNT,
|
|
|
|
|
|
|
|
OVERSHUNT,
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class INA2XX: # noqa: PLR0904
|
|
|
|
|
|
|
|
"""Base driver for INA2XX series power and current sensors.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
:param ~busio.I2C i2c_bus: The I2C bus the INA2XX is connected to.
|
|
|
|
|
|
|
|
:param int address: The I2C device address. Defaults to :const:`0x40`
|
|
|
|
|
|
|
|
:param bool skip_reset: Skip resetting the device on init. Defaults to False.
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Configuration register bits
|
|
|
|
|
|
|
|
_reset = RWBit(_CONFIG, 15, register_width=2, lsb_first=False)
|
|
|
|
|
|
|
|
_adc_range = RWBit(_CONFIG, 4, register_width=2, lsb_first=False)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ADC Configuration register bits
|
|
|
|
|
|
|
|
_mode = RWBits(4, _ADCCFG, 12, register_width=2, lsb_first=False)
|
|
|
|
|
|
|
|
_vbus_conv_time = RWBits(3, _ADCCFG, 9, register_width=2, lsb_first=False)
|
|
|
|
|
|
|
|
_vshunt_conv_time = RWBits(3, _ADCCFG, 6, register_width=2, lsb_first=False)
|
|
|
|
|
|
|
|
_temp_conv_time = RWBits(3, _ADCCFG, 3, register_width=2, lsb_first=False)
|
|
|
|
|
|
|
|
_avg_count = RWBits(3, _ADCCFG, 0, register_width=2, lsb_first=False)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Diagnostic/Alert register bits
|
|
|
|
|
|
|
|
_alert_latch = RWBit(_DIAGALRT, 15, register_width=2, lsb_first=False)
|
|
|
|
|
|
|
|
_alert_conv = RWBit(_DIAGALRT, 14, register_width=2, lsb_first=False)
|
|
|
|
|
|
|
|
_alert_polarity = RWBit(_DIAGALRT, 12, register_width=2, lsb_first=False)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Measurement registers
|
|
|
|
|
|
|
|
_raw_dietemp = ROUnaryStruct(_DIETEMP, ">h")
|
|
|
|
|
|
|
|
_raw_vbus = ROUnaryStruct(_VBUS, ">H")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Calibration register
|
|
|
|
|
|
|
|
_shunt_cal = UnaryStruct(_SHUNTCAL, ">H")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ID registers
|
|
|
|
|
|
|
|
_manufacturer_id = ROUnaryStruct(_MFG_UID, ">H")
|
|
|
|
|
|
|
|
_device_id = ROUnaryStruct(_DVC_UID, ">H")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def __init__(
|
|
|
|
|
|
|
|
self, i2c_bus: I2C, address: int = _INA2XX_DEFAULT_ADDR, skip_reset: bool = False
|
|
|
|
|
|
|
|
) -> None:
|
|
|
|
|
|
|
|
self.i2c_device = I2CDevice(i2c_bus, address)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Verify manufacturer ID
|
|
|
|
|
|
|
|
if self._manufacturer_id != _TEXAS_INSTRUMENTS_ID:
|
|
|
|
|
|
|
|
raise ValueError("Failed to find INA2XX - incorrect manufacturer ID")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
self._shunt_res = 0.1 # Default shunt resistance
|
|
|
|
|
|
|
|
self._current_lsb = 0.0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if not skip_reset:
|
|
|
|
|
|
|
|
self.reset()
|
|
|
|
|
|
|
|
time.sleep(0.002) # 2ms delay for first measurement
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Set defaults
|
|
|
|
|
|
|
|
self.averaging_count = AveragingCount.COUNT_16
|
|
|
|
|
|
|
|
self.bus_voltage_conv_time = ConversionTime.TIME_150_US
|
|
|
|
|
|
|
|
self.shunt_voltage_conv_time = ConversionTime.TIME_280_US
|
|
|
|
|
|
|
|
self.mode = Mode.CONTINUOUS
|
|
|
|
|
|
|
|
|
|
|
|
def reset(self) -> None:
|
|
|
|
def reset(self) -> None:
|
|
|
|
"""Reset the INA228"""
|
|
|
|
"""Reset the sensor to default configuration."""
|
|
|
|
self._config = 0x8000
|
|
|
|
self._reset = True
|
|
|
|
|
|
|
|
self._alert_conv = True
|
|
|
|
|
|
|
|
self.mode = Mode.CONTINUOUS
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
|
|
|
def device_id(self) -> int:
|
|
|
|
|
|
|
|
"""Device ID"""
|
|
|
|
|
|
|
|
return (self._device_id >> 4) & 0xFFF
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
|
|
|
def shunt_resistance(self) -> float:
|
|
|
|
|
|
|
|
"""The shunt resistance in ohms."""
|
|
|
|
|
|
|
|
return self._shunt_res
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
|
|
|
def adc_range(self) -> int:
|
|
|
|
|
|
|
|
"""ADC range setting. 0 = ±163.84mV, 1 = ±40.96mV"""
|
|
|
|
|
|
|
|
return self._adc_range
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@adc_range.setter
|
|
|
|
|
|
|
|
def adc_range(self, value: int) -> None:
|
|
|
|
|
|
|
|
if value not in {0, 1}:
|
|
|
|
|
|
|
|
raise ValueError("ADC range must be 0 or 1")
|
|
|
|
|
|
|
|
self._adc_range = value
|
|
|
|
|
|
|
|
self._update_shunt_cal()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
|
|
|
def mode(self) -> int:
|
|
|
|
|
|
|
|
"""Operating mode of the sensor."""
|
|
|
|
|
|
|
|
return self._mode
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@mode.setter
|
|
|
|
|
|
|
|
def mode(self, value: int) -> None:
|
|
|
|
|
|
|
|
if value not in Mode._VALID_MODES:
|
|
|
|
|
|
|
|
raise ValueError(f"Invalid mode 0x{value:02X}. Must be one of the Mode.* constants")
|
|
|
|
|
|
|
|
self._mode = value
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
|
|
|
def averaging_count(self) -> int:
|
|
|
|
|
|
|
|
"""Number of samples to average."""
|
|
|
|
|
|
|
|
return self._avg_count
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@averaging_count.setter
|
|
|
|
|
|
|
|
def averaging_count(self, value: int) -> None:
|
|
|
|
|
|
|
|
if value not in AveragingCount._VALID_COUNTS:
|
|
|
|
|
|
|
|
raise ValueError(
|
|
|
|
|
|
|
|
f"Invalid averaging count {value}. Must be one of the AveragingCount.* constants"
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
self._avg_count = value
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
|
|
|
def bus_voltage_conv_time(self) -> int:
|
|
|
|
|
|
|
|
"""Bus voltage conversion time setting."""
|
|
|
|
|
|
|
|
return self._vbus_conv_time
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@bus_voltage_conv_time.setter
|
|
|
|
|
|
|
|
def bus_voltage_conv_time(self, value: int) -> None:
|
|
|
|
|
|
|
|
if value not in ConversionTime._VALID_TIMES:
|
|
|
|
|
|
|
|
raise ValueError(
|
|
|
|
|
|
|
|
f"Invalid conversion time {value}. Must be one of the ConversionTime.* constants"
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
self._vbus_conv_time = value
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
|
|
|
def shunt_voltage_conv_time(self) -> int:
|
|
|
|
|
|
|
|
"""Shunt voltage conversion time setting."""
|
|
|
|
|
|
|
|
return self._vshunt_conv_time
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@shunt_voltage_conv_time.setter
|
|
|
|
|
|
|
|
def shunt_voltage_conv_time(self, value: int) -> None:
|
|
|
|
|
|
|
|
if value not in ConversionTime._VALID_TIMES:
|
|
|
|
|
|
|
|
raise ValueError(
|
|
|
|
|
|
|
|
f"Invalid conversion time {value}. Must be one of the ConversionTime.* constants"
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
self._vshunt_conv_time = value
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
|
|
|
def temp_conv_time(self) -> int:
|
|
|
|
|
|
|
|
"""Temperature conversion time setting."""
|
|
|
|
|
|
|
|
return self._temp_conv_time
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@temp_conv_time.setter
|
|
|
|
|
|
|
|
def temp_conv_time(self, value: int) -> None:
|
|
|
|
|
|
|
|
if value not in ConversionTime._VALID_TIMES:
|
|
|
|
|
|
|
|
raise ValueError(
|
|
|
|
|
|
|
|
f"Invalid conversion time {value}. Must be one of the ConversionTime.* constants"
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
self._temp_conv_time = value
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
|
|
|
def alert_polarity(self) -> int:
|
|
|
|
|
|
|
|
"""Alert pin polarity. 0 = active high, 1 = active low."""
|
|
|
|
|
|
|
|
return self._alert_polarity
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@alert_polarity.setter
|
|
|
|
|
|
|
|
def alert_polarity(self, value: int) -> None:
|
|
|
|
|
|
|
|
if value not in {0, 1}:
|
|
|
|
|
|
|
|
raise ValueError("Alert polarity must be 0 or 1")
|
|
|
|
|
|
|
|
self._alert_polarity = value
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
|
|
|
def alert_latch(self) -> int:
|
|
|
|
|
|
|
|
"""Alert latch enable. 0 = transparent, 1 = latched."""
|
|
|
|
|
|
|
|
return self._alert_latch
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@alert_latch.setter
|
|
|
|
|
|
|
|
def alert_latch(self, value: int) -> None:
|
|
|
|
|
|
|
|
if value not in {0, 1}:
|
|
|
|
|
|
|
|
raise ValueError("Alert latch must be 0 or 1")
|
|
|
|
|
|
|
|
self._alert_latch = value
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class INA228(INA2XX): # noqa: PLR0904
|
|
|
|
|
|
|
|
"""Driver for the INA228 power and current sensor"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Additional registers for INA228
|
|
|
|
|
|
|
|
_reset_accumulators_bit = RWBit(_CONFIG, 14, register_width=2, lsb_first=False)
|
|
|
|
|
|
|
|
_alert_type = RWBits(6, _DIAGALRT, 8, register_width=2, lsb_first=False)
|
|
|
|
|
|
|
|
_alert_flags = ROUnaryStruct(_DIAGALRT, ">H")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
_shunt_tempco = UnaryStruct(_SHUNT_TEMPCO, ">H")
|
|
|
|
|
|
|
|
_sovl = UnaryStruct(_SOVL, ">H")
|
|
|
|
|
|
|
|
_suvl = UnaryStruct(_SUVL, ">H")
|
|
|
|
|
|
|
|
_bovl = UnaryStruct(_BOVL, ">H")
|
|
|
|
|
|
|
|
_buvl = UnaryStruct(_BUVL, ">H")
|
|
|
|
|
|
|
|
_temp_limit = UnaryStruct(_TEMP_LIMIT, ">H")
|
|
|
|
|
|
|
|
_pwr_limit = UnaryStruct(_PWR_LIMIT, ">H")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
_raw_vshunt = ROUnaryStruct(_VSHUNT, ">h")
|
|
|
|
|
|
|
|
_raw_current = ROUnaryStruct(_CURRENT, ">h")
|
|
|
|
|
|
|
|
_raw_power = ROUnaryStruct(_POWER, ">H")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
_conversion_ready = ROBit(_DIAGALRT, 1, register_width=2, lsb_first=False)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def __init__(self, i2c_bus: I2C, address: int = 0x40, skip_reset: bool = False) -> None:
|
|
|
|
|
|
|
|
# Initialize 24-bit and 40-bit register buffers
|
|
|
|
|
|
|
|
self.buf3 = bytearray(3) # Buffer for 24-bit registers
|
|
|
|
|
|
|
|
self.buf5 = bytearray(5) # Buffer for 40-bit registers
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
super().__init__(i2c_bus, address, skip_reset)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Verify device ID
|
|
|
|
|
|
|
|
dev_id = self.device_id
|
|
|
|
|
|
|
|
if dev_id != _INA228_DEVICE_ID:
|
|
|
|
|
|
|
|
raise RuntimeError(f"Failed to find INA228 - check your wiring! (Got ID: 0x{dev_id:X})")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Set INA228 defaults
|
|
|
|
|
|
|
|
self.set_calibration(0.015, 10.0)
|
|
|
|
|
|
|
|
|
|
|
|
def _reg24(self, reg):
|
|
|
|
def _reg24(self, reg):
|
|
|
|
"""Read 24-bit register"""
|
|
|
|
"""Read 24-bit register"""
|
|
|
|
@ -152,247 +426,125 @@ class INA228: # noqa: PLR0904
|
|
|
|
|
|
|
|
|
|
|
|
def reset_accumulators(self) -> None:
|
|
|
|
def reset_accumulators(self) -> None:
|
|
|
|
"""Reset the energy and charge accumulators"""
|
|
|
|
"""Reset the energy and charge accumulators"""
|
|
|
|
self._config = 1 << 14
|
|
|
|
self._reset_accumulators_bit = True
|
|
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
def set_calibration(self, shunt_res: float = 0.015, max_current: float = 10.0) -> None:
|
|
|
|
def conversion_time_bus(self) -> int:
|
|
|
|
"""Set the calibration based on shunt resistance and maximum expected current.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
:param float shunt_res: Shunt resistance in ohms
|
|
|
|
|
|
|
|
:param float max_current: Maximum expected current in amperes
|
|
|
|
"""
|
|
|
|
"""
|
|
|
|
Bus voltage conversion time in microseconds.
|
|
|
|
|
|
|
|
Valid values are: 50, 84, 150, 280, 540, 1052, 2074, 4120.
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
times = [50, 84, 150, 280, 540, 1052, 2074, 4120]
|
|
|
|
|
|
|
|
return times[self._vbus_ct]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@conversion_time_bus.setter
|
|
|
|
|
|
|
|
def conversion_time_bus(self, usec: int):
|
|
|
|
|
|
|
|
times = [50, 84, 150, 280, 540, 1052, 2074, 4120]
|
|
|
|
|
|
|
|
if usec not in times:
|
|
|
|
|
|
|
|
raise ValueError(
|
|
|
|
|
|
|
|
f"Invalid conversion time: {usec}. Valid values are: {', '.join(map(str, times))}."
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
self._vbus_ct = times.index(usec)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
|
|
|
def conversion_time_shunt(self) -> int:
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
Shunt voltage conversion time in microseconds.
|
|
|
|
|
|
|
|
Valid values are: 50, 84, 150, 280, 540, 1052, 2074, 4120.
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
times = [50, 84, 150, 280, 540, 1052, 2074, 4120]
|
|
|
|
|
|
|
|
return times[self._vshunt_ct]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@conversion_time_shunt.setter
|
|
|
|
|
|
|
|
def conversion_time_shunt(self, usec: int):
|
|
|
|
|
|
|
|
times = [50, 84, 150, 280, 540, 1052, 2074, 4120]
|
|
|
|
|
|
|
|
if usec not in times:
|
|
|
|
|
|
|
|
raise ValueError(
|
|
|
|
|
|
|
|
f"Invalid conversion time: {usec}. Valid values are: {', '.join(map(str, times))}."
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
self._vshunt_ct = times.index(usec)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
|
|
|
def averaging_count(self) -> int:
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
Number of samples to average. Returns actual count.
|
|
|
|
|
|
|
|
Valid values are: 1, 4, 16, 64, 128, 256, 512, 1024.
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
counts = [1, 4, 16, 64, 128, 256, 512, 1024]
|
|
|
|
|
|
|
|
return counts[self._avg_count]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@averaging_count.setter
|
|
|
|
|
|
|
|
def averaging_count(self, count: int):
|
|
|
|
|
|
|
|
counts = [1, 4, 16, 64, 128, 256, 512, 1024]
|
|
|
|
|
|
|
|
if count not in counts:
|
|
|
|
|
|
|
|
raise ValueError(
|
|
|
|
|
|
|
|
"Invalid averaging count: "
|
|
|
|
|
|
|
|
+ str(count)
|
|
|
|
|
|
|
|
+ ". "
|
|
|
|
|
|
|
|
+ "Valid values are: "
|
|
|
|
|
|
|
|
+ ", ".join(map(str, counts))
|
|
|
|
|
|
|
|
+ "."
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
self._avg_count = counts.index(count)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def set_shunt(self, shunt_res: float, max_current: float) -> None:
|
|
|
|
|
|
|
|
"""Configure shunt resistor value and maximum expected current"""
|
|
|
|
|
|
|
|
self._shunt_res = shunt_res
|
|
|
|
self._shunt_res = shunt_res
|
|
|
|
|
|
|
|
# INA228 uses 2^19 as divisor
|
|
|
|
self._current_lsb = max_current / (1 << 19)
|
|
|
|
self._current_lsb = max_current / (1 << 19)
|
|
|
|
self._update_calibration()
|
|
|
|
self._update_shunt_cal()
|
|
|
|
time.sleep(0.001)
|
|
|
|
time.sleep(0.001)
|
|
|
|
|
|
|
|
|
|
|
|
def _update_calibration(self):
|
|
|
|
def _update_shunt_cal(self) -> None:
|
|
|
|
"""Update the calibration register based on shunt and current settings"""
|
|
|
|
"""Update the shunt calibration register."""
|
|
|
|
scale = 4 if self._adc_range else 1
|
|
|
|
scale = 4 if self._adc_range else 1
|
|
|
|
|
|
|
|
# INA228 formula: SHUNT_CAL = 13107.2 × 10^6 × CURRENT_LSB × RSHUNT × scale
|
|
|
|
cal_value = int(13107.2 * 1000000.0 * self._shunt_res * self._current_lsb * scale)
|
|
|
|
cal_value = int(13107.2 * 1000000.0 * self._shunt_res * self._current_lsb * scale)
|
|
|
|
self._shunt_cal = cal_value
|
|
|
|
self._shunt_cal = cal_value
|
|
|
|
read_cal = self._shunt_cal
|
|
|
|
|
|
|
|
if read_cal != cal_value:
|
|
|
|
|
|
|
|
raise ValueError(" Warning: Calibration readback mismatch!")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def set_calibration_32V_2A(self) -> None:
|
|
|
|
|
|
|
|
"""Configure for 32V and up to 2A measurements"""
|
|
|
|
|
|
|
|
self._mode = Mode.CONTINUOUS_ALL
|
|
|
|
|
|
|
|
time.sleep(0.001)
|
|
|
|
|
|
|
|
self.set_shunt(0.1, 2.0)
|
|
|
|
|
|
|
|
self._vbus_ct = 5
|
|
|
|
|
|
|
|
self._vshunt_ct = 5
|
|
|
|
|
|
|
|
self._temper_ct = 5
|
|
|
|
|
|
|
|
self._avg_count = 0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def set_calibration_32V_1A(self) -> None:
|
|
|
|
|
|
|
|
"""Configure for 32V and up to 1A measurements"""
|
|
|
|
|
|
|
|
self.set_shunt(0.1, 1.0)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def set_calibration_16V_400mA(self) -> None:
|
|
|
|
|
|
|
|
"""Configure for 16V and up to 400mA measurements"""
|
|
|
|
|
|
|
|
self.set_shunt(0.1, 0.4)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
@property
|
|
|
|
def conversion_ready(self) -> bool:
|
|
|
|
def die_temperature(self) -> float:
|
|
|
|
"""Check if conversion is ready"""
|
|
|
|
"""Die temperature in degrees Celsius."""
|
|
|
|
return bool(self._diag_alrt & (1 << 1))
|
|
|
|
# INA228 uses 7.8125 m°C/LSB
|
|
|
|
|
|
|
|
return self._raw_dietemp * 7.8125e-3
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
|
|
|
def bus_voltage(self) -> float:
|
|
|
|
|
|
|
|
"""Bus voltage in volts."""
|
|
|
|
|
|
|
|
raw = self._reg24(_VBUS)
|
|
|
|
|
|
|
|
# INA228 uses 195.3125 µV/LSB
|
|
|
|
|
|
|
|
return (raw >> 4) * 195.3125e-6
|
|
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
@property
|
|
|
|
def shunt_voltage(self) -> float:
|
|
|
|
def shunt_voltage(self) -> float:
|
|
|
|
"""Shunt voltage in V"""
|
|
|
|
"""Shunt voltage in volts."""
|
|
|
|
raw = self._reg24(_VSHUNT)
|
|
|
|
raw = self._reg24(_VSHUNT)
|
|
|
|
if raw & 0x800000:
|
|
|
|
if raw & 0x800000:
|
|
|
|
raw -= 0x1000000
|
|
|
|
raw -= 0x1000000
|
|
|
|
|
|
|
|
# Scale depends on ADC range
|
|
|
|
scale = 78.125e-9 if self._adc_range else 312.5e-9
|
|
|
|
scale = 78.125e-9 if self._adc_range else 312.5e-9
|
|
|
|
return (raw / 16.0) * scale
|
|
|
|
return (raw / 16.0) * scale
|
|
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
@property
|
|
|
|
def voltage(self) -> float:
|
|
|
|
def current(self) -> float:
|
|
|
|
"""Bus voltage measurement in V"""
|
|
|
|
"""Current in amperes."""
|
|
|
|
raw = self._reg24(_VBUS)
|
|
|
|
raw = self._reg24(_CURRENT)
|
|
|
|
value = (raw >> 4) * 195.3125e-6
|
|
|
|
if raw & 0x800000:
|
|
|
|
return value
|
|
|
|
raw -= 0x1000000
|
|
|
|
|
|
|
|
return (raw / 16.0) * self._current_lsb
|
|
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
@property
|
|
|
|
def power(self) -> float:
|
|
|
|
def power(self) -> float:
|
|
|
|
"""Power measurement in mW"""
|
|
|
|
"""Power in watts."""
|
|
|
|
raw = self._reg24(_POWER)
|
|
|
|
raw = self._reg24(_POWER)
|
|
|
|
value = raw * 3.2 * self._current_lsb * 1000
|
|
|
|
# INA228 power LSB = 3.2 × current_lsb
|
|
|
|
return value
|
|
|
|
return raw * 3.2 * self._current_lsb
|
|
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
@property
|
|
|
|
def energy(self) -> float:
|
|
|
|
def energy(self) -> float:
|
|
|
|
"""Energy measurement in Joules"""
|
|
|
|
"""Energy measurement in Joules"""
|
|
|
|
raw = self._reg40(_ENERGY)
|
|
|
|
raw = self._reg40(_ENERGY)
|
|
|
|
value = raw * 16.0 * 3.2 * self._current_lsb
|
|
|
|
return raw * 16.0 * 3.2 * self._current_lsb
|
|
|
|
return value
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
|
|
|
def current(self) -> float:
|
|
|
|
|
|
|
|
"""Current measurement in mA"""
|
|
|
|
|
|
|
|
raw = self._reg24(_CURRENT)
|
|
|
|
|
|
|
|
if raw & 0x800000:
|
|
|
|
|
|
|
|
raw -= 0x1000000
|
|
|
|
|
|
|
|
value = (raw / 16.0) * self._current_lsb * 1000.0
|
|
|
|
|
|
|
|
return value
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
@property
|
|
|
|
def charge(self) -> float:
|
|
|
|
def charge(self) -> float:
|
|
|
|
"""Accumulated charge in coulombs"""
|
|
|
|
"""Accumulated charge in coulombs"""
|
|
|
|
raw = self._reg40(_CHARGE)
|
|
|
|
raw = self._reg40(_CHARGE)
|
|
|
|
|
|
|
|
if raw & (1 << 39):
|
|
|
|
|
|
|
|
raw |= -1 << 40
|
|
|
|
return raw * self._current_lsb
|
|
|
|
return raw * self._current_lsb
|
|
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
@property
|
|
|
|
def temperature(self) -> float:
|
|
|
|
def conversion_ready(self) -> bool:
|
|
|
|
"""Die temperature in celsius"""
|
|
|
|
"""Check if conversion is complete."""
|
|
|
|
return self._temperature * 7.8125e-3
|
|
|
|
return bool(self._conversion_ready)
|
|
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
@property
|
|
|
|
def shunt_tempco(self) -> int:
|
|
|
|
def alert_type(self) -> int:
|
|
|
|
"""Shunt temperature coefficient in ppm/°C"""
|
|
|
|
"""Alert type configuration."""
|
|
|
|
return self._shunt_tempco
|
|
|
|
return self._alert_type
|
|
|
|
|
|
|
|
|
|
|
|
@shunt_tempco.setter
|
|
|
|
@alert_type.setter
|
|
|
|
def shunt_tempco(self, value: int):
|
|
|
|
def alert_type(self, value: int) -> None:
|
|
|
|
self._shunt_tempco = value
|
|
|
|
# Alert type can be a combination of flags
|
|
|
|
|
|
|
|
valid_mask = (
|
|
|
|
@property
|
|
|
|
AlertType.CONVERSION_READY
|
|
|
|
def conversion_time_temperature(self) -> int:
|
|
|
|
| AlertType.OVERPOWER
|
|
|
|
"""
|
|
|
|
| AlertType.UNDERVOLTAGE
|
|
|
|
Temperature conversion time in microseconds.
|
|
|
|
| AlertType.OVERVOLTAGE
|
|
|
|
Valid values are: 50, 84, 150, 280, 540, 1052, 2074, 4120.
|
|
|
|
| AlertType.UNDERCURRENT
|
|
|
|
"""
|
|
|
|
| AlertType.OVERCURRENT
|
|
|
|
times = [50, 84, 150, 280, 540, 1052, 2074, 4120]
|
|
|
|
)
|
|
|
|
return times[self._temper_ct]
|
|
|
|
if value & ~valid_mask:
|
|
|
|
|
|
|
|
|
|
|
|
@conversion_time_temperature.setter
|
|
|
|
|
|
|
|
def conversion_time_temperature(self, usec: int):
|
|
|
|
|
|
|
|
times = [50, 84, 150, 280, 540, 1052, 2074, 4120]
|
|
|
|
|
|
|
|
if usec not in times:
|
|
|
|
|
|
|
|
raise ValueError(
|
|
|
|
raise ValueError(
|
|
|
|
f"Invalid conversion time: {usec}. Valid values are: {', '.join(map(str, times))}."
|
|
|
|
f"Invalid alert type 0x{value:02X}. Must be a combination of AlertType.* constants"
|
|
|
|
)
|
|
|
|
)
|
|
|
|
self._temper_ct = times.index(usec)
|
|
|
|
self._alert_type = value
|
|
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
|
|
|
def alert_latch(self) -> bool:
|
|
|
|
|
|
|
|
"""Alert latch setting. True=latched, False=transparent"""
|
|
|
|
|
|
|
|
return bool(self._diag_alrt & (1 << 15))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@alert_latch.setter
|
|
|
|
|
|
|
|
def alert_latch(self, value: bool):
|
|
|
|
|
|
|
|
if value:
|
|
|
|
|
|
|
|
self._diag_alrt |= 1 << 15
|
|
|
|
|
|
|
|
else:
|
|
|
|
|
|
|
|
self._diag_alrt &= ~(1 << 15)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
|
|
|
def alert_polarity(self) -> bool:
|
|
|
|
|
|
|
|
"""Alert polarity. True=inverted, False=normal"""
|
|
|
|
|
|
|
|
return bool(self._diag_alrt & (1 << 12))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@alert_polarity.setter
|
|
|
|
|
|
|
|
def alert_polarity(self, value: bool):
|
|
|
|
|
|
|
|
if value:
|
|
|
|
|
|
|
|
self._diag_alrt |= 1 << 12
|
|
|
|
|
|
|
|
else:
|
|
|
|
|
|
|
|
self._diag_alrt &= ~(1 << 12)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
|
|
|
def shunt_voltage_overlimit(self) -> float:
|
|
|
|
|
|
|
|
"""Shunt voltage overlimit threshold in volts"""
|
|
|
|
|
|
|
|
return self._sovl * (78.125e-6 if self._adc_range else 312.5e-6)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@shunt_voltage_overlimit.setter
|
|
|
|
|
|
|
|
def shunt_voltage_overlimit(self, value: float):
|
|
|
|
|
|
|
|
scale = 78.125e-6 if self._adc_range else 312.5e-6
|
|
|
|
|
|
|
|
self._sovl = int(value / scale)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
@property
|
|
|
|
def alert_flags(self) -> dict:
|
|
|
|
def alert_flags(self) -> dict:
|
|
|
|
"""
|
|
|
|
"""
|
|
|
|
Get all diagnostic and alert flags
|
|
|
|
All diagnostic and alert flags
|
|
|
|
|
|
|
|
|
|
|
|
Returns a dictionary with the status of each flag:
|
|
|
|
Returns a dictionary with the status of each flag:
|
|
|
|
|
|
|
|
- 'ENERGYOF': bool - Energy overflow
|
|
|
|
'ENERGYOF': bool, # Energy overflow
|
|
|
|
- 'CHARGEOF': bool - Charge overflow
|
|
|
|
|
|
|
|
- 'MATHOF': bool - Math overflow
|
|
|
|
'CHARGEOF': bool, # Charge overflow
|
|
|
|
- 'TMPOL': bool - Temperature overlimit
|
|
|
|
|
|
|
|
- 'SHNTOL': bool - Shunt voltage overlimit
|
|
|
|
'MATHOF': bool, # Math overflow
|
|
|
|
- 'SHNTUL': bool - Shunt voltage underlimit
|
|
|
|
|
|
|
|
- 'BUSOL': bool - Bus voltage overlimit
|
|
|
|
'TMPOL': bool, # Temperature overlimit
|
|
|
|
- 'BUSUL': bool - Bus voltage underlimit
|
|
|
|
|
|
|
|
- 'POL': bool - Power overlimit
|
|
|
|
'SHNTOL': bool, # Shunt voltage overlimit
|
|
|
|
- 'CNVRF': bool - Conversion ready
|
|
|
|
|
|
|
|
- 'MEMSTAT': bool - ADC conversion status
|
|
|
|
'SHNTUL': bool, # Shunt voltage underlimit
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
'BUSOL': bool, # Bus voltage overlimit
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
'BUSUL': bool, # Bus voltage underlimit
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
'POL': bool, # Power overlimit
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
'CNVRF': bool, # Conversion ready
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
'MEMSTAT': bool, # ADC conversion status
|
|
|
|
|
|
|
|
"""
|
|
|
|
"""
|
|
|
|
flags = self._diag_alrt
|
|
|
|
flags = self._alert_flags
|
|
|
|
return {
|
|
|
|
return {
|
|
|
|
"ENERGYOF": bool(flags & (1 << 11)),
|
|
|
|
"ENERGYOF": bool(flags & (1 << 11)),
|
|
|
|
"CHARGEOF": bool(flags & (1 << 10)),
|
|
|
|
"CHARGEOF": bool(flags & (1 << 10)),
|
|
|
|
@ -407,13 +559,100 @@ class INA228: # noqa: PLR0904
|
|
|
|
"MEMSTAT": bool(flags & (1 << 0)),
|
|
|
|
"MEMSTAT": bool(flags & (1 << 0)),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
|
|
|
def shunt_tempco(self) -> int:
|
|
|
|
|
|
|
|
"""Shunt temperature coefficient in ppm/°C"""
|
|
|
|
|
|
|
|
return self._shunt_tempco
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@shunt_tempco.setter
|
|
|
|
|
|
|
|
def shunt_tempco(self, value: int):
|
|
|
|
|
|
|
|
self._shunt_tempco = value
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
|
|
|
def shunt_voltage_overlimit(self) -> float:
|
|
|
|
|
|
|
|
"""Shunt voltage overlimit threshold in volts"""
|
|
|
|
|
|
|
|
return self._sovl * (78.125e-6 if self._adc_range else 312.5e-6)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@shunt_voltage_overlimit.setter
|
|
|
|
|
|
|
|
def shunt_voltage_overlimit(self, value: float):
|
|
|
|
|
|
|
|
scale = 78.125e-6 if self._adc_range else 312.5e-6
|
|
|
|
|
|
|
|
self._sovl = int(value / scale)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
|
|
|
def shunt_voltage_underlimit(self) -> float:
|
|
|
|
|
|
|
|
"""Shunt voltage underlimit threshold in volts"""
|
|
|
|
|
|
|
|
return self._suvl * (78.125e-6 if self._adc_range else 312.5e-6)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@shunt_voltage_underlimit.setter
|
|
|
|
|
|
|
|
def shunt_voltage_underlimit(self, value: float):
|
|
|
|
|
|
|
|
scale = 78.125e-6 if self._adc_range else 312.5e-6
|
|
|
|
|
|
|
|
self._suvl = int(value / scale)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
|
|
|
def bus_voltage_overlimit(self) -> float:
|
|
|
|
|
|
|
|
"""Bus voltage overlimit threshold in volts"""
|
|
|
|
|
|
|
|
return self._bovl * 3.125e-3
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@bus_voltage_overlimit.setter
|
|
|
|
|
|
|
|
def bus_voltage_overlimit(self, value: float):
|
|
|
|
|
|
|
|
self._bovl = int(value / 3.125e-3)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
|
|
|
def bus_voltage_underlimit(self) -> float:
|
|
|
|
|
|
|
|
"""Bus voltage underlimit threshold in volts"""
|
|
|
|
|
|
|
|
return self._buvl * 3.125e-3
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@bus_voltage_underlimit.setter
|
|
|
|
|
|
|
|
def bus_voltage_underlimit(self, value: float):
|
|
|
|
|
|
|
|
self._buvl = int(value / 3.125e-3)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
|
|
|
def temperature_limit(self) -> float:
|
|
|
|
|
|
|
|
"""Temperature overlimit threshold in degrees Celsius"""
|
|
|
|
|
|
|
|
return self._temp_limit * 7.8125e-3
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@temperature_limit.setter
|
|
|
|
|
|
|
|
def temperature_limit(self, value: float):
|
|
|
|
|
|
|
|
self._temp_limit = int(value / 7.8125e-3)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
|
|
|
def power_limit(self) -> float:
|
|
|
|
|
|
|
|
"""Power overlimit threshold in watts"""
|
|
|
|
|
|
|
|
# Power limit LSB = 256 × current_lsb × 3.2
|
|
|
|
|
|
|
|
return self._pwr_limit * 256 * self._current_lsb * 3.2
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@power_limit.setter
|
|
|
|
|
|
|
|
def power_limit(self, value: float):
|
|
|
|
|
|
|
|
self._pwr_limit = int(value / (256 * self._current_lsb * 3.2))
|
|
|
|
|
|
|
|
|
|
|
|
def trigger_measurement(self) -> None:
|
|
|
|
def trigger_measurement(self) -> None:
|
|
|
|
"""Trigger a one-shot measurement when in triggered mode"""
|
|
|
|
"""Trigger a one-shot measurement when in triggered mode"""
|
|
|
|
current_mode = self.mode
|
|
|
|
current_mode = self.mode
|
|
|
|
if current_mode < Mode.SHUTDOWN2:
|
|
|
|
if current_mode <= Mode.TRIG_TEMP_BUS_SHUNT:
|
|
|
|
|
|
|
|
# Re-write the same mode to trigger measurement
|
|
|
|
self.mode = current_mode
|
|
|
|
self.mode = current_mode
|
|
|
|
|
|
|
|
|
|
|
|
def clear_overflow_flags(self) -> None:
|
|
|
|
def clear_overflow_flags(self) -> None:
|
|
|
|
"""Clear energy, charge, and math overflow flags"""
|
|
|
|
"""Clear energy, charge, and math overflow flags"""
|
|
|
|
flags = self._diag_alrt
|
|
|
|
# Read-modify-write to clear only overflow flags
|
|
|
|
self._diag_alrt = flags & ~((1 << 11) | (1 << 10) | (1 << 9))
|
|
|
|
flags = self._alert_flags
|
|
|
|
|
|
|
|
self._alert_flags = flags & ~((1 << 11) | (1 << 10) | (1 << 9))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Convenience calibration methods
|
|
|
|
|
|
|
|
def set_calibration_32V_2A(self) -> None:
|
|
|
|
|
|
|
|
"""Configure for 32V and up to 2A measurements"""
|
|
|
|
|
|
|
|
self.mode = Mode.CONTINUOUS
|
|
|
|
|
|
|
|
time.sleep(0.001)
|
|
|
|
|
|
|
|
self.set_calibration(0.015, 10.0)
|
|
|
|
|
|
|
|
self.bus_voltage_conv_time = ConversionTime.TIME_1052_US
|
|
|
|
|
|
|
|
self.shunt_voltage_conv_time = ConversionTime.TIME_1052_US
|
|
|
|
|
|
|
|
self.temp_conv_time = ConversionTime.TIME_1052_US
|
|
|
|
|
|
|
|
self.averaging_count = AveragingCount.COUNT_1
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def set_calibration_32V_1A(self) -> None:
|
|
|
|
|
|
|
|
"""Configure for 32V and up to 1A measurements"""
|
|
|
|
|
|
|
|
self.set_calibration(0.1, 1.0)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def set_calibration_16V_400mA(self) -> None:
|
|
|
|
|
|
|
|
"""Configure for 16V and up to 400mA measurements"""
|
|
|
|
|
|
|
|
self.set_calibration(0.1, 0.4)
|
|
|
|
|