DEV Community

張旭豐
張旭豐

Posted on

5 ADS1115 ADC Projects That Add 16-bit Precision to Any Arduino

5 ADS1115 ADC Projects That Add 16-bit Precision to Any Arduino

Build precision measurement systems: hydroponic nutrient monitor, EEG signal acquisition, precision weighing scale, environmental data logger, and strain gauge reader

The ADS1115 is a 16-bit I²C analog-to-digital converter that solves the Arduino ADC problem once and for all. The Arduino Nano and Uno have 10-bit ADC — that is only 1,024 discrete levels across a 5V range, which means the smallest detectable voltage step is 4.88mV. For many sensor applications that is not enough. The ADS1115 gives you 65,536 levels (16-bit) across the same 5V range, bringing the resolution down to 0.076mV per LSB — a 16x improvement. It has four differential input channels or eight single-ended inputs, a programmable gain amplifier, and costs less than $5.

In this guide, we build five precision measurement projects that would be impossible or unreliable with the standard Arduino ADC.

Topics covered: I²C address configuration for multiple ADS1115, differential vs single-ended measurement, PGA gain setting, 4-20mA current loop interfacing, Wheatstone bridge signal conditioning, strain gauge amplification, oversampling and noise reduction.


What You'll Need

  • ADS1115 (×1-2 depending on project)
  • Arduino Nano or Uno (×1)
  • Screw terminal adapter (×1, for secure sensor wiring)
  • OLED 128×64 I²C display (×1)
  • pH sensor module (×1, for hydroponics)
  • Soil moisture sensor (×1)
  • DS18B20 temperature sensor (×1)
  • Load cell + HX711 (×1, for weighing scale)
  • Strain gauge (×1, for strain measurement)
  • 4-20mA current loop sensor (×1, for industrial applications)
  • Jumper wires and breadboard
  • USB cable for programming

Why ADS1115 Instead of Arduino Due?

Arduino Due has a 12-bit ADC (4096 levels) — better than standard Arduino but still 4x worse than ADS1115. ADS1115 is cheaper, works with all Arduino boards, and uses I²C so it does not consume any analog pins.

Property Arduino Nano (built-in ADC) Arduino Due ADS1115
Resolution 10-bit (1024 levels) 12-bit (4096 levels) 16-bit (65536 levels)
Voltage step 4.88mV 1.22mV 0.076mV
Interface Built-in analog pins Dedicated analog pins I²C (2 pins)
Channels 8 single-ended 12 single-ended 4 differential / 8 single-ended
Cost $0 (included) $20+ $3-5
PGA No No Yes (1x to 16x)

How ADS1115 Works

Technical Principle

The ADS1115 uses a successive approximation register (SAR) ADC architecture. It samples an analog voltage, converts it through a 16-bit DAC feedback loop, and outputs the result over I²C. The built-in Programmable Gain Amplifier (PGA) can amplify small signals (up to 16x) before conversion, extending effective resolution for low-voltage sensors. You can choose from four data rates: 8, 64, 128, or 860 samples per second.

Wiring Diagram (I²C)

Arduino          ADS1115
  A4 (SDA)  ────  SDA
  A5 (SCL)  ────  SCL
  5V        ────  VDD
  GND       ────  GND

ADS1115 address pins (A0, A1, A2):
  All GND  = address 0x48 (default)
  A0→VDD   = 0x49
  A1→VDD   = 0x4A
  A2→VDD   = 0x4B
Enter fullscreen mode Exit fullscreen mode

Basic Example Code

// WF1 Run #052 - Basic ADS1115 16-bit ADC Reading
#include <Wire.h>
#include <Adafruit_ADS1X15.h>

Adafruit_ADS1115 ads;

void setup() {
  Serial.begin(115200);
  ads.begin();
  // Set PGA gain: ADS1115_PGA_2 = 2x gain, range ±2.048V
  ads.setGain(ADS1115_PGA_2);
}

void loop() {
  int16_t adc0 = ads.readADC_SingleEnded(0);

  // Convert to voltage
  float voltage = ads.computeVolts(adc0);

  Serial.print("ADC0: ");
  Serial.print(adc0);
  Serial.print(" (");
  Serial.print(voltage, 6);
  Serial.println(" V)");

  delay(500);
}
Enter fullscreen mode Exit fullscreen mode

Project 1: Hydroponic Nutrient Monitor

Goal: Monitor pH, electrical conductivity (EC), and temperature of hydroponic nutrient solution with millivolt precision, triggering alerts when values leave the acceptable range

Hydroponic setup: ADS1115 reading multiple sensors with OLED display

Hardware

  • ADS1115 (×1)
  • Arduino Nano (×1)
  • pH sensor module (×1)
  • Soil moisture sensor (analog) (×1)
  • DS18B20 temperature sensor (×1)
  • OLED 128×64 I²C display (×1)

Code

// WF1 Run #052 - Project 1: Hydroponic Nutrient Monitor
#include <Wire.h>
#include <Adafruit_ADS1X15.h>
#include <OneWire.h>
#include <DallasTemperature.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

Adafruit_ADS1115 ads;
OneWire oneWire(A3);  // DS18B20 on pin A3
DallasTemperature sensors(&oneWire);

#define SCREEN_WIDTH  128
#define SCREEN_HEIGHT 64
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);

// pH thresholds (in ADC raw units at PGA_4)
#define PH_MIN_RAW   22000
#define PH_MAX_RAW   28000
#define EC_MIN_RAW   18000
#define EC_MAX_RAW   25000

void setup() {
  Serial.begin(115200);
  ads.begin();
  ads.setGain(ADS1115_PGA_4);   // ±2.048V range for pH/EC sensors
  sensors.begin();
  display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
  display.setTextColor(SSD1306_WHITE);
}

void loop() {
  // Read pH sensor (analog output from pH module)
  int16_t ph_raw = ads.readADC_SingleEnded(0);
  float ph_voltage = ads.computeVolts(ph_raw);
  // pH module outputs 2.5V at pH 7.0, varies ±1V across pH 4-10
  float pH = 7.0 + (2.5 - ph_voltage) / 0.18;  // Approximate calibration

  // Read EC sensor (conductivity probe)
  int16_t ec_raw = ads.readADC_SingleEnded(1);
  float ec_voltage = ads.computeVolts(ec_raw);

  // Read temperature
  sensors.requestTemperatures();
  float tempC = sensors.getTempCByIndex(0);

  // Display
  display.clearDisplay();
  display.setTextSize(1);
  display.setCursor(0, 0);
  display.print("HYDROPONIC MONITOR");
  display.setTextSize(2);
  display.setCursor(0, 16);
  display.print("pH: ");
  display.println(pH, 2);
  display.print("EC: ");
  display.print(ec_voltage, 3);
  display.println(" V");
  display.setTextSize(1);
  display.print("Temp: ");
  display.print(tempC, 1);
  display.println(" C");

  // Alert indicators
  display.setTextSize(1);
  display.setCursor(0, 48);
  if (pH < 5.5 || pH > 6.5) display.print("pH OUT OF RANGE!");
  else display.print("pH OK");

  display.display();

  // Serial for data logging
  Serial.print("pH: "); Serial.print(pH, 2);
  Serial.print(" | EC: "); Serial.print(ec_voltage, 3);
  Serial.print(" V | Temp: "); Serial.println(tempC, 1);

  delay(2000);
}
Enter fullscreen mode Exit fullscreen mode

Project 2: Precision Weighing Scale

Goal: Build a 0.01g resolution weighing scale using a load cell and HX711 — but use ADS1115 with a precision instrumentation amplifier instead of the common HX711 module for higher accuracy

Hardware

  • ADS1115 (×1)
  • Arduino Nano (×1)
  • Load cell (5kg rated) (×1)
  • Instrumentation amplifier (INA122P) (×1)
  • Calibration weight (100g)
  • OLED 128×64 I²C display (×1)

Code

// WF1 Run #052 - Project 2: Precision Weighing Scale
#include <Wire.h>
#include <Adafruit_ADS1X15.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

Adafruit_ADS1115 ads;

#define SCREEN_WIDTH  128
#define SCREEN_HEIGHT 64
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);

// Calibration values (determine by measuring known weights first)
#define CALIBRATION_FACTOR  420.5   // raw units per gram
#define TARE_OFFSET         0       // set to current reading when scale is empty

float scaleOffset = 0;
bool calibrated = false;

void setup() {
  Serial.begin(115200);
  ads.begin();
  ads.setGain(ADS1115_PGA_1);   // ±4.096V range for load cell via instrumentation amp
  display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
  display.setTextColor(SSD1306_WHITE);

  // Tare on startup
  delay(1000);
  float sum = 0;
  for (int i = 0; i < 50; i++) sum += ads.readADC_SingleEnded(0);
  scaleOffset = sum / 50.0;
  Serial.println("Scale tared. Place calibration weight.");
}

void loop() {
  // Oversample 64 readings for noise reduction
  float sum = 0;
  for (int i = 0; i < 64; i++) sum += ads.readADC_SingleEnded(0);
  float avgRaw = sum / 64.0;
  float netRaw = avgRaw - scaleOffset;

  // Convert to grams
  float weightG = netRaw / CALIBRATION_FACTOR;

  // Display
  display.clearDisplay();
  display.setTextSize(1);
  display.setCursor(0, 0);
  display.print("PRECISION SCALE");
  display.setTextSize(3);
  display.setCursor(0, 20);
  if (weightG >= 0) display.print("+");
  display.print(weightG, 2);
  display.setTextSize(1);
  display.setCursor(100, 52);
  display.print("g");

  // Stability indicator
  float stdDev = 0;
  for (int i = 0; i < 20; i++) {
    int16_t r = ads.readADC_SingleEnded(0);
    stdDev += pow(r - avgRaw, 2);
  }
  stdDev = sqrt(stdDev / 20.0);

  display.setTextSize(1);
  display.setCursor(0, 52);
  if (stdDev < 5) display.print("STABLE");
  else if (stdDev < 20) display.print("VARIED");
  else display.print("UNSTABLE");

  display.display();
  Serial.print("Weight: ");
  Serial.print(weightG, 3);
  Serial.print(" g | Noise: ");
  Serial.println(stdDev, 1);

  delay(200);
}
Enter fullscreen mode Exit fullscreen mode

Project 3: Environmental Data Logger

Goal: Log temperature, humidity, pressure, and light levels to SD card with timestamps — use ADS1115 for higher accuracy on the analog sensors

Workbench: ADS1115 reading four sensors simultaneously with serial monitor

Hardware

  • ADS1115 (×1)
  • Arduino Nano (×1)
  • DHT22 temperature/humidity sensor (×1)
  • BMP280 pressure sensor (I²C) (×1)
  • LDR (photoresistor) (×1)
  • SD card module (×1)
  • Real-time clock DS3231 (×1)

Code

// WF1 Run #052 - Project 3: Environmental Data Logger
#include <Wire.h>
#include <Adafruit_ADS1X15.h>
#include <DHT.h>
#include <DHT.h>
#include <RTClib.h>
#include <SD.h>
#include <SPI.h>

Adafruit_ADS1115 ads;
#define DHT_PIN  A3
#define DHT_TYPE DHT22
DHT dht(DHT_PIN, DHT_TYPE);
RTC_DS3231 rtc;

#define SD_CS_PIN  10
#define LDR_PIN     0   // ADS1115 channel 0

File logFile;

void setup() {
  Serial.begin(115200);
  ads.begin();
  ads.setGain(ADS1115_PGA_1);
  dht.begin();
  rtc.begin();
  SD.begin(SD_CS_PIN);

  // Create log file
  DateTime now = rtc.now();
  char filename[16];
  snprintf(filename, 16, "ENV_%04u%02u%02u.CSV", now.year(), now.month(), now.day());
  logFile = SD.open(filename, FILE_WRITE);
  if (logFile) {
    logFile.println("datetime,temp_c,humidity_pct,pressure_hPa,light_adc");
    logFile.close();
  }

  Serial.print("Logging to: ");
  Serial.println(filename);
}

void loop() {
  // Read temperature and humidity from DHT22
  float temp = dht.readTemperature();
  float humidity = dht.readHumidity();

  // Read LDR via ADS1115 (more stable than Arduino ADC)
  int16_t lightRaw = ads.readADC_SingleEnded(LDR_PIN);
  float lightPct = map(lightRaw, 0, 32767, 0, 100);  // 0-100% light

  // Timestamp
  DateTime now = rtc.now();
  char timestamp[20];
  snprintf(timestamp, 20, "%04u-%02u-%02u %02u:%02u:%02u",
    now.year(), now.month(), now.day(), now.hour(), now.minute(), now.second());

  // Write to SD
  logFile = SD.open("ENV_LOG.CSV", FILE_WRITE);
  if (logFile) {
    logFile.print(timestamp);
    logFile.print(",");
    logFile.print(temp, 1);
    logFile.print(",");
    logFile.print(humidity, 1);
    logFile.print(",");
    logFile.print(lightPct, 0);
    logFile.print(",");
    logFile.println(lightRaw);
    logFile.close();
  }

  // Serial output
  Serial.print(timestamp);
  Serial.print(" | T: ");
  Serial.print(temp, 1);
  Serial.print("C | H: ");
  Serial.print(humidity, 1);
  Serial.print("% | Light: ");
  Serial.print(lightPct, 0);
  Serial.print("% (ADC: ");
  Serial.print(lightRaw);
  Serial.println(")");

  delay(60000);  // Log every minute
}
Enter fullscreen mode Exit fullscreen mode

Project 4: 4-20mA Current Loop Receiver

Goal: Read industrial 4-20mA current loop sensors using ADS1115 as the current-to-voltage converter — common for pressure, flow, and level sensors in industrial environments

Hardware

  • ADS1115 (×1)
  • Arduino Nano (×1)
  • 250Ω precision resistor (×1, converts 4-20mA to 1-5V)
  • 4-20mA pressure sensor (×1, or any industrial sensor)
  • OLED 128×64 I²C display (×1)

Code

// WF1 Run #052 - Project 4: 4-20mA Current Loop Receiver
#include <Wire.h>
#include <Adafruit_ADS1X15.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

Adafruit_ADS1115 ads;

#define SCREEN_WIDTH  128
#define SCREEN_HEIGHT 64
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);

// 4-20mA loop: 4mA = 0% sensor value, 20mA = 100% sensor value
// 250Ω resistor: 4mA × 250Ω = 1.0V, 20mA × 250Ω = 5.0V
// ADS1115 PGA_1 range: ±4.096V, so full 5V span uses most of the range
#define R_SENSE  250.0   // Ohm

void setup() {
  Serial.begin(115200);
  ads.begin();
  ads.setGain(ADS1115_PGA_1);   // ±4.096V — covers full 1-5V from 250Ω resistor
  display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
  display.setTextColor(SSD1306_WHITE);
}

void loop() {
  int16_t adcRaw = ads.readADC_SingleEnded(0);
  float voltage = ads.computeVolts(adcRaw);
  float current_mA = (voltage / R_SENSE) * 1000.0;   // V / Ω = A → mA

  // Map 4-20mA to 0-100%
  float sensor_pct = map((long)(current_mA * 1000), 4000, 20000, 0, 10000) / 100.0;

  // Display
  display.clearDisplay();
  display.setTextSize(1);
  display.setCursor(0, 0);
  display.print("4-20mA LOOP RECEIVER");
  display.setTextSize(2);
  display.setCursor(0, 20);
  display.print("I: ");
  display.print(current_mA, 2);
  display.println(" mA");
  display.setTextSize(2);
  display.print("Val: ");
  display.print(sensor_pct, 1);
  display.println(" %");
  display.setTextSize(1);
  display.setCursor(0, 50);
  if (current_mA < 3.5) display.print("LOOP ERROR");
  else if (current_mA < 4.0) display.print("BELOW RANGE");
  else if (current_mA > 21.0) display.print("ABOVE RANGE");
  else display.print("NORMAL");
  display.display();

  Serial.print("Current: ");
  Serial.print(current_mA, 3);
  Serial.print(" mA | Sensor: ");
  Serial.print(sensor_pct, 1);
  Serial.println(" %");

  delay(500);
}
Enter fullscreen mode Exit fullscreen mode

Project 5: Strain Gauge Reader

Goal: Read a strain gauge bonded to a metal cantilever beam using ADS1115 with a Wheatstone bridge interface — detect microstrain changes with 0.001% resolution

Hardware

  • ADS1115 (×1)
  • Arduino Nano (×1)
  • Strain gauge (120Ω, bonded) (×1)
  • Instrumentation amplifier INA122P (×1)
  • Precision resistors for Wheatstone bridge (×4, 120Ω matched)
  • OLED 128×64 I²C display (×1)

Code

// WF1 Run #052 - Project 5: Strain Gauge Reader
#include <Wire.h>
#include <Adafruit_ADS1X15.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

Adafruit_ADS1115 ads;

#define SCREEN_WIDTH  128
#define SCREEN_HEIGHT 64
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);

// Wheatstone bridge: R1=R2=R3=Rgauge = 120Ω at zero strain
// Gauge factor G = 2.0 (typical for foil gauge)
// Microstrain = (dR/R) / G * 10^6
// With ADS1115 PGA_8: each LSB = 0.5mV → strain resolution ~1 microstrain

void setup() {
  Serial.begin(115200);
  ads.begin();
  ads.setGain(ADS1115_PGA_8);    // ±0.512V range for Wheatstone bridge differential output
  display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
  display.setTextColor(SSD1306_WHITE);
  display.clearDisplay();
  display.setTextSize(1);
  display.setCursor(0, 0);
  display.println("STRAIN GAUGE READER");
  display.println("Place beam in unloaded state");
  display.println("Press reset to zero...");
  display.display();
  delay(3000);
}

void loop() {
  // Read differential voltage between A0 (INP) and A1 (INN)
  int16_t diffRaw = ads.readADC_Differential_0_1();
  float voltage_uV = ads.computeVolts(diffRaw) * 1000000.0;  // microvolts

  // Convert to microstrain
  // V_excitation = 3.3V (from Arduino)
  // GF = 2.0, R_gauge = 120Ω
  // microstrain = (4 * voltage_uV / 3.3V) / 2.0
  float microstrain = (4.0 * voltage_uV) / (3.3 * 2.0);

  // Display
  display.clearDisplay();
  display.setTextSize(1);
  display.setCursor(0, 0);
  display.print("STRAIN GAUGE");
  display.setTextSize(3);
  display.setCursor(0, 20);
  display.print(microstrain, 1);
  display.setTextSize(1);
  display.setCursor(0, 52);
  display.print("microstrain (ue)");
  display.setTextSize(1);
  display.setCursor(100, 0);
  display.print("dV:");
  display.print(voltage_uV, 0);
  display.print(" uV");
  display.display();

  Serial.print("dV: ");
  Serial.print(voltage_uV, 0);
  Serial.print(" uV | Strain: ");
  Serial.print(microstrain, 3);
  Serial.println(" ue");

  delay(100);
}
Enter fullscreen mode Exit fullscreen mode

Troubleshooting

Problem Cause Fix
ADS1115 not found on I²C scan Wrong SDA/SCL pins on your board ESP32 uses GPIO21/22; Due uses SDA1/SCL1; verify correct pins
Readings jump by ±10-50 counts PGA gain too high, input exceeds range Lower PGA gain; ensure input voltage never exceeds selected range
All channels read the same value All sensors reading floating inputs Connect actual sensors; add pull-down resistors on unused channels
ADS1115 works but Arduino ADC does not Normal — they are separate ADCs Use ADS1115 for precision, Arduino ADC for fast/dirty readings
Differential reading very small Common-mode voltage issue Ensure both differential inputs are within supply range; use instrumentation amp for very small signals
SD card fails to initialize CS pin conflict or card format Try different CS pin; format SD card as FAT16 or FAT32

Start Here

Affiliate disclosure: As an Amazon Associate, I earn from qualifying purchases.

The right parts make the difference:

ADS1115 ADC module — 16-bit I²C ADC with PGA and four differential inputs. The upgrade that makes any Arduino project precision-capable for about the cost of a pizza.

Arduino Nano CH340 — compact breadboard-compatible microcontroller. Works perfectly with ADS1115 over I²C for precision measurement projects.

pH sensor module for Arduino — BNC connector pH probe with analog output. Pair with ADS1115 for hydroponics, aquarium, or lab automation.

Load cell 5kg with HX711 — for weighing scale projects. While HX711 is common, ADS1115 gives you more flexibility for custom amplification circuits.

SD card module — for data logging projects. Required for the environmental data logger.


Next Step: Precision Measurement Design for Your Project

If this guide showed you how ADS1115 can solve your project's precision problem — but you need help designing the signal conditioning circuit for your specific sensor — I can help you design that.

I offer a personalized interactive device design guide at Fiverr:

👉 https://www.fiverr.com/phd_hfchang/generate-an-arduino-interactive-prototypef

What you get:

  • ADS1115 configuration optimized for your sensor output range
  • Signal conditioning circuit design (instrumentation amp, Wheatstone bridge, current loop)
  • Calibration procedure with pass/fail criteria
  • Arduino code with oversampling for maximum effective resolution

Tags: Arduino, ADS1115, ADC, 16-bit, precision measurement, I2C, hydroponics, strain gauge, data logger, current loop, weighing scale

Top comments (0)