From 8845b0fc90dbc8fb6a8a52f31fb2300fc967f191 Mon Sep 17 00:00:00 2001 From: RoaCode <176423185+RoaCode@users.noreply.github.com> Date: Tue, 6 Aug 2024 15:54:28 -0400 Subject: [PATCH 1/3] Added functions for controlling additional comparator settings Added classes and setter/getter functions for comparator mode, latch, and polarity settings. Also added a read_config function to read the configuration register of the ads1x15 chip. --- adafruit_ads1x15/ads1015.py | 2 +- adafruit_ads1x15/ads1115.py | 2 +- adafruit_ads1x15/ads1x15.py | 121 ++++++++++++++++++++++++- examples/ads1x15_comparator_example.py | 13 ++- 4 files changed, 133 insertions(+), 5 deletions(-) diff --git a/adafruit_ads1x15/ads1015.py b/adafruit_ads1x15/ads1015.py index 38bf7f7..9ec537a 100644 --- a/adafruit_ads1x15/ads1015.py +++ b/adafruit_ads1x15/ads1015.py @@ -20,7 +20,7 @@ pass # pylint: disable=unused-import -from .ads1x15 import ADS1x15, Mode +from .ads1x15 import ADS1x15 # Data sample rates _ADS1015_CONFIG_DR = { diff --git a/adafruit_ads1x15/ads1115.py b/adafruit_ads1x15/ads1115.py index ca834f5..2d23b99 100644 --- a/adafruit_ads1x15/ads1115.py +++ b/adafruit_ads1x15/ads1115.py @@ -20,7 +20,7 @@ pass # pylint: disable=unused-import -from .ads1x15 import ADS1x15, Mode +from .ads1x15 import ADS1x15 # Data sample rates _ADS1115_CONFIG_DR = { diff --git a/adafruit_ads1x15/ads1x15.py b/adafruit_ads1x15/ads1x15.py index fdd53cd..c8456b6 100644 --- a/adafruit_ads1x15/ads1x15.py +++ b/adafruit_ads1x15/ads1x15.py @@ -66,6 +66,42 @@ class Mode: """Single-Shot Mode""" +class Comp_Mode: + """An enum-like class representing possible ADC Comparator operating modes.""" + + # See datasheet "Operating Modes" section + # values here are masks for setting COMP_MODE bit in Config Register + # pylint: disable=too-few-public-methods + TRADITIONAL = 0x0000 + """Traditional Compartor Mode activates above high threshold, de-activates below low""" + WINDOW = 0x0010 + """Window Comparator Mode activates when reading is outside of high and low thresholds""" + + +class Comp_Polarity: + """An enum-like class representing possible ADC Comparator polarity modes.""" + + # See datasheet "Operating Modes" section + # values here are masks for setting COMP_POL bit in Config Register + # pylint: disable=too-few-public-methods + ACTIVE_LOW = 0x0000 + """ALERT_RDY pin is LOW when comparator is active""" + ACTIVE_HIGH = 0x0008 + """ALERT_RDY pin is HIGH when comparator is active""" + + +class Comp_Latch: + """An enum-like class representing possible ADC Comparator latching modes.""" + + # See datasheet "Operating Modes" section + # values here are masks for setting COMP_LAT bit in Config Register + # pylint: disable=too-few-public-methods + NONLATCHING = 0x0000 + """ALERT_RDY pin does not latch when asserted""" + LATCHING = 0x0004 + """ALERT_RDY pin remains asserted until data is read by controller""" + + class ADS1x15: """Base functionality for ADS1x15 analog to digital converters. @@ -79,10 +115,17 @@ class ADS1x15: 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. See subclass for value range and default. + function. Range of -32768 to 32767, default -32768 :param int comparator_high_threshold: Voltage limit over which comparator asserts ALERT/RDY pin. Must be higher than low threshold to use comparator - function. See subclass for value range and default. + function. Range of -32768 to 32767, default 32767 + :param Comp_Mode comparator_mode: Configures the comparator as either traditional or window. + Defaults to 'Comp_Mode.TRADITIONAL' + :param Comp_Polarity comparator_polarity: Configures the comparator output as either active + low or active high. Defaults to 'Comp_Polarity.ACTIVE_LOW' + :param Comp_Latch comparator_latch: Configures the comparator output to only stay asserted while + readings exceed threshold or latch on assertion until data is read. + Defaults to 'Comp_Latch.NONLATCHING' :param int address: The I2C address of the device. """ @@ -96,6 +139,9 @@ def __init__( comparator_queue_length: int = 0, comparator_low_threshold: int = -32768, comparator_high_threshold: int = 32767, + comparator_mode: int = Comp_Mode.TRADITIONAL, + comparator_polarity: int = Comp_Polarity.ACTIVE_LOW, + comparator_latch: int = Comp_Latch.NONLATCHING, address: int = _ADS1X15_DEFAULT_ADDRESS, ): # pylint: disable=too-many-arguments @@ -108,6 +154,9 @@ def __init__( self.i2c_device = I2CDevice(i2c, address) self.comparator_low_threshold = comparator_low_threshold self.comparator_high_threshold = comparator_high_threshold + self.comparator_mode = comparator_mode + self.comparator_polarity = comparator_polarity + self.comparator_latch = comparator_latch @property def bits(self) -> int: @@ -227,6 +276,39 @@ def mode(self, mode: int) -> None: raise ValueError("Unsupported mode.") self._mode = mode + @property + def comparator_mode(self) -> int: + """The ADC comparator mode.""" + return self._comparator_mode + + @comparator_mode.setter + def comparator_mode(self, comp_mode: int) -> None: + if comp_mode not in (Comp_Mode.TRADITIONAL, Comp_Mode.WINDOW): + raise ValueError("Unsupported mode.") + self._comparator_mode = comp_mode + + @property + def comparator_polarity(self) -> int: + """The ADC comparator polarity mode.""" + return self._comparator_polarity + + @comparator_polarity.setter + def comparator_polarity(self, comp_pol: int) -> None: + if comp_pol not in (Comp_Polarity.ACTIVE_LOW, Comp_Polarity.ACTIVE_HIGH): + raise ValueError("Unsupported mode.") + self._comparator_polarity = comp_pol + + @property + def comparator_latch(self) -> int: + """The ADC comparator latching mode.""" + return self._comparator_latch + + @comparator_latch.setter + def comparator_latch(self, comp_latch: int) -> None: + if comp_latch not in (Comp_Latch.NONLATCHING, Comp_Latch.LATCHING): + raise ValueError("Unsupported mode.") + self._comparator_latch = comp_latch + def read(self, pin: Pin, is_differential: bool = False) -> int: """I2C Interface for ADS1x15-based ADCs reads. @@ -268,6 +350,9 @@ def _read(self, pin: Pin) -> int: config |= _ADS1X15_CONFIG_GAIN[self.gain] config |= self.mode config |= self.rate_config[self.data_rate] + config |= self.comparator_mode + config |= self.comparator_polarity + config |= self.comparator_latch config |= _ADS1X15_CONFIG_COMP_QUEUE[self.comparator_queue_length] self._write_register(_ADS1X15_POINTER_CONFIG, config) @@ -317,3 +402,35 @@ def _read_register(self, reg: int, fast: bool = False) -> int: else: i2c.write_then_readinto(bytearray([reg]), self.buf, in_end=2) return self.buf[0] << 8 | self.buf[1] + + def read_config(self) -> None: + """Reads Config Register and sets all properties accordingly""" + config_value = self._read_register(_ADS1X15_POINTER_CONFIG) + + self.gain = next( + key + for key, value in _ADS1X15_CONFIG_GAIN.items() + if value == (config_value & 0x0E00) + ) + self.data_rate = next( + key + for key, value in self.rate_config.items() + if value == (config_value & 0x00E0) + ) + self.comparator_queue_length = next( + key + for key, value in _ADS1X15_CONFIG_COMP_QUEUE.items() + if value == (config_value & 0x0003) + ) + self.mode = Mode.SINGLE if config_value & 0x0100 else Mode.CONTINUOUS + self.comparator_mode = ( + Comp_Mode.WINDOW if config_value & 0x0010 else Comp_Mode.TRADITIONAL + ) + self.comparator_polarity = ( + Comp_Polarity.ACTIVE_HIGH + if config_value & 0x0008 + else Comp_Polarity.ACTIVE_LOW + ) + self.comparator_latch = ( + Comp_Latch.LATCHING if config_value & 0x0004 else Comp_Latch.NONLATCHING + ) diff --git a/examples/ads1x15_comparator_example.py b/examples/ads1x15_comparator_example.py index 25567ff..4fb8609 100644 --- a/examples/ads1x15_comparator_example.py +++ b/examples/ads1x15_comparator_example.py @@ -9,6 +9,7 @@ import adafruit_ads1x15.ads1015 as ADS # import adafruit_ads1x15.ads1115 as ADS +from adafruit_ads1x15.ads1x15 import Mode, Comp_Mode, Comp_Polarity, Comp_Latch from adafruit_ads1x15.analog_in import AnalogIn # Create the I2C bus @@ -26,9 +27,19 @@ # Create Interrupt-driven input to track comparator changes int_pin = countio.Counter(board.GP9, edge=countio.Edge.RISE) +# Set ADC to continuously read new data +ads.mode = Mode.CONTINUOUS # Set comparator to assert after 1 ADC conversion ads.comparator_queue_length = 1 - +# Set comparator to use traditional threshold instead of window +ads.comparator_mode = Comp_Mode.TRADITIONAL +# Set comparator output to de-assert if readings no longer above threshold +ads.comparator_latch = Comp_Latch.NONLATCHING +# Set comparator output to logic LOW when asserted +ads.comparator_polarity = Comp_Polarity.ACTIVE_LOW + +# Gain should be explicitly set to ensure threshold values are calculated correctly +ads.gain = 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 From ba6512042cc8afbca3dbedbbd9d5078aedf02cd6 Mon Sep 17 00:00:00 2001 From: RoaCode <176423185+RoaCode@users.noreply.github.com> Date: Mon, 12 Aug 2024 14:26:03 -0400 Subject: [PATCH 2/3] Added write_config function, updated comparator example --- adafruit_ads1x15/ads1x15.py | 66 ++++++++------------------ adafruit_ads1x15/analog_in.py | 8 +++- examples/ads1x15_comparator_example.py | 3 +- 3 files changed, 29 insertions(+), 48 deletions(-) diff --git a/adafruit_ads1x15/ads1x15.py b/adafruit_ads1x15/ads1x15.py index c8456b6..bb170aa 100644 --- a/adafruit_ads1x15/ads1x15.py +++ b/adafruit_ads1x15/ads1x15.py @@ -309,13 +309,12 @@ def comparator_latch(self, comp_latch: int) -> None: raise ValueError("Unsupported mode.") self._comparator_latch = comp_latch - def read(self, pin: Pin, is_differential: bool = False) -> int: + def read(self, pin: Pin) -> int: """I2C Interface for ADS1x15-based ADCs reads. :param ~microcontroller.Pin pin: individual or differential pin. :param bool is_differential: single-ended or differential read. """ - pin = pin if is_differential else pin + 0x04 return self._read(pin) def _data_rate_default(self) -> int: @@ -342,19 +341,7 @@ def _read(self, pin: Pin) -> int: # Configure ADC every time before a conversion in SINGLE mode # or changing channels in CONTINUOUS mode - if self.mode == Mode.SINGLE: - config = _ADS1X15_CONFIG_OS_SINGLE - else: - config = 0 - config |= (pin & 0x07) << _ADS1X15_CONFIG_MUX_OFFSET - config |= _ADS1X15_CONFIG_GAIN[self.gain] - config |= self.mode - config |= self.rate_config[self.data_rate] - config |= self.comparator_mode - config |= self.comparator_polarity - config |= self.comparator_latch - config |= _ADS1X15_CONFIG_COMP_QUEUE[self.comparator_queue_length] - self._write_register(_ADS1X15_POINTER_CONFIG, config) + self.write_config(pin) # Wait for conversion to complete # ADS1x1x devices settle within a single conversion cycle @@ -403,34 +390,21 @@ def _read_register(self, reg: int, fast: bool = False) -> int: i2c.write_then_readinto(bytearray([reg]), self.buf, in_end=2) return self.buf[0] << 8 | self.buf[1] - def read_config(self) -> None: - """Reads Config Register and sets all properties accordingly""" - config_value = self._read_register(_ADS1X15_POINTER_CONFIG) - - self.gain = next( - key - for key, value in _ADS1X15_CONFIG_GAIN.items() - if value == (config_value & 0x0E00) - ) - self.data_rate = next( - key - for key, value in self.rate_config.items() - if value == (config_value & 0x00E0) - ) - self.comparator_queue_length = next( - key - for key, value in _ADS1X15_CONFIG_COMP_QUEUE.items() - if value == (config_value & 0x0003) - ) - self.mode = Mode.SINGLE if config_value & 0x0100 else Mode.CONTINUOUS - self.comparator_mode = ( - Comp_Mode.WINDOW if config_value & 0x0010 else Comp_Mode.TRADITIONAL - ) - self.comparator_polarity = ( - Comp_Polarity.ACTIVE_HIGH - if config_value & 0x0008 - else Comp_Polarity.ACTIVE_LOW - ) - self.comparator_latch = ( - Comp_Latch.LATCHING if config_value & 0x0004 else Comp_Latch.NONLATCHING - ) + def write_config(self, pin_config: int) -> None: + """Write to configuration register of ADC + + :param int pin_config: setting for MUX value in config register + """ + if self.mode == Mode.SINGLE: + config = _ADS1X15_CONFIG_OS_SINGLE + else: + config = 0 + config |= (pin_config & 0x07) << _ADS1X15_CONFIG_MUX_OFFSET + config |= _ADS1X15_CONFIG_GAIN[self.gain] + config |= self.mode + config |= self.rate_config[self.data_rate] + config |= self.comparator_mode + config |= self.comparator_polarity + config |= self.comparator_latch + config |= _ADS1X15_CONFIG_COMP_QUEUE[self.comparator_queue_length] + self._write_register(_ADS1X15_POINTER_CONFIG, config) diff --git a/adafruit_ads1x15/analog_in.py b/adafruit_ads1x15/analog_in.py index 726314c..021c1b9 100644 --- a/adafruit_ads1x15/analog_in.py +++ b/adafruit_ads1x15/analog_in.py @@ -55,7 +55,13 @@ 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) + pin = self._pin_setting if self.is_differential else self._pin_setting + 0x04 + return self._ads.read(pin) + + def write_config(self) -> None: + """Calculates MUX setting and writes to configuration register""" + pin = self._pin_setting if self.is_differential else self._pin_setting + 0x04 + self._ads.write_config(pin) @property def voltage(self) -> float: diff --git a/examples/ads1x15_comparator_example.py b/examples/ads1x15_comparator_example.py index 4fb8609..23db284 100644 --- a/examples/ads1x15_comparator_example.py +++ b/examples/ads1x15_comparator_example.py @@ -37,7 +37,6 @@ ads.comparator_latch = Comp_Latch.NONLATCHING # Set comparator output to logic LOW when asserted ads.comparator_polarity = Comp_Polarity.ACTIVE_LOW - # Gain should be explicitly set to ensure threshold values are calculated correctly ads.gain = 1 # Set comparator low threshold to 2V @@ -45,6 +44,8 @@ # Set comparator high threshold to 2.002V. High threshold must be above low threshold ads.comparator_high_threshold = chan.convert_to_value(2.002) +chan.write_config() + count = 0 while True: print(chan.value, chan.voltage) # This initiates new ADC reading From ed79c5677f109e9719c50810619cb8e3d70368c2 Mon Sep 17 00:00:00 2001 From: RoaCode <176423185+RoaCode@users.noreply.github.com> Date: Wed, 14 Aug 2024 17:22:56 -0400 Subject: [PATCH 3/3] Moved write_config and read_config to private functions --- adafruit_ads1x15/ads1x15.py | 63 ++++++++++++++++++++++++-- adafruit_ads1x15/analog_in.py | 5 -- examples/ads1x15_comparator_example.py | 2 - 3 files changed, 60 insertions(+), 10 deletions(-) diff --git a/adafruit_ads1x15/ads1x15.py b/adafruit_ads1x15/ads1x15.py index bb170aa..7bee3fd 100644 --- a/adafruit_ads1x15/ads1x15.py +++ b/adafruit_ads1x15/ads1x15.py @@ -147,16 +147,21 @@ def __init__( # pylint: disable=too-many-arguments self._last_pin_read = None self.buf = bytearray(3) + self.initialized = ( + False # Prevents writing to ADC until all values are initialized + ) + self.i2c_device = I2CDevice(i2c, address) 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 = comparator_low_threshold self.comparator_high_threshold = comparator_high_threshold self.comparator_mode = comparator_mode self.comparator_polarity = comparator_polarity self.comparator_latch = comparator_latch + self.initialized = True + self._write_config() @property def bits(self) -> int: @@ -174,6 +179,8 @@ def data_rate(self, rate: int) -> None: if rate not in possible_rates: raise ValueError("Data rate must be one of: {}".format(possible_rates)) self._data_rate = rate + if self.initialized: + self._write_config() @property def rates(self) -> List[int]: @@ -196,6 +203,8 @@ def gain(self, gain: float) -> None: if gain not in possible_gains: raise ValueError("Gain must be one of: {}".format(possible_gains)) self._gain = gain + if self.initialized: + self._write_config() @property def gains(self) -> List[float]: @@ -219,6 +228,8 @@ def comparator_queue_length(self, comparator_queue_length: int) -> None: ) ) self._comparator_queue_length = comparator_queue_length + if self.initialized: + self._write_config() @property def comparator_queue_lengths(self) -> List[int]: @@ -275,6 +286,8 @@ def mode(self, mode: int) -> None: if mode not in (Mode.CONTINUOUS, Mode.SINGLE): raise ValueError("Unsupported mode.") self._mode = mode + if self.initialized: + self._write_config() @property def comparator_mode(self) -> int: @@ -286,6 +299,8 @@ def comparator_mode(self, comp_mode: int) -> None: if comp_mode not in (Comp_Mode.TRADITIONAL, Comp_Mode.WINDOW): raise ValueError("Unsupported mode.") self._comparator_mode = comp_mode + if self.initialized: + self._write_config() @property def comparator_polarity(self) -> int: @@ -297,6 +312,8 @@ def comparator_polarity(self, comp_pol: int) -> None: if comp_pol not in (Comp_Polarity.ACTIVE_LOW, Comp_Polarity.ACTIVE_HIGH): raise ValueError("Unsupported mode.") self._comparator_polarity = comp_pol + if self.initialized: + self._write_config() @property def comparator_latch(self) -> int: @@ -308,6 +325,8 @@ def comparator_latch(self, comp_latch: int) -> None: if comp_latch not in (Comp_Latch.NONLATCHING, Comp_Latch.LATCHING): raise ValueError("Unsupported mode.") self._comparator_latch = comp_latch + if self.initialized: + self._write_config() def read(self, pin: Pin) -> int: """I2C Interface for ADS1x15-based ADCs reads. @@ -341,7 +360,7 @@ def _read(self, pin: Pin) -> int: # Configure ADC every time before a conversion in SINGLE mode # or changing channels in CONTINUOUS mode - self.write_config(pin) + self._write_config(pin) # Wait for conversion to complete # ADS1x1x devices settle within a single conversion cycle @@ -390,15 +409,21 @@ def _read_register(self, reg: int, fast: bool = False) -> int: i2c.write_then_readinto(bytearray([reg]), self.buf, in_end=2) return self.buf[0] << 8 | self.buf[1] - def write_config(self, pin_config: int) -> None: + def _write_config(self, pin_config: Optional[int] = None) -> None: """Write to configuration register of ADC :param int pin_config: setting for MUX value in config register """ + if pin_config is None: + pin_config = ( + self._read_register(_ADS1X15_POINTER_CONFIG) & 0x7000 + ) >> _ADS1X15_CONFIG_MUX_OFFSET + if self.mode == Mode.SINGLE: config = _ADS1X15_CONFIG_OS_SINGLE else: config = 0 + config |= (pin_config & 0x07) << _ADS1X15_CONFIG_MUX_OFFSET config |= _ADS1X15_CONFIG_GAIN[self.gain] config |= self.mode @@ -408,3 +433,35 @@ def write_config(self, pin_config: int) -> None: config |= self.comparator_latch config |= _ADS1X15_CONFIG_COMP_QUEUE[self.comparator_queue_length] self._write_register(_ADS1X15_POINTER_CONFIG, config) + + def _read_config(self) -> None: + """Reads Config Register and sets all properties accordingly""" + config_value = self._read_register(_ADS1X15_POINTER_CONFIG) + + self.gain = next( + key + for key, value in _ADS1X15_CONFIG_GAIN.items() + if value == (config_value & 0x0E00) + ) + self.data_rate = next( + key + for key, value in self.rate_config.items() + if value == (config_value & 0x00E0) + ) + self.comparator_queue_length = next( + key + for key, value in _ADS1X15_CONFIG_COMP_QUEUE.items() + if value == (config_value & 0x0003) + ) + self.mode = Mode.SINGLE if config_value & 0x0100 else Mode.CONTINUOUS + self.comparator_mode = ( + Comp_Mode.WINDOW if config_value & 0x0010 else Comp_Mode.TRADITIONAL + ) + self.comparator_polarity = ( + Comp_Polarity.ACTIVE_HIGH + if config_value & 0x0008 + else Comp_Polarity.ACTIVE_LOW + ) + self.comparator_latch = ( + Comp_Latch.LATCHING if config_value & 0x0004 else Comp_Latch.NONLATCHING + ) diff --git a/adafruit_ads1x15/analog_in.py b/adafruit_ads1x15/analog_in.py index 021c1b9..510151d 100644 --- a/adafruit_ads1x15/analog_in.py +++ b/adafruit_ads1x15/analog_in.py @@ -58,11 +58,6 @@ def value(self) -> int: pin = self._pin_setting if self.is_differential else self._pin_setting + 0x04 return self._ads.read(pin) - def write_config(self) -> None: - """Calculates MUX setting and writes to configuration register""" - pin = self._pin_setting if self.is_differential else self._pin_setting + 0x04 - self._ads.write_config(pin) - @property def voltage(self) -> float: """Returns the voltage from the ADC pin as a floating point value.""" diff --git a/examples/ads1x15_comparator_example.py b/examples/ads1x15_comparator_example.py index 23db284..52dc1f4 100644 --- a/examples/ads1x15_comparator_example.py +++ b/examples/ads1x15_comparator_example.py @@ -44,8 +44,6 @@ # Set comparator high threshold to 2.002V. High threshold must be above low threshold ads.comparator_high_threshold = chan.convert_to_value(2.002) -chan.write_config() - count = 0 while True: print(chan.value, chan.voltage) # This initiates new ADC reading