658 lines
20 KiB
Python
658 lines
20 KiB
Python
# SPDX-FileCopyrightText: Copyright (c) 2025 Liz Clark for Adafruit Industries
|
||
#
|
||
# SPDX-License-Identifier: MIT
|
||
"""
|
||
`adafruit_ina228`
|
||
================================================================================
|
||
|
||
CircuitPython driver for the INA228 I2C 85V, 20-bit High or Low Side Power Monitor
|
||
|
||
|
||
* Author(s): Liz Clark
|
||
|
||
Implementation Notes
|
||
--------------------
|
||
|
||
**Hardware:**
|
||
|
||
* `Adafruit INA228 High or Low Side Current and Power Monitor <https://www.adafruit.com/product/5832>`_
|
||
|
||
**Software and Dependencies:**
|
||
|
||
* Adafruit CircuitPython firmware for the supported boards: https://circuitpython.org/downloads
|
||
|
||
* Adafruit's Bus Device library: https://github.com/adafruit/Adafruit_CircuitPython_BusDevice
|
||
* Adafruit's Register library: https://github.com/adafruit/Adafruit_CircuitPython_Register
|
||
"""
|
||
|
||
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 micropython import const
|
||
|
||
try:
|
||
import typing
|
||
|
||
from busio import I2C
|
||
except ImportError:
|
||
pass
|
||
|
||
__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)
|
||
|
||
|
||
class Mode:
|
||
"""Operating mode constants for INA2XX"""
|
||
|
||
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,
|
||
}
|
||
|
||
|
||
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
|
||
"""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):
|
||
"""Read 24-bit register"""
|
||
with self.i2c_device as i2c:
|
||
i2c.write_then_readinto(bytes([reg]), self.buf3)
|
||
result = (self.buf3[0] << 16) | (self.buf3[1] << 8) | self.buf3[2]
|
||
return result
|
||
|
||
def _reg40(self, reg):
|
||
"""Read 40-bit register"""
|
||
with self.i2c_device as i2c:
|
||
i2c.write_then_readinto(bytes([reg]), self.buf5)
|
||
result = 0
|
||
for b in self.buf5:
|
||
result = (result << 8) | b
|
||
return result
|
||
|
||
def reset_accumulators(self) -> None:
|
||
"""Reset the energy and charge accumulators"""
|
||
self._reset_accumulators_bit = True
|
||
|
||
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
|
||
"""
|
||
self._shunt_res = shunt_res
|
||
# INA228 uses 2^19 as divisor
|
||
self._current_lsb = max_current / (1 << 19)
|
||
self._update_shunt_cal()
|
||
time.sleep(0.001)
|
||
|
||
def _update_shunt_cal(self) -> None:
|
||
"""Update the shunt calibration register."""
|
||
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
|
||
|
||
@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
|
||
|
||
@property
|
||
def shunt_voltage(self) -> float:
|
||
"""Shunt voltage in volts."""
|
||
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
|
||
|
||
@property
|
||
def power(self) -> float:
|
||
"""Power in watts."""
|
||
raw = self._reg24(_POWER)
|
||
# INA228 power LSB = 3.2 × current_lsb
|
||
return raw * 3.2 * self._current_lsb
|
||
|
||
@property
|
||
def energy(self) -> float:
|
||
"""Energy measurement in Joules"""
|
||
raw = self._reg40(_ENERGY)
|
||
return raw * 16.0 * 3.2 * self._current_lsb
|
||
|
||
@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)
|
||
|
||
@property
|
||
def alert_type(self) -> int:
|
||
"""Alert type configuration."""
|
||
return self._alert_type
|
||
|
||
@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:
|
||
raise ValueError(
|
||
f"Invalid alert type 0x{value:02X}. Must be a combination of AlertType.* constants"
|
||
)
|
||
self._alert_type = value
|
||
|
||
@property
|
||
def alert_flags(self) -> dict:
|
||
"""
|
||
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
|
||
"""
|
||
flags = self._alert_flags
|
||
return {
|
||
"ENERGYOF": bool(flags & (1 << 11)),
|
||
"CHARGEOF": bool(flags & (1 << 10)),
|
||
"MATHOF": bool(flags & (1 << 9)),
|
||
"TMPOL": bool(flags & (1 << 7)),
|
||
"SHNTOL": bool(flags & (1 << 6)),
|
||
"SHNTUL": bool(flags & (1 << 5)),
|
||
"BUSOL": bool(flags & (1 << 4)),
|
||
"BUSUL": bool(flags & (1 << 3)),
|
||
"POL": bool(flags & (1 << 2)),
|
||
"CNVRF": bool(flags & (1 << 1)),
|
||
"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
|
||
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)
|