[Resolved]SCT-013-000V -> ADS1015 -> ESP8266 Ammeter schematic and code

Ive had quite a bit of help on here trying to get things sorted out with my project. First, I would like to thank those that have helped.

I wanted to make this post for 2 reasons: 1 to help anyone who is doing the same thing I am, and 2 to help me get this working correctly.

I am trying to create an Ammeter with 2 sensors, one on each hot leg (120v/60Hz) (240v US) and want to calculate current amperage

first, the schematic for the ads:

the ADS to the ESP8266 works fine. I can load up code to search the i2c interface and it responds with the correct address

I have 2x SCT-013-000V 100A/1V CTs hooked up to J3 and J2.

if i use a voltmeter, i get 0.3mV across J3-1 and J3-2. there is a very small load on the mains that I am sensing (12v adapter running the circuitboard the ESP is on.

here is my code:

#include <Adafruit_ADS1015.h> //https://github.com/soligen2010/Adafruit_ADS1X15 Fork of Adafruit library
unsigned int DEBUG = 1;
Adafruit_ADS1015 ads;

void setup() {
  Serial.begin(115200);
  Serial1.begin(115200);
  // put your setup code here, to run once:
  ads.setGain(GAIN_TWO);
  ads.begin(5, 4);
  ads.setSPS(ADS1015_DR_1600SPS);
}

void loop() {
  // put your main code here, to run repeatedly:
  float results0_1, results2_3;

  if (DEBUG) {
    results0_1 = ads.readADC_Differential_0_1_V();
    Serial1.print("Differential 0-1: ");
    Serial1.println(results0_1, 7);
    results2_3 = ads.readADC_Differential_2_3_V();
    Serial1.print("Differential 2-3: ");
    Serial1.println(results2_3, 7);
  }
  delay(500);
}

and here is what it outputs:

Differential 2-3: -0.0010000
Differential 0-1: -0.0010000
Differential 2-3: -0.0010000
Differential 0-1: -0.0010000

ive tried reversing the way the sensors are plugged in, but that doesn’t change anything. I also plugged in a 300W light, and the output is still the same. I have verified the senors are correctly oriented. does anyone have any ideas?

DC can usually be measured with a single measurement.
AC needs sampling for some time to find the peak/peak value of the sine wave.
Time to read up on that before thinking of coding.
Start with this.
Leo..

I’ve seen that article a few times. I guess something isnt clicking for me.

If I do the sampling with no delay, all my figures are exactly the same. Will a while loop go much faster that an unbridled main loop?

Is the 1V off the CT AC? Does that matter?

Yes, a current transformer transforms AC current through it into AC voltage on it's outputs.
So you must read one differential pair as fast as possible for a set time (one second?),
and store min and max in two values.
Then subtract those values to find the differential A/D value, which you then can convert into current.
Leo..

This could get you started.
Leo…

  int readValue; // value read from the sensor
  int maxValue = 0; // reset max value
  int minValue = 0; // reset min value
  unsigned long start_time = millis(); // mark start time
  while ((millis() - start_time) < 1000) { // sample for one second
    readValue = ads.readADC_Differential_0_1_V(); // read
    if (readValue > maxValue) maxValue = readValue; // update max
    if (readValue < minValue) minValue = readValue; // update min
  }
  float current_0_1 = ((maxValue - minValue) * 0.12345); // calculate/calibrate/change multiplication factor

Thank you for that, i changed the ints to floats (the return value of the readDiff) and set it up for both legs.

#include <Adafruit_ADS1015.h>
#include <Adafruit_MCP23008.h>
unsigned int DEBUG = 1;
Adafruit_ADS1015 ads;
Adafruit_MCP23008 mcp;
void setup() {
  Serial.begin(115200);
  Serial1.begin(115200);
  // put your setup code here, to run once:
  ads.setGain(GAIN_TWO);
  ads.begin(5, 4);
  ads.setSPS(ADS1015_DR_1600SPS);
  mcp.begin();
  mcp.pinMode(0, OUTPUT);
  mcp.pinMode(1, OUTPUT);
  mcp.pinMode(2, OUTPUT);
  mcp.pinMode(3, OUTPUT);
  mcp.pinMode(4, OUTPUT);
  mcp.pinMode(5, OUTPUT);
  mcp.pinMode(6, OUTPUT);
  mcp.pinMode(7, OUTPUT);
  mcp.digitalWrite(0, HIGH);
  mcp.digitalWrite(1, HIGH);
  mcp.digitalWrite(2, HIGH);
  mcp.digitalWrite(3, HIGH);
  mcp.digitalWrite(4, HIGH);
  mcp.digitalWrite(5, HIGH);
  mcp.digitalWrite(6, HIGH);
  mcp.digitalWrite(7, HIGH);
  
}

void loop() {
  // put your main code here, to run repeatedly:
  float readValue; // value read from the sensor
  float maxValue = 0; // reset max value
  float minValue = 0; // reset min value
  unsigned long start_time = millis(); // mark start time
  while ((millis() - start_time) < 1000) { // sample for one second
    readValue = ads.readADC_Differential_0_1_V(); // read
    if (readValue > maxValue) maxValue = readValue; // update max
    if (readValue < minValue) minValue = readValue; // update min
  }
  Serial1.print("maxValue: ");
  Serial1.println(maxValue, 7);
  Serial1.print("minValue: ");
  Serial1.println(minValue, 7);
  float current_0_1 = ((maxValue - minValue) * ads.voltsPerBit()); // calculate/calibrate/change multiplication factor
  Serial1.print("Current: ");
  Serial1.println(current_0_1, 7);
  float readValue1; // value read from the sensor
  float maxValue1 = 0; // reset max value
  float minValue1 = 0; // reset min value
  unsigned long start_time1 = millis(); // mark start time
  while ((millis() - start_time1) < 1000) { // sample for one second
    readValue1 = ads.readADC_Differential_2_3_V(); // read
    if (readValue1 > maxValue1) maxValue1 = readValue1; // update max
    if (readValue1 < minValue1) minValue1 = readValue1; // update min
  }
  Serial1.print("maxValue1: ");
  Serial1.println(maxValue1, 7);
  Serial1.print("minValue1: ");
  Serial1.println(minValue1, 7);
  float current_2_3 = ((maxValue1 - minValue1) * ads.voltsPerBit()); // calculate/calibrate/change multiplication factor
  Serial1.print("Current: ");
  Serial1.println(current_2_3, 7);
}

the code above produces this output: (with no load, and 300w on one leg)

//Diff0_1:
maxValue: 0.0000000
minValue: -0.0010000
Current: 0.0000010
//Diff2_3:
maxValue1: 0.0000000
minValue1: -0.0010000
Current: 0.0000010

theskaz:
...i changed the ints to floats (the return value of the readDiff) and set it up for both legs.

Why? The ADS1025 returns an int.

When you build a house, you do that one brick at the time.
Same with code.
Remove everything that is not related to the ADS, and debug 'till it works.

Why trying to print to 7 decimal places, when you are going to get one or two at the most.

I see Mega code in there. Didn't you want this for an ESP-12 module?
The whole ADS1015 setup wouldn't be needed for a Mega.
Leo..

i was going off of the library I am using (Adafruit_ADS1X15/Adafruit_ADS1015.cpp at master · soligen2010/Adafruit_ADS1X15 · GitHub)

float Adafruit_ADS1015::readADC_Differential_0_1_V() {
  return (float) readADC_Differential(DIFF_MUX_0_1) * voltsPerBit();                               // AIN0 = P, AIN1 = N
}

my original code has ~1200 lines in it i ripped everything out to just the basics of this working. I put 7 decimal places to try and pick up any sort of activity. Any kind of difference at all. its all for testing. the MCP23008 is connected to the relays to allow me to generate a load for the ammeter. so it has to be turned on.

The multiplication (not the A/D) returns a float.

Don't know what's in voltsPerBit(). Have to look it up.
But it shouldn't be needed, because you 're not doing a standard thing with the ADS.
Just replace with the silly number I gave you, and calibrate later.

I assume you're pulling AC current through the clip-on sensor, not DC.
And only ONE core, not two (opposite) cores of the power lead.

Did you measure the AC voltage coming from the sensor with a DMM, before trying to measure it with the ADS.
Leo..

Here is a picture of the CTs and a MM attached to one that has a 210w (as measured by another ammeter) load

the voltsPerBit in my application returns this:

#define ADS1015_VOLTS_PER_BIT_GAIN_TWO 0.001F

Ill wipe out all math and try again.

New Code:

#include <Adafruit_ADS1015.h>
#include <Adafruit_MCP23008.h>
unsigned int DEBUG = 1;
Adafruit_ADS1015 ads;
Adafruit_MCP23008 mcp;
void setup() {
  Serial.begin(115200);
  Serial1.begin(115200);
  // put your setup code here, to run once:
  ads.setGain(GAIN_TWO);
  ads.begin(5, 4);
  ads.setSPS(ADS1015_DR_1600SPS);
  //mcp.begin();
  mcp.pinMode(0, OUTPUT);
  mcp.pinMode(1, OUTPUT);
  mcp.pinMode(2, OUTPUT);
  mcp.pinMode(3, OUTPUT);
  mcp.pinMode(4, OUTPUT);
  mcp.pinMode(5, OUTPUT);
  mcp.pinMode(6, OUTPUT);
  mcp.pinMode(7, OUTPUT);
  mcp.digitalWrite(0, HIGH);
  mcp.digitalWrite(1, HIGH);
  mcp.digitalWrite(2, HIGH);
  mcp.digitalWrite(3, HIGH);
  mcp.digitalWrite(4, HIGH);
  mcp.digitalWrite(5, HIGH);
  mcp.digitalWrite(6, HIGH);
  mcp.digitalWrite(7, HIGH);
  
}

void loop() {
  // put your main code here, to run repeatedly:
  int16_t readValue; // value read from the sensor
  int16_t maxValue = 0; // reset max value
  int16_t minValue = 0; // reset min value
  unsigned long start_time = millis(); // mark start time
  while ((millis() - start_time) < 1000) { // sample for one second
    readValue = ads.readADC_Differential_0_1(); // read
    if (readValue > maxValue) maxValue = readValue; // update max
    if (readValue < minValue) minValue = readValue; // update min
  }
  Serial1.print("maxValue: ");
  Serial1.println(maxValue);
  Serial1.print("minValue: ");
  Serial1.println(minValue);
  float current_0_1 = ((maxValue - minValue) * 0.12345); // calculate/calibrate/change multiplication factor
  Serial1.print("Current: ");
  Serial1.println(current_0_1);
  int16_t readValue1; // value read from the sensor
  int16_t maxValue1 = 0; // reset max value
  int16_t minValue1 = 0; // reset min value
  unsigned long start_time1 = millis(); // mark start time
  while ((millis() - start_time1) < 1000) { // sample for one second
    readValue1 = ads.readADC_Differential_2_3(); // read
    if (readValue1 > maxValue1) maxValue1 = readValue1; // update max
    if (readValue1 < minValue1) minValue1 = readValue1; // update min
  }
  Serial1.print("maxValue1: ");
  Serial1.println(maxValue1);
  Serial1.print("minValue1: ");
  Serial1.println(minValue1);
  float current_2_3 = ((maxValue1 - minValue1) * 0.12345); // calculate/calibrate/change multiplication factor
  Serial1.print("Current: ");
  Serial1.println(current_2_3);
}

new results:

maxValue1: 0
minValue1: -1
Current: 0.12
maxValue: 0
minValue: -1
Current: 0.12

I want to say the -1 is an error. i think my real minValue should be ~ 2048, it should never be -1. im going to test the voltage coming off the 4 100k resistors.

1.021v coming off those resistors. which is good.

I also tried continuous mode with 1 CT, still get the -1. Currently looking through datasheet to see if I can find out why a -1 would come across.

ok... so found the issue. incorrect address. I tied the ADDR pin to VDD and NOT GND therefor the i2c address was 0x49 not 0x48.

output=

maxValue: 1
minValue: -1
Current: 0.25
maxValue1: 29
minValue1: -29
Current: 7.16

ok, taking it to the next step.

maths:

Gain set 2x, output from A/D:

0 = -2.048v
4095 = 2.048v

add in the 1.024v shift in schematic and take in range of CT:

100A = 1v + 1.022v ~= 2.048v = 4095
0A = 0v + 1.022v ~= 1.024v = 2048

and if I take my known load (210w) and figure out amperage:

210/120 = 1.75

so that means the 29 should equal about 1.75 (there are a few leds and other tiny items on the load too, cant get around it)

so, i think, 29 * X = 1.75 so that would mean X = 16.57 but it isnt that easy.

float current_0_1 = ((maxValue - minValue) * value

this means

float current_0_1 = ((29 - -29) * value

would = 58 * value (minus a negative would be the same as adding)

so 58 * X ~= 1.8 (gonna round a little bit due to the other load)
x ~= .031 if i apply that to my code, the output is:

maxValue: 1
minValue: -1
Current: 0.06
maxValue1: 29
minValue1: -29
Current: 1.80

I dont think that is right, because lets say i have 50 amps running. that "should" show +3072 max and -3072 min. getting 6144 * 0.031 = 190.464

if 6144 = 50 amps, then the multiplier should be 0.0081

that tells me that this isnt linear, and a simple multiplication isnt going to work.

Actually it might:

The following assumes that for every +1 Amp, the ADC will go up by 16.11 and calculating the 1V offset.

0A = 0V + 1V offset = 0 from ADC = 0 * (X) = 0A (not sure about this one)
1.8A = 29 - -29 From ADC * .031 = 1.8A
25A = 402 - -402 from ADC * 0.031 = 25A
50 = 805.5 - -805.5 from ADC * 0.031 = 50A

Not sure why you mention 0 and 4096.
The ADS is used in differential mode, meaning returning 'zero' without current,
and positive or negative int values with current.
Therefore the initial reset to zero of minValue and maxValue.

(could re-use readValue, minValue and maxValue for the other sensor).

If it all works, then calibrate with a known (big) resistive load (heater) of 10 or 20Amp.
The A/D should be more linear than the CT.
Leo..

As you can see lol, I'm trying to figure it all out still. But yeah, got a 5500w 240v wired up and ready tomorrow. Thanks for your help!

Here are the functions I am working with right now, the only thing to do is dial in the multiplier.

#include <Adafruit_ADS1015.h>
Adafruit_ADS1015 ads;

unsigned long lastAdsSample;
int adsSampleRate = 2000;
int adsSampleTime = 300;
float leg1;
float leg2;
float multiplier = 0.036;
unsigned long currentMillis;

void setup() {
  Serial.begin(115200);
  ads.setGain(GAIN_TWO);
  ads.begin(5, 4);
  ads.setSPS(ADS1015_DR_1600SPS);
}

void loop() {
  readAds();
  currentMillis = millis();
}
void readAds() {
  if (currentMillis - lastAdsSample >= adsSampleRate) {
    leg1 = getADSValue(true);
    if (DEBUG) {
      Serial.print("Leg 1 amperage: ");
      Serial.println(leg1);
    }
    leg2 = getADSValue(false);
    if (DEBUG) {
      Serial.print("Leg 2 amperage: ");
      Serial.println(leg2);
    }
    lastAdsSample = currentMillis;
  }
}
float getADSValue(bool leg1) {
  int readValue = 0, maxValue = 0, minValue = 0;
  unsigned long startTime = millis(); // mark start time
  if (leg1) {
    Serial.print("Leg 1 values: ");
  }
  else {
    Serial.print("Leg 2 values: ");
  }
  while ((millis() - startTime) <= adsSampleTime) { // sample for one second
    if (leg1) {
      readValue = ads.readADC_Differential_0_1(); // read

    }
    else {
      readValue = ads.readADC_Differential_2_3(); // read

    }
    if (readValue > maxValue) maxValue = readValue; // update max
    if (readValue < minValue) minValue = readValue; // update min
  }
  Serial.print("MaxValue = ");
  Serial.print(maxValue);
  Serial.print(" MinValue = ");
  Serial.println(minValue);
  return ((maxValue - minValue) * multiplier);
}

the multiplier is set to 0.036. that gets me really close. close enough for my needs.

As far as the sample time goes, I did some testing with the following assumption

“If the Max Value is the exact opposite of the Min Value, I got the whole sine wave”

i wanted to take the 1 second sample time down as far as possible while ensuring I get the whole sine wave. its between 250ms and 300ms. that might be good for someone else, but it’s causing response time issue with my application.

in my application, I have 1 display of Amps, so I think ((leg1 + leg2) / 2) gives me the right amperage

Good to hear you got it working.
Yes, you can try to take sample time down until the display gets unstable.
Or read only one sensor at the time.
Or only sample for one full wave, and smooth several results.

Not sure why you call ads.setSPS(ADS1015_DR_1600SPS); // which is already the default setting
Did you try the 3300SPS setting.

Yes, (A+B)/2 totals current.
Is there no room on the LCD to display the three currents.
Or you could have a button to display the individual currents for a few seconds.
Leo..