← All projects
Sensors Intermediate

A Precise Voltmeter on the ADS1115: 16 Bits Instead of the ESP32's Wobbly ADC

The ESP32's built-in ADC has a dead zone near zero, a 3.1 V ceiling and non-linearity. An external 16-bit ADS1115 on I2C measures in 0.125 mV steps. We compare both on one potentiometer and measure a 12 V battery through a divider.

Difficulty
Intermediate
Time
45 min
Category
Sensors
Updated

About the project

analogRead() on the ESP32 is handy but not accurate: up to ~0.1 V it reads zero, above ~3.1 V it reads full scale, and in between it can be tens of millivolts off. That is fine for a battery indicator or a potentiometer, not for a measuring instrument. The ADS1115 is a 16-bit ADC with a programmable gain amplifier and its own voltage reference: 4 inputs, I2C, and errors in fractions of a percent. We compare it with the built-in ADC and build a voltmeter for a 12 V battery.

What you need

  • ESP32 DevKit
  • ADS1115 module (the purple board)
  • 10 kΩ potentiometer
  • 100 kΩ and 22 kΩ resistors for the divider
  • The Adafruit ADS1X15 library

Wiring

ADS1115 Connection
VDD / GND 3V3 / GND
SDA / SCL GPIO21 / GPIO22
ADDR GND → address 0x48
A0 the potentiometer wiper (also on GPIO34 for comparison)
A1 the midpoint of a 100 kΩ / 22 kΩ divider from "+12 V"

Potentiometer: outer pins to 3V3 and GND. Divider: "+12 V" → 100 kΩ → A1 → 22 kΩ → GND, with the battery ground tied to the ESP32 GND.

Setting the gain

setGain() sets the measuring range:

GAIN Range Step (16-bit)
GAIN_TWOTHIRDS ±6.144 V 0.1875 mV
GAIN_ONE ±4.096 V 0.125 mV
GAIN_TWO (default) ±2.048 V 0.0625 mV
GAIN_FOUR … GAIN_SIXTEEN ±1.024 … ±0.256 V down to 0.0078 mV

Important: a ±6.144 V range does not mean you may feed 6 V into an input. No input may go outside GND − 0.3 V … VDD + 0.3 V. On a 3.3 V supply that is 3.6 V at most. Anything higher must go through a divider. The simulator warns when you exceed it.

Sizing the divider

V(in) = V(bat) × R2 / (R1 + R2) = 14.4 × 22 / 122 ≈ 2.6 V even for a battery on charge (14.4 V), with room to spare in the ±4.096 V range. The code multiplies back by (100 + 22) / 22. A 122 kΩ divider draws only ~0.1 mA from the battery.

Code

// Voltmeter: ADS1115 (16-bit) vs the ESP32 ADC + a battery voltage
#include <Adafruit_ADS1X15.h>

Adafruit_ADS1115 ads;
const float DIVIDER = (100.0 + 22.0) / 22.0;   // 100 kΩ / 22 kΩ divider on A1

void setup() {
  Serial.begin(115200);
  ads.setGain(GAIN_ONE);   // ±4.096 V, 0.125 mV steps
  if (!ads.begin(0x48)) {
    Serial.println("ADS1115 not found");
    while (true) delay(1000);
  }
}

void loop() {
  float pot = ads.computeVolts(ads.readADC_SingleEnded(0));
  float naive = analogRead(34) * 3.3 / 4095.0;    // the "naive" conversion
  float mv = analogReadMilliVolts(34) / 1000.0;   // with the factory calibration
  float battery = ads.computeVolts(ads.readADC_SingleEnded(1)) * DIVIDER;
  Serial.printf("A0: ADS1115 %.4f V | analogRead %.3f V | analogReadMilliVolts %.3f V\n", pot, naive, mv);
  Serial.printf("Battery: %.3f V\n", battery);
  delay(1000);
}

What you will see

Turn the potentiometer to one end. Near zero the ESP32 reads 0 V while the ADS1115 still sees 0.05 V, and near 3.3 V the built-in ADC "hits the ceiling" at 3.1 V. analogReadMilliVolts() is clearly better than the naive formula, but ±10–20 mV of noise remains.

Good to know

  • Speed. The default is 128 samples per second (~8 ms each). setDataRate(RATE_ADS1115_860SPS) is faster but noisier.
  • Differential mode, readADC_Differential_0_1(), measures the difference between A0 and A1, for example across a current shunt.
  • The ADS1015 is the 12-bit sibling with the same pinout; in code it is the Adafruit_ADS1015 class. Mix up the classes and your readings are off by a factor of 16.
  • Four ADS1115s on one bus: ADDR to GND, VDD, SDA or SCL gives addresses 0x48–0x4B.