Power Calcs Design Pt2

A project log for Home Power Usage Monitor

Gather power data from the breaker panel and beam it to a Raspberry Pi for storage and web serving

drew-yenzerDrew Yenzer 08/07/2015 at 23:090 Comments

I'll just dive right in and show you what I've got so far.

/* Main Loop */
while(1) {
        if (_adcCounter >= NUM_SAMPLES){
            CalcPower(instData,powerData);  //tally up power data
            UpdateLcd(powerData,display_state);  //Display on LCD

This is the main loop. Currently it does the power calculations and updates the LCD. Eventually it will also transmit data over the nRF24. Outside of this loop I've set up a timer that will trigger every (60Hz*64) 3840Hz. Inside of that timer interrupt I call the GatherData() method shown below:

//Read each voltage/current pair
void GatherData(struct datapoint *dp, uint16_t midPtV, uint16_t midPtA[3]) {
    uint16_t tempV[3],tempA[3];
    for (uint8_t i=0; i < 3; i++) {//read ADCs
        MySetChanADC(VIN_ADC_CHAN);    //read voltage
        ConvertADC();    //starts ADC capture
        tempV[i] = ReadADC();
        MySetChanADC(i+1);    //read current transformer
        tempA[i] = ReadADC();
    for (uint8_t i=0; i < 3; i++) {//Scale and store data
        //scale voltage and current readings
        tempV[i] = tempV[i] >= midPtV ? tempV[i]-midPtV : midPtV-tempV[i];  //calculate difference about midpoint value
        tempV[i] *= V_SCALE_FACTOR;
        tempA[i] = tempA[i] >= midPtA[i] ? tempA[i]-midPtA[i] : midPtA[i]-tempA[i];
        tempA[i] *= A_SCALE_FACTOR;
        //accumulate values for inst power and RMS volts/amps
        dp[i].instVolts += (uint32_t)tempV[i] * tempV[i];
        dp[i].instAmps += (uint32_t)tempA[i] * tempA[i];
        dp[i].instPower += (uint32_t)tempV[i] * tempA[i];

All this function does is read in a voltage and current for each power channel, scales it, then accumulates for the RMS and power calculations. The ADC values are scaled about the alternating voltage/current midpoint, which would be the value read by the ADC when no voltage or current sensor is present. I'll determine those midpoint values by a yet unimplemented calibration routine that runs on startup. It'll average the ADC values over some power of 2 number of 60Hz cycles.

In case you're wondering why the datapoint structure uses uint32 types, it's because it needs to store 3840 squared ADC values. That adds up to a larger than uint24 value. I tried looking for a way to save that data without squaring it and without a large array, but couldn't come up with anything else.

void CalcPower(struct datapoint *dp, struct power_data *pwr){
    struct datapoint lastDp[3];

    for (uint8_t i=0; i < 3; i++) {//transfer data to temps first
        lastDp[i].instAmps = dp[i].instAmps;
        lastDp[i].instPower = dp[i].instPower;
        lastDp[i].instVolts = dp[i].instVolts;
        dp[i].instAmps = 0; //clear data to prepare for next wave
        dp[i].instPower = 0;
        dp[i].instVolts = 0;
    for (uint8_t i=0; i < 3; i++) {
        lastDp[i].instAmps *= A_SCALE_FACTOR_SQ;  //scale ADC values
        lastDp[i].instVolts *= V_SCALE_FACTOR_SQ;
        lastDp[i].instPower *= VA_SCALE_FACTOR;
        pwr[i].voltsRMS = sqrt(lastDp[i].instVolts / NUM_SAMPLES); //RMS calc
        pwr[i].ampsRMS = sqrt(lastDp[i].instAmps / NUM_SAMPLES);
        pwr[i].realPower = lastDp[i].instPower / NUM_SAMPLES;
        pwr[i].apparentPower = pwr[i].voltsRMS * pwr[i].ampsRMS;
        pwr[i].powerFactor = pwr[i].realPower / pwr[i].apparentPower;
Lastly is the actual power calculation. This is called once a second from the main loop. The first loop in this function tries to quickly transfer the instantaneous voltage/current/power data into temporary variables, then clear those accumulations before the next timer interrupt. The next loop is the meat of it. First it scales the data from ADC values to actual units (power_data structure is all floats). The scaling factors are squared since the data is squared before it's accumulated. The rest is just the standard RMS and power calculations

If you're looking at this and thinking it's going to take a lot of instructions to accomplish not only the math but the conversions between 16-bit, 32-bit, and float data types then you are right! It actually took 10 times more cycles than I allotted for in the GatherData() function. I'll get into all of that in the next post talking about simulation and optimizations. I actually discovered something pretty interesting (or infuriating depending on your point of view), which is why I rushed through this post.