External ADC (MCP3208): why voltage increases when CT is loaded?

So there is no way to eliminate it unless I put CTs into a metal box far away from other (not useable) AC cables and systems?

Essentially, no. (at least not easily) But the levels you are seeing are typical, and can be ignored

Ok. So it looks like I can measure everything above ~23 Watts (for my 230V standard AC lines) of power and not below this threshold?

Even at 100 Watts, your resolution and accuracy will not be good.

There are two entries in the FAQ section that explain why.

Just had one (potentially very stupid) thought that I can apply some kind of corection table to my CT readings. I.e. for currents below 1A decrease the value by 2%, for currents above 3A increase by 1,5% etc. (Just an example to illustrate the idea). According to this:


the non-linearity is present in SCT transformers.

The non-linearity is at its worst at low power levels, and especially so with reactive loads.

You’re good to go with what you’ve got now. No real need to do anything else except make sure your calibration is good.

When you calibrate, use a large, resistive load, i.e one that’s in the middle or upper half of the CT max rating.

If you don’t have a resistive load that large, you can wrap more than one turn through the CT.
e.g. if you have a 1500 Watt heater, you can wrap 3 turns through the CT to get an effective load of 4500 Watts.

As I mentioned above, the FAQ has the answers to your questions.

I did. When I say that I use a water boiler I mean something like this: :grinning:


Absolutelly resistive, just a nichrome wire inside.

True, but if memory serves, not a large load. i.e. the one’s I’ve seen range from 100 to 300 Watts.

You really need to be calibrating at the middle of the CT rating, or as close to it as you can get.

You can wrap as many turns as necessary (provided they fit, of course!) around the CT to get a larger equivalent load.

Say for example your immersion heater uses 125 Watts. At that power level, the wire diameter can be failry small. That means you should be able to wrap quite a few turns on the CT.

e.g. I use a 100 Watt incandescent lamp with a CT that has 60 turns of wire wound around it to give me an equivalent load of 6000 Watts.

I know my questions are far away off-topic now but let me ask: if I wrap the phase-carrying wire 5 times (for example) around the CT clip core and then divide the numbers I get by the actual power value that I read on my bypassed SDM630 meter should I get a correct number of turns if everything is callibrated as it has to be?

With five turns, (which really isn’t emnough) emonCMS or whatever you’e using to display the measured data, should read five times the value of the immersion heater rating.

Yes, or a number very close to five. (taking the various component tolerances into account)

Ok. So for those who will face the same ADC problems, here’s do’s and don’ts of using MCP3208 that I learned for now:

  • Do not overheat your chip! If you have a solder station with a heat gun and a solder iron then the last one is your weapon of choice (need a thin tip and straight steady hands);

  • Solder the MCP3208 at the end of the assembly process (especially if you do your assembly with a heat gun) to avoid overheating

Nice. Yes, I see now that triggering everything off that leading start bit adds a lot of flexibility.

Just found out one unpleasant thing: when I read the current from 2 different channels and voltage from the same channel I get way higher idle value for current as when I read only one channel current. The code:

#include <MCP3208.h>
#include <SPI.h>

#define ADC_BITS 12
#define ADC_COUNTS (1 << ADC_BITS)

#define ICAL 61.5
#define VCAL 255.8
#define PHASECAL 1.00

MCP3208 adc(4);

unsigned int current = 0;
unsigned int voltage = 0;

/*unsigned int currentChannel = 4;
unsigned int voltageChannel = 5;*/

double offsetV, offsetI, filteredV, filteredI, lastFilteredV, phaseShiftedV;

double sqI, sumI, Irms, sqV, sumV, Vrms, sumP, instP;
double realPower, apparentPower, powerFactor;
int startV;

unsigned long start;
unsigned int numberOfSamples;

boolean lastVCross, checkVCross;

unsigned int timeout, crossCount, crossings;

void calcVI(unsigned int currentCh, 
		    double currentCal, 
			unsigned int voltageCh, 
		    double voltageCal, 
			double voltagePhaseShift)
{
    boolean st = false;
	
    start = millis();
	while(st == false)
    {
	    startV = adc.analogRead(voltageCh);
		if((startV < (ADC_COUNTS * 0.55)) && (startV > (ADC_COUNTS * 0.45))) st = true;
	    if((millis() - start) > timeout) st = true;
    }
	
    numberOfSamples = 0;
	crossCount = 0;
    
	start = millis();
    while(((millis() - start) < timeout) && (crossCount < crossings))
	{
	    numberOfSamples++;
		lastFilteredV = filteredV;
	    
		current = adc.analogRead(currentCh);
	    voltage = adc.analogRead(voltageCh);
		
	    offsetV = offsetV + ((voltage - offsetV) / ADC_COUNTS);
		filteredV = voltage - offsetV;
	    offsetI = offsetI + ((current - offsetI) / ADC_COUNTS);
		filteredI = current - offsetI;
	    
		sqI = filteredI * filteredI;
	    sumI += sqI;
		
	    sqV = filteredV * filteredV;
		sumV += sqV;
	    
		phaseShiftedV = lastFilteredV + voltagePhaseShift * (filteredV - lastFilteredV);
	    
		instP = phaseShiftedV * filteredI;
	    sumP += instP;
		
	    lastVCross = checkVCross;
		if(voltage > startV) checkVCross = true;
					    else checkVCross = false;
	    if(numberOfSamples == 1) lastVCross = checkVCross;
		if(lastVCross != checkVCross) crossCount++;
    }
	
    double I_RATIO = currentCal * ((5000 / 1000.0) / (ADC_COUNTS));
	Irms = I_RATIO * sqrt(sumI / numberOfSamples);
    
	double V_RATIO = voltageCal * ((5000 / 1000.0) / (ADC_COUNTS));
    Vrms = V_RATIO * sqrt(sumV / numberOfSamples);
	
    realPower = V_RATIO * I_RATIO * sumP / numberOfSamples;
	apparentPower = Vrms * Irms;
    powerFactor = realPower / apparentPower;
	
    sumI = 0;
	sqI = 0;
    sumV = 0;
	sqV = 0;
    sumP = 0;
}

void setup()
{
    adc.begin();
	SPI.setFrequency(1000000); // 1MHz max samplerate
    Serial.begin(115200);
	
    timeout = 1000;
	crossings = 50;
}

void loop()
{
    calcVI(3, ICAL, 5, VCAL, 1.00);
	Serial.println("Phase A:");
    Serial.println("----------");
	Serial.print(Vrms);
    Serial.print("V, ");
	Serial.print(Irms);
    Serial.print("A, ");
	Serial.print(realPower);
    Serial.print("W, ");
	Serial.print(apparentPower);
    Serial.print("VA apparent, PF = ");
	Serial.println(powerFactor);
    Serial.println("-----------------------------");
	delay(2000);
    calcVI(4, ICAL, 5, VCAL, 1.00);
	Serial.println("Phase B:");
    Serial.println("----------");
	Serial.print(Vrms);
    Serial.print("V, ");
	Serial.print(Irms);
    Serial.print("A, ");
	Serial.print(realPower);
    Serial.print("W, ");
	Serial.print(apparentPower);
    Serial.print("VA apparent, PF = ");
	Serial.println(powerFactor);
    Serial.println("=============================");
	Serial.println("");
    Serial.println("");
	delay(2000);
}

If for one CT reading I had 0.1A value for two of them I have 0.4A which is very strange… Could it be somehow related to ADC or it’s just my program bug?

Are you still using those 470K resistors for the voltage divider and are you still using .1uf caps in those circuits?

Yes. I did not make any changes to my test setup. How could it affect the current readings?

I don’t know much, but I do have a lot of experience over the last year with MCP3208 and ESP8266. I’ve probably used 100 of them and rarely have problems. When I do, it’s all or nothing. You may have damaged one channel, but I doubt it. The channels are muxed into a single ADC. It works or it doesn’t. Maybe a mux channel is bad, but I’ve never seen it.

The problem of high impedance inputs is real. I think if you replace those 470K resistors with 4.7K - or even 12K like the CT channels, and put 10uf capacitors to ground, you will see a big increase in stability. The current available to charge the ADC sample and hold caps is 100 times less than what I’ve found to work acceptably.

What value do your offsets settle in at?

Your CT channels are calibrated at 60 amps per 1V at the ADC. 1 volt at the adc is 4096/5 = 819 counts per volt. So 1 count is .073 amps. The ADCs are accurate to about 1 count. I’d argue that the difference between .1 and .4 amps is hardly signoificant.

I was going to bring this up after you fixed the input circuits, but running the ADCs at 5 volt may not be best for a couple of reasons:

One is that the ESP8266 is a 3.3v device and shouldn’t be exposed to 5v inputs. I realize it is working but the MISO line will be 5V. It does work to knock it down with a resistor voltage divider.

More importantly, your VT and CT don’t put out more than 1V and your effective range is 2.5V. Running everything off the 3.3V output of the nodeMCU will make it quite a bit more accurate.

Actually, he’ll get more resolution, as each ADC count will represent a smaller voltage or current value.
Accuracy is dependent on component tolerances and calibration. IOW, even though he gets more resolution with a 3.3 Volt reference, the measurements could still be in error if component values drift, or the system isn’t calibrated.

There’s a laundry list of potential errors in this project, but the big nut is getting the ADCs working properly in the appropriate range.

Ok. So it worth to divide the SPI MISO level with a resistor-based divider?
And why I get different results depending on how much CTs I poll?