# ADS1115 – A/D converter with amplifier

With this article I would like to introduce the 16-bit, 4-channel A/D converter ADS1115 and my associated library. First I will look at the technical features, then I use example sketches to show how you can run the ADS1115 with the library. Finally, for those interested, there is a deeper insight into the ADS1115 and its registers.

In this article, I’m going to cover ADS1115 modules. Of course, if you’re fit in soldering SMD parts, you can also use the bare ADS1115. My library should work the same way.

## Why do I need the ADS1115?

“My Arduino or microcontroller already has a built-in A/D converter. So why should I deal with this external A/D converter?”

Here are a few reasons. The ADS1115

• has a resolution of 16 bits compared to the 10 bits of the Arduino.
• uses an internal amplifier so that it can measure even small voltages.
• has an alert function that you can use to efficiently monitor voltages, because:
• if your microcontroller has nothing else to do, you could send it to sleep and let the power efficient ADS1115 work.
• outsourced processes that are connected via interrupts can simplify your code.

## Technical features of the ADS1115

### Inputs and outputs of the ADS1115

The ADS1115 or the modules have the following inputs and outputs:

• VCC / GND: To be connected to 2 to 5.5 volts.
• SDA / SCL: Communication via I2C. I didn’t need any extra pull-up resistors, but they’re not necessarily implemented in every module.
• ADDR: Address pin – you can choose from four I2C addresses according to the table shown below.
• ALRT: the alert pin asserts when adjustable thresholds are exceeded or when an A/D conversion (“conversion ready”) is completed. By default, the pin is deactivated.
• A0 – A3: Connections for the four channels. The applied voltage must not exceed VCC by more than 0.3 volts!

As you can see, the middle module shown above does not have an address and alert pin. Thus, you can’t change the address 0x48 and use the alert functions. If you don’t need it, you have a very space-saving solution with such a module.

If you want to check the I2C address, you can use this I2C scanner.

### Properties

Here is a brief overview of the most important features:

• 16 bit Resolution – signed (+/- 215 Bit).
• 4 channels that can be measured against each other or against GND.
• Multiplexing, so only one channel can be converted per time.
• 6 voltage (or gain) ranges from +/- 256 mV to +/- 6.144 V.
• Alert function (limit overrun or “conversion ready”).
• Conversion rate from 8 s-1 to 860 s-1.
• Continuous mode or single-shot.
• Internal voltage reference.
• Low power consumption: 150 µA in continuous mode, 2.5 µA in power-down mode. The ADS1115 automatically enters power-down mode after a single-shot measurement.

Further information can be found in the data sheet of the ADS1115.

ADS1115 modules are available for a few euros in most online stores, e.g. here at Amazon.

It seems that there ADS1115 modules which do not provide 16 bit resolution. You can test your module by reading the raw values and displaying them as a binary number. If the last three bits are always zero, you have such a model. You can do nothing but return it to the store where you bought it. You find some more details here.

### Typical circuit

I used the circuit shown below for all example sketches. Instead of the potentiometers you can, of course, connect anything else to the inputs. You could use a multimeter to check the results, or you could measure in parallel with the analog inputs of the Arduino.

As already mentioned, I didn’t need pull-ups for the I2C lines. If you have problems, add them.

## Control with the ADS1115_WE library

You can download the library with the Arduino Library Manager or directly here from GitHub.

The best way to learn how to use the ADS1115_WE library is to work through the sample sketches. I have equipped the library with six example sketches. I go most intensively into Single_Shot.ino. Many functions used there are also used in other sketches.

### Example sketch 1: Single_Shot.ino

With this sketch, we measure the four inputs in single-shot mode (i.e. on request) against GND one after the other. The parameters for the library functions you can choose from are listed in the comments in the sketch. A few notes on the (relevant) functions:

• ADS1115_WE adc = ADS1115_WE() creates the ADS1115_WE object. You can use alternative I2C addresses and busses.
• init() resets the settings of the configuration register of the ADS1115 to the default values. This has the advantage that you do not have to disconnect the ADS1115 from the power supply after you have changed your sketch. Otherwise, you would have to do this to have a defined state of the registers. init() also checks the connection to the module. The function returns false if the ADS1115 does not respond.
• setVoltageRange_mV() determines the voltage range in millivolts. The smaller the range, the greater the gain and resolution (= +/-range / 215).
• setCompareChannels() sets the channels to be compared.
• setConversionRate() sets the conversion rate in conversions per second (SPS = Samples per second).
• setMeasureMode() sets the mode, i.e. continuous or single-shot. The latter is the default.
• startSingleMeasurement() starts a conversion for the channels previously selected with setCompareChannels().
• isBusy() returns true while the conversion is not yet complete. Without while(adc.isBusy()){}, the value would be read from the last conversion – and, in case you just switched channels, it could be still the one from the former channel. There is only one conversion register!
• getResult_V() delivers the voltage in volts. For small values you can use getResult_mV().

The other functions only become relevant when you use the alert pin.

/***************************************************************************
* Example sketch for the ADS1115_WE library
*
* This sketch shows how to use the ADS1115 in single shot mode.
*
* Further information can be found on:
*
***************************************************************************/

#include<Wire.h>

/* There are several ways to create your ADS1115_WE object:
* Successfully tested with two I2C busses on an ESP32
*/

void setup() {
Wire.begin();
Serial.begin(9600);
}

/* Set the voltage range of the ADC to adjust the gain
* Please note that you must not apply more than VDD + 0.3V to the input pins!
*
* ADS1115_RANGE_6144  ->  +/- 6144 mV
* ADS1115_RANGE_4096  ->  +/- 4096 mV
* ADS1115_RANGE_2048  ->  +/- 2048 mV (default)
* ADS1115_RANGE_1024  ->  +/- 1024 mV
* ADS1115_RANGE_0512  ->  +/- 512 mV
* ADS1115_RANGE_0256  ->  +/- 256 mV
*/
adc.setVoltageRange_mV(ADS1115_RANGE_6144); //comment line/change parameter to change range

/* Set the inputs to be compared
*
*  ADS1115_COMP_0_1    ->  compares 0 with 1 (default)
*  ADS1115_COMP_0_3    ->  compares 0 with 3
*  ADS1115_COMP_1_3    ->  compares 1 with 3
*  ADS1115_COMP_2_3    ->  compares 2 with 3
*  ADS1115_COMP_0_GND  ->  compares 0 with GND
*  ADS1115_COMP_1_GND  ->  compares 1 with GND
*  ADS1115_COMP_2_GND  ->  compares 2 with GND
*  ADS1115_COMP_3_GND  ->  compares 3 with GND
*/
//adc.setCompareChannels(ADS1115_COMP_0_GND); //uncomment if you want to change the default

/* Set number of conversions after which the alert pin will assert
* - or you can disable the alert
*
*  ADS1115_ASSERT_AFTER_1  -> after 1 conversion
*  ADS1115_ASSERT_AFTER_2  -> after 2 conversions
*  ADS1115_ASSERT_AFTER_4  -> after 4 conversions
*/

/* Set the conversion rate in SPS (samples per second)
* Options should be self-explaining:
*
*/
//adc.setConvRate(ADS1115_128_SPS); //uncomment if you want to change the default

/* Set continuous or single shot mode:
*
*  ADS1115_CONTINUOUS  ->  continuous mode
*  ADS1115_SINGLE     ->  single shot mode (default)
*/
//adc.setMeasureMode(ADS1115_CONTINUOUS); //uncomment if you want to change the default

/* Choose maximum limit or maximum and minimum alert limit (window) in volts - alert pin will
*  assert when measured values are beyond the maximum limit or outside the window
*  Upper limit first: setAlertLimit_V(MODE, maximum, minimum)
*  In max limit mode the minimum value is the limit where the alert pin assertion will be
*  be cleared (if not latched)
*
*
*/
//adc.setAlertModeAndLimit_V(ADS1115_MAX_LIMIT, 3.0, 1.5); //uncomment if you want to change the default

/* Enable or disable latch. If latch is enabled the alert pin will assert until the
* conversion register is read (getResult functions). If disabled the alert pin assertion
* will be cleared with next value within limits.
*
*/

/* Sets the alert pin polarity if active:
*
* ADS1115_ACT_LOW  ->  active low (default)
* ADS1115_ACT_HIGH ->  active high
*/

/* With this function the alert pin will assert, when a conversion is ready.
* In order to deactivate, use the setAlertLimit_V function
*/

Serial.println("ADS1115 Example Sketch - Single Shot Mode");
Serial.println("Channel / Voltage [V]: ");
Serial.println();
}

void loop() {
float voltage = 0.0;

Serial.print("0: ");
Serial.print(voltage);

Serial.print(",   1: ");
Serial.print(voltage);

Serial.print(",   2: ");
Serial.print(voltage);

Serial.print(",   3: ");
Serial.println(voltage);

delay(1000);
}

float voltage = 0.0;
voltage = adc.getResult_V(); // alternative: getResult_mV for Millivolt
return voltage;
}

### Example sketch 2: Continuous.ino

In this example, the ADS1115 measures continuously. Again, the channels 0 to 3 are read against GND. You can read the conversion register at any time. It does not matter whether the value in the conversion register has been updated since the last reading or not. If you have changed the channel, it takes about the time for two conversions to get a new value. To prevent reading values from former channels the library adds a delay() after you have changed the channel. The delay() period is adjusted to the conversion rate.

The main difference to the single-shot sketch is that you don’t need to initiate the conversion.

I show here only the relevant or changed lines. Download the full sketches with the library.

  adc.setVoltageRange_mV(ADS1115_RANGE_6144); // wir nutzen wieder die ganze Range
....
....

adc.setCompareChannels(ADS1115_COMP_0_GND); // Startwert - wir wechseln später den Kanal
....
....
adc.setMeasureMode(ADS1115_CONTINUOUS); // hierwählen wir jetzt den kontinuierlichen Modus

void loop() {
float voltage = 0.0;

Serial.print("0: ");
Serial.print(voltage);

Serial.print(",   1: ");
Serial.print(voltage);
....
....
delay(1000);
}

float voltage = 0.0;
voltage = adc.getResult_V(); // wir holen einfach einen neuen Wert / kein startSingleMeasurement() notwendig
return voltage;
}

The sketch should be understandable without further explanation.

### Example sketch 3: Single_Shot_Conv_Ready_Controlled.ino

I have chosen a somewhat unwieldy name for the sketch. What I mean to say is that the ADS1115 measures in single-shot mode and controls the frequency of value output via the number of completed measurements and the conversion rate. I have set the conversion rate to 8 SPS. At every 32nd conversion the sketch outputs a value, i.e. every 4 seconds. Of course, you don’t have to perform 32 conversions to get one result. I have only done it this way to slow down the output rate. I want to show that the output rate is controlled by the conversion rate and not by a delay.

  adc.setVoltageRange_mV(ADS1115_RANGE_6144);
....
....
....
....
....
....
//adc.setMeasureMode(ADS1115_CONTINUOUS); // commented line, there the default is applied (Single-Shot)
....
....
void loop() {
float voltage = 0.0;
for(int i=0; i<32; i++){ // wait until 32 conversions are completed
}
voltage = adc.getResult_V(); // alternative: getResult_mV for Millivolt
Serial.print("Channel 0 vs GND [V]: ");
Serial.println(voltage);
Serial.println("-------------------------------");
}

Another even more unwieldy name. This sketch also controls the output frequency by the number of conversions made. Unlike the last example, the Arduino does not ask for completion with isBusy(), but the ADS1115 reports this event via the alert pin. If this goes LOW (asserts), an interrupt is triggered that increments the counter variable.

Again, the only reason for performing 32 conversions is to slow down the output rate.

As new functions, the sketch uses:

• setAlertPinMode() normally determines after how many limit overruns the alert pin asserts (1, 2 or 4). Actually, this is not relevant here. However, if you do not call the function, the default setting will be applied (ADS1115_DISABLE_ALERT). This means: the function must be called for this sketch with any parameter except ADS1115_DISABLE_ALERT.
• setAlertPol() determines whether the alert pin is LOW or HIGH in the event of an alert. The default setting is LOW (ADS1115_ACT_LOW).
• setAlertPinToConversionReady() tells the ADS1115 that the alert should be triggered when the conversion is completed.

The conversion alert also works in the continuous mode. However, this only applies to the alert pin method (hardware method). The software check by isBusy() only works in single-shot mode.

....
....
int interruptPin = 2;
volatile bool convReady = false;
....
....
void setup() {
....
....
pinMode(interruptPin, INPUT_PULLUP);
....
....
....
....
....
....
....
....
adc.setConvRate(ADS1115_8_SPS); //comment line/change paramater to change SPS
....
....
....
....
....
....
....
....
adc.startSingleMeasurement(); // conversion starts
}

void loop() {
float voltage = 0.0;
static int counter = 0;
counter++;
if(counter==32){  // counter is 32, conversion rate is 8 SPS --> 4s
Serial.print("Channel 0 vs GND [V]: ");
Serial.println(voltage);
Serial.println("-------------------------------");
counter = 0;
}
}
}

void convReadyAlert(){  // Interrupt Service Routine (ISR)
}

### Example sketch 5: Alert_Window_Mode.ino

Now we come to a very helpful function of the ADS1115, namely the alert if a limit value being exceeded. This allows you to monitor voltages very conveniently. Please note there is only one register for the upper threshold and one for the lower threshold. If you want to set individual limits for different channels you will have to change the thresholds with every change of channel.

The alert pin asserts if you exceed the limits set by you. The conditions under which the ADS1115 clears the assertion of the alert pin again depends on further settings.

The following new function is relevant:

• setAlertModeAndLimit():
• Sets the mode:
• With ADS1115_WINDOW, you define a window with maximum and minimum. If values outside the limits are detected, the alert pin asserts. If the ADS1115 then determines values within the limit, the alert pin assertion is cleared – unless latch is active.
• With ADS1115_MAX_LIMIT, the alert pin only asserts if the maximum is exceeded. The minimum is the value the assertion ends – unless latch is active.
• In addition to the mode, the function also passes the limits in volts.

You can find out what latch is all about in example sketch 6. In the current example, the alert pin assertion is automatically cleared when the above conditions occur.

Note: with assert / clearing assertion I mean “alert on” / alert off”. The terms “assert” is taken from the technical data sheet. Since I am not a native English speaker, I am not sure if these terms are in common usage.

....
....
int ledPin = 10; // eine LED, die einen Alarm anzeigt
volatile bool outOfLimit = false;
....
....

void setup() {
....
pinMode(interruptPin, INPUT_PULLUP);
pinMode(ledPin, OUTPUT);
digitalWrite(ledPin, LOW);
....
....
....
....
....
....
adc.setMeasureMode(ADS1115_CONTINUOUS); // es macht Sinn eine Überwachung im Continous Modus laufen zu lassen
....
....
....
....
....
}

void loop() {
float voltage = 0.0;
if(outOfLimit){
Serial.print("Voltage [V]: ");
Serial.println(voltage);
digitalWrite(ledPin,HIGH);
delay(1000);
digitalWrite(ledPin,LOW);
outOfLimit = false;
}
}

detachInterrupt(2);
outOfLimit = true;
}

### Example sketch 6: Alert_Window_Mode_with_Latch.ino

This sketch basically does the same as Alert_Window_Mode.ino, except that latch is used. Latch prevents the alert pin from automatically clearing assertion again.

The following new functions are relevant:

• setAlertLatch() activates or deactivates latch.
• clearAlert() clears assertion of the alert pin. Latch is active when the next reading is outside the limits. As an alternative to clearAlert() you can use getResult_V() or getResult_mV().
....
volatile int interruptPin = 2;
int ledPin = 10;
volatile bool outOfLimit = false;
....
....
void setup() {
....
....
....
....
....
....
....
....
....
....
....
....
}

void loop() {
float voltage = 0.0;
if(outOfLimit){
Serial.print("Voltage [V]: ");
Serial.println(voltage);
digitalWrite(ledPin,HIGH);
delay(1000);
digitalWrite(ledPin,LOW);
outOfLimit = false;
adc.clearAlert(); // alert is deasserted, alternatively you coud call getResult_V() / getResult_mV
}
}

detachInterrupt(2);
outOfLimit = true;
}

After the output of the exceeded limit value and the warning light of the LED, first the interrupt is switched on again. Then the assertion of the alert pin is cleared with clearAlert(), i.e. the pin goes HIGH. If the out-of-limit condition still exists, the next interrupt is triggered immediately. As a result, the LED lights up almost continuously.

The latch provides more control, but you have to be careful with the timing. If you switch on the interrupt function after you have deactivated the alert, then the alert pin may be low again and a “falling” will not occur anymore.

### Example sketch 7: Auto_Range.ino

In this example sketch I introduce the functions setAutoRange() and setPermanentAutoRangeMode(). As the name suggests, they automatically choose the range. To do this setAutoRange() does the following:

• If the ADS1115 is in single-shot mode, it will change to continuous mode.
• The maximum range will be applied (+/- 6.144 volts).
• A conversion is performed.
• The smallest range in which the measured value is still below 80% of the maximum of the range will be selected. I have chosen 80% to still have some buffer in case of fluctuations. Otherwise, overflows could happen.
• If the ADS1115 had been in single-shot mode before, it will change back again.

Using setAutoRange() only makes sense if you expect stable or slow changing voltages. The function requires the time equivalent for multiple conversions. If your conversion rate is 8 SPS the time needed to set the auto range is quite significant.

setPermanentAutoRangeMode() has to be called once only. If enabled, all measured values will be checked if they are in a range of 30 – 80 % of the maximum of the current voltage range. Only if the values are outside setAutoRange() will be called. Therefore, this method is faster. Use either setPermanentAutoRangeMode() OR setAutoRange()

To show that the change of ranges does work the example sketch outputs the ranges for each value.

....
adc.setMeasureMode(ADS1115_CONTINUOUS); //comment line/change parameter to change mode
....
....
void loop() {
float voltage = 0.0;

Serial.print("Channel 0 - ");
Serial.println(voltage);

Serial.print("Channel 1 - ");
Serial.println(voltage);
....
....
Serial.println("-------------------------------");
delay(1000);
}

float voltage = 0.0;
printVoltageRange(); // this is just to show that the range is changing with changing voltages
voltage = adc.getResult_V(); // alternative: getResult_mV for Millivolt
return voltage;
}

void printVoltageRange(){
unsigned int voltageRange = adc.getVoltageRange_mV();
Serial.print("Range: ");

switch(voltageRange){
case 6144:
Serial.print("+/- 6144 mV, Voltage [mV]: ");
break;
case 4096:
Serial.print("+/- 4096 mV, Voltage [mV]: ");
break;
case 2048:
Serial.print("+/- 2048 mV, Voltage [mV]: ");
break;
case 1024:
Serial.print("+/- 1024 mV, Voltage [mV]: ");
break;
case 512:
Serial.print("+/- 512 mV, Voltage [mV]: ");
break;
case 256:
Serial.print("+/- 256 mV, Voltage [mV]: ");
break;
default:
Serial.println("Something went wrong");
}
}

#### Output of Auto_Range.ino

The output on the serial monitor could look like this:

### Beispielsketch 8: Result_Format_Options

In this sketch I introduce the different options to output the measured values:

• getResult_mV() returns the result in millivolts.
• getResult_V() provides the result in volts.
• getRawResult() returns the raw value from the conversion register as it is.
• With getResultWith Range(x,y) the raw value is scaled to the range from x to y. For example, if you select (-1023, 1023), you will get your result back as a 10 bit value.
• If you call the last function with a third parameter in the form getResultWithRange(x,y,z), the result is additionally scaled to a voltage range. The parameter z is in millivolts. Calling the function with (-1023, 1023, 5000) for example would give similar values as you would get with an Arduino UNO (in default settings).
 ....
adc.setVoltageRange_mV(ADS1115_RANGE_6144); //comment line/change parameter to change range
adc.setCompareChannels(ADS1115_COMP_0_GND); //comment line/change parameter to change channel
adc.setMeasureMode(ADS1115_CONTINUOUS); //comment line/change parameter to change mode
....
....

void loop() {

float voltageInMillivolt = adc.getResult_mV();
Serial.print("Result in Millivolt          [mV]: ");
Serial.println(voltageInMillivolt);

float voltageInVolt = adc.getResult_V();
Serial.print("Result in Volt                [V]: ");
Serial.println(voltageInVolt);

int rawResult = adc.getRawResult();
Serial.print("Raw Result                       : ");
Serial.println(rawResult);

int scaledResult = adc.getResultWithRange(-1023, 1023);
Serial.print("Scaled result                    : ");
Serial.println(scaledResult);

int scaledResultWithMaxVoltage = adc.getResultWithRange(-1023, 1023, 5000);
Serial.print("Scaled result with voltage scale : ");
Serial.println(scaledResultWithMaxVoltage);

unsigned int voltRange = adc.getVoltageRange_mV();
Serial.print("Voltage Range of ADS1115     [mV]: ");
Serial.println(voltRange);

Serial.println("-------------------------------");
delay(2000);
}

#### Output of Result_Format_Options.ino

And this is how an output of the Result_Format_Option.ino sketch looks like:

### All functions at a glance

Here I have summarized all the functions. You will also find it on GitHub.

I have not mentioned one function yet, namely reset(). It uses the global I2C reset (0x06). All I2C devices that are on the same communication line and listen to the global commands (general calls) perform a reset when the function is called.

## Deeper insights into the ADS1115

### Register map

The ADS1115 has a relatively small number of registers. All settings – with one exception – are made in the configuration register (config register). I’ll go into that in a little bit.

The results of the conversions are retrieved from the conversion register. There is only one for all four channels. Therefore, you always have to select the channel first and then wait for a result for this channel. The LSB for the conversion register is:

LSB = \frac{Range}{2^{15}}

If you have defined the range, as I did, in millivolts, you then calculate the voltage in volts as follows:

U \text{[V]} = \frac{LSB \cdot ConvRegister}{1000}

Because the conversion register is signed, it can take values from +215 to -215.

Reading the conversion register deletes a limit alert unless latch is enabled.

In the two limit registers (Lo_thresh and Hi_thesh) you enter the limit values if you want to use the alert function. The ADS1115 compares the content of the limit registers with the content of the conversion register. If you change the range, you must also convert the content of the limit registers. My library does this automatically. As a result, my function setVoltageRange() has become somewhat complex.

The only setting you make outside the configuration register (excluding the limits, of course) is to set the conversion ready alert. To activate this function, you need to write a “1” in the MSB of the Hi_thresh register and a “0” in the MSB of the Lo_thresh register. Since the limit registers are signed, the upper limit is negative and the lower one is positive. This condition, meaningless in itself, instructs the ADS1115 to assert the alert pin when a conversion is complete. The library provides the function setAlertPinToConversionReady() for this purpose.

### The configuration register

The configuration register contains the following bits:

• COMP_QUE [1:0]: determines how many “out-of-range” measurements of the ADS1115 assert the alert pin. Alternatively, the bits switch off the alert function. The associated function is setAlertPinMode().
• COMP_LAT activates or deactivates latch. If latch is activated, you must manually clear the assertion of the alert pin. The COMP_LAT bit is set or deleted with setAlertLatch().
• COMP_POL determines the polarity of the alert pin -> setAlertPol().
• COMP_MODE determines the alert mode. Either you define an upper limit or a window. The associated function setAlertModeAndLimit() determines the mode and sets the limits.
• DR [2:0] determines the conversion rate -> setConvRate().
• MODE: sets the continuous or single-shot mode -> setMeasureMode().
• PGA [2:0] sets the range -> setVoltageRange_mV();
• MUX [2:0] selects the channel or channels to compare – > setCompareChannels().
• OS has a double meaning. When writing, a “1” in single-shot mode triggers a measurement – > startSingleMeasurement(). When reading, a “0” means that a conversion is ongoing, and a “1” means the opposite. The function for this is isBusy().

## 49 thoughts on “ADS1115 – A/D converter with amplifier”

1. Dear Wolfgang,

I am impressed with your work, it is very useful, it is very complete and it is well explained.
I have a question about the delay times between samples when using continuous mode and the highest sample rate of 860 sps.
When I measure the times between samples of two channels a0 and a1. The difference is about 6ms. I expected a maximum of 4 ms considering that the channel is changed and you introduce a delay of 2 ms so as not to mistakenly read values that mix the two channels.
So, reading your code, I have seen that in ads1115_we.cpp on line 241 and 242 there is a double delay(rate).
Could this be a bug in the code?

Thank you very much
Tomeu Estrany
desde
Majorca SPAIN

PS: I hope that if you ever visit Mallorca you contact me, best regards

code between the loop
….
….
gettimeofday(&ts, NULL);
sec = (long)ts.tv_sec;
micrs = (unsigned long)ts.tv_usec;
long milis0 = round(micrs/1000) + sec * 1000; // en milisegons

gettimeofday(&ts, NULL);
sec = (long)ts.tv_sec;
micrs = (unsigned long)ts.tv_usec;
long milis1 = round(micrs/1000) + sec * 1000; // en milisegons
…..
…..

printed results
milis0 milis1 –a0– –a1–
4:55:34.112 -> 37002 37008 2.2574 2.2192
4:55:34.149 -> 37016 37022 2.2977 2.2234
4:55:34.149 -> 37030 37036 2.1552 2.2387
4:55:34.191 -> 37044 37050 2.2422 2.1776
4:55:34.191 -> 37058 37064 2.2988 2.2210
4:55:34.191 -> 37073 37079 2.2347 2.2302

1. Hi Tomreau,
I have tried a lot to find the correct delay time. As I have written in the article: “If you have changed the channel, it takes about the time for two conversions to get a new value.” Since one delay is one conversion time I put in two. You can easily try and take out one delay. Then apply different voltages to the channels and see what happens when you change channels. I am happy to review if you can reduce the delay time.
Best wishes, Wolfgang

PS: I spent some holiday in the northern area of Mallorca a few years ago. It is such a beautiful place. Hope I have the chance to go there again.

2. Hi Ewald,
Thanks for the elaborate explanation. Great work. I’m currently building a project that requires precise current sensing, 50mA. In terms of the ADS1115 16bit resolution, what minimum current can I precisely measure with the ADS1115?
Thanks.

1. Hi Zakari, the ADS1115 is an ADC and therfore it mesures voltages and not currents. For measuring something like 50 mA I recomment the INA219 or the INA 226:

Alterntively, you can build a current sensor yourself using an ADS1115:
https://wolles-elektronikkiste.de/en/current-sensor-how-to-build-it-yourself

The limit depends on the shunt (resistor to measure the current) you apply. The bigger the shunt the more sensitive, but the shunt also influnces the current. This becomes clearer if you read the post.

Good luck!
Best wishes, Wolfgang

3. Hello Wolle !
I recently discovered your site, and I am reading your blogs with big interest.
I want to thank you for sharing your knowledge in such a great way !
I use an Arduino sketch to monitor the voltage of my mobilhome battery.
The Arduino texts me the voltage (and cabin temp) whenever I call, or when things
go below a certain treshold.
All works fine, but I wanted more accuracy in the voltage reading, so I thought of
using the more accurate 16-bit A/D converter ADS1115.
Experimenting on the breadboard, I set up a circuit using your one-shot reading sketch.
Again, all works fine, but I notice a voltage drop on my multimeter, the moment I
In real figures : I have a (stable) DC source (11.9V) (shared ground with the circuit),
a voltage divider (2 x 1.5K) (since I am on the 6V gain) which brings me to a 5.95V reading on my multimeter.
As I connect to the ADS1115, the reading on my m.m. drops to 5.75V, the serial
from the ADS1115 gives me about 5.55V.
I’ve noticed, the lower the measuring voltage, the smaller the deviation.
I’m sure there’s an explanation for this, and I am even more sure you have it 🙂
Thank you for taking time and maybe help me out !
From Belgium,
Ronald.

1. Hi Ronald,
first thanks for your kind feedback! I am not sure if I have the full answer to your question. What you should consider is the maximum supply voltage for the ADS1115 which is 5.5 volts and that the voltage at the inputs A0 to A3 shall not exceed VDD + 0.3 volts. According to the data sheet this can destroy the ADS1115. That also means you can’t go beyond 5.8 volts. And I am not sure that you will measure meaningful data in the area of 5.5 – 5.8 volts at a supply voltage of 5.5 volts. What kind of power supply do you use for the ADS1115? Do you take the 5 volts from the Arduino board? What I have noticed is that this supply is not very stable. Even if you supply a 10 mA LED with it the voltage will go down a bit – which effects the maximum voltage you can measure. Maybe it’s getting betting with a stable power supply for the ADS1115.
I hope this helps for the moment. Happy to hear from you.
Best wishes, Wolle

1. Thank you Wolle, for your reply.
I am certainly going to follow your hint, like considering the power supply of the
ADS1115, which is in fact fed by the Arduino now, which in turn hangs on the PC-usb.
I will also recalculate the voltage divider, bring down the measuring voltage to
maybe 2.5V range, and set the gain to the default value.
This would not influence accuracy much, would it ?
Thanks again, also for the post on the SIM800L, because that is what I am using
now, bit I am sure I can get a even better understanding after reading through
this (like all your) interesting post.
I am so glad I’ve found your site !
Ronald

4. This might be basic, but could you tell me the difference between the Single shot mode and the Continuous mode? I’m thinking of making my stuff so that it returns a measurement result once when the reset button is pressed. In this case, which mode is more appropriate?

1. In continuous mode the ADS1115 will continuously do conversions. This means there’s always data you can immediately query. Like buying food in supermarkets – it’s ready on the shelf. In single shot mode the conversion is only made on demand. Like ordering a meal in the restaurant. Depending on the parameters it takes few to several hundred milliseconds. The advantage is energy saving. So I think for your application single shot is fine.

1. Very clear analogy! Thanks for the reply!

One more thing I want to check is the function of “Set the inputs to be compared”.
In the sample code above, even if you don’t specify whether single-ended mode or differential mode is selected in void setup(), multiple single-ended measurements are performed in the loop. If this is the case, is it not necessary to specify a measurement method you want to employ in void setup()?
For example, if I want to measure both ADS1115_COMP_0_1 and ADS1115_COMP_2_3 in the same loop, what should I do?

1. The ADS1115 has only one A/D converter. So, if you want to read certain channels, e.g. 0 vs GND, 0 vs 1, or whatever, then you just have to change the compare channel and then read the voltage.

Let’s take your example:
You set the compare channel 0 vs 1:
float voltage_0_1 = adc.getResult_V();
Then you change the compare channels:
float voltage_2_3 = adc.getResult_V();

Hope this helps.

1. Thanks, I got reasonable results with the code below, is there anything I should fix?
Only the parts that may need to be changed are excerpted,

void setup() {

}
void loop() {
float voltage_0_1 = 0.0;
float voltage_2_3 = 0.0;

Serial.println(voltage);
Serial.println(voltage);

delay(1000);
}

float voltage = 0.0;
return voltage;
}

1. Looks good – but you can delete the setCompareChannels fuctions in the setup. You define them later anyway.

5. Does your library supports differential measurements, eg a measurement between A0 and A1?

1. Yes, you choose the channels with the setCompareChannels() function. The available options are listed in the comments of the example sketches.

6. Hi Wolfgang, greetings from South Africa. I am very impressed with your library but now i need to add additional ADC’s and i have not been able to address the second namely 0x49 with an Arduino Mega. I have it on the scanner (0x48 and 0x49) Do you have an example code for the addressing i have not been able to find one.
Your input would be greatly appreciated. Thanking you.

1. Hi James,

in the latest release of my library you find an example sketch, called “Using_two_ADS1115.ino”. Since you find both ADS1115 with the scanner you seem to have connected everything correctly. Therefore the example should work as is – good luck.

Greetings from Germany to South Africa! Best wishes, Wolfgang

1. Hi Wolfgang. Thank you for your speedy reply. I found it, i had version 1.3.6 installed. The functionality and detailed operation of your library is outstanding. Your lib surpasses others. Thank you once again.
Best regards. James.

7. Hi Wolfgang – I like your coding and will leverage off of it. Thank you for sharing your good work! I was reviewing ADS1115_WE.h and notice in the comments, you have decimal points, making the full scale voltage values 1000x smaller.

* voltage range. E.g. if the voltage range is 6144 mV (ADS1115_RANGE_6144),
* +32767 is 6144 mV; if the range is 4096 mV, +32767 is 4096 mV, and so on.
*/
int16_t getRawResult();

/* Scaling of the result to a different range:
* The results in the conversion register are in a range of -32767 to +32767
* You might want to receive the result in a different scale, e.g. -1023 to 1023.
* For -1023 to 1023, and if you have chosen e.g. ADS1115_RANGE_4096, 0 Volt would
* give 0 as result and 4096 mV would give 1023. -4096 mV would give -1023.

1. Hi Robert, thanks for the feedback and this hint. The problem is that in Germany and some other European countries the use of “,” and “.” is exactly opposite to other parts of the world. One thousand with one decimal is in Germany 1.000,0 where other countries would write 1,000.0. I will take the points out completely from the comments.

8. Thanks Ewald for sharing. I don’t understand two things:
1- The ASD1115 can measure negative voltages (under ground level) if I conect it directly? Or the signal must be conditioned before to be all positive?
2-If the voltage to be measure is only positive, is any way to take advantage of the sign bit? I mean to get a fully 16 bits resolution, from zero to max voltage level.

1. Hi Richard, you can measure negative voltages directly which is an advantage versus ADCs of microcontroller. However this limits the resolution if you only use the positive range. The resolution is +/-2^15 bit. There may be ways to somehow modify your signal with some electronic parts (gain factor 2 and shift) but I assume that would be difficult to do with high precision.

1. Thanks Ewal, I was confused about the ranges and the microcontroller specs that use a common point to mesure negative voltage, but how you said, it has all to do with the implementation of the module itself. Excellent web, thank for sharing!

9. Wolfgang Ewald

My Last Comment after checking was not correct there is a fault some where as the continuous mode is the only place for the Alert pin to be used for when the voltage is in Range or Window mode.

In the single shot mode it works after the Data ready is used once.. It will flash the alert and reset at the same time and then maybe not. Its random and this happens after the 4th reading as requested however single shot may not trip to the forth as the unit goes to sleep. The internal counter trips on forth single shot. However that being said it zeros out or may stay on. It is not stable or dependable.

Only in continuous mode mode does it work best to indicate that there is alarm at too low or too high voltage. Its rock solid predictable and dependable.

So as it stands I can use a NANO Arduino as simple as it is to decided on the mode based on users settings. Do a reset and reconfigure the ADS on the fly. It is really a simple call.

When battery is in work mode or rest mode after it is charged and sitting with no draw or charge input since both of these conditions are detectable.

***** Latch Must be disabled for this to work. *****/
#define LOWVOLT 2.0
#define UPPERVOLT 3.6
// This code is in the setup of the Arduino and ADS

adc1.setVoltageRange_mV(ADS1115_RANGE_4096); //comment line to change parameter to change range Voltage
//will never be over 4.096 volts aka will not be capped out.

adc1.setConvRate(GetComandConvRate(mCommand)); // My sub call based on my Binary dipswitch selection.

// Set Alert HIGH so that as soon as the window range (anything above UPPERVOLT or below LOWVOLT)

//Disable the latch so that window sets the alert at the time of the sample Immediately.

//At this point one can use the following choice knowing the state and the dependability of the Alert/Ready
//Last step in the setup of the ADS
if(bmContinuous == true){
// Slower samples rates are best here so as not to consume as much power.
// Serial.println(“InContinuous Mode”);
}else{
// Does not react to the Alert and only to the “Data Ready pin status”. (unstable Alert in this mode)
// Regardless of the Alert tripping to high to low or low to high, rising or dropping edge, it never happens unless
//the Data is indeed Ready! This is the only power saving mode and consumes great deal of less power.
//Fastest “sample rate” aka “ConvRate” works best in this mode. sooner the sample is over with in the second it
//is active, till the next call seconds or minutes later.
// This is all noted in the Chips manufacture Datasheet and as you have stated.
}

1. It is a bit difficult to reproduce this without the complete code and your circuit. I have just tried the Window Alert Mode with the single shot mode and it worked as expected. Working with interrupts can be quite tricky. I prefer to work with enabled latch to have better control when the interrupt is cleared on the ADS1115 side. Don’t know if this would help. And I always detach the interrupt on the MCU side until the actions I have defined as a reaction to the interrupt are completed. Not sure if this helps.

10. Just love your notes and Yes in Single snap read mode. “startSingleMeasurement” the one thing I tried was indeed the Alert pin.. Now the ADC1115 can go into sleep mode and not use much power.

Yes it also works in continuous mode as well.

/***** Latch Must be disabled for this to work. *****/
#define LOWVOLT 2.0
#define UPPERVOLT 3.6

adc.setConvRate(ADS1115_64_SPS); /// Really does not matter here slower faster makes no difference.
adc.setAlertPinMode(ADS1115_ASSERT_AFTER_4); // This needs to be set so it knows if it should or not and when.
adc.setAlertLatch(ADS1115_LATCH_DISABLED); // Disable this if you wish to have the alert pin let you if one is with in the window of value.

With these settings the alert pin will go HIGH only when the voltage is above or below the upper and lower window values.

If one sets the ADS1115_ACT_LOW then Alerts is High while voltage value is between the Upper and lower limits

However thanks for the code and its working great.

11. hello my name is Andrews, I’m from Brazil, so my English is bad(sprry).

I’m trying to use the ADC1115, with an OLED display SH1106 (u8g2 library), and when I take the data and convert it, the display keeps locking and sometimes the ESP32 even restarts.

can you tell me what causes this ?, will i2c interference? I use esp32 with multi tasks, congratulations to the project, and thank you.

1. Hi Andrews, great to hear my library found its way to Brazil! Does the ADS1115 work with the ESP32 if you don’t connect the SH1106? The SH1106 also runs with I2C? Does it have a different address? Can you run an I2C scanner sketch, e.g.:
https://wolles-elektronikkiste.de/en/i2c-scanner?lang=en
Do you see the two I2C addresses?
And when the ESP32 restarts, what is the output on the serial monitor? Can you send a screenshot to wolfgang.ewald@wolles-elektronikkiste.de ?
Is the power supply sufficient?
Just first ideas….

1. Hello first thanks for the quick answer.

I did some tests and got the following results:

OK power supply
I2C address OK(display on 0x3C and 1115 on 0x4B)

Sh1106 (without ads1115) OK

Ads1115 (serial monitor test) Works

When I use the u8g2 library to control the display the GetRawResult is = 0

clearly an interference of the display library with that of the Ads1115.

I would like to try to use the second ESP32 I2c bus with Ads1115.

does your library support this? what would have to be done? Use Wire1.begin?

Vielen Dank!

1. I am not too familiar with the ESP32, but I think you need to setup two I2C busses like this:

#define SDA1 21
#define SCL1 22
#define SDA2 17
#define SCL2 16

TwoWire I2Cone = TwoWire(0);
TwoWire I2Ctwo = TwoWire(1);

void setup(){
I2Cone.begin(SDA1,SCL1,400000);
I2Ctwo.begin(SDA2,SCL2,400000);
}

For this I have to rewrite my library, sorry.

1. I have “played” a little bit with my library. I’ll send you a modified version. Don’t know if it works. Maybe you want to try?

12. Looking at the raw output of the ADS1115 it strikes me that the output always is a plural of 16 (13520, 13504, etc.).
Is this to be expected or is my ADS1115 defective? Changing the gain only increases this.

1. Thanks for the link. That looks very much like my results with a device with markings 92 BOGI TI

Looking a bit further, the datasheet of the ADS1015 (12 bits) shows that the architecture of the registers of the 1115 and the 1015 are identical, except for the lower 4 bits of conversion register which for the 1015 are always 0, giving exactly the result hoffmakl1961 describes and that i experienced.
That might suggest that there are ADS1115 devices circulating with an incorrect device marking?

Interesting findings; I’ll find myself another device …

1. There are counterfeit ADS1115’s on the market! They are 12 bit versions, marked as ADS1115. I bought some modules from Amazon and found this issue. Pay more, buy on Adafruit, and get 16-bits!

13. Hello

In many of your examples pair of commands
is used in for cycle
is there a reason to make multiple measurements before read a result ?

1. Hello, this is just to demonstrate the effect of measuring in conversion ready controlled mode. The slowest data rate is eight samples per second. This is still quite fast if you display it on the serial monitor. So it’s really only to slow down the output rate. It has now other effect. Only the current data in the conversion register will be displayed. Maybe I should have made this point clearer.

1. First, you need to change the channel with

Then you request the results with the function you prefer. That could be adc.getResult_V(), for example. There is only one issue: if you request data directly after the change of channel, then it might be still data from the former channels. In trigger mode you can use while(adc.isBusy()){} to wait for the next conversion. This software check doesn’t work in the continuous mode. You have two options to avoid that:

1) insert a delay after the change of channel. It needs to be adjusted to the conversion rate. If you use ADS1115_8_SPS, e.g. you would need 125 milliseconds to be on the safe side.

2) Use the alert pin for conversion ready check. This hardware check works. To do so:

Then you can use the alert pin status to check if the current conversion is ready.

14. When talking about the continuous mode, you say ……”By the way, the conversion ready alert does not work in continuous mode.”

The data sheet says this …….(9.6.4) “When set to RDY mode, the ALERT/RDY pin outputs the OS bit when in single-shot mode, and provides a continuous-conversion ready pulse when in continuous-conversion mode”

This indicates it DOES work.
Have you found this not to be true, or is it that your library doesn’t support this feature?

1. Two good news for you: Firstly, you are right, it does work (of course hard to admit for me 😉 ). Secondly, it works with my library.

I mixed up to things here. What really does not work in continuous mode is to check whether the conversion is ready via the OS Bit. I use this in single shot mode with the isBusy() function. When you try the fucntion in continuous mode, it seems the ADS1115 is constantly busy. Somehow I draw the conclusion that the alert pin would also not work. But indeed it does work. Fortunately it does not have to be implemented. It works with the available functions.

So if you want to use it just take the continuous.ino example sketch and: