diff --git a/README.rst b/README.rst
index 48719c9..9f4c65f 100644
--- a/README.rst
+++ b/README.rst
@@ -92,8 +92,23 @@ Usage Example
=============
.. code-block:: python
+
+ import time
+ import board
import adafruit_ina228
+ i2c = board.I2C()
+ ina228 = adafruit_ina228.INA228(i2c)
+
+ while True:
+ print(f"Current: {ina228.current:.2f} mA")
+ 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.temperature:.2f} °C")
+ time.sleep(1)
+
Documentation
=============
API documentation for this library can be found on `Read the Docs `_.
diff --git a/adafruit_ina228.py b/adafruit_ina228.py
index fec9c25..57e55bb 100644
--- a/adafruit_ina228.py
+++ b/adafruit_ina228.py
@@ -1,4 +1,3 @@
-# SPDX-FileCopyrightText: 2017 Scott Shawcroft, written for Adafruit Industries
# SPDX-FileCopyrightText: Copyright (c) 2025 Liz Clark for Adafruit Industries
#
# SPDX-License-Identifier: MIT
@@ -16,22 +15,405 @@
**Hardware:**
-.. todo:: Add links to any specific hardware product page(s), or category page(s).
- Use unordered list & hyperlink rST inline format: "* `Link Text `_"
+* `Adafruit INA228 High Side Current and Power Monitor `_
**Software and Dependencies:**
-* Adafruit CircuitPython firmware for the supported boards:
- https://circuitpython.org/downloads
+* Adafruit CircuitPython firmware for the supported boards: https://circuitpython.org/downloads
-.. todo:: Uncomment or remove the Bus Device and/or the Register library dependencies
- based on the library's use of either.
-
-# * Adafruit's Bus Device library: https://github.com/adafruit/Adafruit_CircuitPython_BusDevice
-# * Adafruit's Register library: https://github.com/adafruit/Adafruit_CircuitPython_Register
+* Adafruit's Bus Device library: https://github.com/adafruit/Adafruit_CircuitPython_BusDevice
+* Adafruit's Register library: https://github.com/adafruit/Adafruit_CircuitPython_Register
"""
-# imports
+import time
+
+from adafruit_bus_device.i2c_device import I2CDevice
+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:
+ import typing
+
+ from busio import I2C
+except ImportError:
+ pass
__version__ = "0.0.0+auto.0"
__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_INA228.git"
+
+# 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:
+ """Constants for operating modes"""
+
+ 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 INA228: # noqa: PLR0904
+ """Driver for the INA228 power and current sensor"""
+
+ _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")
+ """Manufacturer ID"""
+ manufacturer_id = UnaryStruct(_MFG_ID, ">H")
+
+ 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
+ # Verify 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
+
+ def reset(self) -> None:
+ """Reset the INA228"""
+ self._config = 0x8000
+
+ 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._config = 1 << 14
+
+ @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
+ self._current_lsb = max_current / (1 << 19)
+ self._update_calibration()
+ time.sleep(0.001)
+
+ def _update_calibration(self):
+ """Update the calibration register based on shunt and current settings"""
+ scale = 4 if self._adc_range else 1
+ 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 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 V"""
+ raw = self._reg24(_VSHUNT)
+ if raw & 0x800000:
+ raw -= 0x1000000
+ scale = 78.125e-9 if self._adc_range else 312.5e-9
+ return (raw / 16.0) * scale
+
+ @property
+ 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 measurement in mW"""
+ raw = self._reg24(_POWER)
+ value = raw * 3.2 * self._current_lsb * 1000
+ return value
+
+ @property
+ def energy(self) -> float:
+ """Energy measurement in Joules"""
+ raw = self._reg40(_ENERGY)
+ 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)
+ return raw * self._current_lsb
+
+ @property
+ def temperature(self) -> float:
+ """Die temperature in celsius"""
+ return self._temperature * 7.8125e-3
+
+ @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 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 conversion time: {usec}. Valid values are: {', '.join(map(str, times))}."
+ )
+ 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:
+ """
+ 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
+ """
+ flags = self._diag_alrt
+ 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)),
+ }
+
+ def trigger_measurement(self) -> None:
+ """Trigger a one-shot measurement when in triggered mode"""
+ current_mode = self.mode
+ if current_mode < Mode.SHUTDOWN2:
+ self.mode = current_mode
+
+ def clear_overflow_flags(self) -> None:
+ """Clear energy, charge, and math overflow flags"""
+ flags = self._diag_alrt
+ self._diag_alrt = flags & ~((1 << 11) | (1 << 10) | (1 << 9))
diff --git a/docs/conf.py b/docs/conf.py
index b77fc29..01a025a 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -25,7 +25,7 @@
# Uncomment the below if you use native CircuitPython modules such as
# digitalio, micropython and busio. List the modules you use. Without it, the
# autodoc module docs will fail to generate with a warning.
-# autodoc_mock_imports = ["digitalio", "busio"]
+autodoc_mock_imports = ["digitalio", "busio", "adafruit_register", "adafruit_bus_device"]
autodoc_preserve_defaults = True
diff --git a/docs/index.rst b/docs/index.rst
index 941a66c..6c188a6 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -24,14 +24,12 @@ Table of Contents
.. toctree::
:caption: Tutorials
-.. todo:: Add any Learn guide links here. If there are none, then simply delete this todo and leave
- the toctree above for use later.
+ Adafruit Learn Guide
.. toctree::
:caption: Related Products
-.. todo:: Add any product links here. If there are none, then simply delete this todo and leave
- the toctree above for use later.
+ Adafruit INA228 - I2C 85V, 20-bit High or Low Side Power Monitor - STEMMA QT / Qwiic
.. toctree::
:caption: Other Links
diff --git a/examples/ina228_simpletest.py b/examples/ina228_simpletest.py
index 42772ff..279c8d6 100644
--- a/examples/ina228_simpletest.py
+++ b/examples/ina228_simpletest.py
@@ -1,4 +1,27 @@
-# SPDX-FileCopyrightText: 2017 Scott Shawcroft, written for Adafruit Industries
# SPDX-FileCopyrightText: Copyright (c) 2025 Liz Clark for Adafruit Industries
#
-# SPDX-License-Identifier: Unlicense
+# SPDX-License-Identifier: MIT
+
+import time
+
+import board
+
+import adafruit_ina228
+
+i2c = board.I2C()
+ina228 = adafruit_ina228.INA228(i2c)
+print("Adafruit INA228 Test")
+
+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.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.temperature:.2f} °C")
+ time.sleep(1)
diff --git a/requirements.txt b/requirements.txt
index c1ad2a0..7284723 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -5,3 +5,4 @@
Adafruit-Blinka
adafruit-circuitpython-busdevice
+adafruit-circuitpython-register