esp32 intermediate 30 min

ESP32: add precision analog inputs with the ADS1115

The ESP32 ADC is noisy and 12-bit. The ADS1115 gives you 16-bit, 4-channel, differential readings over I2C for $4.

Code available for: ESP32 ArduinoArduino C
Published Sep 22, 2026

The ESP32’s built-in ADC is the weakest part of the chip. It is 12-bit, nonlinear at both ends of the range, and noisy enough that consecutive readings of the same voltage can differ by 50 counts. For “is the soil dry” that is fine. For a load cell, a thermocouple, or any sensor where you care about small voltage changes, it is not.

The ADS1115 is the fix. It is a 16-bit, 4-channel ADC that talks I2C for about $4. It has a programmable gain amplifier, true differential inputs, and it is stable enough to read a thermocouple through an amplifier without the reading wandering.

What you need

  • ESP32 dev board
  • ADS1115 breakout (the Adafruit one has a nice terminal block; the GY-ADS1115 clone works identically, about $4)
  • A sensor to test with (a potentiometer is fine for proving it works)
  • Jumper wires

Wiring (I2C)

Wire key: 3.3VGNDSCLGPIOSDAA-pin
ADS1115ESP32
VDD3.3V
GNDGND
SCLGPIO 22
SDAGPIO 21
ADDRGND (address 0x48)
A0-A3Your analog signals

The ADDR pin picks one of four I2C addresses (0x48, 0x49, 0x4A, 0x4B for GND, VDD, SDA, SCL respectively). That means up to four ADS1115s on one bus, 16 analog inputs total.

The ADS1115 runs on 3.3V here, so its full-scale input tracks the supply voltage. Do not feed it 5V signals unless you power it from 5V and use a level shifter on I2C. Staying at 3.3V keeps everything safe for the ESP32.

Install

Arduino IDE >> Sketch >> Include Library >> Manage Libraries, search “Adafruit ADS1X15”, install the Adafruit one.

The code

#include <Wire.h>
#include <Adafruit_ADS1X15.h>

Adafruit_ADS1115 ads;

void setup() {
  Serial.begin(115200);
  Wire.begin(21, 22);

  if (!ads.begin(0x48)) {
    Serial.println("ADS1115 not found, check wiring");
    while (1) delay(1000);
  }

  // Gain sets the full-scale range:
  //   GAIN_TWOTHIRDS -> +/-6.144V (default)
  //   GAIN_ONE       -> +/-4.096V
  //   GAIN_FOUR      -> +/-1.024V
  //   GAIN_SIXTEEN   -> +/-0.256V
  ads.setGain(GAIN_ONE);
}

void loop() {
  int16_t raw = ads.readADC_SingleEnded(0);
  float volts = ads.computeVolts(raw);

  Serial.print("Raw: ");
  Serial.print(raw);
  Serial.print("  Volts: ");
  Serial.println(volts, 4);
  delay(500);
}

Upload, open Serial Monitor. Turn the potentiometer and watch four decimal places move. Compare that to the ESP32’s own ADC and you will see the difference immediately: the ADS1115 holds steady, the internal ADC wanders.

Differential mode, the real reason to buy this

The single-ended read above is the basic use. The feature that earns the $4 is differential mode, where the chip reads the voltage BETWEEN two pins instead of between a pin and ground:

int16_t diff = ads.readADC_Differential_0_1();
float volts = ads.computeVolts(diff);

This is how load cells and thermocouples work: the sensor outputs a tiny voltage difference between two wires (e.g. a load cell might output 10 mV at full weight), and common-mode noise (anything pushing both wires the same direction) cancels out. The ESP32 ADC cannot do this at all. If your project involves a Wheatstone bridge or a shunt resistor, differential mode is the whole reason.

The gain setting

The gain sets the input range. Pick the smallest range that fits your signal:

GainFull-scaleUse when
GAIN_TWOTHIRDS+/-6.144V5V signals
GAIN_ONE+/-4.096VMost 3.3V sensors
GAIN_FOUR+/-1.024VThermocouple amps, shunt resistors
GAIN_SIXTEEN+/-0.256VLoad cells, microvolt signals

Wrong gain does two different bad things. Too small a range clips (readings pin at the max), too large a range wastes resolution. With a load cell amp that outputs 0-20 mV, GAIN_SIXTEEN is the difference between 200 usable counts and 3200.

The data rate tradeoff

The ADS1115 samples at 8 or 128 samples per second. The default in the library is 128. For slow sensors (temperature, weight, light) that is plenty. If you set ads.setDataRate(RATE_ADS1115_8SPS) you get more internal averaging and cleaner readings at the cost of speed (e.g. right choice for a scale).

What you learned

  • The ADS1115 adds 16-bit, low-noise analog inputs over I2C.
  • Differential mode reads the voltage between two pins, which cancels noise and is required for bridge sensors.
  • Gain and data rate trade range against resolution and noise.

When something breaks

  • Readings pegged at 32767 or -32768: your input exceeds the gain range. Drop the gain (bigger +/- range) or scale the signal down.
  • “ADS1115 not found”: run an I2C scanner. Four possible addresses depending on the ADDR pin. The Adafruit library begin() takes the address as its argument.
  • Readings noisy even on the ADS: your sensor wire is long and picking up mains hum. Average 8 readings, or check that the sensor and ESP32 share a ground.
  • Slow loop: at 8 SPS each read blocks for 125 ms. That is the chip doing its job, not your code. Raise the data rate or accept it.

What to build next

  • The load cell + HX711 tutorial covers a dedicated weight amp, but the ADS1115 in differential mode handles bridge sensors too.
  • Pair with a thermocouple amp for high-temp logging with real resolution.
  • The book IoT with ESP32 bundles the sensor tutorials.