Interface ESP32 with ADS1115 ADC – Arduino/MicroPython Code

This tutorial explains how to interface ESP32 with the ADS1115 ADC module. ESP32’s built-in ADC is great for basic applications. However, due to its non-linearity and noisy output, it falls short when you require higher precision. The ADS1115 will enable more precise and stable measurements when used together with ESP32.

The code here is explained in two ways:

  • Using Arduino IDE and C/C++ code
  • Using MicroPython & Thonny IDE.

Why is External ADC Required with ESP32?

ESP32 has two on-chip ADCs (Analog-to-Digital Converter):

  • ADC1: 8 channels
  • ADC2: 10 channels

These channels can measure analog voltages with a 12-bit resolution. However, all these channels may not be available on an ESP32 development board. ADC2 is also used by the Wi-Fi driver, so we can only use the channels of ADC2 when Wi-Fi is inactive.

There are many limitations in the inbuilt ADCs of ESP32:

1. The ADC reference voltage of ESP32 is designed to be around 1100mV. However the reference voltage (Vref) can range from 1000 mV to 1200 mV amongst different ESP32 boards. Here is an image describing how variations in Vref can show significant variations in the ADC readings:

Source: Espressif

Although there are many ways to calibrate the internal ADC, it adds complexity to your projects.

2. The range of voltages that can be measured by ESP32’s ADC is 0 to 3.3V. However, due to internal characteristics, the effective usable range is typically 0.1V to ~3.1V as shown in the graph below.

Source: esp32.com

The graph illustrates that the ADC struggles to distinguish between voltages at its lower range, such as 0V and 0.1V. A similar limitation is observed at the upper end, where it may register identical values for 3.2V and 3.3V

3. The ADC readings should be ideally linear. However, it is evident from the chart above that the results are non-linear across the range.

4. ADC resolution of 12-bits may not be enough for applications requiring high precision and accuracy.

Overview of ADS1115 ADC

The ADS1115 is a 16-bit ADC IC manufactured by Texas Instruments. It divides the input signal range into 65,536 ( 2^16) discrete steps. It uses its own internal low-drift voltage reference which helps in noise rejection.

The IC can easily interface with microcontrollers/microprocessors by using the I2C protocol to send and receive data and commands. I2C devices require just two wires for data and two more wires for power delivery. The ADC supports four single-ended and two differential inputs. The differential inputs help to minimize noise in analog readings. The ADC supports a wide range of supply voltage and consumes very little current.

The ADS1115 IC is commonly available as breakout board modules. Some examples are shown below:

ADS1115 ADC modules from different manufacturers

Features:

  • Range of ADC inputs: ±256 mV to ±6.144 V
  • Supply voltage range: 2.0 V to 5.5 V
  • Current consumption: 150 μA at Continuous-Conversion Mode
  • Programmable Data Rate: 8 samples per second (SPS) to 860 samples per second
  • Inbuilt programmable gain amplifier (PGA) and a digital comparator
  • 4 multiplexed inputs
  • Operates in either single-shot mode or continuous conversion mode

Block Diagram

Block Diagram of ADS1115 IC. Image credits: Texas instruments

The functional block diagram of ADS1115 illustrates how different parts of the IC work together to provide 16-bit digital data. ADS1115 consists of:

  • A delta-sigma ADC
  • A programmable gain amplifier (PGA)
  • An internal voltage reference
  • An internal oscillator for synchronization
  • An I2C interface for communication
  • An inbuilt comparator which can be used for over-voltage or under-voltage detection.

The inputs of ADS1115 are multiplexed through an input multiplexer (MUX) before being fed to the PGA. The device can measure four single-ended inputs or two differential inputs. While measuring single-ended inputs, the negative input of the ADC is internally connected to GND by a switch within the multiplexer.

The pairs of differential inputs are:

  • AIN0 and AIN1
  • AIN0 and AIN3
  • AIN1 and AIN3
  • AIN2 and AIN3

The code section will help you further understand how these blocks work together to gather ADC data.

Pinout of ADS1115 Module

The diagram below shows the pinout of a generic ADS1115 breakout board module:

Pinout description:

PINDESCRIPTION
VDDPositive of power supply (2.0 Volts to 5.5 Volts)
GNDGround of power supply
SCLSerial Clock (I2C)
SDASerial Data (I2C)
ADDRConfigurable Address (I2C)
ALRTComparator output or conversion ready
A0Analog input 0
A1Analog input 1
A2Analog input 2
A3Analog input 3

For more information on ADS1115, you can refer to its datasheet.

The maximum voltage to the analog input pins should be limited to VDD ± 0.3 Volts

Components

Components used in this tutorial:

  • An ESP32 development board
  • ADS1115 ADC module
  • A potentiometer
  • Breadboard and connecting wires

Wiring ESP32 with ADS1115 ADC

The schematic below shows ESP32 connected to an ADS1115 ADC module and a 10-kiloohms potentiometer. You can also use other potentiometers with resistance in the kiloohms range. The wiring is done for the single-ended mode of ADS1115.

Wiring Explained:

  • Connect the center pin of the potentiometer to the A0 pin of ADS1115
  • Connect the 3V3 pin (3.3 Volt) of ESP32 to the VDD pin of ADS1115
  • Connect any Ground pin on ESP32 to the Ground pin of ADS1115.
  • Connect ESP32’s GPIO 21 to the SDA pin of ADS1115
  • Connect ESP32’s GPIO 22 to the SCL pin of ADS1115
  • Connect another wire from the 3V3 pin and the Ground pin to the outermost terminals of the potentiometer

ⓘ The default I2C pins on ESP32 are GPIO 21(SDA) and GPIO 22 (SCL) which I have used here. You can use other pins by making changes in the code.

I2C Address of ADS1115

I2C devices have unique addresses. A master device can use the same I2C bus to communicate with multiple slave devices having different addresses.

Source: analog.com

The ADS1115 can be configured to have a different I2C address by changing the connections to the ADDR pin. The default I2C address of ADS1115 is 0x48 in hexadecimal.

The following table illustrates how the I2C address of ADS1115 changes depending on the ADDR pin connection:

I2C Address (in hexadecimal)ADDR Pin Connected to
0x49VDD
0x4ASDA
0x48GND
0x4BSCL

Thus, we can connect four ADS1115 modules to the same I2C bus by setting a different address for each module.

Scan I2C Address

Before interfacing any I2C module with a microcontroller, checking the device’s I2C address is recommended. Let us see how a few lines of Arduino code will display the I2C address of devices connected to the I2C bus.

After you have wired the ADS1115 ADC to ESP32 as described above, upload the following code to ESP32 using Arduino IDE:

#include <Wire.h>

void setup() {
  Serial.begin(9600);  // Initialize serial communication with 9600 baud rate
  while (!Serial);       // Wait for the serial port to connect (for native USB)
  Serial.println("\nI2C Scanner");
  Wire.begin();          // Initialize I2C communication
}

void loop() {
  byte error, address;
  int devicesFound = 0;

  Serial.println("Scanning for I2C devices...");

  for (address = 1; address < 127; address++) {
    Wire.beginTransmission(address);
    error = Wire.endTransmission();

    if (error == 0) {
      Serial.print("I2C device found at address 0x");
      if (address < 16) Serial.print("0");
      Serial.print(address, HEX);
      Serial.println(" !");
      devicesFound++;
    } else if (error == 4) {
      Serial.print("Unknown error at address 0x");
      if (address < 16) Serial.print("0");
      Serial.println(address, HEX);
    }
  }

  if (devicesFound == 0) {
    Serial.println("No I2C devices found\n");
  } else {
    Serial.println("Scan complete\n");
  }

  delay(5000); // Wait 5 seconds before next scan
}
Code language: PHP (php)

Open the Serial Monitor in Arduino IDE and set the baud rate to 9600. The I2C address of the ADS1115 ADC will be displayed as demonstrated in the screenshot below.

This is the default address and it will be used later in our main code.

Interface ESP32 with ADS1115 using Arduino IDE

Install Arduino Library for ADS1115

We shall use the ADS1115_WE library to read the ADC using Arduino code.

To install the library in Arduino IDE, go to Library Manager (Shortcut: Ctrl+Shift+I) and search “ADS1115_WE”. Click on Install and wait for the installation to complete.

Arduino Code for ADS1115

After installing the library, upload the following Arduino code to read the ADS1115 external ADC.

#include<ADS1115_WE.h> 
#include<Wire.h>

#define I2C_ADDRESS 0x48

ADS1115_WE adc = ADS1115_WE(I2C_ADDRESS);

void setup() {  
  Wire.begin();
  Serial.begin(9600);
  if(!adc.init()){
    Serial.println("ADS1115 not connected!");
  }
  adc.setVoltageRange_mV(ADS1115_RANGE_6144);  
}

void loop() {
  float voltage = 0.0;
  int rawResult = adc.getRawResult();
  Serial.print("Raw Result: ");
  Serial.print(rawResult);
  voltage = readChannel(ADS1115_COMP_0_GND);
  Serial.print("  Voltage [V]: ");
  Serial.println(voltage);
  delay(1000);
}

float readChannel(ADS1115_MUX channel) {
  float voltage = 0.0;
  adc.setCompareChannels(channel);
  adc.startSingleMeasurement();
  while(adc.isBusy()){delay(0);}
  voltage = adc.getResult_V(); // alternative: getResult_mV for Millivolt
  return voltage;
}Code language: PHP (php)

Code Explanation

First, we include the necessary libraries. The ADS115_WE.h library provides functions for interacting with the ADS1115 ADC. The Wire.h library helps in I2C communication.

#include<ADS1115_WE.h> 
#include<Wire.h>Code language: CSS (css)

Next, we specify the I2C address of the ADS1115 ADC which was found to be 0x48 upon running the I2C address scan code earlier.

#define I2C_ADDRESS 0x48Code language: CSS (css)

Create an ADS1115_WE object named adc and initialize it with the I2C address.

ADS1115_WE adc = ADS1115_WE(I2C_ADDRESS);

The setup() function runs once on startup. The Wire.begin() function initializes the Wire library.

Wire.begin();Code language: CSS (css)

Initialize the serial communication at a baud rate of 9600 bps so we can use the Serial Monitor in Arduino IDE to view data.

Serial.begin(9600);Code language: CSS (css)

We then try to initialize the ADC using adc.init() and if it fails, we print a message to the Serial Monitor.

if(!adc.init()){
  Serial.println("ADS1115 not connected!");
}Code language: JavaScript (javascript)

The ADS1115 comes with a built-in programmable gain amplifier (PGA). This PGA can be configured to change the full-scale range of ADS1115 from ±0.256V to ±6.144V. The setVoltageRange_mV() sets the ADC’s input voltage range (full-scale range) to ±6.144V. Selecting other ranges, such as ±4.096V, ±2.048V, etc., will provide higher sensitivity. So higher the input range, the less the sensitivity.

adc.setVoltageRange_mV(ADS1115_RANGE_6144);  Code language: CSS (css)

Before discussing the main loop, let’s first have a look at the readChannel(ADS1115_MUX channel) function, whose purpose is to read the voltage from a specified ADC channel. ADS1115_MUX specifies the expected data type of the channel parameter. It has predefined values like ADS1115_COMP_0_GND or ADS1115_COMP_1_GND (representing the different channels of the ADS1115). The parameter channel is used to specify the ADS1115 channel configuration for measurement.

Here we configure the ADC to measure a specified channel and start a single ADC conversion.

adc.setCompareChannels(channel);
adc.startSingleMeasurement();Code language: CSS (css)

Wait until the ADC finishes the conversion.

while(adc.isBusy()){delay(0);}Code language: JavaScript (javascript)

Get the converted voltage value in volts and return this value.

voltage = adc.getResult_V();
return voltage;Code language: JavaScript (javascript)

The main loop() function runs indefinitely after the setup() function executes. Here we create a variable to store the voltage read by the ADC and initialize it to zero.

<code>float voltage = 0.0</code>Code language: HTML, XML (xml)

The raw ADC result is stored in a new variable and its value is printed to the Serial Monitor.

int rawResult = adc.getRawResult();
Serial.print("Raw Result: ");
Serial.print(rawResult);Code language: PHP (php)

Call the readChannel() function to get the voltage from channel 0 relative to the ground.

voltage = readChannel(ADS1115_COMP_0_GND);

Finally, we print the voltage read by the ADC to the serial monitor and set a delay of 1 second before the next ADC reading.

Serial.print("  Voltage [V]: ");
Serial.println(voltage);
delay(1000);Code language: CSS (css)

Demonstration

When the sketch successfully uploads, the Serial Monitor in Arduino IDE should show the raw result and voltage read by the ADS1115 ADC as shown in the screenshot below.

The image below shows the breadboard layout of the circuit described earlier.

You can verify if the voltage output shown in the Serial Monitor is correct by measuring it with a voltmeter(or a multimeter). Measure the DC voltage between the center and ground positions of the potentiometer. The voltage in the multimeter should be the same as the voltage shown in the Serial Monitor when you run your Arduino sketch.

ESP32 with ADS1115 Using MicroPython

Instead of C/C++ code, you can also program ESP32 using MicroPython. MicroPython simplifies coding with built-in libraries and dynamic typing, reducing development time.

To program ESP32 using MicroPython, you can refer to the following guides:

Also read: Arduino vs MicroPython vs CircuitPython: Which One Will You Choose?

Install MicroPython Library for ADS1115

To send commands and read data from the ADS1115 IC, we shall use a MicroPython library that will provide easy-to-use functions.

Copy and paste the following code to your IDE. The code can also be found on this GitHub page.

# The MIT License (MIT)
#
# Copyright (c) 2016 Radomir Dopieralski (@deshipu),
#               2017 Robert Hammelrath (@robert-hh)
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#
import utime as time

_REGISTER_MASK = const(0x03)
_REGISTER_CONVERT = const(0x00)
_REGISTER_CONFIG = const(0x01)
_REGISTER_LOWTHRESH = const(0x02)
_REGISTER_HITHRESH = const(0x03)

_OS_MASK = const(0x8000)
_OS_SINGLE = const(0x8000)  # Write: Set to start a single-conversion
_OS_BUSY = const(0x0000)  # Read: Bit=0 when conversion is in progress
_OS_NOTBUSY = const(0x8000)  # Read: Bit=1 when no conversion is in progress

_MUX_MASK = const(0x7000)
_MUX_DIFF_0_1 = const(0x0000)  # Differential P  =  AIN0, N  =  AIN1 (default)
_MUX_DIFF_0_3 = const(0x1000)  # Differential P  =  AIN0, N  =  AIN3
_MUX_DIFF_1_3 = const(0x2000)  # Differential P  =  AIN1, N  =  AIN3
_MUX_DIFF_2_3 = const(0x3000)  # Differential P  =  AIN2, N  =  AIN3
_MUX_SINGLE_0 = const(0x4000)  # Single-ended AIN0
_MUX_SINGLE_1 = const(0x5000)  # Single-ended AIN1
_MUX_SINGLE_2 = const(0x6000)  # Single-ended AIN2
_MUX_SINGLE_3 = const(0x7000)  # Single-ended AIN3

_PGA_MASK = const(0x0E00)
_PGA_6_144V = const(0x0000)  # +/-6.144V range  =  Gain 2/3
_PGA_4_096V = const(0x0200)  # +/-4.096V range  =  Gain 1
_PGA_2_048V = const(0x0400)  # +/-2.048V range  =  Gain 2 (default)
_PGA_1_024V = const(0x0600)  # +/-1.024V range  =  Gain 4
_PGA_0_512V = const(0x0800)  # +/-0.512V range  =  Gain 8
_PGA_0_256V = const(0x0A00)  # +/-0.256V range  =  Gain 16

_MODE_MASK = const(0x0100)
_MODE_CONTIN = const(0x0000)  # Continuous conversion mode
_MODE_SINGLE = const(0x0100)  # Power-down single-shot mode (default)

_DR_MASK = const(0x00E0)     # Values ADS1015/ADS1115
_DR_128SPS = const(0x0000)   # 128 /8 samples per second
_DR_250SPS = const(0x0020)   # 250 /16 samples per second
_DR_490SPS = const(0x0040)   # 490 /32 samples per second
_DR_920SPS = const(0x0060)   # 920 /64 samples per second
_DR_1600SPS = const(0x0080)  # 1600/128 samples per second (default)
_DR_2400SPS = const(0x00A0)  # 2400/250 samples per second
_DR_3300SPS = const(0x00C0)  # 3300/475 samples per second
_DR_860SPS = const(0x00E0)  # -   /860 samples per Second

_CMODE_MASK = const(0x0010)
_CMODE_TRAD = const(0x0000)  # Traditional comparator with hysteresis (default)
_CMODE_WINDOW = const(0x0010)  # Window comparator

_CPOL_MASK = const(0x0008)
_CPOL_ACTVLOW = const(0x0000)  # ALERT/RDY pin is low when active (default)
_CPOL_ACTVHI = const(0x0008)  # ALERT/RDY pin is high when active

_CLAT_MASK = const(0x0004)  # Determines if ALERT/RDY pin latches once asserted
_CLAT_NONLAT = const(0x0000)  # Non-latching comparator (default)
_CLAT_LATCH = const(0x0004)  # Latching comparator

_CQUE_MASK = const(0x0003)
_CQUE_1CONV = const(0x0000)  # Assert ALERT/RDY after one conversions
_CQUE_2CONV = const(0x0001)  # Assert ALERT/RDY after two conversions
_CQUE_4CONV = const(0x0002)  # Assert ALERT/RDY after four conversions
# Disable the comparator and put ALERT/RDY in high state (default)
_CQUE_NONE = const(0x0003)

_GAINS = (
    _PGA_6_144V,  # 2/3x
    _PGA_4_096V,  # 1x
    _PGA_2_048V,  # 2x
    _PGA_1_024V,  # 4x
    _PGA_0_512V,  # 8x
    _PGA_0_256V   # 16x
)

_GAINS_V = (
    6.144,  # 2/3x
    4.096,  # 1x
    2.048,  # 2x
    1.024,  # 4x
    0.512,  # 8x
    0.256  # 16x
)

_CHANNELS = {
    (0, None): _MUX_SINGLE_0,
    (1, None): _MUX_SINGLE_1,
    (2, None): _MUX_SINGLE_2,
    (3, None): _MUX_SINGLE_3,
    (0, 1): _MUX_DIFF_0_1,
    (0, 3): _MUX_DIFF_0_3,
    (1, 3): _MUX_DIFF_1_3,
    (2, 3): _MUX_DIFF_2_3,
}

_RATES = (
    _DR_128SPS,   # 128/8 samples per second
    _DR_250SPS,   # 250/16 samples per second
    _DR_490SPS,   # 490/32 samples per second
    _DR_920SPS,   # 920/64 samples per second
    _DR_1600SPS,  # 1600/128 samples per second (default)
    _DR_2400SPS,  # 2400/250 samples per second
    _DR_3300SPS,  # 3300/475 samples per second
    _DR_860SPS    # - /860 samples per Second
)


class ADS1115:
    def __init__(self, i2c, address=0x48, gain=1):
        self.i2c = i2c
        self.address = address
        self.gain = gain
        self.temp2 = bytearray(2)

    def _write_register(self, register, value):
        self.temp2[0] = value >> 8
        self.temp2[1] = value & 0xff
        self.i2c.writeto_mem(self.address, register, self.temp2)

    def _read_register(self, register):
        self.i2c.readfrom_mem_into(self.address, register, self.temp2)
        return (self.temp2[0] << 8) | self.temp2[1]

    def raw_to_v(self, raw):
        v_p_b = _GAINS_V[self.gain] / 32768
        return raw * v_p_b

    def set_conv(self, rate=4, channel1=0, channel2=None):
        """Set mode for read_rev"""
        self.mode = (_CQUE_NONE | _CLAT_NONLAT |
                     _CPOL_ACTVLOW | _CMODE_TRAD | _RATES[rate] |
                     _MODE_SINGLE | _OS_SINGLE | _GAINS[self.gain] |
                     _CHANNELS[(channel1, channel2)])

    def read(self, rate=4, channel1=0, channel2=None):
        """Read voltage between a channel and GND.
           Time depends on conversion rate."""
        self._write_register(_REGISTER_CONFIG, (_CQUE_NONE | _CLAT_NONLAT |
                             _CPOL_ACTVLOW | _CMODE_TRAD | _RATES[rate] |
                             _MODE_SINGLE | _OS_SINGLE | _GAINS[self.gain] |
                             _CHANNELS[(channel1, channel2)]))
        while not self._read_register(_REGISTER_CONFIG) & _OS_NOTBUSY:
            time.sleep_ms(1)
        res = self._read_register(_REGISTER_CONVERT)
        return res if res < 32768 else res - 65536

    def read_rev(self):
        """Read voltage between a channel and GND. and then start
           the next conversion."""
        res = self._read_register(_REGISTER_CONVERT)
        self._write_register(_REGISTER_CONFIG, self.mode)
        return res if res < 32768 else res - 65536

    def alert_start(self, rate=4, channel1=0, channel2=None,
                    threshold_high=0x4000, threshold_low=0, latched=False) :
        """Start continuous measurement, set ALERT pin on threshold."""
        self._write_register(_REGISTER_LOWTHRESH, threshold_low)
        self._write_register(_REGISTER_HITHRESH, threshold_high)
        self._write_register(_REGISTER_CONFIG, _CQUE_1CONV |
                             _CLAT_LATCH if latched else _CLAT_NONLAT |
                             _CPOL_ACTVLOW | _CMODE_TRAD | _RATES[rate] |
                             _MODE_CONTIN | _GAINS[self.gain] |
                             _CHANNELS[(channel1, channel2)])

    def conversion_start(self, rate=4, channel1=0, channel2=None):
        """Start continuous measurement, trigger on ALERT/RDY pin."""
        self._write_register(_REGISTER_LOWTHRESH, 0)
        self._write_register(_REGISTER_HITHRESH, 0x8000)
        self._write_register(_REGISTER_CONFIG, _CQUE_1CONV | _CLAT_NONLAT |
                             _CPOL_ACTVLOW | _CMODE_TRAD | _RATES[rate] |
                             _MODE_CONTIN | _GAINS[self.gain] |
                             _CHANNELS[(channel1, channel2)])

    def alert_read(self):
        """Get the last reading from the continuous measurement."""
        res = self._read_register(_REGISTER_CONVERT)
        return res if res < 32768 else res - 65536


class ADS1113(ADS1115):
    def __init__(self, i2c, address=0x48):
        super().__init__(i2c, address, 1)

    def raw_to_v(self, raw):
        return super().raw_to_v(raw)

    def read(self, rate=4):
        return super().read(rate, 0, 1)

    def alert_start(self, rate=4, threshold_high=0x4000, threshold_low=0, latched=False):
        return super().alert_start(rate, 0, 1, threshold_high, threshold_low, latched)

    def alert_read(self):
        return super().alert_read()


class ADS1114(ADS1115):
    def __init__(self, i2c, address=0x48, gain=1):
        super().__init__(i2c, address, gain)

    def raw_to_v(self, raw):
        return super().raw_to_v(raw)

    def read(self, rate=4):
        return super().read(rate, 0, 1)

    def alert_start(self, rate=4, threshold_high=0x4000, threshold_low=0, latched=False):
        return super().alert_start(rate, 0, 1, threshold_high,
            threshold_low, latched)

    def alert_read(self):
        return super().alert_read()


class ADS1015(ADS1115):
    def __init__(self, i2c, address=0x48, gain=1):
        super().__init__(i2c, address, gain)

    def raw_to_v(self, raw):
        return super().raw_to_v(raw << 4)

    def read(self, rate=4, channel1=0, channel2=None):
        return super().read(rate, channel1, channel2) >> 4

    def alert_start(self, rate=4, channel1=0, channel2=None, threshold_high=0x400,
        threshold_low=0, latched=False):
        return super().alert_start(rate, channel1, channel2, threshold_high << 4,
            threshold_low << 4, latched)

    def alert_read(self):
        return super().alert_read() >> 4

Save the library code as ADS1x15.py to your ESP32.

MicroPython Code for ADS1115

With the library uploaded to ESP32 and wiring done according to the schematic, you can now upload code to read ADC values from ADS1115. The following MicroPython code will display the voltage at the A0 input of ADS1115.

from machine import I2C, Pin
import time
from ads1x15 import ADS1115
i2c=I2C(0, sda=Pin(21), scl=Pin(22))
adc = ADS1115(i2c, address=72, gain=0)

while True:
    value = adc.read(4, 0)
    print("ADC Value: ", value)
    voltage = adc.raw_to_v(value)
    print("Voltage= ", voltage)
    print(" ")
    time.sleep(1)Code language: JavaScript (javascript)

Save this code as main.py.

Code Explanation

First, we import the necessary MicroPython modules. The machine module provides access to the hardware pins, time module is used for delays, and the ads1x15 module contains the driver for the ADS1115 ADC.

from machine import I2C, Pin
import time
from ads1x15 import ADS1115Code language: JavaScript (javascript)

Next, we initialize the I2C interface. The I2C bus is configured using pins 16 (SDA) and 17 (SCL) on the microcontroller. These pins are used for communication with the ADS1115.

i2c=I2C(0, sda=Pin(21), scl=Pin(22))

An ADS1115 object is initialized on the I2C bus. The I2C address is set to 72. The I2C address we got using the address scanner script (explained earlier) is 48 in hexadecimal which converts to 72 in the decimal system.

adc = ADS1115(i2c, address=72, gain=0)

The gain parameter configures the full range of the ADC as follows:

GainFull Scale Range (Voltage)
0±6.144 V
1± 4.096 V
2±2.048
3±1.024
4±0.512
5±0.256

Main Loop

Inside the main loop, the code continuously reads the ADC values from ADS1115 and prints the value and corresponding voltage.

The line below reads the raw ADC value at 128 samples from the first ADC channel.

value = adc.read(4, 0)

For example, in the line of code: value = adc.read([rate, [channel1[, channel2]]]), channel1 is the single input channel. If channel2 is supplied, the difference between channel1 and channel2 is taken. The channels range from 0 to 3 corresponding to the three analog inputs in ADS1115. The rate is the conversion rate which can be one of the following:

Value in codeRate (in samples per second)
08
116
232
364
4128
5250
6475
7860

As we used the value ‘4’ in our code, the rate of conversion is 128 samples per second. As we increase the conversion rate, the noise in results also increases.

Next, we print the raw ADC values

print("ADC Value:", value)Code language: PHP (php)

The raw ADC result is converted to a voltage based on the gain we set earlier. A float value of the voltage is returned.

voltage = adc.raw_to_v(value)

Finally, we print the voltage read by the ADC and introduce a delay of 500 ms before the next reading.

print("Voltage", voltage)
time.sleep(0.5)Code language: CSS (css)

Demonstration

When you run the code, the following output should be displayed on the shell of the IDE:

The ADC values in the output terminal must change as you rotate the potentiometer.

Conclusion

You can check out the code examples in GitHub to try the various features of the ADS1115 ADC. There are examples to check whether you have an ADS1115 or ADS1015 module, operation in continuous conversion mode, using the auto range function, using the ALERT pin to trigger interrupts, using two ADS1115 on a single I2C bus, and many more. Examples can also be found in Arduino IDE by navigating to File>Examples>ADS1115_WE.

The ADS1115, with its 16-bit resolution and programmable gain, ensures more reliable and precise measurements, making it ideal for sensor-based projects, data logging, and industrial applications. Now that you know how to set it up with ESP32, go ahead and integrate it into your next project for better analog data accuracy!

Related Article:


Posted

in

by

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *