Compare commits

..

No commits in common. "main" and "lib_files" have entirely different histories.

3 changed files with 279 additions and 519 deletions

View file

@ -30,7 +30,6 @@ This driver depends on:
* `Adafruit CircuitPython <https://github.com/adafruit/circuitpython>`_ * `Adafruit CircuitPython <https://github.com/adafruit/circuitpython>`_
* `Bus Device <https://github.com/adafruit/Adafruit_CircuitPython_BusDevice>`_ * `Bus Device <https://github.com/adafruit/Adafruit_CircuitPython_BusDevice>`_
* `Register <https://github.com/adafruit/Adafruit_CircuitPython_Register>`_
Please ensure all dependencies are available on the CircuitPython filesystem. Please ensure all dependencies are available on the CircuitPython filesystem.
This is easily achieved by downloading This is easily achieved by downloading
@ -103,11 +102,11 @@ Usage Example
while True: while True:
print(f"Current: {ina228.current:.2f} mA") print(f"Current: {ina228.current:.2f} mA")
print(f"Bus Voltage: {ina228.bus_voltage:.2f} V") print(f"Bus Voltage: {ina228.voltage:.2f} V")
print(f"Shunt Voltage: {ina228.shunt_voltage*1000:.2f} mV") print(f"Shunt Voltage: {ina228.shunt_voltage*1000:.2f} mV")
print(f"Power: {ina228.power:.2f} mW") print(f"Power: {ina228.power:.2f} mW")
print(f"Energy: {ina228.energy:.2f} J") print(f"Energy: {ina228.energy:.2f} J")
print(f"Temperature: {ina228.die_temperature:.2f} °C") print(f"Temperature: {ina228.temperature:.2f} °C")
time.sleep(1) time.sleep(1)
Documentation Documentation

View file

@ -15,7 +15,7 @@ Implementation Notes
**Hardware:** **Hardware:**
* `Adafruit INA228 High or Low Side Current and Power Monitor <https://www.adafruit.com/product/5832>`_ * `Adafruit INA228 High 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 ROBit, RWBit from adafruit_register.i2c_bit import RWBit
from adafruit_register.i2c_bits import ROBits, RWBits from adafruit_register.i2c_bits import RWBits
from adafruit_register.i2c_struct import ROUnaryStruct, UnaryStruct from adafruit_register.i2c_struct import UnaryStruct
from micropython import const from micropython import const
try: try:
@ -43,370 +43,96 @@ 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"
# Common register addresses for INA2XX family # Register addresses
_CONFIG = const(0x00) _CONFIG = const(0x00) # Configuration Register
_ADCCFG = const(0x01) _ADC_CONFIG = const(0x01) # ADC Configuration Register
_SHUNTCAL = const(0x02) _SHUNT_CAL = const(0x02) # Shunt Calibration Register
_VSHUNT = const(0x04) _SHUNT_TEMPCO = const(0x03) # Shunt Temperature Coefficient Register
_VBUS = const(0x05) _VSHUNT = const(0x04) # Shunt Voltage Measurement
_DIETEMP = const(0x06) _VBUS = const(0x05) # Bus Voltage Measurement
_CURRENT = const(0x07) _DIETEMP = const(0x06) # Temperature Measurement
_POWER = const(0x08) _CURRENT = const(0x07) # Current Result
_DIAGALRT = const(0x0B) _POWER = const(0x08) # Power Result
_MFG_UID = const(0x3E) _ENERGY = const(0x09) # Energy Result
_DVC_UID = const(0x3F) _CHARGE = const(0x0A) # Charge Result
_DIAG_ALRT = const(0x0B) # Diagnostic Flags and Alert
# INA228-specific registers _SOVL = const(0x0C) # Shunt Overvoltage Threshold
_SHUNT_TEMPCO = const(0x03) _SUVL = const(0x0D) # Shunt Undervoltage Threshold
_ENERGY = const(0x09) _BOVL = const(0x0E) # Bus Overvoltage Threshold
_CHARGE = const(0x0A) _BUVL = const(0x0F) # Bus Undervoltage Threshold
_SOVL = const(0x0C) _TEMP_LIMIT = const(0x10) # Temperature Over-Limit Threshold
_SUVL = const(0x0D) _PWR_LIMIT = const(0x11) # Power Over-Limit Threshold
_BOVL = const(0x0E) _MFG_ID = const(0x3E) # Manufacturer ID
_BUVL = const(0x0F) _DEVICE_ID = const(0x3F) # Device ID
_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:
"""Operating mode constants for INA2XX""" """Constants for operating modes"""
SHUTDOWN = const(0x00) SHUTDOWN = 0x00
TRIG_BUS = const(0x01) TRIGGERED_BUS = 0x01
TRIG_SHUNT = const(0x02) TRIGGERED_SHUNT = 0x02
TRIG_BUS_SHUNT = const(0x03) TRIGGERED_BUS_SHUNT = 0x03
TRIG_TEMP = const(0x04) TRIGGERED_TEMP = 0x04
TRIG_TEMP_BUS = const(0x05) TRIGGERED_TEMP_BUS = 0x05
TRIG_TEMP_SHUNT = const(0x06) TRIGGERED_TEMP_SHUNT = 0x06
TRIG_TEMP_BUS_SHUNT = const(0x07) TRIGGERED_ALL = 0x07
CONT_BUS = const(0x09) SHUTDOWN2 = 0x08
CONT_SHUNT = const(0x0A) CONTINUOUS_BUS = 0x09
CONT_BUS_SHUNT = const(0x0B) CONTINUOUS_SHUNT = 0x0A
CONT_TEMP = const(0x0C) CONTINUOUS_BUS_SHUNT = 0x0B
CONT_TEMP_BUS = const(0x0D) CONTINUOUS_TEMP = 0x0C
CONT_TEMP_SHUNT = const(0x0E) CONTINUOUS_TEMP_BUS = 0x0D
CONT_TEMP_BUS_SHUNT = const(0x0F) CONTINUOUS_TEMP_SHUNT = 0x0E
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 ConversionTime: class INA228: # noqa: PLR0904
"""Conversion time constants for INA2XX"""
TIME_50_US = const(0)
TIME_84_US = const(1)
TIME_150_US = const(2)
TIME_280_US = const(3)
TIME_540_US = const(4)
TIME_1052_US = const(5)
TIME_2074_US = const(6)
TIME_4120_US = const(7)
_VALID_TIMES = {
TIME_50_US,
TIME_84_US,
TIME_150_US,
TIME_280_US,
TIME_540_US,
TIME_1052_US,
TIME_2074_US,
TIME_4120_US,
}
# Microsecond values for each setting
_VALUES = [50, 84, 150, 280, 540, 1052, 2074, 4120]
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:
"""Reset the sensor to default configuration."""
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""" """Driver for the INA228 power and current sensor"""
# Additional registers for INA228 _config = UnaryStruct(_CONFIG, ">H")
_reset_accumulators_bit = RWBit(_CONFIG, 14, register_width=2, lsb_first=False) _adc_config = UnaryStruct(_ADC_CONFIG, ">H")
_alert_type = RWBits(6, _DIAGALRT, 8, register_width=2, lsb_first=False) _shunt_cal = UnaryStruct(_SHUNT_CAL, ">H")
_alert_flags = ROUnaryStruct(_DIAGALRT, ">H") _diag_alrt = UnaryStruct(_DIAG_ALRT, ">H")
_adc_range = RWBit(_CONFIG, 4, register_width=2)
"""Operating mode"""
mode = RWBits(4, _ADC_CONFIG, 12, register_width=2)
_vbus_ct = RWBits(3, _ADC_CONFIG, 9, register_width=2)
_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") _shunt_tempco = UnaryStruct(_SHUNT_TEMPCO, ">H")
_sovl = UnaryStruct(_SOVL, ">H") """Manufacturer ID"""
_suvl = UnaryStruct(_SUVL, ">H") manufacturer_id = UnaryStruct(_MFG_ID, ">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") def __init__(self, i2c_bus, addr=0x40):
_raw_current = ROUnaryStruct(_CURRENT, ">h") self.i2c_device = I2CDevice(i2c_bus, addr)
_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.buf3 = bytearray(3) # Buffer for 24-bit registers
self.buf5 = bytearray(5) # Buffer for 40-bit registers self.buf5 = bytearray(5) # Buffer for 40-bit registers
super().__init__(i2c_bus, address, skip_reset)
# Verify device ID # Verify device ID
dev_id = self.device_id dev_id = (self._device_id >> 4) & 0xFFF # Get 12-bit device ID
if dev_id != _INA228_DEVICE_ID: if dev_id != 0x228:
raise RuntimeError(f"Failed to find INA228 - check your wiring! (Got ID: 0x{dev_id:X})") raise RuntimeError(f"Failed to find INA228 - check your wiring! (Got ID: 0x{dev_id:X})")
self._current_lsb = 0
self._shunt_res = 0
self.reset()
self.mode = Mode.CONTINUOUS_ALL
self.set_shunt(0.1, 2.0)
self.conversion_time_bus = 150
self.conversion_time_shunt = 280
self.averaging_count = 16
# Set INA228 defaults def reset(self) -> None:
self.set_calibration(0.015, 10.0) """Reset the INA228"""
self._config = 0x8000
def _reg24(self, reg): def _reg24(self, reg):
"""Read 24-bit register""" """Read 24-bit register"""
@ -426,125 +152,247 @@ class INA228(INA2XX): # 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._reset_accumulators_bit = True self._config = 1 << 14
def set_calibration(self, shunt_res: float = 0.015, max_current: float = 10.0) -> None: @property
"""Set the calibration based on shunt resistance and maximum expected current. def conversion_time_bus(self) -> int:
: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_shunt_cal() self._update_calibration()
time.sleep(0.001) time.sleep(0.001)
def _update_shunt_cal(self) -> None: def _update_calibration(self):
"""Update the shunt calibration register.""" """Update the calibration register based on shunt and current settings"""
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 die_temperature(self) -> float: def conversion_ready(self) -> bool:
"""Die temperature in degrees Celsius.""" """Check if conversion is ready"""
# INA228 uses 7.8125 m°C/LSB return bool(self._diag_alrt & (1 << 1))
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 volts.""" """Shunt voltage in V"""
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 current(self) -> float: def voltage(self) -> float:
"""Current in amperes.""" """Bus voltage measurement in V"""
raw = self._reg24(_CURRENT) raw = self._reg24(_VBUS)
if raw & 0x800000: value = (raw >> 4) * 195.3125e-6
raw -= 0x1000000 return value
return (raw / 16.0) * self._current_lsb
@property @property
def power(self) -> float: def power(self) -> float:
"""Power in watts.""" """Power measurement in mW"""
raw = self._reg24(_POWER) raw = self._reg24(_POWER)
# INA228 power LSB = 3.2 × current_lsb value = raw * 3.2 * self._current_lsb * 1000
return raw * 3.2 * self._current_lsb return value
@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)
return raw * 16.0 * 3.2 * self._current_lsb value = 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 conversion_ready(self) -> bool: def temperature(self) -> float:
"""Check if conversion is complete.""" """Die temperature in celsius"""
return bool(self._conversion_ready) return self._temperature * 7.8125e-3
@property @property
def alert_type(self) -> int: def shunt_tempco(self) -> int:
"""Alert type configuration.""" """Shunt temperature coefficient in ppm/°C"""
return self._alert_type return self._shunt_tempco
@alert_type.setter @shunt_tempco.setter
def alert_type(self, value: int) -> None: def shunt_tempco(self, value: int):
# Alert type can be a combination of flags self._shunt_tempco = value
valid_mask = (
AlertType.CONVERSION_READY @property
| AlertType.OVERPOWER def conversion_time_temperature(self) -> int:
| AlertType.UNDERVOLTAGE """
| AlertType.OVERVOLTAGE Temperature conversion time in microseconds.
| AlertType.UNDERCURRENT Valid values are: 50, 84, 150, 280, 540, 1052, 2074, 4120.
| AlertType.OVERCURRENT """
) times = [50, 84, 150, 280, 540, 1052, 2074, 4120]
if value & ~valid_mask: return times[self._temper_ct]
@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 alert type 0x{value:02X}. Must be a combination of AlertType.* constants" f"Invalid conversion time: {usec}. Valid values are: {', '.join(map(str, times))}."
) )
self._alert_type = value self._temper_ct = times.index(usec)
@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:
""" """
All diagnostic and alert flags Get 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
- 'CHARGEOF': bool - Charge overflow 'ENERGYOF': bool, # Energy overflow
- 'MATHOF': bool - Math overflow
- 'TMPOL': bool - Temperature overlimit 'CHARGEOF': bool, # Charge overflow
- 'SHNTOL': bool - Shunt voltage overlimit
- 'SHNTUL': bool - Shunt voltage underlimit 'MATHOF': bool, # Math overflow
- 'BUSOL': bool - Bus voltage overlimit
- 'BUSUL': bool - Bus voltage underlimit 'TMPOL': bool, # Temperature overlimit
- 'POL': bool - Power overlimit
- 'CNVRF': bool - Conversion ready 'SHNTOL': bool, # Shunt voltage overlimit
- '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._alert_flags flags = self._diag_alrt
return { return {
"ENERGYOF": bool(flags & (1 << 11)), "ENERGYOF": bool(flags & (1 << 11)),
"CHARGEOF": bool(flags & (1 << 10)), "CHARGEOF": bool(flags & (1 << 10)),
@ -559,100 +407,13 @@ class INA228(INA2XX): # 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.TRIG_TEMP_BUS_SHUNT: if current_mode < Mode.SHUTDOWN2:
# 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"""
# Read-modify-write to clear only overflow flags flags = self._diag_alrt
flags = self._alert_flags self._diag_alrt = flags & ~((1 << 11) | (1 << 10) | (1 << 9))
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)

View file

@ -12,16 +12,16 @@ i2c = board.I2C()
ina228 = adafruit_ina228.INA228(i2c) ina228 = adafruit_ina228.INA228(i2c)
print("Adafruit INA228 Test") print("Adafruit INA228 Test")
print(f"Bus conversion time: {ina228.bus_voltage_conv_time} microseconds") print(f"Bus conversion time: {ina228.conversion_time_bus} microseconds")
print(f"Shunt conversion time: {ina228.shunt_voltage_conv_time} microseconds") print(f"Shunt conversion time: {ina228.conversion_time_shunt} microseconds")
print(f"Samples averaged: {ina228.averaging_count}") print(f"Samples averaged: {ina228.averaging_count}")
while True: while True:
print("\nCurrent Measurements:") print("\nCurrent Measurements:")
print(f"Current: {ina228.current:.2f} mA") print(f"Current: {ina228.current:.2f} mA")
print(f"Bus Voltage: {ina228.bus_voltage:.2f} V") print(f"Bus Voltage: {ina228.voltage:.2f} V")
print(f"Shunt Voltage: {ina228.shunt_voltage*1000:.2f} mV") print(f"Shunt Voltage: {ina228.shunt_voltage*1000:.2f} mV")
print(f"Power: {ina228.power:.2f} mW") print(f"Power: {ina228.power:.2f} mW")
print(f"Energy: {ina228.energy:.2f} J") print(f"Energy: {ina228.energy:.2f} J")
print(f"Temperature: {ina228.die_temperature:.2f} °C") print(f"Temperature: {ina228.temperature:.2f} °C")
time.sleep(1) time.sleep(1)