I’ve a NRF52 running an ADC via DMA at 8KHz (for two reads) for 0.292s on a AC line with lots of harmonic content. The AC is at around 60Hz so I’m capturing enough data for analysis.
As the voltage line is reasonably low I’m using an INA180 and LM358 in differential mode which induces very little phase. There is 2us phase between the channel read(s) which I think is small enough to not warrant correction.
I then crunch the data and arrive at apparent voltage/current values which measure correct by way of the multimeter.
However there’s a problem. At first the real power is below the apparent but as the load increases it overtakes the apparent. Check out these values (in mW)
ac real 67
ac apparent 77
ac real 68
ac apparent 77
ac real 104
ac apparent 116
ac real 100
ac apparent 115
ac real 764
ac apparent 949
ac real 1143
ac apparent 1004
ac real 1164
ac apparent 1027
ac real 1155
Any ideas what I should look at? I’ve included my crunch code. The apparent calculations are correct and you can see that I’m doing much the same in the real power calculation.
Defines
// reference voltage 4095U/(0.6*5)
#define ADC_CVT_DIV 1355ULL
#define ADC_CVT_MULT 100ULL
// resistor divider factor, factor = 23k/13K = 1.76923076923
#define ADC_DC_VOLTAGE_RESISTOR_A 177ULL
// resistor attuention 100k/2.4k = 41.6666666667
#define ADC_AC_VOLTAGE_RESISTOR_A 42ULL
#define ADC_AC_VOLTAGE_MULTI 10ULL
#define ADC_AC_CURRENT_MULTI 1000ULL
#define ADC_AC_CURRENT_SENSITIVITY 185ULL // sensistivity
#define ADC_DC_CURRENT_MULTI 10000ULL
#define ADC_DC_CURRENT_RESISTOR 82ULL // miliohm
#define ADC_DC_CURRENT_SENSITIVITY 20ULL // INA
Analysis code
struct {
uint64_t acVoltage;
uint64_t acCurrent;
int64_t acPower;
uint64_t dcVoltage;
uint64_t dcCurrent;
} accumulators = { 0, 0, 0, 0, 0 };
uint32_t value;
// remove offsets and convert
struct powerReadProto *p_buffer = &adc.buffer[0];
struct powerReadProto *p_last = &adc.buffer[ADC_POWERREAD_SAMPLES];
while (p_buffer != p_last) {
p_buffer->acVoltage -= adc.calibration.ac.voltage;
p_buffer->acCurrent -= adc.calibration.ac.current;
if (p_buffer->dcVoltage > 0) {
accumulators.dcVoltage += p_buffer->dcVoltage;
}
if (p_buffer->dcCurrent > 0) {
accumulators.dcCurrent += p_buffer->dcCurrent;
}
accumulators.acVoltage += (int32_t) p_buffer->acVoltage * p_buffer->acVoltage;
accumulators.acCurrent += (int32_t) p_buffer->acCurrent * p_buffer->acCurrent;
p_buffer->acCurrent *= -1; // ac current read is inverted
accumulators.acPower += (int32_t) p_buffer->acVoltage * p_buffer->acCurrent;
p_buffer++;
}
// real power
if (accumulators.acPower > 0) {
adc.ac.power.real.wattage = accumulators.acPower / ADC_POWERREAD_SAMPLES;
adc.ac.power.real.wattage *= ADC_AC_VOLTAGE_RESISTOR_A * 100;
adc.ac.power.real.wattage /= ADC_CVT_DIV * ADC_AC_CURRENT_SENSITIVITY;
printf("ac real %d\n", adc.ac.power.real.wattage);
} else {
adc.ac.power.real.wattage = 0;
}
// dc voltage
if (accumulators.dcVoltage) {
value = accumulators.dcVoltage / ADC_POWERREAD_SAMPLES;
value *= ADC_CVT_MULT*ADC_DC_VOLTAGE_RESISTOR_A;
adc.dc.voltage = value / (ADC_CVT_DIV*10U);
}
// dc current
if (accumulators.dcCurrent) {
value = accumulators.dcCurrent / ADC_POWERREAD_SAMPLES;
value *= (ADC_CVT_MULT * ADC_DC_CURRENT_MULTI);
value /= (ADC_CVT_DIV * ADC_DC_CURRENT_RESISTOR * ADC_DC_CURRENT_SENSITIVITY);
adc.dc.wattage = adc.dc.current = value;
// wattage
adc.dc.wattage *= adc.dc.voltage;
// efficiency
adc.dc.efficiency = adc.ac.power.real.wattage? (adc.dc.wattage / adc.ac.power.real.wattage) : 0;
// rebase
adc.dc.wattage /= 1000U;
//printf("dc %d\n", adc.dc.wattage);
} else {
adc.dc.efficiency = 0;
}
// apparent voltage
value = accumulators.acVoltage / ADC_POWERREAD_SAMPLES;
value = sqrt32(value);
value *= (ADC_CVT_MULT*ADC_AC_VOLTAGE_MULTI*ADC_AC_VOLTAGE_RESISTOR_A);
value /= ADC_CVT_DIV;
adc.ac.power.apparent.voltage = value;
// apparent current
value = accumulators.acCurrent / ADC_POWERREAD_SAMPLES;
value = sqrt32(value);
value *= ADC_CVT_MULT*ADC_AC_CURRENT_MULTI;
value /= (ADC_CVT_DIV*ADC_AC_CURRENT_SENSITIVITY);
adc.ac.power.apparent.current = value;
// apparent power
adc.ac.power.apparent.wattage = (value * adc.ac.power.apparent.voltage) / 1000U;
printf("ac apparent %d\n", adc.ac.power.apparent.wattage);
// power factor
value = adc.ac.power.real.wattage;
value *= 100U;
adc.ac.power.factor = adc.ac.power.apparent.wattage? value / adc.ac.power.apparent.wattage : 0;