Noise when using ADS1115

I'm using an ADS1115 to measure the characteristaics of a three terminal device. In this example a 4N35.

This is the circuit


A0 measures Vcc
A1 measures i1 using A0-A1 differential
A2 measures V1
A3 measures V2 and A0 - A3 measures i2
I cant use differential mode because it only works on A0-A1 or A2-A3.

Annoyingly, I'm seeing a lot of noise on the reading for i2 - which I believe is indicative of noise on the GND-Vcc supply; but I've put an RC filter on with little if any improvement.

I've considered the layout; Vdd, A0, C2+ R5 and R6 are all on the same track.
The noise on the i2 measurement is still present if T2 of the DUT is disconnected.

Example data: the 0.019mV on iDUT is 1 lsb so ignore. The 0.356mV on iT3 is 19 lsb.

nOut vDUT iDUT iT3 vT3
0 22.7 0 0 5031.7
2 22.7 0 0 5030.1
4 36.4 0 0.356 5027.2
6 55.5 0 0 5027.2
8 75 0 0 5028
10 95.1 0.019 0 5029.7
12 114.9 0.019 0.356 5025.6
14 135.2 0 0 5026.5
16 155.6 0 0 5027.1
18 175.9 0 0.056 5027.2
20 196.1 0 0 5029.5
22 216.4 0.019 0.056 5028.7
24 236.4 0 0.038 5026.1

Here is the code (sorry there's a lot of commented out)

#include <Adafruit_ADS1X15.h>
//modified from example because Micro Pro uses different pins for scl & sda
// The library has 'getDataRate' and 'setDataRate' commands. Use them to check the sampling rate and set another.

/*
*** Micro Pro and ADS1115 used for testing two (2) terminal or three (3) terminal devices.
*** generates a voltage using a fast PWM wave at 31kHz and RC filter
*** buffers the voltage with an op amp and emitter follower
*** measures device voltage and device current
*** see diagrams rropamptest ads1115.dsn && devtest23ADS1115.dsn tinyCAD
*** skillbank March 2023
*/

Adafruit_ADS1115 ads; /* Use this for the 16-bit version */
//Adafruit_ADS1015 ads;     /* Use this for the 12-bit version */

#define RATE_ADS1115_8SPS (0x0000)    ///< 8 samples per second
#define RATE_ADS1115_16SPS (0x0020)   ///< 16 samples per second
#define RATE_ADS1115_32SPS (0x0040)   ///< 32 samples per second
#define RATE_ADS1115_64SPS (0x0060)   ///< 64 samples per second
#define RATE_ADS1115_128SPS (0x0080)  ///< 128 samples per second (default)
#define RATE_ADS1115_250SPS (0x00A0)  ///< 250 samples per second
#define RATE_ADS1115_475SPS (0x00C0)  ///< 475 samples per second
#define RATE_ADS1115_860SPS (0x00E0)  ///< 860 samples per second

//SDA to D2 and SCL to D3; ALERT to D4; ADDR not connected
// Pin connected to the ALERT/RDY signal for new sample notification.
constexpr int READY_PIN = 4;


//state indicator
byte LED = 5;    //LED on pin 5
bool ON = HIGH;  //LED is ON when pin is HIGH
bool OFF = LOW;


//generating PWM signal
constexpr byte vOutPin = 9;  // using pin 9 for pwm out
const int divider = 1;  //PWM: for pin 9 divider=1 sets pwm freq at 31,250Hz; = 8 sets it to 3,906Hz
//with emitter follower compliance runs out at 4400mV approx
int nMax = 225;     // value is the duty cycle of the pwm ie 255 = always 1, 0 = always off.  2v5=128
int nMin = 000;     //0 to start at 0V
int stepSize = 2;   //each step is an increment of about 20mV at DUT

int nOut;           //number to be output via PWM
int tSettle = 200;  //msec to allow PWM to stabilise

//calculating expected output voltage at filter
float mVoltsOut;
//vCal calibrates the indication of output voltage in mV
//float vCal = 19.53;  // mV out = nOut * Vcc mV / 256  5000 /256 = 19.53
float vCal = 19.5;  //mV out - from EXCEL trend line
int vOffset = 12;   // mV out - from EXCEL trend line

//measuring current
int tInt = 4; //interval between readings
int rIsense = 10;  //value of current measuring resistor
int rIsense3 = 10;  //value of current measuring resistor for T3
float iDUT;        //current in device under test
float iT3;        //current through terminal 3 eg collector of transistor
float iDUTthreshold = 0.04; //current (mA) above which finer steps will be taken note 0.019 or 0.038 is noise level
float iT3threshold = 0.1; //current (mA) above which finer steps will be taken
//*** threshold - take fewer readings below threshold to reduce unnecessary data

int iLimit = 20;   //end test when iLimit is reached


void setPwmFrequency(int pin, int divisor);

// This is required on ESP32 to put the ISR in IRAM. Define as
// empty for other platforms. Be careful - other platforms may have
// other requirements.
#ifndef IRAM_ATTR
#define IRAM_ATTR
#endif

volatile bool new_data = false;
void IRAM_ATTR NewDataReadyISR() {
  new_data = true;
}

void setup(void) {
  Serial.begin(115200);
  pinMode(LED, OUTPUT);  //led & resistor on pin 5 to show test status
  Serial.println("Starting test on device");
  digitalWrite(LED, ON);       //LED on, pause before test starts
  analogWrite(vOutPin, nMin);  //generate minimum voltage for test
  delay(4000);                 //time to allow vout to settle to vMin, and start serial monitor (termite, needs cts/rts)
  Serial.println("Getting single-ended readings from AIN0..3");
  Serial.println("ADC Range: +/- 6.144V (1 bit = 3mV/ADS1015, 0.1875mV/ADS1115)");

  // The ADC input range (or gain) can be changed via the following
  // functions, but be careful never to exceed VDD +0.3V max, or to
  // exceed the upper and lower limits if you adjust the input range!
  // Setting these values incorrectly may destroy your ADC!
  //                                                                ADS1015  ADS1115
  //                                                                -------  -------
  ads.setGain(GAIN_TWOTHIRDS);  // 2/3x gain +/- 6.144V  1 bit = 3mV      0.1875mV (default)
  // ads.setGain(GAIN_ONE);        // 1x gain   +/- 4.096V  1 bit = 2mV      0.125mV
  // ads.setGain(GAIN_TWO);        // 2x gain   +/- 2.048V  1 bit = 1mV      0.0625mV
  // ads.setGain(GAIN_FOUR);       // 4x gain   +/- 1.024V  1 bit = 0.5mV    0.03125mV
  // ads.setGain(GAIN_EIGHT);      // 8x gain   +/- 0.512V  1 bit = 0.25mV   0.015625mV
  // ads.setGain(GAIN_SIXTEEN);    // 16x gain  +/- 0.256V  1 bit = 0.125mV  0.0078125mV

  if (!ads.begin()) {
    Serial.println("Failed to initialize ADS.");
    while (1)
      ;
  }
  ads.setDataRate(RATE_ADS1115_8SPS);
  //ads.setDataRate(RATE_ADS1115_64SPS ); //choose either
  setPwmFrequency(vOutPin, divider);
  Serial.println("Testing device with rail-rail op amp: generating vOut, measuring vIn");

  //turn off built in LEDs by setting them as inputs
  pinMode(LED_BUILTIN_TX, INPUT);
  pinMode(LED_BUILTIN_RX, INPUT);
}

void loop(void) {
  int16_t adc0, adc1, adc2, adc3;
  float multiplier = 0.1875F; /* ADS1115  @ +/- 6.144V gain (16-bit results) */
  //voltages need to be floats or we lose resolution on iDUT as v2-v1/rIsense
  //float volts0, volts1, volts2, volts3;
  float mVolts0, mVolts1, mVolts2, mVolts3;
  float vDiff, vDiff3;  //difference between two readings
  delay(500);   //time to allow vout to settle to zero and
  //Serial.println("nOut, mVoltsOut, vFilter, vDUT, vIsense, Vsupply, vDiff,  iDUT");
  //Serial.println("nOut, mVoltsOut, vDUT, iDUT, Vload, Iload");
  Serial.println("nOut, vDUT, iDUT, iT3, vT3");
  digitalWrite(LED, OFF);  //LED off, test in progress

  for (nOut = nMin; nOut <= nMax; nOut += stepSize) {
   // for (nOut = nMax;;){  //used for full scale calibration
    //for (nOut = 0;;){  //used for offset null
    //for (nOut = 64;;){  //used for freq measurement

    analogWrite(vOutPin, nOut);  //set output voltage; having set the pwm frequency the analog write obeys that
        delay(tSettle);

    //calculate expected output voltage at filter
    mVoltsOut = (nOut * vCal) + vOffset;

    // Serial.print(mVoltsOut, 1);
    //Serial.print(",   ");

    //take numeric readings from adc and convert to mV
    adc0 = ads.readADC_SingleEnded(0);  //supply voltage
    mVolts0 = adc0 * multiplier;
    delay(tInt);
    adc1 = ads.readADC_SingleEnded(1);
    if(adc1 > adc0) adc1 = adc0; //cant be higher voltage than supply
    mVolts1 = adc1 * multiplier;
    delay(tInt);
    adc2 = ads.readADC_SingleEnded(2); 
    mVolts2 = adc2 * multiplier;
    delay(tInt);
    adc3 = ads.readADC_SingleEnded(3);
    if(adc3 > adc0) adc3 = adc0; //cant be higher voltage than supply
    mVolts3 = adc3 * multiplier;
    delay(tInt);
    vDiff = ads.readADC_Differential_0_1();
    vDiff *= multiplier;  //calculate difference voltage in mV between supply and ISense
    //calculate current
    iDUT = vDiff / rIsense;  //

    vDiff3 = mVolts0 - mVolts3;
    iT3 = vDiff3 / rIsense3;


    //*** print results in .csv format:- only need vDUT vs iDUT to plot characteristic
    Serial.print(nOut);
    Serial.print(",  ");
    /*
    //Diagnostic - used while testing
    Serial.print("V supp:   ");
    Serial.print(mVolts0, 0);  //Vsupply
    Serial.print(",   ");

    Serial.print("V rIsense: ");
    Serial.print(mVolts1, 1);  //v DUT output
    Serial.print(",   ");
*/
    //Serial.print("vDUT:  ");
    Serial.print(mVolts2, 1);  //
    Serial.print(",   ");

    //Serial.print("iDUT:  ");
    Serial.print(iDUT, 3);  //current mA
    Serial.print(",   ");

    //Serial.print("iT3:  ");
    Serial.print(iT3, 3);  //T3 current mA
    Serial.print(",   ");

    //Serial.print("Vt3: ");
    Serial.print(mVolts3, 1);  //Vterminal2
    Serial.print(",   ");


    //Serial.print("adc0 raw:  ");
    Serial.print(adc0);  //
    Serial.print(",   ");

    //Serial.print("adc1 raw:  ");
    Serial.print(adc1);  //
    Serial.print(",   ");

    //Serial.print("adc2 raw:  ");
    Serial.print(adc2);  //
    Serial.print(",   ");

    //Serial.print("adc3 raw:  ");
    Serial.print(adc3);  //
    Serial.print(",   ");

  //  Serial.print("vDiff:  ");
  //  Serial.print(vDiff, 2);  //Vdifference
  //  Serial.print(",   ");


     Serial.println();  // end of one line of data
    //test if getting useful data
    if (iDUT > iDUTthreshold) {
    //  if ((iDUT > iDUTthreshold)||(iT3 > iT3threshold)) {
      stepSize = 1; 
    }

    if ((iDUT > iLimit)||(iT3 > iLimit)) {
      nOut = 0;
      Serial.println("current limit exceeded, terminating test");
      analogWrite(vOutPin, nOut);  //*** NB turn off voltage to device so as not to leave it stressed
      break;
    }
  }                         //finished test
  while (1) {               //flash LED to show test finished
    digitalWrite(LED, ON);  //LED on,
    delay(50);
    digitalWrite(LED, OFF);  //LED on,
    delay(1000);
  }
}

/**
   Divides a given PWM pin frequency by a divisor.

   The resulting frequency is equal to the base frequency divided by
   the given divisor:
     - Base frequencies:
        o The base frequency for pins 3, 9, 10, and 11 is 31250 Hz.
        o The base frequency for pins 5 and 6 is 62500 Hz.
     - Divisors:
        o The divisors available on pins 5, 6, 9 and 10 are: 1, 8, 64,
          256, and 1024.
        o The divisors available on pins 3 and 11 are: 1, 8, 32, 64,
          128, 256, and 1024.

   PWM frequencies are tied together in pairs of pins. If one in a
   pair is changed, the other is also changed to match:
     - Pins 5 and 6 are paired on timer0
     - Pins 9 and 10 are paired on timer1
     - Pins 3 and 11 are paired on timer2

   Note that this function will have side effects on anything else
   that uses timers:
     - Changes on pins 3, 5, 6, or 11 may cause the delay() and
       millis() functions to stop working. Other timing-related
       functions may also be affected.
     - Changes on pins 9 or 10 will cause the Servo library to function
       incorrectly.

   Thanks to macegr of the Arduino forums for his documentation of the
   PWM frequency divisors. His post can be viewed at:
     https://forum.arduino.cc/index.php?topic=16612#msg121031
*/
void setPwmFrequency(int pin, int divisor) {
  byte mode;
  if (pin == 5 || pin == 6 || pin == 9 || pin == 10) {
    switch (divisor) {
      case 1: mode = 0x01; break;
      case 8: mode = 0x02; break;
      case 64: mode = 0x03; break;
      case 256: mode = 0x04; break;
      case 1024: mode = 0x05; break;
      default: return;
    }
    if (pin == 5 || pin == 6) {
      TCCR0B = TCCR0B & 0b11111000 | mode;
    } else {
      TCCR1B = TCCR1B & 0b11111000 | mode;
    }
  }
}

What are the resistance of the resistors designated as '10E'

Hi Jim - its 10 ohm; in the absence of a multiplier (k, M, etc) E seems to be used frequently to denote a multiplier of 1. (as example below)
And getting an omega symbol ...
so A0,A1, (A2), A3 are all connected to +Vcc via low impedances.

Hi,
10 Ohm == 10R I always use.

Tom.... :smiley: :+1: :coffee: :australia:

1 Like

So it's either do to poor layout, which you already suspect and/or a noisy USB voltage.

I've looked at the supply at the ads1115 with my scope and I'm seeing about 20mV of very high frequency but thats also the noise level on the hantek 'scope.
Thats why I put the rc filter
However I'll try it on another PC and maybe powered from a laptop on battery.
With this much noise there's little benefit over using the Micro's adc.

I see that the Adafruit lib constantly polls the ADC to find out if its ready. So the SCK and SDA lines will be toggling while a conversion is in progress. This will certainly cause some noise on Vcc and may be coupled to the analog input traces.

powered from the laptop gave the same kind of results.
No correlation between sample number and error.
Analysing the measurements of Vcc (eg vT3 above) I get Vave = 5027mV and SD = 1.7mV
so an expected range of 3sd = 5mV.
Data from the laptop I get Vave = 5163mV and SD = 1.62mV
The current is measured with a 10ohm res - so 20mA = 200mV and 0.356mA = 3.56mV

I'm still suspicious of the readings, I cant believe I'm REALLY getting 5mv (pk) of noise when there is a 100uF cap sat across the supply pins.

I wonder if there is a way to mirror the T3 current so I can make a ground referenced measurement.

100%

A real capacitor can be modeled as an ideal cap in series with a resistor called the capacitor ESR (Equivalent Seies Resistance). How well it will work in a filter circuit depends on this ESR. Many of the cheap electrolytic caps can have high ESRs, maybe 1 or 2 ohms. So still seeing 5-10mV of noise is very possible.

Placing a few 1uF to 10uF Ceramic capacitors in parallel with the 100uF will help since ceramic caps have very low ESRs

1 Like

Thanks Jim I'd not considered that. I guess 0.5 ohm is realistic but given Xc is 2 ohm at 1kHz it changes the filter calcs. I've tried adding some ceramics but not a big enough value to affect things.

At 8sps there could be 125 - 500 msec so I'm needing to look for vlf changes in Vcc.
(Ive tried 240 sps its no better)

I think one part of the problem is that I'm measuring the current with a 10 ohm resistor connected to Vcc - so the max change is only 200mV.
However if I make R bigger the supply voltage to the device will drop.

I'm going to try a crude PNP current mirror to get a ground referenced change, and maybe a 20 ohm resistor.

"lifelong learning"

Further - a current mirror wont work as potentially 40mA * 5V = 200mW would cause too much temperature variation between the transistors.

I used my data logger to monitor Vcc (USB) : note 20mV of apparently random variation.

I think I'm going to have to accept the limitations of the circuit - but at least I know its not an issue with the ADS1115

Added a differential amplifier to increase the signal used for measuring i3.

I'm now getting much less noise on my readings

Did not read all of it, but are you aware that the mcp6002 is not fully rail2rail.
U1B is measuring high-side, but is that 10 ohm sense resistor, at least 0.3volt below VCC of the opamp.
Leo..

Thanks Leo; yes im sure true "rail-rail" at the output is a bit much to expect. However the only load on the output is the 100k feedback resistor and the input impedance of the ads1115.
On test I see a range of 0.000V at the output to + Vdd - 18mV

image

and as far as I have measured the input tuly is "rail-rail".

what I really found interesting is the instability of the usb voltage - even on a PC with a very good psu.

I was talking about the common mode range of the inputs.
Leo..

I couldnt believe this Leo, it says the cmrr exceeds the supply voltages by 300mV; so I tested it

image

a little bit of input offset and a gain error but no non-linearity near the rails!

gain error; Acl = Aol / 1+Aol and for this op amp Aol is typically around 100dB = 1 * 10^5
the expected max error could be Vdd * 1/1 * 10^5 = 50uV - doesnt explain it.

Ahh, read that wrong.
Thought that it had to be 0.3volt inside the supply boundaries.
Leo..

The gain error is a measurement error on the ADS1115 - beware it has a significant channel mismatch and gain error

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.