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>`_
* `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.
This is easily achieved by downloading
@ -103,11 +102,11 @@ Usage Example
while True:
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"Power: {ina228.power:.2f} mW")
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)
Documentation

View file

@ -15,7 +15,7 @@ Implementation Notes
**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:**
@ -28,9 +28,9 @@ Implementation Notes
import time
from adafruit_bus_device.i2c_device import I2CDevice
from adafruit_register.i2c_bit import ROBit, RWBit
from adafruit_register.i2c_bits import ROBits, RWBits
from adafruit_register.i2c_struct import ROUnaryStruct, UnaryStruct
from adafruit_register.i2c_bit import RWBit
from adafruit_register.i2c_bits import RWBits
from adafruit_register.i2c_struct import UnaryStruct
from micropython import const
try:
@ -43,370 +43,96 @@ except ImportError:
__version__ = "0.0.0+auto.0"
__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_INA228.git"
# Common register addresses for INA2XX family
_CONFIG = const(0x00)
_ADCCFG = const(0x01)
_SHUNTCAL = const(0x02)
_VSHUNT = const(0x04)
_VBUS = const(0x05)
_DIETEMP = const(0x06)
_CURRENT = const(0x07)
_POWER = const(0x08)
_DIAGALRT = const(0x0B)
_MFG_UID = const(0x3E)
_DVC_UID = const(0x3F)
# INA228-specific registers
_SHUNT_TEMPCO = const(0x03)
_ENERGY = const(0x09)
_CHARGE = const(0x0A)
_SOVL = const(0x0C)
_SUVL = const(0x0D)
_BOVL = const(0x0E)
_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)
# Register addresses
_CONFIG = const(0x00) # Configuration Register
_ADC_CONFIG = const(0x01) # ADC Configuration Register
_SHUNT_CAL = const(0x02) # Shunt Calibration Register
_SHUNT_TEMPCO = const(0x03) # Shunt Temperature Coefficient Register
_VSHUNT = const(0x04) # Shunt Voltage Measurement
_VBUS = const(0x05) # Bus Voltage Measurement
_DIETEMP = const(0x06) # Temperature Measurement
_CURRENT = const(0x07) # Current Result
_POWER = const(0x08) # Power Result
_ENERGY = const(0x09) # Energy Result
_CHARGE = const(0x0A) # Charge Result
_DIAG_ALRT = const(0x0B) # Diagnostic Flags and Alert
_SOVL = const(0x0C) # Shunt Overvoltage Threshold
_SUVL = const(0x0D) # Shunt Undervoltage Threshold
_BOVL = const(0x0E) # Bus Overvoltage Threshold
_BUVL = const(0x0F) # Bus Undervoltage Threshold
_TEMP_LIMIT = const(0x10) # Temperature Over-Limit Threshold
_PWR_LIMIT = const(0x11) # Power Over-Limit Threshold
_MFG_ID = const(0x3E) # Manufacturer ID
_DEVICE_ID = const(0x3F) # Device ID
class Mode:
"""Operating mode constants for INA2XX"""
"""Constants for operating modes"""
SHUTDOWN = const(0x00)
TRIG_BUS = const(0x01)
TRIG_SHUNT = const(0x02)
TRIG_BUS_SHUNT = const(0x03)
TRIG_TEMP = const(0x04)
TRIG_TEMP_BUS = const(0x05)
TRIG_TEMP_SHUNT = const(0x06)
TRIG_TEMP_BUS_SHUNT = const(0x07)
CONT_BUS = const(0x09)
CONT_SHUNT = const(0x0A)
CONT_BUS_SHUNT = const(0x0B)
CONT_TEMP = const(0x0C)
CONT_TEMP_BUS = const(0x0D)
CONT_TEMP_SHUNT = const(0x0E)
CONT_TEMP_BUS_SHUNT = const(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,
}
SHUTDOWN = 0x00
TRIGGERED_BUS = 0x01
TRIGGERED_SHUNT = 0x02
TRIGGERED_BUS_SHUNT = 0x03
TRIGGERED_TEMP = 0x04
TRIGGERED_TEMP_BUS = 0x05
TRIGGERED_TEMP_SHUNT = 0x06
TRIGGERED_ALL = 0x07
SHUTDOWN2 = 0x08
CONTINUOUS_BUS = 0x09
CONTINUOUS_SHUNT = 0x0A
CONTINUOUS_BUS_SHUNT = 0x0B
CONTINUOUS_TEMP = 0x0C
CONTINUOUS_TEMP_BUS = 0x0D
CONTINUOUS_TEMP_SHUNT = 0x0E
CONTINUOUS_ALL = 0x0F
class ConversionTime:
"""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
class INA228: # 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")
_config = UnaryStruct(_CONFIG, ">H")
_adc_config = UnaryStruct(_ADC_CONFIG, ">H")
_shunt_cal = UnaryStruct(_SHUNT_CAL, ">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")
_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")
"""Manufacturer ID"""
manufacturer_id = UnaryStruct(_MFG_ID, ">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
def __init__(self, i2c_bus, addr=0x40):
self.i2c_device = I2CDevice(i2c_bus, addr)
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:
dev_id = (self._device_id >> 4) & 0xFFF # Get 12-bit device ID
if dev_id != 0x228:
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
self.set_calibration(0.015, 10.0)
def reset(self) -> None:
"""Reset the INA228"""
self._config = 0x8000
def _reg24(self, reg):
"""Read 24-bit register"""
@ -426,125 +152,247 @@ class INA228(INA2XX): # noqa: PLR0904
def reset_accumulators(self) -> None:
"""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:
"""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
@property
def conversion_time_bus(self) -> int:
"""
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
# INA228 uses 2^19 as divisor
self._current_lsb = max_current / (1 << 19)
self._update_shunt_cal()
self._update_calibration()
time.sleep(0.001)
def _update_shunt_cal(self) -> None:
"""Update the shunt calibration register."""
def _update_calibration(self):
"""Update the calibration register based on shunt and current settings"""
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)
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
def die_temperature(self) -> float:
"""Die temperature in degrees Celsius."""
# 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
def conversion_ready(self) -> bool:
"""Check if conversion is ready"""
return bool(self._diag_alrt & (1 << 1))
@property
def shunt_voltage(self) -> float:
"""Shunt voltage in volts."""
"""Shunt voltage in V"""
raw = self._reg24(_VSHUNT)
if raw & 0x800000:
raw -= 0x1000000
# Scale depends on ADC range
scale = 78.125e-9 if self._adc_range else 312.5e-9
return (raw / 16.0) * scale
@property
def current(self) -> float:
"""Current in amperes."""
raw = self._reg24(_CURRENT)
if raw & 0x800000:
raw -= 0x1000000
return (raw / 16.0) * self._current_lsb
def voltage(self) -> float:
"""Bus voltage measurement in V"""
raw = self._reg24(_VBUS)
value = (raw >> 4) * 195.3125e-6
return value
@property
def power(self) -> float:
"""Power in watts."""
"""Power measurement in mW"""
raw = self._reg24(_POWER)
# INA228 power LSB = 3.2 × current_lsb
return raw * 3.2 * self._current_lsb
value = raw * 3.2 * self._current_lsb * 1000
return value
@property
def energy(self) -> float:
"""Energy measurement in Joules"""
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
def charge(self) -> float:
"""Accumulated charge in coulombs"""
raw = self._reg40(_CHARGE)
if raw & (1 << 39):
raw |= -1 << 40
return raw * self._current_lsb
@property
def conversion_ready(self) -> bool:
"""Check if conversion is complete."""
return bool(self._conversion_ready)
def temperature(self) -> float:
"""Die temperature in celsius"""
return self._temperature * 7.8125e-3
@property
def alert_type(self) -> int:
"""Alert type configuration."""
return self._alert_type
def shunt_tempco(self) -> int:
"""Shunt temperature coefficient in ppm/°C"""
return self._shunt_tempco
@alert_type.setter
def alert_type(self, value: int) -> None:
# Alert type can be a combination of flags
valid_mask = (
AlertType.CONVERSION_READY
| AlertType.OVERPOWER
| AlertType.UNDERVOLTAGE
| AlertType.OVERVOLTAGE
| AlertType.UNDERCURRENT
| AlertType.OVERCURRENT
)
if value & ~valid_mask:
@shunt_tempco.setter
def shunt_tempco(self, value: int):
self._shunt_tempco = value
@property
def conversion_time_temperature(self) -> int:
"""
Temperature 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._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(
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
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:
- 'ENERGYOF': bool - Energy overflow
- 'CHARGEOF': bool - Charge overflow
- 'MATHOF': bool - Math overflow
- 'TMPOL': bool - Temperature overlimit
- 'SHNTOL': bool - Shunt voltage overlimit
- '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
'ENERGYOF': bool, # Energy overflow
'CHARGEOF': bool, # Charge overflow
'MATHOF': bool, # Math overflow
'TMPOL': bool, # Temperature overlimit
'SHNTOL': bool, # Shunt voltage overlimit
'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 {
"ENERGYOF": bool(flags & (1 << 11)),
"CHARGEOF": bool(flags & (1 << 10)),
@ -559,100 +407,13 @@ class INA228(INA2XX): # noqa: PLR0904
"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:
"""Trigger a one-shot measurement when in triggered mode"""
current_mode = self.mode
if current_mode <= Mode.TRIG_TEMP_BUS_SHUNT:
# Re-write the same mode to trigger measurement
if current_mode < Mode.SHUTDOWN2:
self.mode = current_mode
def clear_overflow_flags(self) -> None:
"""Clear energy, charge, and math overflow flags"""
# Read-modify-write to clear only overflow flags
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)
flags = self._diag_alrt
self._diag_alrt = flags & ~((1 << 11) | (1 << 10) | (1 << 9))

View file

@ -12,16 +12,16 @@ i2c = board.I2C()
ina228 = adafruit_ina228.INA228(i2c)
print("Adafruit INA228 Test")
print(f"Bus conversion time: {ina228.bus_voltage_conv_time} microseconds")
print(f"Shunt conversion time: {ina228.shunt_voltage_conv_time} microseconds")
print(f"Bus conversion time: {ina228.conversion_time_bus} microseconds")
print(f"Shunt conversion time: {ina228.conversion_time_shunt} microseconds")
print(f"Samples averaged: {ina228.averaging_count}")
while True:
print("\nCurrent Measurements:")
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"Power: {ina228.power:.2f} mW")
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)