Compare commits

...

9 commits

Author SHA1 Message Date
Liz
94a7519c80
Merge pull request #5 from jlunz/jl/fixes_after_split
Some checks failed
Build CI / test (push) Has been cancelled
Fix examples to use changed attributes
2025-06-20 10:23:04 -04:00
Julian Lunz
b5fdd8ada0 Fix examples to use changed attributes
b7299cbe75 introduced changes to attribute
names.
Use the new attributes so that the two examples run again.
2025-06-20 14:50:12 +02:00
Liz
8c591dc96c
Merge pull request #4 from adafruit/ina2xx
Some checks failed
Build CI / test (push) Has been cancelled
split into ina2xx and ina228
2025-06-09 18:07:28 -04:00
Liz
b7299cbe75 split into ina2xx and ina228 2025-06-09 16:00:03 -04:00
Liz
4ae179f9dd
Merge pull request #3 from adafruit/refactor
Some checks failed
Build CI / test (push) Has been cancelled
Update adafruit_ina228.py to match arduino changes
2025-06-04 12:04:14 -04:00
BlitzCityDIY
931e253515 Update adafruit_ina228.py
updating driver to match arduino changes
2025-06-04 12:01:30 -04:00
Liz
5e099d3c62
Update adafruit_ina228.py
Some checks failed
Build CI / test (push) Has been cancelled
2025-03-18 07:46:27 -04:00
Liz
363af64a7a Update README.rst
Some checks failed
Build CI / test (push) Has been cancelled
2025-02-05 09:23:47 -05:00
Liz
cdadbf5e15
Merge pull request #1 from adafruit/lib_files
Some checks failed
Build CI / test (push) Has been cancelled
adding library, docs, example
2025-02-01 19:31:04 -05:00
3 changed files with 524 additions and 284 deletions

View file

@ -30,6 +30,7 @@ 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
@ -102,11 +103,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.voltage:.2f} V") print(f"Bus Voltage: {ina228.bus_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.temperature:.2f} °C") print(f"Temperature: {ina228.die_temperature:.2f} °C")
time.sleep(1) time.sleep(1)
Documentation Documentation

View file

@ -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)

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.conversion_time_bus} microseconds") print(f"Bus conversion time: {ina228.bus_voltage_conv_time} microseconds")
print(f"Shunt conversion time: {ina228.conversion_time_shunt} microseconds") print(f"Shunt conversion time: {ina228.shunt_voltage_conv_time} 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.voltage:.2f} V") print(f"Bus Voltage: {ina228.bus_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.temperature:.2f} °C") print(f"Temperature: {ina228.die_temperature:.2f} °C")
time.sleep(1) time.sleep(1)