This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.

ADS1115 SPI Timing

Part Number: ADS1115

Hi there, I'm using an ADS1115 analogue-digital converter, with 5V supply voltage, with gain set to +-6.144V (I'm reading values between 0.5 and 5 volts, in single-ended mode on three channels of my four).

I seem to be having an issue where when I read one channel, it returns the value associated with the conversion for the previous channel.

I have tried to add a delay in the Adafruit ADS1x15 library between writing the configuration and reading from the conversion register.

However, this does not seem to have resolved my issue. I will post the library code I have below. Does anyone mind taking a quick look and suggesting where I might want to try adding a delay or implementing another change that may help?

Thank you so much.

CircuitPython base class driver for ADS1115

from micropython import const
from adafruit_bus_device.i2c_device import I2CDevice
import time

_ADS1X15_DEFAULT_ADDRESS = const(0x48)
_ADS1X15_POINTER_CONVERSION = const(0x00)
_ADS1X15_POINTER_CONFIG = const(0x01)
_ADS1X15_CONFIG_OS_SINGLE = const(0x8000)
_ADS1X15_CONFIG_MUX_OFFSET = const(12)
_ADS1X15_CONFIG_COMP_QUE_DISABLE = const(0x0003)
_ADS1X15_CONFIG_GAIN = {
2/3: 0x0000,
1: 0x0200,
2: 0x0400,
4: 0x0600,
8: 0x0800,
16: 0x0A00
}

from micropython import const
from adafruit_bus_device.i2c_device import I2CDevice
import time

_ADS1X15_DEFAULT_ADDRESS            = const(0x48)
_ADS1X15_POINTER_CONVERSION         = const(0x00)
_ADS1X15_POINTER_CONFIG             = const(0x01)
_ADS1X15_CONFIG_OS_SINGLE           = const(0x8000)
_ADS1X15_CONFIG_MUX_OFFSET          = const(12)
_ADS1X15_CONFIG_COMP_QUE_DISABLE    = const(0x0003)
_ADS1X15_CONFIG_GAIN = {
    2/3: 0x0000,
    1:   0x0200,
    2:   0x0400,
    4:   0x0600,
    8:   0x0800,
    16:  0x0A00
}

class Mode:
    # values here are masks for setting MODE bit in Config Register
    CONTINUOUS = 0x0000
    SINGLE = 0x0100


class ADS1x15(object):
    """Base functionality for ADS1x15 analog to digital converters."""

    def __init__(self, i2c, gain=1, data_rate=None, mode=Mode.SINGLE,
                 address=_ADS1X15_DEFAULT_ADDRESS):
        #pylint: disable=too-many-arguments
        self._last_pin_read = None
        self.buf = bytearray(3)
        self._data_rate = self._gain = self._mode = None
        self.gain = gain
        self.data_rate = self._data_rate_default() if data_rate is None else data_rate
        self.mode = mode
        self.i2c_device = I2CDevice(i2c, address)

    @property
    def data_rate(self):
        """The data rate for ADC conversion in samples per second."""
        return self._data_rate

    @data_rate.setter
    def data_rate(self, rate):
        possible_rates = self.rates
        if rate not in possible_rates:
            raise ValueError("Data rate must be one of: {}".format(possible_rates))
        self._data_rate = rate

    @property
    def rates(self):
        """Possible data rate settings."""
        raise NotImplementedError('Subclass must implement rates property.')

    @property
    def rate_config(self):
        """Rate configuration masks."""
        raise NotImplementedError('Subclass must implement rate_config property.')

    @property
    def gain(self):
        """The ADC gain."""
        return self._gain

    @gain.setter
    def gain(self, gain):
        possible_gains = self.gains
        if gain not in possible_gains:
            raise ValueError("Gain must be one of: {}".format(possible_gains))
        self._gain = gain

    @property
    def gains(self):
        """Possible gain settings."""
        g = list(_ADS1X15_CONFIG_GAIN.keys())
        g.sort()
        return g

    @property
    def mode(self):
        """The ADC conversion mode."""
        return self._mode

    @mode.setter
    def mode(self, mode):
        if mode != Mode.CONTINUOUS and mode != Mode.SINGLE:
            raise ValueError("Unsupported mode.")
        self._mode = mode

    def read(self, pin, is_differential=False):
        pin = pin if is_differential else pin + 0x04
        return self._read(pin)

    def _data_rate_default(self):
        raise NotImplementedError('Subclasses must implement _data_rate_default!')

    def _conversion_value(self, raw_adc):
        raise NotImplementedError('Subclass must implement _conversion_value function!')

    def _read(self, pin):
        """Perform an ADC read. Returns the signed integer result of the read."""
        data_rate = self.data_rate
        fast = True
        if self._last_pin_read != pin:
            self._last_pin_read = pin
            fast = False
            config = _ADS1X15_CONFIG_OS_SINGLE
            config |= (pin & 0x07) << _ADS1X15_CONFIG_MUX_OFFSET
            config |= _ADS1X15_CONFIG_GAIN[self.gain]
            config |= self.mode
            config |= self.rate_config[self.data_rate]
            config |= _ADS1X15_CONFIG_COMP_QUE_DISABLE
            #time.sleep(1.0/data_rate+0.0001)
            self._write_register(_ADS1X15_POINTER_CONFIG, config)
        #if self.mode == Mode.SINGLE:
        while not self._conversion_complete():
            pass
        return self._conversion_value(self.get_last_result(fast))

    def _conversion_complete(self):
        """Return status of ADC conversion."""
        # OS is bit 15
        # OS = 0: Device is currently performing a conversion
        # OS = 1: Device is not currently performing a conversion
        return self._read_register(_ADS1X15_POINTER_CONFIG) & 0x8000

    def get_last_result(self, fast=False):
        """Read the last conversion result when in continuous conversion mode.
        Will return a signed integer value. If fast is True, the register
        pointer is not updated as part of the read. This reduces I2C traffic
        and increases possible read rate.
        """
        return self._read_register(_ADS1X15_POINTER_CONVERSION, fast)

    def _write_register(self, reg, value):
        """Write 16 bit value to register."""
        self.buf[0] = reg
        self.buf[1] = (value >> 8) & 0xFF
        self.buf[2] = value & 0xFF
        with self.i2c_device as i2c:
            i2c.write(self.buf)

    def _read_register(self, reg, fast=False):
        """Read 16 bit register value. If fast is True, the pointer register
        is not updated.
        """
        self.buf[0] = reg
        with self.i2c_device as i2c:
            if fast:
                i2c.readinto(self.buf, end=2)
            else:
                i2c.write_then_readinto(bytearray([reg]), self.buf, in_end=2, stop=False)
        return self.buf[0] << 8 | self.buf[1]

  • Charlie,


    While I do know quite a bit about the ADS1115, I'm not much of a coder. Let me give you a couple of thoughts first and maybe it will help.

    If you're reading multiple channels, it's often best to use single-shot conversion mode. This way you start the conversion, wait for enough time to guarantee that the conversion completes, and then read the conversion. If you try to use continuous conversion mode, it's usually more difficult to get the correct data. If you change the ADS1115 channel, the device doesn't immediately change the channel. It will first complete the conversion in progress, and then change the channel. This means that if you send the channel change at the beginning of a conversion, you could be waiting for 2x(1/data rate) to get the data you want.

    Also, there is some tolerance in the internal oscillator, so the nominal data rate is ±10%. Also, you need to wait an extra 20us for the device to wake up. As an example, if you are running 128SPS. You would start the conversion, and then wait [1.1*(1/128)]+20us. This would be about 8.62ms wait to guarantee that the conversion has completed and you can read the data.

    For your code, you could just set the conversion for single-shot mode, and then insert the delay. Then you could use the get_last_result after that, without having to check conversion complete. I would also get an oscilloscope to check the I2C communication, and the timing between the start of the conversion and read.

    As for the Adafruit library, you could also check with them. I'm sure they've had similar questions about their code, and you can post in their customer support for that as well.


    Joseph Wu

  • Hi Joseph,
    Thanks for getting back to me so quickly.

    I will have another look at running in single-shot conversion mode.
    I've tried adding in delays in various places, with no luck.

    I will be sure to post on the Adafruit support forum as you suggest.

    If I'm unable to resolve my issue then I'll look into an oscilloscope.
    I'll let you know when it's resolved.

    Thanks!

  • Charlie,

    Have you been able to solve this problem with the ADS1115? As I mentioned in my previous post, I think you should be able to get good data by setting the device in single-shot conversion mode and timing the read of the data.

    I'll close this thread for now. If you've solved this, then I'm glad that it worked for you. However, if you haven't, post back and we can work on this again.

    Joseph Wu