Skip to content
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

Added Comparator functionality #98

Merged
merged 12 commits into from
Aug 6, 2024
86 changes: 84 additions & 2 deletions adafruit_ads1x15/ads1x15.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,17 @@
_ADS1X15_DEFAULT_ADDRESS = const(0x48)
_ADS1X15_POINTER_CONVERSION = const(0x00)
_ADS1X15_POINTER_CONFIG = const(0x01)
_ADS1X15_POINTER_LO_THRES = const(0x02)
_ADS1X15_POINTER_HI_THRES = const(0x03)

_ADS1X15_CONFIG_OS_SINGLE = const(0x8000)
_ADS1X15_CONFIG_MUX_OFFSET = const(12)
_ADS1X15_CONFIG_COMP_QUE_DISABLE = const(0x0003)
_ADS1X15_CONFIG_COMP_QUEUE = {
0: 0x0003,
1: 0x0000,
2: 0x0001,
4: 0x0002,
}
_ADS1X15_CONFIG_GAIN = {
2 / 3: 0x0000,
1: 0x0200,
Expand Down Expand Up @@ -63,9 +71,20 @@ class ADS1x15:

:param ~busio.I2C i2c: The I2C bus the device is connected to.
:param float gain: The ADC gain.
Search logs

tannewt marked this conversation as resolved.
Show resolved Hide resolved
:param int data_rate: The data rate for ADC conversion in samples per second.
Default value depends on the device.
:param Mode mode: The conversion mode, defaults to `Mode.SINGLE`.
:param int comparator_queue_length: The number of successive conversions exceeding
the comparator threshold before asserting ALERT/RDY pin.
Defaults to 0 (comparator function disabled).
:param int comparator_low_threshold: Voltage limit under which comparator de-asserts
ALERT/RDY pin. Must be lower than high threshold to use comparator
function. Defaults to 0x8000.
:param int comparator_high_threshold: Voltage limit over which comparator asserts
ALERT/RDY pin. Must be higher than low threshold to use comparator
function. Defaults to 0x7FF0.
:param int address: The I2C address of the device.
"""

Expand All @@ -75,6 +94,9 @@ def __init__(
gain: float = 1,
data_rate: Optional[int] = None,
mode: int = Mode.SINGLE,
comparator_queue_length: int = 0,
comparator_low_threshold: int = 0x8000,
comparator_high_threshold: int = 0x7FF0,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is the low threshold default higher than the default high threshold?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is tricky because the values are in 2's complement, but also they are 12-bit values stored in 16-bit registers where the 4 LSBs are 0. Because of this, 0x8000 is the lowest possible value (decimal -32768 in 16-bit or really -2048 in 12-bit) and 0x7FF0 is the highest possible value (decimal 32752 in 16-bit or really 2047 in 12-bit).
I expanded the comments to hopefully explain this better.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah! I think it'll be clearer to set the defaults to the actual values and have the properties do the conversion and masking.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I set the defaults to the actual (12-bit signed) values so that it is easier to read, and moved the 2's complement and bit shift to 16-bit to the setter functions.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use 16-bit values because this library is also for the ADS1115 which is 16-bit. In the math you can do self.bits to read if it is 12 or 16 bit.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried to copy the handling of the comparator low/high threshold properties just like the other properties, such as data rate. The subclasses handle the lower level chip differences (12 vs 16 bits, default settings, etc) and pass those back to the higher level ads1x15 class.
Also, the ADC data in the analog_in class is handled as a signed 16-bit integer, so I felt it was appropriate to similarly handle the comparator threshold values as signed 16-bit integers as well.

address: int = _ADS1X15_DEFAULT_ADDRESS,
):
# pylint: disable=too-many-arguments
Expand All @@ -83,6 +105,9 @@ def __init__(
self.gain = gain
self.data_rate = self._data_rate_default() if data_rate is None else data_rate
self.mode = mode
self.comparator_queue_length = comparator_queue_length
self.comparator_low_threshold: comparator_low_threshold
self.comparator_high_threshold: comparator_high_threshold
self.i2c_device = I2CDevice(i2c, address)

@property
Expand Down Expand Up @@ -131,6 +156,63 @@ def gains(self) -> List[float]:
g.sort()
return g

@property
def comparator_queue_length(self) -> int:
"""The ADC comparator queue length."""
return self._comparator_queue_length

@comparator_queue_length.setter
def comparator_queue_length(self, comparator_queue_length: int) -> None:
possible_comparator_queue_lengths = self.comparator_queue_lengths
if comparator_queue_length not in possible_comparator_queue_lengths:
raise ValueError("Comparator Queue must be one of: {}".format(possible_comparator_queue_lengths))
self._comparator_queue_length = comparator_queue_length

@property
def comparator_queue_lengths(self) -> List[int]:
"""Possible comparator queue length settings."""
g = list(_ADS1X15_CONFIG_COMP_QUEUE.keys())
g.sort()
return g

@property
def comparator_low_threshold(self) -> int:
"""The ADC Comparator Lower Limit Threshold."""
return self._comparator_low_threshold

@comparator_low_threshold.setter
def comparator_low_threshold(self, comparator_low_threshold: int) -> None:
"""Sets 12-bit threshold in 16-bit register in unsigned format."""
if comparator_low_threshold < 0 or comparator_low_threshold > 65535:
raise ValueError("Comparator Low Threshold must be unsigned 16-bit integer between 0 and 65535")
self._comparator_low_threshold = comparator_low_threshold

"""Write value to chip"""
self.buf[0] = _ADS1X15_POINTER_LO_THRES
self.buf[1] = (self._comparator_low_threshold >> 8) & 0xFF
self.buf[2] = self._comparator_low_threshold & 0xFF
with self.i2c_device as i2c:
i2c.write(self.buf)

@property
def comparator_high_threshold(self) -> int:
"""The ADC Comparator Higher Limit Threshold."""
return self._comparator_high_threshold

@comparator_high_threshold.setter
def comparator_high_threshold(self, comparator_high_threshold: int) -> None:
"""Sets 12-bit threshold in 16-bit register in unsigned format."""
if comparator_high_threshold < 0 or comparator_high_threshold > 65535:
raise ValueError("Comparator High Threshold must be unsigned 16-bit integer between 0 and 65535")
self._comparator_high_threshold = comparator_high_threshold

"""Write value to chip"""
self.buf[0] = _ADS1X15_POINTER_HI_THRES
self.buf[1] = (self._comparator_high_threshold >> 8) & 0xFF
self.buf[2] = self._comparator_high_threshold & 0xFF
with self.i2c_device as i2c:
i2c.write(self.buf)

@property
def mode(self) -> int:
"""The ADC conversion mode."""
Expand Down Expand Up @@ -183,7 +265,7 @@ def _read(self, pin: Pin) -> int:
config |= _ADS1X15_CONFIG_GAIN[self.gain]
config |= self.mode
config |= self.rate_config[self.data_rate]
config |= _ADS1X15_CONFIG_COMP_QUE_DISABLE
config |= _ADS1X15_CONFIG_COMP_QUEUE[self.comparator_queue_length]
self._write_register(_ADS1X15_POINTER_CONFIG, config)

# Wait for conversion to complete
Expand Down
16 changes: 15 additions & 1 deletion adafruit_ads1x15/analog_in.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,5 +62,19 @@ def value(self) -> int:
@property
def voltage(self) -> float:
"""Returns the voltage from the ADC pin as a floating point value."""
volts = self.value * _ADS1X15_PGA_RANGE[self._ads.gain] / 32767
volts = self.convert_to_voltage(self.value)
return volts

def convert_to_value(self, volts: float) -> int:
"""Calculates integer for threshold registers from voltage level input"""
value = round((volts * 32767) / _ADS1X15_PGA_RANGE[self._ads.gain])
if value < 0:
value = 65536 + value
return value

def convert_to_voltage(self, value: int) -> float:
"""Calculates integer for threshold registers from voltage level input"""
volts = value * _ADS1X15_PGA_RANGE[self._ads.gain] / 32767
if volts > _ADS1X15_PGA_RANGE[self._ads.gain]:
volts = _ADS1X15_PGA_RANGE[self._ads.gain] - volts
return volts
42 changes: 42 additions & 0 deletions examples/ads1x15_comparator_example.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries
# SPDX-License-Identifier: MIT

import time
import board
import busio
import countio

import adafruit_ads1x15.ads1015 as ADS
# import adafruit_ads1x15.ads1115 as ADS
from adafruit_ads1x15.analog_in import AnalogIn

# Create the I2C bus
i2c = busio.I2C(board.SCL, board.SDA)

# Create the ADS object
ads = ADS.ADS1015(i2c)
# ads = ADS.ADS1115(i2c)

# Create a single-ended channel on Pin 0
# Max counts for ADS1015 = 2047
# ADS1115 = 32767
chan = AnalogIn(ads, ADS.P0)

# Create Interrupt-driven input to track comparator changes
int_pin = countio.Counter(board.GP9, edge=countio.Edge.RISE)

# Set comparator to assert after 1 ADC conversion
ads.comparator_queue_length = 1

# Set comparator low threshold to 2V
ads.comparator_low_threshold = chan.convert_to_value(2.000)
# Set comparator high threshold to 2.002V. High threshold must be above low threshold
ads.comparator_high_threshold = chan.convert_to_value(2.002)

count = 0
while True:
print(chan.value, chan.voltage) #This initiates new ADC reading
if int_pin.count > count:
print("Comparator Triggered")
count = int_pin.count
time.sleep(2)