ADS1115 and sampling speed

Ok the speed problem is now solved, the Adafruit library needs some serious work, I suggest people use GitHub - jrowberg/i2cdevlib: I2C device library collection for AVR/Arduino or other C++-based MCUs instead, it’ll let you adjust reading speed easily and has no constant delay implemented.

The relevant code is below in EmonLib.cpp's EnergyMonitor::calcIrms

  for (unsigned int n = 0; n < Number_of_Samples; n++)
  {
    #ifdef EMON_ADS1115
    sampleI = ads.getConversion(true)*4096/3300;
    #else
    sampleI = analogRead(inPinI);                 //Read in raw current signal
    #endif

    // Digital low pass filter extracts the 2.5 V or 1.65 V dc offset,
    //  then subtract this - signal is now centered on 0 counts.
    //offsetI = (offsetI + (sampleI-offsetI)/1024);
    offsetI = (offsetI + (sampleI-offsetI)/1024);
    filteredI = sampleI - offsetI;

    // Root-mean-square method current
    // 1) square current values
    sqI = filteredI * filteredI;
    // 2) sum
    sumI += sqI;
  }

  ICAL=1;
  double I_RATIO = ICAL * ((SupplyVoltage/1000.0) / (ADC_COUNTS*4096/3300));

  Irms = I_RATIO * sqrt(sumI / Number_of_Samples);

  //Reset accumulators
  sumI = 0;
  //--------------------------------------------------------------------------------------

  return Irms;

Here sampleI is pretty steady between 16107 and 16110, which is 1622 mV.

Running the code by disabling ICAL(set it to 1) I get Irms = 0.01 Beautiful!

Now I need to continue and start the tests with the CT connected.

As you are measuring only 14 samples per cycle, you might need a different constant (not 1/1024) in the filter to maintain roughly the same time constant in seconds - or you might find that the ripple is more noticeable with the higher resolution of your ADC, so the original value remains sensible because it will give a (roughly) 8 times longer physical time constant which more than compensates for the increase in ADC resolution.

If you’re not switching the CT input from zero to half-scale (by plugging in a CT as is done with the emonTx V3), then you can have a much longer settling time because the d.c. bias should never move quickly, if at all. The only reason for the filter to adjust the offset quickly will be at start-up if the d.c. bias is not exactly mid-rail but the filter output has been pre-loaded to the ADC mid-point value.

Right now, I’m actually reading 1480 samples in 1032ms. One cycle here is 50Hz, 20ms ? Witch is 28 samples per cycles, but that exceeds the 860 max SPS of the ADS1115, so some values are probably returned twice.

I’m not sure I understand what you suggest (ok, let’s face it, I don’t understand what you suggest :-)).

  • What would be a good replacement for the 1024 constant? 4096 gives me worse values (1.17 Irms) whereas 256 gives me 0.41, better. should I go lower or is this nonsense? Disabling the low pass completely gives me 0.00, even when the CT is plugged with some charge.

With a charge of approx 47W (I know this is really low, I need to put together an extension chord with a meter in the path), I get an Irms of 0.83 (should be 0.37, but with no charge I’m already getting 0.79), the amplitude of the values I read are like this with the CT connected :

low:  15965 (delta 277, 34mV)
high: 16242
Irms = 0.83
low:  15970
high: 16238 (delta 268, 33.5mV)
Irms = 0.83
  • Is the fact that I’m getting some values twice (because of the 860SPS) a problem?
  • I’m still getting 0.79 Irms unplugged, I don’t really understand that, without applying a constant, the Irms is 0.01, probably too much in fact. My raw V reading is 1621mV without the CT, is that the expected value or should I be closer to 1650 (3300/2)?

Thank you!

Nope. 60 Hz, 16.667 ms.

That is the filter’s time constant. That’s a loose term, because it is related to sample rate, not time per se, which is why I suggested it would be wrong. It could also be not right because it also controls the ripple in the output. Ideally, output ripple should be less than one LSB, so that the filter output is always the same number, but that’s probably unrealistic, and a variation of a few counts won’t materially affect the answers. If you make it too large, the filter will have zero ripple but will take too long to settle if the input level shifts. If you make it too small, the filter will respond quickly to a level shift but will also let through more of the 60 Hz that you’re trying to remove.

You need to be sure you’re reading the ADC correctly - you’re wasting time and effort until you get that right.

Why not set up a spreadsheet with the same number of samples per cycle of a sine wave as you’re reading (several cycles), write the filter equation into the sheet and plot the filter output against time (sample number). Then you can see exactly what happens as you add in a step in the input, or change the filter constant.

Great idea, I’ll work on that.

Isn’t it 50Hz in the U.S. ?

What about that Robert?

Top is raw values, bottom is filteredI, as in :

sampleI = ads.getConversion(true)*4096/3300; // Apply scale factor from reference 4096mV to 3300 mV
#else
sampleI = analogRead(inPinI);                 //Read in raw current signal
#endif

// Digital low pass filter extracts the 2.5 V or 1.65 V dc offset,
//  then subtract this - signal is now centered on 0 counts.
//offsetI = (offsetI + (sampleI-offsetI)/1024);
offsetI = (offsetI + (sampleI-offsetI)/1024);
filteredI = sampleI - offsetI;

That screenshot represents 1000ms, as you can see, it takes a lot of time to stabilize with 1024

With 128, here is what I get :

Much better.

Tese samples were takes with a load ~1500W

I will do the test again with 128 tonight and see if I can get consistant and reliable values!

Maybe it makes more sense presented that way, this is millisecond 2000 to 3000

My interpretation is that given the relatively high values of filteredI, the calculations for the calibration value are no longer valid but I fail to understand the logic behind the original 60.60 value, that didn’t take the ADC scale into consideration if I understand the documentation, why changing the scale of the converter require an adjustment of calibration?

Do these results look valid and consistant to you?

No, the North American grid frequency is 60 Hertz.

One of the reasons that we changed from using a high pass filter - to remove the d.c. offset directly and used instead the low pass filter to obtain the value of the offset and then remove it by a simple subtraction (there, that’s answered the questions I set you in your other thread!) was it is very simple to pre-load the filter output at start-up with the expected result. That means that the start-up time of the filter is very short, so you can have a long time constant. The advantage of a long time constant is it removes the ripple, as you can see in your graphs. If you try 1024, but initialise the filter output to the value it is aiming for, you will see a response much more like the “128” graph but with the ripple of the “1024”.

It is related to the CT ratio and the burden resistor value. Because you are now using a totally different ADC, you will need to calculate your calibration from first principles. The steps are roughly: What primary current swing gives you what values swing from the ADC? (This incorporates and so needs knowledge of the CT ratio, the burden value and the ADC reference voltage and resolution.) The calibration constant is then the scale factor that means the maths gives you the correct rms value for the rms value of that primary current.

So I understand now why offsetI is initialized to ADC_COUNT >> 1, which is the binary equivalent of ADC_COUNT / 2.

All right, in the end, it means I just have to be patient, and looking at a bigger scale, I actually stabilize after 4s or so with a value of 1024.

I actually fixed the spreadsheet, and calibration is more aligned with “regular” values

My next step is probably to do different load tests and see how it goes.

Thank you Robert

That’s probably not a good thing and warrants further investigation. Which part are you using? The title suggests an ADS1015 but I see references to ADS1115. It looks like the former can do 12-bit samples at 3300 SPS and the latter can do 16-bit conversions at 860 SPS.

I thought of you OEMers the other day. I’m currently using some STM32 processors on a project unrelated to energy monitoring, but when I discovered the phenomenal ADC performance I couldn’t help but think what a great OEM platform it would make. It can do 12-bit conversions in 1 usec, do a continuous sequence of them in the pin-order of your choosing, and load the results directly into your C datastructure, all without any CPU interaction at all. Imagine an interrupt every 1 msec to say “I’ve just loaded those 1,000 12-bit samples you requested into your first array and I’m currently working on filling your second array with a new 1,000 samples”.

2 Likes

Yes I fixed the title earlier, it’s actually a ADS1115, so I’m doing 16bit@860 (actually 15bit as there is one reserved for the sign, as the chip can measure +/- voltage).

Yes, that sounds like a really good fit for what we’re doing! The STM32 seems like a pretty beefy chip though compared to the ESP8266. That’s pretty amazing what you can do today with $10 bucks though.

I think I am getting somewhere though, the waves looks good enough for the precision I need, but I just realized something : when I decided on my burden resistance I did all the calculation with 50mA CT output… but that’s the RMS, I need to do the math with the peak, is that correct?

So instead of 33Ω, I need 23.57 (and whatever is lower as an actual resistor, probably 22Ω, then I will probably get a proper “zero” which is not the case at all now.

After that, I decided to give interrupt a try to detect the laser in front of the meter disc, instead of polling.

That way, I will be able to alternate CT measuring for CT1 and CT2, and setup an interrupt to get the disc spin notification, and I won’t have to worry about the “slow” ADS1115 anymore, as I have nothing else that is time critical.

But you’re getting readings back more frequent than that? Your call, but if it were me I’d be wanting to get to the bottom of that. You might end up with all sorts of strange beat effects if you’re not sampling when you think you are or picking up the same reading twice.

Interesting. I wonder if you could have done away with the mid-rail and just used 0V as a mid-rail. That’s how most energy ICs work too, the V and I signals swing either side of GND.

Yes, I think the STM32F0 development board I got is about US$11, complete with Arduino headers. Alas, no wifi though so that’s a big plus there for your ESP8266. It’s all starting to make the old traditional Arduino stuff look as historic and folkloric as a black-and-white TV though.

Yes, when it comes to calculating the maximum (or minimum sub-zero) voltage you’re going to subject your ADC input pin to, it’s the peak that matters. In the case of I, your current signal might be a long way from a sinewave and very peaky so a simple root-2 multiplier is generally not enough. Robert has a rule-of-thumb multiplier he uses that takes into account some level of peakiness and component tolerances, but I can’t find it right now. You may be able to find it with a search.

Yes you have a valid point, I will experiment, and maybe adjust some timers. Right now it works in a mode they call “continuous”, you can poll as much as you want but the value doesn’t get updated as fast, that’s why sometimes I will get the same value twice. But as you can see the sine isn’t that bad. or is it?

UGH. I Why didn’t I think about that earlier. That’s a major change though, now I would have to remove the divider right?

Haha, I’m with you there, that’s actually what is running the CT now, an old UNO, the ADC is pretty good though (fast at least)

Ha! I didn’t get that in the Resource section, they are just using root2! Very good point!

Thanks for the tips!

Now that you mention it, and have encouraged me to zoom in, I think I can see kinks in your sine where it’s “slipped”. At over 1 msec per sample, I can see why you’ve taken the approach you have… you’re presumably trying to overlap your CPU processing time with the conversion time? My gut feel is you want to be sampling the signal at a constant rate and that can’t be faster than 860 SPS. I suspect going faster than that, and reusing previous conversion results will do more harm than good to your accuracy, although that’s more a gut feel than a serious calculation. Assuming your processing time is less than your conversion time, perhaps you could get the overlap by:

for required measurement time:
. wait for a conversion to complete
. do the processing

That way you’re being “clocked” by the ADC, and while it’s busy converting the next result, you’re processing the previous one and hopefully getting back to step 1 well before the next one is ready.

A very quick scan of the datasheet made me think you can’t feed it voltages below GND, so it probably makes sense to stick with what you have. It looks like you’ll only get negative readings in differential mode, but I haven’t studied it in detail.

Pffff… the STM32 can deliver 104 12-bit conversions right to my C array in the time it takes the Uno to do one 10-bit conversion, and it’s a $2 part! OK, OK, that’s probably not want you want to hear right now… I’ll stop …soon ;-).

It’s about now I usually dust off this pic of my lights circuit (compact fluros). This circuit was drawing 893mA RMS at the time and you can see the peak current exceeds 3A, so needs a multiplier of 3.36 there. While that is a real circuit, the numbers are still quite small. It’s unlikely anyone will have designed things so that they start clipping at 3A. With much bigger whole-house aggregate signals, you’re unlikely to see anything anywhere near that peaky (power factor regulations don’t allow it), but it illustrates the issue.

Very interesting demonstration.

Here is a zoomed in view of the “blips” I get in the waveform.

The loop that samples is pretty fast in the Emon code as it’s not doing much, just a little bit of math with each iteration, I don’t think I should be concerned with with. I could just add a delay() with a value TBD to get reliable reading I think, I’ll probably do that.

OK, the rule of thumb is:
1.1 V rms for an emonTx or anything running off 3.3 V
1.6 V rms for an Arduino or anything running off 5 V.

Those numbers take into account tolerances on the transformers (voltage or current), the bias chain offset and voltage divider or burden resistor tolerances, plus a few percent for waveform distortion. The reasons for not allowing 3 times are (1) the voltage wave won’t be anywhere near that in its crest factor, and a high current load that approaches the peak design current is much more likely* to have a wave shape that is close to the shape of the voltage wave, and (2) most people require good accuracy at low currents, so throwing away a third (or whatever) of the resolution is for most people a bad idea.

If you are designing your system properly, and you know the current wave shape, then the right way to do it is to use the peak value that you know about, and add the circuit tolerances on top of that.

*[The high current load is likely to be well-behaved because of legislation regarding harmonic pollution of the supply.]

2 posts were split to a new topic: STM32 Boards for Energy Monitoring

That’ll certainly help, but you’ll still have two asynchronous clocks (the ESP clock and the ADC oscillator) that will eventually slip past each other. If you can time the delay accurately enough it may happen so rarely you never notice. The idea behind the pseudo-code loop above was to eliminate one clock altogether (the ESP clock) and let the ADC dictate when things happen.

Hello :wave:
I appreciate it’s been a while since since this topic has been active but I have been finding it very useful. I opened another topic for my specific setup but the problem that I am facing at the minute is calibrating the readings (Power meter using Raspberry pi & SEN0211)

I’ve been comparing the code that you added in this post with what’s on github: https://github.com/openenergymonitor/EmonLib/blob/524c76a6c61acf7c35d90c4a53ddb0abb6173590/EmonLib.cpp and they seem pretty much the same, EXCEPT the fact that you are multiplying sampleI with *4096/3300; and then you are also using this for I_RATIO.

I am curios to know the following:

  • Why are you doing this?
  • Why 4096?
  • What value are you using for ADC_COUNTS?
  • I assume 3300 means 3.3V ?

Thank you!