Calibration issues with RPICT3

I use http://lechacal.com/RPICT/3CT/RPICT3_v1_4.ino as a base and I ported this to pure AVR code.

I use a vacuum cleaner to test with a clamp meter and compare this to what this gives me. It seems to become more accurate as the amperage increases. But when the vacuum cleaner is on lowest setting, the clamp meter shows 2.9, while the RPICT3 shows 4.0.

When on full power the clamp meter shows 6.2 while the RPICT3 shows 6.4, so then it’s closer. Is there some coefficient that’s off here? The atmel AVRISP MK II showed 3.4 supply, but the hardcoded value is 3.3

The burden resistor is 24 ohm, so the coeff is 83.3. The code loops through 1024 samples but it will spend some time on a poor little attiny85 doing those low pass calculations.

What kind of accuracy should I expect?

You might be able to speed up your code a little by using the later version of emonLib as a basis for your sketch.

The sketch calculates the true rms value of current - assuming it reads enough samples to be reasonably accurate. Does your ammeter measure true rms, or does it measure the rectified average and display that scaled to the rms value of a sine wave? If your meter does not say “True rms”, then it is most likely that it is average-reading. That could be one source of the difference that you see.

The other possibility is electrical noise. Does your RPICT3 read close to zero with no current flowing? If you are picking up electrical noise from somewhere, that will give you a false reading that is worse at low currents.

Disregarding those things, we have found the the emonTx should be linear down to around 1% of the maximum reading, but below that, offsets in the ADC and quantization errors build up and become significant.

Thanks!

I converted my code where the body of the loop is this:

double sampleI, lastSampleI;
double lastFilteredI, filteredI;
double sqI,sumI;
unsigned long resultc1;
static double numSamples = (double)NUMBER_OF_SAMPLES;

void calc_irms(uint16_t channel)
{	
	for (uint16_t n = 0; n < NUMBER_OF_SAMPLES; n++)
	{
		lastSampleI = sampleI;
		sampleI = adc_read(channel);
		lastFilteredI = filteredI;
		filteredI = 0.996 * (lastFilteredI+sampleI-lastSampleI);

		// Root-mean-square method current
		// 1) square current values
		sqI = filteredI * filteredI;
		// 2) sum
		sumI += sqI;
	
	}
	//Reset accumulators
	resultc1 = (unsigned long)sqrt(sumI / numSamples) * 1000.0;

	sumI = 0;
}    

Where resultc1 is then sent as an unsigned long (hence the * 1000.0) . Then on the receiving end, I use the rest of the original calculation:

private static final double conversionFactor = 1000.0;
private static final double burdenCalibration = (100.0 / 0.05) / 24.0; // Calibration coef for 24ohm burden resistor is 83.33
private static final double supplyVoltage = 3.30;
private static final double ratio = burdenCalibration * (supplyVoltage / 1023.0); // 0-1023 = the 10 bit range.

result = ratio * ((double) rawValue / conversionFactor);

This way I avoid using sprintf.

The manual for the meter: http://www.biltema.no/BiltemaDocuments/Manuals/15-287_man.pdf (it’s in nordic languages, but there seems to be no “true rms”, although I can see it has some kind of buffering since the number rises / falls after 1-2 seconds.

The shield is bought from ebay, and it seems OK, it uses SMD components but I have confirmed that it uses 24 ohm. The readings without any current are between 0.0 steadily and an occasional 0.28.

[0000000,0000000,0000000]

versus

[0001000,0001000,0000000]

as raw output from the chip.

When on the lower load from the vacuum cleaner it’s

[0000000,0015000,0000000]   4.0444770283479965

Do you have a link to the better calcIrms?

You can download the complete emonLib from GitHub -
@glyn.hudson, @TrystanLea, @Gwil Please Note:
How you know where in GitHub it is, is a very good question. Even I cannot find a link to download it!

(It is GitHub - openenergymonitor/EmonLib: Electricity monitoring library - install in Arduino IDE's libraries folder then restart the IDE )

Note, your readings are not very helpful without knowing the maximum that your system can read.

Haha. The circuit is this: RPICT3 - lechacal

The thing is that at 0 reading, the ADC value is 512. When I turn on the vacuum it drops .

Look at “Learn” to see how we interface to the ADC in the Atmega 328P. I would expect a “half-scale” value of 512 if the module uses a similar bias network to the emonTx - but without a circuit diagram, that is a guess.

OK Thanks.

Yes this uses 24 Ohm burden resistors below. Something ain’t right:

[0000912,0000786,0001170] raw values from attiny85

calculated values
0.24193548387096772, 0.1739247311827957, 0.2798387096774193

Which is when nothing is happening. I have 1 CT plugged in .

Full code listing http://pastebin.com/6CBxaa6M

further processing in java is as above.

Can you please post the code here, use 3 backticks ( `) before & after for formatting.

#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>
#include <stdint.h>
#include <stdlib.h>
//#define OSCCAL_TWEAK 20100000L
#include "softuart.h"
#define BUFFER_LENGTH 26
#define NUMBER_OF_SAMPLES 1024
static char buf[BUFFER_LENGTH] = {0};

void adc_init(void)
{	
	ADCSRA |= ((1<<ADPS2)|(1<<ADPS1)|(0<<ADPS0));    // 8MHz / 64 = 128 kHz clock
	ADMUX = (0 << ADLAR)  //10bit precision
	| (1 << MUX1)   //use PB4 as input pin
	| (0 << REFS0)  //set refs0 and refs1 to 0 to use Vcc as Vref
	| (0 << REFS1);
	//| (0 << REFS2)
	ADCSRA |= (1<<ADEN);                //Turn on ADC
	ADCSRA |= (1<<ADSC);                //Do an initial conversion because this one is the slowest and to ensure that everything is up and running
}

uint16_t adc_read(uint8_t channel)
{
	ADMUX &= 0xF0;                    //Clear the older channel that was read
	ADMUX |= channel;                //Defines the new ADC channel to be read
	ADCSRA |= (1<<ADSC);                //Starts a new conversion
	while(ADCSRA & (1<<ADSC));            //Wait until the conversion is done
	return ADCW;                    //Returns the ADC value of the chosen channel
}

void utoa_padded(uint32_t i, uint8_t idx)
{
	if (i < 10)
	{
		buf[idx] = '0';
		idx++;
	}
	if (i < 100)
	{
		buf[idx] = '0';
		idx++;
	}
	if (i < 1000)
	{
		buf[idx] = '0';
		idx++;
	}
	if (i < 10000)
	{
		buf[idx] = '0';
		idx++;
	}
	if (i < 100000)
	{
		buf[idx] = '0';
		idx++;
	}
	if (i < 1000000)
	{
		buf[idx] = '0';
		idx++;
	}

	ultoa(i, &buf[idx], 10);
}

int sampleI, lastSampleI;
double filteredI, lastFilteredI;
double sqI,sumI;
unsigned long resultc1;

void calc_irms(uint16_t channel)
{	
	for (uint16_t n = 0; n < NUMBER_OF_SAMPLES; n++)
	{
	  lastSampleI = sampleI;
	  sampleI = adc_read(channel);
	  lastFilteredI = filteredI;
	  filteredI = 0.996*(lastFilteredI+sampleI-lastSampleI);

	  // Root-mean-square method current
	  // 1) square current values
	  sqI = filteredI * filteredI;
	  // 2) sum
	  sumI += sqI;
	
	}
	//Reset accumulators
	resultc1 = sqrt(sumI / NUMBER_OF_SAMPLES) * 1000.0;

	sumI = 0;
}

int main (void)
{
	//OSCCAL = 0x50;
	//OSCCAL = 0x93;
	OSCCAL = 0x41;
	
	adc_init();	
	softuart_tx_init();		

	while(1)
	{			
		calc_irms(1);
		utoa_padded(resultc1, 1);
		calc_irms(2);
		utoa_padded(resultc1, 9);
		calc_irms(3);
		utoa_padded(resultc1, 17);
		buf[0] = '[';
		buf[8] = ',';
		buf[16] = ',';
		buf[BUFFER_LENGTH - 2] = ']';
		buf[BUFFER_LENGTH - 1] = '\n';

		for (uint16_t i = 0; i < BUFFER_LENGTH; i++)
			softuart_putchar(buf[i]);
	}

	return 0;
}

Edit - formatted text. BT, Moderator

I see that you are still using the high-pass filter version. I have never run a test to compare the high-pass with the later low-pass version presently being used in emonLib, but I think the latter performs somewhat better.

This is the current section lifted out of emonLib.cpp:

To begin, you must set the initial offset to the mid-scale value:

   offsetI = ADC_COUNTS>>1;

(511 or 512 for you). Then the ‘loop’ calculation:

//-----------------------------------------------------------------------------
// A) Read in raw voltage and current samples
//-----------------------------------------------------------------------------
sampleI = analogRead(inPinI);            //Read in raw current signal

//-----------------------------------------------------------------------------
// B) Apply digital low pass filters to extract the 2.5 V or 1.65 V dc offset, 
	//     then subtract this - signal is now centred on 0 counts.
//-----------------------------------------------------------------------------
offsetI += ((sampleI-offsetI)/1024);
	filteredI = sampleI - offsetI;
	
//-----------------------------------------------------------------------------
// D) Root-mean-square method current
//-----------------------------------------------------------------------------   
sumI += filteredI * filteredI;           //2) sum squared current values

When the required number of times around the loop is finished:

 double I_RATIO = ICAL *((SupplyVoltage/1000.0) / (ADC_COUNTS));
  Irms = I_RATIO * sqrt(sumI / numberOfSamples); 

You’ll see that the maths is somewhat simpler and this leads to a faster loop with more samples per cycle.

However, you have not yet answered and told us what your maximum design current is, so I cannot tell whether the numbers you are seeing are reasonable or not.

I use the 100A clamps https://www.seeedstudio.com/Non-invasive-AC-Current-Sensor-(100A-max)-p-547.html

My code as as shown does the final calculation after the data is sent. It is calculated further in java as showed in this post in the thread: Calibration issues with RPICT3 - #3 by emil

ADC_COUNTS does not exist in AVR but I suppose it’s 1023?

Yes, because you measured 512 with no input. We use ADC_COUNTS because that is automatically available, the user does not need to set it or change it.

I see. nice.

But is http://lechacal. dot com /RPICT/3CT/RPICT3_v1_4.ino fundamentally broken then? I mean as I have shown, I have used that exact code in a splitted fashion between C and Java.

It’s not “fundamentally” broken, but the method of extracting the offset with a low pass filter and then subtracting that number means that you do not have to wait for the filter to acquire the quiescent value when the loop starts, and you can have a much longer time constant, therefore you can have much lower ripple content, thus giving you more accuracy.

You can split the code - that should make no difference. If you have a variable number of samples, as we do when measuring the power by measuring current and voltage over 10 mains cycles, then it is sensible to do the final calculations where you have counted the number of samples. But if you always measure the same number of samples, you can do that anywhere.

You’re using a 100 A current transformer. We have found that, especially when you measure current, electrical noise finds its way into the analogue input - we think via the power supply and the analogue reference voltage, but that is not proven - and the rectification that happens in the software, by squaring, means that you can have a varying number out when there is no current flowing. Generally we can expect to see a few tenths of an Amp.
When we are calculating real power, voltage and current are multiplied together and then averaged, there is no rectification and the ‘noise power’ tends to be much lower.

I am finding it hard to work out what those numbers represent. Can you explain?

0.28 A is a little higher than the value we normally see.

1 Like

It’s the numbers generated by my AVR source :slight_smile: That is - averaged and filtered and multiplied by 1000 to avoid decimals and sprintf.

However they are not multiplied by the ratio from supply voltage and burden before they reach the java code.

OK. My biggest concern is the brutal difference between 2.6 amps from the consumer ampere clamp meter and my 4.0 and 4.1 even. The ampere meter does not state “true RMS” anywhere. Am I actually the correct one?

With your code I get these value on another ADC without the circuitry, but the code should be testable on fantasy values. The 1023 one is 1015-1023 .

Raw ADC: 1023 - Result: 90.393150
Raw ADC: 438 - Result: 12.997628

``void JNICALL Java_emil_CTReader_readData(JNIEnv *env, jobject obj)
{
if (!isOpen_)
{
printf(“SPI not open…\n”);
return;
}

uint8_t default_tx[] = 
{
	0x01,
	0x00,
	0x00
};

uint8_t default_rx[] =
{
	0x00,
	0x00,
	0x00
};



for (size_t chanNumber = 0; chanNumber < 2; chanNumber++)
{
	default_tx[1] = (0x08 + chanNumber) << 4;;
	uint16_t sampleI;
	double offsetI = ADC_COUNTS >> 1;
	double filteredI, sumI = 0.0;
	uint16_t numberOfSamples = 1024;

	for (size_t i = 0; i < numberOfSamples; i++)
	{
		transfer(fileDescriptor_, default_tx, default_rx, sizeof(default_tx));

		//-----------------------------------------------------------------------------
		// A) Read in raw voltage and current samples
		//-----------------------------------------------------------------------------
		sampleI = ((default_rx[1] & 0x03) << 8) | (default_rx[2] & 0xff); //Read in raw current signal

		if (i == 0)
			printf("Sample 0: %d\n", sampleI);

		//-----------------------------------------------------------------------------
		// B) Apply digital low pass filters to extract the 2.5 V or 1.65 V dc offset, 
		//     then subtract this - signal is now centred on 0 counts.
		//-----------------------------------------------------------------------------
		offsetI += ((sampleI - offsetI) / 1024);
		filteredI = sampleI - offsetI;

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

	double I_RATIO = 83.33 * (3.3 / (ADC_COUNTS));
	double endResult = I_RATIO * sqrt(sumI / numberOfSamples);

	printf("Result for chan %d: %f\n", chanNumber, endResult);
}

return;

}``

Unless you know the shape of the current wave, it is impossible to say which value is correct, or even if one is correct. If you use a resistor - for example an electric kettle or heater, a large load that does not contain a motor - for your tests, then your current wave will be much closer to a sine wave and you will be much more likely to be able to calibrate and test your system correctly. You can, and probably should, take the wire several times through the current transformer so that the actual current is multiplied. Example: If you have a 1.5 kW heater at 230 V, you will have a current of 6.522 A. If you pass the wire through the current transformer 5 times, you will have a test current equivalent to 32.61 A. That is still only 1/3 of the maximum current that your system can measure, but at that current, you should eliminate inaccuracy due to noise, movement of the centre bias voltage and things like that.

If you look at the test report for your current transformer, at the end you will see some examples of the errors that measuring a distorted wave with instruments with different responses gives.

Thanks a lot! I will try with a resistive load instead.

Also - is it really necessary with 1024 samples? Aren’t 512 enough? How did you come to this number? I am using an MCP3008 directly on the PI.

You specified 1024 samples, not me!
The ‘correct’ number is a whole number of mains cycles. You must decide how to balance two opposing factors: the more cycles and samples you have, the smaller the effect that a part-cycle has on your average; the fewer cycles you have, the smaller the effect that a change of frequency has on the number of cycles.

You know that a single mains cycle takes 20 ms ±1%, so the way to measure how many samples you get per cycle is: start a timer, run your measurement loop for (say) 1000 samples, measure the time taken. Then do the same with 2000 samples. Subtract the two times, and you have the time for 1000 samples without any overhead for starting and stopping the loop and getting the time. Then work out how many samples you need for 1, 2, 3 or however many mains cycles you want to average over. We aim for 10 (200 ms).