Strange cyclical voltage fluctuations on esp32 with sct013-030

Hi all:

I’m at my wit’s end. I’ve been trying for days to use an sct013-030 current transform to measure voltage, using an ADC pin on a esp32 development board - details in footer

I’m using the circuit described on openenergymonitor “interface with an arduino”. Others have used the same circuit with an esp32 with no problems.

All I’m really trying to do is detect the on/off status of a thermostat. Accuracy in current/voltage detection is only needed to the extent that I can determine on/off status.

Would appreciate any help you can give. I don’t quite know the best place to ask, so if you’re not able to help, but can point me in the right direction, that would also be a huge help. See my issue below:

Expected Result:

  • I should get a value in amps when calling emon.current(34, 30)
  • The value should be 0, or very close, when no current is flowing
  • Some small fluctuations in the result due to noise are expected.

Actual Result:

  • When current isn’t flowing, I get a value around 73 when calling emon.current(34, 30)
  • When current is flowing, I get the same result.
  • The value is constant around that number, with some minor fluctuations

Circuit Details:

  • The SCT013-030 is a voltage type and has a built-in burden resistor. Its output is between 0-1V.
  • I’m using 100k divider resistors
  • I’m using a 10uF cap


#include "EmonLib.h"
EnergyMonitor emon1;

void setup() {

void loop() {
   emon1.current(34, 30);
  int amps = emon1.calcIrms(1480);
  delay(100);        // delay in between reads for stability

What I’ve tried:

  • Replaced all passives
  • Replaced SCT013
  • Replaced esp32 development board
  • Checked, double checked, triple checked wiring
  • Tried sct013 on bench, around multiple cables

All leading to the same result.

esp32 development board:


Sorry about the notes above - I can’t add enough links, since I’m a new user

Thanks for your time.

You’re definitely putting the CT around only a single conductor and not the whole power cable?
Maybe post a picture of how you have it connected.

You should be able to now - I’ve upped your status for you.

If you forget emonLib and use a “one-line” program to read the analogue pin, do you get a reading that’s about half-way in relation to the ADC’s range? If you short out one of those 100 kΩ resistors, does that reading change to zero or the maximum - depending on which one you short out?

Have you got a multimeter that can measure down to a few tens of millivolts? If you put that directly across the c.t’s plug (tip and sleeve) you should read something. Bear in mind you can multiply the current by having a multi-turn primary winding - pass 6 turns through the c.t. of a single wire carrying 5 A, and you’ll get the full 30 A that your c.t. can measure, and 1 V out at its plug.

Hi Greebo. Thanks so much for taking an interest. Pictures follow:

Here’s the breadboard:

And a closeup of the business parts:

Here’s where I have the CT installed - in a relay panel for my heating system. It’s measuring power on Zone 1:

Here’s the circulator for Zone 1 - it shows a 13W power draw

And here’s the serial plotter as I was turning the power on and off to the relay panel, and therefore the circulator was turning on and off. No correlation between the power and the output. Only a few volts of random noise.

Hope that helps!

I don’t think that c.t’s on the wrong conductor.

Have you got the correct connections on the c.t’s socket? If you use a multimeter on the resistance range across the c.t. connections, what value do you read? it should be about 35 - 40 Ω.

Hi Robert. Thanks so much for your help, and for upping my status.

Here’s my code now, as per your suggestions:

// the setup routine runs once when you press reset:
void setup() {
// initialize serial communication at 9600 bits per second:

// the loop routine runs over and over again forever:
void loop() {
    delay(100);        // delay in between reads for stability

When the CT is not connected, the pin reads 4095 - the default width of the ADC is 12 bit, so that makes sense.

When connected, and the CT is on the bench, not around any wire, the pin reads1840, with occasional ups and downs of a few volts. Exactly half of 4095 would be 2047. So it’s close, but not quite.

When I short out the resistors, both go to zero, rather than one to zero, and one to the maximum. Perhaps it’s mis-wired? I posted pictures of my wiring below. I think it’s right, but I’m a total newb.

I don’t have a meter that can measure AC millivolts. And since this is pre-existing work, theres no cable slack to do multiple turns around the CT to get a higher primary winding. I could try that if it comes down to it though. But before I do:

Based on the 13 watts the circulator is using, and 120V supply, it’s only using about 0.1A. Given the 1V rating of the CT, that would roughly imply 0.003V (assuming it’s linear etc, and if I’ve don my math correctly). Is 0.003V reasonable to expect the ESP32 ADC to be able to measure, taking into account I don’t need an accurate current reading, just an on/off status?

That’s close enough - it shows that you’re biasing the input roughly in the middle, so no major problem there.

Yes, you’ll be struggling against noise picked up for here, there and everywhere at that sort of level, especially with a breadboard construction. And yes, you are looking for 3 - 4 mV, which should give you a swing of about 10 mV peak-peak, or about 12 counts peak to peak. You should be able to detect that, provided it isn’t swamped by noise and pickup. Accuracy is a different matter.

It’s anyone’s bet what the input goes to with nothing connected - that’s a believable number.

I think that’s told us enough - you can go back to your original sketch.

I went back to the original sketch, but no luck. Same result where output value is pinned at around 72, with a bit of noise. Plot below:

In fact, in this case, the spikes in the plot roughly correlate with turning the power on and off. Is that may be some sort of inrush current to the circulator? In either case, it doesn’t sense the normal 13W or so when it’s on.

Any ideas where to go from here?

I’ve just noticed, in your sketch:
int amps = emon1.calcIrms(1480);

OF COURSE you’ll never read anything less than 1 A, because the result from calcIrms is truncated to an integer. Change it to a float, and print with 3 decimal places.

For goodness sake. The software part is the part where I’m supposed to be vaguely competent!

Thanks so much Robert. An updated plot below:

Pretty easy to see on/off status now. Thanks so much for your better eyes!

If you could suggest an algorithm for getting a binary on/off status from this waveform, that would be super helpful. What I’ve been planning on doing up until now was using a sliding window, and averaging inside that window. But since you’re so much more knowledgeable in the domain, I bet you know a better way. It’s pretty clear my naive idea won’t work very well.

Thanks again.

What you might already exist in emonLib.

What does “7328” on the Y-axis represent? It cannot be the output from emonLib’s calcIrms, as you have (on your own admission :grin: ) a value of less than 1 A there.

You need to establish the number of readings that you average over. “1480” is a long out-of-date value for emonLib running on an Atmel 328P, you need to time how long it takes for calcIrms to return and aim for a few hundred milliseconds - 200–300 say. Ideally, an exact whole number of mains cycles.

(If you want an accurate value for the time per reading, get the time for a call to calcIrms(1480) (= 1480 readings), then for 1980, subtract one from the other and divide by 500. That removes the overhead and gives the time per reading, then you need the nearest count that will give a multiple of 16.6667 ms. 12 - 18 cycles could be a good number - 200–300 ms.)

What does “7328” on the Y-axis represent? It cannot be the output from emonLib’s calcIrms, as you have (on your own admission :grin: ) a value of less than 1 A there.

Sorry. I should have mentioned: I multiplied by 100 before plotting because it seemed to scale better on the plotter.

I’ll give your suggestions a try and report back, I now see that when more than one heating zone is active at a time, it seems to become harder to see the “signal”. I would assume because there’s more noise, (and perhaps some current drop?) in that case.

Perhaps my best option now is to try another tack on the hardware side. Is there an option for a more sensitive CT (Presumably a 5A rated CT is more sensitive than a 30A one)?

Or would a better approach be to give up on the CT altogether and try and interface directly to the 24V AC side of the relay (although I was trying to be as non-invasive as possible)?

Wait until we see the results of my suggestion. The output from emonLib shouldn’t look like your trace.

Thanks Robert. This looks really good now. Here’s some details, in the hope you have more advice:

I figured out the time per reading with this code:

#include "EmonLib.h"
EnergyMonitor emon1;

// the setup routine runs once when you press reset:
void setup() {
  // initialize serial communication at 9600 bits per second:

// the loop routine runs over and over again forever:
void loop() {

    emon1.current(34, 30);
    unsigned long start1480 = millis();
    unsigned long end1480 = millis();
    unsigned long duration1480 = end1480 - start1480;
    Serial.print("CalcIrms(1480): ");

    unsigned long start1980 = millis();
    unsigned long end1980 = millis();
    unsigned long duration1980 = end1980 - start1980;
    Serial.print("CalcIrms(1980): ");

    unsigned long calcIrmsTimeDifference = duration1980 - duration1480;
    Serial.print("CalcIrms() time difference: ");
    float timePerReading = (float)calcIrmsTimeDifference / 500.0; //500 is the # of readings difference
                                                             //between 1980 and 1480
   float timePerReadingInMs = timePerReading * 1000;
   Serial.print("Time per Reading in ms: ");
   Serial.println(timePerReadingInMs, 4);

   delay(50);        // delay in between reads for stability

And here’s the results:

As you can see, the time per reading bounces between 10 and 12 ms.

Now, if I’ve understood correctly, 16.667ms * 15 = 250.005. If we choose 10ms for the time per reading, based on what we learned above, 250 is a good number of readings for calcIrms, since it divides by 10ms, and by 16.667ms, correct?

It certainly seems to work well at 250:

Should be pretty clear where on / off is :grinning: And, there’s now no issue with noise when multiple heating zones are active.

You mentioned that there may be something existing in emonlib to convert this waveform to a binary on/off status?

No, I didn’t mean that - I was thinking about the averaging time, which as you’ve seen has tidied things up quite a bit.

But there’s something drastically wrong with your maths - surely the time per reading is 6 ms ÷ 500 = 12 µs – so you’re 3 orders of magnitude out?

If that’s right, you’re only measuring a fraction of one cycle, and that explains the ripple you’re seeing in the output.

If that’s the case, you could repeat the measurement with a much bigger difference to get a more accurate average, then recalculate the number of samples you want to average over. I think that number is more like 7000 - which should cover 5 mains cycles.

This should give you a clear step when current is drawn. This is my refrigerator working overnight - it’s using the later emonLibCM and reporting the 1-second average power every second.
You should see something like that - probably not quite so clean because you’re looking at a current about one fifth of mine. (But the 45 W or so of standing load that I have is not very much different to the current you’re trying to measure.)

I should mention that the plot above was taken from the 24V thermostat wiring, rather than from the line voltage to the pump.

Now I’m confused. A current transformer measures the current in a wire, the voltage has got nothing to do with it. So where does that plot come from? Unless it’s the current reading that’s the output from your sketch, you can forget all the advice I’ve given you because it’s probably wrong.

Sorry to be confusing. Your advice is right, and it’s a huge help. I’m measuring current.

My heating system has a 24VAC circuit to the thermostat, tied to a relay, which switches the 110VAC circuit to the pump.

What I was trying to get across, badly, was that in the plot above, I had the CT measuring the 24VAC thermostat circuit, rather than the 110VAC pump circuit, in case it was germane .

But there’s something drastically wrong with your maths - surely the time per reading is 6 ms ÷ 500 = 12 µs – so you’re 3 orders of magnitude out?

Ugh. Decimal places fail. /embarrased! Sorry about that. I’ll take another look as soon as I get another chance.

Your plot looks great. That’s exactly what I hope to get to.

Roger, this thread is getting long. I appreciate your time sticking with me.

Based on my understanding of what you said:

  • I remeasured the average reading time with larger difference, and got a stable 0.012ms.
  • Time to measure 6 cycles: 6 cycles * 16.666 ms = 99.996ms.
  • Number of readings: 99.996ms / 0.012ms per reading = 8333 readings

Let me know if you see any issue with my understanding there.

Unfortunately the results don’t look as promising:

I also scaled the plot by multiplying by 100 before plotting:

In the scaled plot you can see the same spike when I turn the power on, followed by an identifiable (ripple?) pattern, then back to noise when I turn it off around 300.

One other thing I’m curious about: When the power is off, there should be no current draw. But when the power is off, the result of calcIrms() is not zero - it’s a value dependent on the parameter I pass it. Is that normal?

The results I"m seeing are approximately 32 for calcIrms(8300), or approximately 114 for calcIrms(250). Seems to be an inverse relationship.