diff --git a/README.rst b/README.rst index 6e786e5..fa82cab 100644 --- a/README.rst +++ b/README.rst @@ -31,6 +31,7 @@ This driver depends on: * `Adafruit CircuitPython `_ * `Bus Device `_ * `Register `_ +* `Adafruit CircuitPython INA228 `_ Please ensure all dependencies are available on the CircuitPython filesystem. This is easily achieved by downloading @@ -39,6 +40,7 @@ or individual libraries can be installed using `circup `_. `Purchase INA237 from the Adafruit shop `_ + `Purchase INA238 from the Adafruit shop `_ Installing from PyPI @@ -93,8 +95,24 @@ Or the following command to update an existing version: Usage Example ============= -.. todo:: Add a quick, simple example. It and other examples should live in the -examples folder and be included in docs/examples.rst. +.. code-block:: python + + import time + import board + import adafruit_ina23x + + i2c = board.I2C() + ina23x = adafruit_ina23x.INA23X(i2c) + + while True: + print(f"Current: {ina23x.current * 1000:.2f} mA") + print(f"Bus Voltage: {ina23x.bus_voltage:.2f} V") + print(f"Shunt Voltage: {ina23x.shunt_voltage * 1000:.2f} mV") + print(f"Power: {ina23x.power * 1000:.2f} mW") + print(f"Temperature: {ina23x.die_temperature:.2f} °C") + print() + + time.sleep(2) Documentation ============= diff --git a/adafruit_ina23x.py b/adafruit_ina23x.py index 5a88e9e..1827fe0 100644 --- a/adafruit_ina23x.py +++ b/adafruit_ina23x.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 @@ -6,7 +5,7 @@ `adafruit_ina23x` ================================================================================ -CircuitPython driver for the INA237 and INA238 DC Current Voltage Power Monitor +CircuitPython driver for the INA237 and INA238 DC Current Voltage Power Monitors * Author(s): Liz Clark @@ -16,22 +15,155 @@ **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 INA237 Breakout `_ +* `Adafruit INA238 Breakout `_ **Software and Dependencies:** * 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 +* Adafruit CircuitPython INA228 library: https://github.com/adafruit/Adafruit_CircuitPython_INA228 """ -# imports +import time + +from adafruit_ina228 import INA2XX, AlertType +from adafruit_register.i2c_bit import ROBit +from adafruit_register.i2c_bits import ROBits, RWBits +from adafruit_register.i2c_struct import ROUnaryStruct +from micropython import const + +try: + import typing # pylint: disable=unused-import + + from busio import I2C +except ImportError: + pass __version__ = "0.0.0+auto.0" __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_INA23x.git" + +_SOVL = const(0x0C) # Shunt Overvoltage Threshold +_SUVL = const(0x0D) # Shunt Undervoltage Threshold +_BOVL = const(0x0E) # Bus Overvoltage Threshold +_BUVL = const(0x0F) # Bus Undervoltage Threshold +_TEMPLIMIT = const(0x10) # Temperature Over-Limit Threshold +_PWRLIMIT = const(0x11) # Power Over-Limit Threshold + +# Constants +_INA237_DEVICE_ID = const(0x237) +_INA238_DEVICE_ID = const(0x238) + + +class INA23X(INA2XX): # noqa: PLR0904 + """Driver for the INA237/INA238 current and power sensor. + + :param ~busio.I2C i2c_bus: The I2C bus the INA23X 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. + """ + + # INA23X-specific register bits + _alert_type = RWBits(7, 0x0B, 5, register_width=2, lsb_first=False) + _conversion_ready = ROBit(0x0B, 1, register_width=2, lsb_first=False) + _alert_flags = ROBits(12, 0x0B, 0, register_width=2, lsb_first=False) + + _raw_vshunt = ROUnaryStruct(0x04, ">h") + _raw_current = ROUnaryStruct(0x07, ">h") + _raw_power = ROUnaryStruct(0x08, ">H") + + def __init__(self, i2c_bus: I2C, address: int = 0x40, skip_reset: bool = False) -> None: + super().__init__(i2c_bus, address, skip_reset) + + # Verify device ID (both INA237 and INA238 use compatible IDs) + if self.device_id not in {_INA237_DEVICE_ID, _INA238_DEVICE_ID}: + raise ValueError("Failed to find INA237/INA238 - incorrect device ID") + + # Set INA23X defaults + self.set_calibration(0.015, 10.0) + + 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 + # INA237/238 uses 2^15 as divisor + self._current_lsb = max_current / (1 << 15) + self._update_shunt_cal() + + def _update_shunt_cal(self) -> None: + """Update the shunt calibration register.""" + # Scale factor based on ADC range + scale = 4 if self._adc_range else 1 + + # INA237/238 formula: SHUNT_CAL = 819.2 × 10^6 × CURRENT_LSB × RSHUNT × scale + shunt_cal = int(819.2e6 * self._current_lsb * self._shunt_res * scale) + self._shunt_cal = min(shunt_cal, 0xFFFF) + + @property + def die_temperature(self) -> float: + """Die temperature in degrees Celsius.""" + # INA237/238 uses 12 bits (15:4) with 125 m°C/LSB + return (self._raw_dietemp >> 4) * 0.125 + + @property + def bus_voltage(self) -> float: + """Bus voltage in volts.""" + # INA237/238 uses 3.125 mV/LSB + return self._raw_vbus * 0.003125 + + @property + def shunt_voltage(self) -> float: + """Shunt voltage in volts.""" + # Scale depends on ADC range + scale = 1.25e-6 if self._adc_range else 5.0e-6 # µV/LSB + return self._raw_vshunt * scale + + @property + def current(self) -> float: + """Current in amperes.""" + return self._raw_current * self._current_lsb + + @property + def power(self) -> float: + """Power in watts.""" + # INA237/238 power LSB = 20 × current_lsb + return self._raw_power * 20.0 * 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, so we check if all bits are valid + valid_mask = ( + AlertType.CONVERSION_READY + | AlertType.OVERTEMPERATURE + | AlertType.OVERPOWER + | AlertType.UNDERVOLTAGE + | AlertType.OVERVOLTAGE + | AlertType.UNDERSHUNT + | AlertType.OVERSHUNT + ) + 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) -> int: + """Current alert flags.""" + return self._alert_flags diff --git a/docs/conf.py b/docs/conf.py index 91246b9..3461936 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_ina228", "adafruit_register"] autodoc_preserve_defaults = True @@ -33,6 +33,7 @@ "python": ("https://docs.python.org/3", None), "BusDevice": ("https://docs.circuitpython.org/projects/busdevice/en/latest/", None), "Register": ("https://docs.circuitpython.org/projects/register/en/latest/", None), + "INA228": ("https://docs.circuitpython.org/projects/ina228/en/latest/", None), "CircuitPython": ("https://docs.circuitpython.org/en/latest/", None), } diff --git a/docs/index.rst b/docs/index.rst index 5317960..843793b 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -24,14 +24,13 @@ 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 INA23x 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 INA237 85V 10A 16-bit DC Current Voltage Power Monitor - STEMMA QT + Adafruit INA238 DC Current Voltage Power Monitor - STEMMA QT .. toctree:: :caption: Other Links diff --git a/examples/ina23x_simpletest.py b/examples/ina23x_simpletest.py index 42772ff..6238919 100644 --- a/examples/ina23x_simpletest.py +++ b/examples/ina23x_simpletest.py @@ -1,4 +1,46 @@ -# 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 + +"""Adafruit CircuitPython INA23x Simpletest""" + +import time + +import adafruit_ina228 +import board + +import adafruit_ina23x + +# Create I2C bus +i2c = board.I2C() + +# Create INA237/238 instance +ina23x = adafruit_ina23x.INA23X(i2c) + +# Configure the sensor (optional - these are just examples) +# ina23x.set_calibration(0.015, 10.0) # Default values +# ina23x.mode = adafruit_ina228.Mode.CONTINUOUS # Already default +# ina23x.averaging_count = adafruit_ina228.AveragingCount.COUNT_4 + +conv_times = [50, 84, 150, 280, 540, 1052, 2074, 4120] +avg_counts = [1, 4, 16, 64, 128, 256, 512, 1024] + +print("CircuitPython INA23x Test") +print(f"Bus conversion time: {conv_times[ina23x.bus_voltage_conv_time]} microseconds") +print(f"Shunt conversion time: {conv_times[ina23x.shunt_voltage_conv_time]} microseconds") +print(f"Samples averaged: {avg_counts[ina23x.averaging_count]}") +print() + +while True: + print(f"Current: {ina23x.current * 1000:.2f} mA") + print(f"Bus Voltage: {ina23x.bus_voltage:.2f} V") + print(f"Shunt Voltage: {ina23x.shunt_voltage * 1000:.2f} mV") + print(f"Power: {ina23x.power * 1000:.2f} mW") + print(f"Temperature: {ina23x.die_temperature:.2f} °C") + print() + + # Check if conversion is ready (useful in triggered mode) + # if ina23x.conversion_ready: + # print("Conversion ready!") + + time.sleep(2) diff --git a/requirements.txt b/requirements.txt index 7284723..28f4e2b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,3 +6,4 @@ Adafruit-Blinka adafruit-circuitpython-busdevice adafruit-circuitpython-register +adafruit-circuitpython-ina228