ADS1115 on STM32F103: Configuration and I²C Readout with Example Code
ADS1115 at a glance
- 16‑bit SAR ADC, low power
- Four analog inputs: AIN0–AIN3 (single‑ended) or differential pairs
- Supply: 2.0–5.5 V on VDD, GND reference
- Digital interface: I²C (SCL, SDA)
- Address select: ADDR pin (GND/ VDD/ SDA/ SCL)
- Optional comparator/alert output: ALERT/RDY
Typical wiring to STM32F103 (3.3 V system):
- VDD → 3V3
- GND → GND
- ADDR → GND (7‑bit I²C address 0x48)
- SDA/SCL → MCU SDA/SCL (with pull‑ups)
- ALERT/RDY → leave unconnected unless comparator/ready is used
Note on I²C address: ADS1115 uses a 7‑bit address. With ADDR=GND it is 0x48. Some code bases show the 8‑bit "wire format" address (0x90 for write, 0x91 for read). Both represent the same device.
Brief I²C recap (start/stop/ACK/NACK)
- Start (S): while SCL is high, SDA transitions high→low
- Stop (P): while SCL is high, SDA transitions low→high
- ACK: receiver pulls SDA low during the 9th SCL pulce
- NACK: receiver leaves SDA high during the 9th SCL pulse
The following software I²C snippets demonstrate the signaling. Replace the pin accesors and delays with your board’s implementation.
// soft_i2c.h — minimal bit‑banged I²C interface (GPIO + delays required)
#include <stdint.h>
#include <stdbool.h>
// Platform hooks to implement
void i2c_gpio_sda_out(void);
void i2c_gpio_sda_in(void);
void i2c_gpio_set_sda(int level);
void i2c_gpio_set_scl(int level);
int i2c_gpio_get_sda(void);
void i2c_delay_us(unsigned int us);
void i2c_init(void);
void i2c_start(void);
void i2c_stop(void);
bool i2c_write_byte(uint8_t byte);
uint8_t i2c_read_byte(bool ack);
// soft_i2c.c — simple implementation
#include "soft_i2c.h"
void i2c_init(void) {
i2c_gpio_sda_out();
i2c_gpio_set_sda(1);
i2c_gpio_set_scl(1);
}
static void i2c_clock_tick(void) {
i2c_gpio_set_scl(1);
i2c_delay_us(3);
i2c_gpio_set_scl(0);
}
void i2c_start(void) {
i2c_gpio_sda_out();
i2c_gpio_set_sda(1);
i2c_gpio_set_scl(1);
i2c_delay_us(3);
i2c_gpio_set_sda(0); // START: SDA high→low while SCL high
i2c_delay_us(3);
i2c_gpio_set_scl(0);
}
void i2c_stop(void) {
i2c_gpio_sda_out();
i2c_gpio_set_sda(0);
i2c_gpio_set_scl(1);
i2c_delay_us(3);
i2c_gpio_set_sda(1); // STOP: SDA low→high while SCL high
i2c_delay_us(3);
}
bool i2c_write_byte(uint8_t byte) {
i2c_gpio_sda_out();
for (int i = 7; i >= 0; --i) {
i2c_gpio_set_sda((byte >> i) & 0x01);
i2c_clock_tick();
}
// Read ACK
i2c_gpio_sda_in();
i2c_gpio_set_scl(1);
i2c_delay_us(3);
bool ack = (i2c_gpio_get_sda() == 0);
i2c_gpio_set_scl(0);
i2c_gpio_sda_out();
return ack;
}
uint8_t i2c_read_byte(bool ack) {
uint8_t data = 0;
i2c_gpio_sda_in();
for (int i = 7; i >= 0; --i) {
i2c_gpio_set_scl(1);
i2c_delay_us(3);
data |= (i2c_gpio_get_sda() & 1) << i;
i2c_gpio_set_scl(0);
i2c_delay_us(3);
}
// Send ACK/NACK
i2c_gpio_sda_out();
i2c_gpio_set_sda(ack ? 0 : 1);
i2c_clock_tick();
i2c_gpio_set_sda(1);
return data;
}
ADS1115 registers and configuration feilds
-
Pointer register (write one byte before data access):
- 0x00: Conversion register (read 16‑bit)
- 0x01: Config register (write/read 16‑bit)
- 0x02: Lo_thresh (comparator)
- 0x03: Hi_thresh (comparator)
-
Config register bit map (MSB→LSB):
- [15] OS (1 too start single conversion; reads 0 while converting, 1 when ready)
- [14:12] MUX (input selection)
- [11:9] PGA (full‑scale range)
- [8] MODE (0=continuous, 1=single‑shot)
- [7:5] DR (data rate)
- [4] COMP_MODE
- [3] COMP_POL
- [2] COMP_LAT
- [1:0] COMP_QUE (0–2 enable queue, 3 disables comparator)
Convenient bit masks (high/low byte accurate to the datasheet):
// ads1115.h — register addresses and field masks
#pragma once
#include <stdint.h>
#define ADS1115_PTR_CONVERT 0x00
#define ADS1115_PTR_CONFIG 0x01
#define ADS1115_PTR_LO_THR 0x02
#define ADS1115_PTR_HI_THR 0x03
// 7‑bit I²C base address when ADDR=GND
#define ADS1115_ADDR_7BIT 0x48
// Config register fields
#define ADS1115_OS_SINGLE (1u << 15)
// MUX (single‑ended and differential)
#define ADS1115_MUX_AIN0_AIN1 (0u << 12)
#define ADS1115_MUX_AIN0_AIN3 (1u << 12)
#define ADS1115_MUX_AIN1_AIN3 (2u << 12)
#define ADS1115_MUX_AIN2_AIN3 (3u << 12)
#define ADS1115_MUX_AIN0_GND (4u << 12)
#define ADS1115_MUX_AIN1_GND (5u << 12)
#define ADS1115_MUX_AIN2_GND (6u << 12)
#define ADS1115_MUX_AIN3_GND (7u << 12)
// PGA (full‑scale range)
#define ADS1115_PGA_6V144 (0u << 9)
#define ADS1115_PGA_4V096 (1u << 9)
#define ADS1115_PGA_2V048 (2u << 9)
#define ADS1115_PGA_1V024 (3u << 9)
#define ADS1115_PGA_0V512 (4u << 9)
#define ADS1115_PGA_0V256 (5u << 9) // 5,6,7 map to the same 0.256 V range
#define ADS1115_MODE_CONT (0u << 8)
#define ADS1115_MODE_SINGLE (1u << 8)
// Data rate
#define ADS1115_DR_8SPS (0u << 5)
#define ADS1115_DR_16SPS (1u << 5)
#define ADS1115_DR_32SPS (2u << 5)
#define ADS1115_DR_64SPS (3u << 5)
#define ADS1115_DR_128SPS (4u << 5)
#define ADS1115_DR_250SPS (5u << 5)
#define ADS1115_DR_475SPS (6u << 5)
#define ADS1115_DR_860SPS (7u << 5)
// Comparator disable
#define ADS1115_COMP_WINDOW (1u << 4) // comparator mode
#define ADS1115_COMP_ACTIVE_H (1u << 3) // polarity
#define ADS1115_COMP_LATCH (1u << 2) // latch
#define ADS1115_COMP_QUE(x) ((x) & 0x3) // 0–3; 3 disables comparator
#define ADS1115_COMP_DISABLE (0x3)
// LSB sizes for voltage conversion
static inline float ads1115_lsb_volts(uint16_t pga_field) {
switch (pga_field) {
case ADS1115_PGA_6V144: return 6.144f / 32768.0f; // ~0.1875 mV
case ADS1115_PGA_4V096: return 4.096f / 32768.0f;
case ADS1115_PGA_2V048: return 2.048f / 32768.0f;
case ADS1115_PGA_1V024: return 1.024f / 32768.0f;
case ADS1115_PGA_0V512: return 0.512f / 32768.0f;
default: return 0.256f / 32768.0f; // 0.256 V
}
}
Low‑level register access helpers (software I²C)
// ads1115_ll.c — basic read/write over bit‑banged I²C
#include "soft_i2c.h"
#include "ads1115.h"
static inline uint8_t addr8(uint8_t addr7, int read) {
return (uint8_t)((addr7 << 1) | (read ? 1 : 0));
}
static bool ads1115_write16(uint8_t addr7, uint8_t reg, uint16_t val) {
i2c_start();
if (!i2c_write_byte(addr8(addr7, 0))) { i2c_stop(); return false; }
if (!i2c_write_byte(reg)) { i2c_stop(); return false; }
// MSB first
if (!i2c_write_byte((uint8_t)(val >> 8))) { i2c_stop(); return false; }
if (!i2c_write_byte((uint8_t)(val & 0xFF))) { i2c_stop(); return false; }
i2c_stop();
return true;
}
static bool ads1115_read16(uint8_t addr7, uint8_t reg, uint16_t *out) {
// Write pointer register
i2c_start();
if (!i2c_write_byte(addr8(addr7, 0))) { i2c_stop(); return false; }
if (!i2c_write_byte(reg)) { i2c_stop(); return false; }
// Repeated START, switch to read
i2c_start();
if (!i2c_write_byte(addr8(addr7, 1))) { i2c_stop(); return false; }
uint8_t msb = i2c_read_byte(true);
uint8_t lsb = i2c_read_byte(false); // NACK last byte
i2c_stop();
*out = (uint16_t)((msb << 8) | lsb);
return true;
}
ADS1115 configuration and single‑shot conversion
The configuration sequence:
- Build the 16‑bit config word (MUX, PGA, mode, data rate, comparator disabled).
- Write the config register.
- Wait until conversion completes (OS bit reads back as 1) or wait long enough based on DR.
- Read the 16‑bit conversion register.
// ads1115.c — single‑shot read API
typedef struct {
uint8_t i2c_addr7; // e.g., 0x48 for ADDR=GND
uint16_t pga_field; // one of ADS1115_PGA_* (kept for scaling)
} ads1115_t;
static bool ads1115_read_config(uint8_t addr7, uint16_t *cfg) {
return ads1115_read16(addr7, ADS1115_PTR_CONFIG, cfg);
}
static bool ads1115_write_config(uint8_t addr7, uint16_t cfg) {
return ads1115_write16(addr7, ADS1115_PTR_CONFIG, cfg);
}
bool ads1115_init(ads1115_t *dev, uint8_t addr7) {
dev->i2c_addr7 = addr7;
dev->pga_field = ADS1115_PGA_6V144; // default range
// Optional: program comparator thresholds if used; here we disable comparator
return true;
}
// Trigger a single conversion on a selected channel and read raw result
bool ads1115_read_single_raw(ads1115_t *dev, uint16_t mux_field,
uint16_t dr_field, int16_t *raw) {
// Build config word
uint16_t cfg = 0;
cfg |= ADS1115_OS_SINGLE;
cfg |= mux_field; // input selection
cfg |= dev->pga_field; // full scale range
cfg |= ADS1115_MODE_SINGLE; // single-shot mode
cfg |= dr_field; // data rate
cfg |= ADS1115_COMP_QUE(ADS1115_COMP_DISABLE);
if (!ads1115_write_config(dev->i2c_addr7, cfg)) return false;
// Poll OS bit until ready (or use a conservative delay)
for (int i = 0; i < 50; ++i) {
uint16_t r;
if (!ads1115_read_config(dev->i2c_addr7, &r)) return false;
if (r & ADS1115_OS_SINGLE) break; // ready
// ~1 ms max at 860 SPS; adjust if DR is lower
i2c_delay_us(500);
}
uint16_t data;
if (!ads1115_read16(dev->i2c_addr7, ADS1115_PTR_CONVERT, &data)) return false;
*raw = (int16_t)data; // two's complement
return true;
}
// Convert raw code to volts based on the configured PGA
double ads1115_raw_to_volts(const ads1115_t *dev, int16_t code) {
const float lsb = ads1115_lsb_volts(dev->pga_field);
return (double)code * (double)lsb;
}
Usage example for single‑ended AIN3 vs GND at 860 SPS with ±6.144 V range:
// example.c
#include "soft_i2c.h"
#include "ads1115.h"
int main(void) {
i2c_init();
ads1115_t adc;
ads1115_init(&adc, ADS1115_ADDR_7BIT); // 0x48 when ADDR=GND
adc.pga_field = ADS1115_PGA_6V144; // ±6.144 V, ~0.1875 mV/LSB
while (1) {
int16_t raw = 0;
if (ads1115_read_single_raw(&adc, ADS1115_MUX_AIN3_GND, ADS1115_DR_860SPS, &raw)) {
double v = ads1115_raw_to_volts(&adc, raw);
// Use v (volts)
}
// Small delay
i2c_delay_us(10000);
}
}
Pointer operations (explicit)
If you perfer to split pointer and data phases manually:
- To select the conversion register for a subsequent read, write the pointer 0x00, then re‑start and read two bytes.
- To write the configuration, write pointer 0x01 followed by MSB then LSB of the config word.
// Set pointer to conversion register only
static bool ads1115_select_conversion(uint8_t addr7) {
i2c_start();
if (!i2c_write_byte((addr7 << 1) | 0)) { i2c_stop(); return false; }
if (!i2c_write_byte(ADS1115_PTR_CONVERT)) { i2c_stop(); return false; }
i2c_stop();
return true;
}
Trimmed‑mean filter (drop min and max)
Collect multiple samples, discard the largest and smallest, then average the rest.
// filter.c
#include <float.h>
#include <stddef.h>
static double trimmed_mean_read(ads1115_t *dev, uint16_t mux_field,
uint16_t dr_field, int samples) {
if (samples < 3) samples = 3;
double sum = 0.0;
double vmin = DBL_MAX, vmax = -DBL_MAX;
for (int i = 0; i < samples; ++i) {
int16_t raw;
if (!ads1115_read_single_raw(dev, mux_field, dr_field, &raw)) {
return 0.0; // handle I²C error appropriately
}
double v = ads1115_raw_to_volts(dev, raw);
sum += v;
if (v < vmin) vmin = v;
if (v > vmax) vmax = v;
}
sum -= vmin;
sum -= vmax;
return sum / (double)(samples - 2);
}
Example wrapper to read AIN3 with a 10‑sample trimmed mean:
double ads1115_read_voltage_filtered(ads1115_t *dev) {
return trimmed_mean_read(dev, ADS1115_MUX_AIN3_GND, ADS1115_DR_860SPS, 10);
}
Notes on comparator thresholds (optional)
The Lo_thresh and Hi_thresh registers (0x02, 0x03) are only needed if using the comparator/ALERT. For simple polling reads, you can leave the comparator disabled via COMP_QUE=3 in the config word.
Changing input channels
Select the desired input using MUX bits when building the config:
- AIN0 vs GND: ADS1115_MUX_AIN0_GND
- AIN1 vs GND: ADS1115_MUX_AIN1_GND
- AIN2 vs GND: ADS1115_MUX_AIN2_GND
- AIN3 vs GND: ADS1115_MUX_AIN3_GND
For differential measurements, use the corresponding AINp/AINn fields (e.g., ADS1115_MUX_AIN0_AIN1).
Voltage conversion reminder
The ADS1115 returns a signed 16‑bit code (two’s complement). To convert to volts, multiply by the LSB size determined by the selected PGA. For ±6.144 V full‑scale, LSB ≈ 0.1875 mV. For other ranges, use the provided ads1115_lsb_volts helper.