crappy.inout.ads1115 — Crappy 1.5.3 documentation

# Source code for crappy.inout.ads1115

    # coding: utf-8

    from time import time, sleep
    from .inout import InOut
    from ..tool import ft232h_server as ft232h, Usb_server
    from .._global import OptionalModule

    try:
      import smbus2
    except (ModuleNotFoundError, ImportError):
      smbus2 = OptionalModule("smbus2")

    # Register and other configuration values:
    Ads1115_default_address = 0x48
    Ads1115_pointer_conversion = 0x00
    Ads1115_pointer_config = 0x01

    Ads1115_config_mux = {'A0 - A1': 0x0000,
                          'A0 - A3': 0x1000,
                          'A1 - A3': 0x2000,
                          'A2 - A3': 0x3000,
                          'A0': 0x4000,
                          'A1': 0x5000,
                          'A2': 0x6000,
                          'A3': 0x7000}

    Ads1115_config_gain = {0.256: 0x0a00,
                           0.512: 0x0800,
                           1.024: 0x0600,
                           2.048: 0x0400,
                           4.096: 0x0200,
                           6.144: 0x0000}

    Ads1115_config_dr = {8: 0x0000,
                         16: 0x0020,
                         32: 0x0040,
                         64: 0x0060,
                         128: 0x0080,
                         250: 0x00A0,
                         475: 0x00C0,
                         860: 0x00E0}

    Ads1115_backends = ['Pi4', 'ft232h']

    class Ads1115(Usb_server, InOut):
      """A class for controlling Adafruit's ADS1115 16-bits ADC.

      The Ads1115 InOut block is meant for reading output values from a 16-bits
      ADS1115 ADC, using the I2C protocol. The output is in Volts by default, but a
      ``gain`` and an ``offset`` can be specified.

      Warning:
        Only available on Raspberry Pi for now !
      """

      def __init__(self,
                   backend: str,
                   device_address: int = Ads1115_default_address,
                   i2c_port: int = 1,
                   sample_rate: int = 128,
                   v_range: float = 2.048,
                   multiplexer: str = 'A1',
                   gain: float = 1,
                   offset: float = 0,
                   ft232h_ser_num: str = None) -> None:
        """Checks arguments validity.

        Args:
          backend (:obj:`str`): The backend for communicating with the NAU7802.
            Should be one of:
            ::

              'Pi4', 'ft232h'

          device_address (:obj:`int`, optional): The I2C address of the ADS1115.
            The default address is `0x48`, but it is possible to change this
            setting using the `ADDR` pin.
          i2c_port (:obj:`int`, optional): The I2C port over which the ADS1115
            should communicate. On most Raspberry Pi models the default I2C port is
            `1`.
          sample_rate (:obj:`int`, optional): The sample rate for data conversion
            (in SPS). Available sample rates are:
            ::

              8, 16, 32, 64, 128, 250, 475, 860

          v_range (:obj:`float`, optional): The value (in Volts) of the measured
            signal corresponding to the `0x7FFF` output in bits. A signal of
            ``-v_range`` Volts gives a `0x8000` output in bits. Available
            ``v_range`` values are:
            ::

              0.256, 0.512, 1.024, 2.048, 4.096, 6.144

          multiplexer (:obj:`str`, optional): Choice of the inputs to consider.
            Single-input modes actually measure `Ax - GND`. The available
            ``multiplexer`` values are:
            ::

              'A0', 'A1', 'A2', 'A3',
              'A0 - A1',
              'A0 - A3',
              'A1 - A3',
              'A2 - A3'

          gain (:obj:`float`, optional): Allows to tune the output value according
            to the formula:
            ::

              output = gain * tension + offset.

          offset (:obj:`float`, optional): Allows to tune the output value
            according to the formula:
            ::

              output = gain * tension + offset.

          ft232h_ser_num (:obj:`str`, optional): If backend is `'ft232h'`, the
            serial number of the ft232h to use for communication.

        Warning:
          AINx voltages should not be higher than `VDD+0.3V` nor lower than
          `GND-0.3V`. Setting high ``v_range`` values does not allow to measure
          voltages higher than `VDD` !!
        """

        if backend not in Ads1115_backends:
          raise ValueError("backend should be in {}".format(Ads1115_backends))

        Usb_server.__init__(self,
                            serial_nr=ft232h_ser_num if ft232h_ser_num else '',
                            backend=backend)
        InOut.__init__(self)
        queue, block_number, namespace, command_event, \
            answer_event, next_event, done_event = super().start_server()

        if backend == 'Pi4':
          self._bus = smbus2.SMBus(i2c_port)
        else:
          self._bus = ft232h(mode='I2C',
                             queue=queue,
                             namespace=namespace,
                             command_event=command_event,
                             answer_event=answer_event,
                             block_number=block_number,
                             next_block=next_event,
                             done_event=done_event,
                             serial_nr=ft232h_ser_num)

        self._device_address = device_address

        if v_range not in Ads1115_config_gain:
          raise ValueError("v_range should be in {}".format(list(
            Ads1115_config_gain.keys())))
        else:
          self._v_range = v_range

        if sample_rate not in Ads1115_config_dr:
          raise ValueError("sample_rate should be in {}".format(list(
            Ads1115_config_dr.keys())))
        else:
          self._sample_rate = sample_rate

        if multiplexer not in Ads1115_config_mux:
          raise ValueError("multiplexer should be in {}".format(list(
            Ads1115_config_mux.keys())))
        else:
          self._multiplexer = multiplexer

        self._gain = gain
        self._offset = offset

      def open(self) -> None:
        """Sets the I2C communication and device."""

        if not self._is_connected():
          raise IOError("The ADS1115 is not connected")

        # Setting the configuration register according to the user values
        init_value = 0x0000
        init_value |= Ads1115_config_mux[self._multiplexer]
        init_value |= Ads1115_config_gain[self._v_range]
        init_value |= 0x0100  # Single shot operating mode
        init_value |= Ads1115_config_dr[self._sample_rate]
        init_value |= 0x0003  # set remaining parameters to default value

        self._set_register(Ads1115_pointer_config, init_value)

      def get_data(self) -> list:
        """Reads the registers containing the conversion result.

        The output is in Volts, unless a gain and offset are applied.

        Returns:
          :obj:`list`: A list containing the timeframe and the voltage value
        """

        # Reading the config register, and setting it so that the ADS1115 starts
        # a conversion
        ms_byte, ls_byte = self._bus.read_i2c_block_data(self._device_address,
                                                         Ads1115_pointer_config, 2)
        ms_byte |= (1 << 7)
        self._set_register(Ads1115_pointer_config, (ms_byte << 8) | ls_byte)

        # Reading the output of the conversion
        sleep(1 / self._sample_rate + 0.00005)
        out = [time()]
        ms_byte, ls_byte = self._bus.read_i2c_block_data(self._device_address,
                                                         Ads1115_pointer_conversion,
                                                         2)

        # Converting the output value into Volts
        if ms_byte >> 7:
          value = (-(2 ** 15 - 1) + (((ms_byte << 8) | ls_byte) & 0x7fff)) \
                  * self._v_range / (2 ** 15 - 1)
        else:
          value = ((ms_byte << 8) | ls_byte) * self._v_range / (2 ** 15 - 1)
        out.append(self._offset + self._gain * value)
        return out

      def close(self) -> None:
        """Closes the I2C bus"""

        self._bus.close()

      def _set_register(self, register_address: int, value: int) -> None:
        """Thin wrapper for writing data to the registers."""

        ms_byte = value >> 8 & 0xff
        ls_byte = value & 0xff
        self._bus.write_i2c_block_data(self._device_address, register_address,
                                       [ms_byte, ls_byte])

      def _is_connected(self) -> bool:
        """Tries reading a byte from the device.

        Returns:
          :obj:`bool`: :obj:`True` if reading was successful, else :obj:`False`
        """

        try:
          self._bus.read_byte(self._device_address)
          return True
        except IOError:
          return False