Real Power Calc > Apparent Power Under Load

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;

It has to be something in the maths. We know emonLib can generate a power factor greater than one, but that’s because the phase correction algorithm introduces an amplitude change, which means that a different voltage is used for the real power calculation compared to the apparent power. You are using different maths in the two paths, so if there’s rounding error, or an automatic truncation to an integer somewhere in there, that’s something to look for.

Have you checked for overflows?

If you still can’t see anything wrong, you’ll need to extract the numbers at various stages in the calculation and manually check them. It will be easier if you can create a synthetic wave in place of measured values, for which you can calculate the values you expect at each stage. The simplest of course would be one cycle of square wave,

No overflows that I’m sure. Went through them with a fine toothed comb!

You can see that for apparent I’m doing the final calc after sqrt in one multi-divide step, which I believe is as simple/accurate as I can make it.

So I guess the inaccuracy is in the real part.

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);

Generally speaking I do divides at the end as long as it doesn’t result in overflow, which is why the /samples is done at that point.

2336 reads * 4096 * 4096 = 39172442400 max value, so I do need to switch adc.ac.power.real.wattage to be uint64_t for heavier loads.

39172442400 x 42 x 100 = 1.645 e+14 (within 64 bit), so I can shift the /ADC_POWERREAD_SAMPLES into the end divide for better accuracy.

That’s all I can think off. Will try it in an hour!

Refactored the real calc, same outcome. As soon as there’s a minor load the real wattage exceeds the apparent :frowning:

// real power
  if (accumulators.acPower > 0) {
    uint64_t power = accumulators.acPower;
    power *= (ADC_CVT_MULT*ADC_AC_VOLTAGE_MULTI*ADC_AC_VOLTAGE_RESISTOR_A*ADC_AC_CURRENT_MULTI);
    power /= (ADC_CVT_DIV*ADC_AC_CURRENT_SENSITIVITY*ADC_POWERREAD_SAMPLES);
    adc.ac.power.real.wattage = power / 10000U;
    printf("ac real %d\n", adc.ac.power.real.wattage);
  } else {
    adc.ac.power.real.wattage = 0;
  }

Got it! Yes!!

 power *= (ADC_CVT_MULT*ADC_CVT_MULT*ADC_AC_VOLTAGE_MULTI*ADC_AC_VOLTAGE_RESISTOR_A*ADC_AC_CURRENT_MULTI);
    power /= (ADC_CVT_DIV*ADC_CVT_DIV*ADC_AC_CURRENT_SENSITIVITY*ADC_POWERREAD_SAMPLES);
    

Does that mean it’s now correct (or believable) at all powers?

It does, seems to be pretty accurate. PF +/- 1.5%

1 Like