-
Notifications
You must be signed in to change notification settings - Fork 60
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
Changes from 9 commits
017fb9f
c84a39d
7d98fde
0aa1cd7
29ee801
a730639
f74e4f9
4ba6144
8ddd81c
657b768
f7fd261
3e31097
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -67,6 +67,12 @@ def rate_config(self) -> Dict[int, int]: | |
def _data_rate_default(self) -> Literal[1600]: | ||
return 1600 | ||
|
||
def _comp_low_thres_default(self) -> Literal[0x8000]: | ||
return 0x8000 | ||
|
||
def _comp_high_thres_default(self) -> Literal[0x7FF0]: | ||
return 0x7FF0 | ||
|
||
def _conversion_value(self, raw_adc: int) -> int: | ||
value = struct.unpack(">h", raw_adc.to_bytes(2, "big"))[0] | ||
return value >> 4 | ||
return value | ||
Comment on lines
-72
to
+73
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Don't change this. You'll change how There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In the ads1015 flow, I believe there are 2 unnecessary bit shifts. This is the right bit shift, which is immediately cancelled out by the left bit shift in the value property of analog_in.py. I removed both of these. There is no need to take in a 16-bit number, convert to 12, convert back to 16, and then report as 16. Removing both keeps .value at 16-bits for both ads1015 and ads1115. |
Original file line number | Diff line number | Diff line change | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -68,6 +68,12 @@ def rate_config(self) -> Dict[int, int]: | |||||||||||
def _data_rate_default(self) -> Literal[128]: | ||||||||||||
return 128 | ||||||||||||
|
||||||||||||
def _comp_low_thres_default(self) -> Literal[0x8000]: | ||||||||||||
return 0x8000 | ||||||||||||
|
||||||||||||
def _comp_high_thres_default(self) -> Literal[0x7FFF]: | ||||||||||||
return 0x7FFF | ||||||||||||
|
||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||
def _conversion_value(self, raw_adc: int) -> int: | ||||||||||||
value = struct.unpack(">h", raw_adc.to_bytes(2, "big"))[0] | ||||||||||||
return value |
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -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, | ||||||||||||||||||||||||||
|
@@ -66,15 +74,30 @@ class ADS1x15: | |||||||||||||||||||||||||
: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. Value is 12-bit, 2's complement stored in | ||||||||||||||||||||||||||
16-bit register where 4 LSBs are 0. Defaults to 0x8000 (decimal -32768). | ||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I just realized I forgot to update this comment in the higher level (ads1x15) class to be more generic, since the subclasses/chips handle this differently. |
||||||||||||||||||||||||||
:param int comparator_high_threshold: Voltage limit over which comparator asserts | ||||||||||||||||||||||||||
ALERT/RDY pin. Must be higher than low threshold to use comparator | ||||||||||||||||||||||||||
function. Value is 12-bit, 2's complement stored in | ||||||||||||||||||||||||||
16-bit register where 4 LSBs are 0. Defaults to 0x7FF0 (decimal 32752). | ||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||||
:param int address: The I2C address of the device. | ||||||||||||||||||||||||||
""" | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
# pylint: disable=too-many-instance-attributes | ||||||||||||||||||||||||||
def __init__( | ||||||||||||||||||||||||||
self, | ||||||||||||||||||||||||||
i2c: I2C, | ||||||||||||||||||||||||||
gain: float = 1, | ||||||||||||||||||||||||||
data_rate: Optional[int] = None, | ||||||||||||||||||||||||||
mode: int = Mode.SINGLE, | ||||||||||||||||||||||||||
comparator_queue_length: int = 0, | ||||||||||||||||||||||||||
comparator_low_threshold: Optional[int] = None, | ||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||||
comparator_high_threshold: Optional[int] = None, | ||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||||
address: int = _ADS1X15_DEFAULT_ADDRESS, | ||||||||||||||||||||||||||
): | ||||||||||||||||||||||||||
# pylint: disable=too-many-arguments | ||||||||||||||||||||||||||
|
@@ -83,7 +106,18 @@ 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.i2c_device = I2CDevice(i2c, address) | ||||||||||||||||||||||||||
self.comparator_low_threshold = ( | ||||||||||||||||||||||||||
self._comp_low_thres_default() | ||||||||||||||||||||||||||
if comparator_low_threshold is None | ||||||||||||||||||||||||||
else comparator_low_threshold | ||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||
self.comparator_high_threshold = ( | ||||||||||||||||||||||||||
self._comp_high_thres_default() | ||||||||||||||||||||||||||
if comparator_high_threshold is None | ||||||||||||||||||||||||||
else comparator_high_threshold | ||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The default is the same for both because the lower four bits are 1s in the high threshold.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You are correct. I mistakenly went to some effort to separate them when I didn't need to. |
||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
@property | ||||||||||||||||||||||||||
def bits(self) -> int: | ||||||||||||||||||||||||||
|
@@ -131,6 +165,61 @@ 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_comp_queue_lengths = self.comparator_queue_lengths | ||||||||||||||||||||||||||
if comparator_queue_length not in possible_comp_queue_lengths: | ||||||||||||||||||||||||||
raise ValueError( | ||||||||||||||||||||||||||
"Comparator Queue must be one of: {}".format( | ||||||||||||||||||||||||||
possible_comp_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 | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
@property | ||||||||||||||||||||||||||
def comparator_high_threshold(self) -> int: | ||||||||||||||||||||||||||
"""The ADC Comparator Higher Limit Threshold.""" | ||||||||||||||||||||||||||
return self._comparator_high_threshold | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
@comparator_low_threshold.setter | ||||||||||||||||||||||||||
def comparator_low_threshold(self, value: int) -> None: | ||||||||||||||||||||||||||
"""Set comparator low threshold value for ADS1015 ADC | ||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
:param int value: 16-bit signed integer to write to register | ||||||||||||||||||||||||||
""" | ||||||||||||||||||||||||||
if value < 0 or value > 65535: | ||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Isn't this the right range to check against since it is signed?
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You are technically correct, but python will look at 0xFFFF as 65535 and say that it is out of range, instead of looking at it as -1 and say that it is in range. I feel like what I did was a workaround to make things work. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I just discovered "a_bytes_big = an_int.to_bytes(2, 'big', signed = True)" that should convert between signed integers and bytes easier. I'll try to work that in. |
||||||||||||||||||||||||||
raise ValueError("Comparator Threshold value must be between 0 and 65535") | ||||||||||||||||||||||||||
self._comparator_low_threshold = value | ||||||||||||||||||||||||||
self._write_register(_ADS1X15_POINTER_LO_THRES, self.comparator_low_threshold) | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
@comparator_high_threshold.setter | ||||||||||||||||||||||||||
def comparator_high_threshold(self, value: int) -> None: | ||||||||||||||||||||||||||
"""Set comparator high threshold value for ADS1015 ADC | ||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
:param int value: 16-bit signed integer to write to register | ||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||||
""" | ||||||||||||||||||||||||||
if value < 0 or value > 65535: | ||||||||||||||||||||||||||
raise ValueError("Comparator Threshold value must be between 0 and 65535") | ||||||||||||||||||||||||||
self._comparator_high_threshold = value | ||||||||||||||||||||||||||
self._write_register(_ADS1X15_POINTER_HI_THRES, self.comparator_high_threshold) | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
@property | ||||||||||||||||||||||||||
def mode(self) -> int: | ||||||||||||||||||||||||||
"""The ADC conversion mode.""" | ||||||||||||||||||||||||||
|
@@ -157,6 +246,18 @@ def _data_rate_default(self) -> int: | |||||||||||||||||||||||||
""" | ||||||||||||||||||||||||||
raise NotImplementedError("Subclasses must implement _data_rate_default!") | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
def _comp_low_thres_default(self) -> int: | ||||||||||||||||||||||||||
"""Retrieve the default comparator low threshold for this ADC (in 16-bit signed int). | ||||||||||||||||||||||||||
Should be implemented by subclasses. | ||||||||||||||||||||||||||
""" | ||||||||||||||||||||||||||
raise NotImplementedError("Subclasses must implement _comp_low_thres_default!") | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
def _comp_high_thres_default(self) -> int: | ||||||||||||||||||||||||||
"""Retrieve the default comparator high threshold for this ADC (in 16-bit signed int). | ||||||||||||||||||||||||||
Should be implemented by subclasses. | ||||||||||||||||||||||||||
""" | ||||||||||||||||||||||||||
raise NotImplementedError("Subclasses must implement _comp_high_thres_default!") | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||||
def _conversion_value(self, raw_adc: int) -> int: | ||||||||||||||||||||||||||
"""Subclasses should override this function that takes the 16 raw ADC | ||||||||||||||||||||||||||
values of a conversion result and returns a signed integer value. | ||||||||||||||||||||||||||
|
@@ -183,7 +284,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 | ||||||||||||||||||||||||||
|
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -55,12 +55,49 @@ def value(self) -> int: | |||||
Even if the underlying analog to digital converter (ADC) is | ||||||
lower resolution, the value is 16-bit. | ||||||
""" | ||||||
return self._ads.read( | ||||||
self._pin_setting, is_differential=self.is_differential | ||||||
) << (16 - self._ads.bits) | ||||||
return self._ads.read(self._pin_setting, is_differential=self.is_differential) | ||||||
|
||||||
@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 12-bit integer for threshold registers from voltage level input""" | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
||||||
# Convert 2's complement of signed int if number is negative | ||||||
tannewt marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
if volts > 0: | ||||||
value = round( | ||||||
(volts / _ADS1X15_PGA_RANGE[self._ads.gain]) | ||||||
* ((1 << (self._ads.bits - 1)) - 1) | ||||||
) | ||||||
else: | ||||||
value = round( | ||||||
(volts / _ADS1X15_PGA_RANGE[self._ads.gain]) | ||||||
* (1 << (self._ads.bits - 1)) | ||||||
) | ||||||
value += 1 << self._ads.bits | ||||||
|
||||||
# Need to bit shift if value is only 12-bits | ||||||
value <<= 16 - self._ads.bits | ||||||
return value | ||||||
|
||||||
def convert_to_voltage(self, value_int: int) -> float: | ||||||
"""Calculates voltage from 16-bit ADC reading""" | ||||||
|
||||||
if value_int & 0x8000: | ||||||
# Need to convert negative number through 2's complement | ||||||
value_int -= 0x10000 | ||||||
|
||||||
# Need to bit shift if value is only 12-bits | ||||||
value_int >>= 16 - self._ads.bits | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Again. Don't worry about two's complement or the numbers of bits. This should be 16 bit unsigned or "17 bit" signed to voltage. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we are on different pages as far as how to represent value. From "Signed number representations" on Wikipedia, 8-bit signed goes from -127 to 127. 8-bit unsigned goes from 0 to 255. I think value should match the registers in the datasheet, which is 16 bit two's complement or signed. Can you explain 17-bit to me? Voltage is a float since we want it to have decimal places. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. value for reading from an adc has been defined as 16-bit unsigned since we added the AnalogIn API. If you want this resolution signed, then you need to add another bit for the sign. That would make it 17 total bits. I realize that the comparator will only do 16-bit signed. This conversion from voltage to value should handle the full 16-bit unsigned range still though. |
||||||
|
||||||
volts = float(value_int) | ||||||
volts = ( | ||||||
volts | ||||||
* _ADS1X15_PGA_RANGE[self._ads.gain] | ||||||
/ (0x7FFF >> (16 - self._ads.bits)) | ||||||
) | ||||||
|
||||||
return volts |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
# 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) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.