That looks very good!!! Live data also reasonable (PV + small consumers, then with toaster), all fine. Let’s see tonight.
Also I improved my spreadsheet, attached:
PhaseCal-Generic.ods (544.3 KB)
The yellow data must be entered/configured, maybe some comments might be helpful …
Updated EmonLib code (the interesting part):
void EnergyMonitor::calcVI(unsigned int crossings, unsigned int timeout)
{
#if defined emonTxV3
int SupplyVoltage=3300;
#else
int SupplyVoltage = readVcc();
#endif
unsigned int crossCount = 0; //Used to measure number of times threshold is crossed.
unsigned int numberOfSamples = 0; //This is now incremented
//-------------------------------------------------------------------------------------------------------------------------
// 1) Waits for the waveform to be close to 'zero' (mid-scale adc) part in sin curve.
//-------------------------------------------------------------------------------------------------------------------------
unsigned long start = millis(); //millis()-start makes sure it doesnt get stuck in the loop if there is an error.
while(1) //the while loop...
{
startV = analogRead(inPinV); //using the voltage waveform
if ((startV < (ADC_COUNTS*0.55)) && (startV > (ADC_COUNTS*0.45))) break; //check its within range
if ((millis()-start)>timeout) break;
}
#define NUM_SAMPLES 1000
#define LOOP_DELAY 100
#ifdef NUM_SAMPLES
printf("TGE PIN I: %d, U: %d\n", inPinI, inPinV);
printf("TGE ADC range: %d\n", ADC_COUNTS);
printf("TGE Supply: %dmV, vCal: %.2f, phaseCal: %.2f\n", SupplyVoltage, VCAL, PHASECAL);
unsigned long tgeNow = millis();
printf("TGE start %ld Timeout: %ld, now: %ld -> %ldms\n", start, timeout, tgeNow, (tgeNow - start));
printf("TGE startV %ld\n", startV);
printf("TGE start Offsets: I: %.2f U: %.2f\n", offsetI, offsetV);
struct timeval t1;
// struct timeval t2;
struct timeval tLast;
uint samplesI[NUM_SAMPLES];
uint samplesV[NUM_SAMPLES];
unsigned long samplesT[NUM_SAMPLES];
gettimeofday(&t1, NULL); // TGE
#endif // NUM_SAMPLES
uint phaseCalInt = (uint) PHASECAL; // as int type
double phaseShiftedI;
double pcSamplesI[100]; // PHASECAL cannot be more than 100
//-------------------------------------------------------------------------------------------------------------------------
// 2) Main measurement loop
//-------------------------------------------------------------------------------------------------------------------------
start = millis();
while ((crossCount < crossings) && ((millis()-start)<timeout))
{
numberOfSamples++; //Count number of times looped.
lastFilteredV = filteredV; //Used for delay/phase compensation
//-----------------------------------------------------------------------------
// A) Read in raw voltage and current samples
//-----------------------------------------------------------------------------
#ifdef NUM_SAMPLES
tLast = t1;
gettimeofday(&t1, NULL); // TGE
#endif // NUM_SAMPLES
sampleV = analogRead(inPinV); //Read in raw voltage signal
#ifdef NUM_SAMPLES
// gettimeofday(&t2, NULL); // TGE
#endif // NUM_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.
//-----------------------------------------------------------------------------
offsetV = offsetV + ((sampleV-offsetV)/1024);
filteredV = sampleV - offsetV;
offsetI = offsetI + ((sampleI-offsetI)/1024);
filteredI = sampleI - offsetI;
//-----------------------------------------------------------------------------
// C) Root-mean-square method voltage
//-----------------------------------------------------------------------------
sqV= filteredV * filteredV; //1) square voltage values
sumV += sqV; //2) sum
//-----------------------------------------------------------------------------
// D) Root-mean-square method current
//-----------------------------------------------------------------------------
sqI = filteredI * filteredI; //1) square current values
sumI += sqI; //2) sum
//-----------------------------------------------------------------------------
// E) Phase calibration
//-----------------------------------------------------------------------------
if(phaseCalInt > 2)
{
// Store 100 samples in a circular buffer
pcSamplesI[numberOfSamples % (sizeof(pcSamplesI) / sizeof(pcSamplesI[0]))] = filteredI;
// High sampling rates and/or large phase errors - use I value <PHASECAL> samples in the past
// If I is ahead of V
phaseShiftedV = filteredV;
// Which sample do we want?
uint refSample = numberOfSamples - phaseCalInt;
// Chose val depending on whether we already have that val, if so determine right index in circular buffer
phaseShiftedI = (numberOfSamples > phaseCalInt ? pcSamplesI[refSample % (sizeof(pcSamplesI) / sizeof(pcSamplesI[0]))] : 0);
}
else
{
// "Classic" phase calibration, tune V value with previous sample
phaseShiftedV = lastFilteredV + PHASECAL * (filteredV - lastFilteredV);
phaseShiftedI = filteredI;
}
//-----------------------------------------------------------------------------
// F) Instantaneous power calc
//-----------------------------------------------------------------------------
instP = phaseShiftedV * phaseShiftedI; //Instantaneous Power
sumP +=instP; //Sum
//-----------------------------------------------------------------------------
// G) Find the number of times the voltage has crossed the initial voltage
// - every 2 crosses we will have sampled 1 wavelength
// - so this method allows us to sample an integer number of half wavelengths which increases accuracy
//-----------------------------------------------------------------------------
lastVCross = checkVCross;
if (sampleV > startV) checkVCross = true;
else checkVCross = false;
if (numberOfSamples==1) lastVCross = checkVCross;
if (lastVCross != checkVCross) crossCount++;
#ifdef LOOP_DELAY
sleep_us(LOOP_DELAY);
#endif // LOOP_DELAY
#ifdef NUM_SAMPLES
if(numberOfSamples <= NUM_SAMPLES)
{
samplesI[numberOfSamples - 1] = sampleI;
samplesV[numberOfSamples - 1] = sampleV;
samplesT[numberOfSamples - 1] = tLast.tv_usec;
}
#endif // NUM_SAMPLES
}
#ifdef NUM_SAMPLES
unsigned long tgeNow2 = millis();
#endif // NUM_SAMPLES
//-------------------------------------------------------------------------------------------------------------------------
// 3) Post loop calculations
//-------------------------------------------------------------------------------------------------------------------------
//Calculation of the root of the mean of the voltage and current squared (rms)
//Calibration coefficients applied.
double V_RATIO = VCAL *((SupplyVoltage/1000.0) / (ADC_COUNTS));
Vrms = V_RATIO * sqrt(sumV / numberOfSamples);
double I_RATIO = ICAL *((SupplyVoltage/1000.0) / (ADC_COUNTS));
Irms = I_RATIO * sqrt(sumI / numberOfSamples);
#ifdef NUM_SAMPLES
printf("TGE end meas loop: %ldms NumSamples: %d (%d)\n", (tgeNow2 - tgeNow), numberOfSamples, phaseCalInt);
printf("t;ADC V;ADC I\n");
for(int i = 0; (i < NUM_SAMPLES && i < numberOfSamples); i++)
{
printf("%ld;%d;%d\n", samplesT[i], samplesV[i], samplesI[i]);
}
printf("TGE Vrms: %.2fV\n", Vrms);
printf("TGE Irms: %.2fA\n", Irms);
#endif // NUM_SAMPLES
//Calculation power values
realPower = V_RATIO * I_RATIO * sumP / (phaseCalInt > 2 ? (numberOfSamples - phaseCalInt) : numberOfSamples);
apparentPower = Vrms * Irms;
powerFactor=realPower / apparentPower;
//Reset accumulators
sumV = 0;
sumI = 0;
sumP = 0;
//--------------------------------------------------------------------------------------
}
I set phaseCal=26 (as determined above).
All to be reviewed and cleaned up, but at least to me that looks promising.
The question, where that phase error came from is still open, I will try to find out (VT + oscilloscope). The VT seems to contain a thermal fuse at least:
(Symbol under “CE”)