Skip to content

library, docs and example #1

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jun 9, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 20 additions & 2 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ 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>`_
* `Adafruit CircuitPython INA228 <https://github.com/adafruit/Adafruit_CircuitPython_INA228>`_

Please ensure all dependencies are available on the CircuitPython filesystem.
This is easily achieved by downloading
Expand All @@ -39,6 +40,7 @@ or individual libraries can be installed using
`circup <https://github.com/adafruit/circup>`_.

`Purchase INA237 from the Adafruit shop <http://www.adafruit.com/products/6340>`_

`Purchase INA238 from the Adafruit shop <http://www.adafruit.com/products/6349>`_

Installing from PyPI
Expand Down Expand Up @@ -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
=============
Expand Down
152 changes: 142 additions & 10 deletions adafruit_ina23x.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
# SPDX-FileCopyrightText: 2017 Scott Shawcroft, written for Adafruit Industries
# SPDX-FileCopyrightText: Copyright (c) 2025 Liz Clark for Adafruit Industries
#
# SPDX-License-Identifier: MIT
"""
`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
Expand All @@ -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 <url>`_"
* `Adafruit INA237 Breakout <https://www.adafruit.com/product/6340>`_
* `Adafruit INA238 Breakout <https://www.adafruit.com/product/6349>`_

**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
3 changes: 2 additions & 1 deletion docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,15 @@
# 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

intersphinx_mapping = {
"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),
}

Expand Down
7 changes: 3 additions & 4 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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 <https://learn.adafruit.com/adafruit-ina237-dc-current-voltage-power-monitor>

.. 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 <http://www.adafruit.com/products/6340>
Adafruit INA238 DC Current Voltage Power Monitor - STEMMA QT <http://www.adafruit.com/products/6349>

.. toctree::
:caption: Other Links
Expand Down
46 changes: 44 additions & 2 deletions examples/ina23x_simpletest.py
Original file line number Diff line number Diff line change
@@ -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)
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@
Adafruit-Blinka
adafruit-circuitpython-busdevice
adafruit-circuitpython-register
adafruit-circuitpython-ina228